From d6f95c4e8c38bf816d5f1d316d5b487d4b3396f4 Mon Sep 17 00:00:00 2001 From: Hajime Hoshi Date: Sun, 11 May 2025 21:02:59 +0900 Subject: [PATCH] ebiten: add RunGameOptions.ApplePressAndHoldEnabled Updates #3233 --- examples/textinput/main.go | 4 +++- internal/glfw/cocoa_init_darwin.m | 4 ---- internal/ui/ui.go | 1 + internal/ui/ui_darwin.go | 24 ++++++++++++++++++++++-- internal/ui/ui_glfw.go | 2 ++ internal/ui/ui_linbsd.go | 4 ++++ internal/ui/ui_windows.go | 4 ++++ run.go | 30 +++++++++++++++++++++--------- 8 files changed, 57 insertions(+), 16 deletions(-) diff --git a/examples/textinput/main.go b/examples/textinput/main.go index f9f725e22..b956e35e2 100644 --- a/examples/textinput/main.go +++ b/examples/textinput/main.go @@ -325,7 +325,9 @@ func (g *Game) Layout(outsideWidth, outsideHeight int) (int, int) { func main() { ebiten.SetWindowSize(screenWidth, screenHeight) ebiten.SetWindowTitle("Text Input (Ebitengine Demo)") - if err := ebiten.RunGame(&Game{}); err != nil { + op := &ebiten.RunGameOptions{} + op.ApplePressAndHoldEnabled = true + if err := ebiten.RunGameWithOptions(&Game{}, op); err != nil { log.Fatal(err) } } diff --git a/internal/glfw/cocoa_init_darwin.m b/internal/glfw/cocoa_init_darwin.m index 417c91a06..dc55fe01b 100644 --- a/internal/glfw/cocoa_init_darwin.m +++ b/internal/glfw/cocoa_init_darwin.m @@ -481,10 +481,6 @@ int _glfwPlatformInit(void) if (_glfw.hints.init.ns.chdir) changeToResourcesDirectory(); - // Press and Hold prevents some keys from emitting repeated characters - NSDictionary* defaults = @{@"ApplePressAndHoldEnabled":@NO}; - [[NSUserDefaults standardUserDefaults] registerDefaults:defaults]; - [[NSNotificationCenter defaultCenter] addObserver:_glfw.ns.helper selector:@selector(selectedKeyboardInputSourceChanged:) diff --git a/internal/ui/ui.go b/internal/ui/ui.go index 45a6fe2f3..61d063ddb 100644 --- a/internal/ui/ui.go +++ b/internal/ui/ui.go @@ -179,6 +179,7 @@ type RunOptions struct { SingleThread bool DisableHiDPI bool ColorSpace graphicsdriver.ColorSpace + ApplePressAndHoldEnabled bool X11ClassName string X11InstanceName string StrictContextRestoration bool diff --git a/internal/ui/ui_darwin.go b/internal/ui/ui_darwin.go index f773e12fa..e58552626 100644 --- a/internal/ui/ui_darwin.go +++ b/internal/ui/ui_darwin.go @@ -164,6 +164,19 @@ func (u *UserInterface) initializePlatform() error { return nil } +func (u *UserInterface) setApplePressAndHoldEnabled(enabled bool) { + var val int + if enabled { + val = 1 + } + defaults := objc.ID(class_NSMutableDictionary).Send(sel_alloc).Send(sel_init) + defaults.Send(sel_setObjectForKey, + objc.ID(class_NSNumber).Send(sel_alloc).Send(sel_initWithBool, val), + cocoa.NSString_alloc().InitWithUTF8String("ApplePressAndHoldEnabled")) + ud := objc.ID(class_NSUserDefaults).Send(sel_standardUserDefaults) + ud.Send(sel_registerDefaults, defaults) +} + type graphicsDriverCreatorImpl struct { transparent bool colorSpace graphicsdriver.ColorSpace @@ -223,8 +236,11 @@ func (u *UserInterface) adjustWindowPosition(x, y int, monitor *Monitor) (int, i } var ( - class_NSCursor = objc.GetClass("NSCursor") - class_NSEvent = objc.GetClass("NSEvent") + class_NSCursor = objc.GetClass("NSCursor") + class_NSEvent = objc.GetClass("NSEvent") + class_NSMutableDictionary = objc.GetClass("NSMutableDictionary") + class_NSNumber = objc.GetClass("NSNumber") + class_NSUserDefaults = objc.GetClass("NSUserDefaults") ) var ( @@ -232,15 +248,19 @@ var ( sel_collectionBehavior = objc.RegisterName("collectionBehavior") sel_delegate = objc.RegisterName("delegate") sel_init = objc.RegisterName("init") + sel_initWithBool = objc.RegisterName("initWithBool:") sel_initWithOrigDelegate = objc.RegisterName("initWithOrigDelegate:") sel_mouseLocation = objc.RegisterName("mouseLocation") sel_origDelegate = objc.RegisterName("origDelegate") sel_origResizable = objc.RegisterName("isOrigResizable") + sel_registerDefaults = objc.RegisterName("registerDefaults:") sel_setCollectionBehavior = objc.RegisterName("setCollectionBehavior:") sel_setDelegate = objc.RegisterName("setDelegate:") sel_setDocumentEdited = objc.RegisterName("setDocumentEdited:") + sel_setObjectForKey = objc.RegisterName("setObject:forKey:") sel_setOrigDelegate = objc.RegisterName("setOrigDelegate:") sel_setOrigResizable = objc.RegisterName("setOrigResizable:") + sel_standardUserDefaults = objc.RegisterName("standardUserDefaults") sel_toggleFullScreen = objc.RegisterName("toggleFullScreen:") sel_windowDidBecomeKey = objc.RegisterName("windowDidBecomeKey:") sel_windowDidEnterFullScreen = objc.RegisterName("windowDidEnterFullScreen:") diff --git a/internal/ui/ui_glfw.go b/internal/ui/ui_glfw.go index 131d887de..1a8d34389 100644 --- a/internal/ui/ui_glfw.go +++ b/internal/ui/ui_glfw.go @@ -1053,6 +1053,8 @@ event: } func (u *UserInterface) initOnMainThread(options *RunOptions) error { + u.setApplePressAndHoldEnabled(options.ApplePressAndHoldEnabled) + if err := glfw.WindowHint(glfw.AutoIconify, glfw.False); err != nil { return err } diff --git a/internal/ui/ui_linbsd.go b/internal/ui/ui_linbsd.go index c7b54fee3..b47c6f9da 100644 --- a/internal/ui/ui_linbsd.go +++ b/internal/ui/ui_linbsd.go @@ -34,6 +34,10 @@ func (u *UserInterface) initializePlatform() error { return nil } +func (u *UserInterface) setApplePressAndHoldEnabled(enabled bool) { + // Do nothings. +} + type graphicsDriverCreatorImpl struct { transparent bool colorSpace graphicsdriver.ColorSpace diff --git a/internal/ui/ui_windows.go b/internal/ui/ui_windows.go index 93249abbf..bf00e99a9 100644 --- a/internal/ui/ui_windows.go +++ b/internal/ui/ui_windows.go @@ -34,6 +34,10 @@ func (u *UserInterface) initializePlatform() error { return nil } +func (u *UserInterface) setApplePressAndHoldEnabled(enabled bool) { + // Do nothings. +} + type graphicsDriverCreatorImpl struct { transparent bool colorSpace graphicsdriver.ColorSpace diff --git a/run.go b/run.go index 8cc96dfbc..b86e3f0a6 100644 --- a/run.go +++ b/run.go @@ -294,6 +294,17 @@ type RunGameOptions struct { // The default (zero) value is ColorSpaceDefault, which means that color space depends on the environment. ColorSpace ColorSpace + // ApplePressAndHoldEnabled indicates whether the press-and-hold feature is enabled or not. + // If true, pressing and holding a key might show a menu to select a character glyph variant. + // This is useful for GUI applications, but some APIs like [AppendInputChars]'s behavior is changed: + // for example, pressing and holding Q key would not repeat 'q' by [AppendInputChars]. + // If false, pressing and holding a key repeats the key event. + // + // ApplePressAndHoldEnabled is available only on macOS. + // + // The default (zero) value is false, which means that the press-and-hold feature is disabled. + ApplePressAndHoldEnabled bool + // X11DisplayName is a class name in the ICCCM WM_CLASS window property. X11ClassName string @@ -723,15 +734,16 @@ func toUIRunOptions(options *RunGameOptions) *ui.RunOptions { // The default (zero) value is false. return &ui.RunOptions{ - GraphicsLibrary: ui.GraphicsLibrary(options.GraphicsLibrary), - InitUnfocused: options.InitUnfocused, - ScreenTransparent: options.ScreenTransparent, - SkipTaskbar: options.SkipTaskbar, - SingleThread: options.SingleThread, - DisableHiDPI: options.DisableHiDPI, - ColorSpace: graphicsdriver.ColorSpace(options.ColorSpace), - X11ClassName: options.X11ClassName, - X11InstanceName: options.X11InstanceName, + GraphicsLibrary: ui.GraphicsLibrary(options.GraphicsLibrary), + InitUnfocused: options.InitUnfocused, + ScreenTransparent: options.ScreenTransparent, + SkipTaskbar: options.SkipTaskbar, + SingleThread: options.SingleThread, + DisableHiDPI: options.DisableHiDPI, + ColorSpace: graphicsdriver.ColorSpace(options.ColorSpace), + ApplePressAndHoldEnabled: options.ApplePressAndHoldEnabled, + X11ClassName: options.X11ClassName, + X11InstanceName: options.X11InstanceName, } }