From f4029aaa77ac429d122b914c320896101611b3d6 Mon Sep 17 00:00:00 2001 From: Hajime Hoshi Date: Sat, 23 Mar 2024 22:31:08 +0900 Subject: [PATCH] ebiten: add (*Monitor).Size() to replace ScreenSizeInFullscreen() Also, this change fixes redundant checks the case when a monitor does not exist. Now Ebitengine checks a monitor existence at the initialization. Closes #2145 Closes #2795 --- examples/fullscreen/main.go | 2 +- examples/mascot/main.go | 2 +- examples/windowsize/main.go | 2 +- internal/ui/monitor_glfw.go | 16 ++++++--- internal/ui/screensizeinfullscreen_js.go | 20 ++++++++++++ internal/ui/screensizeinfullscreen_notjs.go | 21 ++++++++++++ internal/ui/ui_glfw.go | 36 ++------------------- internal/ui/ui_js.go | 9 +++--- internal/ui/ui_linbsd.go | 2 +- internal/ui/ui_mobile.go | 11 +++---- internal/ui/ui_nintendosdk.go | 8 ++--- internal/ui/ui_playstation5.go | 8 ++--- monitor.go | 16 +++++++-- run.go | 2 ++ window.go | 4 +-- 15 files changed, 94 insertions(+), 65 deletions(-) create mode 100644 internal/ui/screensizeinfullscreen_js.go create mode 100644 internal/ui/screensizeinfullscreen_notjs.go diff --git a/examples/fullscreen/main.go b/examples/fullscreen/main.go index b8de6df2e..c60229efc 100644 --- a/examples/fullscreen/main.go +++ b/examples/fullscreen/main.go @@ -85,7 +85,7 @@ func (g *Game) Draw(screen *ebiten.Image) { op.Filter = ebiten.FilterLinear screen.DrawImage(gophersImage, op) - fw, fh := ebiten.ScreenSizeInFullscreen() + fw, fh := ebiten.Monitor().Size() msg := "This is an example of the finest fullscreen.\n" if runtime.GOOS == "js" { msg += "Press F or touch the screen to enter fullscreen (again).\n" diff --git a/examples/mascot/main.go b/examples/mascot/main.go index 0773af538..a9efaeab1 100644 --- a/examples/mascot/main.go +++ b/examples/mascot/main.go @@ -80,7 +80,7 @@ func (m *mascot) Layout(outsideWidth, outsideHeight int) (int, int) { func (m *mascot) Update() error { m.count++ - sw, sh := ebiten.ScreenSizeInFullscreen() + sw, sh := ebiten.Monitor().Size() ebiten.SetWindowPosition(m.x16/16, m.y16/16+sh-height) if m.vx16 == 0 { diff --git a/examples/windowsize/main.go b/examples/windowsize/main.go index 4d352c150..14b9d2e3d 100644 --- a/examples/windowsize/main.go +++ b/examples/windowsize/main.go @@ -401,7 +401,7 @@ func parseWindowPosition() (int, int, bool) { func main() { fmt.Printf("Device scale factor: %0.2f\n", ebiten.Monitor().DeviceScaleFactor()) - w, h := ebiten.ScreenSizeInFullscreen() + w, h := ebiten.Monitor().Size() fmt.Printf("Screen size in fullscreen: %d, %d\n", w, h) // Decode an image from the image file's byte slice. diff --git a/internal/ui/monitor_glfw.go b/internal/ui/monitor_glfw.go index 9b207f75e..aa12765cb 100644 --- a/internal/ui/monitor_glfw.go +++ b/internal/ui/monitor_glfw.go @@ -42,14 +42,15 @@ func (m *Monitor) Name() string { // DeviceScaleFactor is concurrent-safe as contentScale is immutable. func (m *Monitor) DeviceScaleFactor() float64 { - // It is rare, but monitor can be nil when glfw.GetPrimaryMonitor returns nil. - // In this case, return 1 as a tentative scale (#1878). - if m == nil { - return 1 - } return m.contentScale } +// Size returns the size of the monitor in device-independent pixels. +func (m *Monitor) Size() (int, int) { + w, h := m.sizeInDIP() + return int(w), int(h) +} + func (m *Monitor) sizeInDIP() (float64, float64) { w, h := m.boundsInGLFWPixels.Dx(), m.boundsInGLFWPixels.Dy() s := m.DeviceScaleFactor() @@ -88,6 +89,11 @@ func (m *monitors) primaryMonitor() *Monitor { m.m.Lock() defer m.m.Unlock() + // GetMonitors might return nil in theory (#1878, #1887). + // primaryMonitor can be called at the initialization, so monitors can be nil. + if len(m.monitors) == 0 { + return nil + } return m.monitors[0] } diff --git a/internal/ui/screensizeinfullscreen_js.go b/internal/ui/screensizeinfullscreen_js.go new file mode 100644 index 000000000..40ed9b5c0 --- /dev/null +++ b/internal/ui/screensizeinfullscreen_js.go @@ -0,0 +1,20 @@ +// Copyright 2024 The Ebitengine Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package ui + +func (u *UserInterface) ScreenSizeInFullscreen() (int, int) { + // On browsers, ScreenSizeInFullscreen returns the 'window' (global object) size, not 'screen' size for backward compatibility (#2145). + return window.Get("width").Int(), window.Get("height").Int() +} diff --git a/internal/ui/screensizeinfullscreen_notjs.go b/internal/ui/screensizeinfullscreen_notjs.go new file mode 100644 index 000000000..89e880807 --- /dev/null +++ b/internal/ui/screensizeinfullscreen_notjs.go @@ -0,0 +1,21 @@ +// Copyright 2024 The Ebitengine Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +//go:build !js + +package ui + +func (u *UserInterface) ScreenSizeInFullscreen() (int, int) { + return u.Monitor().Size() +} diff --git a/internal/ui/ui_glfw.go b/internal/ui/ui_glfw.go index 9564e73b4..74289026e 100644 --- a/internal/ui/ui_glfw.go +++ b/internal/ui/ui_glfw.go @@ -178,9 +178,9 @@ func (u *UserInterface) initializeGLFW() error { m = theMonitors.primaryMonitor() } - // GetPrimaryMonitor might return nil in theory (#1887). + // GetMonitors might return nil in theory (#1878, #1887). if m == nil { - return errors.New("ui: no monitor was found at initialize") + return errors.New("ui: no monitor was found at initializeGLFW") } u.setInitMonitor(m) @@ -265,7 +265,7 @@ func (u *UserInterface) AppendMonitors(monitors []*Monitor) []*Monitor { // Monitor returns the window's current monitor. Returns nil if there is no current monitor yet. func (u *UserInterface) Monitor() *Monitor { if !u.isRunning() { - return nil + return u.getInitMonitor() } var monitor *Monitor u.mainThread.Call(func() { @@ -571,36 +571,6 @@ func (u *UserInterface) setWindowClosingHandled(handled bool) { u.m.Unlock() } -func (u *UserInterface) ScreenSizeInFullscreen() (int, int) { - if u.isTerminated() { - return 0, 0 - } - if !u.isRunning() { - m := u.getInitMonitor() - w, h := m.sizeInDIP() - return int(w), int(h) - } - - var w, h int - u.mainThread.Call(func() { - if u.isTerminated() { - return - } - m, err := u.currentMonitor() - if err != nil { - u.setError(err) - return - } - if m == nil { - return - } - wf, hf := m.sizeInDIP() - w = int(wf) - h = int(hf) - }) - return w, h -} - // isFullscreen must be called from the main thread. func (u *UserInterface) isFullscreen() (bool, error) { if !u.isRunning() { diff --git a/internal/ui/ui_js.go b/internal/ui/ui_js.go index 505cb1ec4..86ea9c0a2 100644 --- a/internal/ui/ui_js.go +++ b/internal/ui/ui_js.go @@ -121,6 +121,7 @@ type userInterfaceImpl struct { var ( window = js.Global().Get("window") document = js.Global().Get("document") + screen = js.Global().Get("screen") canvas js.Value requestAnimationFrame = js.Global().Get("requestAnimationFrame") setTimeout = js.Global().Get("setTimeout") @@ -131,10 +132,6 @@ var ( documentHidden = js.Global().Get("Object").Call("getOwnPropertyDescriptor", js.Global().Get("Document").Get("prototype"), "hidden").Get("get").Call("bind", document) ) -func (u *UserInterface) ScreenSizeInFullscreen() (int, int) { - return window.Get("innerWidth").Int(), window.Get("innerHeight").Int() -} - func (u *UserInterface) SetFullscreen(fullscreen bool) { if !canvas.Truthy() { return @@ -796,6 +793,10 @@ func (m *Monitor) DeviceScaleFactor() float64 { return m.deviceScaleFactor } +func (m *Monitor) Size() (int, int) { + return screen.Get("width").Int(), screen.Get("height").Int() +} + func (u *UserInterface) AppendMonitors(mons []*Monitor) []*Monitor { return append(mons, theMonitor) } diff --git a/internal/ui/ui_linbsd.go b/internal/ui/ui_linbsd.go index 50b7f4554..378f27f35 100644 --- a/internal/ui/ui_linbsd.go +++ b/internal/ui/ui_linbsd.go @@ -72,7 +72,7 @@ func glfwMonitorSizeInGLFWPixels(m *glfw.Monitor) (int, int, error) { // Note: GLFW currently returns physical pixel sizes, // but we need to predict the window system-side size of the fullscreen window - // for Ebitengine's `ScreenSizeInFullscreen` public API. + // for Ebitengine's `(*Monitor).Size()` public API. // Also at the moment we need this prior to switching to fullscreen, but that might be replaceable. // So this function computes the ratio of physical per logical pixels. xconn, err := xgb.NewConn() diff --git a/internal/ui/ui_mobile.go b/internal/ui/ui_mobile.go index 30a7d71e8..d95ece36c 100644 --- a/internal/ui/ui_mobile.go +++ b/internal/ui/ui_mobile.go @@ -183,12 +183,6 @@ func (u *UserInterface) update() error { return nil } -func (u *UserInterface) ScreenSizeInFullscreen() (int, int) { - // TODO: This function should return gbuildWidthPx, gbuildHeightPx, - // but these values are not initialized until the main loop starts. - return 0, 0 -} - // SetOutsideSize is called from mobile/ebitenmobileview. // // SetOutsideSize is concurrent safe. @@ -289,6 +283,11 @@ func (m *Monitor) DeviceScaleFactor() float64 { return m.deviceScaleFactor } +func (m *Monitor) Size() (int, int) { + // TODO: Return a valid value. + return 0, 0 +} + func (u *UserInterface) AppendMonitors(mons []*Monitor) []*Monitor { return append(mons, theMonitor) } diff --git a/internal/ui/ui_nintendosdk.go b/internal/ui/ui_nintendosdk.go index 52cef975d..f10795c82 100644 --- a/internal/ui/ui_nintendosdk.go +++ b/internal/ui/ui_nintendosdk.go @@ -102,10 +102,6 @@ func (*UserInterface) IsFocused() bool { return true } -func (*UserInterface) ScreenSizeInFullscreen() (int, int) { - return 0, 0 -} - func (u *UserInterface) readInputState(inputState *InputState) { u.m.Lock() defer u.m.Unlock() @@ -170,6 +166,10 @@ func (m *Monitor) DeviceScaleFactor() float64 { return 1 } +func (m *Monitor) Size() (int, int) { + return int(C.kScreenWidth), int(C.kScreenHeight) +} + func (u *UserInterface) AppendMonitors(mons []*Monitor) []*Monitor { return append(mons, theMonitor) } diff --git a/internal/ui/ui_playstation5.go b/internal/ui/ui_playstation5.go index a367cc91c..277a72f41 100644 --- a/internal/ui/ui_playstation5.go +++ b/internal/ui/ui_playstation5.go @@ -92,10 +92,6 @@ func (*UserInterface) IsFocused() bool { return true } -func (*UserInterface) ScreenSizeInFullscreen() (int, int) { - return 0, 0 -} - func (u *UserInterface) readInputState(inputState *InputState) { // TODO: Implement this. } @@ -163,6 +159,10 @@ func (m *Monitor) DeviceScaleFactor() float64 { return 1 } +func (m *Monitor) Size() (int, int) { + return screenWidth, screenHeight +} + func (u *UserInterface) AppendMonitors(mons []*Monitor) []*Monitor { return append(mons, theMonitor) } diff --git a/monitor.go b/monitor.go index f5c7691f5..dfd7a93b3 100644 --- a/monitor.go +++ b/monitor.go @@ -34,13 +34,23 @@ func (m *MonitorType) Name() string { // // DeviceScaleFactor might panic on init function on some devices like Android. // Then, it is not recommended to call DeviceScaleFactor from init functions. -// -// DeviceScaleFactor must be called on the main thread before the main loop, -// and is concurrent-safe after the main loop. func (m *MonitorType) DeviceScaleFactor() float64 { return (*ui.Monitor)(m).DeviceScaleFactor() } +// Size returns the size of the monitor in device-independent pixels. +// This is the same as the screen size in fullscreen mode. +// The returned value can be given to SetSize function if the perfectly fit fullscreen is needed. +// +// On mobiles, Size returns (0, 0) so far. +// +// Size's use cases are limited. If you are making a fullscreen application, you can use RunGame and +// the Game interface's Layout function instead. If you are making a not-fullscreen application but the application's +// behavior depends on the monitor size, Size is useful. +func (m *MonitorType) Size() (int, int) { + return (*ui.Monitor)(m).Size() +} + // Monitor returns the current monitor. func Monitor() *MonitorType { m := ui.Get().Monitor() diff --git a/run.go b/run.go index 668cfee31..fb49bc222 100644 --- a/run.go +++ b/run.go @@ -352,6 +352,8 @@ func isRunGameEnded() bool { // // ScreenSizeInFullscreen must be called on the main thread before ebiten.RunGame, and is concurrent-safe after // ebiten.RunGame. +// +// Deprecated: as of v2.6. Use Monitor().Size() instead. func ScreenSizeInFullscreen() (int, int) { return ui.Get().ScreenSizeInFullscreen() } diff --git a/window.go b/window.go index 05a88db6b..b5945fdcb 100644 --- a/window.go +++ b/window.go @@ -165,7 +165,7 @@ var ( func initializeWindowPositionIfNeeded(width, height int) { if atomic.LoadUint32(&windowPositionSetExplicitly) == 0 { - sw, sh := ui.Get().ScreenSizeInFullscreen() + sw, sh := ui.Get().Monitor().Size() x, y := ui.InitialWindowPosition(sw, sh, width, height) ui.Get().Window().SetPosition(x, y) } @@ -175,7 +175,7 @@ func initializeWindowPositionIfNeeded(width, height int) { // WindowSize returns (0, 0) on other environments. // // Even if the application is in fullscreen mode, WindowSize returns the original window size -// If you need the fullscreen dimensions, see ScreenSizeInFullscreen instead. +// If you need the fullscreen dimensions, see Monitor().Size() instead. // // WindowSize is concurrent-safe. func WindowSize() (int, int) {