vendor dependencies, make some changes to how input is done
This commit is contained in:
+160
@@ -0,0 +1,160 @@
|
||||
// Copyright 2022 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
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"runtime"
|
||||
"syscall"
|
||||
"unsafe"
|
||||
|
||||
"golang.org/x/sys/windows"
|
||||
)
|
||||
|
||||
const (
|
||||
_CLSCTX_INPROC_SERVER = 0x1
|
||||
_CLSCTX_LOCAL_SERVER = 0x4
|
||||
_CLSCTX_REMOTE_SERVER = 0x10
|
||||
_CLSCTX_SERVER = _CLSCTX_INPROC_SERVER | _CLSCTX_LOCAL_SERVER | _CLSCTX_REMOTE_SERVER
|
||||
_MONITOR_DEFAULTTONEAREST = 2
|
||||
_SM_CYCAPTION = 4
|
||||
)
|
||||
|
||||
var (
|
||||
_CLSID_TaskbarList = windows.GUID{
|
||||
Data1: 0x56FDF344,
|
||||
Data2: 0xFD6D,
|
||||
Data3: 0x11D0,
|
||||
Data4: [...]byte{0x95, 0x8A, 0x00, 0x60, 0x97, 0xC9, 0xA0, 0x90},
|
||||
}
|
||||
_IID_ITaskbarList = windows.GUID{
|
||||
Data1: 0x56FDF342,
|
||||
Data2: 0xFD6D,
|
||||
Data3: 0x11D0,
|
||||
Data4: [...]byte{0x95, 0x8A, 0x00, 0x60, 0x97, 0xC9, 0xA0, 0x90},
|
||||
}
|
||||
)
|
||||
|
||||
type _RECT struct {
|
||||
left int32
|
||||
top int32
|
||||
right int32
|
||||
bottom int32
|
||||
}
|
||||
|
||||
type _MONITORINFO struct {
|
||||
cbSize uint32
|
||||
rcMonitor _RECT
|
||||
rcWork _RECT
|
||||
dwFlags uint32
|
||||
}
|
||||
|
||||
type _POINT struct {
|
||||
x int32
|
||||
y int32
|
||||
}
|
||||
|
||||
var (
|
||||
ole32 = windows.NewLazySystemDLL("ole32.dll")
|
||||
user32 = windows.NewLazySystemDLL("user32.dll")
|
||||
|
||||
procCoCreateInstance = ole32.NewProc("CoCreateInstance")
|
||||
|
||||
procGetSystemMetrics = user32.NewProc("GetSystemMetrics")
|
||||
procMonitorFromWindow = user32.NewProc("MonitorFromWindow")
|
||||
procGetMonitorInfoW = user32.NewProc("GetMonitorInfoW")
|
||||
procGetCursorPos = user32.NewProc("GetCursorPos")
|
||||
)
|
||||
|
||||
func _CoCreateInstance(rclsid *windows.GUID, pUnkOuter unsafe.Pointer, dwClsContext uint32, riid *windows.GUID) (unsafe.Pointer, error) {
|
||||
var ptr unsafe.Pointer
|
||||
r, _, _ := procCoCreateInstance.Call(uintptr(unsafe.Pointer(rclsid)), uintptr(pUnkOuter), uintptr(dwClsContext), uintptr(unsafe.Pointer(riid)), uintptr(unsafe.Pointer(&ptr)))
|
||||
runtime.KeepAlive(rclsid)
|
||||
runtime.KeepAlive(riid)
|
||||
if uint32(r) != uint32(windows.S_OK) {
|
||||
return nil, fmt.Errorf("ui: CoCreateInstance failed: error code: HRESULT(%d)", uint32(r))
|
||||
}
|
||||
return ptr, nil
|
||||
}
|
||||
|
||||
func _GetSystemMetrics(nIndex int) (int32, error) {
|
||||
r, _, _ := procGetSystemMetrics.Call(uintptr(nIndex))
|
||||
if int32(r) == 0 {
|
||||
// GetLastError doesn't provide an extended information.
|
||||
// See https://docs.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-getsystemmetrics
|
||||
return 0, fmt.Errorf("ui: GetSystemMetrics returned 0")
|
||||
}
|
||||
return int32(r), nil
|
||||
}
|
||||
|
||||
func _MonitorFromWindow(hwnd windows.HWND, dwFlags uint32) uintptr {
|
||||
r, _, _ := procMonitorFromWindow.Call(uintptr(hwnd), uintptr(dwFlags))
|
||||
return r
|
||||
}
|
||||
|
||||
func _GetMonitorInfoW(hMonitor uintptr) (_MONITORINFO, error) {
|
||||
mi := _MONITORINFO{}
|
||||
mi.cbSize = uint32(unsafe.Sizeof(mi))
|
||||
|
||||
r, _, e := procGetMonitorInfoW.Call(hMonitor, uintptr(unsafe.Pointer(&mi)))
|
||||
if int32(r) == 0 {
|
||||
if e != nil && !errors.Is(e, windows.ERROR_SUCCESS) {
|
||||
return _MONITORINFO{}, fmt.Errorf("ui: GetMonitorInfoW failed: error code: %w", e)
|
||||
}
|
||||
return _MONITORINFO{}, fmt.Errorf("ui: GetMonitorInfoW failed: returned 0")
|
||||
}
|
||||
return mi, nil
|
||||
}
|
||||
|
||||
func _GetCursorPos() (int32, int32, error) {
|
||||
var pt _POINT
|
||||
r, _, e := procGetCursorPos.Call(uintptr(unsafe.Pointer(&pt)))
|
||||
if int32(r) == 0 {
|
||||
if e != nil && !errors.Is(e, windows.ERROR_SUCCESS) {
|
||||
return 0, 0, fmt.Errorf("ui: GetCursorPos failed: error code: %w", e)
|
||||
}
|
||||
return 0, 0, fmt.Errorf("ui: GetCursorPos failed: returned 0")
|
||||
}
|
||||
return pt.x, pt.y, nil
|
||||
}
|
||||
|
||||
type _ITaskbarList struct {
|
||||
vtbl *_ITaskbarList_Vtbl
|
||||
}
|
||||
|
||||
type _ITaskbarList_Vtbl struct {
|
||||
QueryInterface uintptr
|
||||
AddRef uintptr
|
||||
Release uintptr
|
||||
|
||||
HrInit uintptr
|
||||
AddTab uintptr
|
||||
DeleteTab uintptr
|
||||
ActivateTab uintptr
|
||||
SetActiveAlt uintptr
|
||||
}
|
||||
|
||||
func (i *_ITaskbarList) DeleteTab(hwnd windows.HWND) error {
|
||||
r, _, _ := syscall.Syscall(i.vtbl.DeleteTab, 2, uintptr(unsafe.Pointer(i)), uintptr(hwnd), 0)
|
||||
if uint32(r) != uint32(windows.S_OK) {
|
||||
return fmt.Errorf("ui: ITaskbarList::DeleteTab failed: HRESULT(%d)", uint32(r))
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (i *_ITaskbarList) Release() {
|
||||
_, _, _ = syscall.Syscall(i.vtbl.Release, 1, uintptr(unsafe.Pointer(i)), 0, 0)
|
||||
}
|
||||
+331
@@ -0,0 +1,331 @@
|
||||
// Copyright 2022 The Ebiten 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
|
||||
|
||||
import (
|
||||
"math"
|
||||
"time"
|
||||
|
||||
"github.com/hajimehoshi/ebiten/v2/internal/atlas"
|
||||
"github.com/hajimehoshi/ebiten/v2/internal/clock"
|
||||
"github.com/hajimehoshi/ebiten/v2/internal/debug"
|
||||
"github.com/hajimehoshi/ebiten/v2/internal/graphicsdriver"
|
||||
"github.com/hajimehoshi/ebiten/v2/internal/hook"
|
||||
)
|
||||
|
||||
var (
|
||||
NearestFilterShader = &Shader{shader: atlas.NearestFilterShader}
|
||||
LinearFilterShader = &Shader{shader: atlas.LinearFilterShader}
|
||||
)
|
||||
|
||||
type Game interface {
|
||||
NewOffscreenImage(width, height int) *Image
|
||||
NewScreenImage(width, height int) *Image
|
||||
Layout(outsideWidth, outsideHeight float64) (screenWidth, screenHeight float64)
|
||||
UpdateInputState(fn func(*InputState))
|
||||
Update() error
|
||||
DrawOffscreen() error
|
||||
DrawFinalScreen(scale, offsetX, offsetY float64)
|
||||
}
|
||||
|
||||
type context struct {
|
||||
game Game
|
||||
|
||||
updateCalled bool
|
||||
|
||||
offscreen *Image
|
||||
screen *Image
|
||||
|
||||
screenWidth float64
|
||||
screenHeight float64
|
||||
offscreenWidth float64
|
||||
offscreenHeight float64
|
||||
|
||||
isOffscreenModified bool
|
||||
lastDrawTime time.Time
|
||||
|
||||
skipCount int
|
||||
|
||||
funcsInFrameCh chan func()
|
||||
}
|
||||
|
||||
func newContext(game Game) *context {
|
||||
return &context{
|
||||
game: game,
|
||||
funcsInFrameCh: make(chan func()),
|
||||
}
|
||||
}
|
||||
|
||||
func (c *context) updateFrame(graphicsDriver graphicsdriver.Graphics, outsideWidth, outsideHeight float64, deviceScaleFactor float64, ui *UserInterface) error {
|
||||
// TODO: If updateCount is 0 and vsync is disabled, swapping buffers can be skipped.
|
||||
return c.updateFrameImpl(graphicsDriver, clock.UpdateFrame(), outsideWidth, outsideHeight, deviceScaleFactor, ui, false)
|
||||
}
|
||||
|
||||
func (c *context) forceUpdateFrame(graphicsDriver graphicsdriver.Graphics, outsideWidth, outsideHeight float64, deviceScaleFactor float64, ui *UserInterface) error {
|
||||
n := 1
|
||||
if ui.GraphicsLibrary() == GraphicsLibraryDirectX {
|
||||
// On DirectX, both framebuffers in the swap chain should be updated.
|
||||
// Or, the rendering result becomes unexpected when the window is resized.
|
||||
n = 2
|
||||
}
|
||||
for i := 0; i < n; i++ {
|
||||
if err := c.updateFrameImpl(graphicsDriver, 1, outsideWidth, outsideHeight, deviceScaleFactor, ui, true); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *context) updateFrameImpl(graphicsDriver graphicsdriver.Graphics, updateCount int, outsideWidth, outsideHeight float64, deviceScaleFactor float64, ui *UserInterface, forceDraw bool) (err error) {
|
||||
// The given outside size can be 0 e.g. just after restoring from the fullscreen mode on Windows (#1589)
|
||||
// Just ignore such cases. Otherwise, creating a zero-sized framebuffer causes a panic.
|
||||
if outsideWidth == 0 || outsideHeight == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
debug.Logf("----\n")
|
||||
|
||||
if err := atlas.BeginFrame(graphicsDriver); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
defer func() {
|
||||
if err1 := atlas.EndFrame(); err1 != nil && err == nil {
|
||||
err = err1
|
||||
return
|
||||
}
|
||||
|
||||
if err1 := atlas.SwapBuffers(graphicsDriver); err1 != nil && err == nil {
|
||||
err = err1
|
||||
return
|
||||
}
|
||||
}()
|
||||
|
||||
// Flush deferred functions, like reading pixels from GPU.
|
||||
if err := c.processFuncsInFrame(ui); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// ForceUpdate can be invoked even if the context is not initialized yet (#1591).
|
||||
if w, h := c.layoutGame(outsideWidth, outsideHeight, deviceScaleFactor); w == 0 || h == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Update the input state after the layout is updated as a cursor position is affected by the layout.
|
||||
if err := ui.updateInputState(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Ensure that Update is called once before Draw so that Update can be used for initialization.
|
||||
if !c.updateCalled && updateCount == 0 {
|
||||
updateCount = 1
|
||||
c.updateCalled = true
|
||||
}
|
||||
debug.Logf("Update count per frame: %d\n", updateCount)
|
||||
|
||||
// Update the game.
|
||||
for i := 0; i < updateCount; i++ {
|
||||
// Read the input state and use it for one tick to give a consistent result for one tick (#2496, #2501).
|
||||
c.game.UpdateInputState(func(inputState *InputState) {
|
||||
ui.readInputState(inputState)
|
||||
})
|
||||
|
||||
if err := hook.RunBeforeUpdateHooks(); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := c.game.Update(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Catch the error that happened at (*Image).At.
|
||||
if err := ui.error(); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
// Update window icons during a frame, since an icon might be *ebiten.Image and
|
||||
// getting pixels from it needs to be in a frame (#1468).
|
||||
if err := ui.updateIconIfNeeded(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Draw the game.
|
||||
if err := c.drawGame(graphicsDriver, ui, forceDraw); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *context) newOffscreenImage(w, h int) *Image {
|
||||
img := c.game.NewOffscreenImage(w, h)
|
||||
img.modifyCallback = func() {
|
||||
c.isOffscreenModified = true
|
||||
}
|
||||
return img
|
||||
}
|
||||
|
||||
func (c *context) drawGame(graphicsDriver graphicsdriver.Graphics, ui *UserInterface, forceDraw bool) error {
|
||||
if (c.offscreen.imageType == atlas.ImageTypeVolatile) != ui.IsScreenClearedEveryFrame() {
|
||||
w, h := c.offscreen.width, c.offscreen.height
|
||||
c.offscreen.Deallocate()
|
||||
c.offscreen = c.newOffscreenImage(w, h)
|
||||
}
|
||||
|
||||
// isOffscreenModified is updated when an offscreen's modifyCallback.
|
||||
c.isOffscreenModified = false
|
||||
|
||||
// Even though updateCount == 0, the offscreen is cleared and Draw is called.
|
||||
// Draw should not update the game state and then the screen should not be updated without Update, but
|
||||
// users might want to process something at Draw with the time intervals of FPS.
|
||||
if ui.IsScreenClearedEveryFrame() {
|
||||
c.offscreen.clear()
|
||||
}
|
||||
|
||||
if err := c.game.DrawOffscreen(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
const maxSkipCount = 3
|
||||
|
||||
if !forceDraw && !c.isOffscreenModified {
|
||||
if c.skipCount < maxSkipCount {
|
||||
c.skipCount++
|
||||
}
|
||||
} else {
|
||||
c.skipCount = 0
|
||||
}
|
||||
|
||||
now := time.Now()
|
||||
defer func() {
|
||||
c.lastDrawTime = now
|
||||
}()
|
||||
|
||||
if c.skipCount < maxSkipCount {
|
||||
if graphicsDriver.NeedsClearingScreen() {
|
||||
// This clear is needed for fullscreen mode or some mobile platforms (#622).
|
||||
c.screen.clear()
|
||||
}
|
||||
|
||||
c.game.DrawFinalScreen(c.screenScaleAndOffsets())
|
||||
|
||||
// The final screen is never used as the rendering source.
|
||||
// Flush its buffer here just in case.
|
||||
c.screen.flushBufferIfNeeded()
|
||||
} else if delta := time.Second/60 - now.Sub(c.lastDrawTime); delta > 0 {
|
||||
// When swapping buffers is skipped and Draw is called too early, sleep for a while to suppress CPU usages (#2890).
|
||||
time.Sleep(delta)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *context) layoutGame(outsideWidth, outsideHeight float64, deviceScaleFactor float64) (int, int) {
|
||||
owf, ohf := c.game.Layout(outsideWidth, outsideHeight)
|
||||
if owf <= 0 || ohf <= 0 {
|
||||
panic("ui: Layout must return positive numbers")
|
||||
}
|
||||
|
||||
c.screenWidth = outsideWidth * deviceScaleFactor
|
||||
c.screenHeight = outsideHeight * deviceScaleFactor
|
||||
c.offscreenWidth = owf
|
||||
c.offscreenHeight = ohf
|
||||
|
||||
sw := int(math.Ceil(c.screenWidth))
|
||||
sh := int(math.Ceil(c.screenHeight))
|
||||
ow := int(math.Ceil(c.offscreenWidth))
|
||||
oh := int(math.Ceil(c.offscreenHeight))
|
||||
|
||||
if c.screen != nil && (c.screen.width != sw || c.screen.height != sh) {
|
||||
c.screen.Deallocate()
|
||||
c.screen = nil
|
||||
}
|
||||
if c.screen == nil {
|
||||
c.screen = c.game.NewScreenImage(sw, sh)
|
||||
}
|
||||
|
||||
if c.offscreen != nil && (c.offscreen.width != ow || c.offscreen.height != oh) {
|
||||
c.offscreen.Deallocate()
|
||||
c.offscreen = nil
|
||||
}
|
||||
if c.offscreen == nil {
|
||||
c.offscreen = c.newOffscreenImage(ow, oh)
|
||||
}
|
||||
|
||||
return ow, oh
|
||||
}
|
||||
|
||||
func (c *context) clientPositionToLogicalPosition(x, y float64, deviceScaleFactor float64) (float64, float64) {
|
||||
s, ox, oy := c.screenScaleAndOffsets()
|
||||
// The scale 0 indicates that the screen is not initialized yet.
|
||||
// As any cursor values don't make sense, just return NaN.
|
||||
if s == 0 {
|
||||
return math.NaN(), math.NaN()
|
||||
}
|
||||
return (x*deviceScaleFactor - ox) / s, (y*deviceScaleFactor - oy) / s
|
||||
}
|
||||
|
||||
func (c *context) logicalPositionToClientPosition(x, y float64, deviceScaleFactor float64) (float64, float64) {
|
||||
s, ox, oy := c.screenScaleAndOffsets()
|
||||
return (x*s + ox) / deviceScaleFactor, (y*s + oy) / deviceScaleFactor
|
||||
}
|
||||
|
||||
func (c *context) screenScaleAndOffsets() (scale, offsetX, offsetY float64) {
|
||||
scaleX := c.screenWidth / c.offscreenWidth
|
||||
scaleY := c.screenHeight / c.offscreenHeight
|
||||
scale = math.Min(scaleX, scaleY)
|
||||
width := c.offscreenWidth * scale
|
||||
height := c.offscreenHeight * scale
|
||||
offsetX = (c.screenWidth - width) / 2
|
||||
offsetY = (c.screenHeight - height) / 2
|
||||
return
|
||||
}
|
||||
|
||||
func (u *UserInterface) LogicalPositionToClientPositionInNativePixels(x, y float64) (float64, float64) {
|
||||
s := u.Monitor().DeviceScaleFactor()
|
||||
x, y = u.context.logicalPositionToClientPosition(x, y, s)
|
||||
x = dipToNativePixels(x, s)
|
||||
y = dipToNativePixels(y, s)
|
||||
return x, y
|
||||
}
|
||||
|
||||
func (c *context) runInFrame(f func()) {
|
||||
ch := make(chan struct{})
|
||||
c.funcsInFrameCh <- func() {
|
||||
defer close(ch)
|
||||
f()
|
||||
}
|
||||
<-ch
|
||||
return
|
||||
}
|
||||
|
||||
func (c *context) processFuncsInFrame(ui *UserInterface) error {
|
||||
var processed bool
|
||||
for {
|
||||
select {
|
||||
case f := <-c.funcsInFrameCh:
|
||||
f()
|
||||
processed = true
|
||||
default:
|
||||
if processed {
|
||||
// Catch the error that happened at (*Image).At.
|
||||
if err := ui.error(); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
}
|
||||
}
|
||||
+129
@@ -0,0 +1,129 @@
|
||||
// Copyright 2022 The Ebiten 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
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
|
||||
"github.com/hajimehoshi/ebiten/v2/internal/graphicsdriver"
|
||||
)
|
||||
|
||||
type graphicsDriverCreator interface {
|
||||
newAuto() (graphicsdriver.Graphics, GraphicsLibrary, error)
|
||||
newOpenGL() (graphicsdriver.Graphics, error)
|
||||
newDirectX() (graphicsdriver.Graphics, error)
|
||||
newMetal() (graphicsdriver.Graphics, error)
|
||||
newPlayStation5() (graphicsdriver.Graphics, error)
|
||||
}
|
||||
|
||||
func newGraphicsDriver(creator graphicsDriverCreator, graphicsLibrary GraphicsLibrary) (graphicsdriver.Graphics, GraphicsLibrary, error) {
|
||||
if graphicsLibrary == GraphicsLibraryAuto {
|
||||
envName := "EBITENGINE_GRAPHICS_LIBRARY"
|
||||
env := os.Getenv(envName)
|
||||
if env == "" {
|
||||
// For backward compatibility, read the EBITEN_ version.
|
||||
envName = "EBITEN_GRAPHICS_LIBRARY"
|
||||
env = os.Getenv(envName)
|
||||
}
|
||||
|
||||
switch env {
|
||||
case "", "auto":
|
||||
// Keep the automatic choosing.
|
||||
case "opengl":
|
||||
graphicsLibrary = GraphicsLibraryOpenGL
|
||||
case "directx":
|
||||
graphicsLibrary = GraphicsLibraryDirectX
|
||||
case "metal":
|
||||
graphicsLibrary = GraphicsLibraryMetal
|
||||
case "playstation5":
|
||||
graphicsLibrary = GraphicsLibraryPlayStation5
|
||||
default:
|
||||
return nil, 0, fmt.Errorf("ui: an unsupported graphics library is specified by the environment variable: %s", env)
|
||||
}
|
||||
}
|
||||
|
||||
switch graphicsLibrary {
|
||||
case GraphicsLibraryAuto:
|
||||
g, lib, err := creator.newAuto()
|
||||
if err != nil {
|
||||
return nil, 0, err
|
||||
}
|
||||
if g == nil {
|
||||
return nil, 0, fmt.Errorf("ui: no graphics library is available")
|
||||
}
|
||||
return g, lib, nil
|
||||
case GraphicsLibraryOpenGL:
|
||||
g, err := creator.newOpenGL()
|
||||
if err != nil {
|
||||
return nil, 0, err
|
||||
}
|
||||
return g, GraphicsLibraryOpenGL, nil
|
||||
case GraphicsLibraryDirectX:
|
||||
g, err := creator.newDirectX()
|
||||
if err != nil {
|
||||
return nil, 0, err
|
||||
}
|
||||
return g, GraphicsLibraryDirectX, nil
|
||||
case GraphicsLibraryMetal:
|
||||
g, err := creator.newMetal()
|
||||
if err != nil {
|
||||
return nil, 0, err
|
||||
}
|
||||
return g, GraphicsLibraryMetal, nil
|
||||
case GraphicsLibraryPlayStation5:
|
||||
g, err := creator.newPlayStation5()
|
||||
if err != nil {
|
||||
return nil, 0, err
|
||||
}
|
||||
return g, GraphicsLibraryPlayStation5, nil
|
||||
default:
|
||||
return nil, 0, fmt.Errorf("ui: an unsupported graphics library is specified: %d", graphicsLibrary)
|
||||
}
|
||||
}
|
||||
|
||||
func (u *UserInterface) GraphicsDriverForTesting() graphicsdriver.Graphics {
|
||||
return u.graphicsDriver
|
||||
}
|
||||
|
||||
type GraphicsLibrary int
|
||||
|
||||
const (
|
||||
GraphicsLibraryAuto GraphicsLibrary = iota
|
||||
GraphicsLibraryUnknown
|
||||
GraphicsLibraryOpenGL
|
||||
GraphicsLibraryDirectX
|
||||
GraphicsLibraryMetal
|
||||
GraphicsLibraryPlayStation5
|
||||
)
|
||||
|
||||
func (g GraphicsLibrary) String() string {
|
||||
switch g {
|
||||
case GraphicsLibraryAuto:
|
||||
return "Auto"
|
||||
case GraphicsLibraryUnknown:
|
||||
return "Unknown"
|
||||
case GraphicsLibraryOpenGL:
|
||||
return "OpenGL"
|
||||
case GraphicsLibraryDirectX:
|
||||
return "DirectX"
|
||||
case GraphicsLibraryMetal:
|
||||
return "Metal"
|
||||
case GraphicsLibraryPlayStation5:
|
||||
return "PlayStation 5"
|
||||
default:
|
||||
return fmt.Sprintf("GraphicsLibrary(%d)", g)
|
||||
}
|
||||
}
|
||||
+361
@@ -0,0 +1,361 @@
|
||||
// Copyright 2022 The Ebiten 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
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"image"
|
||||
"math"
|
||||
|
||||
"github.com/hajimehoshi/ebiten/v2/internal/atlas"
|
||||
"github.com/hajimehoshi/ebiten/v2/internal/graphics"
|
||||
"github.com/hajimehoshi/ebiten/v2/internal/graphicsdriver"
|
||||
"github.com/hajimehoshi/ebiten/v2/internal/mipmap"
|
||||
)
|
||||
|
||||
// panicOnErrorOnReadingPixels indicates whether reading pixels panics on an error or not.
|
||||
// This value is set only on testing.
|
||||
var panicOnErrorOnReadingPixels bool
|
||||
|
||||
func SetPanicOnErrorOnReadingPixelsForTesting(value bool) {
|
||||
panicOnErrorOnReadingPixels = value
|
||||
}
|
||||
|
||||
const bigOffscreenScale = 2
|
||||
|
||||
type Image struct {
|
||||
ui *UserInterface
|
||||
|
||||
mipmap *mipmap.Mipmap
|
||||
width int
|
||||
height int
|
||||
imageType atlas.ImageType
|
||||
|
||||
// lastBlend is the lastly-used blend for mipmap.Image.
|
||||
lastBlend graphicsdriver.Blend
|
||||
|
||||
// bigOffscreenBuffer is a double-sized offscreen for anti-alias rendering.
|
||||
bigOffscreenBuffer *bigOffscreenImage
|
||||
|
||||
// modifyCallback is a callback called when DrawTriangles or WritePixels is called.
|
||||
// modifyCallback is useful to detect whether the image is manipulated or not after a certain time.
|
||||
modifyCallback func()
|
||||
|
||||
tmpVerticesForFill []float32
|
||||
}
|
||||
|
||||
func (u *UserInterface) NewImage(width, height int, imageType atlas.ImageType) *Image {
|
||||
return &Image{
|
||||
ui: u,
|
||||
mipmap: mipmap.New(width, height, imageType),
|
||||
width: width,
|
||||
height: height,
|
||||
imageType: imageType,
|
||||
lastBlend: graphicsdriver.BlendSourceOver,
|
||||
}
|
||||
}
|
||||
|
||||
func (i *Image) Deallocate() {
|
||||
if i.mipmap == nil {
|
||||
return
|
||||
}
|
||||
if i.bigOffscreenBuffer != nil {
|
||||
i.bigOffscreenBuffer.deallocate()
|
||||
}
|
||||
i.mipmap.Deallocate()
|
||||
}
|
||||
|
||||
func (i *Image) DrawTriangles(srcs [graphics.ShaderImageCount]*Image, vertices []float32, indices []uint32, blend graphicsdriver.Blend, dstRegion image.Rectangle, srcRegions [graphics.ShaderImageCount]image.Rectangle, shader *Shader, uniforms []uint32, fillRule graphicsdriver.FillRule, canSkipMipmap bool, antialias bool) {
|
||||
if i.modifyCallback != nil {
|
||||
i.modifyCallback()
|
||||
}
|
||||
|
||||
i.lastBlend = blend
|
||||
|
||||
if antialias {
|
||||
if i.bigOffscreenBuffer == nil {
|
||||
var imageType atlas.ImageType
|
||||
switch i.imageType {
|
||||
case atlas.ImageTypeRegular, atlas.ImageTypeUnmanaged:
|
||||
imageType = atlas.ImageTypeUnmanaged
|
||||
case atlas.ImageTypeScreen, atlas.ImageTypeVolatile:
|
||||
imageType = atlas.ImageTypeVolatile
|
||||
default:
|
||||
panic(fmt.Sprintf("ui: unexpected image type: %d", imageType))
|
||||
}
|
||||
i.bigOffscreenBuffer = i.ui.newBigOffscreenImage(i, imageType)
|
||||
}
|
||||
|
||||
i.bigOffscreenBuffer.drawTriangles(srcs, vertices, indices, blend, dstRegion, srcRegions, shader, uniforms, fillRule, canSkipMipmap, false)
|
||||
return
|
||||
}
|
||||
|
||||
i.flushBufferIfNeeded()
|
||||
|
||||
var srcMipmaps [graphics.ShaderImageCount]*mipmap.Mipmap
|
||||
for i, src := range srcs {
|
||||
if src == nil {
|
||||
continue
|
||||
}
|
||||
src.flushBufferIfNeeded()
|
||||
srcMipmaps[i] = src.mipmap
|
||||
}
|
||||
|
||||
i.mipmap.DrawTriangles(srcMipmaps, vertices, indices, blend, dstRegion, srcRegions, shader.shader, uniforms, fillRule, canSkipMipmap)
|
||||
}
|
||||
|
||||
func (i *Image) WritePixels(pix []byte, region image.Rectangle) {
|
||||
if i.modifyCallback != nil {
|
||||
i.modifyCallback()
|
||||
}
|
||||
i.flushBufferIfNeeded()
|
||||
i.mipmap.WritePixels(pix, region)
|
||||
}
|
||||
|
||||
func (i *Image) ReadPixels(pixels []byte, region image.Rectangle) {
|
||||
// Check the error existence and avoid unnecessary calls.
|
||||
if i.ui.error() != nil {
|
||||
return
|
||||
}
|
||||
|
||||
i.flushBigOffscreenBufferIfNeeded()
|
||||
|
||||
if err := i.ui.readPixels(i.mipmap, pixels, region); err != nil {
|
||||
if panicOnErrorOnReadingPixels {
|
||||
panic(err)
|
||||
}
|
||||
i.ui.setError(err)
|
||||
}
|
||||
}
|
||||
|
||||
func (i *Image) DumpScreenshot(name string, blackbg bool) (string, error) {
|
||||
i.flushBufferIfNeeded()
|
||||
return i.ui.dumpScreenshot(i.mipmap, name, blackbg)
|
||||
}
|
||||
|
||||
func (i *Image) flushBufferIfNeeded() {
|
||||
i.flushBigOffscreenBufferIfNeeded()
|
||||
}
|
||||
|
||||
func (i *Image) flushBigOffscreenBufferIfNeeded() {
|
||||
if i.bigOffscreenBuffer != nil {
|
||||
i.bigOffscreenBuffer.flush()
|
||||
}
|
||||
}
|
||||
|
||||
func (u *UserInterface) DumpImages(dir string) (string, error) {
|
||||
return u.dumpImages(dir)
|
||||
}
|
||||
|
||||
func (i *Image) clear() {
|
||||
i.Fill(0, 0, 0, 0, image.Rect(0, 0, i.width, i.height))
|
||||
}
|
||||
|
||||
func (i *Image) Fill(r, g, b, a float32, region image.Rectangle) {
|
||||
if len(i.tmpVerticesForFill) < 4*graphics.VertexFloatCount {
|
||||
i.tmpVerticesForFill = make([]float32, 4*graphics.VertexFloatCount)
|
||||
}
|
||||
// i.tmpVerticesForFill can be reused as this is sent to DrawTriangles immediately.
|
||||
graphics.QuadVertices(
|
||||
i.tmpVerticesForFill,
|
||||
1, 1, float32(i.ui.whiteImage.width-1), float32(i.ui.whiteImage.height-1),
|
||||
float32(i.width), 0, 0, float32(i.height), 0, 0,
|
||||
r, g, b, a)
|
||||
is := graphics.QuadIndices()
|
||||
|
||||
srcs := [graphics.ShaderImageCount]*Image{i.ui.whiteImage}
|
||||
|
||||
blend := graphicsdriver.BlendCopy
|
||||
// If possible, use BlendSourceOver to encourage batching (#2817).
|
||||
if a == 1 && i.lastBlend == graphicsdriver.BlendSourceOver {
|
||||
blend = graphicsdriver.BlendSourceOver
|
||||
}
|
||||
// i.lastBlend is updated in DrawTriangles.
|
||||
i.DrawTriangles(srcs, i.tmpVerticesForFill, is, blend, region, [graphics.ShaderImageCount]image.Rectangle{}, NearestFilterShader, nil, graphicsdriver.FillAll, true, false)
|
||||
}
|
||||
|
||||
type bigOffscreenImage struct {
|
||||
ui *UserInterface
|
||||
|
||||
orig *Image
|
||||
imageType atlas.ImageType
|
||||
|
||||
image *Image
|
||||
region image.Rectangle
|
||||
|
||||
blend graphicsdriver.Blend
|
||||
dirty bool
|
||||
|
||||
tmpVerticesForFlushing []float32
|
||||
tmpVerticesForCopying []float32
|
||||
}
|
||||
|
||||
func (u *UserInterface) newBigOffscreenImage(orig *Image, imageType atlas.ImageType) *bigOffscreenImage {
|
||||
return &bigOffscreenImage{
|
||||
ui: u,
|
||||
orig: orig,
|
||||
imageType: imageType,
|
||||
}
|
||||
}
|
||||
|
||||
func (i *bigOffscreenImage) deallocate() {
|
||||
if i.image != nil {
|
||||
i.image.Deallocate()
|
||||
}
|
||||
i.dirty = false
|
||||
}
|
||||
|
||||
func (i *bigOffscreenImage) drawTriangles(srcs [graphics.ShaderImageCount]*Image, vertices []float32, indices []uint32, blend graphicsdriver.Blend, dstRegion image.Rectangle, srcRegions [graphics.ShaderImageCount]image.Rectangle, shader *Shader, uniforms []uint32, fillRule graphicsdriver.FillRule, canSkipMipmap bool, antialias bool) {
|
||||
if i.blend != blend {
|
||||
i.flush()
|
||||
}
|
||||
i.blend = blend
|
||||
|
||||
// If the new region doesn't match with the current region, remove the buffer image and recreate it later.
|
||||
if r := i.requiredRegion(vertices); i.region != r {
|
||||
i.flush()
|
||||
i.image = nil
|
||||
i.region = r
|
||||
}
|
||||
|
||||
if i.region.Empty() {
|
||||
return
|
||||
}
|
||||
|
||||
if i.image == nil {
|
||||
i.image = i.ui.NewImage(i.region.Dx()*bigOffscreenScale, i.region.Dy()*bigOffscreenScale, i.imageType)
|
||||
}
|
||||
|
||||
// Copy the current rendering result to get the correct blending result.
|
||||
if blend != graphicsdriver.BlendSourceOver && !i.dirty {
|
||||
srcs := [graphics.ShaderImageCount]*Image{i.orig}
|
||||
if len(i.tmpVerticesForCopying) < 4*graphics.VertexFloatCount {
|
||||
i.tmpVerticesForCopying = make([]float32, 4*graphics.VertexFloatCount)
|
||||
}
|
||||
// i.tmpVerticesForCopying can be reused as this is sent to DrawTriangles immediately.
|
||||
graphics.QuadVertices(
|
||||
i.tmpVerticesForCopying,
|
||||
float32(i.region.Min.X), float32(i.region.Min.Y), float32(i.region.Max.X), float32(i.region.Max.Y),
|
||||
bigOffscreenScale, 0, 0, bigOffscreenScale, 0, 0,
|
||||
1, 1, 1, 1)
|
||||
is := graphics.QuadIndices()
|
||||
dstRegion := image.Rect(0, 0, i.region.Dx()*bigOffscreenScale, i.region.Dy()*bigOffscreenScale)
|
||||
i.image.DrawTriangles(srcs, i.tmpVerticesForCopying, is, graphicsdriver.BlendCopy, dstRegion, [graphics.ShaderImageCount]image.Rectangle{}, NearestFilterShader, nil, graphicsdriver.FillAll, true, false)
|
||||
}
|
||||
|
||||
for idx := 0; idx < len(vertices); idx += graphics.VertexFloatCount {
|
||||
vertices[idx] = (vertices[idx] - float32(i.region.Min.X)) * bigOffscreenScale
|
||||
vertices[idx+1] = (vertices[idx+1] - float32(i.region.Min.Y)) * bigOffscreenScale
|
||||
}
|
||||
|
||||
// Translate to i.region coordinate space, and clamp against region size.
|
||||
dstRegion = dstRegion.Sub(i.region.Min)
|
||||
dstRegion = dstRegion.Intersect(image.Rect(0, 0, i.region.Dx(), i.region.Dy()))
|
||||
dstRegion.Min.X *= bigOffscreenScale
|
||||
dstRegion.Min.Y *= bigOffscreenScale
|
||||
dstRegion.Max.X *= bigOffscreenScale
|
||||
dstRegion.Max.Y *= bigOffscreenScale
|
||||
|
||||
i.image.DrawTriangles(srcs, vertices, indices, blend, dstRegion, srcRegions, shader, uniforms, fillRule, canSkipMipmap, false)
|
||||
i.dirty = true
|
||||
}
|
||||
|
||||
func (i *bigOffscreenImage) flush() {
|
||||
if i.image == nil {
|
||||
return
|
||||
}
|
||||
|
||||
if !i.dirty {
|
||||
return
|
||||
}
|
||||
|
||||
// Mark the offscreen clean earlier to avoid recursive calls.
|
||||
i.dirty = false
|
||||
|
||||
srcs := [graphics.ShaderImageCount]*Image{i.image}
|
||||
if len(i.tmpVerticesForFlushing) < 4*graphics.VertexFloatCount {
|
||||
i.tmpVerticesForFlushing = make([]float32, 4*graphics.VertexFloatCount)
|
||||
}
|
||||
// i.tmpVerticesForFlushing can be reused as this is sent to DrawTriangles in this function.
|
||||
graphics.QuadVertices(
|
||||
i.tmpVerticesForFlushing,
|
||||
0, 0, float32(i.region.Dx()*bigOffscreenScale), float32(i.region.Dy()*bigOffscreenScale),
|
||||
1.0/bigOffscreenScale, 0, 0, 1.0/bigOffscreenScale, float32(i.region.Min.X), float32(i.region.Min.Y),
|
||||
1, 1, 1, 1)
|
||||
is := graphics.QuadIndices()
|
||||
dstRegion := i.region
|
||||
blend := graphicsdriver.BlendSourceOver
|
||||
if i.blend != graphicsdriver.BlendSourceOver {
|
||||
blend = graphicsdriver.BlendCopy
|
||||
}
|
||||
i.orig.DrawTriangles(srcs, i.tmpVerticesForFlushing, is, blend, dstRegion, [graphics.ShaderImageCount]image.Rectangle{}, LinearFilterShader, nil, graphicsdriver.FillAll, true, false)
|
||||
|
||||
i.image.clear()
|
||||
i.dirty = false
|
||||
}
|
||||
|
||||
func (i *bigOffscreenImage) requiredRegion(vertices []float32) image.Rectangle {
|
||||
minX := float32(i.orig.width)
|
||||
minY := float32(i.orig.height)
|
||||
maxX := float32(0)
|
||||
maxY := float32(0)
|
||||
for i := 0; i < len(vertices); i += graphics.VertexFloatCount {
|
||||
dstX := vertices[i]
|
||||
dstY := vertices[i+1]
|
||||
if minX > floor(dstX)-1 {
|
||||
minX = floor(dstX) - 1
|
||||
}
|
||||
if minY > floor(dstY)-1 {
|
||||
minY = floor(dstY) - 1
|
||||
}
|
||||
if maxX < ceil(dstX)+1 {
|
||||
maxX = ceil(dstX) + 1
|
||||
}
|
||||
if maxY < ceil(dstY)+1 {
|
||||
maxY = ceil(dstY) + 1
|
||||
}
|
||||
}
|
||||
|
||||
// Adjust the granularity of the rectangle.
|
||||
r := image.Rect(
|
||||
roundDown16(int(minX)),
|
||||
roundDown16(int(minY)),
|
||||
roundUp16(int(maxX)),
|
||||
roundUp16(int(maxY)))
|
||||
r = r.Intersect(image.Rect(0, 0, i.orig.width, i.orig.height))
|
||||
|
||||
// TODO: Is this check required?
|
||||
if r.Dx() < 0 || r.Dy() < 0 {
|
||||
return i.region
|
||||
}
|
||||
|
||||
return r.Union(i.region)
|
||||
}
|
||||
|
||||
func floor(x float32) float32 {
|
||||
return float32(math.Floor(float64(x)))
|
||||
}
|
||||
|
||||
func ceil(x float32) float32 {
|
||||
return float32(math.Ceil(float64(x)))
|
||||
}
|
||||
|
||||
func roundDown16(x int) int {
|
||||
return x & ^(0xf)
|
||||
}
|
||||
|
||||
func roundUp16(x int) int {
|
||||
return ((x - 1) & ^(0xf)) + 0x10
|
||||
}
|
||||
+25
@@ -0,0 +1,25 @@
|
||||
// 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.
|
||||
|
||||
//go:build nintendosdk
|
||||
|
||||
// The actual implementation will be provided by -overlay.
|
||||
|
||||
#include "init_nintendosdk.h"
|
||||
|
||||
extern "C" NativeWindowType ebitengine_Initialize() { return 0; }
|
||||
|
||||
extern "C" void ebitengine_InitializeProfiler() {}
|
||||
|
||||
extern "C" void ebitengine_RecordProfilerHeartbeat() {}
|
||||
+29
@@ -0,0 +1,29 @@
|
||||
// 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.
|
||||
|
||||
//go:build nintendosdk
|
||||
|
||||
#include <EGL/egl.h>
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
NativeWindowType ebitengine_Initialize();
|
||||
void ebitengine_InitializeProfiler();
|
||||
void ebitengine_RecordProfilerHeartbeat();
|
||||
|
||||
#ifdef __cplusplus
|
||||
} // extern "C"
|
||||
#endif
|
||||
+81
@@ -0,0 +1,81 @@
|
||||
// Copyright 2022 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
|
||||
|
||||
import (
|
||||
"io/fs"
|
||||
"unicode"
|
||||
)
|
||||
|
||||
type MouseButton int
|
||||
|
||||
const (
|
||||
MouseButton0 MouseButton = iota // The 'left' button
|
||||
MouseButton1 // The 'right' button
|
||||
MouseButton2 // The 'middle' button
|
||||
MouseButton3 // The additional button (usually browser-back)
|
||||
MouseButton4 // The additional button (usually browser-forward)
|
||||
MouseButtonMax = MouseButton4
|
||||
)
|
||||
|
||||
type TouchID int
|
||||
|
||||
type Touch struct {
|
||||
ID TouchID
|
||||
X int
|
||||
Y int
|
||||
}
|
||||
|
||||
type InputState struct {
|
||||
KeyPressed [KeyMax + 1]bool
|
||||
MouseButtonPressed [MouseButtonMax + 1]bool
|
||||
CursorX float64
|
||||
CursorY float64
|
||||
WheelX float64
|
||||
WheelY float64
|
||||
Touches []Touch
|
||||
Runes []rune
|
||||
WindowBeingClosed bool
|
||||
DroppedFiles fs.FS
|
||||
}
|
||||
|
||||
func (i *InputState) copyAndReset(dst *InputState) {
|
||||
dst.KeyPressed = i.KeyPressed
|
||||
dst.MouseButtonPressed = i.MouseButtonPressed
|
||||
dst.CursorX = i.CursorX
|
||||
dst.CursorY = i.CursorY
|
||||
dst.WheelX = i.WheelX
|
||||
dst.WheelY = i.WheelY
|
||||
dst.Touches = append(dst.Touches[:0], i.Touches...)
|
||||
dst.Runes = append(dst.Runes[:0], i.Runes...)
|
||||
dst.WindowBeingClosed = i.WindowBeingClosed
|
||||
dst.DroppedFiles = i.DroppedFiles
|
||||
|
||||
// Reset the members that are updated by deltas, rather than absolute values.
|
||||
i.WheelX = 0
|
||||
i.WheelY = 0
|
||||
i.Runes = i.Runes[:0]
|
||||
|
||||
// Reset the members that are never reset until they are explicitly done.
|
||||
i.WindowBeingClosed = false
|
||||
i.DroppedFiles = nil
|
||||
}
|
||||
|
||||
func (i *InputState) appendRune(r rune) {
|
||||
if !unicode.IsPrint(r) {
|
||||
return
|
||||
}
|
||||
i.Runes = append(i.Runes, r)
|
||||
}
|
||||
+156
@@ -0,0 +1,156 @@
|
||||
// Copyright 2015 Hajime Hoshi
|
||||
//
|
||||
// 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 !android && !ios && !js && !nintendosdk && !playstation5
|
||||
|
||||
package ui
|
||||
|
||||
import (
|
||||
"math"
|
||||
|
||||
"github.com/hajimehoshi/ebiten/v2/internal/gamepad"
|
||||
"github.com/hajimehoshi/ebiten/v2/internal/glfw"
|
||||
)
|
||||
|
||||
var glfwMouseButtonToMouseButton = map[glfw.MouseButton]MouseButton{
|
||||
glfw.MouseButtonLeft: MouseButton0,
|
||||
glfw.MouseButtonMiddle: MouseButton1,
|
||||
glfw.MouseButtonRight: MouseButton2,
|
||||
glfw.MouseButton4: MouseButton3,
|
||||
glfw.MouseButton5: MouseButton4,
|
||||
}
|
||||
|
||||
func (u *UserInterface) registerInputCallbacks() error {
|
||||
if _, err := u.window.SetCharModsCallback(func(w *glfw.Window, char rune, mods glfw.ModifierKey) {
|
||||
// As this function is called from GLFW callbacks, the current thread is main.
|
||||
u.m.Lock()
|
||||
defer u.m.Unlock()
|
||||
u.inputState.appendRune(char)
|
||||
}); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if _, err := u.window.SetScrollCallback(func(w *glfw.Window, xoff float64, yoff float64) {
|
||||
// As this function is called from GLFW callbacks, the current thread is main.
|
||||
u.m.Lock()
|
||||
defer u.m.Unlock()
|
||||
u.inputState.WheelX += xoff
|
||||
u.inputState.WheelY += yoff
|
||||
}); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (u *UserInterface) updateInputState() error {
|
||||
var err error
|
||||
u.mainThread.Call(func() {
|
||||
err = u.updateInputStateImpl()
|
||||
})
|
||||
return err
|
||||
}
|
||||
|
||||
// updateInputStateImpl must be called from the main thread.
|
||||
func (u *UserInterface) updateInputStateImpl() error {
|
||||
u.m.Lock()
|
||||
defer u.m.Unlock()
|
||||
|
||||
for uk, gk := range uiKeyToGLFWKey {
|
||||
s, err := u.window.GetKey(gk)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
u.inputState.KeyPressed[uk] = s == glfw.Press
|
||||
}
|
||||
for gb, ub := range glfwMouseButtonToMouseButton {
|
||||
s, err := u.window.GetMouseButton(gb)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
u.inputState.MouseButtonPressed[ub] = s == glfw.Press
|
||||
}
|
||||
|
||||
m, err := u.currentMonitor()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
s := m.DeviceScaleFactor()
|
||||
|
||||
cx, cy := u.savedCursorX, u.savedCursorY
|
||||
defer func() {
|
||||
u.savedCursorX = math.NaN()
|
||||
u.savedCursorY = math.NaN()
|
||||
}()
|
||||
|
||||
if !math.IsNaN(cx) && !math.IsNaN(cy) {
|
||||
cx2, cy2 := u.context.logicalPositionToClientPosition(cx, cy, s)
|
||||
cx2 = dipToGLFWPixel(cx2, s)
|
||||
cy2 = dipToGLFWPixel(cy2, s)
|
||||
if err := u.window.SetCursorPos(cx2, cy2); err != nil {
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
cx2, cy2, err := u.window.GetCursorPos()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
cx2 = dipFromGLFWPixel(cx2, s)
|
||||
cy2 = dipFromGLFWPixel(cy2, s)
|
||||
cx, cy = u.context.clientPositionToLogicalPosition(cx2, cy2, s)
|
||||
}
|
||||
|
||||
// AdjustPosition can return NaN at the initialization.
|
||||
if !math.IsNaN(cx) && !math.IsNaN(cy) {
|
||||
u.inputState.CursorX, u.inputState.CursorY = cx, cy
|
||||
}
|
||||
|
||||
if err := gamepad.Update(); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (u *UserInterface) KeyName(key Key) string {
|
||||
if !u.isRunning() {
|
||||
return ""
|
||||
}
|
||||
|
||||
gk, ok := uiKeyToGLFWKey[key]
|
||||
if !ok {
|
||||
return ""
|
||||
}
|
||||
|
||||
var name string
|
||||
u.mainThread.Call(func() {
|
||||
if u.isTerminated() {
|
||||
return
|
||||
}
|
||||
n, err := glfw.GetKeyName(gk, 0)
|
||||
if err != nil {
|
||||
u.setError(err)
|
||||
return
|
||||
}
|
||||
name = n
|
||||
})
|
||||
return name
|
||||
}
|
||||
|
||||
func (u *UserInterface) saveCursorPosition() {
|
||||
u.m.Lock()
|
||||
defer u.m.Unlock()
|
||||
|
||||
u.savedCursorX = u.inputState.CursorX
|
||||
u.savedCursorY = u.inputState.CursorY
|
||||
}
|
||||
+428
@@ -0,0 +1,428 @@
|
||||
// Copyright 2015 Hajime Hoshi
|
||||
//
|
||||
// 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
|
||||
|
||||
import (
|
||||
"math"
|
||||
"syscall/js"
|
||||
"unicode"
|
||||
)
|
||||
|
||||
var (
|
||||
stringAlt = js.ValueOf("Alt")
|
||||
stringControl = js.ValueOf("Control")
|
||||
stringMeta = js.ValueOf("Meta")
|
||||
stringShift = js.ValueOf("Shift")
|
||||
|
||||
stringKeydown = js.ValueOf("keydown")
|
||||
stringKeyup = js.ValueOf("keyup")
|
||||
stringMousedown = js.ValueOf("mousedown")
|
||||
stringMouseup = js.ValueOf("mouseup")
|
||||
stringMousemove = js.ValueOf("mousemove")
|
||||
stringWheel = js.ValueOf("wheel")
|
||||
stringTouchstart = js.ValueOf("touchstart")
|
||||
stringTouchend = js.ValueOf("touchend")
|
||||
stringTouchmove = js.ValueOf("touchmove")
|
||||
)
|
||||
|
||||
type touchInClient struct {
|
||||
id TouchID
|
||||
x float64
|
||||
y float64
|
||||
}
|
||||
|
||||
func jsCodeToID(code js.Value) Key {
|
||||
// js.Value cannot be used as a map key.
|
||||
// As the number of keys is around 100, just a dumb loop should work.
|
||||
for uiKey, jsCode := range uiKeyToJSCode {
|
||||
if jsCode.Equal(code) {
|
||||
return uiKey
|
||||
}
|
||||
}
|
||||
return -1
|
||||
}
|
||||
|
||||
var codeToMouseButton = map[int]MouseButton{
|
||||
0: MouseButton0, // Left
|
||||
1: MouseButton1, // Middle
|
||||
2: MouseButton2, // Right
|
||||
3: MouseButton3,
|
||||
4: MouseButton4,
|
||||
}
|
||||
|
||||
func eventToKeys(e js.Value) (key0, key1 Key, fromKeyProperty bool) {
|
||||
id := jsCodeToID(e.Get("code"))
|
||||
if id >= 0 {
|
||||
return id, -1, false
|
||||
}
|
||||
|
||||
// With a virtual keyboard on mobile devices, e.code is empty. Use a 'key' property instead (#2898).
|
||||
key := e.Get("key")
|
||||
|
||||
// The key property doesn't distinghlish between left and right modifier keys.
|
||||
// Let's assume both keys are pressed.
|
||||
switch {
|
||||
case key.Equal(stringAlt):
|
||||
return KeyAltLeft, KeyAltRight, true
|
||||
case key.Equal(stringControl):
|
||||
return KeyControlLeft, KeyControlRight, true
|
||||
case key.Equal(stringMeta):
|
||||
return KeyMetaLeft, KeyMetaRight, true
|
||||
case key.Equal(stringShift):
|
||||
return KeyShiftLeft, KeyShiftRight, true
|
||||
}
|
||||
|
||||
for uiKey, jsKey := range uiKeyToJSKey {
|
||||
if key.Equal(jsKey) {
|
||||
return uiKey, -1, true
|
||||
}
|
||||
}
|
||||
|
||||
return -1, -1, false
|
||||
}
|
||||
|
||||
func (u *UserInterface) keyDown(event js.Value) {
|
||||
key0, key1, fromKeyProperty := eventToKeys(event)
|
||||
if key0 >= 0 {
|
||||
// If the key value comes from a 'key' property, a 'keydown' and 'keyup' event might be fired too quickly.
|
||||
// Record the key duration to prevent immediate resetting a key state by a 'keyup' event.
|
||||
// Resetting a key state is delayed until the next tick. See updateInputState.
|
||||
if fromKeyProperty && !u.inputState.KeyPressed[key0] {
|
||||
if u.keyDurationsByKeyProperty == nil {
|
||||
u.keyDurationsByKeyProperty = map[Key]int{}
|
||||
}
|
||||
u.keyDurationsByKeyProperty[key0] = 1
|
||||
}
|
||||
u.inputState.KeyPressed[key0] = true
|
||||
}
|
||||
if key1 >= 0 {
|
||||
if fromKeyProperty && !u.inputState.KeyPressed[key1] {
|
||||
if u.keyDurationsByKeyProperty == nil {
|
||||
u.keyDurationsByKeyProperty = map[Key]int{}
|
||||
}
|
||||
u.keyDurationsByKeyProperty[key1] = 1
|
||||
}
|
||||
u.inputState.KeyPressed[key1] = true
|
||||
}
|
||||
}
|
||||
|
||||
func (u *UserInterface) keyUp(event js.Value) {
|
||||
key0, key1, fromKeyProperty := eventToKeys(event)
|
||||
if key0 >= 0 {
|
||||
if !fromKeyProperty || u.keyDurationsByKeyProperty[key0] == 0 {
|
||||
u.inputState.KeyPressed[key0] = false
|
||||
}
|
||||
}
|
||||
if key1 >= 0 {
|
||||
if !fromKeyProperty || u.keyDurationsByKeyProperty[key1] == 0 {
|
||||
u.inputState.KeyPressed[key1] = false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (u *UserInterface) mouseDown(code int) {
|
||||
u.inputState.MouseButtonPressed[codeToMouseButton[code]] = true
|
||||
}
|
||||
|
||||
func (u *UserInterface) mouseUp(code int) {
|
||||
u.inputState.MouseButtonPressed[codeToMouseButton[code]] = false
|
||||
}
|
||||
|
||||
func (u *UserInterface) updateInputFromEvent(e js.Value) error {
|
||||
// Avoid using js.Value.String() as String creates a Uint8Array via a TextEncoder and causes a heavy
|
||||
// overhead (#1437).
|
||||
switch t := e.Get("type"); {
|
||||
case t.Equal(stringKeydown):
|
||||
if str := e.Get("key").String(); isKeyString(str) {
|
||||
for _, r := range str {
|
||||
u.inputState.appendRune(r)
|
||||
}
|
||||
}
|
||||
u.keyDown(e)
|
||||
case t.Equal(stringKeyup):
|
||||
u.keyUp(e)
|
||||
case t.Equal(stringMousedown):
|
||||
u.mouseDown(e.Get("button").Int())
|
||||
u.setMouseCursorFromEvent(e)
|
||||
case t.Equal(stringMouseup):
|
||||
u.mouseUp(e.Get("button").Int())
|
||||
u.setMouseCursorFromEvent(e)
|
||||
case t.Equal(stringMousemove):
|
||||
u.setMouseCursorFromEvent(e)
|
||||
case t.Equal(stringWheel):
|
||||
// TODO: What if e.deltaMode is not DOM_DELTA_PIXEL?
|
||||
u.inputState.WheelX = -e.Get("deltaX").Float()
|
||||
u.inputState.WheelY = -e.Get("deltaY").Float()
|
||||
case t.Equal(stringTouchstart) || t.Equal(stringTouchend) || t.Equal(stringTouchmove):
|
||||
u.updateTouchesFromEvent(e)
|
||||
}
|
||||
|
||||
u.forceUpdateOnMinimumFPSMode()
|
||||
return nil
|
||||
}
|
||||
|
||||
func (u *UserInterface) setMouseCursorFromEvent(e js.Value) {
|
||||
if u.context == nil {
|
||||
return
|
||||
}
|
||||
|
||||
u.origCursorXInClient = e.Get("clientX").Float()
|
||||
u.origCursorYInClient = e.Get("clientY").Float()
|
||||
|
||||
if u.cursorMode == CursorModeCaptured {
|
||||
u.cursorXInClient += e.Get("movementX").Float()
|
||||
u.cursorYInClient += e.Get("movementY").Float()
|
||||
return
|
||||
}
|
||||
|
||||
u.cursorXInClient = u.origCursorXInClient
|
||||
u.cursorYInClient = u.origCursorYInClient
|
||||
}
|
||||
|
||||
func (u *UserInterface) recoverCursorPosition() {
|
||||
u.cursorXInClient = u.origCursorXInClient
|
||||
u.cursorYInClient = u.origCursorYInClient
|
||||
}
|
||||
|
||||
func (u *UserInterface) updateTouchesFromEvent(e js.Value) {
|
||||
u.touchesInClient = u.touchesInClient[:0]
|
||||
|
||||
touches := e.Get("targetTouches")
|
||||
for i := 0; i < touches.Length(); i++ {
|
||||
t := touches.Call("item", i)
|
||||
u.touchesInClient = append(u.touchesInClient, touchInClient{
|
||||
id: TouchID(t.Get("identifier").Int()),
|
||||
x: t.Get("clientX").Float(),
|
||||
y: t.Get("clientY").Float(),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func isKeyString(str string) bool {
|
||||
// From https://www.w3.org/TR/uievents-key/#keys-unicode,
|
||||
//
|
||||
// A key string is a string containing a 0 or 1 non-control characters
|
||||
// ("base" characters) followed by 0 or more combining characters. The
|
||||
// string MUST be in Normalized Form C (NFC) as described in
|
||||
// [UnicodeNormalizationForms].
|
||||
//
|
||||
// A non-control character is any valid Unicode character except those
|
||||
// that are part of the "Other, Control" ("Cc") General Category.
|
||||
//
|
||||
// A combining character is any valid Unicode character in the "Mark,
|
||||
// Spacing Combining" ("Mc") General Category or with a non-zero
|
||||
// Combining Class.
|
||||
for i, r := range str {
|
||||
if i == 0 {
|
||||
if unicode.Is(unicode.Cc, r) {
|
||||
return false
|
||||
}
|
||||
continue
|
||||
}
|
||||
if !unicode.Is(unicode.Mc, r) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
var (
|
||||
jsKeyboard = js.Global().Get("navigator").Get("keyboard")
|
||||
jsKeyboardGetLayoutMap js.Value
|
||||
jsKeyboardGetLayoutMapCh chan js.Value
|
||||
jsKeyboardGetLayoutMapCallback js.Func
|
||||
)
|
||||
|
||||
func init() {
|
||||
if !jsKeyboard.Truthy() {
|
||||
return
|
||||
}
|
||||
|
||||
jsKeyboardGetLayoutMap = jsKeyboard.Get("getLayoutMap").Call("bind", jsKeyboard)
|
||||
jsKeyboardGetLayoutMapCh = make(chan js.Value, 1)
|
||||
jsKeyboardGetLayoutMapCallback = js.FuncOf(func(this js.Value, args []js.Value) any {
|
||||
jsKeyboardGetLayoutMapCh <- args[0]
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
func (u *UserInterface) KeyName(key Key) string {
|
||||
if !u.isRunning() {
|
||||
return ""
|
||||
}
|
||||
|
||||
// keyboardLayoutMap is reset every tick.
|
||||
if u.keyboardLayoutMap.IsUndefined() {
|
||||
if !jsKeyboard.Truthy() {
|
||||
return ""
|
||||
}
|
||||
|
||||
// Invoke getLayoutMap every tick to detect the keyboard change.
|
||||
// TODO: Calling this every tick might be inefficient. Is there a way to detect a keyboard change?
|
||||
jsKeyboardGetLayoutMap.Invoke().Call("then", jsKeyboardGetLayoutMapCallback)
|
||||
u.keyboardLayoutMap = <-jsKeyboardGetLayoutMapCh
|
||||
}
|
||||
|
||||
n := u.keyboardLayoutMap.Call("get", uiKeyToJSCode[key])
|
||||
if n.IsUndefined() {
|
||||
return ""
|
||||
}
|
||||
return n.String()
|
||||
}
|
||||
|
||||
func (u *UserInterface) UpdateInputFromEvent(e js.Value) {
|
||||
u.updateInputFromEvent(e)
|
||||
}
|
||||
|
||||
func (u *UserInterface) saveCursorPosition() {
|
||||
u.savedCursorX = u.inputState.CursorX
|
||||
u.savedCursorY = u.inputState.CursorY
|
||||
w, h := u.outsideSize()
|
||||
u.savedOutsideWidth = w
|
||||
u.savedOutsideHeight = h
|
||||
}
|
||||
|
||||
func (u *UserInterface) updateInputState() error {
|
||||
// Reset the key state if a key is pressed by a 'key' property and the key's duration is big enough.
|
||||
for key, duration := range u.keyDurationsByKeyProperty {
|
||||
if duration >= 2 {
|
||||
delete(u.keyDurationsByKeyProperty, key)
|
||||
u.inputState.KeyPressed[key] = false
|
||||
continue
|
||||
}
|
||||
u.keyDurationsByKeyProperty[key]++
|
||||
}
|
||||
|
||||
s := theMonitor.DeviceScaleFactor()
|
||||
|
||||
if !math.IsNaN(u.savedCursorX) && !math.IsNaN(u.savedCursorY) {
|
||||
// If savedCursorX and savedCursorY are valid values, the cursor is saved just before entering or exiting from fullscreen.
|
||||
// Even after entering or exiting from fullscreening, the outside (body) size is not updated for a while.
|
||||
// Wait for the outside size updated.
|
||||
if w, h := u.outsideSize(); u.savedOutsideWidth != w || u.savedOutsideHeight != h {
|
||||
u.inputState.CursorX = u.savedCursorX
|
||||
u.inputState.CursorY = u.savedCursorY
|
||||
cx, cy := u.context.logicalPositionToClientPosition(u.inputState.CursorX, u.inputState.CursorY, s)
|
||||
u.cursorXInClient = cx
|
||||
u.cursorYInClient = cy
|
||||
u.savedCursorX = math.NaN()
|
||||
u.savedCursorY = math.NaN()
|
||||
u.savedOutsideWidth = 0
|
||||
u.savedOutsideHeight = 0
|
||||
u.outsideSizeUnchangedCount = 0
|
||||
} else {
|
||||
u.outsideSizeUnchangedCount++
|
||||
|
||||
// If the outside size is not changed for a while, probably the screen size is not actually changed.
|
||||
// Reset the state.
|
||||
if u.outsideSizeUnchangedCount > 60 {
|
||||
u.savedCursorX = math.NaN()
|
||||
u.savedCursorY = math.NaN()
|
||||
u.savedOutsideWidth = 0
|
||||
u.savedOutsideHeight = 0
|
||||
u.outsideSizeUnchangedCount = 0
|
||||
}
|
||||
}
|
||||
} else {
|
||||
cx, cy := u.context.clientPositionToLogicalPosition(u.cursorXInClient, u.cursorYInClient, s)
|
||||
u.inputState.CursorX = cx
|
||||
u.inputState.CursorY = cy
|
||||
}
|
||||
|
||||
u.inputState.Touches = u.inputState.Touches[:0]
|
||||
for _, t := range u.touchesInClient {
|
||||
x, y := u.context.clientPositionToLogicalPosition(t.x, t.y, s)
|
||||
u.inputState.Touches = append(u.inputState.Touches, Touch{
|
||||
ID: t.id,
|
||||
X: int(x),
|
||||
Y: int(y),
|
||||
})
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// uiKeyToJSKey is a map from Key values to KeyboardEvent's key values.
|
||||
// Note that js.Value cannot be a map key.
|
||||
//
|
||||
// Reference: https://developer.mozilla.org/en-US/docs/Web/API/UI_Events/Keyboard_event_key_values
|
||||
var uiKeyToJSKey = map[Key]js.Value{
|
||||
KeyCapsLock: js.ValueOf("CapsLock"),
|
||||
KeyNumLock: js.ValueOf("NumLock"),
|
||||
KeyScrollLock: js.ValueOf("ScrollLock"),
|
||||
KeyEnter: js.ValueOf("Enter"),
|
||||
KeyTab: js.ValueOf("Tab"),
|
||||
KeySpace: js.ValueOf(" "),
|
||||
KeyArrowDown: js.ValueOf("ArrowDown"),
|
||||
KeyArrowLeft: js.ValueOf("ArrowLeft"),
|
||||
KeyArrowRight: js.ValueOf("ArrowRight"),
|
||||
KeyArrowUp: js.ValueOf("ArrowUp"),
|
||||
KeyEnd: js.ValueOf("End"),
|
||||
KeyHome: js.ValueOf("Home"),
|
||||
KeyPageDown: js.ValueOf("PageDown"),
|
||||
KeyPageUp: js.ValueOf("PageUp"),
|
||||
KeyBackspace: js.ValueOf("Backspace"),
|
||||
KeyDelete: js.ValueOf("Delete"),
|
||||
KeyInsert: js.ValueOf("Insert"),
|
||||
KeyContextMenu: js.ValueOf("ContextMenu"),
|
||||
KeyEscape: js.ValueOf("Escape"),
|
||||
KeyPause: js.ValueOf("Pause"),
|
||||
KeyPrintScreen: js.ValueOf("PrintScreen"),
|
||||
KeyF1: js.ValueOf("F1"),
|
||||
KeyF2: js.ValueOf("F2"),
|
||||
KeyF3: js.ValueOf("F3"),
|
||||
KeyF4: js.ValueOf("F4"),
|
||||
KeyF5: js.ValueOf("F5"),
|
||||
KeyF6: js.ValueOf("F6"),
|
||||
KeyF7: js.ValueOf("F7"),
|
||||
KeyF8: js.ValueOf("F8"),
|
||||
KeyF9: js.ValueOf("F9"),
|
||||
KeyF10: js.ValueOf("F10"),
|
||||
KeyF11: js.ValueOf("F11"),
|
||||
KeyF12: js.ValueOf("F12"),
|
||||
KeyF13: js.ValueOf("F13"),
|
||||
KeyF14: js.ValueOf("F14"),
|
||||
KeyF15: js.ValueOf("F15"),
|
||||
KeyF16: js.ValueOf("F16"),
|
||||
KeyF17: js.ValueOf("F17"),
|
||||
KeyF18: js.ValueOf("F18"),
|
||||
KeyF19: js.ValueOf("F19"),
|
||||
KeyF20: js.ValueOf("F20"),
|
||||
KeyNumpadDecimal: js.ValueOf("Decimal"),
|
||||
KeyNumpadMultiply: js.ValueOf("Multiply"),
|
||||
KeyNumpadAdd: js.ValueOf("Add"),
|
||||
KeyNumpadDivide: js.ValueOf("Divide"),
|
||||
KeyNumpadSubtract: js.ValueOf("Subtract"),
|
||||
KeyNumpad0: js.ValueOf("0"),
|
||||
KeyNumpad1: js.ValueOf("1"),
|
||||
KeyNumpad2: js.ValueOf("2"),
|
||||
KeyNumpad3: js.ValueOf("3"),
|
||||
KeyNumpad4: js.ValueOf("4"),
|
||||
KeyNumpad5: js.ValueOf("5"),
|
||||
KeyNumpad6: js.ValueOf("6"),
|
||||
KeyNumpad7: js.ValueOf("7"),
|
||||
KeyNumpad8: js.ValueOf("8"),
|
||||
KeyNumpad9: js.ValueOf("9"),
|
||||
}
|
||||
|
||||
func (i *InputState) resetForBlur() {
|
||||
for j := range i.KeyPressed {
|
||||
i.KeyPressed[j] = false
|
||||
}
|
||||
for j := range i.MouseButtonPressed {
|
||||
i.MouseButtonPressed[j] = false
|
||||
}
|
||||
i.Touches = i.Touches[:0]
|
||||
}
|
||||
+67
@@ -0,0 +1,67 @@
|
||||
// Copyright 2016 Hajime Hoshi
|
||||
//
|
||||
// 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 android || ios
|
||||
|
||||
package ui
|
||||
|
||||
type TouchForInput struct {
|
||||
ID TouchID
|
||||
|
||||
// X is in device-independent pixels.
|
||||
X float64
|
||||
|
||||
// Y is in device-independent pixels.
|
||||
Y float64
|
||||
}
|
||||
|
||||
func (u *UserInterface) updateInputStateFromOutside(keys map[Key]struct{}, runes []rune, touches []TouchForInput) {
|
||||
u.m.Lock()
|
||||
defer u.m.Unlock()
|
||||
|
||||
for k := range u.inputState.KeyPressed {
|
||||
_, ok := keys[Key(k)]
|
||||
u.inputState.KeyPressed[k] = ok
|
||||
}
|
||||
|
||||
u.inputState.Runes = append(u.inputState.Runes, runes...)
|
||||
|
||||
u.touches = u.touches[:0]
|
||||
for _, t := range touches {
|
||||
u.touches = append(u.touches, t)
|
||||
}
|
||||
}
|
||||
|
||||
func (u *UserInterface) updateInputState() error {
|
||||
u.m.Lock()
|
||||
defer u.m.Unlock()
|
||||
|
||||
s := theMonitor.DeviceScaleFactor()
|
||||
|
||||
u.inputState.Touches = u.inputState.Touches[:0]
|
||||
for _, t := range u.touches {
|
||||
x, y := u.context.clientPositionToLogicalPosition(t.X, t.Y, s)
|
||||
u.inputState.Touches = append(u.inputState.Touches, Touch{
|
||||
ID: t.ID,
|
||||
X: int(x),
|
||||
Y: int(y),
|
||||
})
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (u *UserInterface) KeyName(key Key) string {
|
||||
// TODO: Implement this.
|
||||
return ""
|
||||
}
|
||||
+25
@@ -0,0 +1,25 @@
|
||||
// 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.
|
||||
|
||||
//go:build nintendosdk
|
||||
|
||||
// The actual implementaiton will be provided by -overlay.
|
||||
|
||||
#include "input_nintendosdk.h"
|
||||
|
||||
extern "C" void ebitengine_UpdateTouches() {}
|
||||
|
||||
extern "C" int ebitengine_GetTouchCount() { return 0; }
|
||||
|
||||
extern "C" void ebitengine_GetTouches(struct Touch *touches) {}
|
||||
+76
@@ -0,0 +1,76 @@
|
||||
// Copyright 2021 The Ebiten 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 nintendosdk
|
||||
|
||||
package ui
|
||||
|
||||
// #cgo !darwin LDFLAGS: -Wl,-unresolved-symbols=ignore-all
|
||||
// #cgo darwin LDFLAGS: -Wl,-undefined,dynamic_lookup
|
||||
//
|
||||
// #include "input_nintendosdk.h"
|
||||
//
|
||||
// const int kScreenWidth = 1920;
|
||||
// const int kScreenHeight = 1080;
|
||||
import "C"
|
||||
|
||||
import (
|
||||
"github.com/hajimehoshi/ebiten/v2/internal/gamepad"
|
||||
)
|
||||
|
||||
func (u *UserInterface) updateInputState() error {
|
||||
var err error
|
||||
u.mainThread.Call(func() {
|
||||
err = u.updateInputStateImpl()
|
||||
})
|
||||
return err
|
||||
}
|
||||
|
||||
// updateInputStateImpl must be called from the main thread.
|
||||
func (u *UserInterface) updateInputStateImpl() error {
|
||||
if err := gamepad.Update(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
C.ebitengine_UpdateTouches()
|
||||
|
||||
u.nativeTouches = u.nativeTouches[:0]
|
||||
if n := int(C.ebitengine_GetTouchCount()); n > 0 {
|
||||
if cap(u.nativeTouches) < n {
|
||||
u.nativeTouches = make([]C.struct_Touch, n)
|
||||
} else {
|
||||
u.nativeTouches = u.nativeTouches[:n]
|
||||
}
|
||||
C.ebitengine_GetTouches(&u.nativeTouches[0])
|
||||
}
|
||||
|
||||
u.m.Lock()
|
||||
defer u.m.Unlock()
|
||||
|
||||
u.inputState.Touches = u.inputState.Touches[:0]
|
||||
for _, t := range u.nativeTouches {
|
||||
x, y := u.context.clientPositionToLogicalPosition(float64(t.x), float64(t.y), theMonitor.DeviceScaleFactor())
|
||||
u.inputState.Touches = append(u.inputState.Touches, Touch{
|
||||
ID: TouchID(t.id),
|
||||
X: int(x),
|
||||
Y: int(y),
|
||||
})
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (u *UserInterface) KeyName(key Key) string {
|
||||
return ""
|
||||
}
|
||||
+36
@@ -0,0 +1,36 @@
|
||||
// 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.
|
||||
|
||||
//go:build nintendosdk
|
||||
|
||||
struct Touch {
|
||||
int id;
|
||||
int x;
|
||||
int y;
|
||||
};
|
||||
|
||||
extern const int kScreenWidth;
|
||||
extern const int kScreenHeight;
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
void ebitengine_UpdateTouches();
|
||||
int ebitengine_GetTouchCount();
|
||||
void ebitengine_GetTouches(struct Touch* touches);
|
||||
|
||||
#ifdef __cplusplus
|
||||
} // extern "C"
|
||||
#endif
|
||||
+26
@@ -0,0 +1,26 @@
|
||||
// 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.
|
||||
|
||||
//go:build playstation5
|
||||
|
||||
package ui
|
||||
|
||||
func (u *UserInterface) updateInputState() error {
|
||||
// TODO: Implement this
|
||||
return nil
|
||||
}
|
||||
|
||||
func (u *UserInterface) KeyName(key Key) string {
|
||||
return ""
|
||||
}
|
||||
+391
@@ -0,0 +1,391 @@
|
||||
// Copyright 2013 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.
|
||||
|
||||
// Code generated by genkeys.go using 'go generate'. DO NOT EDIT.
|
||||
|
||||
package ui
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
)
|
||||
|
||||
type Key int
|
||||
|
||||
const (
|
||||
KeyA Key = iota
|
||||
KeyB
|
||||
KeyC
|
||||
KeyD
|
||||
KeyE
|
||||
KeyF
|
||||
KeyG
|
||||
KeyH
|
||||
KeyI
|
||||
KeyJ
|
||||
KeyK
|
||||
KeyL
|
||||
KeyM
|
||||
KeyN
|
||||
KeyO
|
||||
KeyP
|
||||
KeyQ
|
||||
KeyR
|
||||
KeyS
|
||||
KeyT
|
||||
KeyU
|
||||
KeyV
|
||||
KeyW
|
||||
KeyX
|
||||
KeyY
|
||||
KeyZ
|
||||
KeyAltLeft
|
||||
KeyAltRight
|
||||
KeyArrowDown
|
||||
KeyArrowLeft
|
||||
KeyArrowRight
|
||||
KeyArrowUp
|
||||
KeyBackquote
|
||||
KeyBackslash
|
||||
KeyBackspace
|
||||
KeyBracketLeft
|
||||
KeyBracketRight
|
||||
KeyCapsLock
|
||||
KeyComma
|
||||
KeyContextMenu
|
||||
KeyControlLeft
|
||||
KeyControlRight
|
||||
KeyDelete
|
||||
KeyDigit0
|
||||
KeyDigit1
|
||||
KeyDigit2
|
||||
KeyDigit3
|
||||
KeyDigit4
|
||||
KeyDigit5
|
||||
KeyDigit6
|
||||
KeyDigit7
|
||||
KeyDigit8
|
||||
KeyDigit9
|
||||
KeyEnd
|
||||
KeyEnter
|
||||
KeyEqual
|
||||
KeyEscape
|
||||
KeyF1
|
||||
KeyF2
|
||||
KeyF3
|
||||
KeyF4
|
||||
KeyF5
|
||||
KeyF6
|
||||
KeyF7
|
||||
KeyF8
|
||||
KeyF9
|
||||
KeyF10
|
||||
KeyF11
|
||||
KeyF12
|
||||
KeyF13
|
||||
KeyF14
|
||||
KeyF15
|
||||
KeyF16
|
||||
KeyF17
|
||||
KeyF18
|
||||
KeyF19
|
||||
KeyF20
|
||||
KeyF21
|
||||
KeyF22
|
||||
KeyF23
|
||||
KeyF24
|
||||
KeyHome
|
||||
KeyInsert
|
||||
KeyIntlBackslash
|
||||
KeyMetaLeft
|
||||
KeyMetaRight
|
||||
KeyMinus
|
||||
KeyNumLock
|
||||
KeyNumpad0
|
||||
KeyNumpad1
|
||||
KeyNumpad2
|
||||
KeyNumpad3
|
||||
KeyNumpad4
|
||||
KeyNumpad5
|
||||
KeyNumpad6
|
||||
KeyNumpad7
|
||||
KeyNumpad8
|
||||
KeyNumpad9
|
||||
KeyNumpadAdd
|
||||
KeyNumpadDecimal
|
||||
KeyNumpadDivide
|
||||
KeyNumpadEnter
|
||||
KeyNumpadEqual
|
||||
KeyNumpadMultiply
|
||||
KeyNumpadSubtract
|
||||
KeyPageDown
|
||||
KeyPageUp
|
||||
KeyPause
|
||||
KeyPeriod
|
||||
KeyPrintScreen
|
||||
KeyQuote
|
||||
KeyScrollLock
|
||||
KeySemicolon
|
||||
KeyShiftLeft
|
||||
KeyShiftRight
|
||||
KeySlash
|
||||
KeySpace
|
||||
KeyTab
|
||||
KeyReserved0
|
||||
KeyReserved1
|
||||
KeyReserved2
|
||||
KeyReserved3
|
||||
KeyMax = KeyReserved3
|
||||
)
|
||||
|
||||
func (k Key) String() string {
|
||||
switch k {
|
||||
case KeyA:
|
||||
return "KeyA"
|
||||
case KeyB:
|
||||
return "KeyB"
|
||||
case KeyC:
|
||||
return "KeyC"
|
||||
case KeyD:
|
||||
return "KeyD"
|
||||
case KeyE:
|
||||
return "KeyE"
|
||||
case KeyF:
|
||||
return "KeyF"
|
||||
case KeyG:
|
||||
return "KeyG"
|
||||
case KeyH:
|
||||
return "KeyH"
|
||||
case KeyI:
|
||||
return "KeyI"
|
||||
case KeyJ:
|
||||
return "KeyJ"
|
||||
case KeyK:
|
||||
return "KeyK"
|
||||
case KeyL:
|
||||
return "KeyL"
|
||||
case KeyM:
|
||||
return "KeyM"
|
||||
case KeyN:
|
||||
return "KeyN"
|
||||
case KeyO:
|
||||
return "KeyO"
|
||||
case KeyP:
|
||||
return "KeyP"
|
||||
case KeyQ:
|
||||
return "KeyQ"
|
||||
case KeyR:
|
||||
return "KeyR"
|
||||
case KeyS:
|
||||
return "KeyS"
|
||||
case KeyT:
|
||||
return "KeyT"
|
||||
case KeyU:
|
||||
return "KeyU"
|
||||
case KeyV:
|
||||
return "KeyV"
|
||||
case KeyW:
|
||||
return "KeyW"
|
||||
case KeyX:
|
||||
return "KeyX"
|
||||
case KeyY:
|
||||
return "KeyY"
|
||||
case KeyZ:
|
||||
return "KeyZ"
|
||||
case KeyAltLeft:
|
||||
return "KeyAltLeft"
|
||||
case KeyAltRight:
|
||||
return "KeyAltRight"
|
||||
case KeyArrowDown:
|
||||
return "KeyArrowDown"
|
||||
case KeyArrowLeft:
|
||||
return "KeyArrowLeft"
|
||||
case KeyArrowRight:
|
||||
return "KeyArrowRight"
|
||||
case KeyArrowUp:
|
||||
return "KeyArrowUp"
|
||||
case KeyBackquote:
|
||||
return "KeyBackquote"
|
||||
case KeyBackslash:
|
||||
return "KeyBackslash"
|
||||
case KeyBackspace:
|
||||
return "KeyBackspace"
|
||||
case KeyBracketLeft:
|
||||
return "KeyBracketLeft"
|
||||
case KeyBracketRight:
|
||||
return "KeyBracketRight"
|
||||
case KeyCapsLock:
|
||||
return "KeyCapsLock"
|
||||
case KeyComma:
|
||||
return "KeyComma"
|
||||
case KeyContextMenu:
|
||||
return "KeyContextMenu"
|
||||
case KeyControlLeft:
|
||||
return "KeyControlLeft"
|
||||
case KeyControlRight:
|
||||
return "KeyControlRight"
|
||||
case KeyDelete:
|
||||
return "KeyDelete"
|
||||
case KeyDigit0:
|
||||
return "KeyDigit0"
|
||||
case KeyDigit1:
|
||||
return "KeyDigit1"
|
||||
case KeyDigit2:
|
||||
return "KeyDigit2"
|
||||
case KeyDigit3:
|
||||
return "KeyDigit3"
|
||||
case KeyDigit4:
|
||||
return "KeyDigit4"
|
||||
case KeyDigit5:
|
||||
return "KeyDigit5"
|
||||
case KeyDigit6:
|
||||
return "KeyDigit6"
|
||||
case KeyDigit7:
|
||||
return "KeyDigit7"
|
||||
case KeyDigit8:
|
||||
return "KeyDigit8"
|
||||
case KeyDigit9:
|
||||
return "KeyDigit9"
|
||||
case KeyEnd:
|
||||
return "KeyEnd"
|
||||
case KeyEnter:
|
||||
return "KeyEnter"
|
||||
case KeyEqual:
|
||||
return "KeyEqual"
|
||||
case KeyEscape:
|
||||
return "KeyEscape"
|
||||
case KeyF1:
|
||||
return "KeyF1"
|
||||
case KeyF2:
|
||||
return "KeyF2"
|
||||
case KeyF3:
|
||||
return "KeyF3"
|
||||
case KeyF4:
|
||||
return "KeyF4"
|
||||
case KeyF5:
|
||||
return "KeyF5"
|
||||
case KeyF6:
|
||||
return "KeyF6"
|
||||
case KeyF7:
|
||||
return "KeyF7"
|
||||
case KeyF8:
|
||||
return "KeyF8"
|
||||
case KeyF9:
|
||||
return "KeyF9"
|
||||
case KeyF10:
|
||||
return "KeyF10"
|
||||
case KeyF11:
|
||||
return "KeyF11"
|
||||
case KeyF12:
|
||||
return "KeyF12"
|
||||
case KeyF13:
|
||||
return "KeyF13"
|
||||
case KeyF14:
|
||||
return "KeyF14"
|
||||
case KeyF15:
|
||||
return "KeyF15"
|
||||
case KeyF16:
|
||||
return "KeyF16"
|
||||
case KeyF17:
|
||||
return "KeyF17"
|
||||
case KeyF18:
|
||||
return "KeyF18"
|
||||
case KeyF19:
|
||||
return "KeyF19"
|
||||
case KeyF20:
|
||||
return "KeyF20"
|
||||
case KeyF21:
|
||||
return "KeyF21"
|
||||
case KeyF22:
|
||||
return "KeyF22"
|
||||
case KeyF23:
|
||||
return "KeyF23"
|
||||
case KeyF24:
|
||||
return "KeyF24"
|
||||
case KeyHome:
|
||||
return "KeyHome"
|
||||
case KeyInsert:
|
||||
return "KeyInsert"
|
||||
case KeyIntlBackslash:
|
||||
return "KeyIntlBackslash"
|
||||
case KeyMetaLeft:
|
||||
return "KeyMetaLeft"
|
||||
case KeyMetaRight:
|
||||
return "KeyMetaRight"
|
||||
case KeyMinus:
|
||||
return "KeyMinus"
|
||||
case KeyNumLock:
|
||||
return "KeyNumLock"
|
||||
case KeyNumpad0:
|
||||
return "KeyNumpad0"
|
||||
case KeyNumpad1:
|
||||
return "KeyNumpad1"
|
||||
case KeyNumpad2:
|
||||
return "KeyNumpad2"
|
||||
case KeyNumpad3:
|
||||
return "KeyNumpad3"
|
||||
case KeyNumpad4:
|
||||
return "KeyNumpad4"
|
||||
case KeyNumpad5:
|
||||
return "KeyNumpad5"
|
||||
case KeyNumpad6:
|
||||
return "KeyNumpad6"
|
||||
case KeyNumpad7:
|
||||
return "KeyNumpad7"
|
||||
case KeyNumpad8:
|
||||
return "KeyNumpad8"
|
||||
case KeyNumpad9:
|
||||
return "KeyNumpad9"
|
||||
case KeyNumpadAdd:
|
||||
return "KeyNumpadAdd"
|
||||
case KeyNumpadDecimal:
|
||||
return "KeyNumpadDecimal"
|
||||
case KeyNumpadDivide:
|
||||
return "KeyNumpadDivide"
|
||||
case KeyNumpadEnter:
|
||||
return "KeyNumpadEnter"
|
||||
case KeyNumpadEqual:
|
||||
return "KeyNumpadEqual"
|
||||
case KeyNumpadMultiply:
|
||||
return "KeyNumpadMultiply"
|
||||
case KeyNumpadSubtract:
|
||||
return "KeyNumpadSubtract"
|
||||
case KeyPageDown:
|
||||
return "KeyPageDown"
|
||||
case KeyPageUp:
|
||||
return "KeyPageUp"
|
||||
case KeyPause:
|
||||
return "KeyPause"
|
||||
case KeyPeriod:
|
||||
return "KeyPeriod"
|
||||
case KeyPrintScreen:
|
||||
return "KeyPrintScreen"
|
||||
case KeyQuote:
|
||||
return "KeyQuote"
|
||||
case KeyScrollLock:
|
||||
return "KeyScrollLock"
|
||||
case KeySemicolon:
|
||||
return "KeySemicolon"
|
||||
case KeyShiftLeft:
|
||||
return "KeyShiftLeft"
|
||||
case KeyShiftRight:
|
||||
return "KeyShiftRight"
|
||||
case KeySlash:
|
||||
return "KeySlash"
|
||||
case KeySpace:
|
||||
return "KeySpace"
|
||||
case KeyTab:
|
||||
return "KeyTab"
|
||||
}
|
||||
return fmt.Sprintf("Key(%d)", k)
|
||||
}
|
||||
+144
@@ -0,0 +1,144 @@
|
||||
// Copyright 2013 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.
|
||||
|
||||
// Code generated by genkeys.go using 'go generate'. DO NOT EDIT.
|
||||
|
||||
//go:build !android && !ios && !js && !nintendosdk && !playstation5
|
||||
|
||||
package ui
|
||||
|
||||
import (
|
||||
"github.com/hajimehoshi/ebiten/v2/internal/glfw"
|
||||
)
|
||||
|
||||
var uiKeyToGLFWKey = map[Key]glfw.Key{
|
||||
KeyA: glfw.KeyA,
|
||||
KeyAltLeft: glfw.KeyLeftAlt,
|
||||
KeyAltRight: glfw.KeyRightAlt,
|
||||
KeyArrowDown: glfw.KeyDown,
|
||||
KeyArrowLeft: glfw.KeyLeft,
|
||||
KeyArrowRight: glfw.KeyRight,
|
||||
KeyArrowUp: glfw.KeyUp,
|
||||
KeyB: glfw.KeyB,
|
||||
KeyBackquote: glfw.KeyGraveAccent,
|
||||
KeyBackslash: glfw.KeyBackslash,
|
||||
KeyBackspace: glfw.KeyBackspace,
|
||||
KeyBracketLeft: glfw.KeyLeftBracket,
|
||||
KeyBracketRight: glfw.KeyRightBracket,
|
||||
KeyC: glfw.KeyC,
|
||||
KeyCapsLock: glfw.KeyCapsLock,
|
||||
KeyComma: glfw.KeyComma,
|
||||
KeyContextMenu: glfw.KeyMenu,
|
||||
KeyControlLeft: glfw.KeyLeftControl,
|
||||
KeyControlRight: glfw.KeyRightControl,
|
||||
KeyD: glfw.KeyD,
|
||||
KeyDelete: glfw.KeyDelete,
|
||||
KeyDigit0: glfw.Key0,
|
||||
KeyDigit1: glfw.Key1,
|
||||
KeyDigit2: glfw.Key2,
|
||||
KeyDigit3: glfw.Key3,
|
||||
KeyDigit4: glfw.Key4,
|
||||
KeyDigit5: glfw.Key5,
|
||||
KeyDigit6: glfw.Key6,
|
||||
KeyDigit7: glfw.Key7,
|
||||
KeyDigit8: glfw.Key8,
|
||||
KeyDigit9: glfw.Key9,
|
||||
KeyE: glfw.KeyE,
|
||||
KeyEnd: glfw.KeyEnd,
|
||||
KeyEnter: glfw.KeyEnter,
|
||||
KeyEqual: glfw.KeyEqual,
|
||||
KeyEscape: glfw.KeyEscape,
|
||||
KeyF: glfw.KeyF,
|
||||
KeyF1: glfw.KeyF1,
|
||||
KeyF10: glfw.KeyF10,
|
||||
KeyF11: glfw.KeyF11,
|
||||
KeyF12: glfw.KeyF12,
|
||||
KeyF13: glfw.KeyF13,
|
||||
KeyF14: glfw.KeyF14,
|
||||
KeyF15: glfw.KeyF15,
|
||||
KeyF16: glfw.KeyF16,
|
||||
KeyF17: glfw.KeyF17,
|
||||
KeyF18: glfw.KeyF18,
|
||||
KeyF19: glfw.KeyF19,
|
||||
KeyF2: glfw.KeyF2,
|
||||
KeyF20: glfw.KeyF20,
|
||||
KeyF21: glfw.KeyF21,
|
||||
KeyF22: glfw.KeyF22,
|
||||
KeyF23: glfw.KeyF23,
|
||||
KeyF24: glfw.KeyF24,
|
||||
KeyF3: glfw.KeyF3,
|
||||
KeyF4: glfw.KeyF4,
|
||||
KeyF5: glfw.KeyF5,
|
||||
KeyF6: glfw.KeyF6,
|
||||
KeyF7: glfw.KeyF7,
|
||||
KeyF8: glfw.KeyF8,
|
||||
KeyF9: glfw.KeyF9,
|
||||
KeyG: glfw.KeyG,
|
||||
KeyH: glfw.KeyH,
|
||||
KeyHome: glfw.KeyHome,
|
||||
KeyI: glfw.KeyI,
|
||||
KeyInsert: glfw.KeyInsert,
|
||||
KeyIntlBackslash: glfw.KeyWorld1,
|
||||
KeyJ: glfw.KeyJ,
|
||||
KeyK: glfw.KeyK,
|
||||
KeyL: glfw.KeyL,
|
||||
KeyM: glfw.KeyM,
|
||||
KeyMetaLeft: glfw.KeyLeftSuper,
|
||||
KeyMetaRight: glfw.KeyRightSuper,
|
||||
KeyMinus: glfw.KeyMinus,
|
||||
KeyN: glfw.KeyN,
|
||||
KeyNumLock: glfw.KeyNumLock,
|
||||
KeyNumpad0: glfw.KeyKP0,
|
||||
KeyNumpad1: glfw.KeyKP1,
|
||||
KeyNumpad2: glfw.KeyKP2,
|
||||
KeyNumpad3: glfw.KeyKP3,
|
||||
KeyNumpad4: glfw.KeyKP4,
|
||||
KeyNumpad5: glfw.KeyKP5,
|
||||
KeyNumpad6: glfw.KeyKP6,
|
||||
KeyNumpad7: glfw.KeyKP7,
|
||||
KeyNumpad8: glfw.KeyKP8,
|
||||
KeyNumpad9: glfw.KeyKP9,
|
||||
KeyNumpadAdd: glfw.KeyKPAdd,
|
||||
KeyNumpadDecimal: glfw.KeyKPDecimal,
|
||||
KeyNumpadDivide: glfw.KeyKPDivide,
|
||||
KeyNumpadEnter: glfw.KeyKPEnter,
|
||||
KeyNumpadEqual: glfw.KeyKPEqual,
|
||||
KeyNumpadMultiply: glfw.KeyKPMultiply,
|
||||
KeyNumpadSubtract: glfw.KeyKPSubtract,
|
||||
KeyO: glfw.KeyO,
|
||||
KeyP: glfw.KeyP,
|
||||
KeyPageDown: glfw.KeyPageDown,
|
||||
KeyPageUp: glfw.KeyPageUp,
|
||||
KeyPause: glfw.KeyPause,
|
||||
KeyPeriod: glfw.KeyPeriod,
|
||||
KeyPrintScreen: glfw.KeyPrintScreen,
|
||||
KeyQ: glfw.KeyQ,
|
||||
KeyQuote: glfw.KeyApostrophe,
|
||||
KeyR: glfw.KeyR,
|
||||
KeyS: glfw.KeyS,
|
||||
KeyScrollLock: glfw.KeyScrollLock,
|
||||
KeySemicolon: glfw.KeySemicolon,
|
||||
KeyShiftLeft: glfw.KeyLeftShift,
|
||||
KeyShiftRight: glfw.KeyRightShift,
|
||||
KeySlash: glfw.KeySlash,
|
||||
KeySpace: glfw.KeySpace,
|
||||
KeyT: glfw.KeyT,
|
||||
KeyTab: glfw.KeyTab,
|
||||
KeyU: glfw.KeyU,
|
||||
KeyV: glfw.KeyV,
|
||||
KeyW: glfw.KeyW,
|
||||
KeyX: glfw.KeyX,
|
||||
KeyY: glfw.KeyY,
|
||||
KeyZ: glfw.KeyZ,
|
||||
}
|
||||
+142
@@ -0,0 +1,142 @@
|
||||
// Copyright 2013 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.
|
||||
|
||||
// Code generated by genkeys.go using 'go generate'. DO NOT EDIT.
|
||||
|
||||
package ui
|
||||
|
||||
import (
|
||||
"syscall/js"
|
||||
)
|
||||
|
||||
var uiKeyToJSCode = map[Key]js.Value{
|
||||
KeyA: js.ValueOf("KeyA"),
|
||||
KeyAltLeft: js.ValueOf("AltLeft"),
|
||||
KeyAltRight: js.ValueOf("AltRight"),
|
||||
KeyArrowDown: js.ValueOf("ArrowDown"),
|
||||
KeyArrowLeft: js.ValueOf("ArrowLeft"),
|
||||
KeyArrowRight: js.ValueOf("ArrowRight"),
|
||||
KeyArrowUp: js.ValueOf("ArrowUp"),
|
||||
KeyB: js.ValueOf("KeyB"),
|
||||
KeyBackquote: js.ValueOf("Backquote"),
|
||||
KeyBackslash: js.ValueOf("Backslash"),
|
||||
KeyBackspace: js.ValueOf("Backspace"),
|
||||
KeyBracketLeft: js.ValueOf("BracketLeft"),
|
||||
KeyBracketRight: js.ValueOf("BracketRight"),
|
||||
KeyC: js.ValueOf("KeyC"),
|
||||
KeyCapsLock: js.ValueOf("CapsLock"),
|
||||
KeyComma: js.ValueOf("Comma"),
|
||||
KeyContextMenu: js.ValueOf("ContextMenu"),
|
||||
KeyControlLeft: js.ValueOf("ControlLeft"),
|
||||
KeyControlRight: js.ValueOf("ControlRight"),
|
||||
KeyD: js.ValueOf("KeyD"),
|
||||
KeyDelete: js.ValueOf("Delete"),
|
||||
KeyDigit0: js.ValueOf("Digit0"),
|
||||
KeyDigit1: js.ValueOf("Digit1"),
|
||||
KeyDigit2: js.ValueOf("Digit2"),
|
||||
KeyDigit3: js.ValueOf("Digit3"),
|
||||
KeyDigit4: js.ValueOf("Digit4"),
|
||||
KeyDigit5: js.ValueOf("Digit5"),
|
||||
KeyDigit6: js.ValueOf("Digit6"),
|
||||
KeyDigit7: js.ValueOf("Digit7"),
|
||||
KeyDigit8: js.ValueOf("Digit8"),
|
||||
KeyDigit9: js.ValueOf("Digit9"),
|
||||
KeyE: js.ValueOf("KeyE"),
|
||||
KeyEnd: js.ValueOf("End"),
|
||||
KeyEnter: js.ValueOf("Enter"),
|
||||
KeyEqual: js.ValueOf("Equal"),
|
||||
KeyEscape: js.ValueOf("Escape"),
|
||||
KeyF: js.ValueOf("KeyF"),
|
||||
KeyF1: js.ValueOf("F1"),
|
||||
KeyF10: js.ValueOf("F10"),
|
||||
KeyF11: js.ValueOf("F11"),
|
||||
KeyF12: js.ValueOf("F12"),
|
||||
KeyF13: js.ValueOf("F13"),
|
||||
KeyF14: js.ValueOf("F14"),
|
||||
KeyF15: js.ValueOf("F15"),
|
||||
KeyF16: js.ValueOf("F16"),
|
||||
KeyF17: js.ValueOf("F17"),
|
||||
KeyF18: js.ValueOf("F18"),
|
||||
KeyF19: js.ValueOf("F19"),
|
||||
KeyF2: js.ValueOf("F2"),
|
||||
KeyF20: js.ValueOf("F20"),
|
||||
KeyF21: js.ValueOf("F21"),
|
||||
KeyF22: js.ValueOf("F22"),
|
||||
KeyF23: js.ValueOf("F23"),
|
||||
KeyF24: js.ValueOf("F24"),
|
||||
KeyF3: js.ValueOf("F3"),
|
||||
KeyF4: js.ValueOf("F4"),
|
||||
KeyF5: js.ValueOf("F5"),
|
||||
KeyF6: js.ValueOf("F6"),
|
||||
KeyF7: js.ValueOf("F7"),
|
||||
KeyF8: js.ValueOf("F8"),
|
||||
KeyF9: js.ValueOf("F9"),
|
||||
KeyG: js.ValueOf("KeyG"),
|
||||
KeyH: js.ValueOf("KeyH"),
|
||||
KeyHome: js.ValueOf("Home"),
|
||||
KeyI: js.ValueOf("KeyI"),
|
||||
KeyInsert: js.ValueOf("Insert"),
|
||||
KeyIntlBackslash: js.ValueOf("IntlBackslash"),
|
||||
KeyJ: js.ValueOf("KeyJ"),
|
||||
KeyK: js.ValueOf("KeyK"),
|
||||
KeyL: js.ValueOf("KeyL"),
|
||||
KeyM: js.ValueOf("KeyM"),
|
||||
KeyMetaLeft: js.ValueOf("MetaLeft"),
|
||||
KeyMetaRight: js.ValueOf("MetaRight"),
|
||||
KeyMinus: js.ValueOf("Minus"),
|
||||
KeyN: js.ValueOf("KeyN"),
|
||||
KeyNumLock: js.ValueOf("NumLock"),
|
||||
KeyNumpad0: js.ValueOf("Numpad0"),
|
||||
KeyNumpad1: js.ValueOf("Numpad1"),
|
||||
KeyNumpad2: js.ValueOf("Numpad2"),
|
||||
KeyNumpad3: js.ValueOf("Numpad3"),
|
||||
KeyNumpad4: js.ValueOf("Numpad4"),
|
||||
KeyNumpad5: js.ValueOf("Numpad5"),
|
||||
KeyNumpad6: js.ValueOf("Numpad6"),
|
||||
KeyNumpad7: js.ValueOf("Numpad7"),
|
||||
KeyNumpad8: js.ValueOf("Numpad8"),
|
||||
KeyNumpad9: js.ValueOf("Numpad9"),
|
||||
KeyNumpadAdd: js.ValueOf("NumpadAdd"),
|
||||
KeyNumpadDecimal: js.ValueOf("NumpadDecimal"),
|
||||
KeyNumpadDivide: js.ValueOf("NumpadDivide"),
|
||||
KeyNumpadEnter: js.ValueOf("NumpadEnter"),
|
||||
KeyNumpadEqual: js.ValueOf("NumpadEqual"),
|
||||
KeyNumpadMultiply: js.ValueOf("NumpadMultiply"),
|
||||
KeyNumpadSubtract: js.ValueOf("NumpadSubtract"),
|
||||
KeyO: js.ValueOf("KeyO"),
|
||||
KeyP: js.ValueOf("KeyP"),
|
||||
KeyPageDown: js.ValueOf("PageDown"),
|
||||
KeyPageUp: js.ValueOf("PageUp"),
|
||||
KeyPause: js.ValueOf("Pause"),
|
||||
KeyPeriod: js.ValueOf("Period"),
|
||||
KeyPrintScreen: js.ValueOf("PrintScreen"),
|
||||
KeyQ: js.ValueOf("KeyQ"),
|
||||
KeyQuote: js.ValueOf("Quote"),
|
||||
KeyR: js.ValueOf("KeyR"),
|
||||
KeyS: js.ValueOf("KeyS"),
|
||||
KeyScrollLock: js.ValueOf("ScrollLock"),
|
||||
KeySemicolon: js.ValueOf("Semicolon"),
|
||||
KeyShiftLeft: js.ValueOf("ShiftLeft"),
|
||||
KeyShiftRight: js.ValueOf("ShiftRight"),
|
||||
KeySlash: js.ValueOf("Slash"),
|
||||
KeySpace: js.ValueOf("Space"),
|
||||
KeyT: js.ValueOf("KeyT"),
|
||||
KeyTab: js.ValueOf("Tab"),
|
||||
KeyU: js.ValueOf("KeyU"),
|
||||
KeyV: js.ValueOf("KeyV"),
|
||||
KeyW: js.ValueOf("KeyW"),
|
||||
KeyX: js.ValueOf("KeyX"),
|
||||
KeyY: js.ValueOf("KeyY"),
|
||||
KeyZ: js.ValueOf("KeyZ"),
|
||||
}
|
||||
+184
@@ -0,0 +1,184 @@
|
||||
// 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.
|
||||
|
||||
//go:build !android && !ios && !js && !nintendosdk && !playstation5
|
||||
|
||||
package ui
|
||||
|
||||
import (
|
||||
"image"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
|
||||
"github.com/hajimehoshi/ebiten/v2/internal/glfw"
|
||||
)
|
||||
|
||||
// Monitor is a wrapper around glfw.Monitor.
|
||||
type Monitor struct {
|
||||
m *glfw.Monitor
|
||||
videoMode *glfw.VidMode
|
||||
|
||||
id int
|
||||
name string
|
||||
boundsInGLFWPixels image.Rectangle
|
||||
contentScale float64
|
||||
}
|
||||
|
||||
// Name returns the monitor's name.
|
||||
func (m *Monitor) Name() string {
|
||||
return m.name
|
||||
}
|
||||
|
||||
// DeviceScaleFactor is concurrent-safe as contentScale is immutable.
|
||||
func (m *Monitor) DeviceScaleFactor() float64 {
|
||||
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()
|
||||
return dipFromGLFWPixel(float64(w), s), dipFromGLFWPixel(float64(h), s)
|
||||
}
|
||||
|
||||
type monitors struct {
|
||||
// monitors is the monitor list cache for desktop glfw compile targets.
|
||||
// populated by 'updateMonitors' which is called on init and every
|
||||
// monitor config change event.
|
||||
monitors []*Monitor
|
||||
|
||||
m sync.Mutex
|
||||
|
||||
updateCalled int32
|
||||
}
|
||||
|
||||
var theMonitors monitors
|
||||
|
||||
func (m *monitors) append(ms []*Monitor) []*Monitor {
|
||||
if atomic.LoadInt32(&m.updateCalled) == 0 {
|
||||
panic("ui: (*monitors).update must be called before (*monitors).append is called")
|
||||
}
|
||||
|
||||
m.m.Lock()
|
||||
defer m.m.Unlock()
|
||||
|
||||
return append(ms, m.monitors...)
|
||||
}
|
||||
|
||||
func (m *monitors) primaryMonitor() *Monitor {
|
||||
if atomic.LoadInt32(&m.updateCalled) == 0 {
|
||||
panic("ui: (*monitors).update must be called before (*monitors).primaryMonitor is called")
|
||||
}
|
||||
|
||||
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]
|
||||
}
|
||||
|
||||
func (m *monitors) monitorFromID(id int) *Monitor {
|
||||
m.m.Lock()
|
||||
defer m.m.Unlock()
|
||||
|
||||
return m.monitors[id]
|
||||
}
|
||||
|
||||
// monitorFromPosition returns a monitor for the given position (x, y),
|
||||
// or returns nil if monitor is not found.
|
||||
// The position is in GLFW pixels.
|
||||
func (m *monitors) monitorFromPosition(x, y int) *Monitor {
|
||||
m.m.Lock()
|
||||
defer m.m.Unlock()
|
||||
|
||||
for _, m := range m.monitors {
|
||||
// Use an inclusive range. On macOS, the cursor position can take this range (#2794).
|
||||
b := m.boundsInGLFWPixels
|
||||
if b.Min.X <= x && x <= b.Max.X && b.Min.Y <= y && y <= b.Max.Y {
|
||||
return m
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// update must be called from the main thread.
|
||||
func (m *monitors) update() error {
|
||||
glfwMonitors, err := glfw.GetMonitors()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
newMonitors := make([]*Monitor, 0, len(glfwMonitors))
|
||||
for i, m := range glfwMonitors {
|
||||
x, y, err := m.GetPos()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// TODO: Detect the update of the content scale by SetContentScaleCallback (#2343).
|
||||
contentScale := 1.0
|
||||
|
||||
// Keep calling GetContentScale until the returned scale is 0 (#2051).
|
||||
// Retry this at most 5 times to avoid an infinite loop.
|
||||
for i := 0; i < 5; i++ {
|
||||
// An error can happen e.g. when entering a screensaver on Windows (#2488).
|
||||
sx, _, err := m.GetContentScale()
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
if sx == 0 {
|
||||
continue
|
||||
}
|
||||
contentScale = float64(sx)
|
||||
break
|
||||
}
|
||||
|
||||
videoMode, err := m.GetVideoMode()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
name, err := m.GetName()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
w, h, err := glfwMonitorSizeInGLFWPixels(m)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
b := image.Rect(x, y, x+w, y+h)
|
||||
newMonitors = append(newMonitors, &Monitor{
|
||||
m: m,
|
||||
videoMode: videoMode,
|
||||
id: i,
|
||||
name: name,
|
||||
boundsInGLFWPixels: b,
|
||||
contentScale: contentScale,
|
||||
})
|
||||
}
|
||||
|
||||
m.m.Lock()
|
||||
m.monitors = newMonitors
|
||||
m.m.Unlock()
|
||||
|
||||
atomic.StoreInt32(&m.updateCalled, 1)
|
||||
return nil
|
||||
}
|
||||
+23
@@ -0,0 +1,23 @@
|
||||
// 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.
|
||||
|
||||
//go:build nintendosdk && !nintendosdkprofile
|
||||
|
||||
package ui
|
||||
|
||||
func initializeProfiler() {
|
||||
}
|
||||
|
||||
func recordProfilerHeartbeat() {
|
||||
}
|
||||
+28
@@ -0,0 +1,28 @@
|
||||
// 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.
|
||||
|
||||
//go:build nintendosdk && nintendosdkprofile
|
||||
|
||||
package ui
|
||||
|
||||
// #include "init_nintendosdk.h"
|
||||
import "C"
|
||||
|
||||
func initializeProfiler() {
|
||||
C.ebitengine_InitializeProfiler()
|
||||
}
|
||||
|
||||
func recordProfilerHeartbeat() {
|
||||
C.ebitengine_RecordProfilerHeartbeat()
|
||||
}
|
||||
+93
@@ -0,0 +1,93 @@
|
||||
// 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.
|
||||
|
||||
//go:build !android && !ios
|
||||
|
||||
package ui
|
||||
|
||||
import (
|
||||
stdcontext "context"
|
||||
"runtime"
|
||||
|
||||
"golang.org/x/sync/errgroup"
|
||||
|
||||
"github.com/hajimehoshi/ebiten/v2/internal/graphicscommand"
|
||||
"github.com/hajimehoshi/ebiten/v2/internal/thread"
|
||||
)
|
||||
|
||||
func (u *UserInterface) Run(game Game, options *RunOptions) error {
|
||||
if options.SingleThread || buildTagSingleThread || runtime.GOOS == "js" {
|
||||
return u.runSingleThread(game, options)
|
||||
}
|
||||
return u.runMultiThread(game, options)
|
||||
}
|
||||
|
||||
func (u *UserInterface) runMultiThread(game Game, options *RunOptions) error {
|
||||
u.mainThread = thread.NewOSThread()
|
||||
graphicscommand.SetOSThreadAsRenderThread()
|
||||
|
||||
// Set the running state true after the main thread is set, and before initOnMainThread is called (#2742).
|
||||
// TODO: As the existence of the main thread is the same as the value of `running`, this is redundant.
|
||||
// Make `mainThread` atomic and remove `running` if possible.
|
||||
u.setRunning(true)
|
||||
defer u.setRunning(false)
|
||||
|
||||
u.context = newContext(game)
|
||||
|
||||
if err := u.initOnMainThread(options); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
ctx, cancel := stdcontext.WithCancel(stdcontext.Background())
|
||||
defer cancel()
|
||||
|
||||
var wg errgroup.Group
|
||||
|
||||
// Run the render thread.
|
||||
wg.Go(func() error {
|
||||
defer cancel()
|
||||
graphicscommand.LoopRenderThread(ctx)
|
||||
return nil
|
||||
})
|
||||
|
||||
// Run the game thread.
|
||||
wg.Go(func() error {
|
||||
defer cancel()
|
||||
return u.loopGame()
|
||||
})
|
||||
|
||||
// Run the main thread.
|
||||
_ = u.mainThread.Loop(ctx)
|
||||
return wg.Wait()
|
||||
}
|
||||
|
||||
func (u *UserInterface) runSingleThread(game Game, options *RunOptions) error {
|
||||
// Initialize the main thread first so the thread is available at u.run (#809).
|
||||
u.mainThread = thread.NewNoopThread()
|
||||
|
||||
u.setRunning(true)
|
||||
defer u.setRunning(false)
|
||||
|
||||
u.context = newContext(game)
|
||||
|
||||
if err := u.initOnMainThread(options); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := u.loopGame(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
+19
@@ -0,0 +1,19 @@
|
||||
// Copyright 2020 The Ebiten 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 !ebitenginesinglethread && !ebitensinglethread
|
||||
|
||||
package ui
|
||||
|
||||
const buildTagSingleThread = false
|
||||
+19
@@ -0,0 +1,19 @@
|
||||
// Copyright 2020 The Ebiten 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 ebitenginesinglethread || ebitensinglethread
|
||||
|
||||
package ui
|
||||
|
||||
const buildTagSingleThread = true
|
||||
+20
@@ -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("innerWidth").Int(), window.Get("innerHeight").Int()
|
||||
}
|
||||
Generated
Vendored
+21
@@ -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()
|
||||
}
|
||||
+118
@@ -0,0 +1,118 @@
|
||||
// Copyright 2022 The Ebiten 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
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"math"
|
||||
"reflect"
|
||||
|
||||
"github.com/hajimehoshi/ebiten/v2/internal/atlas"
|
||||
"github.com/hajimehoshi/ebiten/v2/internal/graphics"
|
||||
"github.com/hajimehoshi/ebiten/v2/internal/shaderir"
|
||||
)
|
||||
|
||||
type Shader struct {
|
||||
shader *atlas.Shader
|
||||
|
||||
uniformNames []string
|
||||
uniformTypes []shaderir.Type
|
||||
uniformUint32Count int
|
||||
}
|
||||
|
||||
func NewShader(ir *shaderir.Program) *Shader {
|
||||
return &Shader{
|
||||
shader: atlas.NewShader(ir),
|
||||
uniformNames: ir.UniformNames[graphics.PreservedUniformVariablesCount:],
|
||||
uniformTypes: ir.Uniforms[graphics.PreservedUniformVariablesCount:],
|
||||
}
|
||||
}
|
||||
|
||||
func (s *Shader) Deallocate() {
|
||||
s.shader.Deallocate()
|
||||
}
|
||||
|
||||
func (s *Shader) AppendUniforms(dst []uint32, uniforms map[string]any) []uint32 {
|
||||
if s.uniformUint32Count == 0 {
|
||||
for _, typ := range s.uniformTypes {
|
||||
s.uniformUint32Count += typ.Uint32Count()
|
||||
}
|
||||
}
|
||||
|
||||
origLen := len(dst)
|
||||
if cap(dst)-len(dst) >= s.uniformUint32Count {
|
||||
dst = dst[:len(dst)+s.uniformUint32Count]
|
||||
for i := origLen; i < len(dst); i++ {
|
||||
dst[i] = 0
|
||||
}
|
||||
} else {
|
||||
dst = append(dst, make([]uint32, s.uniformUint32Count)...)
|
||||
}
|
||||
|
||||
idx := origLen
|
||||
for i, name := range s.uniformNames {
|
||||
typ := s.uniformTypes[i]
|
||||
|
||||
// Ignore if an unused name is specified (#2710).
|
||||
if uv, ok := uniforms[name]; ok {
|
||||
v := reflect.ValueOf(uv)
|
||||
t := v.Type()
|
||||
switch t.Kind() {
|
||||
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
|
||||
if typ.Uint32Count() != 1 {
|
||||
panic(fmt.Sprintf("ui: unexpected uniform value for %s (%s)", name, typ.String()))
|
||||
}
|
||||
dst[idx] = uint32(v.Int())
|
||||
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr:
|
||||
if typ.Uint32Count() != 1 {
|
||||
panic(fmt.Sprintf("ui: unexpected uniform value for %s (%s)", name, typ.String()))
|
||||
}
|
||||
dst[idx] = uint32(v.Uint())
|
||||
case reflect.Float32, reflect.Float64:
|
||||
if typ.Uint32Count() != 1 {
|
||||
panic(fmt.Sprintf("ui: unexpected uniform value for %s (%s)", name, typ.String()))
|
||||
}
|
||||
dst[idx] = math.Float32bits(float32(v.Float()))
|
||||
case reflect.Slice, reflect.Array:
|
||||
l := v.Len()
|
||||
if typ.Uint32Count() != l {
|
||||
panic(fmt.Sprintf("ui: unexpected uniform value for %s (%s)", name, typ.String()))
|
||||
}
|
||||
switch t.Elem().Kind() {
|
||||
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
|
||||
for i := 0; i < l; i++ {
|
||||
dst[idx+i] = uint32(v.Index(i).Int())
|
||||
}
|
||||
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr:
|
||||
for i := 0; i < l; i++ {
|
||||
dst[idx+i] = uint32(v.Index(i).Uint())
|
||||
}
|
||||
case reflect.Float32, reflect.Float64:
|
||||
for i := 0; i < l; i++ {
|
||||
dst[idx+i] = math.Float32bits(float32(v.Index(i).Float()))
|
||||
}
|
||||
default:
|
||||
panic(fmt.Sprintf("ui: unexpected uniform value type: %s (%s)", name, v.Kind().String()))
|
||||
}
|
||||
default:
|
||||
panic(fmt.Sprintf("ui: unexpected uniform value type: %s (%s)", name, v.Kind().String()))
|
||||
}
|
||||
}
|
||||
|
||||
idx += typ.Uint32Count()
|
||||
}
|
||||
|
||||
return dst
|
||||
}
|
||||
+236
@@ -0,0 +1,236 @@
|
||||
// Copyright 2022 The Ebiten 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
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"image"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
|
||||
_ "github.com/ebitengine/hideconsole"
|
||||
|
||||
"github.com/hajimehoshi/ebiten/v2/internal/atlas"
|
||||
"github.com/hajimehoshi/ebiten/v2/internal/mipmap"
|
||||
"github.com/hajimehoshi/ebiten/v2/internal/thread"
|
||||
)
|
||||
|
||||
// RegularTermination represents a regular termination.
|
||||
// Run can return this error, and if this error is received,
|
||||
// the game loop should be terminated as soon as possible.
|
||||
var RegularTermination = errors.New("regular termination")
|
||||
|
||||
type FPSModeType int
|
||||
|
||||
const (
|
||||
FPSModeVsyncOn FPSModeType = iota
|
||||
FPSModeVsyncOffMaximum
|
||||
FPSModeVsyncOffMinimum
|
||||
)
|
||||
|
||||
type CursorMode int
|
||||
|
||||
const (
|
||||
CursorModeVisible CursorMode = iota
|
||||
CursorModeHidden
|
||||
CursorModeCaptured
|
||||
)
|
||||
|
||||
type CursorShape int
|
||||
|
||||
const (
|
||||
CursorShapeDefault CursorShape = iota
|
||||
CursorShapeText
|
||||
CursorShapeCrosshair
|
||||
CursorShapePointer
|
||||
CursorShapeEWResize
|
||||
CursorShapeNSResize
|
||||
CursorShapeNESWResize
|
||||
CursorShapeNWSEResize
|
||||
CursorShapeMove
|
||||
CursorShapeNotAllowed
|
||||
)
|
||||
|
||||
type WindowResizingMode int
|
||||
|
||||
const (
|
||||
WindowResizingModeDisabled WindowResizingMode = iota
|
||||
WindowResizingModeOnlyFullscreenEnabled
|
||||
WindowResizingModeEnabled
|
||||
)
|
||||
|
||||
type UserInterface struct {
|
||||
err error
|
||||
errM sync.Mutex
|
||||
|
||||
isScreenClearedEveryFrame int32
|
||||
graphicsLibrary int32
|
||||
running int32
|
||||
terminated int32
|
||||
|
||||
whiteImage *Image
|
||||
|
||||
mainThread thread.Thread
|
||||
|
||||
userInterfaceImpl
|
||||
}
|
||||
|
||||
var (
|
||||
theUI *UserInterface
|
||||
)
|
||||
|
||||
func init() {
|
||||
// newUserInterface() must be called in the main goroutine.
|
||||
u, err := newUserInterface()
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
theUI = u
|
||||
}
|
||||
|
||||
func Get() *UserInterface {
|
||||
return theUI
|
||||
}
|
||||
|
||||
// newUserInterface must be called from the main thread.
|
||||
func newUserInterface() (*UserInterface, error) {
|
||||
u := &UserInterface{
|
||||
isScreenClearedEveryFrame: 1,
|
||||
graphicsLibrary: int32(GraphicsLibraryUnknown),
|
||||
}
|
||||
|
||||
u.whiteImage = u.NewImage(3, 3, atlas.ImageTypeRegular)
|
||||
pix := make([]byte, 4*u.whiteImage.width*u.whiteImage.height)
|
||||
for i := range pix {
|
||||
pix[i] = 0xff
|
||||
}
|
||||
// As a white image is used at Fill, use WritePixels instead.
|
||||
u.whiteImage.WritePixels(pix, image.Rect(0, 0, u.whiteImage.width, u.whiteImage.height))
|
||||
|
||||
if err := u.init(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return u, nil
|
||||
}
|
||||
|
||||
func (u *UserInterface) readPixels(mipmap *mipmap.Mipmap, pixels []byte, region image.Rectangle) error {
|
||||
ok, err := mipmap.ReadPixels(u.graphicsDriver, pixels, region)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// ReadPixels failed since this was called in between two frames.
|
||||
// Try this again at the next frame.
|
||||
if !ok {
|
||||
// If this function is called from the same sequence as a game's Update and Draw,
|
||||
// this causes a dead lock.
|
||||
// This never happens so far, but if handling inputs after EndFrame is implemented,
|
||||
// this might be possible (#1704).
|
||||
|
||||
var err1 error
|
||||
u.context.runInFrame(func() {
|
||||
ok, err := mipmap.ReadPixels(u.graphicsDriver, pixels, region)
|
||||
if err != nil {
|
||||
err1 = err
|
||||
return
|
||||
}
|
||||
if !ok {
|
||||
// This never reaches since this function must be called in a frame.
|
||||
panic("ui: ReadPixels unexpectedly failed")
|
||||
}
|
||||
})
|
||||
return err1
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (u *UserInterface) dumpScreenshot(mipmap *mipmap.Mipmap, name string, blackbg bool) (string, error) {
|
||||
return mipmap.DumpScreenshot(u.graphicsDriver, name, blackbg)
|
||||
}
|
||||
|
||||
func (u *UserInterface) dumpImages(dir string) (string, error) {
|
||||
return atlas.DumpImages(u.graphicsDriver, dir)
|
||||
}
|
||||
|
||||
type RunOptions struct {
|
||||
GraphicsLibrary GraphicsLibrary
|
||||
InitUnfocused bool
|
||||
ScreenTransparent bool
|
||||
SkipTaskbar bool
|
||||
SingleThread bool
|
||||
X11ClassName string
|
||||
X11InstanceName string
|
||||
}
|
||||
|
||||
// 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
|
||||
}
|
||||
|
||||
func (u *UserInterface) error() error {
|
||||
u.errM.Lock()
|
||||
defer u.errM.Unlock()
|
||||
return u.err
|
||||
}
|
||||
|
||||
func (u *UserInterface) setError(err error) {
|
||||
u.errM.Lock()
|
||||
defer u.errM.Unlock()
|
||||
if u.err == nil {
|
||||
u.err = err
|
||||
}
|
||||
}
|
||||
|
||||
func (u *UserInterface) IsScreenClearedEveryFrame() bool {
|
||||
return atomic.LoadInt32(&u.isScreenClearedEveryFrame) != 0
|
||||
}
|
||||
|
||||
func (u *UserInterface) SetScreenClearedEveryFrame(cleared bool) {
|
||||
v := int32(0)
|
||||
if cleared {
|
||||
v = 1
|
||||
}
|
||||
atomic.StoreInt32(&u.isScreenClearedEveryFrame, v)
|
||||
}
|
||||
|
||||
func (u *UserInterface) setGraphicsLibrary(library GraphicsLibrary) {
|
||||
atomic.StoreInt32(&u.graphicsLibrary, int32(library))
|
||||
}
|
||||
|
||||
func (u *UserInterface) GraphicsLibrary() GraphicsLibrary {
|
||||
return GraphicsLibrary(atomic.LoadInt32(&u.graphicsLibrary))
|
||||
}
|
||||
|
||||
func (u *UserInterface) isRunning() bool {
|
||||
return atomic.LoadInt32(&u.running) != 0 && !u.isTerminated()
|
||||
}
|
||||
|
||||
func (u *UserInterface) setRunning(running bool) {
|
||||
if running {
|
||||
atomic.StoreInt32(&u.running, 1)
|
||||
} else {
|
||||
atomic.StoreInt32(&u.running, 0)
|
||||
}
|
||||
}
|
||||
|
||||
func (u *UserInterface) isTerminated() bool {
|
||||
return atomic.LoadInt32(&u.terminated) != 0
|
||||
}
|
||||
|
||||
func (u *UserInterface) setTerminated() {
|
||||
atomic.StoreInt32(&u.terminated, 1)
|
||||
}
|
||||
+135
@@ -0,0 +1,135 @@
|
||||
// Copyright 2022 The Ebiten 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
|
||||
|
||||
/*
|
||||
#include <jni.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
// Basically same as:
|
||||
//
|
||||
// WindowService windowService = context.getSystemService(Context.WINDOW_SERVICE);
|
||||
// Display display = windowManager.getDefaultDisplay();
|
||||
// DisplayMetrics displayMetrics = new DisplayMetrics();
|
||||
// display.getRealMetrics(displayMetrics);
|
||||
// this.deviceScale = displayMetrics.density;
|
||||
//
|
||||
static float deviceScale(uintptr_t java_vm, uintptr_t jni_env, uintptr_t ctx) {
|
||||
JavaVM* vm = (JavaVM*)java_vm;
|
||||
JNIEnv* env = (JNIEnv*)jni_env;
|
||||
jobject context = (jobject)ctx;
|
||||
|
||||
const char* kWindowService = "window";
|
||||
|
||||
const jclass android_content_Context =
|
||||
(*env)->FindClass(env, "android/content/Context");
|
||||
const jclass android_view_WindowManager =
|
||||
(*env)->FindClass(env, "android/view/WindowManager");
|
||||
const jclass android_view_Display =
|
||||
(*env)->FindClass(env, "android/view/Display");
|
||||
const jclass android_util_DisplayMetrics =
|
||||
(*env)->FindClass(env, "android/util/DisplayMetrics");
|
||||
|
||||
const jobject android_context_Context_WINDOW_SERVICE =
|
||||
(*env)->GetStaticObjectField(
|
||||
env, android_content_Context,
|
||||
(*env)->GetStaticFieldID(env, android_content_Context, "WINDOW_SERVICE", "Ljava/lang/String;"));
|
||||
|
||||
const jobject windowManager =
|
||||
(*env)->CallObjectMethod(
|
||||
env, context,
|
||||
(*env)->GetMethodID(env, android_content_Context, "getSystemService", "(Ljava/lang/String;)Ljava/lang/Object;"),
|
||||
android_context_Context_WINDOW_SERVICE);
|
||||
const jobject display =
|
||||
(*env)->CallObjectMethod(
|
||||
env, windowManager,
|
||||
(*env)->GetMethodID(env, android_view_WindowManager, "getDefaultDisplay", "()Landroid/view/Display;"));
|
||||
const jobject displayMetrics =
|
||||
(*env)->NewObject(
|
||||
env, android_util_DisplayMetrics,
|
||||
(*env)->GetMethodID(env, android_util_DisplayMetrics, "<init>", "()V"));
|
||||
(*env)->CallVoidMethod(
|
||||
env, display,
|
||||
(*env)->GetMethodID(env, android_view_Display, "getRealMetrics", "(Landroid/util/DisplayMetrics;)V"),
|
||||
displayMetrics);
|
||||
const float density =
|
||||
(*env)->GetFloatField(
|
||||
env, displayMetrics,
|
||||
(*env)->GetFieldID(env, android_util_DisplayMetrics, "density", "F"));
|
||||
|
||||
(*env)->DeleteLocalRef(env, android_content_Context);
|
||||
(*env)->DeleteLocalRef(env, android_view_WindowManager);
|
||||
(*env)->DeleteLocalRef(env, android_view_Display);
|
||||
(*env)->DeleteLocalRef(env, android_util_DisplayMetrics);
|
||||
|
||||
(*env)->DeleteLocalRef(env, android_context_Context_WINDOW_SERVICE);
|
||||
(*env)->DeleteLocalRef(env, windowManager);
|
||||
(*env)->DeleteLocalRef(env, display);
|
||||
(*env)->DeleteLocalRef(env, displayMetrics);
|
||||
|
||||
return density;
|
||||
}
|
||||
*/
|
||||
import "C"
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
|
||||
"github.com/ebitengine/gomobile/app"
|
||||
|
||||
"github.com/hajimehoshi/ebiten/v2/internal/graphicsdriver"
|
||||
"github.com/hajimehoshi/ebiten/v2/internal/graphicsdriver/opengl"
|
||||
)
|
||||
|
||||
type graphicsDriverCreatorImpl struct {
|
||||
}
|
||||
|
||||
func (g *graphicsDriverCreatorImpl) newAuto() (graphicsdriver.Graphics, GraphicsLibrary, error) {
|
||||
graphics, err := g.newOpenGL()
|
||||
return graphics, GraphicsLibraryOpenGL, err
|
||||
}
|
||||
|
||||
func (g *graphicsDriverCreatorImpl) newOpenGL() (graphicsdriver.Graphics, error) {
|
||||
return opengl.NewGraphics()
|
||||
}
|
||||
|
||||
func (*graphicsDriverCreatorImpl) newDirectX() (graphicsdriver.Graphics, error) {
|
||||
return nil, errors.New("ui: DirectX is not supported in this environment")
|
||||
}
|
||||
|
||||
func (*graphicsDriverCreatorImpl) newMetal() (graphicsdriver.Graphics, error) {
|
||||
return nil, errors.New("ui: Metal is not supported in this environment")
|
||||
}
|
||||
|
||||
func (*graphicsDriverCreatorImpl) newPlayStation5() (graphicsdriver.Graphics, error) {
|
||||
return nil, errors.New("ui: PlayStation 5 is not supported in this environment")
|
||||
}
|
||||
|
||||
func deviceScaleFactorImpl() float64 {
|
||||
var s float64
|
||||
if err := app.RunOnJVM(func(vm, env, ctx uintptr) error {
|
||||
// TODO: This might be crash when this is called from init(). How can we detect this?
|
||||
s = float64(C.deviceScale(C.uintptr_t(vm), C.uintptr_t(env), C.uintptr_t(ctx)))
|
||||
return nil
|
||||
}); err != nil {
|
||||
panic(fmt.Sprintf("devicescale: error %v", err))
|
||||
}
|
||||
return s
|
||||
}
|
||||
|
||||
func dipToNativePixels(x float64, scale float64) float64 {
|
||||
return x * scale
|
||||
}
|
||||
+436
@@ -0,0 +1,436 @@
|
||||
// Copyright 2016 Hajime Hoshi
|
||||
//
|
||||
// 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 !ios
|
||||
|
||||
package ui
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"reflect"
|
||||
"unsafe"
|
||||
|
||||
"github.com/ebitengine/purego/objc"
|
||||
|
||||
"github.com/hajimehoshi/ebiten/v2/internal/cocoa"
|
||||
"github.com/hajimehoshi/ebiten/v2/internal/glfw"
|
||||
"github.com/hajimehoshi/ebiten/v2/internal/graphicsdriver"
|
||||
"github.com/hajimehoshi/ebiten/v2/internal/graphicsdriver/metal"
|
||||
"github.com/hajimehoshi/ebiten/v2/internal/graphicsdriver/opengl"
|
||||
)
|
||||
|
||||
var class_EbitengineWindowDelegate objc.Class
|
||||
|
||||
func (u *UserInterface) initializePlatform() error {
|
||||
pushResizableState := func(id, win objc.ID) {
|
||||
window := cocoa.NSWindow{ID: win}
|
||||
id.Send(sel_setOrigResizable, window.StyleMask()&cocoa.NSWindowStyleMaskResizable != 0)
|
||||
if !objc.Send[bool](id, sel_origResizable) {
|
||||
window.SetStyleMask(window.StyleMask() | cocoa.NSWindowStyleMaskResizable)
|
||||
}
|
||||
}
|
||||
popResizableState := func(id, win objc.ID) {
|
||||
if !objc.Send[bool](id, sel_origResizable) {
|
||||
window := cocoa.NSWindow{ID: win}
|
||||
window.SetStyleMask(window.StyleMask() & ^uint(cocoa.NSWindowStyleMaskResizable))
|
||||
}
|
||||
id.Send(sel_setOrigResizable, false)
|
||||
}
|
||||
d, err := objc.RegisterClass(
|
||||
"EbitengineWindowDelegate",
|
||||
objc.GetClass("NSObject"),
|
||||
[]*objc.Protocol{objc.GetProtocol("NSWindowDelegate")},
|
||||
[]objc.FieldDef{
|
||||
{
|
||||
Name: "origDelegate",
|
||||
Type: reflect.TypeOf(objc.ID(0)),
|
||||
Attribute: objc.ReadWrite,
|
||||
},
|
||||
{
|
||||
Name: "origResizable",
|
||||
Type: reflect.TypeOf(true),
|
||||
Attribute: objc.ReadWrite,
|
||||
},
|
||||
},
|
||||
[]objc.MethodDef{
|
||||
{
|
||||
Cmd: sel_initWithOrigDelegate,
|
||||
Fn: func(id objc.ID, cmd objc.SEL, origDelegate objc.ID) objc.ID {
|
||||
self := id.SendSuper(sel_init)
|
||||
if self != 0 {
|
||||
id.Send(sel_setOrigDelegate, origDelegate)
|
||||
}
|
||||
return self
|
||||
},
|
||||
},
|
||||
// The method set of origDelegate must sync with GLFWWindowDelegate's implementation.
|
||||
// See cocoa_window.m in GLFW.
|
||||
{
|
||||
Cmd: sel_windowShouldClose,
|
||||
Fn: func(id objc.ID, cmd objc.SEL, notification objc.ID) bool {
|
||||
return id.Send(sel_origDelegate).Send(cmd, notification) != 0
|
||||
},
|
||||
},
|
||||
{
|
||||
Cmd: sel_windowDidResize,
|
||||
Fn: func(id objc.ID, cmd objc.SEL, notification objc.ID) {
|
||||
id.Send(sel_origDelegate).Send(cmd, notification)
|
||||
},
|
||||
},
|
||||
{
|
||||
Cmd: sel_windowDidMove,
|
||||
Fn: func(id objc.ID, cmd objc.SEL, notification objc.ID) {
|
||||
id.Send(sel_origDelegate).Send(cmd, notification)
|
||||
},
|
||||
},
|
||||
{
|
||||
Cmd: sel_windowDidMiniaturize,
|
||||
Fn: func(id objc.ID, cmd objc.SEL, notification objc.ID) {
|
||||
id.Send(sel_origDelegate).Send(cmd, notification)
|
||||
},
|
||||
},
|
||||
{
|
||||
Cmd: sel_windowDidBecomeKey,
|
||||
Fn: func(id objc.ID, cmd objc.SEL, notification objc.ID) {
|
||||
id.Send(sel_origDelegate).Send(cmd, notification)
|
||||
},
|
||||
},
|
||||
{
|
||||
Cmd: sel_windowDidResignKey,
|
||||
Fn: func(id objc.ID, cmd objc.SEL, notification objc.ID) {
|
||||
id.Send(sel_origDelegate).Send(cmd, notification)
|
||||
},
|
||||
},
|
||||
{
|
||||
Cmd: sel_windowDidChangeOcclusionState,
|
||||
Fn: func(id objc.ID, cmd objc.SEL, notification objc.ID) {
|
||||
id.Send(sel_origDelegate).Send(cmd, notification)
|
||||
},
|
||||
},
|
||||
{
|
||||
Cmd: sel_windowWillEnterFullScreen,
|
||||
Fn: func(id objc.ID, cmd objc.SEL, notification objc.ID) {
|
||||
if err := u.setOrigWindowPosWithCurrentPos(); err != nil {
|
||||
u.setError(err)
|
||||
return
|
||||
}
|
||||
pushResizableState(id, cocoa.NSNotification{ID: notification}.Object())
|
||||
},
|
||||
},
|
||||
{
|
||||
Cmd: sel_windowDidEnterFullScreen,
|
||||
Fn: func(id objc.ID, cmd objc.SEL, notification objc.ID) {
|
||||
popResizableState(id, cocoa.NSNotification{ID: notification}.Object())
|
||||
},
|
||||
},
|
||||
{
|
||||
Cmd: sel_windowWillExitFullScreen,
|
||||
Fn: func(id objc.ID, cmd objc.SEL, notification objc.ID) {
|
||||
pushResizableState(id, cocoa.NSNotification{ID: notification}.Object())
|
||||
// Even a window has a size limitation, a window can be fullscreen by calling SetFullscreen(true).
|
||||
// In this case, the window size limitation is disabled temporarily.
|
||||
// When exiting from fullscreen, reset the window size limitation.
|
||||
if err := u.updateWindowSizeLimits(); err != nil {
|
||||
u.setError(err)
|
||||
return
|
||||
}
|
||||
},
|
||||
},
|
||||
{
|
||||
Cmd: sel_windowDidExitFullScreen,
|
||||
Fn: func(id objc.ID, cmd objc.SEL, notification objc.ID) {
|
||||
popResizableState(id, cocoa.NSNotification{ID: notification}.Object())
|
||||
// Do not call setFrame here (#2295). setFrame here causes unexpected results.
|
||||
},
|
||||
},
|
||||
},
|
||||
)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
class_EbitengineWindowDelegate = d
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
type graphicsDriverCreatorImpl struct {
|
||||
transparent bool
|
||||
}
|
||||
|
||||
func (g *graphicsDriverCreatorImpl) newAuto() (graphicsdriver.Graphics, GraphicsLibrary, error) {
|
||||
m, err1 := g.newMetal()
|
||||
if err1 == nil {
|
||||
return m, GraphicsLibraryMetal, nil
|
||||
}
|
||||
o, err2 := g.newOpenGL()
|
||||
if err2 == nil {
|
||||
return o, GraphicsLibraryOpenGL, nil
|
||||
}
|
||||
return nil, GraphicsLibraryUnknown, fmt.Errorf("ui: failed to choose graphics drivers: Metal: %v, OpenGL: %v", err1, err2)
|
||||
}
|
||||
|
||||
func (*graphicsDriverCreatorImpl) newOpenGL() (graphicsdriver.Graphics, error) {
|
||||
return opengl.NewGraphics()
|
||||
}
|
||||
|
||||
func (*graphicsDriverCreatorImpl) newDirectX() (graphicsdriver.Graphics, error) {
|
||||
return nil, errors.New("ui: DirectX is not supported in this environment")
|
||||
}
|
||||
|
||||
func (*graphicsDriverCreatorImpl) newMetal() (graphicsdriver.Graphics, error) {
|
||||
return metal.NewGraphics()
|
||||
}
|
||||
|
||||
func (*graphicsDriverCreatorImpl) newPlayStation5() (graphicsdriver.Graphics, error) {
|
||||
return nil, errors.New("ui: PlayStation 5 is not supported in this environment")
|
||||
}
|
||||
|
||||
// glfwMonitorSizeInGLFWPixels must be called from the main thread.
|
||||
func glfwMonitorSizeInGLFWPixels(m *glfw.Monitor) (int, int, error) {
|
||||
vm, err := m.GetVideoMode()
|
||||
if err != nil {
|
||||
return 0, 0, err
|
||||
}
|
||||
return vm.Width, vm.Height, nil
|
||||
}
|
||||
|
||||
func dipFromGLFWPixel(x float64, scale float64) float64 {
|
||||
// NOTE: On macOS, GLFW exposes the device independent coordinate system.
|
||||
// Thus, the conversion functions are unnecessary,
|
||||
// however we still need the deviceScaleFactor internally
|
||||
// so we can create and maintain a HiDPI frame buffer.
|
||||
return x
|
||||
}
|
||||
|
||||
func dipToGLFWPixel(x float64, scale float64) float64 {
|
||||
return x
|
||||
}
|
||||
|
||||
func (u *UserInterface) adjustWindowPosition(x, y int, monitor *Monitor) (int, int) {
|
||||
return x, y
|
||||
}
|
||||
|
||||
var (
|
||||
class_NSCursor = objc.GetClass("NSCursor")
|
||||
class_NSEvent = objc.GetClass("NSEvent")
|
||||
)
|
||||
|
||||
var (
|
||||
sel_alloc = objc.RegisterName("alloc")
|
||||
sel_collectionBehavior = objc.RegisterName("collectionBehavior")
|
||||
sel_delegate = objc.RegisterName("delegate")
|
||||
sel_init = objc.RegisterName("init")
|
||||
sel_initWithOrigDelegate = objc.RegisterName("initWithOrigDelegate:")
|
||||
sel_mouseLocation = objc.RegisterName("mouseLocation")
|
||||
sel_origDelegate = objc.RegisterName("origDelegate")
|
||||
sel_origResizable = objc.RegisterName("isOrigResizable")
|
||||
sel_setCollectionBehavior = objc.RegisterName("setCollectionBehavior:")
|
||||
sel_setDelegate = objc.RegisterName("setDelegate:")
|
||||
sel_setOrigDelegate = objc.RegisterName("setOrigDelegate:")
|
||||
sel_setOrigResizable = objc.RegisterName("setOrigResizable:")
|
||||
sel_toggleFullScreen = objc.RegisterName("toggleFullScreen:")
|
||||
sel_windowDidBecomeKey = objc.RegisterName("windowDidBecomeKey:")
|
||||
sel_windowDidDeminiaturize = objc.RegisterName("windowDidDeminiaturize:")
|
||||
sel_windowDidEnterFullScreen = objc.RegisterName("windowDidEnterFullScreen:")
|
||||
sel_windowDidExitFullScreen = objc.RegisterName("windowDidExitFullScreen:")
|
||||
sel_windowDidMiniaturize = objc.RegisterName("windowDidMiniaturize:")
|
||||
sel_windowDidMove = objc.RegisterName("windowDidMove:")
|
||||
sel_windowDidResignKey = objc.RegisterName("windowDidResignKey:")
|
||||
sel_windowDidResize = objc.RegisterName("windowDidResize:")
|
||||
sel_windowDidChangeOcclusionState = objc.RegisterName("windowDidChangeOcclusionState:")
|
||||
sel_windowShouldClose = objc.RegisterName("windowShouldClose:")
|
||||
sel_windowWillEnterFullScreen = objc.RegisterName("windowWillEnterFullScreen:")
|
||||
sel_windowWillExitFullScreen = objc.RegisterName("windowWillExitFullScreen:")
|
||||
)
|
||||
|
||||
func currentMouseLocation() (x, y int) {
|
||||
sig := cocoa.NSMethodSignature_signatureWithObjCTypes("{NSPoint=dd}@:")
|
||||
inv := cocoa.NSInvocation_invocationWithMethodSignature(sig)
|
||||
inv.SetTarget(objc.ID(class_NSEvent))
|
||||
inv.SetSelector(sel_mouseLocation)
|
||||
inv.Invoke()
|
||||
var point cocoa.NSPoint
|
||||
inv.GetReturnValue(unsafe.Pointer(&point))
|
||||
|
||||
x, y = int(point.X), int(point.Y)
|
||||
|
||||
// On macOS, the Y axis is upward. Adjust the Y position (#807, #2794).
|
||||
y = -y
|
||||
m := theMonitors.primaryMonitor()
|
||||
y += m.videoMode.Height
|
||||
return x, y
|
||||
}
|
||||
|
||||
func initialMonitorByOS() (*Monitor, error) {
|
||||
x, y := currentMouseLocation()
|
||||
|
||||
// Find the monitor including the cursor.
|
||||
return theMonitors.monitorFromPosition(x, y), nil
|
||||
}
|
||||
|
||||
func monitorFromWindowByOS(w *glfw.Window) (*Monitor, error) {
|
||||
cocoaWindow, err := w.GetCocoaWindow()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
window := cocoa.NSWindow{ID: objc.ID(cocoaWindow)}
|
||||
pool := cocoa.NSAutoreleasePool_new()
|
||||
screen := cocoa.NSScreen_mainScreen()
|
||||
if window.ID != 0 && window.IsVisible() {
|
||||
// When the window is visible, the window is already initialized.
|
||||
// [NSScreen mainScreen] sometimes tells a lie when the window is put across monitors (#703).
|
||||
screen = window.Screen()
|
||||
}
|
||||
screenDictionary := screen.DeviceDescription()
|
||||
screenID := cocoa.NSNumber{ID: screenDictionary.ObjectForKey(cocoa.NSString_alloc().InitWithUTF8String("NSScreenNumber").ID)}
|
||||
aID := uintptr(screenID.UnsignedIntValue()) // CGDirectDisplayID
|
||||
pool.Release()
|
||||
for _, m := range theMonitors.append(nil) {
|
||||
cocoaMonitor, err := m.m.GetCocoaMonitor()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if cocoaMonitor == aID {
|
||||
return m, nil
|
||||
}
|
||||
}
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
func (u *UserInterface) nativeWindow() (uintptr, error) {
|
||||
return u.window.GetCocoaWindow()
|
||||
}
|
||||
|
||||
func (u *UserInterface) isNativeFullscreen() (bool, error) {
|
||||
w, err := u.window.GetCocoaWindow()
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
return cocoa.NSWindow{ID: objc.ID(w)}.StyleMask()&cocoa.NSWindowStyleMaskFullScreen != 0, nil
|
||||
}
|
||||
|
||||
func (u *UserInterface) isNativeFullscreenAvailable() bool {
|
||||
// TODO: If the window is transparent, we should use GLFW's windowed fullscreen (#1822, #1857).
|
||||
// However, if the user clicks the green button, should this window be in native fullscreen mode?
|
||||
return true
|
||||
}
|
||||
|
||||
func (u *UserInterface) setNativeFullscreen(fullscreen bool) error {
|
||||
// Toggling fullscreen might ignore events like keyUp. Ensure that events are fired.
|
||||
if err := glfw.WaitEventsTimeout(0.1); err != nil {
|
||||
return err
|
||||
}
|
||||
w, err := u.window.GetCocoaWindow()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
window := cocoa.NSWindow{ID: objc.ID(w)}
|
||||
if window.StyleMask()&cocoa.NSWindowStyleMaskFullScreen != 0 == fullscreen {
|
||||
return nil
|
||||
}
|
||||
// Even though EbitengineWindowDelegate is used, this hack is still required.
|
||||
// toggleFullscreen doesn't work when the window is not resizable.
|
||||
origCollectionBehavior := window.Send(sel_collectionBehavior)
|
||||
origFullScreen := origCollectionBehavior&cocoa.NSWindowCollectionBehaviorFullScreenPrimary != 0
|
||||
if !origFullScreen {
|
||||
collectionBehavior := origCollectionBehavior
|
||||
collectionBehavior |= cocoa.NSWindowCollectionBehaviorFullScreenPrimary
|
||||
collectionBehavior &^= cocoa.NSWindowCollectionBehaviorFullScreenNone
|
||||
window.Send(sel_setCollectionBehavior, cocoa.NSUInteger(collectionBehavior))
|
||||
}
|
||||
window.Send(sel_toggleFullScreen, 0)
|
||||
if !origFullScreen {
|
||||
window.Send(sel_setCollectionBehavior, cocoa.NSUInteger(cocoa.NSUInteger(origCollectionBehavior)))
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (u *UserInterface) adjustViewSizeAfterFullscreen() error {
|
||||
if u.GraphicsLibrary() == GraphicsLibraryOpenGL {
|
||||
return nil
|
||||
}
|
||||
|
||||
w, err := u.window.GetCocoaWindow()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
window := cocoa.NSWindow{ID: objc.ID(w)}
|
||||
if window.StyleMask()&cocoa.NSWindowStyleMaskFullScreen == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Reduce the view height (#1745).
|
||||
// https://stackoverflow.com/questions/27758027/sprite-kit-serious-fps-issue-in-full-screen-mode-on-os-x
|
||||
windowSize := window.Frame().Size
|
||||
view := window.ContentView()
|
||||
viewSize := view.Frame().Size
|
||||
if windowSize.Width != viewSize.Width || windowSize.Height != viewSize.Height {
|
||||
return nil
|
||||
}
|
||||
viewSize.Width--
|
||||
view.SetFrameSize(viewSize)
|
||||
|
||||
// NSColor.blackColor (0, 0, 0, 1) didn't work.
|
||||
// Use the transparent color instead.
|
||||
window.SetBackgroundColor(cocoa.NSColor_colorWithSRGBRedGreenBlueAlpha(0, 0, 0, 0))
|
||||
return nil
|
||||
}
|
||||
|
||||
func (u *UserInterface) isFullscreenAllowedFromUI(mode WindowResizingMode) bool {
|
||||
if u.maxWindowWidthInDIP != glfw.DontCare || u.maxWindowHeightInDIP != glfw.DontCare {
|
||||
return false
|
||||
}
|
||||
if mode == WindowResizingModeOnlyFullscreenEnabled {
|
||||
return true
|
||||
}
|
||||
if mode == WindowResizingModeEnabled {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func (u *UserInterface) setWindowResizingModeForOS(mode WindowResizingMode) error {
|
||||
var collectionBehavior uint
|
||||
if u.isFullscreenAllowedFromUI(mode) {
|
||||
collectionBehavior |= cocoa.NSWindowCollectionBehaviorManaged
|
||||
collectionBehavior |= cocoa.NSWindowCollectionBehaviorFullScreenPrimary
|
||||
} else {
|
||||
collectionBehavior |= cocoa.NSWindowCollectionBehaviorFullScreenNone
|
||||
}
|
||||
w, err := u.window.GetCocoaWindow()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
objc.ID(w).Send(sel_setCollectionBehavior, collectionBehavior)
|
||||
return nil
|
||||
}
|
||||
|
||||
func initializeWindowAfterCreation(w *glfw.Window) error {
|
||||
// TODO: Register NSWindowWillEnterFullScreenNotification and so on.
|
||||
// Enable resizing temporary before making the window fullscreen.
|
||||
cocoaWindow, err := w.GetCocoaWindow()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
nswindow := objc.ID(cocoaWindow)
|
||||
delegate := objc.ID(class_EbitengineWindowDelegate).Send(sel_alloc).Send(sel_initWithOrigDelegate, nswindow.Send(sel_delegate))
|
||||
nswindow.Send(sel_setDelegate, delegate)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (u *UserInterface) skipTaskbar() error {
|
||||
return nil
|
||||
}
|
||||
+2141
File diff suppressed because it is too large
Load Diff
+98
@@ -0,0 +1,98 @@
|
||||
// Copyright 2022 The Ebiten 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
|
||||
|
||||
// #cgo CFLAGS: -x objective-c
|
||||
// #cgo LDFLAGS: -framework Foundation -framework UIKit
|
||||
//
|
||||
// #import <UIKit/UIKit.h>
|
||||
//
|
||||
// static double devicePixelRatio() {
|
||||
// return [[UIScreen mainScreen] nativeScale];
|
||||
// }
|
||||
import "C"
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
|
||||
"github.com/hajimehoshi/ebiten/v2/internal/graphicsdriver"
|
||||
"github.com/hajimehoshi/ebiten/v2/internal/graphicsdriver/metal"
|
||||
"github.com/hajimehoshi/ebiten/v2/internal/graphicsdriver/opengl"
|
||||
)
|
||||
|
||||
type graphicsDriverCreatorImpl struct {
|
||||
}
|
||||
|
||||
func (g *graphicsDriverCreatorImpl) newAuto() (graphicsdriver.Graphics, GraphicsLibrary, error) {
|
||||
m, err1 := g.newMetal()
|
||||
if err1 == nil {
|
||||
return m, GraphicsLibraryMetal, nil
|
||||
}
|
||||
o, err2 := g.newOpenGL()
|
||||
if err2 == nil {
|
||||
return o, GraphicsLibraryMetal, nil
|
||||
}
|
||||
return nil, GraphicsLibraryUnknown, fmt.Errorf("ui: failed to choose graphics drivers: Metal: %v, OpenGL: %v", err1, err2)
|
||||
}
|
||||
|
||||
func (g *graphicsDriverCreatorImpl) newOpenGL() (graphicsdriver.Graphics, error) {
|
||||
return opengl.NewGraphics()
|
||||
}
|
||||
|
||||
func (*graphicsDriverCreatorImpl) newDirectX() (graphicsdriver.Graphics, error) {
|
||||
return nil, errors.New("ui: DirectX is not supported in this environment")
|
||||
}
|
||||
|
||||
func (g *graphicsDriverCreatorImpl) newMetal() (graphicsdriver.Graphics, error) {
|
||||
return metal.NewGraphics()
|
||||
}
|
||||
|
||||
func (*graphicsDriverCreatorImpl) newPlayStation5() (graphicsdriver.Graphics, error) {
|
||||
return nil, errors.New("ui: PlayStation 5 is not supported in this environment")
|
||||
}
|
||||
|
||||
func (u *UserInterface) SetUIView(uiview uintptr) error {
|
||||
select {
|
||||
case err := <-u.errCh:
|
||||
return err
|
||||
case <-u.graphicsLibraryInitCh:
|
||||
}
|
||||
|
||||
// This function should be called only when the graphics library is Metal.
|
||||
if g, ok := u.graphicsDriver.(interface{ SetUIView(uintptr) }); ok {
|
||||
g.SetUIView(uiview)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (u *UserInterface) IsGL() (bool, error) {
|
||||
select {
|
||||
case err := <-u.errCh:
|
||||
return false, err
|
||||
case <-u.graphicsLibraryInitCh:
|
||||
}
|
||||
|
||||
return u.GraphicsLibrary() == GraphicsLibraryOpenGL, nil
|
||||
}
|
||||
|
||||
func deviceScaleFactorImpl() float64 {
|
||||
// TODO: Can this be called from non-main threads?
|
||||
return float64(C.devicePixelRatio())
|
||||
}
|
||||
|
||||
func dipToNativePixels(x float64, scale float64) float64 {
|
||||
return x
|
||||
}
|
||||
+828
@@ -0,0 +1,828 @@
|
||||
// Copyright 2015 Hajime Hoshi
|
||||
//
|
||||
// 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
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"math"
|
||||
"sync"
|
||||
"syscall/js"
|
||||
"time"
|
||||
|
||||
"github.com/hajimehoshi/ebiten/v2/internal/file"
|
||||
"github.com/hajimehoshi/ebiten/v2/internal/gamepad"
|
||||
"github.com/hajimehoshi/ebiten/v2/internal/graphicsdriver"
|
||||
"github.com/hajimehoshi/ebiten/v2/internal/graphicsdriver/opengl"
|
||||
"github.com/hajimehoshi/ebiten/v2/internal/hook"
|
||||
)
|
||||
|
||||
type graphicsDriverCreatorImpl struct {
|
||||
canvas js.Value
|
||||
}
|
||||
|
||||
func (g *graphicsDriverCreatorImpl) newAuto() (graphicsdriver.Graphics, GraphicsLibrary, error) {
|
||||
graphics, err := g.newOpenGL()
|
||||
return graphics, GraphicsLibraryOpenGL, err
|
||||
}
|
||||
|
||||
func (g *graphicsDriverCreatorImpl) newOpenGL() (graphicsdriver.Graphics, error) {
|
||||
return opengl.NewGraphics(g.canvas)
|
||||
}
|
||||
|
||||
func (*graphicsDriverCreatorImpl) newDirectX() (graphicsdriver.Graphics, error) {
|
||||
return nil, errors.New("ui: DirectX is not supported in this environment")
|
||||
}
|
||||
|
||||
func (*graphicsDriverCreatorImpl) newMetal() (graphicsdriver.Graphics, error) {
|
||||
return nil, errors.New("ui: Metal is not supported in this environment")
|
||||
}
|
||||
|
||||
func (*graphicsDriverCreatorImpl) newPlayStation5() (graphicsdriver.Graphics, error) {
|
||||
return nil, errors.New("ui: PlayStation 5 is not supported in this environment")
|
||||
}
|
||||
|
||||
var (
|
||||
stringNone = js.ValueOf("none")
|
||||
stringTransparent = js.ValueOf("transparent")
|
||||
)
|
||||
|
||||
func driverCursorShapeToCSSCursor(cursor CursorShape) string {
|
||||
switch cursor {
|
||||
case CursorShapeDefault:
|
||||
return "default"
|
||||
case CursorShapeText:
|
||||
return "text"
|
||||
case CursorShapeCrosshair:
|
||||
return "crosshair"
|
||||
case CursorShapePointer:
|
||||
return "pointer"
|
||||
case CursorShapeEWResize:
|
||||
return "ew-resize"
|
||||
case CursorShapeNSResize:
|
||||
return "ns-resize"
|
||||
case CursorShapeNESWResize:
|
||||
return "nesw-resize"
|
||||
case CursorShapeNWSEResize:
|
||||
return "nwse-resize"
|
||||
case CursorShapeMove:
|
||||
return "move"
|
||||
case CursorShapeNotAllowed:
|
||||
return "not-allowed"
|
||||
}
|
||||
return "auto"
|
||||
}
|
||||
|
||||
type userInterfaceImpl struct {
|
||||
graphicsDriver graphicsdriver.Graphics
|
||||
|
||||
runnableOnUnfocused bool
|
||||
fpsMode FPSModeType
|
||||
renderingScheduled bool
|
||||
cursorMode CursorMode
|
||||
cursorPrevMode CursorMode
|
||||
captureCursorLater bool
|
||||
cursorShape CursorShape
|
||||
onceUpdateCalled bool
|
||||
lastCaptureExitTime time.Time
|
||||
|
||||
context *context
|
||||
inputState InputState
|
||||
keyDurationsByKeyProperty map[Key]int
|
||||
cursorXInClient float64
|
||||
cursorYInClient float64
|
||||
origCursorXInClient float64
|
||||
origCursorYInClient float64
|
||||
touchesInClient []touchInClient
|
||||
|
||||
savedCursorX float64
|
||||
savedCursorY float64
|
||||
savedOutsideWidth float64
|
||||
savedOutsideHeight float64
|
||||
outsideSizeUnchangedCount int
|
||||
|
||||
keyboardLayoutMap js.Value
|
||||
|
||||
m sync.Mutex
|
||||
dropFileM sync.Mutex
|
||||
}
|
||||
|
||||
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")
|
||||
)
|
||||
|
||||
var (
|
||||
documentHasFocus = document.Get("hasFocus").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) SetFullscreen(fullscreen bool) {
|
||||
if !canvas.Truthy() {
|
||||
return
|
||||
}
|
||||
if !document.Truthy() {
|
||||
return
|
||||
}
|
||||
if fullscreen == u.IsFullscreen() {
|
||||
return
|
||||
}
|
||||
|
||||
if u.cursorMode == CursorModeCaptured {
|
||||
u.saveCursorPosition()
|
||||
}
|
||||
|
||||
if fullscreen {
|
||||
f := canvas.Get("requestFullscreen")
|
||||
if !f.Truthy() {
|
||||
f = canvas.Get("webkitRequestFullscreen")
|
||||
}
|
||||
f.Call("bind", canvas).Invoke()
|
||||
return
|
||||
}
|
||||
|
||||
f := document.Get("exitFullscreen")
|
||||
if !f.Truthy() {
|
||||
f = document.Get("webkitExitFullscreen")
|
||||
}
|
||||
f.Call("bind", document).Invoke()
|
||||
}
|
||||
|
||||
func (u *UserInterface) IsFullscreen() bool {
|
||||
if !document.Truthy() {
|
||||
return false
|
||||
}
|
||||
if !document.Get("fullscreenElement").Truthy() && !document.Get("webkitFullscreenElement").Truthy() {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
func (u *UserInterface) IsFocused() bool {
|
||||
return u.isFocused()
|
||||
}
|
||||
|
||||
func (u *UserInterface) SetRunnableOnUnfocused(runnableOnUnfocused bool) {
|
||||
u.runnableOnUnfocused = runnableOnUnfocused
|
||||
}
|
||||
|
||||
func (u *UserInterface) IsRunnableOnUnfocused() bool {
|
||||
return u.runnableOnUnfocused
|
||||
}
|
||||
|
||||
func (u *UserInterface) FPSMode() FPSModeType {
|
||||
return u.fpsMode
|
||||
}
|
||||
|
||||
func (u *UserInterface) SetFPSMode(mode FPSModeType) {
|
||||
u.fpsMode = mode
|
||||
}
|
||||
|
||||
func (u *UserInterface) ScheduleFrame() {
|
||||
u.renderingScheduled = true
|
||||
}
|
||||
|
||||
func (u *UserInterface) CursorMode() CursorMode {
|
||||
if !canvas.Truthy() {
|
||||
return CursorModeHidden
|
||||
}
|
||||
return u.cursorMode
|
||||
}
|
||||
|
||||
func (u *UserInterface) SetCursorMode(mode CursorMode) {
|
||||
if mode == CursorModeCaptured && !u.canCaptureCursor() {
|
||||
u.captureCursorLater = true
|
||||
return
|
||||
}
|
||||
u.setCursorMode(mode)
|
||||
}
|
||||
|
||||
func (u *UserInterface) setCursorMode(mode CursorMode) {
|
||||
u.captureCursorLater = false
|
||||
|
||||
if !canvas.Truthy() {
|
||||
return
|
||||
}
|
||||
if u.cursorMode == mode {
|
||||
return
|
||||
}
|
||||
// Remember the previous cursor mode in the case when the pointer lock exits by pressing ESC.
|
||||
u.cursorPrevMode = u.cursorMode
|
||||
if u.cursorMode == CursorModeCaptured {
|
||||
document.Call("exitPointerLock")
|
||||
u.lastCaptureExitTime = time.Now()
|
||||
}
|
||||
u.cursorMode = mode
|
||||
switch mode {
|
||||
case CursorModeVisible:
|
||||
canvas.Get("style").Set("cursor", driverCursorShapeToCSSCursor(u.cursorShape))
|
||||
case CursorModeHidden:
|
||||
canvas.Get("style").Set("cursor", stringNone)
|
||||
case CursorModeCaptured:
|
||||
canvas.Call("requestPointerLock")
|
||||
}
|
||||
}
|
||||
|
||||
func (u *UserInterface) recoverCursorMode() {
|
||||
if u.cursorPrevMode == CursorModeCaptured {
|
||||
panic("ui: cursorPrevMode must not be CursorModeCaptured at recoverCursorMode")
|
||||
}
|
||||
u.SetCursorMode(u.cursorPrevMode)
|
||||
}
|
||||
|
||||
func (u *UserInterface) CursorShape() CursorShape {
|
||||
if !canvas.Truthy() {
|
||||
return CursorShapeDefault
|
||||
}
|
||||
return u.cursorShape
|
||||
}
|
||||
|
||||
func (u *UserInterface) SetCursorShape(shape CursorShape) {
|
||||
if !canvas.Truthy() {
|
||||
return
|
||||
}
|
||||
if u.cursorShape == shape {
|
||||
return
|
||||
}
|
||||
|
||||
u.cursorShape = shape
|
||||
if u.cursorMode == CursorModeVisible {
|
||||
canvas.Get("style").Set("cursor", driverCursorShapeToCSSCursor(u.cursorShape))
|
||||
}
|
||||
}
|
||||
|
||||
func (u *UserInterface) outsideSize() (float64, float64) {
|
||||
if document.Truthy() {
|
||||
body := document.Get("body")
|
||||
bw := body.Get("clientWidth").Float()
|
||||
bh := body.Get("clientHeight").Float()
|
||||
return bw, bh
|
||||
}
|
||||
|
||||
// Node.js
|
||||
return 640, 480
|
||||
}
|
||||
|
||||
func (u *UserInterface) suspended() bool {
|
||||
if u.runnableOnUnfocused {
|
||||
return false
|
||||
}
|
||||
return !u.isFocused()
|
||||
}
|
||||
|
||||
func (u *UserInterface) isFocused() bool {
|
||||
if !documentHasFocus.Invoke().Bool() {
|
||||
return false
|
||||
}
|
||||
if documentHidden.Invoke().Bool() {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
// canCaptureCursor reports whether a cursor can be captured or not now.
|
||||
// Just after escaping from a capture, a browser might not be able to capture a cursor (#2693).
|
||||
// If it is too early to capture a cursor, Ebitengine tries to delay it.
|
||||
//
|
||||
// See also https://w3c.github.io/pointerlock/#extensions-to-the-element-interface
|
||||
//
|
||||
// > Pointer lock is a transient activation-gated API, therefore a requestPointerLock() call
|
||||
// > MUST fail if the relevant global object of this does not have transient activation.
|
||||
// > This prevents locking upon initial navigation or re-acquiring lock without user's attention.
|
||||
func (u *UserInterface) canCaptureCursor() bool {
|
||||
// 1.5 [sec] seems enough in the real world.
|
||||
return time.Now().Sub(u.lastCaptureExitTime) >= 1500*time.Millisecond
|
||||
}
|
||||
|
||||
func (u *UserInterface) update() error {
|
||||
if u.captureCursorLater && u.canCaptureCursor() {
|
||||
u.setCursorMode(CursorModeCaptured)
|
||||
}
|
||||
|
||||
if u.suspended() {
|
||||
return hook.SuspendAudio()
|
||||
}
|
||||
if err := hook.ResumeAudio(); err != nil {
|
||||
return err
|
||||
}
|
||||
return u.updateImpl(false)
|
||||
}
|
||||
|
||||
func (u *UserInterface) updateImpl(force bool) error {
|
||||
// Guard updateImpl as this function cannot be invoked until this finishes (#2339).
|
||||
u.m.Lock()
|
||||
defer u.m.Unlock()
|
||||
|
||||
// context can be nil when an event is fired but the loop doesn't start yet (#1928).
|
||||
if u.context == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
if err := gamepad.Update(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// TODO: If DeviceScaleFactor changes, call updateScreenSize.
|
||||
// Now there is not a good way to detect the change.
|
||||
// See also https://crbug.com/123694.
|
||||
|
||||
w, h := u.outsideSize()
|
||||
if force {
|
||||
if err := u.context.forceUpdateFrame(u.graphicsDriver, w, h, theMonitor.DeviceScaleFactor(), u); err != nil {
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
if err := u.context.updateFrame(u.graphicsDriver, w, h, theMonitor.DeviceScaleFactor(), u); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (u *UserInterface) needsUpdate() bool {
|
||||
if u.fpsMode != FPSModeVsyncOffMinimum {
|
||||
return true
|
||||
}
|
||||
if !u.onceUpdateCalled {
|
||||
return true
|
||||
}
|
||||
if u.renderingScheduled {
|
||||
return true
|
||||
}
|
||||
// TODO: Watch the gamepad state?
|
||||
return false
|
||||
}
|
||||
|
||||
func (u *UserInterface) loopGame() error {
|
||||
// Initialize the screen size first (#3033).
|
||||
// If ebiten.SetRunnableOnUnfocused(false) and the canvas is not focused,
|
||||
// suspended() returns true and the update routine cannot start.
|
||||
u.updateScreenSize()
|
||||
|
||||
errCh := make(chan error, 1)
|
||||
reqStopAudioCh := make(chan struct{})
|
||||
resStopAudioCh := make(chan struct{})
|
||||
|
||||
var cf js.Func
|
||||
f := func() {
|
||||
if err := u.error(); err != nil {
|
||||
errCh <- err
|
||||
return
|
||||
}
|
||||
if u.needsUpdate() {
|
||||
defer func() {
|
||||
u.onceUpdateCalled = true
|
||||
}()
|
||||
u.renderingScheduled = false
|
||||
if err := u.update(); err != nil {
|
||||
close(reqStopAudioCh)
|
||||
<-resStopAudioCh
|
||||
|
||||
errCh <- err
|
||||
return
|
||||
}
|
||||
}
|
||||
switch u.fpsMode {
|
||||
case FPSModeVsyncOn:
|
||||
requestAnimationFrame.Invoke(cf)
|
||||
case FPSModeVsyncOffMaximum:
|
||||
setTimeout.Invoke(cf, 0)
|
||||
case FPSModeVsyncOffMinimum:
|
||||
requestAnimationFrame.Invoke(cf)
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: Should cf be released after the game ends?
|
||||
cf = js.FuncOf(func(this js.Value, args []js.Value) any {
|
||||
// f can be blocked but callbacks must not be blocked. Create a goroutine (#1161).
|
||||
go f()
|
||||
return nil
|
||||
})
|
||||
|
||||
// Call f asyncly since ch is used in f.
|
||||
go f()
|
||||
|
||||
// Run another loop to watch suspended() as the above update function is never called when the tab is hidden.
|
||||
// To check the document's visibility, visibilitychange event should usually be used. However, this event is
|
||||
// not reliable and sometimes it is not fired (#961). Then, watch the state regularly instead.
|
||||
go func() {
|
||||
defer close(resStopAudioCh)
|
||||
|
||||
const interval = 100 * time.Millisecond
|
||||
t := time.NewTicker(interval)
|
||||
defer func() {
|
||||
t.Stop()
|
||||
|
||||
// This is a dirty hack. (*time.Ticker).Stop() just marks the timer 'deleted' [1] and
|
||||
// something might run even after Stop. On Wasm, this causes an issue to execute Go program
|
||||
// even after finishing (#1027). Sleep for the interval time duration to ensure that
|
||||
// everything related to the timer is finished.
|
||||
//
|
||||
// [1] runtime.deltimer
|
||||
time.Sleep(interval)
|
||||
}()
|
||||
|
||||
for {
|
||||
select {
|
||||
case <-t.C:
|
||||
if u.suspended() {
|
||||
if err := hook.SuspendAudio(); err != nil {
|
||||
errCh <- err
|
||||
return
|
||||
}
|
||||
} else {
|
||||
if err := hook.ResumeAudio(); err != nil {
|
||||
errCh <- err
|
||||
return
|
||||
}
|
||||
}
|
||||
case <-reqStopAudioCh:
|
||||
return
|
||||
}
|
||||
}
|
||||
}()
|
||||
|
||||
return <-errCh
|
||||
}
|
||||
|
||||
func (u *UserInterface) init() error {
|
||||
u.userInterfaceImpl = userInterfaceImpl{
|
||||
runnableOnUnfocused: true,
|
||||
savedCursorX: math.NaN(),
|
||||
savedCursorY: math.NaN(),
|
||||
}
|
||||
|
||||
// document is undefined on node.js
|
||||
if !document.Truthy() {
|
||||
return nil
|
||||
}
|
||||
|
||||
if !document.Get("body").Truthy() {
|
||||
ch := make(chan struct{})
|
||||
window.Call("addEventListener", "load", js.FuncOf(func(this js.Value, args []js.Value) any {
|
||||
close(ch)
|
||||
return nil
|
||||
}))
|
||||
<-ch
|
||||
}
|
||||
|
||||
u.setWindowEventHandlers(window)
|
||||
|
||||
// Adjust the initial scale to 1.
|
||||
// https://developer.mozilla.org/en/docs/Mozilla/Mobile/Viewport_meta_tag
|
||||
meta := document.Call("createElement", "meta")
|
||||
meta.Set("name", "viewport")
|
||||
meta.Set("content", "width=device-width, initial-scale=1")
|
||||
document.Get("head").Call("appendChild", meta)
|
||||
|
||||
canvas = document.Call("createElement", "canvas")
|
||||
canvas.Set("width", 16)
|
||||
canvas.Set("height", 16)
|
||||
|
||||
document.Get("body").Call("appendChild", canvas)
|
||||
|
||||
htmlStyle := document.Get("documentElement").Get("style")
|
||||
htmlStyle.Set("height", "100%")
|
||||
htmlStyle.Set("margin", "0")
|
||||
htmlStyle.Set("padding", "0")
|
||||
|
||||
bodyStyle := document.Get("body").Get("style")
|
||||
bodyStyle.Set("backgroundColor", "#000")
|
||||
bodyStyle.Set("height", "100%")
|
||||
bodyStyle.Set("margin", "0")
|
||||
bodyStyle.Set("padding", "0")
|
||||
|
||||
canvasStyle := canvas.Get("style")
|
||||
canvasStyle.Set("width", "100%")
|
||||
canvasStyle.Set("height", "100%")
|
||||
canvasStyle.Set("margin", "0")
|
||||
canvasStyle.Set("padding", "0")
|
||||
|
||||
// Make the canvas focusable.
|
||||
canvas.Call("setAttribute", "tabindex", 1)
|
||||
canvas.Get("style").Set("outline", "none")
|
||||
|
||||
u.setCanvasEventHandlers(canvas)
|
||||
|
||||
// Pointer Lock
|
||||
document.Call("addEventListener", "pointerlockchange", js.FuncOf(func(this js.Value, args []js.Value) any {
|
||||
if document.Get("pointerLockElement").Truthy() {
|
||||
return nil
|
||||
}
|
||||
// Recover the state correctly when the pointer lock exits.
|
||||
|
||||
// A user can exit the pointer lock by pressing ESC. In this case, sync the cursor mode state.
|
||||
if u.cursorMode == CursorModeCaptured {
|
||||
u.recoverCursorMode()
|
||||
}
|
||||
u.recoverCursorPosition()
|
||||
return nil
|
||||
}))
|
||||
document.Call("addEventListener", "pointerlockerror", js.FuncOf(func(this js.Value, args []js.Value) any {
|
||||
js.Global().Get("console").Call("error", "pointerlockerror event is fired. 'sandbox=\"allow-pointer-lock\"' might be required at an iframe. This function on browsers must be called as a result of a gestural interaction or orientation change.")
|
||||
return nil
|
||||
}))
|
||||
document.Call("addEventListener", "fullscreenerror", js.FuncOf(func(this js.Value, args []js.Value) any {
|
||||
js.Global().Get("console").Call("error", "fullscreenerror event is fired. 'allow=\"fullscreen\"' or 'allowfullscreen' might be required at an iframe. This function on browsers must be called as a result of a gestural interaction or orientation change.")
|
||||
return nil
|
||||
}))
|
||||
document.Call("addEventListener", "webkitfullscreenerror", js.FuncOf(func(this js.Value, args []js.Value) any {
|
||||
js.Global().Get("console").Call("error", "webkitfullscreenerror event is fired. 'allow=\"fullscreen\"' or 'allowfullscreen' might be required at an iframe. This function on browsers must be called as a result of a gestural interaction or orientation change.")
|
||||
return nil
|
||||
}))
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (u *UserInterface) setWindowEventHandlers(v js.Value) {
|
||||
v.Call("addEventListener", "resize", js.FuncOf(func(this js.Value, args []js.Value) any {
|
||||
u.updateScreenSize()
|
||||
|
||||
// updateImpl can block. Use goroutine.
|
||||
// See https://pkg.go.dev/syscall/js#FuncOf.
|
||||
go func() {
|
||||
if err := u.updateImpl(true); err != nil {
|
||||
u.setError(err)
|
||||
return
|
||||
}
|
||||
}()
|
||||
return nil
|
||||
}))
|
||||
}
|
||||
|
||||
func (u *UserInterface) setCanvasEventHandlers(v js.Value) {
|
||||
// Keyboard
|
||||
v.Call("addEventListener", "keydown", js.FuncOf(func(this js.Value, args []js.Value) any {
|
||||
// Focus the canvas explicitly to activate tha game (#961).
|
||||
v.Call("focus")
|
||||
|
||||
e := args[0]
|
||||
e.Call("preventDefault")
|
||||
if err := u.updateInputFromEvent(e); err != nil {
|
||||
u.setError(err)
|
||||
return nil
|
||||
}
|
||||
return nil
|
||||
}))
|
||||
v.Call("addEventListener", "keyup", js.FuncOf(func(this js.Value, args []js.Value) any {
|
||||
e := args[0]
|
||||
e.Call("preventDefault")
|
||||
if err := u.updateInputFromEvent(e); err != nil {
|
||||
u.setError(err)
|
||||
return nil
|
||||
}
|
||||
return nil
|
||||
}))
|
||||
|
||||
// Mouse
|
||||
v.Call("addEventListener", "mousedown", js.FuncOf(func(this js.Value, args []js.Value) any {
|
||||
// Focus the canvas explicitly to activate tha game (#961).
|
||||
v.Call("focus")
|
||||
|
||||
e := args[0]
|
||||
e.Call("preventDefault")
|
||||
if err := u.updateInputFromEvent(e); err != nil {
|
||||
u.setError(err)
|
||||
return nil
|
||||
}
|
||||
return nil
|
||||
}))
|
||||
v.Call("addEventListener", "mouseup", js.FuncOf(func(this js.Value, args []js.Value) any {
|
||||
e := args[0]
|
||||
e.Call("preventDefault")
|
||||
if err := u.updateInputFromEvent(e); err != nil {
|
||||
u.setError(err)
|
||||
return nil
|
||||
}
|
||||
return nil
|
||||
}))
|
||||
v.Call("addEventListener", "mousemove", js.FuncOf(func(this js.Value, args []js.Value) any {
|
||||
e := args[0]
|
||||
e.Call("preventDefault")
|
||||
if err := u.updateInputFromEvent(e); err != nil {
|
||||
u.setError(err)
|
||||
return nil
|
||||
}
|
||||
return nil
|
||||
}))
|
||||
v.Call("addEventListener", "wheel", js.FuncOf(func(this js.Value, args []js.Value) any {
|
||||
e := args[0]
|
||||
e.Call("preventDefault")
|
||||
if err := u.updateInputFromEvent(e); err != nil {
|
||||
u.setError(err)
|
||||
return nil
|
||||
}
|
||||
return nil
|
||||
}))
|
||||
|
||||
// Touch
|
||||
v.Call("addEventListener", "touchstart", js.FuncOf(func(this js.Value, args []js.Value) any {
|
||||
// Focus the canvas explicitly to activate tha game (#961).
|
||||
v.Call("focus")
|
||||
|
||||
e := args[0]
|
||||
e.Call("preventDefault")
|
||||
if err := u.updateInputFromEvent(e); err != nil {
|
||||
u.setError(err)
|
||||
return nil
|
||||
}
|
||||
return nil
|
||||
}))
|
||||
v.Call("addEventListener", "touchend", js.FuncOf(func(this js.Value, args []js.Value) any {
|
||||
e := args[0]
|
||||
e.Call("preventDefault")
|
||||
if err := u.updateInputFromEvent(e); err != nil {
|
||||
u.setError(err)
|
||||
return nil
|
||||
}
|
||||
return nil
|
||||
}))
|
||||
v.Call("addEventListener", "touchmove", js.FuncOf(func(this js.Value, args []js.Value) any {
|
||||
e := args[0]
|
||||
e.Call("preventDefault")
|
||||
if err := u.updateInputFromEvent(e); err != nil {
|
||||
u.setError(err)
|
||||
return nil
|
||||
}
|
||||
return nil
|
||||
}))
|
||||
|
||||
// Context menu
|
||||
v.Call("addEventListener", "contextmenu", js.FuncOf(func(this js.Value, args []js.Value) any {
|
||||
e := args[0]
|
||||
e.Call("preventDefault")
|
||||
return nil
|
||||
}))
|
||||
|
||||
// Context
|
||||
v.Call("addEventListener", "webglcontextlost", js.FuncOf(func(this js.Value, args []js.Value) any {
|
||||
e := args[0]
|
||||
e.Call("preventDefault")
|
||||
window.Get("location").Call("reload")
|
||||
return nil
|
||||
}))
|
||||
|
||||
// Drop
|
||||
v.Call("addEventListener", "dragover", js.FuncOf(func(this js.Value, args []js.Value) any {
|
||||
e := args[0]
|
||||
e.Call("preventDefault")
|
||||
return nil
|
||||
}))
|
||||
v.Call("addEventListener", "drop", js.FuncOf(func(this js.Value, args []js.Value) any {
|
||||
e := args[0]
|
||||
e.Call("preventDefault")
|
||||
data := e.Get("dataTransfer")
|
||||
if !data.Truthy() {
|
||||
return nil
|
||||
}
|
||||
|
||||
go u.appendDroppedFiles(data)
|
||||
return nil
|
||||
}))
|
||||
|
||||
// Blur
|
||||
v.Call("addEventListener", "blur", js.FuncOf(func(this js.Value, args []js.Value) any {
|
||||
u.inputState.resetForBlur()
|
||||
return nil
|
||||
}))
|
||||
}
|
||||
|
||||
func (u *UserInterface) appendDroppedFiles(data js.Value) {
|
||||
u.dropFileM.Lock()
|
||||
defer u.dropFileM.Unlock()
|
||||
items := data.Get("items")
|
||||
|
||||
for i := 0; i < items.Length(); i++ {
|
||||
kind := items.Index(i).Get("kind").String()
|
||||
switch kind {
|
||||
case "file":
|
||||
fs := items.Index(i).Call("webkitGetAsEntry").Get("filesystem").Get("root")
|
||||
u.inputState.DroppedFiles = file.NewFileEntryFS(fs)
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (u *UserInterface) forceUpdateOnMinimumFPSMode() {
|
||||
if u.fpsMode != FPSModeVsyncOffMinimum {
|
||||
return
|
||||
}
|
||||
|
||||
// updateImpl can block. Use goroutine.
|
||||
// See https://pkg.go.dev/syscall/js#FuncOf.
|
||||
go func() {
|
||||
if err := u.updateImpl(true); err != nil {
|
||||
u.setError(err)
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
func (u *UserInterface) initOnMainThread(options *RunOptions) error {
|
||||
if !options.InitUnfocused && window.Truthy() {
|
||||
// Do not focus the canvas when the current document is in an iframe.
|
||||
// Otherwise, the parent page tries to focus the iframe on every loading, which is annoying (#1373).
|
||||
isInIframe := !window.Get("location").Equal(window.Get("parent").Get("location"))
|
||||
if !isInIframe {
|
||||
canvas.Call("focus")
|
||||
}
|
||||
}
|
||||
|
||||
g, lib, err := newGraphicsDriver(&graphicsDriverCreatorImpl{
|
||||
canvas: canvas,
|
||||
}, options.GraphicsLibrary)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
u.graphicsDriver = g
|
||||
u.setGraphicsLibrary(lib)
|
||||
|
||||
if bodyStyle := document.Get("body").Get("style"); options.ScreenTransparent {
|
||||
bodyStyle.Set("backgroundColor", "transparent")
|
||||
} else {
|
||||
bodyStyle.Set("backgroundColor", "#000")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (u *UserInterface) updateScreenSize() {
|
||||
if document.Truthy() {
|
||||
body := document.Get("body")
|
||||
f := theMonitor.DeviceScaleFactor()
|
||||
bw := int(body.Get("clientWidth").Float() * f)
|
||||
bh := int(body.Get("clientHeight").Float() * f)
|
||||
canvas.Set("width", bw)
|
||||
canvas.Set("height", bh)
|
||||
}
|
||||
}
|
||||
|
||||
func (u *UserInterface) readInputState(inputState *InputState) {
|
||||
u.inputState.copyAndReset(inputState)
|
||||
u.keyboardLayoutMap = js.Value{}
|
||||
}
|
||||
|
||||
func (u *UserInterface) Window() Window {
|
||||
return &nullWindow{}
|
||||
}
|
||||
|
||||
type Monitor struct {
|
||||
deviceScaleFactor float64
|
||||
}
|
||||
|
||||
var theMonitor = &Monitor{}
|
||||
|
||||
func (m *Monitor) Name() string {
|
||||
return ""
|
||||
}
|
||||
|
||||
func (m *Monitor) DeviceScaleFactor() float64 {
|
||||
if m.deviceScaleFactor != 0 {
|
||||
return m.deviceScaleFactor
|
||||
}
|
||||
|
||||
ratio := window.Get("devicePixelRatio").Float()
|
||||
if ratio == 0 {
|
||||
ratio = 1
|
||||
}
|
||||
m.deviceScaleFactor = ratio
|
||||
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)
|
||||
}
|
||||
|
||||
func (u *UserInterface) Monitor() *Monitor {
|
||||
return theMonitor
|
||||
}
|
||||
|
||||
func (u *UserInterface) updateIconIfNeeded() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func IsScreenTransparentAvailable() bool {
|
||||
return true
|
||||
}
|
||||
|
||||
func dipToNativePixels(x float64, scale float64) float64 {
|
||||
return x
|
||||
}
|
||||
+202
@@ -0,0 +1,202 @@
|
||||
// Copyright 2016 Hajime Hoshi
|
||||
//
|
||||
// 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 (freebsd || (linux && !android) || netbsd || openbsd) && !nintendosdk && !playstation5
|
||||
|
||||
package ui
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"runtime"
|
||||
|
||||
"github.com/jezek/xgb"
|
||||
"github.com/jezek/xgb/randr"
|
||||
"github.com/jezek/xgb/xproto"
|
||||
|
||||
"github.com/hajimehoshi/ebiten/v2/internal/glfw"
|
||||
"github.com/hajimehoshi/ebiten/v2/internal/graphicsdriver"
|
||||
"github.com/hajimehoshi/ebiten/v2/internal/graphicsdriver/opengl"
|
||||
)
|
||||
|
||||
func (u *UserInterface) initializePlatform() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
type graphicsDriverCreatorImpl struct {
|
||||
transparent bool
|
||||
}
|
||||
|
||||
func (g *graphicsDriverCreatorImpl) newAuto() (graphicsdriver.Graphics, GraphicsLibrary, error) {
|
||||
graphics, err := g.newOpenGL()
|
||||
return graphics, GraphicsLibraryOpenGL, err
|
||||
}
|
||||
|
||||
func (*graphicsDriverCreatorImpl) newOpenGL() (graphicsdriver.Graphics, error) {
|
||||
return opengl.NewGraphics()
|
||||
}
|
||||
|
||||
func (*graphicsDriverCreatorImpl) newDirectX() (graphicsdriver.Graphics, error) {
|
||||
return nil, errors.New("ui: DirectX is not supported in this environment")
|
||||
}
|
||||
|
||||
func (*graphicsDriverCreatorImpl) newMetal() (graphicsdriver.Graphics, error) {
|
||||
return nil, errors.New("ui: Metal is not supported in this environment")
|
||||
}
|
||||
|
||||
func (*graphicsDriverCreatorImpl) newPlayStation5() (graphicsdriver.Graphics, error) {
|
||||
return nil, errors.New("ui: PlayStation 5 is not supported in this environment")
|
||||
}
|
||||
|
||||
// glfwMonitorSizeInGLFWPixels must be called from the main thread.
|
||||
func glfwMonitorSizeInGLFWPixels(m *glfw.Monitor) (int, int, error) {
|
||||
vm, err := m.GetVideoMode()
|
||||
if err != nil {
|
||||
return 0, 0, err
|
||||
}
|
||||
physWidth, physHeight := vm.Width, vm.Height
|
||||
|
||||
// TODO: if glfw/glfw#1961 gets fixed, this function may need revising.
|
||||
// In case GLFW decides to switch to returning logical pixels, we can just return 1.
|
||||
|
||||
// Note: GLFW currently returns physical pixel sizes,
|
||||
// but we need to predict the window system-side size of the fullscreen window
|
||||
// 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()
|
||||
if err != nil {
|
||||
// No X11 connection?
|
||||
// Assume we're on pure Wayland then.
|
||||
// GLFW/Wayland shouldn't be having this issue.
|
||||
return physWidth, physHeight, nil
|
||||
}
|
||||
defer xconn.Close()
|
||||
|
||||
if err := randr.Init(xconn); err != nil {
|
||||
// No RANDR extension? No problem.
|
||||
return physWidth, physHeight, nil
|
||||
}
|
||||
|
||||
root := xproto.Setup(xconn).DefaultScreen(xconn).Root
|
||||
res, err := randr.GetScreenResourcesCurrent(xconn, root).Reply()
|
||||
if err != nil {
|
||||
// Likely means RANDR is not working. No problem.
|
||||
return physWidth, physHeight, nil
|
||||
}
|
||||
|
||||
monitorX, monitorY, err := m.GetPos()
|
||||
if err != nil {
|
||||
// TODO: Is it OK to ignore this error?
|
||||
return physWidth, physHeight, nil
|
||||
}
|
||||
|
||||
for _, crtc := range res.Crtcs[:res.NumCrtcs] {
|
||||
info, err := randr.GetCrtcInfo(xconn, crtc, res.ConfigTimestamp).Reply()
|
||||
if err != nil {
|
||||
// This Crtc is bad. Maybe just got disconnected?
|
||||
continue
|
||||
}
|
||||
if info.NumOutputs == 0 {
|
||||
// This Crtc is not connected to any output.
|
||||
// In other words, a disabled monitor.
|
||||
continue
|
||||
}
|
||||
if int(info.X) == monitorX && int(info.Y) == monitorY {
|
||||
return int(info.Width), int(info.Height), nil
|
||||
}
|
||||
}
|
||||
|
||||
// Monitor not known to XRandR. Weird.
|
||||
return physWidth, physHeight, nil
|
||||
}
|
||||
|
||||
func dipFromGLFWPixel(x float64, deviceScaleFactor float64) float64 {
|
||||
return x / deviceScaleFactor
|
||||
}
|
||||
|
||||
func dipToGLFWPixel(x float64, deviceScaleFactor float64) float64 {
|
||||
return x * deviceScaleFactor
|
||||
}
|
||||
|
||||
func (u *UserInterface) adjustWindowPosition(x, y int, monitor *Monitor) (int, int) {
|
||||
return x, y
|
||||
}
|
||||
|
||||
func initialMonitorByOS() (*Monitor, error) {
|
||||
xconn, err := xgb.NewConn()
|
||||
if err != nil {
|
||||
// Assume we're on pure Wayland then.
|
||||
return nil, nil
|
||||
}
|
||||
defer xconn.Close()
|
||||
|
||||
root := xproto.Setup(xconn).DefaultScreen(xconn).Root
|
||||
rep, err := xproto.QueryPointer(xconn, root).Reply()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
x, y := int(rep.RootX), int(rep.RootY)
|
||||
|
||||
// Find the monitor including the cursor.
|
||||
return theMonitors.monitorFromPosition(x, y), nil
|
||||
}
|
||||
|
||||
func monitorFromWindowByOS(_ *glfw.Window) (*Monitor, error) {
|
||||
// TODO: Implement this correctly. (#1119).
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
func (u *UserInterface) nativeWindow() (uintptr, error) {
|
||||
// TODO: Implement this.
|
||||
return 0, nil
|
||||
}
|
||||
|
||||
func (u *UserInterface) isNativeFullscreen() (bool, error) {
|
||||
return false, nil
|
||||
}
|
||||
|
||||
func (u *UserInterface) isNativeFullscreenAvailable() bool {
|
||||
return false
|
||||
}
|
||||
|
||||
func (u *UserInterface) setNativeFullscreen(fullscreen bool) error {
|
||||
panic(fmt.Sprintf("ui: setNativeFullscreen is not implemented in this environment: %s", runtime.GOOS))
|
||||
}
|
||||
|
||||
func (u *UserInterface) adjustViewSizeAfterFullscreen() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (u *UserInterface) setWindowResizingModeForOS(mode WindowResizingMode) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func initializeWindowAfterCreation(w *glfw.Window) error {
|
||||
// Show the window once before getting the position of the window.
|
||||
// On Linux/Unix, the window position is not reliable before showing.
|
||||
if err := w.Show(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Hiding the window makes the position unreliable again. Do not call w.Hide() here (#1829)
|
||||
// Calling Hide is problematic especially on XWayland and/or Sway.
|
||||
// Apparently the window state is inconsistent just after the window is created, but we are not sure.
|
||||
// For more details, see the discussion in #1829.
|
||||
return nil
|
||||
}
|
||||
|
||||
func (u *UserInterface) skipTaskbar() error {
|
||||
return nil
|
||||
}
|
||||
+328
@@ -0,0 +1,328 @@
|
||||
// Copyright 2016 Hajime Hoshi
|
||||
//
|
||||
// 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 android || ios
|
||||
|
||||
package ui
|
||||
|
||||
import (
|
||||
stdcontext "context"
|
||||
"fmt"
|
||||
"runtime"
|
||||
"runtime/debug"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
|
||||
"github.com/hajimehoshi/ebiten/v2/internal/gamepad"
|
||||
"github.com/hajimehoshi/ebiten/v2/internal/graphicscommand"
|
||||
"github.com/hajimehoshi/ebiten/v2/internal/graphicsdriver"
|
||||
"github.com/hajimehoshi/ebiten/v2/internal/hook"
|
||||
)
|
||||
|
||||
var (
|
||||
// renderCh receives when updating starts.
|
||||
renderCh = make(chan struct{})
|
||||
|
||||
// renderEndCh receives when updating finishes.
|
||||
renderEndCh = make(chan struct{})
|
||||
)
|
||||
|
||||
func (u *UserInterface) init() error {
|
||||
u.userInterfaceImpl = userInterfaceImpl{
|
||||
foreground: 1,
|
||||
graphicsLibraryInitCh: make(chan struct{}),
|
||||
errCh: make(chan error),
|
||||
|
||||
// Give a default outside size so that the game can start without initializing them.
|
||||
outsideWidth: 640,
|
||||
outsideHeight: 480,
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Update is called from mobile/ebitenmobileview.
|
||||
//
|
||||
// Update must be called on the rendering thread.
|
||||
func (u *UserInterface) Update() error {
|
||||
select {
|
||||
case err := <-u.errCh:
|
||||
return err
|
||||
default:
|
||||
}
|
||||
|
||||
if !u.IsFocused() {
|
||||
return nil
|
||||
}
|
||||
|
||||
if err := gamepad.Update(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
ctx, cancel := stdcontext.WithCancel(stdcontext.Background())
|
||||
defer cancel()
|
||||
|
||||
renderCh <- struct{}{}
|
||||
go func() {
|
||||
<-renderEndCh
|
||||
cancel()
|
||||
}()
|
||||
|
||||
graphicscommand.LoopRenderThread(ctx)
|
||||
return nil
|
||||
}
|
||||
|
||||
type userInterfaceImpl struct {
|
||||
graphicsDriver graphicsdriver.Graphics
|
||||
graphicsLibraryInitCh chan struct{}
|
||||
|
||||
outsideWidth float64
|
||||
outsideHeight float64
|
||||
|
||||
foreground int32
|
||||
errCh chan error
|
||||
|
||||
context *context
|
||||
|
||||
inputState InputState
|
||||
touches []TouchForInput
|
||||
|
||||
fpsMode int32
|
||||
renderRequester RenderRequester
|
||||
|
||||
m sync.RWMutex
|
||||
}
|
||||
|
||||
func (u *UserInterface) SetForeground(foreground bool) error {
|
||||
var v int32
|
||||
if foreground {
|
||||
v = 1
|
||||
}
|
||||
atomic.StoreInt32(&u.foreground, v)
|
||||
|
||||
if foreground {
|
||||
return hook.ResumeAudio()
|
||||
} else {
|
||||
return hook.SuspendAudio()
|
||||
}
|
||||
}
|
||||
|
||||
func (u *UserInterface) Run(game Game, options *RunOptions) error {
|
||||
return fmt.Errorf("internal/ui: Run is not implemented for GOOS=%s", runtime.GOOS)
|
||||
}
|
||||
|
||||
func (u *UserInterface) RunWithoutMainLoop(game Game, options *RunOptions) {
|
||||
go func() {
|
||||
if err := u.runMobile(game, options); err != nil {
|
||||
u.errCh <- err
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
func (u *UserInterface) runMobile(game Game, options *RunOptions) (err error) {
|
||||
// Convert the panic to a regular error so that Java/Objective-C layer can treat this easily e.g., for
|
||||
// Crashlytics. A panic is treated as SIGABRT, and there is no way to handle this on Java/Objective-C layer
|
||||
// unfortunately.
|
||||
// TODO: Panic on other goroutines cannot be handled here.
|
||||
defer func() {
|
||||
if r := recover(); r != nil {
|
||||
err = fmt.Errorf("%v\n%s", r, string(debug.Stack()))
|
||||
}
|
||||
}()
|
||||
|
||||
graphicscommand.SetOSThreadAsRenderThread()
|
||||
|
||||
u.setRunning(true)
|
||||
defer u.setRunning(false)
|
||||
|
||||
u.context = newContext(game)
|
||||
|
||||
g, lib, err := newGraphicsDriver(&graphicsDriverCreatorImpl{}, options.GraphicsLibrary)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
u.graphicsDriver = g
|
||||
u.setGraphicsLibrary(lib)
|
||||
close(u.graphicsLibraryInitCh)
|
||||
|
||||
for {
|
||||
if err := u.update(); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// outsideSize must be called on the same goroutine as update().
|
||||
func (u *UserInterface) outsideSize() (float64, float64) {
|
||||
u.m.RLock()
|
||||
defer u.m.RUnlock()
|
||||
|
||||
return u.outsideWidth, u.outsideHeight
|
||||
}
|
||||
|
||||
func (u *UserInterface) update() error {
|
||||
<-renderCh
|
||||
defer func() {
|
||||
renderEndCh <- struct{}{}
|
||||
}()
|
||||
|
||||
w, h := u.outsideSize()
|
||||
if err := u.context.updateFrame(u.graphicsDriver, w, h, theMonitor.DeviceScaleFactor(), u); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// SetOutsideSize is called from mobile/ebitenmobileview.
|
||||
//
|
||||
// SetOutsideSize is concurrent safe.
|
||||
func (u *UserInterface) SetOutsideSize(outsideWidth, outsideHeight float64) {
|
||||
u.m.Lock()
|
||||
defer u.m.Unlock()
|
||||
if u.outsideWidth != outsideWidth || u.outsideHeight != outsideHeight {
|
||||
u.outsideWidth = outsideWidth
|
||||
u.outsideHeight = outsideHeight
|
||||
}
|
||||
}
|
||||
|
||||
func (u *UserInterface) CursorMode() CursorMode {
|
||||
return CursorModeHidden
|
||||
}
|
||||
|
||||
func (u *UserInterface) SetCursorMode(mode CursorMode) {
|
||||
// Do nothing
|
||||
}
|
||||
|
||||
func (u *UserInterface) CursorShape() CursorShape {
|
||||
return CursorShapeDefault
|
||||
}
|
||||
|
||||
func (u *UserInterface) SetCursorShape(shape CursorShape) {
|
||||
// Do nothing
|
||||
}
|
||||
|
||||
func (u *UserInterface) IsFullscreen() bool {
|
||||
return false
|
||||
}
|
||||
|
||||
func (u *UserInterface) SetFullscreen(fullscreen bool) {
|
||||
// Do nothing
|
||||
}
|
||||
|
||||
func (u *UserInterface) IsFocused() bool {
|
||||
return atomic.LoadInt32(&u.foreground) != 0
|
||||
}
|
||||
|
||||
func (u *UserInterface) IsRunnableOnUnfocused() bool {
|
||||
return false
|
||||
}
|
||||
|
||||
func (u *UserInterface) SetRunnableOnUnfocused(runnableOnUnfocused bool) {
|
||||
// Do nothing
|
||||
}
|
||||
|
||||
func (u *UserInterface) FPSMode() FPSModeType {
|
||||
return FPSModeType(atomic.LoadInt32(&u.fpsMode))
|
||||
}
|
||||
|
||||
func (u *UserInterface) SetFPSMode(mode FPSModeType) {
|
||||
atomic.StoreInt32(&u.fpsMode, int32(mode))
|
||||
u.updateExplicitRenderingModeIfNeeded(mode)
|
||||
}
|
||||
|
||||
func (u *UserInterface) updateExplicitRenderingModeIfNeeded(fpsMode FPSModeType) {
|
||||
if u.renderRequester == nil {
|
||||
return
|
||||
}
|
||||
u.renderRequester.SetExplicitRenderingMode(fpsMode == FPSModeVsyncOffMinimum)
|
||||
}
|
||||
|
||||
func (u *UserInterface) readInputState(inputState *InputState) {
|
||||
u.m.Lock()
|
||||
defer u.m.Unlock()
|
||||
u.inputState.copyAndReset(inputState)
|
||||
}
|
||||
|
||||
func (u *UserInterface) Window() Window {
|
||||
return &nullWindow{}
|
||||
}
|
||||
|
||||
type Monitor struct {
|
||||
deviceScaleFactor float64
|
||||
deviceScaleFactorOnce sync.Once
|
||||
|
||||
m sync.Mutex
|
||||
}
|
||||
|
||||
var theMonitor = &Monitor{}
|
||||
|
||||
func (m *Monitor) Name() string {
|
||||
return ""
|
||||
}
|
||||
|
||||
func (m *Monitor) DeviceScaleFactor() float64 {
|
||||
m.m.Lock()
|
||||
defer m.m.Unlock()
|
||||
|
||||
// The device scale factor can be obtained after the main function starts, especially on Android.
|
||||
// Initialize this lazily.
|
||||
m.deviceScaleFactorOnce.Do(func() {
|
||||
// Assume that the device scale factor never changes on mobiles.
|
||||
m.deviceScaleFactor = deviceScaleFactorImpl()
|
||||
})
|
||||
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)
|
||||
}
|
||||
|
||||
func (u *UserInterface) Monitor() *Monitor {
|
||||
return theMonitor
|
||||
}
|
||||
|
||||
func (u *UserInterface) UpdateInput(keys map[Key]struct{}, runes []rune, touches []TouchForInput) {
|
||||
u.updateInputStateFromOutside(keys, runes, touches)
|
||||
if FPSModeType(atomic.LoadInt32(&u.fpsMode)) == FPSModeVsyncOffMinimum {
|
||||
u.renderRequester.RequestRenderIfNeeded()
|
||||
}
|
||||
}
|
||||
|
||||
type RenderRequester interface {
|
||||
SetExplicitRenderingMode(explicitRendering bool)
|
||||
RequestRenderIfNeeded()
|
||||
}
|
||||
|
||||
func (u *UserInterface) SetRenderRequester(renderRequester RenderRequester) {
|
||||
u.renderRequester = renderRequester
|
||||
u.updateExplicitRenderingModeIfNeeded(FPSModeType(atomic.LoadInt32(&u.fpsMode)))
|
||||
}
|
||||
|
||||
func (u *UserInterface) ScheduleFrame() {
|
||||
if u.renderRequester != nil && FPSModeType(atomic.LoadInt32(&u.fpsMode)) == FPSModeVsyncOffMinimum {
|
||||
u.renderRequester.RequestRenderIfNeeded()
|
||||
}
|
||||
}
|
||||
|
||||
func (u *UserInterface) updateIconIfNeeded() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func IsScreenTransparentAvailable() bool {
|
||||
return false
|
||||
}
|
||||
+187
@@ -0,0 +1,187 @@
|
||||
// Copyright 2021 The Ebiten 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 nintendosdk
|
||||
|
||||
package ui
|
||||
|
||||
// #include "init_nintendosdk.h"
|
||||
// #include "input_nintendosdk.h"
|
||||
import "C"
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"runtime"
|
||||
"sync"
|
||||
|
||||
"github.com/hajimehoshi/ebiten/v2/internal/graphicsdriver"
|
||||
"github.com/hajimehoshi/ebiten/v2/internal/graphicsdriver/opengl"
|
||||
)
|
||||
|
||||
type graphicsDriverCreatorImpl struct {
|
||||
nativeWindow C.NativeWindowType
|
||||
}
|
||||
|
||||
func (g *graphicsDriverCreatorImpl) newAuto() (graphicsdriver.Graphics, GraphicsLibrary, error) {
|
||||
graphics, err := g.newOpenGL()
|
||||
return graphics, GraphicsLibraryOpenGL, err
|
||||
}
|
||||
|
||||
func (g *graphicsDriverCreatorImpl) newOpenGL() (graphicsdriver.Graphics, error) {
|
||||
return opengl.NewGraphics(uintptr(g.nativeWindow))
|
||||
}
|
||||
|
||||
func (*graphicsDriverCreatorImpl) newDirectX() (graphicsdriver.Graphics, error) {
|
||||
return nil, errors.New("ui: DirectX is not supported in this environment")
|
||||
}
|
||||
|
||||
func (*graphicsDriverCreatorImpl) newMetal() (graphicsdriver.Graphics, error) {
|
||||
return nil, errors.New("ui: Metal is not supported in this environment")
|
||||
}
|
||||
|
||||
func (*graphicsDriverCreatorImpl) newPlayStation5() (graphicsdriver.Graphics, error) {
|
||||
return nil, errors.New("ui: PlayStation 5 is not supported in this environment")
|
||||
}
|
||||
|
||||
func init() {
|
||||
runtime.LockOSThread()
|
||||
}
|
||||
|
||||
type userInterfaceImpl struct {
|
||||
graphicsDriver graphicsdriver.Graphics
|
||||
|
||||
context *context
|
||||
inputState InputState
|
||||
nativeTouches []C.struct_Touch
|
||||
|
||||
m sync.Mutex
|
||||
}
|
||||
|
||||
func (u *UserInterface) init() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (u *UserInterface) initOnMainThread(options *RunOptions) error {
|
||||
n := C.ebitengine_Initialize()
|
||||
g, lib, err := newGraphicsDriver(&graphicsDriverCreatorImpl{
|
||||
nativeWindow: n,
|
||||
}, options.GraphicsLibrary)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
u.graphicsDriver = g
|
||||
u.setGraphicsLibrary(lib)
|
||||
|
||||
initializeProfiler()
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (u *UserInterface) loopGame() error {
|
||||
for {
|
||||
recordProfilerHeartbeat()
|
||||
|
||||
if err := u.context.updateFrame(u.graphicsDriver, float64(C.kScreenWidth), float64(C.kScreenHeight), theMonitor.DeviceScaleFactor(), u); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (*UserInterface) IsFocused() bool {
|
||||
return true
|
||||
}
|
||||
|
||||
func (u *UserInterface) readInputState(inputState *InputState) {
|
||||
u.m.Lock()
|
||||
defer u.m.Unlock()
|
||||
u.inputState.copyAndReset(inputState)
|
||||
}
|
||||
|
||||
func (*UserInterface) CursorMode() CursorMode {
|
||||
return CursorModeHidden
|
||||
}
|
||||
|
||||
func (*UserInterface) SetCursorMode(mode CursorMode) {
|
||||
}
|
||||
|
||||
func (*UserInterface) CursorShape() CursorShape {
|
||||
return CursorShapeDefault
|
||||
}
|
||||
|
||||
func (*UserInterface) SetCursorShape(shape CursorShape) {
|
||||
}
|
||||
|
||||
func (*UserInterface) IsFullscreen() bool {
|
||||
return false
|
||||
}
|
||||
|
||||
func (*UserInterface) SetFullscreen(fullscreen bool) {
|
||||
}
|
||||
|
||||
func (*UserInterface) IsRunnableOnUnfocused() bool {
|
||||
return false
|
||||
}
|
||||
|
||||
func (*UserInterface) SetRunnableOnUnfocused(runnableOnUnfocused bool) {
|
||||
}
|
||||
|
||||
func (*UserInterface) FPSMode() FPSModeType {
|
||||
return FPSModeVsyncOn
|
||||
}
|
||||
|
||||
func (*UserInterface) SetFPSMode(mode FPSModeType) {
|
||||
}
|
||||
|
||||
func (*UserInterface) ScheduleFrame() {
|
||||
}
|
||||
|
||||
func (*UserInterface) Window() Window {
|
||||
return &nullWindow{}
|
||||
}
|
||||
|
||||
func (u *UserInterface) updateIconIfNeeded() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
type Monitor struct{}
|
||||
|
||||
var theMonitor = &Monitor{}
|
||||
|
||||
func (m *Monitor) Name() string {
|
||||
return ""
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
|
||||
func (u *UserInterface) Monitor() *Monitor {
|
||||
return theMonitor
|
||||
}
|
||||
|
||||
func IsScreenTransparentAvailable() bool {
|
||||
return false
|
||||
}
|
||||
|
||||
func dipToNativePixels(x float64, scale float64) float64 {
|
||||
return x
|
||||
}
|
||||
+180
@@ -0,0 +1,180 @@
|
||||
// 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.
|
||||
|
||||
//go:build playstation5
|
||||
|
||||
package ui
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"image"
|
||||
"runtime"
|
||||
|
||||
"github.com/hajimehoshi/ebiten/v2/internal/graphicsdriver"
|
||||
"github.com/hajimehoshi/ebiten/v2/internal/graphicsdriver/playstation5"
|
||||
)
|
||||
|
||||
type graphicsDriverCreatorImpl struct{}
|
||||
|
||||
func (g *graphicsDriverCreatorImpl) newAuto() (graphicsdriver.Graphics, GraphicsLibrary, error) {
|
||||
graphics, err := g.newPlayStation5()
|
||||
return graphics, GraphicsLibraryPlayStation5, err
|
||||
}
|
||||
|
||||
func (*graphicsDriverCreatorImpl) newOpenGL() (graphicsdriver.Graphics, error) {
|
||||
return nil, errors.New("ui: OpenGL is not supported in this environment")
|
||||
}
|
||||
|
||||
func (*graphicsDriverCreatorImpl) newDirectX() (graphicsdriver.Graphics, error) {
|
||||
return nil, errors.New("ui: DirectX is not supported in this environment")
|
||||
}
|
||||
|
||||
func (*graphicsDriverCreatorImpl) newMetal() (graphicsdriver.Graphics, error) {
|
||||
return nil, errors.New("ui: Metal is not supported in this environment")
|
||||
}
|
||||
|
||||
func (*graphicsDriverCreatorImpl) newPlayStation5() (graphicsdriver.Graphics, error) {
|
||||
return playstation5.NewGraphics()
|
||||
}
|
||||
|
||||
const (
|
||||
// TODO: Get this value from the SDK.
|
||||
screenWidth = 3840
|
||||
screenHeight = 2160
|
||||
)
|
||||
|
||||
func init() {
|
||||
runtime.LockOSThread()
|
||||
}
|
||||
|
||||
type userInterfaceImpl struct {
|
||||
graphicsDriver graphicsdriver.Graphics
|
||||
|
||||
context *context
|
||||
}
|
||||
|
||||
func (u *UserInterface) init() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (u *UserInterface) initOnMainThread(options *RunOptions) error {
|
||||
g, lib, err := newGraphicsDriver(&graphicsDriverCreatorImpl{}, options.GraphicsLibrary)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
u.graphicsDriver = g
|
||||
u.setGraphicsLibrary(lib)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (u *UserInterface) loopGame() error {
|
||||
for {
|
||||
if err := u.context.updateFrame(u.graphicsDriver, screenWidth, screenHeight, theMonitor.DeviceScaleFactor(), u); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (*UserInterface) IsFocused() bool {
|
||||
return true
|
||||
}
|
||||
|
||||
func (u *UserInterface) readInputState(inputState *InputState) {
|
||||
// TODO: Implement this.
|
||||
}
|
||||
|
||||
func (*UserInterface) CursorMode() CursorMode {
|
||||
return CursorModeHidden
|
||||
}
|
||||
|
||||
func (*UserInterface) SetCursorMode(mode CursorMode) {
|
||||
}
|
||||
|
||||
func (*UserInterface) CursorShape() CursorShape {
|
||||
return CursorShapeDefault
|
||||
}
|
||||
|
||||
func (*UserInterface) SetCursorShape(shape CursorShape) {
|
||||
}
|
||||
|
||||
func (*UserInterface) IsFullscreen() bool {
|
||||
return false
|
||||
}
|
||||
|
||||
func (*UserInterface) SetFullscreen(fullscreen bool) {
|
||||
}
|
||||
|
||||
func (*UserInterface) IsRunnableOnUnfocused() bool {
|
||||
return false
|
||||
}
|
||||
|
||||
func (*UserInterface) SetRunnableOnUnfocused(runnableOnUnfocused bool) {
|
||||
}
|
||||
|
||||
func (*UserInterface) FPSMode() FPSModeType {
|
||||
return FPSModeVsyncOn
|
||||
}
|
||||
|
||||
func (*UserInterface) SetFPSMode(mode FPSModeType) {
|
||||
}
|
||||
|
||||
func (*UserInterface) ScheduleFrame() {
|
||||
}
|
||||
|
||||
func (*UserInterface) Window() Window {
|
||||
return &nullWindow{}
|
||||
}
|
||||
|
||||
func (u *UserInterface) updateIconIfNeeded() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
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 (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)
|
||||
}
|
||||
|
||||
func (u *UserInterface) Monitor() *Monitor {
|
||||
return theMonitor
|
||||
}
|
||||
|
||||
func IsScreenTransparentAvailable() bool {
|
||||
return false
|
||||
}
|
||||
|
||||
func dipToNativePixels(x float64, scale float64) float64 {
|
||||
return x
|
||||
}
|
||||
+254
@@ -0,0 +1,254 @@
|
||||
// Copyright 2016 Hajime Hoshi
|
||||
//
|
||||
// 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
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"runtime"
|
||||
"syscall"
|
||||
|
||||
"golang.org/x/sys/windows"
|
||||
|
||||
"github.com/hajimehoshi/ebiten/v2/internal/glfw"
|
||||
"github.com/hajimehoshi/ebiten/v2/internal/graphicsdriver"
|
||||
"github.com/hajimehoshi/ebiten/v2/internal/graphicsdriver/directx"
|
||||
"github.com/hajimehoshi/ebiten/v2/internal/graphicsdriver/opengl"
|
||||
"github.com/hajimehoshi/ebiten/v2/internal/microsoftgdk"
|
||||
"github.com/hajimehoshi/ebiten/v2/internal/winver"
|
||||
)
|
||||
|
||||
func (u *UserInterface) initializePlatform() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
type graphicsDriverCreatorImpl struct {
|
||||
transparent bool
|
||||
}
|
||||
|
||||
func (g *graphicsDriverCreatorImpl) newAuto() (graphicsdriver.Graphics, GraphicsLibrary, error) {
|
||||
var dxErr error
|
||||
var glErr error
|
||||
if winver.IsWindows10OrGreater() {
|
||||
d, err := g.newDirectX()
|
||||
if err == nil {
|
||||
return d, GraphicsLibraryDirectX, nil
|
||||
}
|
||||
dxErr = err
|
||||
|
||||
o, err := g.newOpenGL()
|
||||
if err == nil {
|
||||
return o, GraphicsLibraryOpenGL, nil
|
||||
}
|
||||
glErr = err
|
||||
} else {
|
||||
// Creating a swap chain on an older machine than Windows 10 might fail (#2613).
|
||||
// Prefer OpenGL to DirectX.
|
||||
o, err := g.newOpenGL()
|
||||
if err == nil {
|
||||
return o, GraphicsLibraryOpenGL, nil
|
||||
}
|
||||
glErr = err
|
||||
|
||||
// Initializing OpenGL can fail, though this is pretty rare.
|
||||
d, err := g.newDirectX()
|
||||
if err == nil {
|
||||
return d, GraphicsLibraryDirectX, nil
|
||||
}
|
||||
dxErr = err
|
||||
}
|
||||
|
||||
return nil, GraphicsLibraryUnknown, fmt.Errorf("ui: failed to choose graphics drivers: DirectX: %v, OpenGL: %v", dxErr, glErr)
|
||||
}
|
||||
|
||||
func (*graphicsDriverCreatorImpl) newOpenGL() (graphicsdriver.Graphics, error) {
|
||||
return opengl.NewGraphics()
|
||||
}
|
||||
|
||||
func (g *graphicsDriverCreatorImpl) newDirectX() (graphicsdriver.Graphics, error) {
|
||||
if g.transparent {
|
||||
return nil, errors.New("ui: DirectX is not available with a transparent window")
|
||||
}
|
||||
return directx.NewGraphics()
|
||||
}
|
||||
|
||||
func (*graphicsDriverCreatorImpl) newMetal() (graphicsdriver.Graphics, error) {
|
||||
return nil, errors.New("ui: Metal is not supported in this environment")
|
||||
}
|
||||
|
||||
func (*graphicsDriverCreatorImpl) newPlayStation5() (graphicsdriver.Graphics, error) {
|
||||
return nil, errors.New("ui: PlayStation 5 is not supported in this environment")
|
||||
}
|
||||
|
||||
// glfwMonitorSizeInGLFWPixels must be called from the main thread.
|
||||
func glfwMonitorSizeInGLFWPixels(m *glfw.Monitor) (int, int, error) {
|
||||
vm, err := m.GetVideoMode()
|
||||
if err != nil {
|
||||
return 0, 0, err
|
||||
}
|
||||
return vm.Width, vm.Height, nil
|
||||
}
|
||||
|
||||
func dipFromGLFWPixel(x float64, deviceScaleFactor float64) float64 {
|
||||
return x / deviceScaleFactor
|
||||
}
|
||||
|
||||
func dipToGLFWPixel(x float64, deviceScaleFactor float64) float64 {
|
||||
return x * deviceScaleFactor
|
||||
}
|
||||
|
||||
func (u *UserInterface) adjustWindowPosition(x, y int, monitor *Monitor) (int, int) {
|
||||
if microsoftgdk.IsXbox() {
|
||||
return x, y
|
||||
}
|
||||
|
||||
mx := monitor.boundsInGLFWPixels.Min.X
|
||||
my := monitor.boundsInGLFWPixels.Min.Y
|
||||
// As the video width/height might be wrong,
|
||||
// adjust x/y at least to enable to handle the window (#328)
|
||||
if x < mx {
|
||||
x = mx
|
||||
}
|
||||
t, err := _GetSystemMetrics(_SM_CYCAPTION)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
if y < my+int(t) {
|
||||
y = my + int(t)
|
||||
}
|
||||
return x, y
|
||||
}
|
||||
|
||||
func initialMonitorByOS() (*Monitor, error) {
|
||||
if microsoftgdk.IsXbox() {
|
||||
return theMonitors.primaryMonitor(), nil
|
||||
}
|
||||
|
||||
px, py, err := _GetCursorPos()
|
||||
if err != nil {
|
||||
if errors.Is(err, windows.ERROR_ACCESS_DENIED) {
|
||||
return nil, nil
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
x, y := int(px), int(py)
|
||||
|
||||
// Find the monitor including the cursor.
|
||||
return theMonitors.monitorFromPosition(x, y), nil
|
||||
}
|
||||
|
||||
func monitorFromWindowByOS(w *glfw.Window) (*Monitor, error) {
|
||||
if microsoftgdk.IsXbox() {
|
||||
return theMonitors.primaryMonitor(), nil
|
||||
}
|
||||
window, err := w.GetWin32Window()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return monitorFromWin32Window(window), nil
|
||||
}
|
||||
|
||||
func monitorFromWin32Window(w windows.HWND) *Monitor {
|
||||
// Get the current monitor by the window handle instead of the window position. It is because the window
|
||||
// position is not reliable in some cases e.g. when the window is put across multiple monitors.
|
||||
|
||||
m := _MonitorFromWindow(w, _MONITOR_DEFAULTTONEAREST)
|
||||
if m == 0 {
|
||||
// monitorFromWindow can return error on Wine. Ignore this.
|
||||
return nil
|
||||
}
|
||||
|
||||
mi, err := _GetMonitorInfoW(m)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
x, y := int(mi.rcMonitor.left), int(mi.rcMonitor.top)
|
||||
for _, m := range theMonitors.append(nil) {
|
||||
mx := m.boundsInGLFWPixels.Min.X
|
||||
my := m.boundsInGLFWPixels.Min.Y
|
||||
if mx == x && my == y {
|
||||
return m
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (u *UserInterface) nativeWindow() (uintptr, error) {
|
||||
w, err := u.window.GetWin32Window()
|
||||
return uintptr(w), err
|
||||
}
|
||||
|
||||
func (u *UserInterface) isNativeFullscreen() (bool, error) {
|
||||
return false, nil
|
||||
}
|
||||
|
||||
func (u *UserInterface) isNativeFullscreenAvailable() bool {
|
||||
return false
|
||||
}
|
||||
|
||||
func (u *UserInterface) setNativeFullscreen(fullscreen bool) error {
|
||||
panic(fmt.Sprintf("ui: setNativeFullscreen is not implemented in this environment: %s", runtime.GOOS))
|
||||
}
|
||||
|
||||
func (u *UserInterface) adjustViewSizeAfterFullscreen() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (u *UserInterface) setWindowResizingModeForOS(mode WindowResizingMode) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func initializeWindowAfterCreation(w *glfw.Window) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (u *UserInterface) skipTaskbar() error {
|
||||
// S_FALSE is returned when CoInitializeEx is nested. This is a successful case.
|
||||
if err := windows.CoInitializeEx(0, windows.COINIT_MULTITHREADED); err != nil && !errors.Is(err, syscall.Errno(windows.S_FALSE)) {
|
||||
return err
|
||||
}
|
||||
// CoUninitialize should be called even when CoInitializeEx returns S_FALSE.
|
||||
defer windows.CoUninitialize()
|
||||
|
||||
ptr, err := _CoCreateInstance(&_CLSID_TaskbarList, nil, _CLSCTX_SERVER, &_IID_ITaskbarList)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
t := (*_ITaskbarList)(ptr)
|
||||
defer t.Release()
|
||||
|
||||
w, err := u.window.GetWin32Window()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if err := t.DeleteTab(w); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func init() {
|
||||
if microsoftgdk.IsXbox() {
|
||||
// TimeBeginPeriod might not be defined in Xbox.
|
||||
return
|
||||
}
|
||||
// Use a better timer resolution (golang/go#44343).
|
||||
// An error is ignored. The application is still valid even if a higher resolution timer is not available.
|
||||
// TODO: This might not be necessary from Go 1.23.
|
||||
_ = windows.TimeBeginPeriod(1)
|
||||
}
|
||||
+130
@@ -0,0 +1,130 @@
|
||||
// Copyright 2022 The Ebiten 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
|
||||
|
||||
import (
|
||||
"image"
|
||||
)
|
||||
|
||||
type Window interface {
|
||||
IsDecorated() bool
|
||||
SetDecorated(decorated bool)
|
||||
ResizingMode() WindowResizingMode
|
||||
SetResizingMode(mode WindowResizingMode)
|
||||
SetMonitor(*Monitor)
|
||||
Position() (int, int)
|
||||
SetPosition(x, y int)
|
||||
Size() (int, int)
|
||||
SetSize(width, height int)
|
||||
SizeLimits() (minw, minh, maxw, maxh int)
|
||||
SetSizeLimits(minw, minh, maxw, maxh int)
|
||||
IsFloating() bool
|
||||
SetFloating(floating bool)
|
||||
Maximize()
|
||||
IsMaximized() bool
|
||||
Minimize()
|
||||
IsMinimized() bool
|
||||
SetIcon(iconImages []image.Image)
|
||||
SetTitle(title string)
|
||||
Restore()
|
||||
SetClosingHandled(handled bool)
|
||||
IsClosingHandled() bool
|
||||
SetMousePassthrough(enabled bool)
|
||||
IsMousePassthrough() bool
|
||||
}
|
||||
|
||||
type nullWindow struct{}
|
||||
|
||||
func (*nullWindow) IsDecorated() bool {
|
||||
return false
|
||||
}
|
||||
|
||||
func (*nullWindow) SetDecorated(decorated bool) {
|
||||
}
|
||||
|
||||
func (*nullWindow) ResizingMode() WindowResizingMode {
|
||||
return WindowResizingModeDisabled
|
||||
}
|
||||
|
||||
func (*nullWindow) SetResizingMode(mode WindowResizingMode) {
|
||||
}
|
||||
|
||||
func (*nullWindow) SetMonitor(monitor *Monitor) {
|
||||
}
|
||||
|
||||
func (*nullWindow) Position() (int, int) {
|
||||
return 0, 0
|
||||
}
|
||||
|
||||
func (*nullWindow) SetPosition(x, y int) {
|
||||
}
|
||||
|
||||
func (*nullWindow) Size() (int, int) {
|
||||
return 0, 0
|
||||
}
|
||||
|
||||
func (*nullWindow) SetSize(width, height int) {
|
||||
}
|
||||
|
||||
func (*nullWindow) SizeLimits() (minw, minh, maxw, maxh int) {
|
||||
return -1, -1, -1, -1
|
||||
}
|
||||
|
||||
func (*nullWindow) SetSizeLimits(minw, minh, maxw, maxh int) {
|
||||
}
|
||||
|
||||
func (*nullWindow) IsFloating() bool {
|
||||
return false
|
||||
}
|
||||
|
||||
func (*nullWindow) SetFloating(floating bool) {
|
||||
}
|
||||
|
||||
func (*nullWindow) Maximize() {
|
||||
}
|
||||
|
||||
func (*nullWindow) IsMaximized() bool {
|
||||
return false
|
||||
}
|
||||
|
||||
func (*nullWindow) Minimize() {
|
||||
}
|
||||
|
||||
func (*nullWindow) IsMinimized() bool {
|
||||
return false
|
||||
}
|
||||
|
||||
func (*nullWindow) SetIcon(iconImages []image.Image) {
|
||||
}
|
||||
|
||||
func (*nullWindow) SetTitle(title string) {
|
||||
}
|
||||
|
||||
func (*nullWindow) Restore() {
|
||||
}
|
||||
|
||||
func (*nullWindow) SetClosingHandled(handled bool) {
|
||||
}
|
||||
|
||||
func (*nullWindow) IsClosingHandled() bool {
|
||||
return false
|
||||
}
|
||||
|
||||
func (*nullWindow) SetMousePassthrough(enabled bool) {
|
||||
}
|
||||
|
||||
func (*nullWindow) IsMousePassthrough() bool {
|
||||
return false
|
||||
}
|
||||
+508
@@ -0,0 +1,508 @@
|
||||
// Copyright 2019 The Ebiten 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 !android && !ios && !js && !nintendosdk && !playstation5
|
||||
|
||||
package ui
|
||||
|
||||
import (
|
||||
"image"
|
||||
"runtime"
|
||||
|
||||
"github.com/hajimehoshi/ebiten/v2/internal/glfw"
|
||||
)
|
||||
|
||||
type glfwWindow struct {
|
||||
ui *UserInterface
|
||||
}
|
||||
|
||||
func (w *glfwWindow) IsDecorated() bool {
|
||||
if w.ui.isTerminated() {
|
||||
return false
|
||||
}
|
||||
if !w.ui.isRunning() {
|
||||
return w.ui.isInitWindowDecorated()
|
||||
}
|
||||
var v bool
|
||||
w.ui.mainThread.Call(func() {
|
||||
if w.ui.isTerminated() {
|
||||
return
|
||||
}
|
||||
a, err := w.ui.window.GetAttrib(glfw.Decorated)
|
||||
if err != nil {
|
||||
w.ui.setError(err)
|
||||
return
|
||||
}
|
||||
v = a == glfw.True
|
||||
})
|
||||
return v
|
||||
}
|
||||
|
||||
func (w *glfwWindow) SetDecorated(decorated bool) {
|
||||
if w.ui.isTerminated() {
|
||||
return
|
||||
}
|
||||
if !w.ui.isRunning() {
|
||||
w.ui.setInitWindowDecorated(decorated)
|
||||
return
|
||||
}
|
||||
|
||||
w.ui.mainThread.Call(func() {
|
||||
if w.ui.isTerminated() {
|
||||
return
|
||||
}
|
||||
if err := w.ui.setWindowDecorated(decorated); err != nil {
|
||||
w.ui.setError(err)
|
||||
return
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func (w *glfwWindow) ResizingMode() WindowResizingMode {
|
||||
if w.ui.isTerminated() {
|
||||
return 0
|
||||
}
|
||||
if !w.ui.isRunning() {
|
||||
w.ui.m.Lock()
|
||||
mode := w.ui.windowResizingMode
|
||||
w.ui.m.Unlock()
|
||||
return mode
|
||||
}
|
||||
var mode WindowResizingMode
|
||||
w.ui.mainThread.Call(func() {
|
||||
if w.ui.isTerminated() {
|
||||
return
|
||||
}
|
||||
mode = w.ui.windowResizingMode
|
||||
})
|
||||
return mode
|
||||
}
|
||||
|
||||
func (w *glfwWindow) SetResizingMode(mode WindowResizingMode) {
|
||||
if w.ui.isTerminated() {
|
||||
return
|
||||
}
|
||||
if !w.ui.isRunning() {
|
||||
w.ui.m.Lock()
|
||||
w.ui.windowResizingMode = mode
|
||||
w.ui.m.Unlock()
|
||||
return
|
||||
}
|
||||
w.ui.mainThread.Call(func() {
|
||||
if w.ui.isTerminated() {
|
||||
return
|
||||
}
|
||||
if err := w.ui.setWindowResizingMode(mode); err != nil {
|
||||
w.ui.setError(err)
|
||||
return
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func (w *glfwWindow) IsFloating() bool {
|
||||
if w.ui.isTerminated() {
|
||||
return false
|
||||
}
|
||||
if !w.ui.isRunning() {
|
||||
return w.ui.isInitWindowFloating()
|
||||
}
|
||||
var v bool
|
||||
w.ui.mainThread.Call(func() {
|
||||
if w.ui.isTerminated() {
|
||||
return
|
||||
}
|
||||
a, err := w.ui.window.GetAttrib(glfw.Floating)
|
||||
if err != nil {
|
||||
w.ui.setError(err)
|
||||
return
|
||||
}
|
||||
v = a == glfw.True
|
||||
})
|
||||
return v
|
||||
}
|
||||
|
||||
func (w *glfwWindow) SetFloating(floating bool) {
|
||||
if w.ui.isTerminated() {
|
||||
return
|
||||
}
|
||||
if !w.ui.isRunning() {
|
||||
w.ui.setInitWindowFloating(floating)
|
||||
return
|
||||
}
|
||||
w.ui.mainThread.Call(func() {
|
||||
if w.ui.isTerminated() {
|
||||
return
|
||||
}
|
||||
if err := w.ui.setWindowFloating(floating); err != nil {
|
||||
w.ui.setError(err)
|
||||
return
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func (w *glfwWindow) IsMaximized() bool {
|
||||
if w.ui.isTerminated() {
|
||||
return false
|
||||
}
|
||||
if !w.ui.isRunning() {
|
||||
return w.ui.isInitWindowMaximized()
|
||||
}
|
||||
if w.ResizingMode() != WindowResizingModeEnabled {
|
||||
return false
|
||||
}
|
||||
var v bool
|
||||
w.ui.mainThread.Call(func() {
|
||||
if w.ui.isTerminated() {
|
||||
return
|
||||
}
|
||||
m, err := w.ui.isWindowMaximized()
|
||||
if err != nil {
|
||||
w.ui.setError(err)
|
||||
return
|
||||
}
|
||||
v = m
|
||||
})
|
||||
return v
|
||||
}
|
||||
|
||||
func (w *glfwWindow) Maximize() {
|
||||
if w.ui.isTerminated() {
|
||||
return
|
||||
}
|
||||
|
||||
// Do not allow maximizing the window when the window is not resizable.
|
||||
// On Windows, it is possible to restore the window from being maximized by mouse-dragging,
|
||||
// and this can be an unexpected behavior (#1990).
|
||||
if w.ResizingMode() != WindowResizingModeEnabled {
|
||||
return
|
||||
}
|
||||
|
||||
if !w.ui.isWindowMaximizable() {
|
||||
return
|
||||
}
|
||||
|
||||
if !w.ui.isRunning() {
|
||||
w.ui.setInitWindowMaximized(true)
|
||||
return
|
||||
}
|
||||
w.ui.mainThread.Call(func() {
|
||||
if w.ui.isTerminated() {
|
||||
return
|
||||
}
|
||||
if err := w.ui.maximizeWindow(); err != nil {
|
||||
w.ui.setError(err)
|
||||
return
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func (w *glfwWindow) IsMinimized() bool {
|
||||
if !w.ui.isRunning() {
|
||||
return false
|
||||
}
|
||||
var v bool
|
||||
w.ui.mainThread.Call(func() {
|
||||
if w.ui.isTerminated() {
|
||||
return
|
||||
}
|
||||
a, err := w.ui.window.GetAttrib(glfw.Iconified)
|
||||
if err != nil {
|
||||
w.ui.setError(err)
|
||||
return
|
||||
}
|
||||
v = a == glfw.True
|
||||
})
|
||||
return v
|
||||
}
|
||||
|
||||
func (w *glfwWindow) Minimize() {
|
||||
if !w.ui.isRunning() {
|
||||
// Do nothing
|
||||
return
|
||||
}
|
||||
w.ui.mainThread.Call(func() {
|
||||
if w.ui.isTerminated() {
|
||||
return
|
||||
}
|
||||
if err := w.ui.iconifyWindow(); err != nil {
|
||||
w.ui.setError(err)
|
||||
return
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func (w *glfwWindow) Restore() {
|
||||
if w.ui.isTerminated() {
|
||||
return
|
||||
}
|
||||
if !w.ui.isWindowMaximizable() {
|
||||
return
|
||||
}
|
||||
if !w.ui.isRunning() {
|
||||
// Do nothing
|
||||
return
|
||||
}
|
||||
w.ui.mainThread.Call(func() {
|
||||
if w.ui.isTerminated() {
|
||||
return
|
||||
}
|
||||
if err := w.ui.restoreWindow(); err != nil {
|
||||
w.ui.setError(err)
|
||||
return
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func (w *glfwWindow) SetMonitor(monitor *Monitor) {
|
||||
if monitor == nil {
|
||||
panic("ui: monitor cannot be nil at SetMonitor")
|
||||
}
|
||||
if w.ui.isTerminated() {
|
||||
return
|
||||
}
|
||||
if !w.ui.isRunning() {
|
||||
w.ui.setInitMonitor(monitor)
|
||||
return
|
||||
}
|
||||
w.ui.mainThread.Call(func() {
|
||||
if w.ui.isTerminated() {
|
||||
return
|
||||
}
|
||||
if err := w.ui.setWindowMonitor(monitor); err != nil {
|
||||
w.ui.setError(err)
|
||||
return
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func (w *glfwWindow) Position() (int, int) {
|
||||
if w.ui.isTerminated() {
|
||||
return 0, 0
|
||||
}
|
||||
if !w.ui.isRunning() {
|
||||
panic("ui: WindowPosition can't be called before the main loop starts")
|
||||
}
|
||||
var x, y int
|
||||
w.ui.mainThread.Call(func() {
|
||||
if w.ui.isTerminated() {
|
||||
return
|
||||
}
|
||||
f, err := w.ui.isFullscreen()
|
||||
if err != nil {
|
||||
w.ui.setError(err)
|
||||
return
|
||||
}
|
||||
|
||||
var wx, wy int
|
||||
if f {
|
||||
wx, wy = w.ui.origWindowPos()
|
||||
} else {
|
||||
x, y, err := w.ui.window.GetPos()
|
||||
if err != nil {
|
||||
w.ui.setError(err)
|
||||
return
|
||||
}
|
||||
wx, wy = x, y
|
||||
}
|
||||
m, err := w.ui.currentMonitor()
|
||||
if err != nil {
|
||||
w.ui.setError(err)
|
||||
return
|
||||
}
|
||||
wx -= m.boundsInGLFWPixels.Min.X
|
||||
wy -= m.boundsInGLFWPixels.Min.Y
|
||||
s := m.DeviceScaleFactor()
|
||||
xf := dipFromGLFWPixel(float64(wx), s)
|
||||
yf := dipFromGLFWPixel(float64(wy), s)
|
||||
x, y = int(xf), int(yf)
|
||||
})
|
||||
return x, y
|
||||
}
|
||||
|
||||
func (w *glfwWindow) SetPosition(x, y int) {
|
||||
if w.ui.isTerminated() {
|
||||
return
|
||||
}
|
||||
if !w.ui.isRunning() {
|
||||
w.ui.setInitWindowPositionInDIP(x, y)
|
||||
return
|
||||
}
|
||||
w.ui.mainThread.Call(func() {
|
||||
if w.ui.isTerminated() {
|
||||
return
|
||||
}
|
||||
m, err := w.ui.currentMonitor()
|
||||
if err != nil {
|
||||
w.ui.setError(err)
|
||||
return
|
||||
}
|
||||
if err := w.ui.setWindowPositionInDIP(x, y, m); err != nil {
|
||||
w.ui.setError(err)
|
||||
return
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func (w *glfwWindow) Size() (int, int) {
|
||||
if w.ui.isTerminated() {
|
||||
return 0, 0
|
||||
}
|
||||
if !w.ui.isRunning() {
|
||||
ww, wh := w.ui.getInitWindowSizeInDIP()
|
||||
return w.ui.adjustWindowSizeBasedOnSizeLimitsInDIP(ww, wh)
|
||||
}
|
||||
var ww, wh int
|
||||
w.ui.mainThread.Call(func() {
|
||||
if w.ui.isTerminated() {
|
||||
return
|
||||
}
|
||||
// Unlike origWindowPos, origWindow{Width,Height}InDPI are always updated via the callback.
|
||||
ww = w.ui.origWindowWidthInDIP
|
||||
wh = w.ui.origWindowHeightInDIP
|
||||
})
|
||||
return ww, wh
|
||||
}
|
||||
|
||||
func (w *glfwWindow) SetSize(width, height int) {
|
||||
if w.ui.isTerminated() {
|
||||
return
|
||||
}
|
||||
if !w.ui.isRunning() {
|
||||
// If the window is initially maximized, the set size is ignored anyway.
|
||||
w.ui.setInitWindowSizeInDIP(width, height)
|
||||
return
|
||||
}
|
||||
w.ui.mainThread.Call(func() {
|
||||
if w.ui.isTerminated() {
|
||||
return
|
||||
}
|
||||
m, err := w.ui.isWindowMaximized()
|
||||
if err != nil {
|
||||
w.ui.setError(err)
|
||||
return
|
||||
}
|
||||
if m && runtime.GOOS != "darwin" {
|
||||
return
|
||||
}
|
||||
if err := w.ui.setWindowSizeInDIP(width, height, true); err != nil {
|
||||
w.ui.setError(err)
|
||||
return
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func (w *glfwWindow) SizeLimits() (minw, minh, maxw, maxh int) {
|
||||
return w.ui.getWindowSizeLimitsInDIP()
|
||||
}
|
||||
|
||||
func (w *glfwWindow) SetSizeLimits(minw, minh, maxw, maxh int) {
|
||||
if w.ui.isTerminated() {
|
||||
return
|
||||
}
|
||||
if !w.ui.setWindowSizeLimitsInDIP(minw, minh, maxw, maxh) {
|
||||
return
|
||||
}
|
||||
if !w.ui.isRunning() {
|
||||
return
|
||||
}
|
||||
|
||||
w.ui.mainThread.Call(func() {
|
||||
if w.ui.isTerminated() {
|
||||
return
|
||||
}
|
||||
if err := w.ui.updateWindowSizeLimits(); err != nil {
|
||||
w.ui.setError(err)
|
||||
return
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func (w *glfwWindow) SetIcon(iconImages []image.Image) {
|
||||
if w.ui.isTerminated() {
|
||||
return
|
||||
}
|
||||
// The icons are actually set at (*UserInterface).loop.
|
||||
w.ui.setIconImages(iconImages)
|
||||
}
|
||||
|
||||
func (w *glfwWindow) SetTitle(title string) {
|
||||
if w.ui.isTerminated() {
|
||||
return
|
||||
}
|
||||
if !w.ui.isRunning() {
|
||||
w.ui.m.Lock()
|
||||
w.ui.title = title
|
||||
w.ui.m.Unlock()
|
||||
return
|
||||
}
|
||||
w.ui.title = title
|
||||
w.ui.mainThread.Call(func() {
|
||||
if w.ui.isTerminated() {
|
||||
return
|
||||
}
|
||||
if err := w.ui.setWindowTitle(title); err != nil {
|
||||
w.ui.setError(err)
|
||||
return
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func (w *glfwWindow) SetClosingHandled(handled bool) {
|
||||
w.ui.setWindowClosingHandled(handled)
|
||||
}
|
||||
|
||||
func (w *glfwWindow) IsClosingHandled() bool {
|
||||
return w.ui.isWindowClosingHandled()
|
||||
}
|
||||
|
||||
func (w *glfwWindow) SetMousePassthrough(enabled bool) {
|
||||
if w.ui.isTerminated() {
|
||||
return
|
||||
}
|
||||
if !w.ui.isRunning() {
|
||||
w.ui.setInitWindowMousePassthrough(enabled)
|
||||
return
|
||||
}
|
||||
w.ui.mainThread.Call(func() {
|
||||
if w.ui.isTerminated() {
|
||||
return
|
||||
}
|
||||
if err := w.ui.setWindowMousePassthrough(enabled); err != nil {
|
||||
w.ui.setError(err)
|
||||
return
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func (w *glfwWindow) IsMousePassthrough() bool {
|
||||
if w.ui.isTerminated() {
|
||||
return false
|
||||
}
|
||||
if !w.ui.isRunning() {
|
||||
return w.ui.isInitWindowMousePassthrough()
|
||||
}
|
||||
var v bool
|
||||
w.ui.mainThread.Call(func() {
|
||||
if w.ui.isTerminated() {
|
||||
return
|
||||
}
|
||||
a, err := w.ui.window.GetAttrib(glfw.MousePassthrough)
|
||||
if err != nil {
|
||||
w.ui.setError(err)
|
||||
return
|
||||
}
|
||||
v = a == glfw.True
|
||||
})
|
||||
return v
|
||||
}
|
||||
Reference in New Issue
Block a user