diff --git a/internal/uidriver/glfw/ui.go b/internal/uidriver/glfw/ui.go index 0d79e7361..6c9d8ca3a 100644 --- a/internal/uidriver/glfw/ui.go +++ b/internal/uidriver/glfw/ui.go @@ -816,9 +816,6 @@ func (u *UserInterface) init() error { } u.setSizeCallbackEnabled = true - setPosition := func() { - u.iwindow.setPosition(u.getInitWindowPosition()) - } setSize := func() { ww, wh := u.getInitWindowSize() ww = int(u.toGLFWPixel(float64(ww))) @@ -830,11 +827,11 @@ func (u *UserInterface) init() error { // but this should be inverted on Windows. This is very tricky, but there is no obvious way to solve // this. This doesn't matter on macOS. if runtime.GOOS == "windows" { - setPosition() + u.setWindowPosition(u.getInitWindowPosition()) setSize() } else { setSize() - setPosition() + u.setWindowPosition(u.getInitWindowPosition()) } u.updateWindowSizeLimits() @@ -1353,8 +1350,12 @@ func (u *UserInterface) Window() driver.Window { return &u.iwindow } -func (u *UserInterface) maximize() { - // Maximize invokes the SetSize callback but the callback must not be called in the game's Update (#1576). +// GLFW's functions to manipulate a window can invoke the SetSize callback (#1576, #1585, #1606). +// As the callback must not be called in the frame (between BeginFrame and EndFrame), +// disable the callback temporarily. + +// maximizeWindow must be called from the main thread. +func (u *UserInterface) maximizeWindow() { if u.setSizeCallbackEnabled { u.setSizeCallbackEnabled = false defer func() { @@ -1368,8 +1369,8 @@ func (u *UserInterface) maximize() { u.setWindowSize(w, h, u.isFullscreen()) } -func (u *UserInterface) iconify() { - // Iconify invokes the SetSize callback but the callback must not be called in the game's Update (#1576). +// iconifyWindow must be called from the main thread. +func (u *UserInterface) iconifyWindow() { if u.setSizeCallbackEnabled { u.setSizeCallbackEnabled = false defer func() { @@ -1382,8 +1383,8 @@ func (u *UserInterface) iconify() { // Rather, the window size might be (0, 0) and it might be impossible to call setWindowSize (#1585). } -func (u *UserInterface) restore() { - // Restore invokes the SetSize callback but the callback must not be called in the game's Update (#1576). +// restoreWindow must be called from the main thread. +func (u *UserInterface) restoreWindow() { if u.setSizeCallbackEnabled { u.setSizeCallbackEnabled = false defer func() { @@ -1397,9 +1398,8 @@ func (u *UserInterface) restore() { u.setWindowSize(w, h, u.isFullscreen()) } -func (u *UserInterface) setDecorated(decorated bool) { - // SetAttrib with glfw.Decorated invokes the SetSize callback but the callback must not be called in the game's Update (#1586). - // SetSize callback is invoked in the limited situations like just after restoring from the fullscreen mode. +// setWindowDecorated must be called from the main thread. +func (u *UserInterface) setWindowDecorated(decorated bool) { if u.setSizeCallbackEnabled { u.setSizeCallbackEnabled = false defer func() { @@ -1412,7 +1412,75 @@ func (u *UserInterface) setDecorated(decorated bool) { } u.window.SetAttrib(glfw.Decorated, v) - // Just after restoring from the fullscreen mode, the window's size might be a wrong value on Windows. - // This was the cause to invoke SetSize callback unexpectedly. This sounds like a GLFW's issue, but this is not confirmed. - // As the window size should not be changed, setWindowSize doesn't have to be called anyway. + // The title can be lost when the decoration is gone. Recover this. + if decorated { + u.window.SetTitle(u.title) + } +} + +// setWindowFloating must be called from the main thread. +func (u *UserInterface) setWindowFloating(floating bool) { + if u.setSizeCallbackEnabled { + u.setSizeCallbackEnabled = false + defer func() { + u.setSizeCallbackEnabled = true + }() + } + v := glfw.False + if floating { + v = glfw.True + } + u.window.SetAttrib(glfw.Floating, v) +} + +// setWindowResizable must be called from the main thread. +func (u *UserInterface) setWindowResizable(resizable bool) { + if u.setSizeCallbackEnabled { + u.setSizeCallbackEnabled = false + defer func() { + u.setSizeCallbackEnabled = true + }() + } + + v := glfw.False + if resizable { + v = glfw.True + } + u.window.SetAttrib(glfw.Resizable, v) +} + +// setWindowPosition must be called from the main thread. +func (u *UserInterface) setWindowPosition(x, y int) { + if u.setSizeCallbackEnabled { + u.setSizeCallbackEnabled = false + defer func() { + u.setSizeCallbackEnabled = true + }() + } + + mx, my := currentMonitor(u.window).GetPos() + xf := u.toGLFWPixel(float64(x)) + yf := u.toGLFWPixel(float64(y)) + if x, y := u.adjustWindowPosition(mx+int(xf), my+int(yf)); u.isFullscreen() { + u.origPosX, u.origPosY = x, y + } else { + u.window.SetPos(x, y) + } + + // Call setWindowSize explicitly in order to update the rendering since the callback is disabled now. + // This is necessary in some very limited cases (#1606). + w, h := u.window.GetSize() + u.setWindowSize(w, h, u.isFullscreen()) +} + +// setWindowTitle must be called from the main thread. +func (u *UserInterface) setWindowTitle(title string) { + if u.setSizeCallbackEnabled { + u.setSizeCallbackEnabled = false + defer func() { + u.setSizeCallbackEnabled = true + }() + } + + u.window.SetTitle(title) } diff --git a/internal/uidriver/glfw/window.go b/internal/uidriver/glfw/window.go index 3b7c49890..ae5afdaf9 100644 --- a/internal/uidriver/glfw/window.go +++ b/internal/uidriver/glfw/window.go @@ -51,12 +51,7 @@ func (w *window) SetDecorated(decorated bool) { return nil } - w.ui.setDecorated(decorated) - - // The title can be lost when the decoration is gone. Recover this. - if decorated { - w.ui.window.SetTitle(w.ui.title) - } + w.ui.setWindowDecorated(decorated) return nil }) } @@ -82,12 +77,7 @@ func (w *window) SetResizable(resizable bool) { if w.ui.isNativeFullscreen() { return nil } - - v := glfw.False - if resizable { - v = glfw.True - } - w.ui.window.SetAttrib(glfw.Resizable, v) + w.ui.setWindowResizable(resizable) return nil }) } @@ -113,12 +103,7 @@ func (w *window) SetFloating(floating bool) { if w.ui.isNativeFullscreen() { return nil } - - v := glfw.False - if floating { - v = glfw.True - } - w.ui.window.SetAttrib(glfw.Floating, v) + w.ui.setWindowFloating(floating) return nil }) } @@ -144,7 +129,7 @@ func (w *window) Maximize() { return } _ = w.ui.t.Call(func() error { - w.ui.maximize() + w.ui.maximizeWindow() return nil }) } @@ -167,7 +152,7 @@ func (w *window) Minimize() { return } _ = w.ui.t.Call(func() error { - w.ui.iconify() + w.ui.iconifyWindow() return nil }) } @@ -178,7 +163,7 @@ func (w *window) Restore() { return } _ = w.ui.t.Call(func() error { - w.ui.restore() + w.ui.restoreWindow() return nil }) } @@ -212,23 +197,11 @@ func (w *window) SetPosition(x, y int) { return } _ = w.ui.t.Call(func() error { - w.setPosition(x, y) + w.ui.setWindowPosition(x, y) return nil }) } -// setPosition must be called from the main thread -func (w *window) setPosition(x, y int) { - mx, my := currentMonitor(w.ui.window).GetPos() - xf := w.ui.toGLFWPixel(float64(x)) - yf := w.ui.toGLFWPixel(float64(y)) - if x, y := w.ui.adjustWindowPosition(mx+int(xf), my+int(yf)); w.ui.isFullscreen() { - w.ui.origPosX, w.ui.origPosY = x, y - } else { - w.ui.window.SetPos(x, y) - } -} - func (w *window) Size() (int, int) { if !w.ui.isRunning() { ww, wh := w.ui.getInitWindowSize() @@ -282,7 +255,7 @@ func (w *window) SetTitle(title string) { } w.ui.title = title _ = w.ui.t.Call(func() error { - w.ui.window.SetTitle(title) + w.ui.setWindowTitle(title) return nil }) }