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