mirror of
https://github.com/hajimehoshi/ebiten.git
synced 2024-11-10 04:57:26 +01:00
ebiten: add APIs to treat monitors (#2597)
This change adds these APIs: * `type MonitorType` * `func (*MonitorType) Bounds() image.Rectangle` * `func (*MonitorType) Name() string` * `func Monitor() *MonitorType` * `func SetMonitor(*MonitorType)` * `func AppendMonitors([]*MonitorType) []*MonitorType` Closes #1835
This commit is contained in:
parent
b1b4335423
commit
60b7de6a3c
127
examples/monitor/main.go
Normal file
127
examples/monitor/main.go
Normal file
@ -0,0 +1,127 @@
|
|||||||
|
// Copyright 2023 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 main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"flag"
|
||||||
|
"fmt"
|
||||||
|
"image/color"
|
||||||
|
"log"
|
||||||
|
|
||||||
|
"golang.org/x/image/font"
|
||||||
|
"golang.org/x/image/font/opentype"
|
||||||
|
|
||||||
|
"github.com/hajimehoshi/ebiten/v2"
|
||||||
|
"github.com/hajimehoshi/ebiten/v2/examples/resources/fonts"
|
||||||
|
"github.com/hajimehoshi/ebiten/v2/inpututil"
|
||||||
|
"github.com/hajimehoshi/ebiten/v2/text"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
mplusNormalFont font.Face
|
||||||
|
)
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
tt, err := opentype.Parse(fonts.MPlus1pRegular_ttf)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
const dpi = 72
|
||||||
|
mplusNormalFont, err = opentype.NewFace(tt, &opentype.FaceOptions{
|
||||||
|
Size: 24,
|
||||||
|
DPI: dpi,
|
||||||
|
Hinting: font.HintingFull,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const (
|
||||||
|
screenWidth = 640
|
||||||
|
screenHeight = 480
|
||||||
|
)
|
||||||
|
|
||||||
|
type Game struct {
|
||||||
|
monitors []*ebiten.MonitorType
|
||||||
|
}
|
||||||
|
|
||||||
|
func (g *Game) Update() error {
|
||||||
|
// Refresh monitors.
|
||||||
|
g.monitors = ebiten.AppendMonitors(g.monitors[:0])
|
||||||
|
|
||||||
|
// Handle keypresses.
|
||||||
|
if inpututil.IsKeyJustReleased(ebiten.KeyF) {
|
||||||
|
ebiten.SetFullscreen(!ebiten.IsFullscreen())
|
||||||
|
} else {
|
||||||
|
for i, m := range g.monitors {
|
||||||
|
if inpututil.IsKeyJustPressed(ebiten.KeyDigit0 + ebiten.Key(i)) {
|
||||||
|
ebiten.SetWindowTitle(m.Name())
|
||||||
|
ebiten.SetMonitor(m)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (g *Game) Draw(screen *ebiten.Image) {
|
||||||
|
const x = 0
|
||||||
|
y := 24
|
||||||
|
text.Draw(screen, "F to toggle fullscreen\n0-9 to change monitor", mplusNormalFont, x, y, color.White)
|
||||||
|
y += 72
|
||||||
|
|
||||||
|
for i, m := range g.monitors {
|
||||||
|
text.Draw(screen, fmt.Sprintf("%d: %s %s", i, m.Name(), m.Bounds().String()), mplusNormalFont, x, y, color.White)
|
||||||
|
y += 24
|
||||||
|
}
|
||||||
|
|
||||||
|
activeMonitor := ebiten.Monitor()
|
||||||
|
for i, m := range g.monitors {
|
||||||
|
if m == activeMonitor {
|
||||||
|
text.Draw(screen, fmt.Sprintf("active: %s (%d)", m.Name(), i), mplusNormalFont, x, y, color.White)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (g *Game) Layout(outsideWidth, outsideHeight int) (int, int) {
|
||||||
|
return screenWidth, screenHeight
|
||||||
|
}
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
g := &Game{}
|
||||||
|
|
||||||
|
// Allow the user to pass in a monitor flag to target a specific monitor.
|
||||||
|
var monitor int
|
||||||
|
flag.IntVar(&monitor, "monitor", 0, "target monitor index to run the program on")
|
||||||
|
flag.Parse()
|
||||||
|
|
||||||
|
// Read our monitors.
|
||||||
|
g.monitors = ebiten.AppendMonitors(nil)
|
||||||
|
|
||||||
|
// Ensure the user did not supply a monitor index beyond the range of available monitors. If they did, set the monitor to the primary.
|
||||||
|
if monitor < 0 || monitor >= len(g.monitors) {
|
||||||
|
monitor = 0
|
||||||
|
}
|
||||||
|
|
||||||
|
targetMonitor := g.monitors[monitor]
|
||||||
|
ebiten.SetMonitor(targetMonitor)
|
||||||
|
ebiten.SetWindowTitle(targetMonitor.Name())
|
||||||
|
ebiten.SetWindowSize(screenWidth, screenHeight)
|
||||||
|
|
||||||
|
if err := ebiten.RunGame(g); err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
}
|
@ -94,6 +94,10 @@ func (m *Monitor) GetVideoMode() *VidMode {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (m *Monitor) GetName() string {
|
||||||
|
return m.m.GetName()
|
||||||
|
}
|
||||||
|
|
||||||
type Window struct {
|
type Window struct {
|
||||||
w *cglfw.Window
|
w *cglfw.Window
|
||||||
|
|
||||||
|
@ -56,6 +56,14 @@ func (m *Monitor) GetVideoMode() *VidMode {
|
|||||||
return (*VidMode)(v)
|
return (*VidMode)(v)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (m *Monitor) GetName() string {
|
||||||
|
v, err := (*goglfw.Monitor)(m).GetName()
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
return v
|
||||||
|
}
|
||||||
|
|
||||||
type Window goglfw.Window
|
type Window goglfw.Window
|
||||||
|
|
||||||
func (w *Window) Destroy() {
|
func (w *Window) Destroy() {
|
||||||
|
@ -95,3 +95,8 @@ type RunOptions struct {
|
|||||||
ScreenTransparent bool
|
ScreenTransparent bool
|
||||||
SkipTaskbar bool
|
SkipTaskbar bool
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// InitialWindowPosition returns the position for centering the given second width/height pair within the first width/height pair.
|
||||||
|
func InitialWindowPosition(mw, mh, ww, wh int) (x, y int) {
|
||||||
|
return (mw - ww) / 2, (mh - wh) / 3
|
||||||
|
}
|
||||||
|
@ -85,6 +85,7 @@ type userInterfaceImpl struct {
|
|||||||
initFullscreen bool
|
initFullscreen bool
|
||||||
initCursorMode CursorMode
|
initCursorMode CursorMode
|
||||||
initWindowDecorated bool
|
initWindowDecorated bool
|
||||||
|
initWindowMonitor int
|
||||||
initWindowPositionXInDIP int
|
initWindowPositionXInDIP int
|
||||||
initWindowPositionYInDIP int
|
initWindowPositionYInDIP int
|
||||||
initWindowWidthInDIP int
|
initWindowWidthInDIP int
|
||||||
@ -135,6 +136,7 @@ func init() {
|
|||||||
maxWindowHeightInDIP: glfw.DontCare,
|
maxWindowHeightInDIP: glfw.DontCare,
|
||||||
initCursorMode: CursorModeVisible,
|
initCursorMode: CursorModeVisible,
|
||||||
initWindowDecorated: true,
|
initWindowDecorated: true,
|
||||||
|
initWindowMonitor: glfw.DontCare,
|
||||||
initWindowPositionXInDIP: invalidPos,
|
initWindowPositionXInDIP: invalidPos,
|
||||||
initWindowPositionYInDIP: invalidPos,
|
initWindowPositionYInDIP: invalidPos,
|
||||||
initWindowWidthInDIP: 640,
|
initWindowWidthInDIP: 640,
|
||||||
@ -180,13 +182,7 @@ func initialize() error {
|
|||||||
return errors.New("ui: no monitor was found at initialize")
|
return errors.New("ui: no monitor was found at initialize")
|
||||||
}
|
}
|
||||||
|
|
||||||
theUI.initMonitor = m
|
theUI.setInitMonitor(m)
|
||||||
theUI.initDeviceScaleFactor = theUI.deviceScaleFactor(m)
|
|
||||||
// GetVideoMode must be called from the main thread, then call this here and record
|
|
||||||
// initFullscreen{Width,Height}InDIP.
|
|
||||||
v := m.GetVideoMode()
|
|
||||||
theUI.initFullscreenWidthInDIP = int(theUI.dipFromGLFWMonitorPixel(float64(v.Width), m))
|
|
||||||
theUI.initFullscreenHeightInDIP = int(theUI.dipFromGLFWMonitorPixel(float64(v.Height), m))
|
|
||||||
|
|
||||||
// Create system cursors. These cursors are destroyed at glfw.Terminate().
|
// Create system cursors. These cursors are destroyed at glfw.Terminate().
|
||||||
glfwSystemCursors[CursorShapeDefault] = nil
|
glfwSystemCursors[CursorShapeDefault] = nil
|
||||||
@ -203,12 +199,43 @@ func initialize() error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
type monitor struct {
|
func (u *userInterfaceImpl) setInitMonitor(m *glfw.Monitor) {
|
||||||
|
u.initMonitor = m
|
||||||
|
u.initDeviceScaleFactor = u.deviceScaleFactor(m)
|
||||||
|
// GetVideoMode must be called from the main thread, then call this here and record
|
||||||
|
// initFullscreen{Width,Height}InDIP.
|
||||||
|
v := m.GetVideoMode()
|
||||||
|
u.initFullscreenWidthInDIP = int(u.dipFromGLFWMonitorPixel(float64(v.Width), m))
|
||||||
|
u.initFullscreenHeightInDIP = int(u.dipFromGLFWMonitorPixel(float64(v.Height), m))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Monitor is a wrapper around glfw.Monitor.
|
||||||
|
type Monitor struct {
|
||||||
m *glfw.Monitor
|
m *glfw.Monitor
|
||||||
vm *glfw.VidMode
|
vm *glfw.VidMode
|
||||||
// Pos of monitor in virtual coords
|
// Pos of monitor in virtual coords
|
||||||
x int
|
x int
|
||||||
y int
|
y int
|
||||||
|
width int
|
||||||
|
height int
|
||||||
|
id int
|
||||||
|
name string
|
||||||
|
}
|
||||||
|
|
||||||
|
// Bounds returns the monitor's bounds.
|
||||||
|
func (m *Monitor) Bounds() image.Rectangle {
|
||||||
|
ui := Get()
|
||||||
|
return image.Rect(
|
||||||
|
int(ui.dipFromGLFWMonitorPixel(float64(m.x), m.m)),
|
||||||
|
int(ui.dipFromGLFWMonitorPixel(float64(m.y), m.m)),
|
||||||
|
int(ui.dipFromGLFWMonitorPixel(float64(m.x+m.width), m.m)),
|
||||||
|
int(ui.dipFromGLFWMonitorPixel(float64(m.x+m.height), m.m)),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Name returns the monitor's name.
|
||||||
|
func (m *Monitor) Name() string {
|
||||||
|
return m.name
|
||||||
}
|
}
|
||||||
|
|
||||||
// monitors is the monitor list cache for desktop glfw compile targets.
|
// monitors is the monitor list cache for desktop glfw compile targets.
|
||||||
@ -216,25 +243,63 @@ type monitor struct {
|
|||||||
// monitor config change event.
|
// monitor config change event.
|
||||||
//
|
//
|
||||||
// monitors must be manipulated on the main thread.
|
// monitors must be manipulated on the main thread.
|
||||||
var monitors []*monitor
|
var monitors []*Monitor
|
||||||
|
|
||||||
|
// AppendMonitors appends the current monitors to the passed in mons slice and returns it.
|
||||||
|
func (u *userInterfaceImpl) AppendMonitors(mons []*Monitor) []*Monitor {
|
||||||
|
u.m.RLock()
|
||||||
|
defer u.m.RUnlock()
|
||||||
|
return append(mons, monitors...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Monitor returns the window's current monitor. Returns nil if there is no current monitor yet.
|
||||||
|
func (u *userInterfaceImpl) Monitor() *Monitor {
|
||||||
|
if !u.isRunning() {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
var monitor *Monitor
|
||||||
|
u.mainThread.Call(func() {
|
||||||
|
glfwMonitor := u.currentMonitor()
|
||||||
|
if glfwMonitor == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
for _, m := range monitors {
|
||||||
|
if m.m == glfwMonitor {
|
||||||
|
monitor = m
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
return monitor
|
||||||
|
}
|
||||||
|
|
||||||
func updateMonitors() {
|
func updateMonitors() {
|
||||||
monitors = nil
|
monitors = nil
|
||||||
ms := glfw.GetMonitors()
|
ms := glfw.GetMonitors()
|
||||||
for _, m := range ms {
|
for i, m := range ms {
|
||||||
x, y := m.GetPos()
|
monitor := glfwMonitorToMonitor(m)
|
||||||
monitors = append(monitors, &monitor{
|
monitor.id = i
|
||||||
m: m,
|
monitors = append(monitors, &monitor)
|
||||||
vm: m.GetVideoMode(),
|
|
||||||
x: x,
|
|
||||||
y: y,
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
clearVideoModeScaleCache()
|
clearVideoModeScaleCache()
|
||||||
devicescale.ClearCache()
|
devicescale.ClearCache()
|
||||||
}
|
}
|
||||||
|
|
||||||
func ensureMonitors() []*monitor {
|
func glfwMonitorToMonitor(m *glfw.Monitor) Monitor {
|
||||||
|
x, y := m.GetPos()
|
||||||
|
vm := m.GetVideoMode()
|
||||||
|
return Monitor{
|
||||||
|
m: m,
|
||||||
|
vm: m.GetVideoMode(),
|
||||||
|
x: x,
|
||||||
|
y: y,
|
||||||
|
width: vm.Width,
|
||||||
|
height: vm.Height,
|
||||||
|
name: m.GetName(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func ensureMonitors() []*Monitor {
|
||||||
if len(monitors) == 0 {
|
if len(monitors) == 0 {
|
||||||
updateMonitors()
|
updateMonitors()
|
||||||
}
|
}
|
||||||
@ -245,7 +310,7 @@ func ensureMonitors() []*monitor {
|
|||||||
// or returns nil if monitor is not found.
|
// or returns nil if monitor is not found.
|
||||||
//
|
//
|
||||||
// getMonitorFromPosition must be called on the main thread.
|
// getMonitorFromPosition must be called on the main thread.
|
||||||
func getMonitorFromPosition(wx, wy int) *monitor {
|
func getMonitorFromPosition(wx, wy int) *Monitor {
|
||||||
for _, m := range ensureMonitors() {
|
for _, m := range ensureMonitors() {
|
||||||
// TODO: Fix incorrectness in the cases of https://github.com/glfw/glfw/issues/1961.
|
// TODO: Fix incorrectness in the cases of https://github.com/glfw/glfw/issues/1961.
|
||||||
// See also internal/devicescale/impl_desktop.go for a maybe better way of doing this.
|
// See also internal/devicescale/impl_desktop.go for a maybe better way of doing this.
|
||||||
@ -268,6 +333,74 @@ func (u *userInterfaceImpl) setRunning(running bool) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// setWindowMonitor must be called on the main thread.
|
||||||
|
func (u *userInterfaceImpl) setWindowMonitor(monitor int) {
|
||||||
|
if microsoftgdk.IsXbox() {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
u.m.RLock()
|
||||||
|
defer u.m.RUnlock()
|
||||||
|
|
||||||
|
m := monitors[monitor].m
|
||||||
|
|
||||||
|
// Ignore if it is the same monitor.
|
||||||
|
if m == u.window.GetMonitor() {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// We set w, h so it can be set to the original dimensions if fullscreen.
|
||||||
|
w, h := u.window.GetSize()
|
||||||
|
fullscreen := u.isFullscreen()
|
||||||
|
// This is copied from setFullscreen. They should probably use a shared function.
|
||||||
|
if fullscreen {
|
||||||
|
origX, origY := u.origWindowPos()
|
||||||
|
|
||||||
|
w = int(u.dipToGLFWPixel(float64(u.origWindowWidthInDIP), u.currentMonitor()))
|
||||||
|
h = int(u.dipToGLFWPixel(float64(u.origWindowHeightInDIP), u.currentMonitor()))
|
||||||
|
if u.isNativeFullscreenAvailable() {
|
||||||
|
u.setNativeFullscreen(false)
|
||||||
|
// Adjust the window size later (after adjusting the position).
|
||||||
|
} else if !u.isNativeFullscreenAvailable() && u.window.GetMonitor() != nil {
|
||||||
|
u.window.SetMonitor(nil, 0, 0, w, h, 0)
|
||||||
|
}
|
||||||
|
|
||||||
|
// glfw.PollEvents is necessary for macOS to enable (*glfw.Window).SetPos and SetSize (#2296).
|
||||||
|
// This polling causes issues on Linux and Windows when rapidly toggling fullscreen, so we only run it under macOS.
|
||||||
|
if runtime.GOOS == "darwin" {
|
||||||
|
glfw.PollEvents()
|
||||||
|
}
|
||||||
|
|
||||||
|
if origX != invalidPos && origY != invalidPos {
|
||||||
|
u.window.SetPos(origX, origY)
|
||||||
|
// Dirty hack for macOS (#703). Rendering doesn't work correctly with one SetPos, but
|
||||||
|
// work with two or more SetPos.
|
||||||
|
if runtime.GOOS == "darwin" {
|
||||||
|
u.window.SetPos(origX+1, origY)
|
||||||
|
u.window.SetPos(origX, origY)
|
||||||
|
}
|
||||||
|
u.setOrigWindowPos(invalidPos, invalidPos)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
x, y := m.GetPos()
|
||||||
|
px, py := InitialWindowPosition(m.GetVideoMode().Width, m.GetVideoMode().Height, w, h)
|
||||||
|
u.window.SetPos(x+px, y+py)
|
||||||
|
|
||||||
|
if fullscreen {
|
||||||
|
if u.isNativeFullscreenAvailable() {
|
||||||
|
u.setNativeFullscreen(fullscreen)
|
||||||
|
} else {
|
||||||
|
v := m.GetVideoMode()
|
||||||
|
u.window.SetMonitor(m, 0, 0, v.Width, v.Height, v.RefreshRate)
|
||||||
|
}
|
||||||
|
|
||||||
|
u.setOrigWindowPos(x, y)
|
||||||
|
|
||||||
|
u.adjustViewSizeAfterFullscreen()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func (u *userInterfaceImpl) getWindowSizeLimitsInDIP() (minw, minh, maxw, maxh int) {
|
func (u *userInterfaceImpl) getWindowSizeLimitsInDIP() (minw, minh, maxw, maxh int) {
|
||||||
if microsoftgdk.IsXbox() {
|
if microsoftgdk.IsXbox() {
|
||||||
return glfw.DontCare, glfw.DontCare, glfw.DontCare, glfw.DontCare
|
return glfw.DontCare, glfw.DontCare, glfw.DontCare, glfw.DontCare
|
||||||
@ -381,6 +514,24 @@ func (u *userInterfaceImpl) setIconImages(iconImages []image.Image) {
|
|||||||
u.m.Unlock()
|
u.m.Unlock()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (u *userInterfaceImpl) getInitWindowMonitor() int {
|
||||||
|
u.m.RLock()
|
||||||
|
v := u.initWindowMonitor
|
||||||
|
u.m.RUnlock()
|
||||||
|
return v
|
||||||
|
}
|
||||||
|
|
||||||
|
func (u *userInterfaceImpl) setInitWindowMonitor(monitor int) {
|
||||||
|
if microsoftgdk.IsXbox() {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
u.m.Lock()
|
||||||
|
defer u.m.Unlock()
|
||||||
|
|
||||||
|
u.initWindowMonitor = monitor
|
||||||
|
}
|
||||||
|
|
||||||
func (u *userInterfaceImpl) getInitWindowPositionInDIP() (int, int) {
|
func (u *userInterfaceImpl) getInitWindowPositionInDIP() (int, int) {
|
||||||
if microsoftgdk.IsXbox() {
|
if microsoftgdk.IsXbox() {
|
||||||
return 0, 0
|
return 0, 0
|
||||||
@ -670,7 +821,7 @@ func init() {
|
|||||||
// createWindow must be called from the main thread.
|
// createWindow must be called from the main thread.
|
||||||
//
|
//
|
||||||
// createWindow does not set the position or size so far.
|
// createWindow does not set the position or size so far.
|
||||||
func (u *userInterfaceImpl) createWindow(width, height int) error {
|
func (u *userInterfaceImpl) createWindow(width, height int, monitor int) error {
|
||||||
if u.window != nil {
|
if u.window != nil {
|
||||||
panic("ui: u.window must not exist at createWindow")
|
panic("ui: u.window must not exist at createWindow")
|
||||||
}
|
}
|
||||||
@ -680,7 +831,16 @@ func (u *userInterfaceImpl) createWindow(width, height int) error {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Set our target monitor if provided. This is required to prevent an initial window flash on the default monitor.
|
||||||
|
if monitor != glfw.DontCare {
|
||||||
|
m := monitors[monitor]
|
||||||
|
x, y := m.m.GetPos()
|
||||||
|
px, py := InitialWindowPosition(m.m.GetVideoMode().Width, m.m.GetVideoMode().Height, width, height)
|
||||||
|
window.SetPos(x+px, y+py)
|
||||||
|
}
|
||||||
initializeWindowAfterCreation(window)
|
initializeWindowAfterCreation(window)
|
||||||
|
|
||||||
u.window = window
|
u.window = window
|
||||||
|
|
||||||
// Even just after a window creation, FramebufferSize callback might be invoked (#1847).
|
// Even just after a window creation, FramebufferSize callback might be invoked (#1847).
|
||||||
@ -866,10 +1026,17 @@ func (u *userInterfaceImpl) initOnMainThread(options *RunOptions) error {
|
|||||||
glfw.WindowHint(glfw.Visible, glfw.True)
|
glfw.WindowHint(glfw.Visible, glfw.True)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Get our target monitor.
|
||||||
|
monitor := u.getInitWindowMonitor()
|
||||||
|
|
||||||
|
if monitor != glfw.DontCare {
|
||||||
|
u.setInitMonitor(monitors[monitor].m)
|
||||||
|
}
|
||||||
|
|
||||||
ww, wh := u.getInitWindowSizeInDIP()
|
ww, wh := u.getInitWindowSizeInDIP()
|
||||||
initW := int(u.dipToGLFWPixel(float64(ww), u.initMonitor))
|
initW := int(u.dipToGLFWPixel(float64(ww), u.initMonitor))
|
||||||
initH := int(u.dipToGLFWPixel(float64(wh), u.initMonitor))
|
initH := int(u.dipToGLFWPixel(float64(wh), u.initMonitor))
|
||||||
if err := u.createWindow(initW, initH); err != nil {
|
if err := u.createWindow(initW, initH, monitor); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1371,11 +1538,6 @@ func (u *userInterfaceImpl) currentMonitor() *glfw.Monitor {
|
|||||||
//
|
//
|
||||||
// monitorFromWindow must be called on the main thread.
|
// monitorFromWindow must be called on the main thread.
|
||||||
func monitorFromWindow(window *glfw.Window) *glfw.Monitor {
|
func monitorFromWindow(window *glfw.Window) *glfw.Monitor {
|
||||||
// GetMonitor is available only in fullscreen.
|
|
||||||
if m := window.GetMonitor(); m != nil {
|
|
||||||
return m
|
|
||||||
}
|
|
||||||
|
|
||||||
// Getting a monitor from a window position is not reliable in general (e.g., when a window is put across
|
// Getting a monitor from a window position is not reliable in general (e.g., when a window is put across
|
||||||
// multiple monitors, or, before SetWindowPosition is called.).
|
// multiple monitors, or, before SetWindowPosition is called.).
|
||||||
// Get the monitor which the current window belongs to. This requires OS API.
|
// Get the monitor which the current window belongs to. This requires OS API.
|
||||||
|
@ -15,6 +15,7 @@
|
|||||||
package ui
|
package ui
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"image"
|
||||||
"sync"
|
"sync"
|
||||||
"syscall/js"
|
"syscall/js"
|
||||||
"time"
|
"time"
|
||||||
@ -756,6 +757,29 @@ func (u *userInterfaceImpl) Window() Window {
|
|||||||
return &nullWindow{}
|
return &nullWindow{}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type Monitor struct{}
|
||||||
|
|
||||||
|
var theMonitor = &Monitor{}
|
||||||
|
|
||||||
|
func (m *Monitor) Bounds() image.Rectangle {
|
||||||
|
screen := window.Get("screen")
|
||||||
|
w := screen.Get("width").Int()
|
||||||
|
h := screen.Get("height").Int()
|
||||||
|
return image.Rect(0, 0, w, h)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *Monitor) Name() string {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
func (u *userInterfaceImpl) AppendMonitors(mons []*Monitor) []*Monitor {
|
||||||
|
return append(mons, theMonitor)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (u *userInterfaceImpl) Monitor() *Monitor {
|
||||||
|
return theMonitor
|
||||||
|
}
|
||||||
|
|
||||||
func (u *userInterfaceImpl) beginFrame() {
|
func (u *userInterfaceImpl) beginFrame() {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -19,6 +19,7 @@ package ui
|
|||||||
import (
|
import (
|
||||||
stdcontext "context"
|
stdcontext "context"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"image"
|
||||||
"runtime/debug"
|
"runtime/debug"
|
||||||
"sync"
|
"sync"
|
||||||
"sync/atomic"
|
"sync/atomic"
|
||||||
@ -428,6 +429,27 @@ func (u *userInterfaceImpl) Window() Window {
|
|||||||
return &nullWindow{}
|
return &nullWindow{}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type Monitor struct{}
|
||||||
|
|
||||||
|
var theMonitor = &Monitor{}
|
||||||
|
|
||||||
|
func (m *Monitor) Bounds() image.Rectangle {
|
||||||
|
// TODO: This should return the available viewport dimensions.
|
||||||
|
return image.Rectangle{}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *Monitor) Name() string {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
func (u *userInterfaceImpl) AppendMonitors(mons []*Monitor) []*Monitor {
|
||||||
|
return append(mons, theMonitor)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (u *userInterfaceImpl) Monitor() *Monitor {
|
||||||
|
return theMonitor
|
||||||
|
}
|
||||||
|
|
||||||
func (u *userInterfaceImpl) UpdateInput(keys map[Key]struct{}, runes []rune, touches []TouchForInput) {
|
func (u *userInterfaceImpl) UpdateInput(keys map[Key]struct{}, runes []rune, touches []TouchForInput) {
|
||||||
u.updateInputState(keys, runes, touches)
|
u.updateInputState(keys, runes, touches)
|
||||||
if u.fpsMode == FPSModeVsyncOffMinimum {
|
if u.fpsMode == FPSModeVsyncOffMinimum {
|
||||||
|
@ -22,6 +22,7 @@ import "C"
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
stdcontext "context"
|
stdcontext "context"
|
||||||
|
"image"
|
||||||
"runtime"
|
"runtime"
|
||||||
"sync"
|
"sync"
|
||||||
|
|
||||||
@ -193,6 +194,27 @@ func (*userInterfaceImpl) Window() Window {
|
|||||||
return &nullWindow{}
|
return &nullWindow{}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type Monitor struct{}
|
||||||
|
|
||||||
|
var theMonitor = &Monitor{}
|
||||||
|
|
||||||
|
func (m *Monitor) Bounds() image.Rectangle {
|
||||||
|
// TODO: This should return the available viewport dimensions.
|
||||||
|
return image.Rectangle{}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *Monitor) Name() string {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
func (u *userInterfaceImpl) AppendMonitors(mons []*Monitor) []*Monitor {
|
||||||
|
return append(mons, theMonitor)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (u *userInterfaceImpl) Monitor() *Monitor {
|
||||||
|
return theMonitor
|
||||||
|
}
|
||||||
|
|
||||||
func (u *userInterfaceImpl) beginFrame() {
|
func (u *userInterfaceImpl) beginFrame() {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -23,6 +23,7 @@ type Window interface {
|
|||||||
SetDecorated(decorated bool)
|
SetDecorated(decorated bool)
|
||||||
ResizingMode() WindowResizingMode
|
ResizingMode() WindowResizingMode
|
||||||
SetResizingMode(mode WindowResizingMode)
|
SetResizingMode(mode WindowResizingMode)
|
||||||
|
SetMonitor(*Monitor)
|
||||||
Position() (int, int)
|
Position() (int, int)
|
||||||
SetPosition(x, y int)
|
SetPosition(x, y int)
|
||||||
Size() (int, int)
|
Size() (int, int)
|
||||||
@ -58,6 +59,9 @@ func (*nullWindow) ResizingMode() WindowResizingMode {
|
|||||||
func (*nullWindow) SetResizingMode(mode WindowResizingMode) {
|
func (*nullWindow) SetResizingMode(mode WindowResizingMode) {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (*nullWindow) SetMonitor(monitor *Monitor) {
|
||||||
|
}
|
||||||
|
|
||||||
func (*nullWindow) Position() (int, int) {
|
func (*nullWindow) Position() (int, int) {
|
||||||
return 0, 0
|
return 0, 0
|
||||||
}
|
}
|
||||||
|
@ -159,6 +159,19 @@ func (w *glfwWindow) Restore() {
|
|||||||
w.ui.mainThread.Call(w.ui.restoreWindow)
|
w.ui.mainThread.Call(w.ui.restoreWindow)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (w *glfwWindow) SetMonitor(monitor *Monitor) {
|
||||||
|
if monitor == nil {
|
||||||
|
panic("ui: monitor cannot be nil at SetMonitor")
|
||||||
|
}
|
||||||
|
if !w.ui.isRunning() {
|
||||||
|
w.ui.setInitWindowMonitor(monitor.id)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
w.ui.mainThread.Call(func() {
|
||||||
|
w.ui.setWindowMonitor(monitor.id)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
func (w *glfwWindow) Position() (int, int) {
|
func (w *glfwWindow) Position() (int, int) {
|
||||||
if !w.ui.isRunning() {
|
if !w.ui.isRunning() {
|
||||||
panic("ui: WindowPosition can't be called before the main loop starts")
|
panic("ui: WindowPosition can't be called before the main loop starts")
|
||||||
|
60
monitor.go
Normal file
60
monitor.go
Normal file
@ -0,0 +1,60 @@
|
|||||||
|
// Copyright 2023 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 ebiten
|
||||||
|
|
||||||
|
import (
|
||||||
|
"image"
|
||||||
|
|
||||||
|
"github.com/hajimehoshi/ebiten/v2/internal/ui"
|
||||||
|
)
|
||||||
|
|
||||||
|
// MonitorType represents a monitor available to the system.
|
||||||
|
type MonitorType ui.Monitor
|
||||||
|
|
||||||
|
// Bounds returns the position and size of the monitor in device-independent pixels.
|
||||||
|
func (m *MonitorType) Bounds() image.Rectangle {
|
||||||
|
return (*ui.Monitor)(m).Bounds()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Name returns the monitor's name. On Linux, this reports the monitors in xrandr format.
|
||||||
|
// On Windows, this reports "Generic PnP Monitor" for all monitors.
|
||||||
|
func (m *MonitorType) Name() string {
|
||||||
|
return (*ui.Monitor)(m).Name()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Monitor returns the current monitor.
|
||||||
|
func Monitor() *MonitorType {
|
||||||
|
m := ui.Get().Monitor()
|
||||||
|
if m == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return (*MonitorType)(m)
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetMonitor sets the monitor that the window should be on. This can be called before or after Run.
|
||||||
|
func SetMonitor(monitor *MonitorType) {
|
||||||
|
ui.Get().Window().SetMonitor((*ui.Monitor)(monitor))
|
||||||
|
}
|
||||||
|
|
||||||
|
// AppendMonitors returns the monitors reported by the system.
|
||||||
|
// On desktop platforms, there will always be at least one monitor appended and the first monitor in the slice will be the primary monitor.
|
||||||
|
// Any monitors added or removed will show up with subsequent calls to this function.
|
||||||
|
func AppendMonitors(monitors []*MonitorType) []*MonitorType {
|
||||||
|
// TODO: This is not an efficient operation. It would be best if we could directly pass monitors directly into `ui.AppendMonitors`.
|
||||||
|
for _, m := range ui.Get().AppendMonitors(nil) {
|
||||||
|
monitors = append(monitors, (*MonitorType)(m))
|
||||||
|
}
|
||||||
|
return monitors
|
||||||
|
}
|
@ -166,8 +166,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().ScreenSizeInFullscreen()
|
||||||
x := (sw - width) / 2
|
x, y := ui.InitialWindowPosition(sw, sh, width, height)
|
||||||
y := (sh - height) / 3
|
|
||||||
ui.Get().Window().SetPosition(x, y)
|
ui.Get().Window().SetPosition(x, y)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user