From 9b6ba5ed2c2c2483fe1557a4b7af83916d240ff4 Mon Sep 17 00:00:00 2001 From: Hajime Hoshi Date: Sat, 17 Apr 2021 02:27:04 +0900 Subject: [PATCH] ebiten: Add {Set,}WindowSizeLimits Closes #1385 --- examples/windowsize/main.go | 22 +++++++++- internal/driver/ui.go | 2 + internal/glfw/const.go | 5 ++- internal/glfw/glfw_notwindows.go | 12 ++++-- internal/uidriver/glfw/ui.go | 72 +++++++++++++++++++++++++++++++- internal/uidriver/glfw/window.go | 46 +++++++++++++++++++- window.go | 21 ++++++++++ 7 files changed, 170 insertions(+), 10 deletions(-) diff --git a/examples/windowsize/main.go b/examples/windowsize/main.go index 254b9af3e..1fc9d76a3 100644 --- a/examples/windowsize/main.go +++ b/examples/windowsize/main.go @@ -25,6 +25,7 @@ import ( "log" "math" "math/rand" + "regexp" "strconv" "strings" "time" @@ -45,6 +46,8 @@ var ( flagMaximize = flag.Bool("maximize", false, "maximize the window") flagVsync = flag.Bool("vsync", true, "enable vsync") flagInitFocused = flag.Bool("initfocused", true, "whether the window is focused on start") + flagMinWindowSize = flag.String("minwindowsize", "", "minimum window size (e.g., 100x200)") + flagMaxWindowSize = flag.String("maxwindowsize", "", "maximium window size (e.g., 1920x1080)") ) func init() { @@ -280,6 +283,7 @@ func (g *game) Draw(screen *ebiten.Image) { screen.DrawImage(gophersImage, op) wx, wy := ebiten.WindowPosition() + minw, minh, maxw, maxh := ebiten.WindowSizeLimits() cx, cy := ebiten.CursorPosition() tpsStr := "Uncapped" if t := ebiten.MaxTPS(); t != ebiten.UncappedTPS { @@ -319,10 +323,11 @@ func (g *game) Draw(screen *ebiten.Image) { %s IsFocused?: %s Windows Position: (%d, %d) +Window size limitation: (%d, %d) - (%d, %d) Cursor: (%d, %d) TPS: Current: %0.2f / Max: %s FPS: %0.2f -Device Scale Factor: %0.2f`, msgM, msgR, fg, wx, wy, cx, cy, ebiten.CurrentTPS(), tpsStr, ebiten.CurrentFPS(), ebiten.DeviceScaleFactor()) +Device Scale Factor: %0.2f`, msgM, msgR, fg, wx, wy, minw, minh, maxw, maxh, cx, cy, ebiten.CurrentTPS(), tpsStr, ebiten.CurrentFPS(), ebiten.DeviceScaleFactor()) ebitenutil.DebugPrint(screen, msg) } @@ -400,6 +405,21 @@ func main() { ebiten.SetRunnableOnUnfocused(true) } + minw, minh, maxw, maxh := -1, -1, -1, -1 + reSize := regexp.MustCompile(`^(\d+)x(\d+)$`) + if m := reSize.FindStringSubmatch(*flagMinWindowSize); m != nil { + minw, _ = strconv.Atoi(m[1]) + minh, _ = strconv.Atoi(m[2]) + } + if m := reSize.FindStringSubmatch(*flagMaxWindowSize); m != nil { + maxw, _ = strconv.Atoi(m[1]) + maxh, _ = strconv.Atoi(m[2]) + } + if minw >= 0 && minh >= 0 && maxw >= 0 && maxh >= 0 { + ebiten.SetWindowSizeLimits(minw, minh, maxw, maxh) + ebiten.SetWindowResizable(true) + } + const title = "Window Size (Ebiten Demo)" ww := int(float64(g.width) * initScreenScale) wh := int(float64(g.height) * initScreenScale) diff --git a/internal/driver/ui.go b/internal/driver/ui.go index 9d5c2167f..5756d2be0 100644 --- a/internal/driver/ui.go +++ b/internal/driver/ui.go @@ -78,6 +78,8 @@ type Window interface { Size() (int, int) SetSize(width, height int) + SizeLimits() (minw, minh, maxw, maxh int) + SetSizeLimits(minw, minh, maxw, maxh int) IsFloating() bool SetFloating(floating bool) diff --git a/internal/glfw/const.go b/internal/glfw/const.go index 82b2d6bdb..8f6f73ab3 100644 --- a/internal/glfw/const.go +++ b/internal/glfw/const.go @@ -34,8 +34,9 @@ type ( ) const ( - False = 0 - True = 1 + DontCare = -1 + False = 0 + True = 1 ) const ( diff --git a/internal/glfw/glfw_notwindows.go b/internal/glfw/glfw_notwindows.go index 64253bf61..678868f71 100644 --- a/internal/glfw/glfw_notwindows.go +++ b/internal/glfw/glfw_notwindows.go @@ -109,10 +109,6 @@ func (w *Window) GetAttrib(attrib Hint) int { return w.w.GetAttrib(glfw.Hint(attrib)) } -func (w *Window) SetAttrib(attrib Hint, value int) { - w.w.SetAttrib(glfw.Hint(attrib), value) -} - func (w *Window) GetCursorPos() (x, y float64) { return w.w.GetCursorPos() } @@ -161,6 +157,10 @@ func (w *Window) Restore() { w.w.Restore() } +func (w *Window) SetAttrib(attrib Hint, value int) { + w.w.SetAttrib(glfw.Hint(attrib), value) +} + func (w *Window) SetCharModsCallback(cbfun CharModsCallback) (previous CharModsCallback) { var gcb glfw.CharModsCallback if cbfun != nil { @@ -215,6 +215,10 @@ func (w *Window) SetSizeCallback(cbfun SizeCallback) (previous SizeCallback) { return prev } +func (w *Window) SetSizeLimits(minw, minh, maxw, maxh int) { + w.w.SetSizeLimits(minw, minh, maxw, maxh) +} + func (w *Window) SetIcon(images []image.Image) { w.w.SetIcon(images) } diff --git a/internal/uidriver/glfw/ui.go b/internal/uidriver/glfw/ui.go index dd1e58cea..da2bc6a9e 100644 --- a/internal/uidriver/glfw/ui.go +++ b/internal/uidriver/glfw/ui.go @@ -54,8 +54,12 @@ type UserInterface struct { // windowWidth and windowHeight represents a window size. // The unit is device-dependent pixels. - windowWidth int - windowHeight int + windowWidth int + windowHeight int + minWindowWidth int + minWindowHeight int + maxWindowWidth int + maxWindowHeight int running uint32 toChangeSize bool @@ -110,6 +114,10 @@ const ( var ( theUI = &UserInterface{ runnableOnUnfocused: true, + minWindowWidth: glfw.DontCare, + minWindowHeight: glfw.DontCare, + maxWindowWidth: glfw.DontCare, + maxWindowHeight: glfw.DontCare, origPosX: invalidPos, origPosY: invalidPos, initVsync: true, @@ -235,6 +243,25 @@ func (u *UserInterface) setRunning(running bool) { } } +func (u *UserInterface) getWindowSizeLimits() (minw, minh, maxw, maxh int) { + u.m.RLock() + defer u.m.RUnlock() + return u.minWindowWidth, u.minWindowHeight, u.maxWindowWidth, u.maxWindowHeight +} + +func (u *UserInterface) setWindowSizeLimits(minw, minh, maxw, maxh int) bool { + u.m.RLock() + defer u.m.RUnlock() + if u.minWindowWidth == minw && u.minWindowHeight == minh && u.maxWindowWidth == maxw && u.maxWindowHeight == maxh { + return false + } + u.minWindowWidth = minw + u.minWindowHeight = minh + u.maxWindowWidth = maxw + u.maxWindowHeight = maxh + return true +} + func (u *UserInterface) getInitTitle() string { u.m.RLock() v := u.initTitle @@ -782,6 +809,8 @@ func (u *UserInterface) init() error { setPosition() } + u.updateWindowSizeLimits() + // Maximizing a window requires a proper size and position. Call Maximize here (#1117). if u.isInitWindowMaximized() { u.window.Maximize() @@ -973,8 +1002,45 @@ func (u *UserInterface) swapBuffers() { } } +// updateWindowSizeLimits must be called from the main thread. +func (u *UserInterface) updateWindowSizeLimits() { + minw, minh, maxw, maxh := u.getWindowSizeLimits() + if minw < 0 { + minw = glfw.DontCare + } + if minh < 0 { + minh = glfw.DontCare + } + if maxw < 0 { + maxw = glfw.DontCare + } + if maxh < 0 { + maxh = glfw.DontCare + } + u.window.SetSizeLimits(minw, minh, maxw, maxh) +} + +func (u *UserInterface) adjustWindowSizeBasedOnSizeLimits(width, height int) (int, int) { + minw, minh, maxw, maxh := u.getWindowSizeLimits() + if minw >= 0 && width < minw { + width = minw + } + if minh >= 0 && height < minh { + height = minh + } + if maxw >= 0 && width > maxw { + width = maxw + } + if maxh >= 0 && height > maxh { + height = maxh + } + return width, height +} + // setWindowSize must be called from the main thread. func (u *UserInterface) setWindowSize(width, height int, fullscreen bool) { + width, height = u.adjustWindowSizeBasedOnSizeLimits(width, height) + if u.windowWidth == width && u.windowHeight == height && u.isFullscreen() == fullscreen && u.lastDeviceScaleFactor == u.deviceScaleFactor() { return } @@ -1045,6 +1111,8 @@ func (u *UserInterface) setWindowSize(width, height int, fullscreen bool) { // TODO: This should return an error. panic(fmt.Sprintf("glfw: failed to recreate window: %v", err)) } + // Reset the size limits explicitly. + u.updateWindowSizeLimits() u.window.Show() windowRecreated = true } diff --git a/internal/uidriver/glfw/window.go b/internal/uidriver/glfw/window.go index 363a6b2ae..075525d19 100644 --- a/internal/uidriver/glfw/window.go +++ b/internal/uidriver/glfw/window.go @@ -228,7 +228,8 @@ func (w *window) setPosition(x, y int) { func (w *window) Size() (int, int) { if !w.ui.isRunning() { - return w.ui.getInitWindowSize() + ww, wh := w.ui.getInitWindowSize() + return w.ui.adjustWindowSizeBasedOnSizeLimits(ww, wh) } ww := int(w.ui.fromGLFWPixel(float64(w.ui.windowWidth))) wh := int(w.ui.fromGLFWPixel(float64(w.ui.windowHeight))) @@ -248,6 +249,49 @@ func (w *window) SetSize(width, height int) { }) } +func (w *window) SizeLimits() (minw, minh, maxw, maxh int) { + minw, minh, maxw, maxh = w.ui.getWindowSizeLimits() + if minw >= 0 { + minw = int(w.ui.fromGLFWPixel(float64(minw))) + } + if minh >= 0 { + minh = int(w.ui.fromGLFWPixel(float64(minh))) + } + if maxw >= 0 { + maxw = int(w.ui.fromGLFWPixel(float64(maxw))) + } + if maxh >= 0 { + maxh = int(w.ui.fromGLFWPixel(float64(maxh))) + } + return +} + +func (w *window) SetSizeLimits(minw, minh, maxw, maxh int) { + if minw >= 0 { + minw = int(w.ui.toGLFWPixel(float64(minw))) + } + if minh >= 0 { + minh = int(w.ui.toGLFWPixel(float64(minh))) + } + if maxw >= 0 { + maxw = int(w.ui.toGLFWPixel(float64(maxw))) + } + if maxh >= 0 { + maxh = int(w.ui.toGLFWPixel(float64(maxh))) + } + if !w.ui.setWindowSizeLimits(minw, minh, maxw, maxh) { + return + } + if !w.ui.isRunning() { + return + } + + _ = w.ui.t.Call(func() error { + w.ui.updateWindowSizeLimits() + return nil + }) +} + func (w *window) SetIcon(iconImages []image.Image) { // The icons are actually set at (*UserInterface).loop. w.ui.setIconImages(iconImages) diff --git a/window.go b/window.go index 5ede13cc5..8dbaab1bc 100644 --- a/window.go +++ b/window.go @@ -235,6 +235,27 @@ func SetWindowSize(width, height int) { } } +// WindowSizeLimist returns the limitation of the window size on desktops. +// A negative value indicates the size is not limited. +// +// WindowMaxSize is concurrent-safe. +func WindowSizeLimits() (minw, minh, maxw, maxh int) { + if w := uiDriver().Window(); w != nil { + return w.SizeLimits() + } + return -1, -1, -1, -1 +} + +// SetWindowSizeLimits sets the limitation of the window size on desktops. +// A negative value indicates the size is not limited. +// +// SetWindowMaxSize is concurrent-safe. +func SetWindowSizeLimits(minw, minh, maxw, maxh int) { + if w := uiDriver().Window(); w != nil { + w.SetSizeLimits(minw, minh, maxw, maxh) + } +} + // IsWindowFloating reports whether the window is always shown above all the other windows. // // IsWindowFloating returns false on browsers and mobiles.