ebiten: add WindowResizingModeType and its constants and functions

This allows a new state to disallow resizing the window but allow
making the window fullscreen on macOS by a user.

This change adds the new type WindowResizingModeType. There are
these constants of this type:

 * WindowResizingModeDisabled
 * WindowResizingModeOnlyFullscreenEnabled
 * WindowResizingModeEnabled

Closes #1819
This commit is contained in:
Hajime Hoshi 2021-12-29 22:08:59 +09:00
parent 3804d4c92d
commit 2c2c4bc428
9 changed files with 225 additions and 54 deletions

View File

@ -131,7 +131,7 @@ func (g *game) Update() error {
positionX, positionY := ebiten.WindowPosition()
g.transparent = ebiten.IsScreenTransparent()
floating := ebiten.IsWindowFloating()
resizable := ebiten.IsWindowResizable()
resizingMode := ebiten.WindowResizingMode()
screenCleared := ebiten.IsScreenClearedEveryFrame()
const d = 16
@ -238,7 +238,16 @@ func (g *game) Update() error {
floating = !floating
}
if inpututil.IsKeyJustPressed(ebiten.KeyR) {
resizable = !resizable
switch resizingMode {
case ebiten.WindowResizingModeDisabled:
resizingMode = ebiten.WindowResizingModeOnlyFullscreenEnabled
case ebiten.WindowResizingModeOnlyFullscreenEnabled:
resizingMode = ebiten.WindowResizingModeEnabled
case ebiten.WindowResizingModeEnabled:
resizingMode = ebiten.WindowResizingModeDisabled
default:
panic("not reached")
}
}
if inpututil.IsKeyJustPressed(ebiten.KeyW) {
screenCleared = !screenCleared
@ -275,7 +284,7 @@ func (g *game) Update() error {
}
ebiten.SetWindowFloating(floating)
ebiten.SetScreenClearedEveryFrame(screenCleared)
if maximize && ebiten.IsWindowResizable() {
if maximize && ebiten.WindowResizingMode() == ebiten.WindowResizingModeEnabled {
ebiten.MaximizeWindow()
}
if minimize {
@ -284,7 +293,7 @@ func (g *game) Update() error {
if restore {
ebiten.RestoreWindow()
}
ebiten.SetWindowResizable(resizable)
ebiten.SetWindowResizingMode(resizingMode)
if inpututil.IsKeyJustPressed(ebiten.KeyI) {
ebiten.SetWindowIcon([]image.Image{createRandomIconImage()})
@ -314,7 +323,7 @@ func (g *game) Draw(screen *ebiten.Image) {
}
var lines []string
if !ebiten.IsWindowMaximized() && ebiten.IsWindowResizable() {
if !ebiten.IsWindowMaximized() && ebiten.WindowResizingMode() == ebiten.WindowResizingModeEnabled {
lines = append(lines, "[M] Maximize the window (only for desktops)")
}
if !ebiten.IsWindowMinimized() {
@ -325,7 +334,7 @@ func (g *game) Draw(screen *ebiten.Image) {
}
msgM := strings.Join(lines, "\n")
msgR := "[R] Switch the window resizable state (only for desktops)\n"
msgR := "[R] Switch the window resizing mode (only for desktops)\n"
fg := "Yes"
if !ebiten.IsFocused() {
fg = "No"
@ -405,20 +414,20 @@ func main() {
ebiten.SetFullscreen(true)
}
if *flagResizable {
ebiten.SetWindowResizable(true)
ebiten.SetWindowResizingMode(ebiten.WindowResizingModeEnabled)
}
if *flagFloating {
ebiten.SetWindowFloating(true)
}
if *flagMaximize {
ebiten.SetWindowResizable(true)
ebiten.SetWindowResizingMode(ebiten.WindowResizingModeEnabled)
ebiten.MaximizeWindow()
}
if !*flagVsync {
ebiten.SetFPSMode(ebiten.FPSModeVsyncOffMaximum)
}
if *flagAutoAdjusting {
ebiten.SetWindowResizable(true)
ebiten.SetWindowResizingMode(ebiten.WindowResizingModeEnabled)
}
ebiten.SetInitFocused(*flagInitFocused)
@ -438,7 +447,7 @@ func main() {
}
if minw >= 0 || minh >= 0 || maxw >= 0 || maxh >= 0 {
ebiten.SetWindowSizeLimits(minw, minh, maxw, maxh)
ebiten.SetWindowResizable(true)
ebiten.SetWindowResizingMode(ebiten.WindowResizingModeEnabled)
}
const title = "Window Size (Ebiten Demo)"

View File

@ -68,3 +68,11 @@ const (
CursorShapeEWResize
CursorShapeNSResize
)
type WindowResizingMode int
const (
WindowResizingModeDisabled WindowResizingMode = iota
WindowResizingModeOnlyFullscreenEnabled
WindowResizingModeEnabled
)

View File

@ -70,6 +70,7 @@ type UserInterface struct {
cursorShape CursorShape
windowClosingHandled bool
windowBeingClosed bool
windowResizingMode WindowResizingMode
// setSizeCallbackEnabled must be accessed from the main thread.
setSizeCallbackEnabled bool
@ -88,7 +89,6 @@ type UserInterface struct {
initFullscreen bool
initCursorMode CursorMode
initWindowDecorated bool
initWindowResizable bool
initWindowPositionXInDIP int
initWindowPositionYInDIP int
initWindowWidthInDIP int
@ -346,19 +346,6 @@ func (u *UserInterface) setRunnableOnUnfocused(runnableOnUnfocused bool) {
u.m.Unlock()
}
func (u *UserInterface) isInitWindowResizable() bool {
u.m.RLock()
v := u.initWindowResizable
u.m.RUnlock()
return v
}
func (u *UserInterface) setInitWindowResizable(resizable bool) {
u.m.Lock()
u.initWindowResizable = resizable
u.m.Unlock()
}
func (u *UserInterface) isInitScreenTransparent() bool {
u.m.RLock()
v := u.initScreenTransparent
@ -873,7 +860,11 @@ func (u *UserInterface) init() error {
// Before creating a window, set it unresizable no matter what u.isInitWindowResizable() is (#1987).
// Making the window resizable here doesn't work correctly when switching to enable resizing.
glfw.WindowHint(glfw.Resizable, glfw.False)
resizable := glfw.False
if u.windowResizingMode == WindowResizingModeEnabled {
resizable = glfw.True
}
glfw.WindowHint(glfw.Resizable, resizable)
floating := glfw.False
if u.isInitWindowFloating() {
@ -908,13 +899,13 @@ func (u *UserInterface) init() error {
u.setWindowPositionInDIP(wx, wy, u.initMonitor)
u.setWindowSizeInDIP(ww, wh, u.isFullscreen())
u.setWindowResizable(u.isInitWindowResizable())
// Maximizing a window requires a proper size and position. Call Maximize here (#1117).
if u.isInitWindowMaximized() {
u.window.Maximize()
}
u.setWindowResizingModeForOS(u.windowResizingMode)
u.window.Show()
if g, ok := Graphics().(interface{ SetWindow(uintptr) }); ok {
@ -1520,8 +1511,12 @@ func (u *UserInterface) setWindowFloating(floating bool) {
u.window.SetAttrib(glfw.Floating, v)
}
// setWindowResizable must be called from the main thread.
func (u *UserInterface) setWindowResizable(resizable bool) {
// setWindowResizingMode must be called from the main thread.
func (u *UserInterface) setWindowResizingMode(mode WindowResizingMode) {
if u.windowResizingMode == mode {
return
}
if u.setSizeCallbackEnabled {
u.setSizeCallbackEnabled = false
defer func() {
@ -1529,11 +1524,14 @@ func (u *UserInterface) setWindowResizable(resizable bool) {
}()
}
u.windowResizingMode = mode
v := glfw.False
if resizable {
if mode == WindowResizingModeEnabled {
v = glfw.True
}
u.window.SetAttrib(glfw.Resizable, v)
u.setWindowResizingModeForOS(mode)
}
// setWindowPositionInDIP sets the window position.

View File

@ -22,6 +22,91 @@ package ui
//
// #import <AppKit/AppKit.h>
//
// @interface EbitenWindowDelegate : NSObject <NSWindowDelegate>
// @end
//
// @implementation EbitenWindowDelegate {
// id<NSWindowDelegate> origDelegate_;
// bool origResizable_;
// }
//
// - (instancetype)initWithOrigDelegate:(id<NSWindowDelegate>)origDelegate {
// self = [super init];
// if (self != nil) {
// origDelegate_ = origDelegate;
// }
// return self;
// }
//
// // The method set of origDelegate_ must sync with GLFWWindowDelegate's implementation.
// // See cocoa_window.m in GLFW.
// - (BOOL)windowShouldClose:(id)sender {
// return [origDelegate_ windowShouldClose:sender];
// }
// - (void)windowDidResize:(NSNotification *)notification {
// [origDelegate_ windowDidResize:notification];
// }
// - (void)windowDidMove:(NSNotification *)notification {
// [origDelegate_ windowDidMove:notification];
// }
// - (void)windowDidMiniaturize:(NSNotification *)notification {
// [origDelegate_ windowDidMiniaturize:notification];
// }
// - (void)windowDidDeminiaturize:(NSNotification *)notification {
// [origDelegate_ windowDidDeminiaturize:notification];
// }
// - (void)windowDidBecomeKey:(NSNotification *)notification {
// [origDelegate_ windowDidBecomeKey:notification];
// }
// - (void)windowDidResignKey:(NSNotification *)notification {
// [origDelegate_ windowDidResignKey:notification];
// }
// - (void)windowDidChangeOcclusionState:(NSNotification* )notification {
// [origDelegate_ windowDidChangeOcclusionState:notification];
// }
//
// - (void)pushResizableState:(NSWindow*)window {
// origResizable_ = window.styleMask & NSWindowStyleMaskResizable;
// if (!origResizable_) {
// window.styleMask |= NSWindowStyleMaskResizable;
// }
// }
//
// - (void)popResizableState:(NSWindow*)window {
// if (!origResizable_) {
// window.styleMask &= ~NSWindowStyleMaskResizable;
// }
// origResizable_ = false;
// }
//
// - (void)windowWillEnterFullScreen:(NSNotification *)notification {
// NSWindow* window = (NSWindow*)[notification object];
// [self pushResizableState:window];
// }
//
// - (void)windowDidEnterFullScreen:(NSNotification *)notification {
// NSWindow* window = (NSWindow*)[notification object];
// [self popResizableState:window];
// }
//
// - (void)windowWillExitFullScreen:(NSNotification *)notification {
// NSWindow* window = (NSWindow*)[notification object];
// [self pushResizableState:window];
// }
//
// - (void)windowDidExitFullScreen:(NSNotification *)notification {
// NSWindow* window = (NSWindow*)[notification object];
// [self popResizableState:window];
// }
//
// @end
//
// static void initializeWindow(uintptr_t windowPtr) {
// NSWindow* window = (NSWindow*)windowPtr;
// // This delegate is never released. This assumes that the window lives until the process lives.
// window.delegate = [[EbitenWindowDelegate alloc] initWithOrigDelegate:window.delegate];
// }
//
// static void currentMonitorPos(uintptr_t windowPtr, int* x, int* y) {
// @autoreleasepool {
// NSScreen* screen = [NSScreen mainScreen];
@ -55,6 +140,9 @@ package ui
// if (((window.styleMask & NSWindowStyleMaskFullScreen) != 0) == fullscreen) {
// return;
// }
//
// // Even though EbitenWindowDelegate is used, this hack is still required.
// // toggleFullscreen doesn't work when the window is not resizable.
// bool origResizable = window.styleMask & NSWindowStyleMaskResizable;
// if (!origResizable) {
// window.styleMask |= NSWindowStyleMaskResizable;
@ -126,6 +214,15 @@ package ui
// *x = (int)(location.x);
// *y = (int)(location.y);
// }
//
// static void setAllowFullscreen(uintptr_t windowPtr, bool allowFullscreen) {
// NSWindow* window = (NSWindow*)windowPtr;
// if (allowFullscreen) {
// window.collectionBehavior |= NSWindowCollectionBehaviorFullScreenPrimary;
// } else {
// window.collectionBehavior &= ~NSWindowCollectionBehaviorFullScreenPrimary;
// }
// }
import "C"
import (
@ -225,5 +322,14 @@ func (u *UserInterface) adjustViewSize() {
C.adjustViewSize(C.uintptr_t(u.window.GetCocoaWindow()))
}
func initializeWindowAfterCreation(w *glfw.Window) {
func (u *UserInterface) setWindowResizingModeForOS(mode WindowResizingMode) {
allowFullscreen := mode == WindowResizingModeOnlyFullscreenEnabled ||
mode == WindowResizingModeEnabled
C.setAllowFullscreen(C.uintptr_t(u.window.GetCocoaWindow()), C.bool(allowFullscreen))
}
func initializeWindowAfterCreation(w *glfw.Window) {
// TODO: Register NSWindowWillEnterFullScreenNotification and so on.
// Enable resizing temporary before making the window fullscreen.
C.initializeWindow(C.uintptr_t(w.GetCocoaWindow()))
}

View File

@ -189,6 +189,9 @@ func (u *UserInterface) setNativeFullscreen(fullscreen bool) {
func (u *UserInterface) adjustViewSize() {
}
func (u *UserInterface) setWindowResizingModeForOS(mode WindowResizingMode) {
}
func initializeWindowAfterCreation(w *glfw.Window) {
// Show the window once before getting the position of the window.
// On Linux/Unix, the window position is not reliable before showing.

View File

@ -203,5 +203,8 @@ func (u *UserInterface) setNativeFullscreen(fullscreen bool) {
func (u *UserInterface) adjustViewSize() {
}
func (u *UserInterface) setWindowResizingModeForOS(mode WindowResizingMode) {
}
func initializeWindowAfterCreation(w *glfw.Window) {
}

View File

@ -53,27 +53,32 @@ func (w *Window) SetDecorated(decorated bool) {
})
}
func (w *Window) IsResizable() bool {
func (w *Window) ResizingMode() WindowResizingMode {
if !w.ui.isRunning() {
return w.ui.isInitWindowResizable()
w.ui.m.Lock()
mode := w.ui.windowResizingMode
w.ui.m.Unlock()
return mode
}
v := false
var mode WindowResizingMode
w.ui.t.Call(func() {
v = w.ui.window.GetAttrib(glfw.Resizable) == glfw.True
mode = w.ui.windowResizingMode
})
return v
return mode
}
func (w *Window) SetResizable(resizable bool) {
func (w *Window) SetResizingMode(mode WindowResizingMode) {
if !w.ui.isRunning() {
w.ui.setInitWindowResizable(resizable)
w.ui.m.Lock()
w.ui.windowResizingMode = mode
w.ui.m.Unlock()
return
}
w.ui.t.Call(func() {
if w.ui.isNativeFullscreen() {
return
}
w.ui.setWindowResizable(resizable)
w.ui.setWindowResizingMode(mode)
})
}
@ -105,7 +110,7 @@ func (w *Window) IsMaximized() bool {
if !w.ui.isRunning() {
return w.ui.isInitWindowMaximized()
}
if !w.IsResizable() {
if w.ResizingMode() != WindowResizingModeEnabled {
return false
}
var v bool
@ -119,7 +124,7 @@ func (w *Window) Maximize() {
// Do not allow maximizing the window when the window is not resizable.
// On Windows, it is possible to restore the window from being maximized by mouse-dragging,
// and this can be an unexpected behavior.
if !w.IsResizable() {
if w.ResizingMode() != WindowResizingModeEnabled {
panic("ui: a window to maximize must be resizable")
}
if !w.ui.isRunning() {

View File

@ -30,11 +30,11 @@ func (*Window) IsDecorated() bool {
func (*Window) SetDecorated(decorated bool) {
}
func (*Window) IsResizable() bool {
return false
func (*Window) ResizingMode() WindowResizingMode {
return WindowResizingModeDisabled
}
func (*Window) SetResizable(resizable bool) {
func (*Window) SetResizingMode(mode WindowResizingMode) {
}
func (*Window) Position() (int, int) {

View File

@ -21,6 +21,27 @@ import (
"github.com/hajimehoshi/ebiten/v2/internal/ui"
)
// WindowResizingModeType represents a mode in which a user resizes the window.
//
// Regardless of the resizing mode, an Ebiten application can still change the window size or make
// the window fullscreen by calling Ebiten functions.
type WindowResizingModeType = ui.WindowResizingMode
// WindowResizingModeTypes
const (
// WindowResizingModeDisabled indicates the mode to disallow resizing the window by a user.
WindowResizingModeDisabled WindowResizingModeType = WindowResizingModeType(ui.WindowResizingModeDisabled)
// WindowResizingModeDisabled indicates the mode to disallow resizing the window,
// but allow to make the window fullscreen by a user.
// This works only on macOS so far.
// On the other platforms, this is the same as WindowResizingModeDisabled.
WindowResizingModeOnlyFullscreenEnabled WindowResizingModeType = WindowResizingModeType(ui.WindowResizingModeOnlyFullscreenEnabled)
// WindowResizingModeEnabled indicates the mode to allow resizing the window by a user.
WindowResizingModeEnabled WindowResizingModeType = WindowResizingModeType(ui.WindowResizingModeEnabled)
)
// IsWindowDecorated reports whether the window is decorated.
//
// IsWindowDecorated is concurrent-safe.
@ -42,24 +63,42 @@ func SetWindowDecorated(decorated bool) {
ui.Get().Window().SetDecorated(decorated)
}
// WindowResizingMode returns the current mode in which a user resizes the window.
//
// The default mode is WindowResizingModeDisabled.
//
// WindowResizingMode is concurrent-safe.
func WindowResizingMode() WindowResizingModeType {
return WindowResizingModeType(ui.Get().Window().ResizingMode())
}
// SetWindowResizingMode sets the mode in which a user resizes the window.
//
// SetWindowResizingMode does nothing on macOS when the window is fullscreened.
//
// SetWindowResizingMode is concurrent-safe.
func SetWindowResizingMode(mode WindowResizingModeType) {
ui.Get().Window().SetResizingMode(ui.WindowResizingMode(mode))
}
// IsWindowResizable reports whether the window is resizable by the user's dragging on desktops.
// On the other environments, IsWindowResizable always returns false.
//
// IsWindowResizable is concurrent-safe.
// Deprecated: as of v2.2. Use WindowResizingMode instead.
func IsWindowResizable() bool {
return ui.Get().Window().IsResizable()
return ui.Get().Window().ResizingMode() == ui.WindowResizingModeEnabled
}
// SetWindowResizable sets whether the window is resizable by the user's dragging on desktops.
// On the other environments, SetWindowResizable does nothing.
//
// The window is not resizable by default.
//
// SetWindowResizable does nothing on macOS when the window is fullscreened.
//
// SetWindowResizable is concurrent-safe.
// Deprecated: as of v2.2, Use SetWindowResizingMode instead.
func SetWindowResizable(resizable bool) {
ui.Get().Window().SetResizable(resizable)
mode := ui.WindowResizingModeDisabled
if resizable {
mode = ui.WindowResizingModeEnabled
}
ui.Get().Window().SetResizingMode(mode)
}
// SetWindowTitle sets the title of the window.
@ -200,7 +239,7 @@ func SetWindowFloating(float bool) {
// MaximizeWindow maximizes the window.
//
// MaximizeWindow panics when the window is not resizable.
// MaximizeWindow panics when the window is not resizable (WindowResizingModeEnabled).
//
// MaximizeWindow does nothing on browsers or mobiles.
//
@ -211,7 +250,7 @@ func MaximizeWindow() {
// IsWindowMaximized reports whether the window is maximized or not.
//
// IsWindowMaximized returns false when the window is not resizable.
// IsWindowMaximized returns false when the window is not resizable (WindowResizingModeEnabled).
//
// IsWindowMaximized always returns false on browsers and mobiles.
//