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