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

View File

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

View File

@ -70,6 +70,7 @@ type UserInterface struct {
cursorShape CursorShape cursorShape CursorShape
windowClosingHandled bool windowClosingHandled bool
windowBeingClosed bool windowBeingClosed bool
windowResizingMode WindowResizingMode
// setSizeCallbackEnabled must be accessed from the main thread. // setSizeCallbackEnabled must be accessed from the main thread.
setSizeCallbackEnabled bool setSizeCallbackEnabled bool
@ -88,7 +89,6 @@ type UserInterface struct {
initFullscreen bool initFullscreen bool
initCursorMode CursorMode initCursorMode CursorMode
initWindowDecorated bool initWindowDecorated bool
initWindowResizable bool
initWindowPositionXInDIP int initWindowPositionXInDIP int
initWindowPositionYInDIP int initWindowPositionYInDIP int
initWindowWidthInDIP int initWindowWidthInDIP int
@ -346,19 +346,6 @@ func (u *UserInterface) setRunnableOnUnfocused(runnableOnUnfocused bool) {
u.m.Unlock() 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 { func (u *UserInterface) isInitScreenTransparent() bool {
u.m.RLock() u.m.RLock()
v := u.initScreenTransparent 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). // 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. // 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 floating := glfw.False
if u.isInitWindowFloating() { if u.isInitWindowFloating() {
@ -908,13 +899,13 @@ func (u *UserInterface) init() error {
u.setWindowPositionInDIP(wx, wy, u.initMonitor) u.setWindowPositionInDIP(wx, wy, u.initMonitor)
u.setWindowSizeInDIP(ww, wh, u.isFullscreen()) u.setWindowSizeInDIP(ww, wh, u.isFullscreen())
u.setWindowResizable(u.isInitWindowResizable())
// Maximizing a window requires a proper size and position. Call Maximize here (#1117). // Maximizing a window requires a proper size and position. Call Maximize here (#1117).
if u.isInitWindowMaximized() { if u.isInitWindowMaximized() {
u.window.Maximize() u.window.Maximize()
} }
u.setWindowResizingModeForOS(u.windowResizingMode)
u.window.Show() u.window.Show()
if g, ok := Graphics().(interface{ SetWindow(uintptr) }); ok { if g, ok := Graphics().(interface{ SetWindow(uintptr) }); ok {
@ -1520,8 +1511,12 @@ func (u *UserInterface) setWindowFloating(floating bool) {
u.window.SetAttrib(glfw.Floating, v) u.window.SetAttrib(glfw.Floating, v)
} }
// setWindowResizable must be called from the main thread. // setWindowResizingMode must be called from the main thread.
func (u *UserInterface) setWindowResizable(resizable bool) { func (u *UserInterface) setWindowResizingMode(mode WindowResizingMode) {
if u.windowResizingMode == mode {
return
}
if u.setSizeCallbackEnabled { if u.setSizeCallbackEnabled {
u.setSizeCallbackEnabled = false u.setSizeCallbackEnabled = false
defer func() { defer func() {
@ -1529,11 +1524,14 @@ func (u *UserInterface) setWindowResizable(resizable bool) {
}() }()
} }
u.windowResizingMode = mode
v := glfw.False v := glfw.False
if resizable { if mode == WindowResizingModeEnabled {
v = glfw.True v = glfw.True
} }
u.window.SetAttrib(glfw.Resizable, v) u.window.SetAttrib(glfw.Resizable, v)
u.setWindowResizingModeForOS(mode)
} }
// setWindowPositionInDIP sets the window position. // setWindowPositionInDIP sets the window position.

View File

@ -22,6 +22,91 @@ package ui
// //
// #import <AppKit/AppKit.h> // #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) { // static void currentMonitorPos(uintptr_t windowPtr, int* x, int* y) {
// @autoreleasepool { // @autoreleasepool {
// NSScreen* screen = [NSScreen mainScreen]; // NSScreen* screen = [NSScreen mainScreen];
@ -55,6 +140,9 @@ package ui
// if (((window.styleMask & NSWindowStyleMaskFullScreen) != 0) == fullscreen) { // if (((window.styleMask & NSWindowStyleMaskFullScreen) != 0) == fullscreen) {
// return; // 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; // bool origResizable = window.styleMask & NSWindowStyleMaskResizable;
// if (!origResizable) { // if (!origResizable) {
// window.styleMask |= NSWindowStyleMaskResizable; // window.styleMask |= NSWindowStyleMaskResizable;
@ -126,6 +214,15 @@ package ui
// *x = (int)(location.x); // *x = (int)(location.x);
// *y = (int)(location.y); // *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 "C"
import ( import (
@ -225,5 +322,14 @@ func (u *UserInterface) adjustViewSize() {
C.adjustViewSize(C.uintptr_t(u.window.GetCocoaWindow())) 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) adjustViewSize() {
} }
func (u *UserInterface) setWindowResizingModeForOS(mode WindowResizingMode) {
}
func initializeWindowAfterCreation(w *glfw.Window) { func initializeWindowAfterCreation(w *glfw.Window) {
// Show the window once before getting the position of the window. // Show the window once before getting the position of the window.
// On Linux/Unix, the window position is not reliable before showing. // 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) adjustViewSize() {
} }
func (u *UserInterface) setWindowResizingModeForOS(mode WindowResizingMode) {
}
func initializeWindowAfterCreation(w *glfw.Window) { 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() { 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() { 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() { if !w.ui.isRunning() {
w.ui.setInitWindowResizable(resizable) w.ui.m.Lock()
w.ui.windowResizingMode = mode
w.ui.m.Unlock()
return return
} }
w.ui.t.Call(func() { w.ui.t.Call(func() {
if w.ui.isNativeFullscreen() { if w.ui.isNativeFullscreen() {
return return
} }
w.ui.setWindowResizable(resizable) w.ui.setWindowResizingMode(mode)
}) })
} }
@ -105,7 +110,7 @@ func (w *Window) IsMaximized() bool {
if !w.ui.isRunning() { if !w.ui.isRunning() {
return w.ui.isInitWindowMaximized() return w.ui.isInitWindowMaximized()
} }
if !w.IsResizable() { if w.ResizingMode() != WindowResizingModeEnabled {
return false return false
} }
var v bool var v bool
@ -119,7 +124,7 @@ func (w *Window) Maximize() {
// Do not allow maximizing the window when the window is not resizable. // 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, // On Windows, it is possible to restore the window from being maximized by mouse-dragging,
// and this can be an unexpected behavior. // and this can be an unexpected behavior.
if !w.IsResizable() { if w.ResizingMode() != WindowResizingModeEnabled {
panic("ui: a window to maximize must be resizable") panic("ui: a window to maximize must be resizable")
} }
if !w.ui.isRunning() { if !w.ui.isRunning() {

View File

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

View File

@ -21,6 +21,27 @@ import (
"github.com/hajimehoshi/ebiten/v2/internal/ui" "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 reports whether the window is decorated.
// //
// IsWindowDecorated is concurrent-safe. // IsWindowDecorated is concurrent-safe.
@ -42,24 +63,42 @@ func SetWindowDecorated(decorated bool) {
ui.Get().Window().SetDecorated(decorated) 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. // IsWindowResizable reports whether the window is resizable by the user's dragging on desktops.
// On the other environments, IsWindowResizable always returns false. // On the other environments, IsWindowResizable always returns false.
// //
// IsWindowResizable is concurrent-safe. // Deprecated: as of v2.2. Use WindowResizingMode instead.
func IsWindowResizable() bool { 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. // SetWindowResizable sets whether the window is resizable by the user's dragging on desktops.
// On the other environments, SetWindowResizable does nothing. // On the other environments, SetWindowResizable does nothing.
// //
// The window is not resizable by default. // Deprecated: as of v2.2, Use SetWindowResizingMode instead.
//
// SetWindowResizable does nothing on macOS when the window is fullscreened.
//
// SetWindowResizable is concurrent-safe.
func SetWindowResizable(resizable bool) { 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. // SetWindowTitle sets the title of the window.
@ -200,7 +239,7 @@ func SetWindowFloating(float bool) {
// MaximizeWindow maximizes the window. // 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. // MaximizeWindow does nothing on browsers or mobiles.
// //
@ -211,7 +250,7 @@ func MaximizeWindow() {
// IsWindowMaximized reports whether the window is maximized or not. // 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. // IsWindowMaximized always returns false on browsers and mobiles.
// //