Move kibodo into etk
This commit is contained in:
parent
fe71e0974a
commit
d4e4ef831f
15 changed files with 2066 additions and 10 deletions
3
.gitignore
vendored
Normal file
3
.gitignore
vendored
Normal file
|
@ -0,0 +1,3 @@
|
|||
.idea
|
||||
*.sh
|
||||
*.wasm
|
57
flex.go
57
flex.go
|
@ -6,12 +6,13 @@ import (
|
|||
"github.com/hajimehoshi/ebiten/v2"
|
||||
)
|
||||
|
||||
// Flex is a flexible stack-based layout. Each Flex widget may be oriented
|
||||
// horizontally or vertically.
|
||||
// Flex is a flexbox layou which may be oriented horizontally or vertically.
|
||||
type Flex struct {
|
||||
*Box
|
||||
|
||||
vertical bool
|
||||
vertical bool
|
||||
childWidth, childHeight int
|
||||
columnGap, rowGap int
|
||||
modified bool
|
||||
}
|
||||
|
||||
// NewFlex returns a new Flex widget.
|
||||
|
@ -27,7 +28,33 @@ func (f *Flex) SetRect(r image.Rectangle) {
|
|||
defer f.Unlock()
|
||||
|
||||
f.Box.rect = r
|
||||
f.reposition()
|
||||
f.modified = true
|
||||
}
|
||||
|
||||
// SetGapSize sets the gap between child in the Flex.
|
||||
func (f *Flex) SetGapSize(columnGap int, rowGap int) {
|
||||
f.Lock()
|
||||
defer f.Unlock()
|
||||
|
||||
if f.columnGap == columnGap && f.rowGap == rowGap {
|
||||
return
|
||||
}
|
||||
|
||||
f.columnGap, f.rowGap = columnGap, rowGap
|
||||
f.modified = true
|
||||
}
|
||||
|
||||
// SetChildSize sets the size of each child in the Flex.
|
||||
func (f *Flex) SetChildSize(width int, height int) {
|
||||
f.Lock()
|
||||
defer f.Unlock()
|
||||
|
||||
if f.childWidth == width && f.childHeight == height {
|
||||
return
|
||||
}
|
||||
|
||||
f.childWidth, f.childHeight = width, height
|
||||
f.modified = true
|
||||
}
|
||||
|
||||
// SetVertical sets the orientation of the child widget stacking.
|
||||
|
@ -40,7 +67,16 @@ func (f *Flex) SetVertical(v bool) {
|
|||
}
|
||||
|
||||
f.vertical = v
|
||||
f.reposition()
|
||||
f.modified = true
|
||||
}
|
||||
|
||||
// AddChild adds a child to the widget.
|
||||
func (f *Flex) AddChild(w ...Widget) {
|
||||
f.Lock()
|
||||
defer f.Unlock()
|
||||
|
||||
f.children = append(f.children, w...)
|
||||
f.modified = true
|
||||
}
|
||||
|
||||
// HandleKeyboard is called when a keyboard event occurs.
|
||||
|
@ -58,6 +94,11 @@ func (f *Flex) Draw(screen *ebiten.Image) error {
|
|||
f.Lock()
|
||||
defer f.Unlock()
|
||||
|
||||
if f.modified {
|
||||
f.reposition()
|
||||
f.modified = false
|
||||
}
|
||||
|
||||
for _, child := range f.children {
|
||||
err := child.Draw(screen)
|
||||
if err != nil {
|
||||
|
@ -72,6 +113,10 @@ func (f *Flex) reposition() {
|
|||
l := len(f.children)
|
||||
r := f.rect
|
||||
|
||||
// flexbox
|
||||
// gap
|
||||
// orientation
|
||||
|
||||
if f.vertical {
|
||||
childHeight := float64(r.Dy()) / float64(l)
|
||||
|
||||
|
|
1
go.mod
1
go.mod
|
@ -3,7 +3,6 @@ module code.rocket9labs.com/tslocum/etk
|
|||
go 1.18
|
||||
|
||||
require (
|
||||
code.rocketnine.space/tslocum/kibodo v1.0.3-0.20240110043547-31f31eb07497
|
||||
code.rocketnine.space/tslocum/messeji v1.0.6-0.20240109205105-4ffeffdd2441
|
||||
github.com/hajimehoshi/ebiten/v2 v2.6.3
|
||||
github.com/llgcode/draw2d v0.0.0-20231212091825-f55e0c776b44
|
||||
|
|
2
go.sum
2
go.sum
|
@ -1,5 +1,3 @@
|
|||
code.rocketnine.space/tslocum/kibodo v1.0.3-0.20240110043547-31f31eb07497 h1:QpzLvcDV7DsaeFKrQZcHkDfq1PqsHcwUVnRXRKBAxe0=
|
||||
code.rocketnine.space/tslocum/kibodo v1.0.3-0.20240110043547-31f31eb07497/go.mod h1:U9KpaLHr7nPZ8XfRUI81uAQMrxob+bI3C/IXzeXe7gw=
|
||||
code.rocketnine.space/tslocum/messeji v1.0.6-0.20240109205105-4ffeffdd2441 h1:pe5QsaN6Tvil0Y+jodbMLfdMWaL5MBb2vyorBIfQwqk=
|
||||
code.rocketnine.space/tslocum/messeji v1.0.6-0.20240109205105-4ffeffdd2441/go.mod h1:cznUGfvC7BKbc5sx4I36XpLsF0ar3TPJYZlrND0IlDQ=
|
||||
github.com/ebitengine/purego v0.5.1 h1:hNunhThpOf1vzKl49v6YxIsXLhl92vbBEv1/2Ez3ZrY=
|
||||
|
|
|
@ -3,7 +3,7 @@ package etk
|
|||
import (
|
||||
"image"
|
||||
|
||||
"code.rocketnine.space/tslocum/kibodo"
|
||||
"code.rocket9labs.com/tslocum/etk/kibodo"
|
||||
"github.com/hajimehoshi/ebiten/v2"
|
||||
)
|
||||
|
||||
|
|
22
kibodo/README.md
Normal file
22
kibodo/README.md
Normal file
|
@ -0,0 +1,22 @@
|
|||
# kibodo
|
||||
[](https://docs.rocketnine.space/code.rocketnine.space/tslocum/etk/kibodo)
|
||||
[](https://liberapay.com/rocketnine.space)
|
||||
[](https://www.patreon.com/rocketnine)
|
||||
|
||||
On-screen keyboard widget for [Ebitengine](https://github.com/hajimehoshi/ebiten)
|
||||
|
||||
## Demo
|
||||
|
||||
[**Try kibodo**](https://kibodo.rocketnine.space)
|
||||
|
||||
## Documentation
|
||||
|
||||
Documentation is available via [godoc](https://docs.rocketnine.space/code.rocketnine.space/tslocum/etk/kibodo).
|
||||
|
||||
## Support
|
||||
|
||||
Please share issues and suggestions [here](https://code.rocketnine.space/tslocum/etk/issues).
|
||||
|
||||
## Screenshot
|
||||
|
||||
[](https://code.rocketnine.space/tslocum/etk/src/branch/main/etk/screenshot.png)
|
4
kibodo/doc.go
Normal file
4
kibodo/doc.go
Normal file
|
@ -0,0 +1,4 @@
|
|||
/*
|
||||
Package kibodo provides an on-screen keyboard widget for Ebitengine.
|
||||
*/
|
||||
package kibodo
|
120
kibodo/examples/kibodo/game/game.go
Normal file
120
kibodo/examples/kibodo/game/game.go
Normal file
|
@ -0,0 +1,120 @@
|
|||
//go:build example
|
||||
|
||||
package game
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"code.rocket9labs.com/tslocum/etk/kibodo"
|
||||
"github.com/hajimehoshi/ebiten/v2"
|
||||
"github.com/hajimehoshi/ebiten/v2/ebitenutil"
|
||||
"github.com/hajimehoshi/ebiten/v2/inpututil"
|
||||
)
|
||||
|
||||
type game struct {
|
||||
w, h int
|
||||
|
||||
k *kibodo.Keyboard
|
||||
|
||||
userInput []byte
|
||||
|
||||
incomingInput []*kibodo.Input
|
||||
|
||||
op *ebiten.DrawImageOptions
|
||||
|
||||
buffer *ebiten.Image
|
||||
}
|
||||
|
||||
var spinner = []byte(`-\|/`)
|
||||
|
||||
// NewDemoGame returns a new kibodo demo game.
|
||||
func NewDemoGame() *game {
|
||||
k := kibodo.NewKeyboard()
|
||||
k.SetAllowUserHide(true)
|
||||
k.SetPassThroughPhysicalInput(true)
|
||||
k.SetKeys(kibodo.KeysQWERTY)
|
||||
k.SetHideShortcuts([]ebiten.Key{ebiten.KeyEscape})
|
||||
|
||||
g := &game{
|
||||
k: k,
|
||||
op: &ebiten.DrawImageOptions{
|
||||
Filter: ebiten.FilterNearest,
|
||||
},
|
||||
}
|
||||
|
||||
go g.showKeyboard()
|
||||
|
||||
return g
|
||||
}
|
||||
|
||||
func (g *game) showKeyboard() {
|
||||
if g.k.Visible() {
|
||||
return
|
||||
}
|
||||
|
||||
// Clear current input
|
||||
g.userInput = nil
|
||||
|
||||
// Show keyboard
|
||||
g.k.Show()
|
||||
}
|
||||
|
||||
func (g *game) Layout(outsideWidth, outsideHeight int) (int, int) {
|
||||
s := ebiten.DeviceScaleFactor()
|
||||
outsideWidth, outsideHeight = int(float64(outsideWidth)*s), int(float64(outsideHeight)*s)
|
||||
if g.w == outsideWidth && g.h == outsideHeight {
|
||||
return outsideWidth, outsideHeight
|
||||
}
|
||||
|
||||
g.w, g.h = outsideWidth, outsideHeight
|
||||
|
||||
g.buffer = ebiten.NewImage(g.w, g.h)
|
||||
|
||||
y := 200
|
||||
if g.h > g.w && (g.h-g.w) > 200 {
|
||||
y = g.h - g.w
|
||||
}
|
||||
g.k.SetRect(0, y, g.w, g.h-y)
|
||||
|
||||
return outsideWidth, outsideHeight
|
||||
}
|
||||
|
||||
func (g *game) Update() error {
|
||||
if inpututil.IsMouseButtonJustPressed(ebiten.MouseButtonLeft) && !g.k.Visible() {
|
||||
g.showKeyboard()
|
||||
}
|
||||
|
||||
g.incomingInput = g.k.AppendInput(g.incomingInput[:0])
|
||||
for _, input := range g.incomingInput {
|
||||
if input.Rune > 0 {
|
||||
g.userInput = append(g.userInput, []byte(string(input.Rune))...)
|
||||
continue
|
||||
}
|
||||
if input.Key == ebiten.KeyBackspace {
|
||||
s := string(g.userInput)
|
||||
if len(s) > 0 {
|
||||
g.userInput = []byte(s[:len(s)-1])
|
||||
}
|
||||
continue
|
||||
} else if input.Key == ebiten.KeyEnter {
|
||||
g.userInput = nil
|
||||
continue
|
||||
} else if input.Key < 0 {
|
||||
continue
|
||||
}
|
||||
g.userInput = append(g.userInput, []byte("<"+input.Key.String()+">")...)
|
||||
}
|
||||
|
||||
return g.k.Update()
|
||||
}
|
||||
|
||||
func (g *game) Draw(screen *ebiten.Image) {
|
||||
g.k.Draw(screen)
|
||||
|
||||
g.buffer.Clear()
|
||||
ebitenutil.DebugPrint(g.buffer, fmt.Sprintf("FPS %0.0f\nTPS %0.0f\n\n%s", ebiten.ActualFPS(), ebiten.ActualTPS(), g.userInput))
|
||||
g.op.GeoM.Reset()
|
||||
g.op.GeoM.Translate(3, 0)
|
||||
g.op.GeoM.Scale(2, 2)
|
||||
screen.DrawImage(g.buffer, g.op)
|
||||
}
|
18
kibodo/examples/kibodo/game/mobile/mobile.go
Normal file
18
kibodo/examples/kibodo/game/mobile/mobile.go
Normal file
|
@ -0,0 +1,18 @@
|
|||
//go:build example
|
||||
|
||||
package mobile
|
||||
|
||||
import (
|
||||
"code.rocket9labs.com/tslocum/etk/kibodo/examples/kibodo/game"
|
||||
"github.com/hajimehoshi/ebiten/v2/mobile"
|
||||
)
|
||||
|
||||
func init() {
|
||||
mobile.SetGame(game.NewDemoGame())
|
||||
}
|
||||
|
||||
// Dummy is a dummy exported function.
|
||||
//
|
||||
// gomobile will only compile packages that include at least one exported function.
|
||||
// Dummy forces gomobile to compile this package.
|
||||
func Dummy() {}
|
23
kibodo/examples/kibodo/main.go
Normal file
23
kibodo/examples/kibodo/main.go
Normal file
|
@ -0,0 +1,23 @@
|
|||
//go:build example
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"log"
|
||||
|
||||
"code.rocket9labs.com/tslocum/etk/kibodo/examples/kibodo/game"
|
||||
"github.com/hajimehoshi/ebiten/v2"
|
||||
)
|
||||
|
||||
func main() {
|
||||
ebiten.SetWindowTitle("キーボード")
|
||||
ebiten.SetWindowResizingMode(ebiten.WindowResizingModeEnabled)
|
||||
|
||||
ebiten.SetTPS(60)
|
||||
ebiten.SetVsyncEnabled(true)
|
||||
|
||||
g := game.NewDemoGame()
|
||||
if err := ebiten.RunGame(g); err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
}
|
33
kibodo/key.go
Normal file
33
kibodo/key.go
Normal file
|
@ -0,0 +1,33 @@
|
|||
package kibodo
|
||||
|
||||
import (
|
||||
"github.com/hajimehoshi/ebiten/v2"
|
||||
)
|
||||
|
||||
// Key represents a virtual key.
|
||||
type Key struct {
|
||||
LowerLabel string
|
||||
UpperLabel string
|
||||
LowerInput *Input
|
||||
UpperInput *Input
|
||||
Wide bool
|
||||
|
||||
x, y int
|
||||
w, h int
|
||||
|
||||
pressed bool
|
||||
pressedTouchID ebiten.TouchID
|
||||
}
|
||||
|
||||
// Input represents the input event from a key press.
|
||||
type Input struct {
|
||||
Rune rune
|
||||
Key ebiten.Key
|
||||
}
|
||||
|
||||
func (i *Input) String() string {
|
||||
if i.Rune > 0 {
|
||||
return string(i.Rune)
|
||||
}
|
||||
return i.Key.String()
|
||||
}
|
774
kibodo/keyboard.go
Normal file
774
kibodo/keyboard.go
Normal file
|
@ -0,0 +1,774 @@
|
|||
package kibodo
|
||||
|
||||
import (
|
||||
"image"
|
||||
"image/color"
|
||||
"log"
|
||||
"time"
|
||||
|
||||
"github.com/hajimehoshi/ebiten/v2"
|
||||
"github.com/hajimehoshi/ebiten/v2/examples/resources/fonts"
|
||||
"github.com/hajimehoshi/ebiten/v2/inpututil"
|
||||
"github.com/hajimehoshi/ebiten/v2/text"
|
||||
"golang.org/x/image/font"
|
||||
"golang.org/x/image/font/opentype"
|
||||
)
|
||||
|
||||
// Keyboard is an on-screen keyboard widget.
|
||||
type Keyboard struct {
|
||||
x, y int
|
||||
w, h int
|
||||
|
||||
visible bool
|
||||
alpha float64
|
||||
passPhysical bool
|
||||
allowUserHide bool
|
||||
|
||||
incomingBuffer []rune
|
||||
|
||||
inputEvents []*Input
|
||||
|
||||
keys [][]*Key
|
||||
normalKeys [][]*Key
|
||||
extendedKeys [][]*Key
|
||||
showExtended bool
|
||||
|
||||
backgroundLower *ebiten.Image
|
||||
backgroundUpper *ebiten.Image
|
||||
backgroundDirty bool
|
||||
|
||||
op *ebiten.DrawImageOptions
|
||||
|
||||
backgroundColor color.Color
|
||||
lastBackgroundColor color.Color
|
||||
|
||||
shift bool
|
||||
|
||||
touchIDs []ebiten.TouchID
|
||||
holdTouchID ebiten.TouchID
|
||||
holdKey *Key
|
||||
wasPressed bool
|
||||
|
||||
hideShortcuts []ebiten.Key
|
||||
|
||||
labelFont font.Face
|
||||
lineHeight int
|
||||
lineOffset int
|
||||
|
||||
backspaceDelay time.Duration
|
||||
backspaceRepeat time.Duration
|
||||
backspaceLast time.Time
|
||||
|
||||
scheduleFrameFunc func()
|
||||
}
|
||||
|
||||
// NewKeyboard returns a new Keyboard widget.
|
||||
func NewKeyboard() *Keyboard {
|
||||
fontFace, err := defaultFontFace(64)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
k := &Keyboard{
|
||||
alpha: 1.0,
|
||||
op: &ebiten.DrawImageOptions{
|
||||
Filter: ebiten.FilterNearest,
|
||||
},
|
||||
keys: KeysQWERTY,
|
||||
normalKeys: KeysQWERTY,
|
||||
backgroundLower: ebiten.NewImage(1, 1),
|
||||
backgroundUpper: ebiten.NewImage(1, 1),
|
||||
backgroundColor: color.Black,
|
||||
holdTouchID: -1,
|
||||
labelFont: fontFace,
|
||||
backspaceDelay: 500 * time.Millisecond,
|
||||
backspaceRepeat: 75 * time.Millisecond,
|
||||
}
|
||||
k.fontUpdated()
|
||||
return k
|
||||
}
|
||||
|
||||
func defaultFont() (*opentype.Font, error) {
|
||||
return opentype.Parse(fonts.MPlus1pRegular_ttf)
|
||||
}
|
||||
|
||||
func defaultFontFace(size float64) (font.Face, error) {
|
||||
f, err := defaultFont()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
const dpi = 72 // TODO
|
||||
return opentype.NewFace(f, &opentype.FaceOptions{
|
||||
Size: size,
|
||||
DPI: dpi,
|
||||
Hinting: font.HintingFull,
|
||||
})
|
||||
}
|
||||
|
||||
// SetRect sets the position and size of the widget.
|
||||
func (k *Keyboard) SetRect(x, y, w, h int) {
|
||||
if k.x == x && k.y == y && k.w == w && k.h == h {
|
||||
return
|
||||
}
|
||||
k.x, k.y, k.w, k.h = x, y, w, h
|
||||
|
||||
k.updateKeyRects()
|
||||
k.backgroundDirty = true
|
||||
}
|
||||
|
||||
// Rect returns the position and size of the widget.
|
||||
func (k *Keyboard) Rect() image.Rectangle {
|
||||
return image.Rect(k.x, k.y, k.x+k.w, k.y+k.h)
|
||||
}
|
||||
|
||||
// GetKeys returns the keys of the keyboard.
|
||||
func (k *Keyboard) GetKeys() [][]*Key {
|
||||
return k.keys
|
||||
}
|
||||
|
||||
// SetKeys sets the keys of the keyboard.
|
||||
func (k *Keyboard) SetKeys(keys [][]*Key) {
|
||||
k.normalKeys = keys
|
||||
|
||||
if !k.showExtended && !keysEqual(keys, k.keys) {
|
||||
k.keys = keys
|
||||
k.updateKeyRects()
|
||||
k.backgroundDirty = true
|
||||
}
|
||||
}
|
||||
|
||||
// SetExtendedKeys sets the keys of the keyboard when the .
|
||||
func (k *Keyboard) SetExtendedKeys(keys [][]*Key) {
|
||||
k.extendedKeys = keys
|
||||
|
||||
if k.showExtended && !keysEqual(keys, k.keys) {
|
||||
k.keys = keys
|
||||
k.updateKeyRects()
|
||||
k.backgroundDirty = true
|
||||
}
|
||||
}
|
||||
|
||||
// SetShowExtended sets whether the normal or extended keyboard is shown.
|
||||
func (k *Keyboard) SetShowExtended(show bool) {
|
||||
if k.showExtended == show {
|
||||
return
|
||||
}
|
||||
k.showExtended = show
|
||||
if k.showExtended {
|
||||
k.keys = k.extendedKeys
|
||||
} else {
|
||||
k.keys = k.normalKeys
|
||||
}
|
||||
k.updateKeyRects()
|
||||
k.backgroundDirty = true
|
||||
}
|
||||
|
||||
// SetLabelFont sets the key label font.
|
||||
func (k *Keyboard) SetLabelFont(face font.Face) {
|
||||
k.labelFont = face
|
||||
k.fontUpdated()
|
||||
|
||||
k.backgroundDirty = true
|
||||
}
|
||||
|
||||
func (k *Keyboard) fontUpdated() {
|
||||
m := k.labelFont.Metrics()
|
||||
k.lineHeight = m.Height.Round()
|
||||
k.lineOffset = m.Ascent.Round()
|
||||
}
|
||||
|
||||
// SetHideShortcuts sets the key shortcuts which, when pressed, will hide the
|
||||
// keyboard.
|
||||
func (k *Keyboard) SetHideShortcuts(shortcuts []ebiten.Key) {
|
||||
k.hideShortcuts = shortcuts
|
||||
}
|
||||
|
||||
func (k *Keyboard) updateKeyRects() {
|
||||
if len(k.keys) == 0 {
|
||||
return
|
||||
}
|
||||
|
||||
maxCells := 0
|
||||
for _, rowKeys := range k.keys {
|
||||
if len(rowKeys) > maxCells {
|
||||
maxCells = len(rowKeys)
|
||||
}
|
||||
}
|
||||
|
||||
// TODO user configurable
|
||||
cellPaddingW := 1
|
||||
cellPaddingH := 1
|
||||
|
||||
cellH := (k.h - (cellPaddingH * (len(k.keys) - 1))) / len(k.keys)
|
||||
|
||||
row := 0
|
||||
x, y := 0, 0
|
||||
for _, rowKeys := range k.keys {
|
||||
if len(rowKeys) == 0 {
|
||||
continue
|
||||
}
|
||||
|
||||
availableWidth := k.w
|
||||
for _, key := range rowKeys {
|
||||
if key.Wide {
|
||||
availableWidth = availableWidth / 2
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
cellW := (availableWidth - (cellPaddingW * (len(rowKeys) - 1))) / len(rowKeys)
|
||||
|
||||
x = 0
|
||||
for i, key := range rowKeys {
|
||||
key.w, key.h = cellW, cellH
|
||||
key.x, key.y = x, y
|
||||
|
||||
if i == len(rowKeys)-1 {
|
||||
key.w = k.w - key.x
|
||||
}
|
||||
|
||||
if key.Wide {
|
||||
key.w = k.w - k.w/2 + (cellW)
|
||||
}
|
||||
|
||||
x += key.w
|
||||
}
|
||||
|
||||
// Count non-empty rows only
|
||||
row++
|
||||
y += (cellH + cellPaddingH)
|
||||
}
|
||||
}
|
||||
|
||||
func (k *Keyboard) at(x, y int) *Key {
|
||||
if !k.visible {
|
||||
return nil
|
||||
}
|
||||
if x >= k.x && x <= k.x+k.w && y >= k.y && y <= k.y+k.h {
|
||||
x, y = x-k.x, y-k.y // Offset
|
||||
for _, rowKeys := range k.keys {
|
||||
for _, key := range rowKeys {
|
||||
if x >= key.x && x <= key.x+key.w && y >= key.y && y <= key.y+key.h {
|
||||
return key
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// KeyAt returns the key located at the specified position, or nil if no key is found.
|
||||
func (k *Keyboard) KeyAt(x, y int) *Key {
|
||||
return k.at(x, y)
|
||||
}
|
||||
|
||||
func (k *Keyboard) handleToggleExtendedKey(inputKey ebiten.Key) bool {
|
||||
if inputKey != KeyToggleExtended {
|
||||
return false
|
||||
}
|
||||
k.showExtended = !k.showExtended
|
||||
if k.showExtended {
|
||||
k.keys = k.extendedKeys
|
||||
} else {
|
||||
k.keys = k.normalKeys
|
||||
}
|
||||
k.updateKeyRects()
|
||||
k.backgroundDirty = true
|
||||
return true
|
||||
}
|
||||
|
||||
func (k *Keyboard) handleHideKey(inputKey ebiten.Key) bool {
|
||||
if !k.allowUserHide {
|
||||
return false
|
||||
}
|
||||
|
||||
for _, key := range k.hideShortcuts {
|
||||
if key == inputKey {
|
||||
k.Hide()
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// Hit handles a key press.
|
||||
func (k *Keyboard) Hit(key *Key) {
|
||||
input := key.LowerInput
|
||||
if k.shift {
|
||||
input = key.UpperInput
|
||||
}
|
||||
|
||||
if input.Key == ebiten.KeyShift {
|
||||
k.shift = !k.shift
|
||||
if k.scheduleFrameFunc != nil {
|
||||
k.scheduleFrameFunc()
|
||||
}
|
||||
return
|
||||
} else if k.handleToggleExtendedKey(input.Key) || k.handleHideKey(input.Key) {
|
||||
return
|
||||
}
|
||||
|
||||
k.inputEvents = append(k.inputEvents, input)
|
||||
}
|
||||
|
||||
// HandleMouse passes the specified mouse event to the on-screen keyboard.
|
||||
func (k *Keyboard) HandleMouse(cursor image.Point, pressed bool, clicked bool) (handled bool, err error) {
|
||||
if k.backgroundDirty {
|
||||
k.drawBackground()
|
||||
k.backgroundDirty = false
|
||||
}
|
||||
|
||||
pressDuration := 50 * time.Millisecond
|
||||
if k.wasPressed && !pressed && !clicked {
|
||||
var key *Key
|
||||
if cursor.X != 0 || cursor.Y != 0 {
|
||||
key = k.at(cursor.X, cursor.Y)
|
||||
} else {
|
||||
PRESSKEY:
|
||||
for _, rowKeys := range k.keys {
|
||||
for _, rowKey := range rowKeys {
|
||||
if rowKey.pressed {
|
||||
key = rowKey
|
||||
break PRESSKEY
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
for _, rowKeys := range k.keys {
|
||||
for _, rowKey := range rowKeys {
|
||||
if key != nil && rowKey == key {
|
||||
continue
|
||||
}
|
||||
rowKey.pressed = false
|
||||
}
|
||||
}
|
||||
if key != nil {
|
||||
key.pressed = true
|
||||
|
||||
k.Hit(key)
|
||||
|
||||
go func() {
|
||||
time.Sleep(pressDuration)
|
||||
|
||||
key.pressed = false
|
||||
if k.scheduleFrameFunc != nil {
|
||||
k.scheduleFrameFunc()
|
||||
}
|
||||
}()
|
||||
}
|
||||
k.wasPressed = false
|
||||
} else if pressed {
|
||||
key := k.at(cursor.X, cursor.Y)
|
||||
if key != nil {
|
||||
if !key.pressed {
|
||||
input := key.LowerInput
|
||||
if k.shift {
|
||||
input = key.UpperInput
|
||||
}
|
||||
|
||||
// Repeat backspace and delete operations.
|
||||
if input.Key == ebiten.KeyBackspace || input.Key == ebiten.KeyDelete {
|
||||
k.backspaceLast = time.Now().Add(k.backspaceDelay)
|
||||
}
|
||||
go func() {
|
||||
t := time.NewTicker(k.backspaceRepeat)
|
||||
for {
|
||||
<-t.C
|
||||
|
||||
if !key.pressed {
|
||||
t.Stop()
|
||||
return
|
||||
}
|
||||
|
||||
if (input.Key == ebiten.KeyBackspace || input.Key == ebiten.KeyDelete) && time.Since(k.backspaceLast) >= k.backspaceRepeat {
|
||||
k.backspaceLast = time.Now()
|
||||
k.inputEvents = append(k.inputEvents, &Input{Key: input.Key})
|
||||
}
|
||||
}
|
||||
|
||||
}()
|
||||
}
|
||||
key.pressed = true
|
||||
k.wasPressed = true
|
||||
|
||||
for _, rowKeys := range k.keys {
|
||||
for _, rowKey := range rowKeys {
|
||||
if rowKey == key || !rowKey.pressed {
|
||||
continue
|
||||
}
|
||||
rowKey.pressed = false
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return true, nil
|
||||
}
|
||||
|
||||
// Update handles user input. This function is called by Ebitengine.
|
||||
func (k *Keyboard) Update() error {
|
||||
if !k.visible {
|
||||
return nil
|
||||
}
|
||||
|
||||
if k.backgroundDirty {
|
||||
k.drawBackground()
|
||||
k.backgroundDirty = false
|
||||
}
|
||||
|
||||
// Pass through physical keyboard input
|
||||
if k.passPhysical {
|
||||
// Read input characters
|
||||
k.incomingBuffer = ebiten.AppendInputChars(k.incomingBuffer[:0])
|
||||
if len(k.incomingBuffer) > 0 {
|
||||
for _, r := range k.incomingBuffer {
|
||||
k.inputEvents = append(k.inputEvents, &Input{Rune: r}) // Pass through
|
||||
}
|
||||
} else {
|
||||
// Read keys
|
||||
for _, key := range allKeys {
|
||||
if inpututil.IsKeyJustPressed(key) {
|
||||
if k.handleHideKey(key) {
|
||||
// Hidden
|
||||
return nil
|
||||
}
|
||||
k.inputEvents = append(k.inputEvents, &Input{Key: key}) // Pass through
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// Handle mouse input
|
||||
pressDuration := 50 * time.Millisecond
|
||||
if inpututil.IsMouseButtonJustReleased(ebiten.MouseButtonLeft) {
|
||||
x, y := ebiten.CursorPosition()
|
||||
|
||||
key := k.at(x, y)
|
||||
if key != nil {
|
||||
for _, rowKeys := range k.keys {
|
||||
for _, rowKey := range rowKeys {
|
||||
rowKey.pressed = false
|
||||
}
|
||||
}
|
||||
key.pressed = true
|
||||
|
||||
k.Hit(key)
|
||||
|
||||
go func() {
|
||||
time.Sleep(pressDuration)
|
||||
|
||||
key.pressed = false
|
||||
if k.scheduleFrameFunc != nil {
|
||||
k.scheduleFrameFunc()
|
||||
}
|
||||
}()
|
||||
}
|
||||
} else if ebiten.IsMouseButtonPressed(ebiten.MouseButtonLeft) {
|
||||
x, y := ebiten.CursorPosition()
|
||||
|
||||
key := k.at(x, y)
|
||||
if key != nil {
|
||||
if !key.pressed {
|
||||
input := key.LowerInput
|
||||
if k.shift {
|
||||
input = key.UpperInput
|
||||
}
|
||||
|
||||
// Repeat backspace and delete operations.
|
||||
if input.Key == ebiten.KeyBackspace || input.Key == ebiten.KeyDelete {
|
||||
k.backspaceLast = time.Now().Add(k.backspaceDelay)
|
||||
}
|
||||
go func() {
|
||||
t := time.NewTicker(k.backspaceRepeat)
|
||||
for {
|
||||
<-t.C
|
||||
|
||||
if !key.pressed {
|
||||
t.Stop()
|
||||
return
|
||||
}
|
||||
|
||||
if (input.Key == ebiten.KeyBackspace || input.Key == ebiten.KeyDelete) && time.Since(k.backspaceLast) >= k.backspaceRepeat {
|
||||
k.backspaceLast = time.Now()
|
||||
k.inputEvents = append(k.inputEvents, &Input{Key: input.Key})
|
||||
}
|
||||
}
|
||||
|
||||
}()
|
||||
}
|
||||
key.pressed = true
|
||||
|
||||
for _, rowKeys := range k.keys {
|
||||
for _, rowKey := range rowKeys {
|
||||
if rowKey == key || !rowKey.pressed {
|
||||
continue
|
||||
}
|
||||
rowKey.pressed = false
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// Handle touch input
|
||||
if k.holdTouchID != -1 {
|
||||
x, y := ebiten.TouchPosition(k.holdTouchID)
|
||||
if x == 0 && y == 0 {
|
||||
k.holdTouchID = -1
|
||||
} else {
|
||||
key := k.at(x, y)
|
||||
if key != k.holdKey {
|
||||
k.holdTouchID = -1
|
||||
return nil
|
||||
}
|
||||
//k.Hold(key)
|
||||
k.holdKey = key
|
||||
}
|
||||
}
|
||||
if k.holdTouchID == -1 {
|
||||
k.touchIDs = inpututil.AppendJustPressedTouchIDs(k.touchIDs[:0])
|
||||
for _, id := range k.touchIDs {
|
||||
x, y := ebiten.TouchPosition(id)
|
||||
|
||||
key := k.at(x, y)
|
||||
if key != nil {
|
||||
input := key.LowerInput
|
||||
if k.shift {
|
||||
input = key.UpperInput
|
||||
}
|
||||
|
||||
if !key.pressed {
|
||||
key.pressed = true
|
||||
key.pressedTouchID = id
|
||||
|
||||
for _, rowKeys := range k.keys {
|
||||
for _, rowKey := range rowKeys {
|
||||
if rowKey != key && rowKey.pressed {
|
||||
rowKey.pressed = false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
k.Hit(key)
|
||||
k.holdTouchID = id
|
||||
k.holdKey = key
|
||||
|
||||
// Repeat backspace and delete operations.
|
||||
if input.Key == ebiten.KeyBackspace || input.Key == ebiten.KeyDelete {
|
||||
k.backspaceLast = time.Now().Add(k.backspaceDelay)
|
||||
}
|
||||
|
||||
go func() {
|
||||
var touchIDs []ebiten.TouchID
|
||||
t := time.NewTicker(pressDuration)
|
||||
for range t.C {
|
||||
touchIDs = ebiten.AppendTouchIDs(touchIDs[:0])
|
||||
|
||||
var found bool
|
||||
for _, touchID := range touchIDs {
|
||||
if id == touchID {
|
||||
found = true
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if found {
|
||||
tx, ty := ebiten.TouchPosition(id)
|
||||
if tx != 0 || ty != 0 {
|
||||
x, y = tx, ty
|
||||
}
|
||||
}
|
||||
|
||||
if !found {
|
||||
key.pressed = false
|
||||
if k.scheduleFrameFunc != nil {
|
||||
k.scheduleFrameFunc()
|
||||
}
|
||||
t.Stop()
|
||||
return
|
||||
}
|
||||
|
||||
// Repeat backspace and delete operations.
|
||||
if (input.Key == ebiten.KeyBackspace || input.Key == ebiten.KeyDelete) && time.Since(k.backspaceLast) >= k.backspaceRepeat {
|
||||
k.backspaceLast = time.Now()
|
||||
k.inputEvents = append(k.inputEvents, &Input{Key: input.Key})
|
||||
}
|
||||
}
|
||||
}()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (k *Keyboard) drawBackground() {
|
||||
if k.w == 0 || k.h == 0 {
|
||||
return
|
||||
}
|
||||
|
||||
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 {
|
||||
k.backgroundLower = ebiten.NewImage(k.w, k.h)
|
||||
k.backgroundUpper = ebiten.NewImage(k.w, k.h)
|
||||
k.lastBackgroundColor = k.backgroundColor
|
||||
}
|
||||
k.backgroundLower.Fill(k.backgroundColor)
|
||||
k.backgroundUpper.Fill(k.backgroundColor)
|
||||
|
||||
halfLineHeight := k.lineHeight / 2
|
||||
|
||||
lightShade := color.RGBA{150, 150, 150, 255}
|
||||
darkShade := color.RGBA{30, 30, 30, 255}
|
||||
|
||||
var keyImage *ebiten.Image
|
||||
for i := 0; i < 2; i++ {
|
||||
shift := i == 1
|
||||
img := k.backgroundLower
|
||||
if shift {
|
||||
img = k.backgroundUpper
|
||||
}
|
||||
for _, rowKeys := range k.keys {
|
||||
for _, key := range rowKeys {
|
||||
r := image.Rect(key.x, key.y, key.x+key.w, key.y+key.h)
|
||||
keyImage = img.SubImage(r).(*ebiten.Image)
|
||||
|
||||
// Draw key background
|
||||
// TODO configurable
|
||||
keyImage.Fill(color.RGBA{90, 90, 90, 255})
|
||||
|
||||
// Draw key label
|
||||
label := key.LowerLabel
|
||||
if shift {
|
||||
label = key.UpperLabel
|
||||
}
|
||||
|
||||
bounds := text.BoundString(k.labelFont, label)
|
||||
x := (key.w - bounds.Dx()) / 2
|
||||
if x < 0 {
|
||||
x = 0
|
||||
}
|
||||
y := halfLineHeight + (key.h-halfLineHeight)/2
|
||||
text.Draw(keyImage, label, k.labelFont, key.x+x, key.y+y, color.White)
|
||||
|
||||
// Draw border
|
||||
keyImage.SubImage(image.Rect(key.x, key.y, key.x+key.w, key.y+1)).(*ebiten.Image).Fill(lightShade)
|
||||
keyImage.SubImage(image.Rect(key.x, key.y, key.x+1, key.y+key.h)).(*ebiten.Image).Fill(lightShade)
|
||||
keyImage.SubImage(image.Rect(key.x, key.y+key.h-1, key.x+key.w, key.y+key.h)).(*ebiten.Image).Fill(darkShade)
|
||||
keyImage.SubImage(image.Rect(key.x+key.w-1, key.y, key.x+key.w, key.y+key.h)).(*ebiten.Image).Fill(darkShade)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Draw draws the widget on the provided image. This function is called by Ebitengine.
|
||||
func (k *Keyboard) Draw(target *ebiten.Image) {
|
||||
if !k.visible {
|
||||
return
|
||||
}
|
||||
|
||||
if k.backgroundDirty {
|
||||
k.drawBackground()
|
||||
k.backgroundDirty = false
|
||||
}
|
||||
|
||||
var background *ebiten.Image
|
||||
if !k.shift {
|
||||
background = k.backgroundLower
|
||||
} else {
|
||||
background = k.backgroundUpper
|
||||
}
|
||||
|
||||
k.op.GeoM.Reset()
|
||||
k.op.GeoM.Translate(float64(k.x), float64(k.y))
|
||||
k.op.ColorM.Scale(1, 1, 1, k.alpha)
|
||||
target.DrawImage(background, k.op)
|
||||
k.op.ColorM.Reset()
|
||||
|
||||
// Draw pressed keys
|
||||
for _, rowKeys := range k.keys {
|
||||
for _, key := range rowKeys {
|
||||
if !key.pressed {
|
||||
continue
|
||||
}
|
||||
|
||||
// TODO buffer to prevent issues with alpha channel
|
||||
k.op.GeoM.Reset()
|
||||
k.op.GeoM.Translate(float64(k.x+key.x), float64(k.y+key.y))
|
||||
k.op.ColorM.Scale(0.75, 0.75, 0.75, k.alpha)
|
||||
|
||||
target.DrawImage(background.SubImage(image.Rect(key.x, key.y, key.x+key.w, key.y+key.h)).(*ebiten.Image), k.op)
|
||||
k.op.ColorM.Reset()
|
||||
|
||||
// Draw shadow.
|
||||
darkShade := color.RGBA{60, 60, 60, 255}
|
||||
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)
|
||||
subImg.Fill(darkShade)
|
||||
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)
|
||||
subImg.Fill(darkShade)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// SetAllowUserHide sets a flag that controls whether the widget may be hidden
|
||||
// by the user.
|
||||
func (k *Keyboard) SetAllowUserHide(allow bool) {
|
||||
k.allowUserHide = allow
|
||||
}
|
||||
|
||||
// SetPassThroughPhysicalInput sets a flag that controls whether physical
|
||||
// keyboard input is passed through to the widget's input buffer. This is not
|
||||
// enabled by default.
|
||||
func (k *Keyboard) SetPassThroughPhysicalInput(pass bool) {
|
||||
k.passPhysical = pass
|
||||
}
|
||||
|
||||
// SetAlpha sets the transparency level of the widget on a scale of 0 to 1.0.
|
||||
func (k *Keyboard) SetAlpha(alpha float64) {
|
||||
k.alpha = alpha
|
||||
}
|
||||
|
||||
// Show shows the widget.
|
||||
func (k *Keyboard) Show() {
|
||||
k.visible = true
|
||||
}
|
||||
|
||||
// Visible returns whether the widget is currently shown.
|
||||
func (k *Keyboard) Visible() bool {
|
||||
return k.visible
|
||||
}
|
||||
|
||||
// Hide hides the widget.
|
||||
func (k *Keyboard) Hide() {
|
||||
k.visible = false
|
||||
if k.showExtended {
|
||||
k.showExtended = false
|
||||
k.keys = k.normalKeys
|
||||
k.updateKeyRects()
|
||||
k.backgroundDirty = true
|
||||
}
|
||||
}
|
||||
|
||||
// AppendInput appends user input that was received since the function was last called.
|
||||
func (k *Keyboard) AppendInput(events []*Input) []*Input {
|
||||
events = append(events, k.inputEvents...)
|
||||
k.inputEvents = nil
|
||||
return events
|
||||
}
|
||||
|
||||
// SetScheduleFrameFunc sets the function called whenever the screen should be redrawn.
|
||||
func (k *Keyboard) SetScheduleFrameFunc(f func()) {
|
||||
k.scheduleFrameFunc = f
|
||||
}
|
||||
|
||||
func keysEqual(a [][]*Key, b [][]*Key) bool {
|
||||
if len(a) != len(b) {
|
||||
return false
|
||||
}
|
||||
for i := range a {
|
||||
if len(a[i]) != len(b[i]) {
|
||||
return false
|
||||
}
|
||||
for j := range b[i] {
|
||||
if a[i][j] != b[i][j] {
|
||||
return false
|
||||
}
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
85
kibodo/keyboard_test.go
Normal file
85
kibodo/keyboard_test.go
Normal file
|
@ -0,0 +1,85 @@
|
|||
package kibodo
|
||||
|
||||
import (
|
||||
"runtime"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/hajimehoshi/ebiten/v2"
|
||||
)
|
||||
|
||||
// TODO test presses registered
|
||||
|
||||
func TestKeyboard_Draw(t *testing.T) {
|
||||
k := newTestKeyboard()
|
||||
|
||||
// Warm caches
|
||||
k.drawBackground()
|
||||
}
|
||||
|
||||
func BenchmarkKeyboard_Draw(b *testing.B) {
|
||||
k := newTestKeyboard()
|
||||
|
||||
// Warm caches
|
||||
k.drawBackground()
|
||||
|
||||
b.ReportAllocs()
|
||||
b.ResetTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
k.drawBackground()
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkKeyboard_Press(b *testing.B) {
|
||||
go func() {
|
||||
time.Sleep(2 * time.Second)
|
||||
|
||||
k := newTestKeyboard()
|
||||
|
||||
// Warm caches
|
||||
k.drawBackground()
|
||||
|
||||
b.ReportAllocs()
|
||||
b.ResetTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
k.drawBackground()
|
||||
k.keys[0][0].pressed = true
|
||||
k.drawBackground()
|
||||
k.keys[0][0].pressed = false
|
||||
}
|
||||
}()
|
||||
|
||||
runtime.LockOSThread()
|
||||
|
||||
err := ebiten.RunGame(NewDummyGame())
|
||||
if err != nil {
|
||||
b.Error(err)
|
||||
}
|
||||
}
|
||||
|
||||
func newTestKeyboard() *Keyboard {
|
||||
k := NewKeyboard()
|
||||
k.SetRect(0, 0, 300, 100)
|
||||
|
||||
return k
|
||||
}
|
||||
|
||||
type DummyGame struct {
|
||||
ready bool
|
||||
}
|
||||
|
||||
func (d *DummyGame) Update() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (d *DummyGame) Draw(screen *ebiten.Image) {
|
||||
d.ready = true
|
||||
}
|
||||
|
||||
func (d *DummyGame) Layout(outsideWidth, outsideHeight int) (screenWidth, screenHeight int) {
|
||||
return outsideWidth, outsideHeight
|
||||
}
|
||||
|
||||
func NewDummyGame() *DummyGame {
|
||||
return &DummyGame{}
|
||||
}
|
932
kibodo/keys.go
Normal file
932
kibodo/keys.go
Normal file
|
@ -0,0 +1,932 @@
|
|||
package kibodo
|
||||
|
||||
import (
|
||||
"github.com/hajimehoshi/ebiten/v2"
|
||||
)
|
||||
|
||||
// KeyToggleExtended is a sentinel key value. Any key with this value set will
|
||||
// toggle the extended set of keys, if any defined via SetExtendedKeys.
|
||||
var KeyToggleExtended = ebiten.Key(-10)
|
||||
|
||||
// KeysQWERTY is a standard QWERTY keyboard layout.
|
||||
var KeysQWERTY = [][]*Key{
|
||||
{
|
||||
{
|
||||
LowerLabel: "`",
|
||||
UpperLabel: "~",
|
||||
LowerInput: &Input{Rune: '`'},
|
||||
UpperInput: &Input{Rune: '~'},
|
||||
},
|
||||
{
|
||||
LowerLabel: "1",
|
||||
UpperLabel: "!",
|
||||
LowerInput: &Input{Rune: '1'},
|
||||
UpperInput: &Input{Rune: '!'},
|
||||
},
|
||||
{
|
||||
LowerLabel: "2",
|
||||
UpperLabel: "@",
|
||||
LowerInput: &Input{Rune: '2'},
|
||||
UpperInput: &Input{Rune: '@'},
|
||||
},
|
||||
{
|
||||
LowerLabel: "3",
|
||||
UpperLabel: "#",
|
||||
LowerInput: &Input{Rune: '3'},
|
||||
UpperInput: &Input{Rune: '#'},
|
||||
},
|
||||
{
|
||||
LowerLabel: "4",
|
||||
UpperLabel: "$",
|
||||
LowerInput: &Input{Rune: '4'},
|
||||
UpperInput: &Input{Rune: '$'},
|
||||
},
|
||||
{
|
||||
LowerLabel: "5",
|
||||
UpperLabel: "%",
|
||||
LowerInput: &Input{Rune: '5'},
|
||||
UpperInput: &Input{Rune: '%'},
|
||||
},
|
||||
{
|
||||
LowerLabel: "6",
|
||||
UpperLabel: "^",
|
||||
LowerInput: &Input{Rune: '6'},
|
||||
UpperInput: &Input{Rune: '^'},
|
||||
},
|
||||
{
|
||||
LowerLabel: "7",
|
||||
UpperLabel: "&",
|
||||
LowerInput: &Input{Rune: '7'},
|
||||
UpperInput: &Input{Rune: '&'},
|
||||
},
|
||||
{
|
||||
LowerLabel: "8",
|
||||
UpperLabel: "*",
|
||||
LowerInput: &Input{Rune: '8'},
|
||||
UpperInput: &Input{Rune: '*'},
|
||||
},
|
||||
{
|
||||
LowerLabel: "9",
|
||||
UpperLabel: "(",
|
||||
LowerInput: &Input{Rune: '9'},
|
||||
UpperInput: &Input{Rune: '('},
|
||||
},
|
||||
{
|
||||
LowerLabel: "0",
|
||||
UpperLabel: ")",
|
||||
LowerInput: &Input{Rune: '0'},
|
||||
UpperInput: &Input{Rune: ')'},
|
||||
},
|
||||
{
|
||||
LowerLabel: "-",
|
||||
UpperLabel: "_",
|
||||
LowerInput: &Input{Rune: '-'},
|
||||
UpperInput: &Input{Rune: '_'},
|
||||
},
|
||||
{
|
||||
LowerLabel: "=",
|
||||
UpperLabel: "+",
|
||||
LowerInput: &Input{Rune: '='},
|
||||
UpperInput: &Input{Rune: '+'},
|
||||
},
|
||||
{
|
||||
LowerLabel: "Backspace",
|
||||
UpperLabel: "BACKSPACE",
|
||||
LowerInput: &Input{Key: ebiten.KeyBackspace},
|
||||
UpperInput: &Input{Key: ebiten.KeyBackspace},
|
||||
},
|
||||
},
|
||||
{
|
||||
{
|
||||
LowerLabel: "q",
|
||||
UpperLabel: "Q",
|
||||
LowerInput: &Input{Rune: 'q'},
|
||||
UpperInput: &Input{Rune: 'Q'},
|
||||
},
|
||||
{
|
||||
LowerLabel: "w",
|
||||
UpperLabel: "W",
|
||||
LowerInput: &Input{Rune: 'w'},
|
||||
UpperInput: &Input{Rune: 'W'},
|
||||
},
|
||||
{
|
||||
LowerLabel: "e",
|
||||
UpperLabel: "E",
|
||||
LowerInput: &Input{Rune: 'e'},
|
||||
UpperInput: &Input{Rune: 'E'},
|
||||
},
|
||||
{
|
||||
LowerLabel: "r",
|
||||
UpperLabel: "R",
|
||||
LowerInput: &Input{Rune: 'r'},
|
||||
UpperInput: &Input{Rune: 'R'},
|
||||
},
|
||||
{
|
||||
LowerLabel: "t",
|
||||
UpperLabel: "T",
|
||||
LowerInput: &Input{Rune: 't'},
|
||||
UpperInput: &Input{Rune: 'T'},
|
||||
},
|
||||
{
|
||||
LowerLabel: "y",
|
||||
UpperLabel: "Y",
|
||||
LowerInput: &Input{Rune: 'y'},
|
||||
UpperInput: &Input{Rune: 'Y'},
|
||||
},
|
||||
{
|
||||
LowerLabel: "u",
|
||||
UpperLabel: "U",
|
||||
LowerInput: &Input{Rune: 'u'},
|
||||
UpperInput: &Input{Rune: 'U'},
|
||||
},
|
||||
{
|
||||
LowerLabel: "i",
|
||||
UpperLabel: "I",
|
||||
LowerInput: &Input{Rune: 'i'},
|
||||
UpperInput: &Input{Rune: 'I'},
|
||||
},
|
||||
{
|
||||
LowerLabel: "o",
|
||||
UpperLabel: "O",
|
||||
LowerInput: &Input{Rune: 'o'},
|
||||
UpperInput: &Input{Rune: 'O'},
|
||||
},
|
||||
{
|
||||
LowerLabel: "p",
|
||||
UpperLabel: "P",
|
||||
LowerInput: &Input{Rune: 'p'},
|
||||
UpperInput: &Input{Rune: 'P'},
|
||||
},
|
||||
{
|
||||
LowerLabel: "[",
|
||||
UpperLabel: "{",
|
||||
LowerInput: &Input{Rune: '['},
|
||||
UpperInput: &Input{Rune: '{'},
|
||||
},
|
||||
{
|
||||
LowerLabel: "]",
|
||||
UpperLabel: "}",
|
||||
LowerInput: &Input{Rune: ']'},
|
||||
UpperInput: &Input{Rune: '}'},
|
||||
},
|
||||
{
|
||||
LowerLabel: "\\",
|
||||
UpperLabel: "|",
|
||||
LowerInput: &Input{Rune: '\\'},
|
||||
UpperInput: &Input{Rune: '|'},
|
||||
},
|
||||
},
|
||||
{
|
||||
{
|
||||
LowerLabel: "a",
|
||||
UpperLabel: "A",
|
||||
LowerInput: &Input{Rune: 'a'},
|
||||
UpperInput: &Input{Rune: 'A'},
|
||||
},
|
||||
{
|
||||
LowerLabel: "s",
|
||||
UpperLabel: "S",
|
||||
LowerInput: &Input{Rune: 's'},
|
||||
UpperInput: &Input{Rune: 'S'},
|
||||
},
|
||||
{
|
||||
LowerLabel: "d",
|
||||
UpperLabel: "D",
|
||||
LowerInput: &Input{Rune: 'd'},
|
||||
UpperInput: &Input{Rune: 'D'},
|
||||
},
|
||||
{
|
||||
LowerLabel: "f",
|
||||
UpperLabel: "F",
|
||||
LowerInput: &Input{Rune: 'f'},
|
||||
UpperInput: &Input{Rune: 'F'},
|
||||
},
|
||||
{
|
||||
LowerLabel: "g",
|
||||
UpperLabel: "G",
|
||||
LowerInput: &Input{Rune: 'g'},
|
||||
UpperInput: &Input{Rune: 'G'},
|
||||
},
|
||||
{
|
||||
LowerLabel: "h",
|
||||
UpperLabel: "H",
|
||||
LowerInput: &Input{Rune: 'h'},
|
||||
UpperInput: &Input{Rune: 'H'},
|
||||
},
|
||||
{
|
||||
LowerLabel: "j",
|
||||
UpperLabel: "J",
|
||||
LowerInput: &Input{Rune: 'j'},
|
||||
UpperInput: &Input{Rune: 'J'},
|
||||
},
|
||||
{
|
||||
LowerLabel: "k",
|
||||
UpperLabel: "K",
|
||||
LowerInput: &Input{Rune: 'k'},
|
||||
UpperInput: &Input{Rune: 'K'},
|
||||
},
|
||||
{
|
||||
LowerLabel: "l",
|
||||
UpperLabel: "L",
|
||||
LowerInput: &Input{Rune: 'l'},
|
||||
UpperInput: &Input{Rune: 'L'},
|
||||
},
|
||||
{
|
||||
LowerLabel: ";",
|
||||
UpperLabel: ":",
|
||||
LowerInput: &Input{Rune: ';'},
|
||||
UpperInput: &Input{Rune: ':'},
|
||||
},
|
||||
{
|
||||
LowerLabel: `'`,
|
||||
UpperLabel: `"`,
|
||||
LowerInput: &Input{Rune: '\''},
|
||||
UpperInput: &Input{Rune: '"'},
|
||||
},
|
||||
{
|
||||
LowerLabel: "Enter",
|
||||
UpperLabel: "ENTER",
|
||||
LowerInput: &Input{Key: ebiten.KeyEnter},
|
||||
UpperInput: &Input{Key: ebiten.KeyEnter},
|
||||
},
|
||||
},
|
||||
{
|
||||
{
|
||||
LowerLabel: "Shift",
|
||||
UpperLabel: "SHIFT",
|
||||
LowerInput: &Input{Key: ebiten.KeyShift},
|
||||
UpperInput: &Input{Key: ebiten.KeyShift},
|
||||
},
|
||||
{
|
||||
LowerLabel: "z",
|
||||
UpperLabel: "Z",
|
||||
LowerInput: &Input{Rune: 'z'},
|
||||
UpperInput: &Input{Rune: 'Z'},
|
||||
},
|
||||
{
|
||||
LowerLabel: "x",
|
||||
UpperLabel: "X",
|
||||
LowerInput: &Input{Rune: 'x'},
|
||||
UpperInput: &Input{Rune: 'X'},
|
||||
},
|
||||
{
|
||||
LowerLabel: "c",
|
||||
UpperLabel: "C",
|
||||
LowerInput: &Input{Rune: 'c'},
|
||||
UpperInput: &Input{Rune: 'C'},
|
||||
},
|
||||
{
|
||||
LowerLabel: "v",
|
||||
UpperLabel: "V",
|
||||
LowerInput: &Input{Rune: 'v'},
|
||||
UpperInput: &Input{Rune: 'V'},
|
||||
},
|
||||
{
|
||||
LowerLabel: "b",
|
||||
UpperLabel: "B",
|
||||
LowerInput: &Input{Rune: 'b'},
|
||||
UpperInput: &Input{Rune: 'B'},
|
||||
},
|
||||
{
|
||||
LowerLabel: "n",
|
||||
UpperLabel: "N",
|
||||
LowerInput: &Input{Rune: 'n'},
|
||||
UpperInput: &Input{Rune: 'N'},
|
||||
},
|
||||
{
|
||||
LowerLabel: "m",
|
||||
UpperLabel: "M",
|
||||
LowerInput: &Input{Rune: 'm'},
|
||||
UpperInput: &Input{Rune: 'M'},
|
||||
},
|
||||
{
|
||||
LowerLabel: ",",
|
||||
UpperLabel: "<",
|
||||
LowerInput: &Input{Rune: ','},
|
||||
UpperInput: &Input{Rune: '<'},
|
||||
},
|
||||
{
|
||||
LowerLabel: ".",
|
||||
UpperLabel: ">",
|
||||
LowerInput: &Input{Rune: '.'},
|
||||
UpperInput: &Input{Rune: '>'},
|
||||
},
|
||||
{
|
||||
LowerLabel: "/",
|
||||
UpperLabel: "?",
|
||||
LowerInput: &Input{Rune: '/'},
|
||||
UpperInput: &Input{Rune: '?'},
|
||||
},
|
||||
},
|
||||
{
|
||||
{
|
||||
LowerLabel: "Space",
|
||||
UpperLabel: "SPACE",
|
||||
LowerInput: &Input{Rune: ' '},
|
||||
UpperInput: &Input{Rune: ' '},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
// KeysMobileQWERTY is a QWERTY keyboard layout optimized for mobile devices.
|
||||
var KeysMobileQWERTY = [][]*Key{
|
||||
{
|
||||
{
|
||||
LowerLabel: "q",
|
||||
UpperLabel: "Q",
|
||||
LowerInput: &Input{Rune: 'q'},
|
||||
UpperInput: &Input{Rune: 'Q'},
|
||||
},
|
||||
{
|
||||
LowerLabel: "w",
|
||||
UpperLabel: "W",
|
||||
LowerInput: &Input{Rune: 'w'},
|
||||
UpperInput: &Input{Rune: 'W'},
|
||||
},
|
||||
{
|
||||
LowerLabel: "e",
|
||||
UpperLabel: "E",
|
||||
LowerInput: &Input{Rune: 'e'},
|
||||
UpperInput: &Input{Rune: 'E'},
|
||||
},
|
||||
{
|
||||
LowerLabel: "r",
|
||||
UpperLabel: "R",
|
||||
LowerInput: &Input{Rune: 'r'},
|
||||
UpperInput: &Input{Rune: 'R'},
|
||||
},
|
||||
{
|
||||
LowerLabel: "t",
|
||||
UpperLabel: "T",
|
||||
LowerInput: &Input{Rune: 't'},
|
||||
UpperInput: &Input{Rune: 'T'},
|
||||
},
|
||||
{
|
||||
LowerLabel: "y",
|
||||
UpperLabel: "Y",
|
||||
LowerInput: &Input{Rune: 'y'},
|
||||
UpperInput: &Input{Rune: 'Y'},
|
||||
},
|
||||
{
|
||||
LowerLabel: "u",
|
||||
UpperLabel: "U",
|
||||
LowerInput: &Input{Rune: 'u'},
|
||||
UpperInput: &Input{Rune: 'U'},
|
||||
},
|
||||
{
|
||||
LowerLabel: "i",
|
||||
UpperLabel: "I",
|
||||
LowerInput: &Input{Rune: 'i'},
|
||||
UpperInput: &Input{Rune: 'I'},
|
||||
},
|
||||
{
|
||||
LowerLabel: "o",
|
||||
UpperLabel: "O",
|
||||
LowerInput: &Input{Rune: 'o'},
|
||||
UpperInput: &Input{Rune: 'O'},
|
||||
},
|
||||
{
|
||||
LowerLabel: "p",
|
||||
UpperLabel: "P",
|
||||
LowerInput: &Input{Rune: 'p'},
|
||||
UpperInput: &Input{Rune: 'P'},
|
||||
},
|
||||
},
|
||||
{
|
||||
{
|
||||
LowerLabel: "a",
|
||||
UpperLabel: "A",
|
||||
LowerInput: &Input{Rune: 'a'},
|
||||
UpperInput: &Input{Rune: 'A'},
|
||||
},
|
||||
{
|
||||
LowerLabel: "s",
|
||||
UpperLabel: "S",
|
||||
LowerInput: &Input{Rune: 's'},
|
||||
UpperInput: &Input{Rune: 'S'},
|
||||
},
|
||||
{
|
||||
LowerLabel: "d",
|
||||
UpperLabel: "D",
|
||||
LowerInput: &Input{Rune: 'd'},
|
||||
UpperInput: &Input{Rune: 'D'},
|
||||
},
|
||||
{
|
||||
LowerLabel: "f",
|
||||
UpperLabel: "F",
|
||||
LowerInput: &Input{Rune: 'f'},
|
||||
UpperInput: &Input{Rune: 'F'},
|
||||
},
|
||||
{
|
||||
LowerLabel: "g",
|
||||
UpperLabel: "G",
|
||||
LowerInput: &Input{Rune: 'g'},
|
||||
UpperInput: &Input{Rune: 'G'},
|
||||
},
|
||||
{
|
||||
LowerLabel: "h",
|
||||
UpperLabel: "H",
|
||||
LowerInput: &Input{Rune: 'h'},
|
||||
UpperInput: &Input{Rune: 'H'},
|
||||
},
|
||||
{
|
||||
LowerLabel: "j",
|
||||
UpperLabel: "J",
|
||||
LowerInput: &Input{Rune: 'j'},
|
||||
UpperInput: &Input{Rune: 'J'},
|
||||
},
|
||||
{
|
||||
LowerLabel: "k",
|
||||
UpperLabel: "K",
|
||||
LowerInput: &Input{Rune: 'k'},
|
||||
UpperInput: &Input{Rune: 'K'},
|
||||
},
|
||||
{
|
||||
LowerLabel: "l",
|
||||
UpperLabel: "L",
|
||||
LowerInput: &Input{Rune: 'l'},
|
||||
UpperInput: &Input{Rune: 'L'},
|
||||
},
|
||||
},
|
||||
{
|
||||
{
|
||||
LowerLabel: "Shift",
|
||||
UpperLabel: "SHIFT",
|
||||
LowerInput: &Input{Key: ebiten.KeyShift},
|
||||
UpperInput: &Input{Key: ebiten.KeyShift},
|
||||
},
|
||||
{
|
||||
LowerLabel: "z",
|
||||
UpperLabel: "Z",
|
||||
LowerInput: &Input{Rune: 'z'},
|
||||
UpperInput: &Input{Rune: 'Z'},
|
||||
},
|
||||
{
|
||||
LowerLabel: "x",
|
||||
UpperLabel: "X",
|
||||
LowerInput: &Input{Rune: 'x'},
|
||||
UpperInput: &Input{Rune: 'X'},
|
||||
},
|
||||
{
|
||||
LowerLabel: "c",
|
||||
UpperLabel: "C",
|
||||
LowerInput: &Input{Rune: 'c'},
|
||||
UpperInput: &Input{Rune: 'C'},
|
||||
},
|
||||
{
|
||||
LowerLabel: "v",
|
||||
UpperLabel: "V",
|
||||
LowerInput: &Input{Rune: 'v'},
|
||||
UpperInput: &Input{Rune: 'V'},
|
||||
},
|
||||
{
|
||||
LowerLabel: "b",
|
||||
UpperLabel: "B",
|
||||
LowerInput: &Input{Rune: 'b'},
|
||||
UpperInput: &Input{Rune: 'B'},
|
||||
},
|
||||
{
|
||||
LowerLabel: "n",
|
||||
UpperLabel: "N",
|
||||
LowerInput: &Input{Rune: 'n'},
|
||||
UpperInput: &Input{Rune: 'N'},
|
||||
},
|
||||
{
|
||||
LowerLabel: "m",
|
||||
UpperLabel: "M",
|
||||
LowerInput: &Input{Rune: 'm'},
|
||||
UpperInput: &Input{Rune: 'M'},
|
||||
},
|
||||
{
|
||||
LowerLabel: "Backspace",
|
||||
UpperLabel: "BACKSPACE",
|
||||
LowerInput: &Input{Key: ebiten.KeyBackspace},
|
||||
UpperInput: &Input{Key: ebiten.KeyBackspace},
|
||||
},
|
||||
},
|
||||
{
|
||||
{
|
||||
LowerLabel: "Symbols",
|
||||
UpperLabel: "SYMBOLS",
|
||||
LowerInput: &Input{Key: KeyToggleExtended},
|
||||
UpperInput: &Input{Key: KeyToggleExtended},
|
||||
},
|
||||
{
|
||||
LowerLabel: "?",
|
||||
UpperLabel: "!",
|
||||
LowerInput: &Input{Rune: '?'},
|
||||
UpperInput: &Input{Rune: '!'},
|
||||
},
|
||||
{
|
||||
LowerLabel: "Space",
|
||||
UpperLabel: "SPACE",
|
||||
LowerInput: &Input{Rune: ' '},
|
||||
UpperInput: &Input{Rune: ' '},
|
||||
Wide: true,
|
||||
},
|
||||
{
|
||||
LowerLabel: ".",
|
||||
UpperLabel: ",",
|
||||
LowerInput: &Input{Rune: '.'},
|
||||
UpperInput: &Input{Rune: ','},
|
||||
},
|
||||
{
|
||||
LowerLabel: "Enter",
|
||||
UpperLabel: "ENTER",
|
||||
LowerInput: &Input{Key: ebiten.KeyEnter},
|
||||
UpperInput: &Input{Key: ebiten.KeyEnter},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
// KeysMobileSymbols is a symbols keyboard layout optimized for mobile devices.
|
||||
var KeysMobileSymbols = [][]*Key{
|
||||
{
|
||||
{
|
||||
LowerLabel: "1",
|
||||
UpperLabel: "1",
|
||||
LowerInput: &Input{Rune: '1'},
|
||||
UpperInput: &Input{Rune: '1'},
|
||||
},
|
||||
{
|
||||
LowerLabel: "2",
|
||||
UpperLabel: "2",
|
||||
LowerInput: &Input{Rune: '2'},
|
||||
UpperInput: &Input{Rune: '2'},
|
||||
},
|
||||
{
|
||||
LowerLabel: "3",
|
||||
UpperLabel: "3",
|
||||
LowerInput: &Input{Rune: '3'},
|
||||
UpperInput: &Input{Rune: '3'},
|
||||
},
|
||||
{
|
||||
LowerLabel: "4",
|
||||
UpperLabel: "4",
|
||||
LowerInput: &Input{Rune: '4'},
|
||||
UpperInput: &Input{Rune: '4'},
|
||||
},
|
||||
{
|
||||
LowerLabel: "5",
|
||||
UpperLabel: "5",
|
||||
LowerInput: &Input{Rune: '5'},
|
||||
UpperInput: &Input{Rune: '5'},
|
||||
},
|
||||
{
|
||||
LowerLabel: "6",
|
||||
UpperLabel: "6",
|
||||
LowerInput: &Input{Rune: '6'},
|
||||
UpperInput: &Input{Rune: '6'},
|
||||
},
|
||||
{
|
||||
LowerLabel: "7",
|
||||
UpperLabel: "7",
|
||||
LowerInput: &Input{Rune: '7'},
|
||||
UpperInput: &Input{Rune: '7'},
|
||||
},
|
||||
{
|
||||
LowerLabel: "8",
|
||||
UpperLabel: "8",
|
||||
LowerInput: &Input{Rune: '8'},
|
||||
UpperInput: &Input{Rune: '8'},
|
||||
},
|
||||
{
|
||||
LowerLabel: "9",
|
||||
UpperLabel: "9",
|
||||
LowerInput: &Input{Rune: '9'},
|
||||
UpperInput: &Input{Rune: '9'},
|
||||
},
|
||||
{
|
||||
LowerLabel: "0",
|
||||
UpperLabel: "0",
|
||||
LowerInput: &Input{Rune: '0'},
|
||||
UpperInput: &Input{Rune: '0'},
|
||||
},
|
||||
},
|
||||
{
|
||||
{
|
||||
LowerLabel: "<",
|
||||
UpperLabel: "<",
|
||||
LowerInput: &Input{Rune: '<'},
|
||||
UpperInput: &Input{Rune: '<'},
|
||||
},
|
||||
{
|
||||
LowerLabel: ">",
|
||||
UpperLabel: ">",
|
||||
LowerInput: &Input{Rune: '>'},
|
||||
UpperInput: &Input{Rune: '>'},
|
||||
},
|
||||
{
|
||||
LowerLabel: "[",
|
||||
UpperLabel: "[",
|
||||
LowerInput: &Input{Rune: '['},
|
||||
UpperInput: &Input{Rune: '['},
|
||||
},
|
||||
{
|
||||
LowerLabel: "]",
|
||||
UpperLabel: "]",
|
||||
LowerInput: &Input{Rune: ']'},
|
||||
UpperInput: &Input{Rune: ']'},
|
||||
},
|
||||
{
|
||||
LowerLabel: "{",
|
||||
UpperLabel: "{",
|
||||
LowerInput: &Input{Rune: '{'},
|
||||
UpperInput: &Input{Rune: '{'},
|
||||
},
|
||||
{
|
||||
LowerLabel: "}",
|
||||
UpperLabel: "}",
|
||||
LowerInput: &Input{Rune: '}'},
|
||||
UpperInput: &Input{Rune: '}'},
|
||||
},
|
||||
{
|
||||
LowerLabel: "(",
|
||||
UpperLabel: "(",
|
||||
LowerInput: &Input{Rune: '('},
|
||||
UpperInput: &Input{Rune: '('},
|
||||
},
|
||||
{
|
||||
LowerLabel: ")",
|
||||
UpperLabel: ")",
|
||||
LowerInput: &Input{Rune: ')'},
|
||||
UpperInput: &Input{Rune: ')'},
|
||||
},
|
||||
{
|
||||
LowerLabel: "-",
|
||||
UpperLabel: "-",
|
||||
LowerInput: &Input{Rune: '-'},
|
||||
UpperInput: &Input{Rune: '-'},
|
||||
},
|
||||
{
|
||||
LowerLabel: "+",
|
||||
UpperLabel: "+",
|
||||
LowerInput: &Input{Rune: '+'},
|
||||
UpperInput: &Input{Rune: '+'},
|
||||
},
|
||||
},
|
||||
{
|
||||
{
|
||||
LowerLabel: "@",
|
||||
UpperLabel: "@",
|
||||
LowerInput: &Input{Rune: '@'},
|
||||
UpperInput: &Input{Rune: '@'},
|
||||
},
|
||||
{
|
||||
LowerLabel: "#",
|
||||
UpperLabel: "#",
|
||||
LowerInput: &Input{Rune: '#'},
|
||||
UpperInput: &Input{Rune: '#'},
|
||||
},
|
||||
{
|
||||
LowerLabel: "$",
|
||||
UpperLabel: "$",
|
||||
LowerInput: &Input{Rune: '$'},
|
||||
UpperInput: &Input{Rune: '$'},
|
||||
},
|
||||
{
|
||||
LowerLabel: "%",
|
||||
UpperLabel: "%",
|
||||
LowerInput: &Input{Rune: '%'},
|
||||
UpperInput: &Input{Rune: '%'},
|
||||
},
|
||||
{
|
||||
LowerLabel: "^",
|
||||
UpperLabel: "^",
|
||||
LowerInput: &Input{Rune: '^'},
|
||||
UpperInput: &Input{Rune: '^'},
|
||||
},
|
||||
{
|
||||
LowerLabel: "&",
|
||||
UpperLabel: "&",
|
||||
LowerInput: &Input{Rune: '&'},
|
||||
UpperInput: &Input{Rune: '&'},
|
||||
},
|
||||
{
|
||||
LowerLabel: "*",
|
||||
UpperLabel: "*",
|
||||
LowerInput: &Input{Rune: '*'},
|
||||
UpperInput: &Input{Rune: '*'},
|
||||
},
|
||||
{
|
||||
LowerLabel: "=",
|
||||
UpperLabel: "=",
|
||||
LowerInput: &Input{Rune: '='},
|
||||
UpperInput: &Input{Rune: '='},
|
||||
},
|
||||
{
|
||||
LowerLabel: "|",
|
||||
UpperLabel: "|",
|
||||
LowerInput: &Input{Rune: '|'},
|
||||
UpperInput: &Input{Rune: '|'},
|
||||
},
|
||||
{
|
||||
LowerLabel: "_",
|
||||
UpperLabel: "_",
|
||||
LowerInput: &Input{Rune: '_'},
|
||||
UpperInput: &Input{Rune: '_'},
|
||||
},
|
||||
},
|
||||
{
|
||||
{
|
||||
LowerLabel: "\\",
|
||||
UpperLabel: "\\",
|
||||
LowerInput: &Input{Rune: '\\'},
|
||||
UpperInput: &Input{Rune: '\\'},
|
||||
},
|
||||
{
|
||||
LowerLabel: "/",
|
||||
UpperLabel: "/",
|
||||
LowerInput: &Input{Rune: '/'},
|
||||
UpperInput: &Input{Rune: '/'},
|
||||
},
|
||||
{
|
||||
LowerLabel: "'",
|
||||
UpperLabel: "'",
|
||||
LowerInput: &Input{Rune: '\''},
|
||||
UpperInput: &Input{Rune: '\''},
|
||||
},
|
||||
{
|
||||
LowerLabel: "\"",
|
||||
UpperLabel: "\"",
|
||||
LowerInput: &Input{Rune: '"'},
|
||||
UpperInput: &Input{Rune: '"'},
|
||||
},
|
||||
{
|
||||
LowerLabel: ";",
|
||||
UpperLabel: ";",
|
||||
LowerInput: &Input{Rune: ';'},
|
||||
UpperInput: &Input{Rune: ';'},
|
||||
},
|
||||
{
|
||||
LowerLabel: ":",
|
||||
UpperLabel: ":",
|
||||
LowerInput: &Input{Rune: ':'},
|
||||
UpperInput: &Input{Rune: ':'},
|
||||
},
|
||||
{
|
||||
LowerLabel: "!",
|
||||
UpperLabel: "!",
|
||||
LowerInput: &Input{Rune: '!'},
|
||||
UpperInput: &Input{Rune: '!'},
|
||||
},
|
||||
{
|
||||
LowerLabel: "?",
|
||||
UpperLabel: "?",
|
||||
LowerInput: &Input{Rune: '?'},
|
||||
UpperInput: &Input{Rune: '?'},
|
||||
},
|
||||
{
|
||||
LowerLabel: "Backspace",
|
||||
UpperLabel: "BACKSPACE",
|
||||
LowerInput: &Input{Key: ebiten.KeyBackspace},
|
||||
UpperInput: &Input{Key: ebiten.KeyBackspace},
|
||||
},
|
||||
},
|
||||
{
|
||||
{
|
||||
LowerLabel: "Symbols",
|
||||
UpperLabel: "SYMBOLS",
|
||||
LowerInput: &Input{Key: KeyToggleExtended},
|
||||
UpperInput: &Input{Key: KeyToggleExtended},
|
||||
},
|
||||
{
|
||||
LowerLabel: ",",
|
||||
UpperLabel: ",",
|
||||
LowerInput: &Input{Rune: ','},
|
||||
UpperInput: &Input{Rune: ','},
|
||||
},
|
||||
{
|
||||
LowerLabel: "Space",
|
||||
UpperLabel: "SPACE",
|
||||
LowerInput: &Input{Rune: ' '},
|
||||
UpperInput: &Input{Rune: ' '},
|
||||
Wide: true,
|
||||
},
|
||||
{
|
||||
LowerLabel: ".",
|
||||
UpperLabel: ".",
|
||||
LowerInput: &Input{Rune: '.'},
|
||||
UpperInput: &Input{Rune: '.'},
|
||||
},
|
||||
{
|
||||
LowerLabel: "Enter",
|
||||
UpperLabel: "ENTER",
|
||||
LowerInput: &Input{Key: ebiten.KeyEnter},
|
||||
UpperInput: &Input{Key: ebiten.KeyEnter},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
var allKeys = []ebiten.Key{
|
||||
ebiten.KeyA,
|
||||
ebiten.KeyB,
|
||||
ebiten.KeyC,
|
||||
ebiten.KeyD,
|
||||
ebiten.KeyE,
|
||||
ebiten.KeyF,
|
||||
ebiten.KeyG,
|
||||
ebiten.KeyH,
|
||||
ebiten.KeyI,
|
||||
ebiten.KeyJ,
|
||||
ebiten.KeyK,
|
||||
ebiten.KeyL,
|
||||
ebiten.KeyM,
|
||||
ebiten.KeyN,
|
||||
ebiten.KeyO,
|
||||
ebiten.KeyP,
|
||||
ebiten.KeyQ,
|
||||
ebiten.KeyR,
|
||||
ebiten.KeyS,
|
||||
ebiten.KeyT,
|
||||
ebiten.KeyU,
|
||||
ebiten.KeyV,
|
||||
ebiten.KeyW,
|
||||
ebiten.KeyX,
|
||||
ebiten.KeyY,
|
||||
ebiten.KeyZ,
|
||||
ebiten.KeyAltLeft,
|
||||
ebiten.KeyAltRight,
|
||||
ebiten.KeyArrowDown,
|
||||
ebiten.KeyArrowLeft,
|
||||
ebiten.KeyArrowRight,
|
||||
ebiten.KeyArrowUp,
|
||||
ebiten.KeyBackquote,
|
||||
ebiten.KeyBackslash,
|
||||
ebiten.KeyBackspace,
|
||||
ebiten.KeyBracketLeft,
|
||||
ebiten.KeyBracketRight,
|
||||
ebiten.KeyCapsLock,
|
||||
ebiten.KeyComma,
|
||||
ebiten.KeyContextMenu,
|
||||
ebiten.KeyControlLeft,
|
||||
ebiten.KeyControlRight,
|
||||
ebiten.KeyDelete,
|
||||
ebiten.KeyDigit0,
|
||||
ebiten.KeyDigit1,
|
||||
ebiten.KeyDigit2,
|
||||
ebiten.KeyDigit3,
|
||||
ebiten.KeyDigit4,
|
||||
ebiten.KeyDigit5,
|
||||
ebiten.KeyDigit6,
|
||||
ebiten.KeyDigit7,
|
||||
ebiten.KeyDigit8,
|
||||
ebiten.KeyDigit9,
|
||||
ebiten.KeyEnd,
|
||||
ebiten.KeyEnter,
|
||||
ebiten.KeyEqual,
|
||||
ebiten.KeyEscape,
|
||||
ebiten.KeyF1,
|
||||
ebiten.KeyF2,
|
||||
ebiten.KeyF3,
|
||||
ebiten.KeyF4,
|
||||
ebiten.KeyF5,
|
||||
ebiten.KeyF6,
|
||||
ebiten.KeyF7,
|
||||
ebiten.KeyF8,
|
||||
ebiten.KeyF9,
|
||||
ebiten.KeyF10,
|
||||
ebiten.KeyF11,
|
||||
ebiten.KeyF12,
|
||||
ebiten.KeyHome,
|
||||
ebiten.KeyInsert,
|
||||
ebiten.KeyMetaLeft,
|
||||
ebiten.KeyMetaRight,
|
||||
ebiten.KeyMinus,
|
||||
ebiten.KeyNumLock,
|
||||
ebiten.KeyNumpad0,
|
||||
ebiten.KeyNumpad1,
|
||||
ebiten.KeyNumpad2,
|
||||
ebiten.KeyNumpad3,
|
||||
ebiten.KeyNumpad4,
|
||||
ebiten.KeyNumpad5,
|
||||
ebiten.KeyNumpad6,
|
||||
ebiten.KeyNumpad7,
|
||||
ebiten.KeyNumpad8,
|
||||
ebiten.KeyNumpad9,
|
||||
ebiten.KeyNumpadAdd,
|
||||
ebiten.KeyNumpadDecimal,
|
||||
ebiten.KeyNumpadDivide,
|
||||
ebiten.KeyNumpadEnter,
|
||||
ebiten.KeyNumpadEqual,
|
||||
ebiten.KeyNumpadMultiply,
|
||||
ebiten.KeyNumpadSubtract,
|
||||
ebiten.KeyPageDown,
|
||||
ebiten.KeyPageUp,
|
||||
ebiten.KeyPause,
|
||||
ebiten.KeyPeriod,
|
||||
ebiten.KeyPrintScreen,
|
||||
ebiten.KeyQuote,
|
||||
ebiten.KeyScrollLock,
|
||||
ebiten.KeySemicolon,
|
||||
ebiten.KeyShiftLeft,
|
||||
ebiten.KeyShiftRight,
|
||||
ebiten.KeySlash,
|
||||
ebiten.KeySpace,
|
||||
ebiten.KeyTab,
|
||||
ebiten.KeyAlt,
|
||||
ebiten.KeyControl,
|
||||
ebiten.KeyShift,
|
||||
ebiten.KeyMeta,
|
||||
ebiten.KeyMax,
|
||||
}
|
BIN
kibodo/screenshot.png
Normal file
BIN
kibodo/screenshot.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 25 KiB |
Loading…
Reference in a new issue