From f403e0716fefd53a27e7773150664c5c34a1e8e0 Mon Sep 17 00:00:00 2001 From: Hajime Hoshi Date: Mon, 7 Jan 2019 00:21:59 +0900 Subject: [PATCH] ui: Add IsWindowResizable and SetWindowResizable Fixes #320 --- examples/windowsize/main.go | 2 + internal/glfw/glfw_notwindows.go | 11 ++++ internal/glfw/type.go | 1 + internal/ui/ui_glfw.go | 87 ++++++++++++++++++++++++++++++-- run.go | 28 +++++++++- 5 files changed, 122 insertions(+), 7 deletions(-) diff --git a/examples/windowsize/main.go b/examples/windowsize/main.go index ba11e0182..ea83ad070 100644 --- a/examples/windowsize/main.go +++ b/examples/windowsize/main.go @@ -37,6 +37,7 @@ import ( var ( windowDecorated = flag.Bool("windowdecorated", true, "whether the window is decorated") + windowResizable = flag.Bool("windowresizable", false, "whether the window is resizable") ) func init() { @@ -221,6 +222,7 @@ func main() { ebiten.SetWindowIcon([]image.Image{createRandomIconImage()}) ebiten.SetWindowDecorated(*windowDecorated) + ebiten.SetWindowResizable(*windowResizable) if err := ebiten.Run(update, initScreenWidth, initScreenHeight, initScreenScale, "Window Size (Ebiten Demo)"); err != nil && err != terminated { log.Fatal(err) diff --git a/internal/glfw/glfw_notwindows.go b/internal/glfw/glfw_notwindows.go index 69eed3fdf..30676c5ae 100644 --- a/internal/glfw/glfw_notwindows.go +++ b/internal/glfw/glfw_notwindows.go @@ -162,6 +162,17 @@ func (w *Window) SetScrollCallback(cbfun ScrollCallback) (previous ScrollCallbac return nil // TODO } +func (w *Window) SetSizeCallback(cbfun SizeCallback) (previous SizeCallback) { + var gcb glfw.SizeCallback + if cbfun != nil { + gcb = func(window *glfw.Window, width, height int) { + cbfun(theWindows.get(window), width, height) + } + } + w.w.SetSizeCallback(gcb) + return nil // TODO +} + func (w *Window) SetIcon(images []image.Image) { w.w.SetIcon(images) } diff --git a/internal/glfw/type.go b/internal/glfw/type.go index 300f679c9..9cff47e06 100644 --- a/internal/glfw/type.go +++ b/internal/glfw/type.go @@ -18,6 +18,7 @@ type ( CharModsCallback func(w *Window, char rune, mods ModifierKey) FramebufferSizeCallback func(w *Window, width int, height int) ScrollCallback func(w *Window, xoff float64, yoff float64) + SizeCallback func(w *Window, width int, height int) ) type VidMode struct { diff --git a/internal/ui/ui_glfw.go b/internal/ui/ui_glfw.go index 3a14b752c..6c5d2f155 100644 --- a/internal/ui/ui_glfw.go +++ b/internal/ui/ui_glfw.go @@ -59,8 +59,12 @@ type userInterface struct { initFullscreen bool initCursorVisible bool initWindowDecorated bool + initWindowResizable bool initIconImages []image.Image + reqWidth int + reqHeight int + m sync.Mutex } @@ -93,7 +97,6 @@ func initialize() error { glfw.WindowHint(glfw.ClientAPI, glfw.NoAPI) } glfw.WindowHint(glfw.Visible, glfw.False) - glfw.WindowHint(glfw.Resizable, glfw.False) // Create a window to set the initial monitor. w, err := glfw.CreateWindow(16, 16, "", nil, nil) @@ -229,6 +232,19 @@ func (u *userInterface) setRunnableInBackground(runnableInBackground bool) { u.m.Unlock() } +func (u *userInterface) isInitWindowResizable() bool { + u.m.Lock() + v := u.initWindowResizable + u.m.Unlock() + return v +} + +func (u *userInterface) setInitWindowResizable(resizable bool) { + u.m.Lock() + u.initWindowResizable = resizable + u.m.Unlock() +} + func (u *userInterface) getInitIconImages() []image.Image { u.m.Lock() i := u.initIconImages @@ -258,17 +274,15 @@ func ScreenSizeInFullscreen() (int, int) { return int(float64(v.Width) / s), int(float64(v.Height) / s) } -func SetScreenSize(width, height int) bool { +func SetScreenSize(width, height int) { u := currentUI if !u.isRunning() { panic("ui: Run is not called yet") } - r := false _ = mainthread.Run(func() error { - r = u.setScreenSize(width, height, u.scale, u.fullscreen(), u.vsync) + u.setScreenSize(width, height, u.scale, u.fullscreen(), u.vsync) return nil }) - return r } func SetScreenScale(scale float64) bool { @@ -509,6 +523,30 @@ func SetWindowDecorated(decorated bool) { // return nil } +func IsWindowResizable() bool { + u := currentUI + if !u.isRunning() { + return u.isInitWindowResizable() + } + v := false + _ = mainthread.Run(func() error { + v = currentUI.window.GetAttrib(glfw.Resizable) == glfw.True + return nil + }) + return v +} + +func SetWindowResizable(resizable bool) { + if !currentUI.isRunning() { + currentUI.setInitWindowResizable(resizable) + return + } + + panic("ui: SetWindowResizable can't be called after Run so far.") + + // TODO: Now SetAttrib doesn't exist on GLFW 3.2. Revisit later (#556). +} + func DeviceScaleFactor() float64 { f := 0.0 u := currentUI @@ -537,6 +575,12 @@ func Run(width, height int, scale float64, title string, g GraphicsContext, main } glfw.WindowHint(glfw.Decorated, decorated) + resizable := glfw.False + if u.isInitWindowResizable() { + resizable = glfw.True + } + glfw.WindowHint(glfw.Resizable, resizable) + // As a start, create a window with temporary size to create OpenGL context thread. window, err := glfw.CreateWindow(16, 16, "", nil, nil) if err != nil { @@ -593,6 +637,21 @@ func Run(width, height int, scale float64, title string, g GraphicsContext, main y := my + (v.Height-h)/3 x, y = adjustWindowPosition(x, y) u.window.SetPos(x, y) + + u.window.SetSizeCallback(func(_ *glfw.Window, width, height int) { + go func() { + w := int(float64(width) / u.scale) + h := int(float64(height) / u.scale) + _ = mainthread.Run(func() error { + if u.fullscreen() { + return nil + } + u.reqWidth = w + u.reqHeight = h + return nil + }) + }() + }) return nil }) @@ -713,6 +772,17 @@ func (u *userInterface) update(g GraphicsContext) error { }); err != nil { return err } + + // Update the screen size when the window is resizable. + _ = mainthread.Run(func() error { + w, h := u.reqWidth, u.reqHeight + if w != 0 || h != 0 { + u.setScreenSize(w, h, u.scale, u.fullscreen(), u.vsync) + } + u.reqWidth = 0 + u.reqHeight = 0 + return nil + }) return nil } @@ -769,6 +839,13 @@ func (u *userInterface) forceSetScreenSize(width, height int, scale float64, ful minWindowWidth = 1 } + if width < 1 { + width = 1 + } + if height < 1 { + height = 1 + } + u.width = width u.windowWidth = width s := scale * devicescale.GetAt(u.currentMonitor().GetPos()) diff --git a/run.go b/run.go index 6fae46c00..4780f70c9 100644 --- a/run.go +++ b/run.go @@ -310,6 +310,8 @@ func IsRunnableInBackground() bool { // SetWindowDecorated sets the state if the window is decorated. // +// The window is decorated by default. +// // SetWindowDecorated works only on desktops. // SetWindowDecorated does nothing on other platforms. // @@ -320,14 +322,36 @@ func SetWindowDecorated(decorated bool) { ui.SetWindowDecorated(decorated) } -// IsWindowDecorated returns a boolean value indicating whether -// the window is decorated. +// IsWindowDecorated reports whether the window is decorated. // // IsWindowDecorated is concurrent-safe. func IsWindowDecorated() bool { return ui.IsWindowDecorated() } +// SetWindowResizable sets the state if the window is resizable. +// +// The window is not resizable by default. +// +// When the window is resizable, the image size given via the update function can be changed by resizing. +// +// SetWindowResizable works only on desktops. +// SetWindowResizable does nothing on other platforms. +// +// SetWindowResizable panics if SetWindowResizable is called after Run. +// +// SetWindowResizable is concurrent-safe. +func SetWindowResizable(resizable bool) { + ui.SetWindowResizable(resizable) +} + +// IsWindowResizable reports whether the window is resizable. +// +// IsWindowResizable is concurrent-safe. +func IsWindowResizable() bool { + return ui.IsWindowResizable() +} + // SetRunnableInBackground sets the state if the game runs even in background. // // If the given value is true, the game runs in background e.g. when losing focus.