ebiten: add KeyName for desktops and browsers

Updates #1904
This commit is contained in:
Hajime Hoshi 2022-08-13 13:33:52 +09:00
parent e6346c01d2
commit dd7e125d9c
14 changed files with 203 additions and 5 deletions

View File

@ -17,15 +17,18 @@ package main
import ( import (
"bytes" "bytes"
"image" "image"
"image/color"
_ "image/png" _ "image/png"
"log" "log"
"strings" "strings"
"github.com/hajimehoshi/bitmapfont/v2"
"github.com/hajimehoshi/ebiten/v2" "github.com/hajimehoshi/ebiten/v2"
"github.com/hajimehoshi/ebiten/v2/ebitenutil"
"github.com/hajimehoshi/ebiten/v2/examples/keyboard/keyboard" "github.com/hajimehoshi/ebiten/v2/examples/keyboard/keyboard"
rkeyboard "github.com/hajimehoshi/ebiten/v2/examples/resources/images/keyboard" rkeyboard "github.com/hajimehoshi/ebiten/v2/examples/resources/images/keyboard"
"github.com/hajimehoshi/ebiten/v2/inpututil" "github.com/hajimehoshi/ebiten/v2/inpututil"
"github.com/hajimehoshi/ebiten/v2/text"
) )
const ( const (
@ -78,11 +81,17 @@ func (g *Game) Draw(screen *ebiten.Image) {
screen.DrawImage(keyboardImage.SubImage(r).(*ebiten.Image), op) screen.DrawImage(keyboardImage.SubImage(r).(*ebiten.Image), op)
} }
keyStrs := []string{} var keyStrs []string
var keyNames []string
for _, k := range g.keys { for _, k := range g.keys {
keyStrs = append(keyStrs, k.String()) keyStrs = append(keyStrs, k.String())
if name := ebiten.KeyName(k); name != "" {
keyNames = append(keyNames, name)
}
} }
ebitenutil.DebugPrint(screen, strings.Join(keyStrs, ", "))
// Use bitmapfont.Face instead of ebitenutil.DebugPrint, since some key names might not be printed with DebugPrint.
text.Draw(screen, strings.Join(keyStrs, ", ")+"\n"+strings.Join(keyNames, ", "), bitmapfont.Face, 8, 12, color.White)
} }
func (g *Game) Layout(outsideWidth, outsideHeight int) (int, int) { func (g *Game) Layout(outsideWidth, outsideHeight int) (int, int) {

View File

@ -69,6 +69,19 @@ func IsKeyPressed(key Key) bool {
return theInputState.isKeyPressed(key) return theInputState.isKeyPressed(key)
} }
// KeyName returns a key name for the current keyboard layout.
// For example, KeyName(KeyQ) returns 'q' for a QWERTY keyboard, and returns 'a' for an AZERTY keyboard.
//
// KeyName returns an empty string if 1) the key doesn't have a phisical key name, 2) the platform doesn't support KeyName,
// or 3) the main loop doesn't start yet.
//
// KeyName is supported by desktops and browsers.
//
// KeyName is concurrent-safe.
func KeyName(key Key) string {
return ui.KeyName(ui.Key(key))
}
// CursorPosition returns a position of a mouse cursor relative to the game screen (window). The cursor position is // CursorPosition returns a position of a mouse cursor relative to the game screen (window). The cursor position is
// 'logical' position and this considers the scale of the screen. // 'logical' position and this considers the scale of the screen.
// //

View File

@ -273,6 +273,10 @@ func CreateWindow(width, height int, title string, monitor *Monitor, share *Wind
return theWindows.add(w), nil return theWindows.add(w), nil
} }
func GetKeyName(key Key, scancode int) string {
return glfw.GetKeyName(glfw.Key(key), scancode)
}
func GetMonitors() []*Monitor { func GetMonitors() []*Monitor {
ms := []*Monitor{} ms := []*Monitor{}
for _, m := range glfw.GetMonitors() { for _, m := range glfw.GetMonitors() {

View File

@ -306,6 +306,14 @@ func CreateWindow(width, height int, title string, monitor *Monitor, share *Wind
return (*Window)(w), err return (*Window)(w), err
} }
func GetKeyName(key Key, scancode int) string {
name, err := glfwwin.GetKeyName(glfwwin.Key(key), scancode)
if err != nil {
panic(err)
}
return name
}
func GetMonitors() []*Monitor { func GetMonitors() []*Monitor {
ms, err := glfwwin.GetMonitors() ms, err := glfwwin.GetMonitors()
if err != nil { if err != nil {

View File

@ -845,6 +845,7 @@ var (
procSetWindowTextW = user32.NewProc("SetWindowTextW") procSetWindowTextW = user32.NewProc("SetWindowTextW")
procShowWindow = user32.NewProc("ShowWindow") procShowWindow = user32.NewProc("ShowWindow")
procSystemParametersInfoW = user32.NewProc("SystemParametersInfoW") procSystemParametersInfoW = user32.NewProc("SystemParametersInfoW")
procToUnicode = user32.NewProc("ToUnicode")
procTranslateMessage = user32.NewProc("TranslateMessage") procTranslateMessage = user32.NewProc("TranslateMessage")
procTrackMouseEvent = user32.NewProc("TrackMouseEvent") procTrackMouseEvent = user32.NewProc("TrackMouseEvent")
procUnregisterClassW = user32.NewProc("UnregisterClassW") procUnregisterClassW = user32.NewProc("UnregisterClassW")
@ -1718,6 +1719,24 @@ func _TlsSetValue(dwTlsIndex uint32, lpTlsValue uintptr) error {
return nil return nil
} }
func _ToUnicode(wVirtualKey uint32, wScanCode uint32, keyState []byte, buff []uint16, cchBuff int32, wFlags uint32) int32 {
var lpKeyState *byte
if len(keyState) > 0 {
lpKeyState = &keyState[0]
}
var pwszBuff *uint16
if len(buff) > 0 {
pwszBuff = &buff[0]
}
r, _, _ := procToUnicode.Call(uintptr(wVirtualKey), uintptr(wScanCode), uintptr(unsafe.Pointer(lpKeyState)),
uintptr(unsafe.Pointer(pwszBuff)), uintptr(cchBuff), uintptr(wFlags))
runtime.KeepAlive(lpKeyState)
runtime.KeepAlive(pwszBuff)
return int32(r)
}
func _TranslateMessage(lpMsg *_MSG) bool { func _TranslateMessage(lpMsg *_MSG) bool {
r, _, _ := procTranslateMessage.Call(uintptr(unsafe.Pointer(lpMsg))) r, _, _ := procTranslateMessage.Call(uintptr(unsafe.Pointer(lpMsg)))
return int32(r) != 0 return int32(r) != 0

View File

@ -240,7 +240,20 @@ func RawMouseMotionSupported() (bool, error) {
return platformRawMouseMotionSupported(), nil return platformRawMouseMotionSupported(), nil
} }
// GetKeyName is not implemented. func GetKeyName(key Key, scancode int) (string, error) {
if !_glfw.initialized {
return "", NotInitialized
}
if key != KeyUnknown {
if key != KeyKPEqual && (key < KeyKP0 || key > KeyKPAdd) && (key < KeyApostrophe || key > KeyWorld2) {
return "", nil
}
scancode = platformGetKeyScancode(key)
}
return platformGetScancodeName(scancode)
}
func GetKeyScancode(key Key) (int, error) { func GetKeyScancode(key Key) (int, error) {
if !_glfw.initialized { if !_glfw.initialized {

View File

@ -261,6 +261,7 @@ type library struct {
clipboardString string clipboardString string
keycodes [512]Key keycodes [512]Key
scancodes [KeyLast + 1]int scancodes [KeyLast + 1]int
keynames [KeyLast + 1]string
// Where to place the cursor when re-enabled // Where to place the cursor when re-enabled
restoreCursorPosX float64 restoreCursorPosX float64

View File

@ -151,6 +151,47 @@ func createKeyTables() {
} }
} }
func updateKeyNamesWin32() {
for i := range _glfw.win32.keynames {
_glfw.win32.keynames[i] = ""
}
var state [256]byte
for key := KeySpace; key <= KeyLast; key++ {
scancode := _glfw.win32.scancodes[key]
if scancode == -1 {
continue
}
var vk uint32
if key >= KeyKP0 && key <= KeyKPAdd {
vks := []uint32{
_VK_NUMPAD0, _VK_NUMPAD1, _VK_NUMPAD2, _VK_NUMPAD3,
_VK_NUMPAD4, _VK_NUMPAD5, _VK_NUMPAD6, _VK_NUMPAD7,
_VK_NUMPAD8, _VK_NUMPAD9, _VK_DECIMAL, _VK_DIVIDE,
_VK_MULTIPLY, _VK_SUBTRACT, _VK_ADD,
}
vk = vks[key-KeyKP0]
} else {
vk = _MapVirtualKeyW(uint32(scancode), _MAPVK_VSC_TO_VK)
}
var chars [16]uint16
length := _ToUnicode(vk, uint32(scancode), state[:], chars[:], int32(len(chars)), 0)
if length == -1 {
// This is a dead key, so we need a second simulated key press
// to make it output its own character (usually a diacritic)
length = _ToUnicode(vk, uint32(scancode), state[:], chars[:], int32(len(chars)), 0)
}
if length < 1 {
continue
}
_glfw.win32.keynames[key] = windows.UTF16ToString(chars[:length])
}
}
func createHelperWindow() error { func createHelperWindow() error {
h, err := _CreateWindowExW(_WS_EX_OVERLAPPEDWINDOW, _GLFW_WNDCLASSNAME, "GLFW message window", _WS_CLIPSIBLINGS|_WS_CLIPCHILDREN, 0, 0, 1, 1, 0, 0, _glfw.win32.instance, nil) h, err := _CreateWindowExW(_WS_EX_OVERLAPPEDWINDOW, _GLFW_WNDCLASSNAME, "GLFW message window", _WS_CLIPSIBLINGS|_WS_CLIPCHILDREN, 0, 0, 1, 1, 0, 0, _glfw.win32.instance, nil)
if err != nil { if err != nil {
@ -238,6 +279,7 @@ func platformInit() error {
_glfw.win32.instance = _HINSTANCE(m) _glfw.win32.instance = _HINSTANCE(m)
createKeyTables() createKeyTables()
updateKeyNamesWin32()
if isWindows10CreatorsUpdateOrGreaterWin32() { if isWindows10CreatorsUpdateOrGreaterWin32() {
if !microsoftgdk.IsXbox() { if !microsoftgdk.IsXbox() {

View File

@ -690,7 +690,8 @@ func windowProc(hWnd windows.HWND, uMsg uint32, wParam _WPARAM, lParam _LPARAM)
return 0 return 0
case _WM_INPUTLANGCHANGE: case _WM_INPUTLANGCHANGE:
// Do nothing updateKeyNamesWin32()
return 0
case _WM_CHAR, _WM_SYSCHAR: case _WM_CHAR, _WM_SYSCHAR:
if wParam >= 0xd800 && wParam <= 0xdbff { if wParam >= 0xd800 && wParam <= 0xdbff {
@ -2204,6 +2205,13 @@ func (w *Window) platformSetCursorMode(mode int) error {
return nil return nil
} }
func platformGetScancodeName(scancode int) (string, error) {
if scancode < 0 || scancode > (_KF_EXTENDED|0xff) || _glfw.win32.keycodes[scancode] == KeyUnknown {
return "", fmt.Errorf("glwfwin: invalid scancode %d: %w", scancode, InvalidValue)
}
return _glfw.win32.keynames[_glfw.win32.keycodes[scancode]], nil
}
func platformGetKeyScancode(key Key) int { func platformGetKeyScancode(key Key) int {
return _glfw.win32.scancodes[key] return _glfw.win32.scancodes[key]
} }

View File

@ -76,3 +76,24 @@ func (u *userInterfaceImpl) updateInputState() error {
} }
return nil return nil
} }
func KeyName(key Key) string {
return theUI.keyName(key)
}
func (u *userInterfaceImpl) keyName(key Key) string {
if !u.isRunning() {
return ""
}
gk, ok := uiKeyToGLFWKey[key]
if !ok {
return ""
}
var name string
u.t.Call(func() {
name = glfw.GetKeyName(gk, 0)
})
return name
}

View File

@ -167,3 +167,51 @@ func isKeyString(str string) bool {
} }
return true return true
} }
var (
jsKeyboard = js.Global().Get("navigator").Get("keyboard")
jsKeyboardGetLayoutMap js.Value
jsKeyboardGetLayoutMapCh chan js.Value
jsKeyboardGetLayoutMapCallback js.Func
)
func init() {
if !jsKeyboard.Truthy() {
return
}
jsKeyboardGetLayoutMap = jsKeyboard.Get("getLayoutMap").Call("bind", jsKeyboard)
jsKeyboardGetLayoutMapCh = make(chan js.Value, 1)
jsKeyboardGetLayoutMapCallback = js.FuncOf(func(this js.Value, args []js.Value) any {
jsKeyboardGetLayoutMapCh <- args[0]
return nil
})
}
func KeyName(key Key) string {
return theUI.keyName(key)
}
func (u *userInterfaceImpl) keyName(key Key) string {
if !u.running {
return ""
}
// keyboardLayoutMap is reset every tick.
if u.keyboardLayoutMap.IsUndefined() {
if !jsKeyboard.Truthy() {
return ""
}
// Invoke getLayoutMap every tick to detect the keyboard change.
// TODO: Calling this every tick might be inefficient. Is there a way to detect a keyboard change?
jsKeyboardGetLayoutMap.Invoke().Call("then", jsKeyboardGetLayoutMapCallback)
u.keyboardLayoutMap = <-jsKeyboardGetLayoutMapCh
}
n := u.keyboardLayoutMap.Call("get", uiKeyToJSKey[key])
if n.IsUndefined() {
return ""
}
return n.String()
}

View File

@ -51,3 +51,8 @@ func (u *userInterfaceImpl) updateInputState(keys map[Key]struct{}, runes []rune
} }
} }
} }
func KeyName(key Key) string {
// TODO: Implement this.
return ""
}

View File

@ -37,3 +37,7 @@ func (u *userInterfaceImpl) updateInputState() {
} }
} }
} }
func KeyName(key Key) string {
return ""
}

View File

@ -91,6 +91,8 @@ type userInterfaceImpl struct {
origCursorX int origCursorX int
origCursorY int origCursorY int
keyboardLayoutMap js.Value
m sync.Mutex m sync.Mutex
} }
@ -686,6 +688,7 @@ func (u *userInterfaceImpl) readInputState(inputState *InputState) {
} }
func (u *userInterfaceImpl) resetForTick() { func (u *userInterfaceImpl) resetForTick() {
u.keyboardLayoutMap = js.Value{}
} }
func (u *userInterfaceImpl) Window() Window { func (u *userInterfaceImpl) Window() Window {