1 package kibodo
2
3 import (
4 "image"
5 "image/color"
6 "time"
7
8 "github.com/hajimehoshi/ebiten/v2"
9 "github.com/hajimehoshi/ebiten/v2/inpututil"
10 "github.com/hajimehoshi/ebiten/v2/text/v2"
11 )
12
13
14 type Keyboard struct {
15 x, y int
16 w, h int
17
18 visible bool
19 alpha float64
20 passPhysical bool
21
22 incomingBuffer []rune
23
24 inputEvents []*Input
25
26 keys [][]*Key
27 normalKeys [][]*Key
28 extendedKeys [][]*Key
29 showExtended bool
30
31 backgroundLower *ebiten.Image
32 backgroundUpper *ebiten.Image
33 backgroundDirty bool
34
35 op *ebiten.DrawImageOptions
36
37 backgroundColor color.RGBA
38 lastBackgroundColor color.RGBA
39
40 shift bool
41
42 touchIDs []ebiten.TouchID
43 holdTouchID ebiten.TouchID
44 holdKey *Key
45 wasPressed bool
46
47 hideShortcuts []ebiten.Key
48
49 labelFont *text.GoTextFace
50 lineHeight int
51 lineOffset int
52
53 backspaceDelay time.Duration
54 backspaceRepeat time.Duration
55 backspaceLast time.Time
56
57 scheduleFrameFunc func()
58 }
59
60
61 func NewKeyboard(f *text.GoTextFaceSource) *Keyboard {
62 k := &Keyboard{
63 alpha: 1.0,
64 op: &ebiten.DrawImageOptions{
65 Filter: ebiten.FilterNearest,
66 },
67 keys: KeysQWERTY,
68 normalKeys: KeysQWERTY,
69 backgroundLower: ebiten.NewImage(1, 1),
70 backgroundUpper: ebiten.NewImage(1, 1),
71 backgroundColor: color.RGBA{0, 0, 0, 255},
72 holdTouchID: -1,
73 hideShortcuts: []ebiten.Key{ebiten.KeyEscape},
74 labelFont: fontFace(f, 64),
75 backspaceDelay: 500 * time.Millisecond,
76 backspaceRepeat: 75 * time.Millisecond,
77 }
78 k.fontUpdated()
79 return k
80 }
81
82 func fontFace(source *text.GoTextFaceSource, size float64) *text.GoTextFace {
83 return &text.GoTextFace{
84 Source: source,
85 Size: size,
86 }
87 }
88
89
90 func (k *Keyboard) SetRect(x, y, w, h int) {
91 if k.x == x && k.y == y && k.w == w && k.h == h {
92 return
93 }
94 k.x, k.y, k.w, k.h = x, y, w, h
95
96 k.updateKeyRects()
97 k.backgroundDirty = true
98 }
99
100
101 func (k *Keyboard) Rect() image.Rectangle {
102 return image.Rect(k.x, k.y, k.x+k.w, k.y+k.h)
103 }
104
105
106 func (k *Keyboard) GetKeys() [][]*Key {
107 return k.keys
108 }
109
110
111 func (k *Keyboard) SetKeys(keys [][]*Key) {
112 k.normalKeys = keys
113
114 if !k.showExtended && !keysEqual(keys, k.keys) {
115 k.keys = keys
116 k.updateKeyRects()
117 k.backgroundDirty = true
118 }
119 }
120
121
122 func (k *Keyboard) SetExtendedKeys(keys [][]*Key) {
123 k.extendedKeys = keys
124
125 if k.showExtended && !keysEqual(keys, k.keys) {
126 k.keys = keys
127 k.updateKeyRects()
128 k.backgroundDirty = true
129 }
130 }
131
132
133 func (k *Keyboard) SetShowExtended(show bool) {
134 if k.showExtended == show {
135 return
136 }
137 k.showExtended = show
138 if k.showExtended {
139 k.keys = k.extendedKeys
140 } else {
141 k.keys = k.normalKeys
142 }
143 k.updateKeyRects()
144 k.backgroundDirty = true
145 }
146
147
148 func (k *Keyboard) SetLabelFont(face *text.GoTextFace) {
149 k.labelFont = face
150 k.fontUpdated()
151
152 k.backgroundDirty = true
153 }
154
155 func (k *Keyboard) fontUpdated() {
156 m := k.labelFont.Metrics()
157 k.lineHeight = int(m.HAscent + m.HDescent)
158 k.lineOffset = int(m.CapHeight)
159 if k.lineOffset < 0 {
160 k.lineOffset *= -1
161 }
162 }
163
164
165
166 func (k *Keyboard) SetHideShortcuts(shortcuts []ebiten.Key) {
167 k.hideShortcuts = shortcuts
168 }
169
170 func (k *Keyboard) updateKeyRects() {
171 if len(k.keys) == 0 {
172 return
173 }
174
175 maxCells := 0
176 for _, rowKeys := range k.keys {
177 if len(rowKeys) > maxCells {
178 maxCells = len(rowKeys)
179 }
180 }
181
182
183 cellPaddingW := 1
184 cellPaddingH := 1
185
186 cellH := (k.h - (cellPaddingH * (len(k.keys) - 1))) / len(k.keys)
187
188 row := 0
189 x, y := 0, 0
190 for _, rowKeys := range k.keys {
191 if len(rowKeys) == 0 {
192 continue
193 }
194
195 availableWidth := k.w
196 for _, key := range rowKeys {
197 if key.Wide {
198 availableWidth = availableWidth / 2
199 break
200 }
201 }
202
203 cellW := (availableWidth - (cellPaddingW * (len(rowKeys) - 1))) / len(rowKeys)
204
205 x = 0
206 for i, key := range rowKeys {
207 key.w, key.h = cellW, cellH
208 key.x, key.y = x, y
209
210 if i == len(rowKeys)-1 {
211 key.w = k.w - key.x
212 }
213
214 if key.Wide {
215 key.w = k.w - k.w/2 + (cellW)
216 }
217
218 x += key.w
219 }
220
221
222 row++
223 y += (cellH + cellPaddingH)
224 }
225 }
226
227 func (k *Keyboard) at(x, y int) *Key {
228 if !k.visible {
229 return nil
230 }
231 if x >= k.x && x <= k.x+k.w && y >= k.y && y <= k.y+k.h {
232 x, y = x-k.x, y-k.y
233 for _, rowKeys := range k.keys {
234 for _, key := range rowKeys {
235 if x >= key.x && x <= key.x+key.w && y >= key.y && y <= key.y+key.h {
236 return key
237 }
238 }
239 }
240 }
241 return nil
242 }
243
244
245 func (k *Keyboard) KeyAt(x, y int) *Key {
246 return k.at(x, y)
247 }
248
249 func (k *Keyboard) handleToggleExtendedKey(inputKey ebiten.Key) bool {
250 if inputKey != KeyToggleExtended {
251 return false
252 }
253 k.showExtended = !k.showExtended
254 if k.showExtended {
255 k.keys = k.extendedKeys
256 } else {
257 k.keys = k.normalKeys
258 }
259 k.updateKeyRects()
260 k.backgroundDirty = true
261 return true
262 }
263
264 func (k *Keyboard) handleHideKey(inputKey ebiten.Key) bool {
265 for _, key := range k.hideShortcuts {
266 if key == inputKey {
267 k.Hide()
268 return true
269 }
270 }
271 return false
272 }
273
274
275 func (k *Keyboard) Hit(key *Key) {
276 now := time.Now()
277 if !key.pressedTime.IsZero() && now.Sub(key.pressedTime) < 50*time.Millisecond {
278 return
279 }
280 key.pressedTime = now
281 key.repeatTime = now.Add(k.backspaceDelay)
282
283 input := key.LowerInput
284 if k.shift {
285 input = key.UpperInput
286 }
287
288 if input.Key == ebiten.KeyShift {
289 k.shift = !k.shift
290 if k.scheduleFrameFunc != nil {
291 k.scheduleFrameFunc()
292 }
293 return
294 } else if k.handleToggleExtendedKey(input.Key) || k.handleHideKey(input.Key) {
295 return
296 }
297
298 k.inputEvents = append(k.inputEvents, input)
299 }
300
301
302 func (k *Keyboard) HandleMouse(cursor image.Point, pressed bool, clicked bool) (handled bool, err error) {
303 if k.backgroundDirty {
304 k.drawBackground()
305 k.backgroundDirty = false
306 }
307
308
309 if clicked {
310 var key *Key
311 if cursor.X != 0 || cursor.Y != 0 {
312 key = k.at(cursor.X, cursor.Y)
313 }
314 for _, rowKeys := range k.keys {
315 for _, rowKey := range rowKeys {
316 if key != nil && rowKey == key {
317 continue
318 }
319 rowKey.pressed = false
320 }
321 }
322 if key != nil {
323 key.pressed = true
324
325 k.Hit(key)
326 }
327 k.wasPressed = true
328 } else if pressed {
329 key := k.at(cursor.X, cursor.Y)
330 if key != nil {
331
332 input := key.LowerInput
333 if k.shift {
334 input = key.UpperInput
335 }
336 if input.Key == ebiten.KeyBackspace || input.Key == ebiten.KeyDelete {
337 for time.Since(key.repeatTime) >= k.backspaceRepeat {
338 k.inputEvents = append(k.inputEvents, &Input{Key: input.Key})
339 key.repeatTime = key.repeatTime.Add(k.backspaceRepeat)
340 }
341 }
342
343 key.pressed = true
344 k.wasPressed = true
345
346 for _, rowKeys := range k.keys {
347 for _, rowKey := range rowKeys {
348 if rowKey == key || !rowKey.pressed {
349 continue
350 }
351 rowKey.pressed = false
352 }
353 }
354 }
355 } else if k.wasPressed {
356 for _, rowKeys := range k.keys {
357 for _, rowKey := range rowKeys {
358 if !rowKey.pressed {
359 continue
360 }
361 rowKey.pressed = false
362 }
363 }
364 }
365 return true, nil
366 }
367
368
369 func (k *Keyboard) Update() error {
370 if !k.visible {
371 return nil
372 }
373
374 if k.backgroundDirty {
375 k.drawBackground()
376 k.backgroundDirty = false
377 }
378
379
380 if k.passPhysical {
381
382 k.incomingBuffer = ebiten.AppendInputChars(k.incomingBuffer[:0])
383 if len(k.incomingBuffer) > 0 {
384 for _, r := range k.incomingBuffer {
385 k.inputEvents = append(k.inputEvents, &Input{Rune: r})
386 }
387 } else {
388
389 for _, key := range allKeys {
390 if inpututil.IsKeyJustPressed(key) {
391 if k.handleHideKey(key) {
392
393 return nil
394 }
395 k.inputEvents = append(k.inputEvents, &Input{Key: key})
396 }
397 }
398 }
399 }
400
401
402 var key *Key
403 pressDuration := 50 * time.Millisecond
404 if inpututil.IsMouseButtonJustPressed(ebiten.MouseButtonLeft) {
405 x, y := ebiten.CursorPosition()
406
407 key = k.at(x, y)
408 if key != nil {
409 for _, rowKeys := range k.keys {
410 for _, rowKey := range rowKeys {
411 rowKey.pressed = false
412 }
413 }
414 key.pressed = true
415
416 k.Hit(key)
417
418 go func() {
419 time.Sleep(pressDuration)
420
421 key.pressed = false
422 if k.scheduleFrameFunc != nil {
423 k.scheduleFrameFunc()
424 }
425 }()
426 }
427
428 k.wasPressed = true
429 } else if ebiten.IsMouseButtonPressed(ebiten.MouseButtonLeft) {
430 x, y := ebiten.CursorPosition()
431
432 key = k.at(x, y)
433 if key != nil {
434 if !key.pressed {
435 input := key.LowerInput
436 if k.shift {
437 input = key.UpperInput
438 }
439
440
441 if input.Key == ebiten.KeyBackspace || input.Key == ebiten.KeyDelete {
442 for time.Since(key.repeatTime) >= k.backspaceRepeat {
443 k.inputEvents = append(k.inputEvents, &Input{Key: input.Key})
444 key.repeatTime = key.repeatTime.Add(k.backspaceRepeat)
445 }
446 }
447 }
448 key.pressed = true
449
450 for _, rowKeys := range k.keys {
451 for _, rowKey := range rowKeys {
452 if rowKey == key || !rowKey.pressed {
453 continue
454 }
455 rowKey.pressed = false
456 }
457 }
458 }
459
460 k.wasPressed = true
461 }
462
463
464 if k.holdTouchID != -1 {
465 x, y := ebiten.TouchPosition(k.holdTouchID)
466 if x == 0 && y == 0 {
467 k.holdTouchID = -1
468 } else {
469 key = k.at(x, y)
470 if key != k.holdKey {
471 k.holdTouchID = -1
472 return nil
473 }
474
475 k.holdKey = key
476 k.wasPressed = true
477 }
478 }
479 if k.holdTouchID == -1 {
480 k.touchIDs = inpututil.AppendJustPressedTouchIDs(k.touchIDs[:0])
481 for _, id := range k.touchIDs {
482 x, y := ebiten.TouchPosition(id)
483
484 key = k.at(x, y)
485 if key != nil {
486 input := key.LowerInput
487 if k.shift {
488 input = key.UpperInput
489 }
490
491 if !key.pressed {
492 key.pressed = true
493 key.pressedTouchID = id
494
495 for _, rowKeys := range k.keys {
496 for _, rowKey := range rowKeys {
497 if rowKey != key && rowKey.pressed {
498 rowKey.pressed = false
499 }
500 }
501 }
502
503 k.Hit(key)
504 k.holdTouchID = id
505 k.holdKey = key
506
507
508 if input.Key == ebiten.KeyBackspace || input.Key == ebiten.KeyDelete {
509 k.backspaceLast = time.Now().Add(k.backspaceDelay)
510 }
511 } else {
512
513 if input.Key == ebiten.KeyBackspace || input.Key == ebiten.KeyDelete {
514 for time.Since(k.backspaceLast) >= k.backspaceRepeat {
515 k.inputEvents = append(k.inputEvents, &Input{Key: input.Key})
516 k.backspaceLast = k.backspaceLast.Add(k.backspaceRepeat)
517 }
518 }
519 }
520 k.wasPressed = true
521 }
522 }
523 }
524 for _, rowKeys := range k.keys {
525 for _, rowKey := range rowKeys {
526 if rowKey == key || !rowKey.pressed {
527 continue
528 }
529 rowKey.pressed = false
530 }
531 }
532 return nil
533 }
534
535 func (k *Keyboard) drawBackground() {
536 if k.w == 0 || k.h == 0 {
537 return
538 }
539
540 if !k.backgroundLower.Bounds().Eq(image.Rect(0, 0, k.w, k.h)) || !k.backgroundUpper.Bounds().Eq(image.Rect(0, 0, k.w, k.h)) || k.backgroundColor != k.lastBackgroundColor {
541 k.backgroundLower = ebiten.NewImage(k.w, k.h)
542 k.backgroundUpper = ebiten.NewImage(k.w, k.h)
543 k.lastBackgroundColor = k.backgroundColor
544 }
545 k.backgroundLower.Fill(k.backgroundColor)
546 k.backgroundUpper.Fill(k.backgroundColor)
547
548 halfLineHeight := k.lineHeight / 2
549
550 lightShade := color.RGBA{150, 150, 150, 255}
551 darkShade := color.RGBA{30, 30, 30, 255}
552
553 var keyImage *ebiten.Image
554 for i := 0; i < 2; i++ {
555 shift := i == 1
556 img := k.backgroundLower
557 if shift {
558 img = k.backgroundUpper
559 }
560 for _, rowKeys := range k.keys {
561 for _, key := range rowKeys {
562 r := image.Rect(key.x, key.y, key.x+key.w, key.y+key.h)
563 keyImage = img.SubImage(r).(*ebiten.Image)
564
565
566
567 keyImage.Fill(color.RGBA{90, 90, 90, 255})
568
569
570 label := key.LowerLabel
571 if shift {
572 label = key.UpperLabel
573 }
574
575 boundsW, boundsY := text.Measure(label, k.labelFont, float64(k.lineHeight))
576 x := key.w/2 - int(boundsW)/2
577 if x < 0 {
578 x = 0
579 }
580 y := key.h/2 - int(boundsY)/2
581 _ = halfLineHeight
582 op := &text.DrawOptions{}
583 op.GeoM.Translate(float64(key.x+x), float64(key.y+y))
584 op.ColorScale.ScaleWithColor(color.White)
585 text.Draw(keyImage, label, k.labelFont, op)
586
587
588 keyImage.SubImage(image.Rect(key.x, key.y, key.x+key.w, key.y+1)).(*ebiten.Image).Fill(lightShade)
589 keyImage.SubImage(image.Rect(key.x, key.y, key.x+1, key.y+key.h)).(*ebiten.Image).Fill(lightShade)
590 keyImage.SubImage(image.Rect(key.x, key.y+key.h-1, key.x+key.w, key.y+key.h)).(*ebiten.Image).Fill(darkShade)
591 keyImage.SubImage(image.Rect(key.x+key.w-1, key.y, key.x+key.w, key.y+key.h)).(*ebiten.Image).Fill(darkShade)
592 }
593 }
594 }
595 }
596
597
598 func (k *Keyboard) Draw(target *ebiten.Image) {
599 if !k.visible {
600 return
601 }
602
603 if k.backgroundDirty {
604 k.drawBackground()
605 k.backgroundDirty = false
606 }
607
608 var background *ebiten.Image
609 if !k.shift {
610 background = k.backgroundLower
611 } else {
612 background = k.backgroundUpper
613 }
614
615 k.op.GeoM.Reset()
616 k.op.GeoM.Translate(float64(k.x), float64(k.y))
617 k.op.ColorM.Scale(1, 1, 1, k.alpha)
618 target.DrawImage(background, k.op)
619 k.op.ColorM.Reset()
620
621
622 for _, rowKeys := range k.keys {
623 for _, key := range rowKeys {
624 if !key.pressed {
625 continue
626 }
627
628
629 k.op.GeoM.Reset()
630 k.op.GeoM.Translate(float64(k.x+key.x), float64(k.y+key.y))
631 k.op.ColorM.Scale(0.75, 0.75, 0.75, k.alpha)
632
633 target.DrawImage(background.SubImage(image.Rect(key.x, key.y, key.x+key.w, key.y+key.h)).(*ebiten.Image), k.op)
634 k.op.ColorM.Reset()
635
636
637 darkShade := color.RGBA{60, 60, 60, 255}
638 subImg := target.SubImage(image.Rect(k.x+key.x, k.y+key.y, k.x+key.x+key.w, k.y+key.y+1)).(*ebiten.Image)
639 subImg.Fill(darkShade)
640 subImg = target.SubImage(image.Rect(k.x+key.x, k.y+key.y, k.x+key.x+1, k.y+key.y+key.h)).(*ebiten.Image)
641 subImg.Fill(darkShade)
642 }
643 }
644 }
645
646
647
648
649 func (k *Keyboard) SetPassThroughPhysicalInput(pass bool) {
650 k.passPhysical = pass
651 }
652
653
654 func (k *Keyboard) SetAlpha(alpha float64) {
655 k.alpha = alpha
656 }
657
658
659 func (k *Keyboard) Show() {
660 k.visible = true
661 }
662
663
664 func (k *Keyboard) Visible() bool {
665 return k.visible
666 }
667
668
669 func (k *Keyboard) Hide() {
670 k.visible = false
671 if k.showExtended {
672 k.showExtended = false
673 k.keys = k.normalKeys
674 k.updateKeyRects()
675 k.backgroundDirty = true
676 }
677 }
678
679
680 func (k *Keyboard) AppendInput(events []*Input) []*Input {
681 events = append(events, k.inputEvents...)
682 k.inputEvents = nil
683 return events
684 }
685
686
687 func (k *Keyboard) SetScheduleFrameFunc(f func()) {
688 k.scheduleFrameFunc = f
689 }
690
691 func keysEqual(a [][]*Key, b [][]*Key) bool {
692 if len(a) != len(b) {
693 return false
694 }
695 for i := range a {
696 if len(a[i]) != len(b[i]) {
697 return false
698 }
699 for j := range b[i] {
700 if a[i][j] != b[i][j] {
701 return false
702 }
703 }
704 }
705 return true
706 }
707
View as plain text