diff --git a/examples/windowsize/main.go b/examples/windowsize/main.go index b9008fc5e..11f77a911 100644 --- a/examples/windowsize/main.go +++ b/examples/windowsize/main.go @@ -55,10 +55,12 @@ var ( flagLegacy = flag.Bool("legacy", false, "use the legacy API") flagFullscreen = flag.Bool("fullscreen", false, "fullscreen") + flagResizable = flag.Bool("resizable", false, "make the window resizable") flagWindowPosition = flag.String("windowposition", "", "window position (e.g., 100,200)") flagScreenTransparent = flag.Bool("screentransparent", false, "screen transparent") flagAutoAdjusting = flag.Bool("autoadjusting", false, "make the game screen auto-adjusting") flagFloating = flag.Bool("floating", false, "make the window floating") + flagMaximize = flag.Bool("maximize", false, "maximize the window") ) func init() { @@ -226,6 +228,9 @@ func (g *game) Update(screen *ebiten.Image) error { if inpututil.IsKeyJustPressed(ebiten.KeyR) { resizable = !resizable } + maximize := inpututil.IsKeyJustPressed(ebiten.KeyM) + minimize := inpututil.IsKeyJustPressed(ebiten.KeyI) + restore := inpututil.IsKeyJustPressed(ebiten.KeyE) if toUpdateWindowSize { if *flagLegacy { @@ -245,6 +250,15 @@ func (g *game) Update(screen *ebiten.Image) error { ebiten.SetWindowDecorated(decorated) ebiten.SetWindowPosition(positionX, positionY) ebiten.SetWindowFloating(floating) + if maximize { + ebiten.MaximizeWindow() + } + if minimize { + ebiten.MinimizeWindow() + } + if restore { + ebiten.RestoreWindow() + } if !*flagLegacy { // A resizable window is available only with RunGame. ebiten.SetWindowResizable(resizable) @@ -279,6 +293,18 @@ func (g *game) Update(screen *ebiten.Image) error { tpsStr = fmt.Sprintf("%d", t) } + var lines []string + if !ebiten.IsWindowMaximized() { + lines = append(lines, "Press M key to maximize the window") + } + if !ebiten.IsWindowMinimized() { + lines = append(lines, "Press I key to minimize the window") + } + if ebiten.IsWindowMaximized() || ebiten.IsWindowMinimized() { + lines = append(lines, "Press E key to restore the window from maximized/minimized state") + } + msgM := strings.Join(lines, "\n") + var msgS string var msgR string if *flagLegacy { @@ -302,12 +328,13 @@ Press T key to switch TPS (ticks per second) Press D key to switch the window decoration (only for desktops) Press L key to switch the window floating state (only for desktops) %s +%s IsFocused?: %s Windows Position: (%d, %d) Cursor: (%d, %d) TPS: Current: %0.2f / Max: %s FPS: %0.2f -Device Scale Factor: %0.2f`, msgS, msgR, fg, wx, wy, cx, cy, ebiten.CurrentTPS(), tpsStr, ebiten.CurrentFPS(), ebiten.DeviceScaleFactor()) +Device Scale Factor: %0.2f`, msgS, msgM, msgR, fg, wx, wy, cx, cy, ebiten.CurrentTPS(), tpsStr, ebiten.CurrentFPS(), ebiten.DeviceScaleFactor()) ebitenutil.DebugPrint(screen, msg) return nil } @@ -370,9 +397,15 @@ func main() { if *flagFullscreen { ebiten.SetFullscreen(true) } + if *flagResizable { + ebiten.SetWindowResizable(true) + } if *flagFloating { ebiten.SetWindowFloating(true) } + if *flagMaximize { + ebiten.MaximizeWindow() + } if *flagAutoAdjusting { if *flagLegacy { log.Println("-autoadjusting flag cannot work with -legacy flag") diff --git a/internal/driver/ui.go b/internal/driver/ui.go index 38577b2dd..4c398a5fc 100644 --- a/internal/driver/ui.go +++ b/internal/driver/ui.go @@ -71,6 +71,13 @@ type Window interface { IsFloating() bool SetFloating(floating bool) + Maximize() + IsMaximized() bool + + Minimize() + IsMinimized() bool + SetIcon(iconImages []image.Image) SetTitle(title string) + Restore() } diff --git a/internal/glfw/const.go b/internal/glfw/const.go index d55c1be80..252b8fbbb 100644 --- a/internal/glfw/const.go +++ b/internal/glfw/const.go @@ -79,6 +79,8 @@ const ( Decorated = Hint(0x00020005) Floating = Hint(0x00020007) Focused = Hint(0x00020001) + Iconified = Hint(0x00020002) + Maximized = Hint(0x00020008) Resizable = Hint(0x00020003) TransparentFramebuffer = Hint(0x0002000A) Visible = Hint(0x00020004) diff --git a/internal/glfw/glfw_notwindows.go b/internal/glfw/glfw_notwindows.go index c643d04d7..c736fa92e 100644 --- a/internal/glfw/glfw_notwindows.go +++ b/internal/glfw/glfw_notwindows.go @@ -130,10 +130,22 @@ func (w *Window) GetSize() (width, height int) { return w.w.GetSize() } +func (w *Window) Iconify() { + w.w.Iconify() +} + func (w *Window) MakeContextCurrent() { w.w.MakeContextCurrent() } +func (w *Window) Maximize() { + w.w.Maximize() +} + +func (w *Window) Restore() { + w.w.Restore() +} + func (w *Window) SetCharModsCallback(cbfun CharModsCallback) (previous CharModsCallback) { var gcb glfw.CharModsCallback if cbfun != nil { diff --git a/internal/glfw/glfw_windows.go b/internal/glfw/glfw_windows.go index 182587129..68702d515 100644 --- a/internal/glfw/glfw_windows.go +++ b/internal/glfw/glfw_windows.go @@ -166,11 +166,21 @@ func (w *Window) GetSize() (int, int) { return int(width), int(height) } +func (w *Window) Iconify() { + glfwDLL.call("glfwIconifyWindow", w.w) + panicError() +} + func (w *Window) MakeContextCurrent() { glfwDLL.call("glfwMakeContextCurrent", w.w) panicError() } +func (w *Window) Maximize() { + glfwDLL.call("glfwMaximizeWindow", w.w) + panicError() +} + func (w *Window) SetCharModsCallback(cbfun CharModsCallback) (previous CharModsCallback) { var gcb uintptr if cbfun != nil { diff --git a/internal/uidriver/glfw/ui.go b/internal/uidriver/glfw/ui.go index 6c00f686f..68a9e1ea0 100644 --- a/internal/uidriver/glfw/ui.go +++ b/internal/uidriver/glfw/ui.go @@ -67,6 +67,7 @@ type UserInterface struct { initWindowWidthInDP int initWindowHeightInDP int initWindowFloating bool + initWindowMaximized bool initScreenTransparent bool initIconImages []image.Image @@ -348,6 +349,19 @@ func (u *UserInterface) setInitWindowFloating(floating bool) { u.m.Unlock() } +func (u *UserInterface) isInitWindowMaximized() bool { + u.m.Lock() + f := u.initWindowMaximized + u.m.Unlock() + return f +} + +func (u *UserInterface) setInitWindowMaximized(floating bool) { + u.m.Lock() + u.initWindowMaximized = floating + u.m.Unlock() +} + // toDeviceIndependentPixel must be called from the main thread. func (u *UserInterface) toDeviceIndependentPixel(x float64) float64 { return x / u.glfwScale() @@ -648,6 +662,12 @@ func (u *UserInterface) run(context driver.UIContext) error { } glfw.WindowHint(glfw.Floating, floating) + maximized := glfw.False + if u.isInitWindowMaximized() { + maximized = glfw.True + } + glfw.WindowHint(glfw.Maximized, maximized) + // Set the window visible explicitly or the application freezes on Wayland (#974). if os.Getenv("WAYLAND_DISPLAY") != "" { glfw.WindowHint(glfw.Visible, glfw.True) diff --git a/internal/uidriver/glfw/window.go b/internal/uidriver/glfw/window.go index 62ba3b95e..d2a0d72f9 100644 --- a/internal/uidriver/glfw/window.go +++ b/internal/uidriver/glfw/window.go @@ -116,6 +116,63 @@ func (w *window) SetFloating(floating bool) { }) } +func (w *window) IsMaximized() bool { + if !w.ui.isRunning() { + return w.ui.isInitWindowMaximized() + } + var v bool + _ = w.ui.t.Call(func() error { + v = w.ui.window.GetAttrib(glfw.Maximized) == glfw.True + return nil + }) + return v +} + +func (w *window) Maximize() { + if !w.ui.isRunning() { + w.ui.setInitWindowMaximized(true) + return + } + _ = w.ui.t.Call(func() error { + w.ui.window.Maximize() + return nil + }) +} + +func (w *window) IsMinimized() bool { + if !w.ui.isRunning() { + return false + } + var v bool + _ = w.ui.t.Call(func() error { + v = w.ui.window.GetAttrib(glfw.Iconified) == glfw.True + return nil + }) + return v +} + +func (w *window) Minimize() { + if !w.ui.isRunning() { + // Do nothing + return + } + _ = w.ui.t.Call(func() error { + w.ui.window.Iconify() + return nil + }) +} + +func (w *window) Restore() { + if !w.ui.isRunning() { + // Do nothing + return + } + _ = w.ui.t.Call(func() error { + w.ui.window.Restore() + return nil + }) +} + func (w *window) Position() (int, int) { if !w.ui.isRunning() { panic("glfw: WindowPosition can't be called before the main loop starts") diff --git a/window.go b/window.go index 317aa720f..1c41b6c66 100644 --- a/window.go +++ b/window.go @@ -236,6 +236,8 @@ func SetWindowSize(width, height int) { // IsWindowFloating reports whether the window is always shown above all the other windows. // +// IsWindowFloating returns false on browsers and mobiles. +// // IsWindowFloating is concurrent-safe. func IsWindowFloating() bool { if w := uiDriver().Window(); w != nil { @@ -246,9 +248,70 @@ 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 is concurrent-safe. func SetWindowFloating(float bool) { if w := uiDriver().Window(); w != nil { w.SetFloating(float) } } + +// MaximizeWindow maximizes the window. +// +// On some environments like macOS, MaximizeWindow requres that the window is resizable. +// +// MaximizeWindow does nothing on browsers or mobiles. +// +// MaximizeWindow is concurrent-safe. +func MaximizeWindow() { + if w := uiDriver().Window(); w != nil { + w.Maximize() + } +} + +// IsWindowMaximized reports whether the window is maximized or not. +// +// IsWindowMaximized always returns false on browsers and mobiles. +// +// IsWindowMaximized is concurrent-safe. +func IsWindowMaximized() bool { + if w := uiDriver().Window(); w != nil { + return w.IsMaximized() + } + return false +} + +// MinimizeWindow minimizes the window. +// +// If the main loop does not start yet, MinimizeWindow does nothing. +// +// MinimizeWindow does nothing on browsers or mobiles. +// +// MinimizeWindow is concurrent-safe. +func MinimizeWindow() { + if w := uiDriver().Window(); w != nil { + w.Minimize() + } +} + +// IsWindowMinimized reports whether the window is minimized or not. +// +// IsWindowMinimized always returns false on browsers and mobiles. +// +// IsWindowMinimized is concurrent-safe. +func IsWindowMinimized() bool { + if w := uiDriver().Window(); w != nil { + return w.IsMinimized() + } + return false +} + +// RestoreWindow restores the window from its maximized or minimized state. +// +// RestoreWindow is concurrent-safe. +func RestoreWindow() { + if w := uiDriver().Window(); w != nil { + w.Restore() + } +}