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
This commit is contained in:
Hajime Hoshi 2024-03-23 22:31:08 +09:00
parent 230619a036
commit f4029aaa77
15 changed files with 94 additions and 65 deletions

View File

@ -85,7 +85,7 @@ func (g *Game) Draw(screen *ebiten.Image) {
op.Filter = ebiten.FilterLinear op.Filter = ebiten.FilterLinear
screen.DrawImage(gophersImage, op) screen.DrawImage(gophersImage, op)
fw, fh := ebiten.ScreenSizeInFullscreen() fw, fh := ebiten.Monitor().Size()
msg := "This is an example of the finest fullscreen.\n" msg := "This is an example of the finest fullscreen.\n"
if runtime.GOOS == "js" { if runtime.GOOS == "js" {
msg += "Press F or touch the screen to enter fullscreen (again).\n" msg += "Press F or touch the screen to enter fullscreen (again).\n"

View File

@ -80,7 +80,7 @@ func (m *mascot) Layout(outsideWidth, outsideHeight int) (int, int) {
func (m *mascot) Update() error { func (m *mascot) Update() error {
m.count++ m.count++
sw, sh := ebiten.ScreenSizeInFullscreen() sw, sh := ebiten.Monitor().Size()
ebiten.SetWindowPosition(m.x16/16, m.y16/16+sh-height) ebiten.SetWindowPosition(m.x16/16, m.y16/16+sh-height)
if m.vx16 == 0 { if m.vx16 == 0 {

View File

@ -401,7 +401,7 @@ func parseWindowPosition() (int, int, bool) {
func main() { func main() {
fmt.Printf("Device scale factor: %0.2f\n", ebiten.Monitor().DeviceScaleFactor()) 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) fmt.Printf("Screen size in fullscreen: %d, %d\n", w, h)
// Decode an image from the image file's byte slice. // Decode an image from the image file's byte slice.

View File

@ -42,14 +42,15 @@ func (m *Monitor) Name() string {
// DeviceScaleFactor is concurrent-safe as contentScale is immutable. // DeviceScaleFactor is concurrent-safe as contentScale is immutable.
func (m *Monitor) DeviceScaleFactor() float64 { 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 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) { func (m *Monitor) sizeInDIP() (float64, float64) {
w, h := m.boundsInGLFWPixels.Dx(), m.boundsInGLFWPixels.Dy() w, h := m.boundsInGLFWPixels.Dx(), m.boundsInGLFWPixels.Dy()
s := m.DeviceScaleFactor() s := m.DeviceScaleFactor()
@ -88,6 +89,11 @@ func (m *monitors) primaryMonitor() *Monitor {
m.m.Lock() m.m.Lock()
defer m.m.Unlock() 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] return m.monitors[0]
} }

View File

@ -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()
}

View File

@ -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()
}

View File

@ -178,9 +178,9 @@ func (u *UserInterface) initializeGLFW() error {
m = theMonitors.primaryMonitor() m = theMonitors.primaryMonitor()
} }
// GetPrimaryMonitor might return nil in theory (#1887). // GetMonitors might return nil in theory (#1878, #1887).
if m == nil { 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) 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. // Monitor returns the window's current monitor. Returns nil if there is no current monitor yet.
func (u *UserInterface) Monitor() *Monitor { func (u *UserInterface) Monitor() *Monitor {
if !u.isRunning() { if !u.isRunning() {
return nil return u.getInitMonitor()
} }
var monitor *Monitor var monitor *Monitor
u.mainThread.Call(func() { u.mainThread.Call(func() {
@ -571,36 +571,6 @@ func (u *UserInterface) setWindowClosingHandled(handled bool) {
u.m.Unlock() 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. // isFullscreen must be called from the main thread.
func (u *UserInterface) isFullscreen() (bool, error) { func (u *UserInterface) isFullscreen() (bool, error) {
if !u.isRunning() { if !u.isRunning() {

View File

@ -121,6 +121,7 @@ type userInterfaceImpl struct {
var ( var (
window = js.Global().Get("window") window = js.Global().Get("window")
document = js.Global().Get("document") document = js.Global().Get("document")
screen = js.Global().Get("screen")
canvas js.Value canvas js.Value
requestAnimationFrame = js.Global().Get("requestAnimationFrame") requestAnimationFrame = js.Global().Get("requestAnimationFrame")
setTimeout = js.Global().Get("setTimeout") 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) 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) { func (u *UserInterface) SetFullscreen(fullscreen bool) {
if !canvas.Truthy() { if !canvas.Truthy() {
return return
@ -796,6 +793,10 @@ func (m *Monitor) DeviceScaleFactor() float64 {
return m.deviceScaleFactor 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 { func (u *UserInterface) AppendMonitors(mons []*Monitor) []*Monitor {
return append(mons, theMonitor) return append(mons, theMonitor)
} }

View File

@ -72,7 +72,7 @@ func glfwMonitorSizeInGLFWPixels(m *glfw.Monitor) (int, int, error) {
// Note: GLFW currently returns physical pixel sizes, // Note: GLFW currently returns physical pixel sizes,
// but we need to predict the window system-side size of the fullscreen window // 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. // 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. // So this function computes the ratio of physical per logical pixels.
xconn, err := xgb.NewConn() xconn, err := xgb.NewConn()

View File

@ -183,12 +183,6 @@ func (u *UserInterface) update() error {
return nil 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 called from mobile/ebitenmobileview.
// //
// SetOutsideSize is concurrent safe. // SetOutsideSize is concurrent safe.
@ -289,6 +283,11 @@ func (m *Monitor) DeviceScaleFactor() float64 {
return m.deviceScaleFactor return m.deviceScaleFactor
} }
func (m *Monitor) Size() (int, int) {
// TODO: Return a valid value.
return 0, 0
}
func (u *UserInterface) AppendMonitors(mons []*Monitor) []*Monitor { func (u *UserInterface) AppendMonitors(mons []*Monitor) []*Monitor {
return append(mons, theMonitor) return append(mons, theMonitor)
} }

View File

@ -102,10 +102,6 @@ func (*UserInterface) IsFocused() bool {
return true return true
} }
func (*UserInterface) ScreenSizeInFullscreen() (int, int) {
return 0, 0
}
func (u *UserInterface) readInputState(inputState *InputState) { func (u *UserInterface) readInputState(inputState *InputState) {
u.m.Lock() u.m.Lock()
defer u.m.Unlock() defer u.m.Unlock()
@ -170,6 +166,10 @@ func (m *Monitor) DeviceScaleFactor() float64 {
return 1 return 1
} }
func (m *Monitor) Size() (int, int) {
return int(C.kScreenWidth), int(C.kScreenHeight)
}
func (u *UserInterface) AppendMonitors(mons []*Monitor) []*Monitor { func (u *UserInterface) AppendMonitors(mons []*Monitor) []*Monitor {
return append(mons, theMonitor) return append(mons, theMonitor)
} }

View File

@ -92,10 +92,6 @@ func (*UserInterface) IsFocused() bool {
return true return true
} }
func (*UserInterface) ScreenSizeInFullscreen() (int, int) {
return 0, 0
}
func (u *UserInterface) readInputState(inputState *InputState) { func (u *UserInterface) readInputState(inputState *InputState) {
// TODO: Implement this. // TODO: Implement this.
} }
@ -163,6 +159,10 @@ func (m *Monitor) DeviceScaleFactor() float64 {
return 1 return 1
} }
func (m *Monitor) Size() (int, int) {
return screenWidth, screenHeight
}
func (u *UserInterface) AppendMonitors(mons []*Monitor) []*Monitor { func (u *UserInterface) AppendMonitors(mons []*Monitor) []*Monitor {
return append(mons, theMonitor) return append(mons, theMonitor)
} }

View File

@ -34,13 +34,23 @@ func (m *MonitorType) Name() string {
// //
// DeviceScaleFactor might panic on init function on some devices like Android. // DeviceScaleFactor might panic on init function on some devices like Android.
// Then, it is not recommended to call DeviceScaleFactor from init functions. // 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 { func (m *MonitorType) DeviceScaleFactor() float64 {
return (*ui.Monitor)(m).DeviceScaleFactor() 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. // Monitor returns the current monitor.
func Monitor() *MonitorType { func Monitor() *MonitorType {
m := ui.Get().Monitor() m := ui.Get().Monitor()

2
run.go
View File

@ -352,6 +352,8 @@ func isRunGameEnded() bool {
// //
// ScreenSizeInFullscreen must be called on the main thread before ebiten.RunGame, and is concurrent-safe after // ScreenSizeInFullscreen must be called on the main thread before ebiten.RunGame, and is concurrent-safe after
// ebiten.RunGame. // ebiten.RunGame.
//
// Deprecated: as of v2.6. Use Monitor().Size() instead.
func ScreenSizeInFullscreen() (int, int) { func ScreenSizeInFullscreen() (int, int) {
return ui.Get().ScreenSizeInFullscreen() return ui.Get().ScreenSizeInFullscreen()
} }

View File

@ -165,7 +165,7 @@ var (
func initializeWindowPositionIfNeeded(width, height int) { func initializeWindowPositionIfNeeded(width, height int) {
if atomic.LoadUint32(&windowPositionSetExplicitly) == 0 { if atomic.LoadUint32(&windowPositionSetExplicitly) == 0 {
sw, sh := ui.Get().ScreenSizeInFullscreen() sw, sh := ui.Get().Monitor().Size()
x, y := ui.InitialWindowPosition(sw, sh, width, height) x, y := ui.InitialWindowPosition(sw, sh, width, height)
ui.Get().Window().SetPosition(x, y) ui.Get().Window().SetPosition(x, y)
} }
@ -175,7 +175,7 @@ func initializeWindowPositionIfNeeded(width, height int) {
// WindowSize returns (0, 0) on other environments. // WindowSize returns (0, 0) on other environments.
// //
// Even if the application is in fullscreen mode, WindowSize returns the original window size // 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. // WindowSize is concurrent-safe.
func WindowSize() (int, int) { func WindowSize() (int, int) {