diff --git a/examples/mascot/main.go b/examples/mascot/main.go index 5bcd99369..0773af538 100644 --- a/examples/mascot/main.go +++ b/examples/mascot/main.go @@ -142,6 +142,7 @@ func main() { ebiten.SetWindowDecorated(false) ebiten.SetWindowFloating(true) ebiten.SetWindowSize(width, height) + ebiten.SetWindowMousePassthrough(true) op := &ebiten.RunGameOptions{} op.ScreenTransparent = true diff --git a/examples/windowsize/main.go b/examples/windowsize/main.go index 911a44f88..ab0f9f4e7 100644 --- a/examples/windowsize/main.go +++ b/examples/windowsize/main.go @@ -147,6 +147,7 @@ func (g *game) Update() error { floating := ebiten.IsWindowFloating() resizingMode := ebiten.WindowResizingMode() screenCleared := ebiten.IsScreenClearedEveryFrame() + mousePassthrough := ebiten.IsWindowMousePassthrough() const d = 16 toUpdateWindowSize := false @@ -267,6 +268,9 @@ func (g *game) Update() error { restore = inpututil.IsKeyJustPressed(ebiten.KeyE) } } + if inpututil.IsKeyJustPressed(ebiten.KeyP) { + mousePassthrough = !mousePassthrough + } if toUpdateWindowSize { g.width = screenWidth @@ -304,6 +308,8 @@ func (g *game) Update() error { ebiten.SetWindowIcon([]image.Image{createRandomIconImage()}) } + ebiten.SetWindowMousePassthrough(mousePassthrough) + g.count++ return nil } @@ -357,6 +363,7 @@ func (g *game) Draw(screen *ebiten.Image) { [D] Switch the window decoration (only for desktops) [L] Switch the window floating state (only for desktops) [W] Switch whether to skip clearing the screen +[P] Switch whether a mouse cursor passthroughs the window (only for desktops) %s IsFocused?: %s Window Position: (%d, %d) diff --git a/internal/ui/ui_glfw.go b/internal/ui/ui_glfw.go index d089ff9a7..69bc11da9 100644 --- a/internal/ui/ui_glfw.go +++ b/internal/ui/ui_glfw.go @@ -84,16 +84,17 @@ type userInterfaceImpl struct { initFullscreenWidthInDIP int initFullscreenHeightInDIP int - initFullscreen bool - initCursorMode CursorMode - initWindowDecorated bool - initWindowMonitor int - initWindowPositionXInDIP int - initWindowPositionYInDIP int - initWindowWidthInDIP int - initWindowHeightInDIP int - initWindowFloating bool - initWindowMaximized bool + initFullscreen bool + initCursorMode CursorMode + initWindowDecorated bool + initWindowMonitor int + initWindowPositionXInDIP int + initWindowPositionYInDIP int + initWindowWidthInDIP int + initWindowHeightInDIP int + initWindowFloating bool + initWindowMaximized bool + initWindowMousePassthrough bool // bufferOnceSwapped must be accessed from the main thread. bufferOnceSwapped bool @@ -525,6 +526,18 @@ func (u *userInterfaceImpl) setInitWindowMaximized(maximized bool) { u.m.Unlock() } +func (u *userInterfaceImpl) isInitWindowMousePassthrough() bool { + u.m.RLock() + defer u.m.RUnlock() + return u.initWindowMousePassthrough +} + +func (u *userInterfaceImpl) setInitWindowMousePassthrough(enabled bool) { + u.m.Lock() + defer u.m.Unlock() + u.initWindowMousePassthrough = enabled +} + func (u *userInterfaceImpl) isWindowClosingHandled() bool { u.m.RLock() v := u.windowClosingHandled @@ -984,6 +997,12 @@ func (u *userInterfaceImpl) initOnMainThread(options *RunOptions) error { } glfw.WindowHint(glfw.FocusOnShow, focused) + mousePassthrough := glfw.False + if u.isInitWindowMousePassthrough() { + mousePassthrough = glfw.True + } + glfw.WindowHint(glfw.MousePassthrough, mousePassthrough) + // Set the window visible explicitly or the application freezes on Wayland (#974). if os.Getenv("WAYLAND_DISPLAY") != "" { glfw.WindowHint(glfw.Visible, glfw.True) @@ -1717,6 +1736,19 @@ func (u *userInterfaceImpl) setOrigWindowPos(x, y int) { u.origWindowPosY = y } +// setWindowMousePassthrough must be called from the main thread. +func (u *userInterfaceImpl) setWindowMousePassthrough(enabled bool) { + if microsoftgdk.IsXbox() { + return + } + + v := glfw.False + if enabled { + v = glfw.True + } + u.window.SetAttrib(glfw.MousePassthrough, v) +} + func IsScreenTransparentAvailable() bool { return true } diff --git a/internal/ui/window.go b/internal/ui/window.go index 4f997e8fd..aae945c37 100644 --- a/internal/ui/window.go +++ b/internal/ui/window.go @@ -41,6 +41,8 @@ type Window interface { Restore() SetClosingHandled(handled bool) IsClosingHandled() bool + SetMousePassthrough(enabled bool) + IsMousePassthrough() bool } type nullWindow struct{} @@ -119,3 +121,10 @@ func (*nullWindow) SetClosingHandled(handled bool) { func (*nullWindow) IsClosingHandled() bool { return false } + +func (*nullWindow) SetMousePassthrough(enabled bool) { +} + +func (*nullWindow) IsMousePassthrough() bool { + return false +} diff --git a/internal/ui/window_glfw.go b/internal/ui/window_glfw.go index 72e147947..7a0177ece 100644 --- a/internal/ui/window_glfw.go +++ b/internal/ui/window_glfw.go @@ -387,3 +387,36 @@ func (w *glfwWindow) SetClosingHandled(handled bool) { func (w *glfwWindow) IsClosingHandled() bool { return w.ui.isWindowClosingHandled() } + +func (w *glfwWindow) SetMousePassthrough(enabled bool) { + if w.ui.isTerminated() { + return + } + if !w.ui.isRunning() { + w.ui.setInitWindowMousePassthrough(enabled) + return + } + w.ui.mainThread.Call(func() { + if w.ui.isTerminated() { + return + } + w.ui.setWindowMousePassthrough(enabled) + }) +} + +func (w *glfwWindow) IsMousePassthrough() bool { + if w.ui.isTerminated() { + return false + } + if !w.ui.isRunning() { + return w.ui.isInitWindowMousePassthrough() + } + var v bool + w.ui.mainThread.Call(func() { + if w.ui.isTerminated() { + return + } + v = w.ui.window.GetAttrib(glfw.MousePassthrough) == glfw.True + }) + return v +} diff --git a/window.go b/window.go index 8b3fd33b5..0a5940e0b 100644 --- a/window.go +++ b/window.go @@ -54,7 +54,7 @@ func IsWindowDecorated() bool { // The window is decorated by default. // // SetWindowDecorated works only on desktops. -// SetWindowDecorated does nothing on other platforms. +// SetWindowDecorated does nothing if the platform is not a desktop. // // SetWindowDecorated is concurrent-safe. func SetWindowDecorated(decorated bool) { @@ -99,7 +99,7 @@ func SetWindowResizable(resizable bool) { // SetWindowTitle sets the title of the window. // -// SetWindowTitle does nothing on browsers or mobiles. +// SetWindowTitle does nothing if the platform is not a desktop. // // SetWindowTitle is concurrent-safe. func SetWindowTitle(title string) { @@ -123,7 +123,7 @@ func SetWindowTitle(title string) { // // As macOS windows don't have icons, SetWindowIcon doesn't work on macOS. // -// SetWindowIcon doesn't work on browsers or mobiles. +// SetWindowIcon doesn't work if the platform is not a desktop. // // SetWindowIcon is concurrent-safe. func SetWindowIcon(iconImages []image.Image) { @@ -138,7 +138,7 @@ func SetWindowIcon(iconImages []image.Image) { // // WindowPosition returns the original window position in fullscreen mode. // -// WindowPosition returns (0, 0) on browsers and mobiles. +// WindowPosition returns (0, 0) if the platform is not a desktop. // // WindowPosition is concurrent-safe. func WindowPosition() (x, y int) { @@ -151,7 +151,7 @@ func WindowPosition() (x, y int) { // // SetWindowPosition sets the original window position in fullscreen mode. // -// SetWindowPosition does nothing on browsers and mobiles. +// SetWindowPosition does nothing if the platform is not a desktop. // // SetWindowPosition is concurrent-safe. func SetWindowPosition(x, y int) { @@ -215,7 +215,7 @@ func SetWindowSizeLimits(minw, minh, maxw, maxh int) { // IsWindowFloating reports whether the window is always shown above all the other windows. // -// IsWindowFloating returns false on browsers and mobiles. +// IsWindowFloating returns false if the platform is not a desktop. // // IsWindowFloating is concurrent-safe. func IsWindowFloating() bool { @@ -224,7 +224,7 @@ func IsWindowFloating() bool { // SetWindowFloating sets the state whether the window is always shown above all the other windows. // -// SetWindowFloating does nothing on browsers or mobiles. +// SetWindowFloating does nothing if the platform is not a desktop. // // SetWindowFloating is concurrent-safe. func SetWindowFloating(float bool) { @@ -235,7 +235,7 @@ func SetWindowFloating(float bool) { // // MaximizeWindow does nothing when the window is not resizable (WindowResizingModeEnabled). // -// MaximizeWindow does nothing on browsers or mobiles. +// MaximizeWindow does nothing if the platform is not a desktop. // // MaximizeWindow is concurrent-safe. func MaximizeWindow() { @@ -246,7 +246,7 @@ func MaximizeWindow() { // // IsWindowMaximized returns false when the window is not resizable (WindowResizingModeEnabled). // -// IsWindowMaximized always returns false on browsers and mobiles. +// IsWindowMaximized always returns false if the platform is not a desktop. // // IsWindowMaximized is concurrent-safe. func IsWindowMaximized() bool { @@ -257,7 +257,7 @@ func IsWindowMaximized() bool { // // If the main loop does not start yet, MinimizeWindow does nothing. // -// MinimizeWindow does nothing on browsers or mobiles. +// MinimizeWindow does nothing if the platform is not a desktop. // // MinimizeWindow is concurrent-safe. func MinimizeWindow() { @@ -266,7 +266,7 @@ func MinimizeWindow() { // IsWindowMinimized reports whether the window is minimized or not. // -// IsWindowMinimized always returns false on browsers and mobiles. +// IsWindowMinimized always returns false if the platform is not a desktop. // // IsWindowMinimized is concurrent-safe. func IsWindowMinimized() bool { @@ -289,7 +289,7 @@ func RestoreWindow() { // As the window is closed immediately by default, // you might want to call SetWindowClosingHandled(true) to prevent the window is automatically closed. // -// IsWindowBeingClosed always returns false on other platforms. +// IsWindowBeingClosed always returns false if the platform is not a desktop. // // IsWindowBeingClosed is concurrent-safe. func IsWindowBeingClosed() bool { @@ -304,7 +304,7 @@ func IsWindowBeingClosed() bool { // To end the game, you have to return an error value at the Game's Update function. // // SetWindowClosingHandled works only on desktops. -// SetWindowClosingHandled does nothing on other platforms. +// SetWindowClosingHandled does nothing if the platform is not a desktop. // // SetWindowClosingHandled is concurrent-safe. func SetWindowClosingHandled(handled bool) { @@ -313,9 +313,31 @@ func SetWindowClosingHandled(handled bool) { // IsWindowClosingHandled reports whether the window closing is handled or not on desktops by SetWindowClosingHandled. // -// IsWindowClosingHandled always returns false on other platforms. +// IsWindowClosingHandled always returns false if the platform is not a desktop. // // IsWindowClosingHandled is concurrent-safe. func IsWindowClosingHandled() bool { return ui.Get().Window().IsClosingHandled() } + +// SetWindowMousePassthrough sets whether a mouse cursor passthroughs the window or not on desktops. The default state is false. +// +// Even if this is set true, some platforms might requrie a window to be undecorated +// in order to make the mouse cursor passthrough the window. +// +// SetWindowMousePassthrough works only on desktops. +// SetWindowMousePassthrough does nothing if the platform is not a desktop. +// +// SetWindowMousePassthrough is concurrent-safe. +func SetWindowMousePassthrough(enabled bool) { + ui.Get().Window().SetMousePassthrough(enabled) +} + +// IsWindowMousePassthrough reports whether a mouse cursor passthroughs the window or not on desktops. +// +// IsWindowMousePassthrough alaywas returns false if the platform is not a desktop. +// +// IsWindowMousePassthrough is concurrent-safe. +func IsWindowMousePassthrough() bool { + return ui.Get().Window().IsMousePassthrough() +}