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
33 backgroundLower *ebiten.Image
34 backgroundUpper *ebiten.Image
35 backgroundDirty bool
36
37 op *ebiten.DrawImageOptions
38
39 backgroundColor color.Color
40 lastBackgroundColor color.Color
41
42 shift bool
43
44 touchIDs []ebiten.TouchID
45
46 hideShortcuts []ebiten.Key
47
48 labelFont font.Face
49 }
50
51
52 func NewKeyboard() *Keyboard {
53 fontFace, err := defaultFontFace(64)
54 if err != nil {
55 log.Fatal(err)
56 }
57
58 k := &Keyboard{
59 alpha: 1.0,
60 op: &ebiten.DrawImageOptions{
61 Filter: ebiten.FilterNearest,
62 },
63 keys: KeysQWERTY,
64 backgroundLower: ebiten.NewImage(1, 1),
65 backgroundUpper: ebiten.NewImage(1, 1),
66 backgroundColor: color.Black,
67 labelFont: fontFace,
68 }
69 return k
70 }
71
72 func defaultFont() (*opentype.Font, error) {
73 return opentype.Parse(fonts.MPlus1pRegular_ttf)
74 }
75
76 func defaultFontFace(size float64) (font.Face, error) {
77 f, err := defaultFont()
78 if err != nil {
79 return nil, err
80 }
81 const dpi = 72
82 return opentype.NewFace(f, &opentype.FaceOptions{
83 Size: size,
84 DPI: dpi,
85 Hinting: font.HintingFull,
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) GetKeys() [][]*Key {
102 return k.keys
103 }
104
105
106 func (k *Keyboard) SetKeys(keys [][]*Key) {
107 k.keys = keys
108
109 k.updateKeyRects()
110 k.backgroundDirty = true
111 }
112
113
114 func (k *Keyboard) SetLabelFont(face font.Face) {
115 k.labelFont = face
116
117 k.backgroundDirty = true
118 }
119
120
121
122 func (k *Keyboard) SetHideShortcuts(shortcuts []ebiten.Key) {
123 k.hideShortcuts = shortcuts
124 }
125
126 func (k *Keyboard) updateKeyRects() {
127 if len(k.keys) == 0 {
128 return
129 }
130
131 maxCells := 0
132 for _, rowKeys := range k.keys {
133 if len(rowKeys) > maxCells {
134 maxCells = len(rowKeys)
135 }
136 }
137
138
139 cellPaddingW := 1
140 cellPaddingH := 1
141
142 cellH := (k.h - (cellPaddingH * (len(k.keys) - 1))) / len(k.keys)
143
144 row := 0
145 for _, rowKeys := range k.keys {
146 if len(rowKeys) == 0 {
147 continue
148 }
149
150 cellW := (k.w - (cellPaddingW * (len(rowKeys) - 1))) / len(rowKeys)
151
152 for i, key := range rowKeys {
153 key.x = (cellW + cellPaddingW) * i
154 key.y = (cellH + cellPaddingH) * row
155 key.w = cellW
156 key.h = cellH
157
158 if i == len(rowKeys)-1 {
159 key.w = k.w - key.x
160 }
161 }
162
163
164 row++
165 }
166 }
167
168 func (k *Keyboard) at(x, y int) *Key {
169 if !k.visible {
170 return nil
171 }
172 if x >= k.x && x <= k.x+k.w && y >= k.y && y <= k.y+k.h {
173 x, y = x-k.x, y-k.y
174 for _, rowKeys := range k.keys {
175 for _, key := range rowKeys {
176 if x >= key.x && x <= key.x+key.w && y >= key.y && y <= key.y+key.h {
177 return key
178 }
179 }
180 }
181 }
182 return nil
183 }
184
185 func (k *Keyboard) handleHideKey(inputKey ebiten.Key) bool {
186 if !k.allowUserHide {
187 return false
188 }
189
190 for _, key := range k.hideShortcuts {
191 if key == inputKey {
192 k.Hide()
193 return true
194 }
195 }
196 return false
197 }
198
199
200 func (k *Keyboard) Hit(key *Key) {
201 input := key.LowerInput
202 if k.shift {
203 input = key.UpperInput
204 }
205
206 if input.Key == ebiten.KeyShift {
207 k.shift = !k.shift
208 ebiten.ScheduleFrame()
209 return
210 } else if k.handleHideKey(input.Key) {
211
212 return
213 }
214
215 k.inputEvents = append(k.inputEvents, input)
216 }
217
218
219 func (k *Keyboard) Update() error {
220 if !k.visible {
221 return nil
222 }
223
224
225 if k.passPhysical {
226
227 k.incomingBuffer = ebiten.AppendInputChars(k.incomingBuffer[:0])
228 if len(k.incomingBuffer) > 0 {
229 for _, r := range k.incomingBuffer {
230 k.inputEvents = append(k.inputEvents, &Input{Rune: r})
231 }
232 } else {
233
234 for _, key := range allKeys {
235 if inpututil.IsKeyJustPressed(key) {
236 if k.handleHideKey(key) {
237
238 return nil
239 }
240 k.inputEvents = append(k.inputEvents, &Input{Key: key})
241 }
242 }
243 }
244 }
245
246 pressDuration := 50 * time.Millisecond
247 if inpututil.IsMouseButtonJustReleased(ebiten.MouseButtonLeft) {
248 x, y := ebiten.CursorPosition()
249
250 key := k.at(x, y)
251 if key != nil {
252 for _, rowKeys := range k.keys {
253 for _, rowKey := range rowKeys {
254 rowKey.pressed = false
255 }
256 }
257 key.pressed = true
258
259 k.Hit(key)
260
261
262 go func() {
263 time.Sleep(pressDuration)
264
265 key.pressed = false
266 ebiten.ScheduleFrame()
267 }()
268 }
269 } else if ebiten.IsMouseButtonPressed(ebiten.MouseButtonLeft) {
270 x, y := ebiten.CursorPosition()
271
272 key := k.at(x, y)
273 if key != nil {
274 key.pressed = true
275
276 for _, rowKeys := range k.keys {
277 for _, rowKey := range rowKeys {
278 if rowKey == key || !rowKey.pressed {
279 continue
280 }
281 rowKey.pressed = false
282 }
283 }
284 }
285 }
286
287 k.touchIDs = inpututil.AppendJustPressedTouchIDs(k.touchIDs[:0])
288 for _, id := range k.touchIDs {
289 x, y := ebiten.TouchPosition(id)
290
291 key := k.at(x, y)
292 if key != nil && !key.pressed {
293 key.pressed = true
294 key.pressedTouchID = id
295
296 for _, rowKeys := range k.keys {
297 for _, rowKey := range rowKeys {
298 if rowKey != key && rowKey.pressed {
299 rowKey.pressed = false
300 }
301 }
302 }
303
304 k.Hit(key)
305
306 go func() {
307 var touchIDs []ebiten.TouchID
308 t := time.NewTicker(pressDuration)
309 for range t.C {
310 touchIDs = ebiten.AppendTouchIDs(touchIDs[:0])
311
312 var found bool
313 for _, touchID := range touchIDs {
314 if id == touchID {
315 found = true
316 break
317 }
318 }
319
320 if found {
321 tx, ty := ebiten.TouchPosition(id)
322 if tx != 0 || ty != 0 {
323 x, y = tx, ty
324 }
325 }
326
327 if !found {
328 key.pressed = false
329 ebiten.ScheduleFrame()
330 return
331 }
332 }
333 }()
334 }
335 }
336 return nil
337 }
338
339 func (k *Keyboard) offset(x, y int) (int, int) {
340 return x + k.x, y + k.y
341 }
342
343 func (k *Keyboard) drawBackground() {
344 if k.w == 0 || k.h == 0 {
345 return
346 }
347
348 if k.backgroundLower.Bounds() != image.Rect(0, 0, k.w, k.h) || k.backgroundUpper.Bounds() != image.Rect(0, 0, k.w, k.h) || k.backgroundColor != k.lastBackgroundColor {
349 k.backgroundLower = ebiten.NewImage(k.w, k.h)
350 k.backgroundLower.Fill(k.backgroundColor)
351
352 k.backgroundUpper = ebiten.NewImage(k.w, k.h)
353 k.backgroundUpper.Fill(k.backgroundColor)
354
355 k.lastBackgroundColor = k.backgroundColor
356 }
357
358 var img *ebiten.Image
359 for i := 0; i < 2; i++ {
360 shift := i == 1
361 for _, rowKeys := range k.keys {
362 for _, key := range rowKeys {
363 if img == nil {
364 img = ebiten.NewImage(key.w, key.h)
365 } else {
366 bounds := img.Bounds()
367 if bounds.Dx() != key.w || bounds.Dy() != key.h {
368 img = ebiten.NewImage(key.w, key.h)
369 }
370 }
371
372
373
374 img.Fill(color.RGBA{90, 90, 90, 255})
375
376
377 label := key.LowerLabel
378 if shift {
379 label = key.UpperLabel
380 }
381
382 bounds := text.BoundString(k.labelFont, label)
383 x := (key.w - bounds.Dx()) / 2
384 if x < 0 {
385 x = 0
386 }
387 y := key.h / 2
388 text.Draw(img, label, k.labelFont, x, y, color.White)
389
390
391 lightShade := color.RGBA{150, 150, 150, 255}
392 darkShade := color.RGBA{30, 30, 30, 255}
393 for j := 0; j < key.w; j++ {
394 img.Set(j, 0, lightShade)
395 img.Set(j, key.h-1, darkShade)
396 }
397 for j := 0; j < key.h; j++ {
398 img.Set(0, j, lightShade)
399 img.Set(key.w-1, j, darkShade)
400 }
401
402
403 k.op.GeoM.Reset()
404 k.op.GeoM.Translate(float64(key.x), float64(key.y))
405
406 if !shift {
407 k.backgroundLower.DrawImage(img, k.op)
408 } else {
409 k.backgroundUpper.DrawImage(img, k.op)
410 }
411 k.op.ColorM.Reset()
412 }
413 }
414 }
415 }
416
417
418 func (k *Keyboard) Draw(target *ebiten.Image) {
419 if !k.visible {
420 return
421 }
422
423 if k.backgroundDirty {
424 k.drawBackground()
425 k.backgroundDirty = false
426 }
427
428 var background *ebiten.Image
429 if !k.shift {
430 background = k.backgroundLower
431 } else {
432 background = k.backgroundUpper
433 }
434
435 k.op.GeoM.Reset()
436 k.op.GeoM.Translate(float64(k.x), float64(k.y))
437 k.op.ColorM.Scale(1, 1, 1, k.alpha)
438 target.DrawImage(background, k.op)
439 k.op.ColorM.Reset()
440
441
442 for _, rowKeys := range k.keys {
443 for _, key := range rowKeys {
444 if !key.pressed {
445 continue
446 }
447
448
449 k.op.GeoM.Reset()
450 k.op.GeoM.Translate(float64(k.x+key.x), float64(k.y+key.y))
451 k.op.ColorM.Scale(0.75, 0.75, 0.75, k.alpha)
452
453 target.DrawImage(background.SubImage(image.Rect(key.x, key.y, key.x+key.w, key.y+key.h)).(*ebiten.Image), k.op)
454 k.op.ColorM.Reset()
455
456
457 darkShade := color.RGBA{60, 60, 60, 255}
458 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)
459 subImg.Fill(darkShade)
460 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)
461 subImg.Fill(darkShade)
462 }
463 }
464 }
465
466
467
468 func (k *Keyboard) SetAllowUserHide(allow bool) {
469 k.allowUserHide = allow
470 }
471
472
473
474
475 func (k *Keyboard) SetPassThroughPhysicalInput(pass bool) {
476 k.passPhysical = pass
477 }
478
479
480 func (k *Keyboard) SetAlpha(alpha float64) {
481 k.alpha = alpha
482 }
483
484
485 func (k *Keyboard) Show() {
486 k.visible = true
487 }
488
489
490 func (k *Keyboard) Visible() bool {
491 return k.visible
492 }
493
494
495 func (k *Keyboard) Hide() {
496 k.visible = false
497 }
498
499
500 func (k *Keyboard) AppendInput(events []*Input) []*Input {
501 events = append(events, k.inputEvents...)
502 k.inputEvents = nil
503 return events
504 }
505
View as plain text