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