internal/uidriver/glfw: Make a 'native' fullscreen on macOS

This is a breaking change. On macOS, `SetFullscreen` creates a new
independent space and maximize the size of the window there.

Updates #1506
Closes #1579
This commit is contained in:
Hajime Hoshi 2021-09-18 02:21:24 +09:00
parent 2a390c18c1
commit 428c079e52
6 changed files with 121 additions and 39 deletions

View File

@ -351,6 +351,10 @@ func WaitEvents() {
glfw.WaitEvents() glfw.WaitEvents()
} }
func WaitEventsTimeout(timeout float64) {
glfw.WaitEventsTimeout(timeout)
}
func WindowHint(target Hint, hint int) { func WindowHint(target Hint, hint int) {
glfw.WindowHint(glfw.Hint(target), hint) glfw.WindowHint(glfw.Hint(target), hint)
} }

View File

@ -545,7 +545,7 @@ func (u *UserInterface) isFullscreen() bool {
if !u.isRunning() { if !u.isRunning() {
panic("glfw: isFullscreen can't be called before the main loop starts") panic("glfw: isFullscreen can't be called before the main loop starts")
} }
return u.window.GetMonitor() != nil return u.window.GetMonitor() != nil || u.isNativeFullscreen()
} }
func (u *UserInterface) IsFullscreen() bool { func (u *UserInterface) IsFullscreen() bool {
@ -576,10 +576,6 @@ func (u *UserInterface) SetFullscreen(fullscreen bool) {
} }
_ = u.t.Call(func() error { _ = u.t.Call(func() error {
if u.isNativeFullscreen() {
return nil
}
w, h := u.windowWidth, u.windowHeight w, h := u.windowWidth, u.windowHeight
u.setWindowSize(w, h, fullscreen) u.setWindowSize(w, h, fullscreen)
return nil return nil
@ -783,7 +779,7 @@ func (u *UserInterface) registerWindowSetSizeCallback() {
if u.window.GetAttrib(glfw.Resizable) == glfw.False { if u.window.GetAttrib(glfw.Resizable) == glfw.False {
return return
} }
if u.isFullscreen() { if u.isFullscreen() && !u.isNativeFullscreen() {
return return
} }
@ -849,8 +845,8 @@ func (u *UserInterface) init() error {
glfw.WindowHint(glfw.ClientAPI, glfw.NoAPI) glfw.WindowHint(glfw.ClientAPI, glfw.NoAPI)
} }
// Enable auto-iconifying on Windows and macOS until some fullscreen issues are solved (#1506). // Enable auto-iconifying on Windows until some fullscreen issues are solved (#1506).
if runtime.GOOS == "windows" || runtime.GOOS == "darwin" { if runtime.GOOS == "windows" {
glfw.WindowHint(glfw.AutoIconify, glfw.True) glfw.WindowHint(glfw.AutoIconify, glfw.True)
} else { } else {
glfw.WindowHint(glfw.AutoIconify, glfw.False) glfw.WindowHint(glfw.AutoIconify, glfw.False)
@ -1205,7 +1201,7 @@ func (u *UserInterface) adjustWindowSizeBasedOnSizeLimitsInDP(width, height int)
func (u *UserInterface) setWindowSize(width, height int, fullscreen bool) { func (u *UserInterface) setWindowSize(width, height int, fullscreen bool) {
width, height = u.adjustWindowSizeBasedOnSizeLimits(width, height) width, height = u.adjustWindowSizeBasedOnSizeLimits(width, height)
u.Graphics().SetFullscreen(fullscreen || u.isNativeFullscreen()) u.Graphics().SetFullscreen(fullscreen)
if u.windowWidth == width && u.windowHeight == height && u.isFullscreen() == fullscreen && u.lastDeviceScaleFactor == u.deviceScaleFactor() { if u.windowWidth == width && u.windowHeight == height && u.isFullscreen() == fullscreen && u.lastDeviceScaleFactor == u.deviceScaleFactor() {
return return
@ -1235,12 +1231,32 @@ func (u *UserInterface) setWindowSize(width, height int, fullscreen bool) {
}() }()
} }
windowRecreated := u.setWindowSizeImpl(width, height, fullscreen)
// As width might be updated, update windowWidth/Height here.
u.windowWidth = width
u.windowHeight = height
u.toChangeSize = true
if windowRecreated {
if g, ok := u.Graphics().(interface{ SetWindow(uintptr) }); ok {
g.SetWindow(u.nativeWindow())
}
}
}
func (u *UserInterface) setWindowSizeImpl(width, height int, fullscreen bool) bool {
var windowRecreated bool var windowRecreated bool
if fullscreen { if fullscreen {
if u.origPosX == invalidPos || u.origPosY == invalidPos { if x, y := u.origPos(); x == invalidPos || y == invalidPos {
u.origPosX, u.origPosY = u.window.GetPos() u.setOrigPos(u.window.GetPos())
} }
if u.isNativeFullscreenAvailable() {
u.setNativeFullscreen(fullscreen)
} else {
m := currentMonitor(u.window) m := currentMonitor(u.window)
v := m.GetVideoMode() v := m.GetVideoMode()
u.window.SetMonitor(m, 0, 0, v.Width, v.Height, v.RefreshRate) u.window.SetMonitor(m, 0, 0, v.Width, v.Height, v.RefreshRate)
@ -1251,6 +1267,7 @@ func (u *UserInterface) setWindowSize(width, height int, fullscreen bool) {
glfw.PollEvents() glfw.PollEvents()
u.swapBuffers() u.swapBuffers()
} }
}
} else { } else {
// On Windows, giving a too small width doesn't call a callback (#165). // On Windows, giving a too small width doesn't call a callback (#165).
// To prevent hanging up, return asap if the width is too small. // To prevent hanging up, return asap if the width is too small.
@ -1263,7 +1280,9 @@ func (u *UserInterface) setWindowSize(width, height int, fullscreen bool) {
width = minWindowWidth width = minWindowWidth
} }
if u.window.GetMonitor() != nil { if u.isNativeFullscreenAvailable() && u.isNativeFullscreen() {
u.setNativeFullscreen(false)
} else if !u.isNativeFullscreenAvailable() && u.window.GetMonitor() != nil {
if u.Graphics().IsGL() { if u.Graphics().IsGL() {
// When OpenGL is used, swapping buffer is enough to solve the image-lag // When OpenGL is used, swapping buffer is enough to solve the image-lag
// issue (#1004). Rather, recreating window destroys GPU resources. // issue (#1004). Rather, recreating window destroys GPU resources.
@ -1289,9 +1308,7 @@ func (u *UserInterface) setWindowSize(width, height int, fullscreen bool) {
} }
} }
if u.origPosX != invalidPos && u.origPosY != invalidPos { if x, y := u.origPos(); x != invalidPos && y != invalidPos {
x := u.origPosX
y := u.origPosY
u.window.SetPos(x, y) u.window.SetPos(x, y)
// Dirty hack for macOS (#703). Rendering doesn't work correctly with one SetPos, but // Dirty hack for macOS (#703). Rendering doesn't work correctly with one SetPos, but
// work with two or more SetPos. // work with two or more SetPos.
@ -1299,8 +1316,7 @@ func (u *UserInterface) setWindowSize(width, height int, fullscreen bool) {
u.window.SetPos(x+1, y) u.window.SetPos(x+1, y)
u.window.SetPos(x, y) u.window.SetPos(x, y)
} }
u.origPosX = invalidPos u.setOrigPos(invalidPos, invalidPos)
u.origPosY = invalidPos
} }
// Set the window size after the position. The order matters. // Set the window size after the position. The order matters.
@ -1350,17 +1366,7 @@ func (u *UserInterface) setWindowSize(width, height int, fullscreen bool) {
u.window.SetTitle(u.title) u.window.SetTitle(u.title)
} }
// As width might be updated, update windowWidth/Height here. return windowRecreated
u.windowWidth = width
u.windowHeight = height
u.toChangeSize = true
if windowRecreated {
if g, ok := u.Graphics().(interface{ SetWindow(uintptr) }); ok {
g.SetWindow(u.nativeWindow())
}
}
} }
// updateVsync must be called on the main thread. // updateVsync must be called on the main thread.
@ -1625,7 +1631,7 @@ func (u *UserInterface) setWindowPosition(x, y int) {
xf := u.toGLFWPixel(float64(x)) xf := u.toGLFWPixel(float64(x))
yf := u.toGLFWPixel(float64(y)) yf := u.toGLFWPixel(float64(y))
if x, y := u.adjustWindowPosition(mx+int(xf), my+int(yf)); u.isFullscreen() { if x, y := u.adjustWindowPosition(mx+int(xf), my+int(yf)); u.isFullscreen() {
u.origPosX, u.origPosY = x, y u.setOrigPos(x, y)
} else { } else {
u.window.SetPos(x, y) u.window.SetPos(x, y)
} }
@ -1654,3 +1660,26 @@ func (u *UserInterface) setWindowTitle(title string) {
u.window.SetTitle(title) u.window.SetTitle(title)
} }
func (u *UserInterface) origPos() (int, int) {
// On macOS, the window can be fullscreened without calling an Ebiten function.
// Then, an original position might not be available by u.window.GetPos().
// Do not rely on the window position.
if u.isNativeFullscreenAvailable() {
return invalidPos, invalidPos
}
return u.origPosX, u.origPosY
}
func (u *UserInterface) setOrigPos(x, y int) {
// TODO: The original position should be updated at a 'PosCallback'.
// On macOS, the window can be fullscreened without calling an Ebiten function.
// Then, an original position might not be available by u.window.GetPos().
// Do not rely on the window position.
if u.isNativeFullscreenAvailable() {
return
}
u.origPosX = x
u.origPosY = y
}

View File

@ -50,6 +50,21 @@ package glfw
// return (window.styleMask & NSWindowStyleMaskFullScreen) != 0; // return (window.styleMask & NSWindowStyleMaskFullScreen) != 0;
// } // }
// //
// static void setNativeFullscreen(uintptr_t windowPtr, bool fullscreen) {
// NSWindow* window = (NSWindow*)windowPtr;
// if (((window.styleMask & NSWindowStyleMaskFullScreen) != 0) == fullscreen) {
// return;
// }
// bool origResizable = window.styleMask & NSWindowStyleMaskResizable;
// if (!origResizable) {
// window.styleMask |= NSWindowStyleMaskResizable;
// }
// [window toggleFullScreen:nil];
// if (!origResizable) {
// window.styleMask &= ~NSWindowStyleMaskResizable;
// }
// }
//
// static void setNativeCursor(int cursorID) { // static void setNativeCursor(int cursorID) {
// id cursor = [[NSCursor class] performSelector:@selector(arrowCursor)]; // id cursor = [[NSCursor class] performSelector:@selector(arrowCursor)];
// switch (cursorID) { // switch (cursorID) {
@ -139,3 +154,13 @@ func (u *UserInterface) isNativeFullscreen() bool {
func (u *UserInterface) setNativeCursor(shape driver.CursorShape) { func (u *UserInterface) setNativeCursor(shape driver.CursorShape) {
C.setNativeCursor(C.int(shape)) C.setNativeCursor(C.int(shape))
} }
func (u *UserInterface) isNativeFullscreenAvailable() bool {
return true
}
func (u *UserInterface) setNativeFullscreen(fullscreen bool) {
// Toggling fullscreen might ignore events like keyUp. Ensure that events are fired.
glfw.WaitEventsTimeout(1)
C.setNativeFullscreen(C.uintptr_t(u.window.GetCocoaWindow()), C.bool(fullscreen))
}

View File

@ -20,6 +20,7 @@ package glfw
import ( import (
"math" "math"
"runtime"
"github.com/hajimehoshi/ebiten/v2/internal/driver" "github.com/hajimehoshi/ebiten/v2/internal/driver"
"github.com/hajimehoshi/ebiten/v2/internal/glfw" "github.com/hajimehoshi/ebiten/v2/internal/glfw"
@ -146,3 +147,11 @@ func (u *UserInterface) setNativeCursor(shape driver.CursorShape) {
// TODO: Use native API in the future (#1571) // TODO: Use native API in the future (#1571)
u.window.SetCursor(glfwSystemCursors[shape]) u.window.SetCursor(glfwSystemCursors[shape])
} }
func (u *UserInterface) isNativeFullscreenAvailable() bool {
return false
}
func (u *UserInterface) setNativeFullscreen(fullscreen bool) {
panic(fmt.Sprintf("glfw: setNativeFullscreen is not implemented in this environment: %s", runtime.GOOS))
}

View File

@ -16,6 +16,7 @@ package glfw
import ( import (
"fmt" "fmt"
"runtime"
"unsafe" "unsafe"
"golang.org/x/sys/windows" "golang.org/x/sys/windows"
@ -194,3 +195,11 @@ func (u *UserInterface) setNativeCursor(shape driver.CursorShape) {
// TODO: Use native API in the future (#1571) // TODO: Use native API in the future (#1571)
u.window.SetCursor(glfwSystemCursors[shape]) u.window.SetCursor(glfwSystemCursors[shape])
} }
func (u *UserInterface) isNativeFullscreenAvailable() bool {
return false
}
func (u *UserInterface) setNativeFullscreen(fullscreen bool) {
panic(fmt.Sprintf("glfw: setNativeFullscreen is not implemented in this environment: %s", runtime.GOOS))
}

View File

@ -176,8 +176,8 @@ func (w *window) Position() (int, int) {
x, y := 0, 0 x, y := 0, 0
_ = w.ui.t.Call(func() error { _ = w.ui.t.Call(func() error {
var wx, wy int var wx, wy int
if w.ui.isFullscreen() { if w.ui.isFullscreen() && !w.ui.isNativeFullscreenAvailable() {
wx, wy = w.ui.origPosX, w.ui.origPosY wx, wy = w.ui.origPos()
} else { } else {
wx, wy = w.ui.window.GetPos() wx, wy = w.ui.window.GetPos()
} }
@ -223,6 +223,12 @@ func (w *window) SetSize(width, height int) {
return return
} }
_ = w.ui.t.Call(func() error { _ = w.ui.t.Call(func() error {
// When a window is a native fullscreen, forcing to resize the window might leave unexpected image lags.
// Forbid this.
if w.ui.isNativeFullscreen() {
return nil
}
ww := int(w.ui.toGLFWPixel(float64(width))) ww := int(w.ui.toGLFWPixel(float64(width)))
wh := int(w.ui.toGLFWPixel(float64(height))) wh := int(w.ui.toGLFWPixel(float64(height)))
w.ui.setWindowSize(ww, wh, w.ui.isFullscreen()) w.ui.setWindowSize(ww, wh, w.ui.isFullscreen())