updated ebiten version from 2.7.9 to 2.9.9
This commit is contained in:
+11
@@ -68,9 +68,12 @@ type _POINT struct {
|
||||
}
|
||||
|
||||
var (
|
||||
imm32 = windows.NewLazySystemDLL("imm32.dll")
|
||||
ole32 = windows.NewLazySystemDLL("ole32.dll")
|
||||
user32 = windows.NewLazySystemDLL("user32.dll")
|
||||
|
||||
procImmAssociateContext = imm32.NewProc("ImmAssociateContext")
|
||||
|
||||
procCoCreateInstance = ole32.NewProc("CoCreateInstance")
|
||||
|
||||
procGetSystemMetrics = user32.NewProc("GetSystemMetrics")
|
||||
@@ -79,6 +82,14 @@ var (
|
||||
procGetCursorPos = user32.NewProc("GetCursorPos")
|
||||
)
|
||||
|
||||
func _ImmAssociateContext(hwnd windows.HWND, hIMC uintptr) (uintptr, error) {
|
||||
r, _, e := procImmAssociateContext.Call(uintptr(hwnd), hIMC)
|
||||
if e != nil && !errors.Is(e, windows.ERROR_SUCCESS) {
|
||||
return 0, fmt.Errorf("ui: ImmAssociateContext failed: error code: %w", e)
|
||||
}
|
||||
return r, nil
|
||||
}
|
||||
|
||||
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)))
|
||||
|
||||
+87
-51
@@ -54,7 +54,7 @@ type context struct {
|
||||
offscreenHeight float64
|
||||
|
||||
isOffscreenModified bool
|
||||
lastDrawTime time.Time
|
||||
lastSwapBufferTime time.Time
|
||||
|
||||
skipCount int
|
||||
|
||||
@@ -70,7 +70,14 @@ func newContext(game Game) *context {
|
||||
|
||||
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)
|
||||
needsSwapBuffers, err := c.updateFrameImpl(graphicsDriver, clock.UpdateFrame(), outsideWidth, outsideHeight, deviceScaleFactor, ui, false)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if err := c.swapBuffersOrWait(needsSwapBuffers, graphicsDriver, ui.FPSMode() == FPSModeVsyncOn); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *context) forceUpdateFrame(graphicsDriver graphicsdriver.Graphics, outsideWidth, outsideHeight float64, deviceScaleFactor float64, ui *UserInterface) error {
|
||||
@@ -81,33 +88,32 @@ func (c *context) forceUpdateFrame(graphicsDriver graphicsdriver.Graphics, outsi
|
||||
n = 2
|
||||
}
|
||||
for i := 0; i < n; i++ {
|
||||
if err := c.updateFrameImpl(graphicsDriver, 1, outsideWidth, outsideHeight, deviceScaleFactor, ui, true); err != nil {
|
||||
needsSwapBuffers, err := c.updateFrameImpl(graphicsDriver, 1, outsideWidth, outsideHeight, deviceScaleFactor, ui, true)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if err := c.swapBuffersOrWait(needsSwapBuffers, graphicsDriver, ui.FPSMode() == FPSModeVsyncOn); 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) {
|
||||
func (c *context) updateFrameImpl(graphicsDriver graphicsdriver.Graphics, updateCount int, outsideWidth, outsideHeight float64, deviceScaleFactor float64, ui *UserInterface, forceDraw bool) (needsSwapBuffers 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
|
||||
return false, nil
|
||||
}
|
||||
|
||||
debug.Logf("----\n")
|
||||
debug.FrameLogf("----\n")
|
||||
|
||||
if err := atlas.BeginFrame(graphicsDriver); err != nil {
|
||||
return err
|
||||
return false, err
|
||||
}
|
||||
|
||||
defer func() {
|
||||
if err1 := atlas.EndFrame(); err1 != nil && err == nil {
|
||||
err = err1
|
||||
return
|
||||
}
|
||||
|
||||
if err1 := atlas.SwapBuffers(graphicsDriver); err1 != nil && err == nil {
|
||||
needsSwapBuffers = false
|
||||
err = err1
|
||||
return
|
||||
}
|
||||
@@ -115,17 +121,17 @@ func (c *context) updateFrameImpl(graphicsDriver graphicsdriver.Graphics, update
|
||||
|
||||
// Flush deferred functions, like reading pixels from GPU.
|
||||
if err := c.processFuncsInFrame(ui); err != nil {
|
||||
return err
|
||||
return false, 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
|
||||
return false, 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
|
||||
if err := ui.updateInputStateForFrame(); err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
// Ensure that Update is called once before Draw so that Update can be used for initialization.
|
||||
@@ -133,7 +139,7 @@ func (c *context) updateFrameImpl(graphicsDriver graphicsdriver.Graphics, update
|
||||
updateCount = 1
|
||||
c.updateCalled = true
|
||||
}
|
||||
debug.Logf("Update count per frame: %d\n", updateCount)
|
||||
debug.FrameLogf("Update count per frame: %d\n", updateCount)
|
||||
|
||||
// Update the game.
|
||||
for i := 0; i < updateCount; i++ {
|
||||
@@ -143,27 +149,55 @@ func (c *context) updateFrameImpl(graphicsDriver graphicsdriver.Graphics, update
|
||||
})
|
||||
|
||||
if err := hook.RunBeforeUpdateHooks(); err != nil {
|
||||
return err
|
||||
return false, err
|
||||
}
|
||||
if err := c.game.Update(); err != nil {
|
||||
return err
|
||||
return false, err
|
||||
}
|
||||
|
||||
// Catch the error that happened at (*Image).At.
|
||||
if err := ui.error(); err != nil {
|
||||
return err
|
||||
return false, err
|
||||
}
|
||||
|
||||
ui.incrementTick()
|
||||
}
|
||||
|
||||
// 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
|
||||
return false, err
|
||||
}
|
||||
|
||||
// Draw the game.
|
||||
if err := c.drawGame(graphicsDriver, ui, forceDraw); err != nil {
|
||||
return err
|
||||
return c.drawGame(graphicsDriver, ui, forceDraw)
|
||||
}
|
||||
|
||||
func (c *context) swapBuffersOrWait(needsSwapBuffers bool, graphicsDriver graphicsdriver.Graphics, vsyncEnabled bool) error {
|
||||
now := time.Now()
|
||||
defer func() {
|
||||
c.lastSwapBufferTime = now
|
||||
}()
|
||||
|
||||
if needsSwapBuffers {
|
||||
if err := atlas.SwapBuffers(graphicsDriver); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
var waitTime time.Duration
|
||||
if !needsSwapBuffers {
|
||||
// When swapping buffers is skipped and Draw is called too early, sleep for a while to suppress CPU usages (#2890).
|
||||
waitTime = time.Second / 60
|
||||
} else if vsyncEnabled {
|
||||
// In some environments, e.g. Linux on Parallels, SwapBuffers doesn't wait for the vsync (#2952).
|
||||
// In the case when the display has high refresh rates like 240 [Hz], the wait time should be small.
|
||||
waitTime = time.Millisecond
|
||||
}
|
||||
if waitTime > 0 {
|
||||
if delta := waitTime - now.Sub(c.lastSwapBufferTime); delta > 0 {
|
||||
time.Sleep(delta)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
@@ -177,7 +211,7 @@ func (c *context) newOffscreenImage(w, h int) *Image {
|
||||
return img
|
||||
}
|
||||
|
||||
func (c *context) drawGame(graphicsDriver graphicsdriver.Graphics, ui *UserInterface, forceDraw bool) error {
|
||||
func (c *context) drawGame(graphicsDriver graphicsdriver.Graphics, ui *UserInterface, forceDraw bool) (needSwapBuffers bool, err error) {
|
||||
if (c.offscreen.imageType == atlas.ImageTypeVolatile) != ui.IsScreenClearedEveryFrame() {
|
||||
w, h := c.offscreen.width, c.offscreen.height
|
||||
c.offscreen.Deallocate()
|
||||
@@ -195,10 +229,10 @@ func (c *context) drawGame(graphicsDriver graphicsdriver.Graphics, ui *UserInter
|
||||
}
|
||||
|
||||
if err := c.game.DrawOffscreen(); err != nil {
|
||||
return err
|
||||
return false, err
|
||||
}
|
||||
|
||||
const maxSkipCount = 3
|
||||
const maxSkipCount = 4
|
||||
|
||||
if !forceDraw && !c.isOffscreenModified {
|
||||
if c.skipCount < maxSkipCount {
|
||||
@@ -208,28 +242,26 @@ func (c *context) drawGame(graphicsDriver graphicsdriver.Graphics, ui *UserInter
|
||||
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)
|
||||
if c.skipCount >= maxSkipCount {
|
||||
return false, nil
|
||||
}
|
||||
|
||||
return nil
|
||||
// screen can be nil for some edge cases (#3121).
|
||||
if c.screen == nil {
|
||||
return false, nil
|
||||
}
|
||||
|
||||
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()
|
||||
return true, nil
|
||||
}
|
||||
|
||||
func (c *context) layoutGame(outsideWidth, outsideHeight float64, deviceScaleFactor float64) (int, int) {
|
||||
@@ -238,8 +270,13 @@ func (c *context) layoutGame(outsideWidth, outsideHeight float64, deviceScaleFac
|
||||
panic("ui: Layout must return positive numbers")
|
||||
}
|
||||
|
||||
c.screenWidth = outsideWidth * deviceScaleFactor
|
||||
c.screenHeight = outsideHeight * deviceScaleFactor
|
||||
screenWidth := outsideWidth * deviceScaleFactor
|
||||
screenHeight := outsideHeight * deviceScaleFactor
|
||||
if c.screenWidth != screenWidth || c.screenHeight != screenHeight {
|
||||
c.skipCount = 0
|
||||
}
|
||||
c.screenWidth = screenWidth
|
||||
c.screenHeight = screenHeight
|
||||
c.offscreenWidth = owf
|
||||
c.offscreenHeight = ohf
|
||||
|
||||
@@ -252,7 +289,7 @@ func (c *context) layoutGame(outsideWidth, outsideHeight float64, deviceScaleFac
|
||||
c.screen.Deallocate()
|
||||
c.screen = nil
|
||||
}
|
||||
if c.screen == nil {
|
||||
if c.screen == nil && sw > 0 && sh > 0 {
|
||||
c.screen = c.game.NewScreenImage(sw, sh)
|
||||
}
|
||||
|
||||
@@ -308,7 +345,6 @@ func (c *context) runInFrame(f func()) {
|
||||
f()
|
||||
}
|
||||
<-ch
|
||||
return
|
||||
}
|
||||
|
||||
func (c *context) processFuncsInFrame(ui *UserInterface) error {
|
||||
|
||||
+21
-15
@@ -23,6 +23,7 @@ import (
|
||||
"github.com/hajimehoshi/ebiten/v2/internal/graphics"
|
||||
"github.com/hajimehoshi/ebiten/v2/internal/graphicsdriver"
|
||||
"github.com/hajimehoshi/ebiten/v2/internal/mipmap"
|
||||
"github.com/hajimehoshi/ebiten/v2/internal/restorable"
|
||||
)
|
||||
|
||||
// panicOnErrorOnReadingPixels indicates whether reading pixels panics on an error or not.
|
||||
@@ -77,7 +78,7 @@ func (i *Image) 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) {
|
||||
func (i *Image) DrawTriangles(srcs [graphics.ShaderSrcImageCount]*Image, vertices []float32, indices []uint32, blend graphicsdriver.Blend, dstRegion image.Rectangle, srcRegions [graphics.ShaderSrcImageCount]image.Rectangle, shader *Shader, uniforms []uint32, fillRule graphicsdriver.FillRule, canSkipMipmap bool, antialias bool, hint restorable.Hint) {
|
||||
if i.modifyCallback != nil {
|
||||
i.modifyCallback()
|
||||
}
|
||||
@@ -98,13 +99,13 @@ func (i *Image) DrawTriangles(srcs [graphics.ShaderImageCount]*Image, vertices [
|
||||
i.bigOffscreenBuffer = i.ui.newBigOffscreenImage(i, imageType)
|
||||
}
|
||||
|
||||
i.bigOffscreenBuffer.drawTriangles(srcs, vertices, indices, blend, dstRegion, srcRegions, shader, uniforms, fillRule, canSkipMipmap, false)
|
||||
i.bigOffscreenBuffer.drawTriangles(srcs, vertices, indices, blend, dstRegion, srcRegions, shader, uniforms, fillRule, canSkipMipmap)
|
||||
return
|
||||
}
|
||||
|
||||
i.flushBufferIfNeeded()
|
||||
|
||||
var srcMipmaps [graphics.ShaderImageCount]*mipmap.Mipmap
|
||||
var srcMipmaps [graphics.ShaderSrcImageCount]*mipmap.Mipmap
|
||||
for i, src := range srcs {
|
||||
if src == nil {
|
||||
continue
|
||||
@@ -113,7 +114,7 @@ func (i *Image) DrawTriangles(srcs [graphics.ShaderImageCount]*Image, vertices [
|
||||
srcMipmaps[i] = src.mipmap
|
||||
}
|
||||
|
||||
i.mipmap.DrawTriangles(srcMipmaps, vertices, indices, blend, dstRegion, srcRegions, shader.shader, uniforms, fillRule, canSkipMipmap)
|
||||
i.mipmap.DrawTriangles(srcMipmaps, vertices, indices, blend, dstRegion, srcRegions, shader.shader, uniforms, fillRule, canSkipMipmap, hint)
|
||||
}
|
||||
|
||||
func (i *Image) WritePixels(pix []byte, region image.Rectangle) {
|
||||
@@ -168,22 +169,23 @@ func (i *Image) Fill(r, g, b, a float32, region image.Rectangle) {
|
||||
i.tmpVerticesForFill = make([]float32, 4*graphics.VertexFloatCount)
|
||||
}
|
||||
// i.tmpVerticesForFill can be reused as this is sent to DrawTriangles immediately.
|
||||
graphics.QuadVertices(
|
||||
graphics.QuadVerticesFromSrcAndMatrix(
|
||||
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}
|
||||
srcs := [graphics.ShaderSrcImageCount]*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
|
||||
}
|
||||
sr := image.Rect(0, 0, i.ui.whiteImage.width, i.ui.whiteImage.height)
|
||||
// i.lastBlend is updated in DrawTriangles.
|
||||
i.DrawTriangles(srcs, i.tmpVerticesForFill, is, blend, region, [graphics.ShaderImageCount]image.Rectangle{}, NearestFilterShader, nil, graphicsdriver.FillAll, true, false)
|
||||
i.DrawTriangles(srcs, i.tmpVerticesForFill, is, blend, region, [graphics.ShaderSrcImageCount]image.Rectangle{sr}, NearestFilterShader, nil, graphicsdriver.FillRuleFillAll, true, false, restorable.HintOverwriteDstRegion)
|
||||
}
|
||||
|
||||
type bigOffscreenImage struct {
|
||||
@@ -217,7 +219,7 @@ func (i *bigOffscreenImage) 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) {
|
||||
func (i *bigOffscreenImage) drawTriangles(srcs [graphics.ShaderSrcImageCount]*Image, vertices []float32, indices []uint32, blend graphicsdriver.Blend, dstRegion image.Rectangle, srcRegions [graphics.ShaderSrcImageCount]image.Rectangle, shader *Shader, uniforms []uint32, fillRule graphicsdriver.FillRule, canSkipMipmap bool) {
|
||||
if i.blend != blend {
|
||||
i.flush()
|
||||
}
|
||||
@@ -240,19 +242,20 @@ func (i *bigOffscreenImage) drawTriangles(srcs [graphics.ShaderImageCount]*Image
|
||||
|
||||
// Copy the current rendering result to get the correct blending result.
|
||||
if blend != graphicsdriver.BlendSourceOver && !i.dirty {
|
||||
srcs := [graphics.ShaderImageCount]*Image{i.orig}
|
||||
srcs := [graphics.ShaderSrcImageCount]*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(
|
||||
graphics.QuadVerticesFromSrcAndMatrix(
|
||||
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)
|
||||
srcRegion := i.region
|
||||
i.image.DrawTriangles(srcs, i.tmpVerticesForCopying, is, graphicsdriver.BlendCopy, dstRegion, [graphics.ShaderSrcImageCount]image.Rectangle{srcRegion}, NearestFilterShader, nil, graphicsdriver.FillRuleFillAll, true, false, restorable.HintOverwriteDstRegion)
|
||||
}
|
||||
|
||||
for idx := 0; idx < len(vertices); idx += graphics.VertexFloatCount {
|
||||
@@ -268,7 +271,7 @@ func (i *bigOffscreenImage) drawTriangles(srcs [graphics.ShaderImageCount]*Image
|
||||
dstRegion.Max.X *= bigOffscreenScale
|
||||
dstRegion.Max.Y *= bigOffscreenScale
|
||||
|
||||
i.image.DrawTriangles(srcs, vertices, indices, blend, dstRegion, srcRegions, shader, uniforms, fillRule, canSkipMipmap, false)
|
||||
i.image.DrawTriangles(srcs, vertices, indices, blend, dstRegion, srcRegions, shader, uniforms, fillRule, canSkipMipmap, false, restorable.HintNone)
|
||||
i.dirty = true
|
||||
}
|
||||
|
||||
@@ -284,23 +287,26 @@ func (i *bigOffscreenImage) flush() {
|
||||
// Mark the offscreen clean earlier to avoid recursive calls.
|
||||
i.dirty = false
|
||||
|
||||
srcs := [graphics.ShaderImageCount]*Image{i.image}
|
||||
srcs := [graphics.ShaderSrcImageCount]*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(
|
||||
graphics.QuadVerticesFromSrcAndMatrix(
|
||||
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
|
||||
srcRegion := image.Rect(0, 0, i.region.Dx()*bigOffscreenScale, i.region.Dy()*bigOffscreenScale)
|
||||
blend := graphicsdriver.BlendSourceOver
|
||||
hint := restorable.HintNone
|
||||
if i.blend != graphicsdriver.BlendSourceOver {
|
||||
blend = graphicsdriver.BlendCopy
|
||||
hint = restorable.HintOverwriteDstRegion
|
||||
}
|
||||
i.orig.DrawTriangles(srcs, i.tmpVerticesForFlushing, is, blend, dstRegion, [graphics.ShaderImageCount]image.Rectangle{}, LinearFilterShader, nil, graphicsdriver.FillAll, true, false)
|
||||
i.orig.DrawTriangles(srcs, i.tmpVerticesForFlushing, is, blend, dstRegion, [graphics.ShaderSrcImageCount]image.Rectangle{srcRegion}, LinearFilterShader, nil, graphicsdriver.FillRuleFillAll, true, false, hint)
|
||||
|
||||
i.image.clear()
|
||||
i.dirty = false
|
||||
|
||||
+1
-1
@@ -14,7 +14,7 @@
|
||||
|
||||
//go:build nintendosdk
|
||||
|
||||
// The actual implementation will be provided by -overlay.
|
||||
// The actual implementation will be provided by github.com/hajimehoshi/uwagaki.
|
||||
|
||||
#include "init_nintendosdk.h"
|
||||
|
||||
|
||||
+171
-12
@@ -39,21 +39,180 @@ type Touch struct {
|
||||
}
|
||||
|
||||
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
|
||||
KeyPressedTimes [KeyMax + 1]InputTime
|
||||
KeyReleasedTimes [KeyMax + 1]InputTime
|
||||
|
||||
MouseButtonPressedTimes [MouseButtonMax + 1]InputTime
|
||||
MouseButtonReleasedTimes [MouseButtonMax + 1]InputTime
|
||||
|
||||
CursorX float64
|
||||
CursorY float64
|
||||
WheelX float64
|
||||
WheelY float64
|
||||
Touches []Touch
|
||||
Runes []rune
|
||||
WindowBeingClosed bool
|
||||
DroppedFiles fs.FS
|
||||
}
|
||||
|
||||
func (i *InputState) setKeyPressed(key Key, t InputTime) {
|
||||
if key < 0 || KeyMax < key {
|
||||
return
|
||||
}
|
||||
i.KeyPressedTimes[key] = t
|
||||
}
|
||||
|
||||
func (i *InputState) setKeyReleased(key Key, t InputTime) {
|
||||
if key < 0 || KeyMax < key {
|
||||
return
|
||||
}
|
||||
// Ignore duplicated key releases (#3326).
|
||||
if i.KeyPressedTimes[key] <= i.KeyReleasedTimes[key] {
|
||||
return
|
||||
}
|
||||
i.KeyReleasedTimes[key] = t
|
||||
}
|
||||
|
||||
func (i *InputState) setMouseButtonPressed(button MouseButton, t InputTime) {
|
||||
if button < 0 || MouseButtonMax < button {
|
||||
return
|
||||
}
|
||||
i.MouseButtonPressedTimes[button] = t
|
||||
}
|
||||
|
||||
func (i *InputState) setMouseButtonReleased(button MouseButton, t InputTime) {
|
||||
if button < 0 || MouseButtonMax < button {
|
||||
return
|
||||
}
|
||||
if i.MouseButtonPressedTimes[button] <= i.MouseButtonReleasedTimes[button] {
|
||||
return
|
||||
}
|
||||
i.MouseButtonReleasedTimes[button] = t
|
||||
}
|
||||
|
||||
// releaseAllButtons is called when the browser window loses focus.
|
||||
func (i *InputState) releaseAllButtons(t InputTime) {
|
||||
for j := range i.KeyPressedTimes {
|
||||
if i.KeyPressedTimes[Key(j)] <= i.KeyReleasedTimes[Key(j)] {
|
||||
continue
|
||||
}
|
||||
i.KeyReleasedTimes[Key(j)] = t
|
||||
}
|
||||
for j := range i.MouseButtonPressedTimes {
|
||||
if i.MouseButtonPressedTimes[MouseButton(j)] <= i.MouseButtonReleasedTimes[MouseButton(j)] {
|
||||
continue
|
||||
}
|
||||
i.MouseButtonReleasedTimes[j] = t
|
||||
}
|
||||
i.Touches = i.Touches[:0]
|
||||
}
|
||||
|
||||
func (i *InputState) IsKeyPressed(key Key, tick int64) bool {
|
||||
switch key {
|
||||
case KeyAlt:
|
||||
return i.IsKeyPressed(KeyAltLeft, tick) || i.IsKeyPressed(KeyAltRight, tick)
|
||||
case KeyControl:
|
||||
return i.IsKeyPressed(KeyControlLeft, tick) || i.IsKeyPressed(KeyControlRight, tick)
|
||||
case KeyShift:
|
||||
return i.IsKeyPressed(KeyShiftLeft, tick) || i.IsKeyPressed(KeyShiftRight, tick)
|
||||
case KeyMeta:
|
||||
return i.IsKeyPressed(KeyMetaLeft, tick) || i.IsKeyPressed(KeyMetaRight, tick)
|
||||
}
|
||||
|
||||
if key < 0 || KeyMax < key {
|
||||
return false
|
||||
}
|
||||
p := i.KeyPressedTimes[key]
|
||||
r := i.KeyReleasedTimes[key]
|
||||
return inputStatePressed(p, r, tick)
|
||||
}
|
||||
|
||||
func (i *InputState) IsKeyJustPressed(key Key, tick int64) bool {
|
||||
if key < 0 || KeyMax < key {
|
||||
return false
|
||||
}
|
||||
p := i.KeyPressedTimes[key]
|
||||
return inputStateJustPressed(p, tick)
|
||||
}
|
||||
|
||||
func (i *InputState) IsKeyJustReleased(key Key, tick int64) bool {
|
||||
if key < 0 || KeyMax < key {
|
||||
return false
|
||||
}
|
||||
r := i.KeyReleasedTimes[key]
|
||||
return inputStateJustReleased(r, tick)
|
||||
}
|
||||
|
||||
func (i *InputState) KeyPressDuration(key Key, tick int64) int64 {
|
||||
if key < 0 || KeyMax < key {
|
||||
return 0
|
||||
}
|
||||
p := i.KeyPressedTimes[key]
|
||||
r := i.KeyReleasedTimes[key]
|
||||
return inputStateDuration(p, r, tick)
|
||||
}
|
||||
|
||||
func (i *InputState) IsMouseButtonPressed(button MouseButton, tick int64) bool {
|
||||
if button < 0 || MouseButtonMax < button {
|
||||
return false
|
||||
}
|
||||
p := i.MouseButtonPressedTimes[button]
|
||||
r := i.MouseButtonReleasedTimes[button]
|
||||
return inputStatePressed(p, r, tick)
|
||||
}
|
||||
|
||||
func (i *InputState) IsMouseButtonJustPressed(button MouseButton, tick int64) bool {
|
||||
if button < 0 || MouseButtonMax < button {
|
||||
return false
|
||||
}
|
||||
p := i.MouseButtonPressedTimes[button]
|
||||
return inputStateJustPressed(p, tick)
|
||||
}
|
||||
|
||||
func (i *InputState) IsMouseButtonJustReleased(button MouseButton, tick int64) bool {
|
||||
if button < 0 || MouseButtonMax < button {
|
||||
return false
|
||||
}
|
||||
r := i.MouseButtonReleasedTimes[button]
|
||||
return inputStateJustReleased(r, tick)
|
||||
}
|
||||
|
||||
func (i *InputState) MouseButtonPressDuration(button MouseButton, tick int64) int64 {
|
||||
if button < 0 || MouseButtonMax < button {
|
||||
return 0
|
||||
}
|
||||
p := i.MouseButtonPressedTimes[button]
|
||||
r := i.MouseButtonReleasedTimes[button]
|
||||
return inputStateDuration(p, r, tick)
|
||||
}
|
||||
|
||||
func inputStatePressed(pressed, released InputTime, tick int64) bool {
|
||||
return released < pressed || inputStateJustPressed(pressed, tick)
|
||||
}
|
||||
|
||||
func inputStateJustPressed(pressed InputTime, tick int64) bool {
|
||||
return pressed > 0 && pressed.Tick() == tick
|
||||
}
|
||||
|
||||
func inputStateJustReleased(released InputTime, tick int64) bool {
|
||||
return released > 0 && released.Tick() == tick
|
||||
}
|
||||
|
||||
func inputStateDuration(pressed, released InputTime, tick int64) int64 {
|
||||
if pressed == 0 {
|
||||
return 0
|
||||
}
|
||||
if pressed < released {
|
||||
return 0
|
||||
}
|
||||
return tick - pressed.Tick() + 1
|
||||
}
|
||||
|
||||
func (i *InputState) copyAndReset(dst *InputState) {
|
||||
dst.KeyPressed = i.KeyPressed
|
||||
dst.MouseButtonPressed = i.MouseButtonPressed
|
||||
dst.KeyPressedTimes = i.KeyPressedTimes
|
||||
dst.KeyReleasedTimes = i.KeyReleasedTimes
|
||||
dst.MouseButtonPressedTimes = i.MouseButtonPressedTimes
|
||||
dst.MouseButtonReleasedTimes = i.MouseButtonReleasedTimes
|
||||
dst.CursorX = i.CursorX
|
||||
dst.CursorY = i.CursorY
|
||||
dst.WheelX = i.WheelX
|
||||
|
||||
+50
-19
@@ -32,6 +32,52 @@ var glfwMouseButtonToMouseButton = map[glfw.MouseButton]MouseButton{
|
||||
}
|
||||
|
||||
func (u *UserInterface) registerInputCallbacks() error {
|
||||
if _, err := u.window.SetKeyCallback(func(w *glfw.Window, key glfw.Key, scancode int, action glfw.Action, mods glfw.ModifierKey) {
|
||||
// Ignore key repeats for now.
|
||||
if action == glfw.Repeat {
|
||||
return
|
||||
}
|
||||
|
||||
// As this function is called from GLFW callbacks, the current thread is main.
|
||||
u.m.Lock()
|
||||
defer u.m.Unlock()
|
||||
|
||||
uk, ok := glfwKeyToUIKey[key]
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
if action == glfw.Press {
|
||||
u.inputState.setKeyPressed(uk, u.InputTime())
|
||||
} else {
|
||||
u.inputState.setKeyReleased(uk, u.InputTime())
|
||||
}
|
||||
}); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if _, err := u.window.SetMouseButtonCallback(func(w *glfw.Window, button glfw.MouseButton, action glfw.Action, mods glfw.ModifierKey) {
|
||||
// Ignore key repeats for now.
|
||||
if action == glfw.Repeat {
|
||||
return
|
||||
}
|
||||
|
||||
// As this function is called from GLFW callbacks, the current thread is main.
|
||||
u.m.Lock()
|
||||
defer u.m.Unlock()
|
||||
|
||||
ub, ok := glfwMouseButtonToMouseButton[button]
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
if action == glfw.Press {
|
||||
u.inputState.setMouseButtonPressed(ub, u.InputTime())
|
||||
} else {
|
||||
u.inputState.setMouseButtonReleased(ub, u.InputTime())
|
||||
}
|
||||
}); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
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()
|
||||
@@ -54,34 +100,19 @@ func (u *UserInterface) registerInputCallbacks() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (u *UserInterface) updateInputState() error {
|
||||
func (u *UserInterface) updateInputStateForFrame() error {
|
||||
var err error
|
||||
u.mainThread.Call(func() {
|
||||
err = u.updateInputStateImpl()
|
||||
err = u.updateInputStateForFrameImpl()
|
||||
})
|
||||
return err
|
||||
}
|
||||
|
||||
// updateInputStateImpl must be called from the main thread.
|
||||
func (u *UserInterface) updateInputStateImpl() error {
|
||||
// updateInputStateForFrameImpl must be called from the main thread.
|
||||
func (u *UserInterface) updateInputStateForFrameImpl() 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
|
||||
|
||||
+61
-63
@@ -16,6 +16,7 @@ package ui
|
||||
|
||||
import (
|
||||
"math"
|
||||
"strings"
|
||||
"syscall/js"
|
||||
"unicode"
|
||||
)
|
||||
@@ -62,10 +63,15 @@ var codeToMouseButton = map[int]MouseButton{
|
||||
4: MouseButton4,
|
||||
}
|
||||
|
||||
func eventToKeys(e js.Value) (key0, key1 Key, fromKeyProperty bool) {
|
||||
func eventToKeys(e js.Value) (key0, key1 Key) {
|
||||
id := jsCodeToID(e.Get("code"))
|
||||
|
||||
// On mobile browsers, treat enter key as if this is from a `key` property.
|
||||
if IsVirtualKeyboard() && id == KeyEnter {
|
||||
return KeyEnter, -1
|
||||
}
|
||||
if id >= 0 {
|
||||
return id, -1, false
|
||||
return id, -1
|
||||
}
|
||||
|
||||
// With a virtual keyboard on mobile devices, e.code is empty. Use a 'key' property instead (#2898).
|
||||
@@ -75,69 +81,56 @@ func eventToKeys(e js.Value) (key0, key1 Key, fromKeyProperty bool) {
|
||||
// Let's assume both keys are pressed.
|
||||
switch {
|
||||
case key.Equal(stringAlt):
|
||||
return KeyAltLeft, KeyAltRight, true
|
||||
return KeyAltLeft, KeyAltRight
|
||||
case key.Equal(stringControl):
|
||||
return KeyControlLeft, KeyControlRight, true
|
||||
return KeyControlLeft, KeyControlRight
|
||||
case key.Equal(stringMeta):
|
||||
return KeyMetaLeft, KeyMetaRight, true
|
||||
return KeyMetaLeft, KeyMetaRight
|
||||
case key.Equal(stringShift):
|
||||
return KeyShiftLeft, KeyShiftRight, true
|
||||
return KeyShiftLeft, KeyShiftRight
|
||||
}
|
||||
|
||||
for uiKey, jsKey := range uiKeyToJSKey {
|
||||
if key.Equal(jsKey) {
|
||||
return uiKey, -1, true
|
||||
return uiKey, -1
|
||||
}
|
||||
}
|
||||
|
||||
return -1, -1, false
|
||||
return -1, -1
|
||||
}
|
||||
|
||||
func (u *UserInterface) keyDown(event js.Value) {
|
||||
key0, key1, fromKeyProperty := eventToKeys(event)
|
||||
// Ignore key repeats for now.
|
||||
if event.Get("repeat").Bool() {
|
||||
return
|
||||
}
|
||||
now := u.InputTime()
|
||||
key0, key1 := 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
|
||||
u.inputState.setKeyPressed(key0, now)
|
||||
}
|
||||
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
|
||||
u.inputState.setKeyPressed(key1, now)
|
||||
}
|
||||
}
|
||||
|
||||
func (u *UserInterface) keyUp(event js.Value) {
|
||||
key0, key1, fromKeyProperty := eventToKeys(event)
|
||||
now := u.InputTime()
|
||||
key0, key1 := eventToKeys(event)
|
||||
if key0 >= 0 {
|
||||
if !fromKeyProperty || u.keyDurationsByKeyProperty[key0] == 0 {
|
||||
u.inputState.KeyPressed[key0] = false
|
||||
}
|
||||
u.inputState.setKeyReleased(key0, now)
|
||||
}
|
||||
if key1 >= 0 {
|
||||
if !fromKeyProperty || u.keyDurationsByKeyProperty[key1] == 0 {
|
||||
u.inputState.KeyPressed[key1] = false
|
||||
}
|
||||
u.inputState.setKeyReleased(key1, now)
|
||||
}
|
||||
}
|
||||
|
||||
func (u *UserInterface) mouseDown(code int) {
|
||||
u.inputState.MouseButtonPressed[codeToMouseButton[code]] = true
|
||||
u.inputState.setMouseButtonPressed(codeToMouseButton[code], u.InputTime())
|
||||
}
|
||||
|
||||
func (u *UserInterface) mouseUp(code int) {
|
||||
u.inputState.MouseButtonPressed[codeToMouseButton[code]] = false
|
||||
u.inputState.setMouseButtonReleased(codeToMouseButton[code], u.InputTime())
|
||||
}
|
||||
|
||||
func (u *UserInterface) updateInputFromEvent(e js.Value) error {
|
||||
@@ -239,10 +232,12 @@ func isKeyString(str string) bool {
|
||||
}
|
||||
|
||||
var (
|
||||
jsKeyboard = js.Global().Get("navigator").Get("keyboard")
|
||||
jsKeyboardGetLayoutMap js.Value
|
||||
jsKeyboardGetLayoutMapCh chan js.Value
|
||||
jsKeyboardGetLayoutMapCallback js.Func
|
||||
jsKeyboard = js.Global().Get("navigator").Get("keyboard")
|
||||
jsKeyboardLayoutAvailable bool
|
||||
jsKeyboardGetLayoutMap js.Value
|
||||
jsKeyboardGetLayoutMapCh chan js.Value
|
||||
jsKeyboardGetLayoutMapThenCallback js.Func
|
||||
jsKeyboardGetLayoutMapCatchCallback js.Func
|
||||
)
|
||||
|
||||
func init() {
|
||||
@@ -252,10 +247,18 @@ func init() {
|
||||
|
||||
jsKeyboardGetLayoutMap = jsKeyboard.Get("getLayoutMap").Call("bind", jsKeyboard)
|
||||
jsKeyboardGetLayoutMapCh = make(chan js.Value, 1)
|
||||
jsKeyboardGetLayoutMapCallback = js.FuncOf(func(this js.Value, args []js.Value) any {
|
||||
jsKeyboardGetLayoutMapThenCallback = js.FuncOf(func(this js.Value, args []js.Value) any {
|
||||
jsKeyboardGetLayoutMapCh <- args[0]
|
||||
return nil
|
||||
})
|
||||
jsKeyboardGetLayoutMapCatchCallback = js.FuncOf(func(this js.Value, args []js.Value) any {
|
||||
err := args[0]
|
||||
js.Global().Get("console").Call("error", "ui: navigator.keyboard.getLayoutMap() failed:", err)
|
||||
jsKeyboardLayoutAvailable = false
|
||||
jsKeyboardGetLayoutMapCh <- js.Undefined()
|
||||
return nil
|
||||
})
|
||||
jsKeyboardLayoutAvailable = true
|
||||
}
|
||||
|
||||
func (u *UserInterface) KeyName(key Key) string {
|
||||
@@ -263,17 +266,20 @@ func (u *UserInterface) KeyName(key Key) string {
|
||||
return ""
|
||||
}
|
||||
|
||||
if !jsKeyboardLayoutAvailable {
|
||||
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)
|
||||
jsKeyboardGetLayoutMap.Invoke().Call("then", jsKeyboardGetLayoutMapThenCallback).Call("catch", jsKeyboardGetLayoutMapCatchCallback)
|
||||
u.keyboardLayoutMap = <-jsKeyboardGetLayoutMapCh
|
||||
}
|
||||
if u.keyboardLayoutMap.IsUndefined() {
|
||||
return ""
|
||||
}
|
||||
|
||||
n := u.keyboardLayoutMap.Call("get", uiKeyToJSCode[key])
|
||||
if n.IsUndefined() {
|
||||
@@ -294,17 +300,7 @@ func (u *UserInterface) saveCursorPosition() {
|
||||
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]++
|
||||
}
|
||||
|
||||
func (u *UserInterface) updateInputStateForFrame() error {
|
||||
s := theMonitor.DeviceScaleFactor()
|
||||
|
||||
if !math.IsNaN(u.savedCursorX) && !math.IsNaN(u.savedCursorY) {
|
||||
@@ -417,12 +413,14 @@ var uiKeyToJSKey = map[Key]js.Value{
|
||||
KeyNumpad9: js.ValueOf("9"),
|
||||
}
|
||||
|
||||
func (i *InputState) resetForBlur() {
|
||||
for j := range i.KeyPressed {
|
||||
i.KeyPressed[j] = false
|
||||
func IsVirtualKeyboard() bool {
|
||||
// Detect a virtual keyboard by the user agent.
|
||||
// Note that this is not a correct way to detect a virtual keyboard.
|
||||
// In the future, we should use the `navigator.virtualKeyboard` API.
|
||||
// https://developer.mozilla.org/en-US/docs/Web/API/Navigator/virtualKeyboard
|
||||
ua := js.Global().Get("navigator").Get("userAgent").String()
|
||||
if strings.Contains(ua, "Android") || strings.Contains(ua, "iPhone") || strings.Contains(ua, "iPad") || strings.Contains(ua, "iPod") {
|
||||
return true
|
||||
}
|
||||
for j := range i.MouseButtonPressed {
|
||||
i.MouseButtonPressed[j] = false
|
||||
}
|
||||
i.Touches = i.Touches[:0]
|
||||
return false
|
||||
}
|
||||
|
||||
+4
-8
@@ -26,24 +26,20 @@ type TouchForInput struct {
|
||||
Y float64
|
||||
}
|
||||
|
||||
func (u *UserInterface) updateInputStateFromOutside(keys map[Key]struct{}, runes []rune, touches []TouchForInput) {
|
||||
func (u *UserInterface) updateInputStateFromOutside(keyPressedTimes, keyReleasedTimes [KeyMax + 1]InputTime, 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.KeyPressedTimes = keyPressedTimes
|
||||
u.inputState.KeyReleasedTimes = keyReleasedTimes
|
||||
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 {
|
||||
func (u *UserInterface) updateInputStateForFrame() error {
|
||||
u.m.Lock()
|
||||
defer u.m.Unlock()
|
||||
|
||||
|
||||
+1
-1
@@ -14,7 +14,7 @@
|
||||
|
||||
//go:build nintendosdk
|
||||
|
||||
// The actual implementaiton will be provided by -overlay.
|
||||
// The actual implementaiton will be provided by github.com/hajimehoshi/uwagaki.
|
||||
|
||||
#include "input_nintendosdk.h"
|
||||
|
||||
|
||||
+4
-4
@@ -29,16 +29,16 @@ import (
|
||||
"github.com/hajimehoshi/ebiten/v2/internal/gamepad"
|
||||
)
|
||||
|
||||
func (u *UserInterface) updateInputState() error {
|
||||
func (u *UserInterface) updateInputStateForFrame() error {
|
||||
var err error
|
||||
u.mainThread.Call(func() {
|
||||
err = u.updateInputStateImpl()
|
||||
err = u.updateInputStateForFrameImpl()
|
||||
})
|
||||
return err
|
||||
}
|
||||
|
||||
// updateInputStateImpl must be called from the main thread.
|
||||
func (u *UserInterface) updateInputStateImpl() error {
|
||||
// updateInputStateForFrameImpl must be called from the main thread.
|
||||
func (u *UserInterface) updateInputStateForFrameImpl() error {
|
||||
if err := gamepad.Update(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
+17
-2
@@ -16,8 +16,23 @@
|
||||
|
||||
package ui
|
||||
|
||||
func (u *UserInterface) updateInputState() error {
|
||||
// TODO: Implement this
|
||||
import (
|
||||
"github.com/hajimehoshi/ebiten/v2/internal/gamepad"
|
||||
)
|
||||
|
||||
func (u *UserInterface) updateInputStateForFrame() error {
|
||||
var err error
|
||||
u.mainThread.Call(func() {
|
||||
err = u.updateInputStateForFrameImpl()
|
||||
})
|
||||
return err
|
||||
}
|
||||
|
||||
// updateInputStateForFrameImpl must be called from the main thread.
|
||||
func (u *UserInterface) updateInputStateForFrameImpl() error {
|
||||
if err := gamepad.Update(); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
|
||||
+5
-5
@@ -141,11 +141,11 @@ const (
|
||||
KeySlash
|
||||
KeySpace
|
||||
KeyTab
|
||||
KeyReserved0
|
||||
KeyReserved1
|
||||
KeyReserved2
|
||||
KeyReserved3
|
||||
KeyMax = KeyReserved3
|
||||
KeyAlt
|
||||
KeyControl
|
||||
KeyShift
|
||||
KeyMeta
|
||||
KeyMax = KeyMeta
|
||||
)
|
||||
|
||||
func (k Key) String() string {
|
||||
|
||||
+121
@@ -142,3 +142,124 @@ var uiKeyToGLFWKey = map[Key]glfw.Key{
|
||||
KeyY: glfw.KeyY,
|
||||
KeyZ: glfw.KeyZ,
|
||||
}
|
||||
|
||||
var glfwKeyToUIKey = map[glfw.Key]Key{
|
||||
glfw.KeyA: KeyA,
|
||||
glfw.KeyLeftAlt: KeyAltLeft,
|
||||
glfw.KeyRightAlt: KeyAltRight,
|
||||
glfw.KeyDown: KeyArrowDown,
|
||||
glfw.KeyLeft: KeyArrowLeft,
|
||||
glfw.KeyRight: KeyArrowRight,
|
||||
glfw.KeyUp: KeyArrowUp,
|
||||
glfw.KeyB: KeyB,
|
||||
glfw.KeyGraveAccent: KeyBackquote,
|
||||
glfw.KeyBackslash: KeyBackslash,
|
||||
glfw.KeyBackspace: KeyBackspace,
|
||||
glfw.KeyLeftBracket: KeyBracketLeft,
|
||||
glfw.KeyRightBracket: KeyBracketRight,
|
||||
glfw.KeyC: KeyC,
|
||||
glfw.KeyCapsLock: KeyCapsLock,
|
||||
glfw.KeyComma: KeyComma,
|
||||
glfw.KeyMenu: KeyContextMenu,
|
||||
glfw.KeyLeftControl: KeyControlLeft,
|
||||
glfw.KeyRightControl: KeyControlRight,
|
||||
glfw.KeyD: KeyD,
|
||||
glfw.KeyDelete: KeyDelete,
|
||||
glfw.Key0: KeyDigit0,
|
||||
glfw.Key1: KeyDigit1,
|
||||
glfw.Key2: KeyDigit2,
|
||||
glfw.Key3: KeyDigit3,
|
||||
glfw.Key4: KeyDigit4,
|
||||
glfw.Key5: KeyDigit5,
|
||||
glfw.Key6: KeyDigit6,
|
||||
glfw.Key7: KeyDigit7,
|
||||
glfw.Key8: KeyDigit8,
|
||||
glfw.Key9: KeyDigit9,
|
||||
glfw.KeyE: KeyE,
|
||||
glfw.KeyEnd: KeyEnd,
|
||||
glfw.KeyEnter: KeyEnter,
|
||||
glfw.KeyEqual: KeyEqual,
|
||||
glfw.KeyEscape: KeyEscape,
|
||||
glfw.KeyF: KeyF,
|
||||
glfw.KeyF1: KeyF1,
|
||||
glfw.KeyF10: KeyF10,
|
||||
glfw.KeyF11: KeyF11,
|
||||
glfw.KeyF12: KeyF12,
|
||||
glfw.KeyF13: KeyF13,
|
||||
glfw.KeyF14: KeyF14,
|
||||
glfw.KeyF15: KeyF15,
|
||||
glfw.KeyF16: KeyF16,
|
||||
glfw.KeyF17: KeyF17,
|
||||
glfw.KeyF18: KeyF18,
|
||||
glfw.KeyF19: KeyF19,
|
||||
glfw.KeyF2: KeyF2,
|
||||
glfw.KeyF20: KeyF20,
|
||||
glfw.KeyF21: KeyF21,
|
||||
glfw.KeyF22: KeyF22,
|
||||
glfw.KeyF23: KeyF23,
|
||||
glfw.KeyF24: KeyF24,
|
||||
glfw.KeyF3: KeyF3,
|
||||
glfw.KeyF4: KeyF4,
|
||||
glfw.KeyF5: KeyF5,
|
||||
glfw.KeyF6: KeyF6,
|
||||
glfw.KeyF7: KeyF7,
|
||||
glfw.KeyF8: KeyF8,
|
||||
glfw.KeyF9: KeyF9,
|
||||
glfw.KeyG: KeyG,
|
||||
glfw.KeyH: KeyH,
|
||||
glfw.KeyHome: KeyHome,
|
||||
glfw.KeyI: KeyI,
|
||||
glfw.KeyInsert: KeyInsert,
|
||||
glfw.KeyWorld1: KeyIntlBackslash,
|
||||
glfw.KeyJ: KeyJ,
|
||||
glfw.KeyK: KeyK,
|
||||
glfw.KeyL: KeyL,
|
||||
glfw.KeyM: KeyM,
|
||||
glfw.KeyLeftSuper: KeyMetaLeft,
|
||||
glfw.KeyRightSuper: KeyMetaRight,
|
||||
glfw.KeyMinus: KeyMinus,
|
||||
glfw.KeyN: KeyN,
|
||||
glfw.KeyNumLock: KeyNumLock,
|
||||
glfw.KeyKP0: KeyNumpad0,
|
||||
glfw.KeyKP1: KeyNumpad1,
|
||||
glfw.KeyKP2: KeyNumpad2,
|
||||
glfw.KeyKP3: KeyNumpad3,
|
||||
glfw.KeyKP4: KeyNumpad4,
|
||||
glfw.KeyKP5: KeyNumpad5,
|
||||
glfw.KeyKP6: KeyNumpad6,
|
||||
glfw.KeyKP7: KeyNumpad7,
|
||||
glfw.KeyKP8: KeyNumpad8,
|
||||
glfw.KeyKP9: KeyNumpad9,
|
||||
glfw.KeyKPAdd: KeyNumpadAdd,
|
||||
glfw.KeyKPDecimal: KeyNumpadDecimal,
|
||||
glfw.KeyKPDivide: KeyNumpadDivide,
|
||||
glfw.KeyKPEnter: KeyNumpadEnter,
|
||||
glfw.KeyKPEqual: KeyNumpadEqual,
|
||||
glfw.KeyKPMultiply: KeyNumpadMultiply,
|
||||
glfw.KeyKPSubtract: KeyNumpadSubtract,
|
||||
glfw.KeyO: KeyO,
|
||||
glfw.KeyP: KeyP,
|
||||
glfw.KeyPageDown: KeyPageDown,
|
||||
glfw.KeyPageUp: KeyPageUp,
|
||||
glfw.KeyPause: KeyPause,
|
||||
glfw.KeyPeriod: KeyPeriod,
|
||||
glfw.KeyPrintScreen: KeyPrintScreen,
|
||||
glfw.KeyQ: KeyQ,
|
||||
glfw.KeyApostrophe: KeyQuote,
|
||||
glfw.KeyR: KeyR,
|
||||
glfw.KeyS: KeyS,
|
||||
glfw.KeyScrollLock: KeyScrollLock,
|
||||
glfw.KeySemicolon: KeySemicolon,
|
||||
glfw.KeyLeftShift: KeyShiftLeft,
|
||||
glfw.KeyRightShift: KeyShiftRight,
|
||||
glfw.KeySlash: KeySlash,
|
||||
glfw.KeySpace: KeySpace,
|
||||
glfw.KeyT: KeyT,
|
||||
glfw.KeyTab: KeyTab,
|
||||
glfw.KeyU: KeyU,
|
||||
glfw.KeyV: KeyV,
|
||||
glfw.KeyW: KeyW,
|
||||
glfw.KeyX: KeyX,
|
||||
glfw.KeyY: KeyY,
|
||||
glfw.KeyZ: KeyZ,
|
||||
}
|
||||
|
||||
+15
-12
@@ -18,6 +18,7 @@ package ui
|
||||
|
||||
import (
|
||||
"image"
|
||||
"slices"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
|
||||
@@ -65,13 +66,13 @@ type monitors struct {
|
||||
|
||||
m sync.Mutex
|
||||
|
||||
updateCalled int32
|
||||
updateCalled atomic.Bool
|
||||
}
|
||||
|
||||
var theMonitors monitors
|
||||
|
||||
func (m *monitors) append(ms []*Monitor) []*Monitor {
|
||||
if atomic.LoadInt32(&m.updateCalled) == 0 {
|
||||
if !m.updateCalled.Load() {
|
||||
panic("ui: (*monitors).update must be called before (*monitors).append is called")
|
||||
}
|
||||
|
||||
@@ -81,15 +82,24 @@ func (m *monitors) append(ms []*Monitor) []*Monitor {
|
||||
return append(ms, m.monitors...)
|
||||
}
|
||||
|
||||
func (m *monitors) contains(monitor *Monitor) bool {
|
||||
if !m.updateCalled.Load() {
|
||||
return false
|
||||
}
|
||||
m.m.Lock()
|
||||
defer m.m.Unlock()
|
||||
return slices.Contains(m.monitors, monitor)
|
||||
}
|
||||
|
||||
func (m *monitors) primaryMonitor() *Monitor {
|
||||
if atomic.LoadInt32(&m.updateCalled) == 0 {
|
||||
if !m.updateCalled.Load() {
|
||||
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).
|
||||
// GetMonitors might return nil in theory (#1878, #1887, #3241).
|
||||
// primaryMonitor can be called at the initialization, so monitors can be nil.
|
||||
if len(m.monitors) == 0 {
|
||||
return nil
|
||||
@@ -97,13 +107,6 @@ func (m *monitors) primaryMonitor() *Monitor {
|
||||
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.
|
||||
@@ -179,6 +182,6 @@ func (m *monitors) update() error {
|
||||
m.monitors = newMonitors
|
||||
m.m.Unlock()
|
||||
|
||||
atomic.StoreInt32(&m.updateCalled, 1)
|
||||
m.updateCalled.Store(true)
|
||||
return nil
|
||||
}
|
||||
|
||||
+15
-10
@@ -37,18 +37,8 @@ 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()
|
||||
|
||||
@@ -57,6 +47,7 @@ func (u *UserInterface) runMultiThread(game Game, options *RunOptions) error {
|
||||
// Run the render thread.
|
||||
wg.Go(func() error {
|
||||
defer cancel()
|
||||
|
||||
graphicscommand.LoopRenderThread(ctx)
|
||||
return nil
|
||||
})
|
||||
@@ -64,6 +55,20 @@ func (u *UserInterface) runMultiThread(game Game, options *RunOptions) error {
|
||||
// Run the game thread.
|
||||
wg.Go(func() error {
|
||||
defer cancel()
|
||||
|
||||
var err error
|
||||
u.mainThread.Call(func() {
|
||||
if err1 := u.initOnMainThread(options); err1 != nil {
|
||||
err = err1
|
||||
}
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// setRunning(true) should be called in initOnMainThread for each platform.
|
||||
defer u.setRunning(false)
|
||||
|
||||
return u.loopGame()
|
||||
})
|
||||
|
||||
|
||||
+32
-15
@@ -27,14 +27,14 @@ import (
|
||||
type Shader struct {
|
||||
shader *atlas.Shader
|
||||
|
||||
uniformNames []string
|
||||
uniformTypes []shaderir.Type
|
||||
uniformUint32Count int
|
||||
uniformNames []string
|
||||
uniformTypes []shaderir.Type
|
||||
uniformDwordCount int
|
||||
}
|
||||
|
||||
func NewShader(ir *shaderir.Program) *Shader {
|
||||
func NewShader(ir *shaderir.Program, name string) *Shader {
|
||||
return &Shader{
|
||||
shader: atlas.NewShader(ir),
|
||||
shader: atlas.NewShader(ir, name),
|
||||
uniformNames: ir.UniformNames[graphics.PreservedUniformVariablesCount:],
|
||||
uniformTypes: ir.Uniforms[graphics.PreservedUniformVariablesCount:],
|
||||
}
|
||||
@@ -45,20 +45,20 @@ func (s *Shader) Deallocate() {
|
||||
}
|
||||
|
||||
func (s *Shader) AppendUniforms(dst []uint32, uniforms map[string]any) []uint32 {
|
||||
if s.uniformUint32Count == 0 {
|
||||
if s.uniformDwordCount == 0 {
|
||||
for _, typ := range s.uniformTypes {
|
||||
s.uniformUint32Count += typ.Uint32Count()
|
||||
s.uniformDwordCount += typ.DwordCount()
|
||||
}
|
||||
}
|
||||
|
||||
origLen := len(dst)
|
||||
if cap(dst)-len(dst) >= s.uniformUint32Count {
|
||||
dst = dst[:len(dst)+s.uniformUint32Count]
|
||||
if cap(dst)-len(dst) >= s.uniformDwordCount {
|
||||
dst = dst[:len(dst)+s.uniformDwordCount]
|
||||
for i := origLen; i < len(dst); i++ {
|
||||
dst[i] = 0
|
||||
}
|
||||
} else {
|
||||
dst = append(dst, make([]uint32, s.uniformUint32Count)...)
|
||||
dst = append(dst, make([]uint32, s.uniformDwordCount)...)
|
||||
}
|
||||
|
||||
idx := origLen
|
||||
@@ -70,27 +70,44 @@ func (s *Shader) AppendUniforms(dst []uint32, uniforms map[string]any) []uint32
|
||||
v := reflect.ValueOf(uv)
|
||||
t := v.Type()
|
||||
switch t.Kind() {
|
||||
case reflect.Bool:
|
||||
if typ.DwordCount() != 1 {
|
||||
panic(fmt.Sprintf("ui: unexpected uniform value for %s (%s)", name, typ.String()))
|
||||
}
|
||||
if v.Bool() {
|
||||
dst[idx] = 1
|
||||
} else {
|
||||
dst[idx] = 0
|
||||
}
|
||||
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
|
||||
if typ.Uint32Count() != 1 {
|
||||
if typ.DwordCount() != 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 {
|
||||
if typ.DwordCount() != 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 {
|
||||
if typ.DwordCount() != 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 {
|
||||
if typ.DwordCount() != l {
|
||||
panic(fmt.Sprintf("ui: unexpected uniform value for %s (%s)", name, typ.String()))
|
||||
}
|
||||
switch t.Elem().Kind() {
|
||||
case reflect.Bool:
|
||||
for i := 0; i < l; i++ {
|
||||
if v.Index(i).Bool() {
|
||||
dst[idx+i] = 1
|
||||
} else {
|
||||
dst[idx+i] = 0
|
||||
}
|
||||
}
|
||||
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())
|
||||
@@ -111,7 +128,7 @@ func (s *Shader) AppendUniforms(dst []uint32, uniforms map[string]any) []uint32
|
||||
}
|
||||
}
|
||||
|
||||
idx += typ.Uint32Count()
|
||||
idx += typ.DwordCount()
|
||||
}
|
||||
|
||||
return dst
|
||||
|
||||
+70
-31
@@ -23,6 +23,7 @@ import (
|
||||
_ "github.com/ebitengine/hideconsole"
|
||||
|
||||
"github.com/hajimehoshi/ebiten/v2/internal/atlas"
|
||||
"github.com/hajimehoshi/ebiten/v2/internal/graphicsdriver"
|
||||
"github.com/hajimehoshi/ebiten/v2/internal/mipmap"
|
||||
"github.com/hajimehoshi/ebiten/v2/internal/thread"
|
||||
)
|
||||
@@ -75,10 +76,12 @@ type UserInterface struct {
|
||||
err error
|
||||
errM sync.Mutex
|
||||
|
||||
isScreenClearedEveryFrame int32
|
||||
graphicsLibrary int32
|
||||
running int32
|
||||
terminated int32
|
||||
isScreenClearedEveryFrame atomic.Bool
|
||||
graphicsLibrary atomic.Int32
|
||||
running atomic.Bool
|
||||
terminated atomic.Bool
|
||||
tick atomic.Int64
|
||||
inputTime atomic.Int64
|
||||
|
||||
whiteImage *Image
|
||||
|
||||
@@ -106,10 +109,9 @@ func Get() *UserInterface {
|
||||
|
||||
// newUserInterface must be called from the main thread.
|
||||
func newUserInterface() (*UserInterface, error) {
|
||||
u := &UserInterface{
|
||||
isScreenClearedEveryFrame: 1,
|
||||
graphicsLibrary: int32(GraphicsLibraryUnknown),
|
||||
}
|
||||
u := &UserInterface{}
|
||||
u.isScreenClearedEveryFrame.Store(true)
|
||||
u.graphicsLibrary.Store(int32(GraphicsLibraryUnknown))
|
||||
|
||||
u.whiteImage = u.NewImage(3, 3, atlas.ImageTypeRegular)
|
||||
pix := make([]byte, 4*u.whiteImage.width*u.whiteImage.height)
|
||||
@@ -127,6 +129,10 @@ func newUserInterface() (*UserInterface, error) {
|
||||
}
|
||||
|
||||
func (u *UserInterface) readPixels(mipmap *mipmap.Mipmap, pixels []byte, region image.Rectangle) error {
|
||||
if !u.running.Load() {
|
||||
panic("ui: ReadPixels cannot be called before the game starts")
|
||||
}
|
||||
|
||||
ok, err := mipmap.ReadPixels(u.graphicsDriver, pixels, region)
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -167,13 +173,17 @@ func (u *UserInterface) dumpImages(dir string) (string, error) {
|
||||
}
|
||||
|
||||
type RunOptions struct {
|
||||
GraphicsLibrary GraphicsLibrary
|
||||
InitUnfocused bool
|
||||
ScreenTransparent bool
|
||||
SkipTaskbar bool
|
||||
SingleThread bool
|
||||
X11ClassName string
|
||||
X11InstanceName string
|
||||
GraphicsLibrary GraphicsLibrary
|
||||
InitUnfocused bool
|
||||
ScreenTransparent bool
|
||||
SkipTaskbar bool
|
||||
SingleThread bool
|
||||
DisableHiDPI bool
|
||||
ColorSpace graphicsdriver.ColorSpace
|
||||
ApplePressAndHoldEnabled bool
|
||||
X11ClassName string
|
||||
X11InstanceName string
|
||||
StrictContextRestoration bool
|
||||
}
|
||||
|
||||
// InitialWindowPosition returns the position for centering the given second width/height pair within the first width/height pair.
|
||||
@@ -196,41 +206,70 @@ func (u *UserInterface) setError(err error) {
|
||||
}
|
||||
|
||||
func (u *UserInterface) IsScreenClearedEveryFrame() bool {
|
||||
return atomic.LoadInt32(&u.isScreenClearedEveryFrame) != 0
|
||||
return u.isScreenClearedEveryFrame.Load()
|
||||
}
|
||||
|
||||
func (u *UserInterface) SetScreenClearedEveryFrame(cleared bool) {
|
||||
v := int32(0)
|
||||
if cleared {
|
||||
v = 1
|
||||
}
|
||||
atomic.StoreInt32(&u.isScreenClearedEveryFrame, v)
|
||||
u.isScreenClearedEveryFrame.Store(cleared)
|
||||
}
|
||||
|
||||
func (u *UserInterface) setGraphicsLibrary(library GraphicsLibrary) {
|
||||
atomic.StoreInt32(&u.graphicsLibrary, int32(library))
|
||||
u.graphicsLibrary.Store(int32(library))
|
||||
}
|
||||
|
||||
func (u *UserInterface) GraphicsLibrary() GraphicsLibrary {
|
||||
return GraphicsLibrary(atomic.LoadInt32(&u.graphicsLibrary))
|
||||
return GraphicsLibrary(u.graphicsLibrary.Load())
|
||||
}
|
||||
|
||||
func (u *UserInterface) isRunning() bool {
|
||||
return atomic.LoadInt32(&u.running) != 0 && !u.isTerminated()
|
||||
return u.running.Load() && !u.isTerminated()
|
||||
}
|
||||
|
||||
func (u *UserInterface) setRunning(running bool) {
|
||||
if running {
|
||||
atomic.StoreInt32(&u.running, 1)
|
||||
} else {
|
||||
atomic.StoreInt32(&u.running, 0)
|
||||
}
|
||||
u.running.Store(running)
|
||||
}
|
||||
|
||||
func (u *UserInterface) isTerminated() bool {
|
||||
return atomic.LoadInt32(&u.terminated) != 0
|
||||
return u.terminated.Load()
|
||||
}
|
||||
|
||||
func (u *UserInterface) setTerminated() {
|
||||
atomic.StoreInt32(&u.terminated, 1)
|
||||
u.terminated.Store(true)
|
||||
}
|
||||
|
||||
func (u *UserInterface) Tick() int64 {
|
||||
return u.tick.Load()
|
||||
}
|
||||
|
||||
func (u *UserInterface) incrementTick() {
|
||||
u.tick.Add(1)
|
||||
u.inputTime.Store(int64(NewInputTimeFromTick(u.tick.Load())))
|
||||
}
|
||||
|
||||
func (u *UserInterface) InputTime() InputTime {
|
||||
t := InputTime(u.inputTime.Add(1))
|
||||
if t.Subtick() == 0 {
|
||||
panic("ui: too many input events in a tick")
|
||||
}
|
||||
return t
|
||||
}
|
||||
|
||||
// inputTimeSubtickBits is the number of bits for a counter in a tick.
|
||||
// An input time consists of a tick and a counter in a tick.
|
||||
// This means that an input time will be invalid when 2^20 = 1048576 inputs are handled in a tick,
|
||||
// but this should unlikely happen.
|
||||
const inputTimeSubtickBits = 20
|
||||
|
||||
type InputTime int64
|
||||
|
||||
func NewInputTimeFromTick(tick int64) InputTime {
|
||||
return InputTime(tick << inputTimeSubtickBits)
|
||||
}
|
||||
|
||||
func (i InputTime) Tick() int64 {
|
||||
return int64(i >> inputTimeSubtickBits)
|
||||
}
|
||||
|
||||
func (i InputTime) Subtick() int64 {
|
||||
return int64(i & ((1 << inputTimeSubtickBits) - 1))
|
||||
}
|
||||
|
||||
+40
-19
@@ -18,15 +18,21 @@ package ui
|
||||
#include <jni.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
// Basically same as:
|
||||
// The following JNI code works as this pseudo Java code:
|
||||
//
|
||||
// WindowService windowService = context.getSystemService(Context.WINDOW_SERVICE);
|
||||
// Display display = windowManager.getDefaultDisplay();
|
||||
// DisplayMetrics displayMetrics = new DisplayMetrics();
|
||||
// display.getRealMetrics(displayMetrics);
|
||||
// this.deviceScale = displayMetrics.density;
|
||||
// return displayMetrics.widthPixels, displayMetrics.heightPixels, displayMetrics.density;
|
||||
//
|
||||
static float deviceScale(uintptr_t java_vm, uintptr_t jni_env, uintptr_t ctx) {
|
||||
#cgo noescape displayInfo
|
||||
#cgo nocallback displayInfo
|
||||
static void displayInfo(int* width, int* height, float* scale, uintptr_t java_vm, uintptr_t jni_env, uintptr_t ctx) {
|
||||
*width = 0;
|
||||
*height = 0;
|
||||
*scale = 1;
|
||||
|
||||
JavaVM* vm = (JavaVM*)java_vm;
|
||||
JNIEnv* env = (JNIEnv*)jni_env;
|
||||
jobject context = (jobject)ctx;
|
||||
@@ -64,7 +70,15 @@ static float deviceScale(uintptr_t java_vm, uintptr_t jni_env, uintptr_t ctx) {
|
||||
env, display,
|
||||
(*env)->GetMethodID(env, android_view_Display, "getRealMetrics", "(Landroid/util/DisplayMetrics;)V"),
|
||||
displayMetrics);
|
||||
const float density =
|
||||
*width =
|
||||
(*env)->GetIntField(
|
||||
env, displayMetrics,
|
||||
(*env)->GetFieldID(env, android_util_DisplayMetrics, "widthPixels", "I"));
|
||||
*height =
|
||||
(*env)->GetIntField(
|
||||
env, displayMetrics,
|
||||
(*env)->GetFieldID(env, android_util_DisplayMetrics, "heightPixels", "I"));
|
||||
*scale =
|
||||
(*env)->GetFloatField(
|
||||
env, displayMetrics,
|
||||
(*env)->GetFieldID(env, android_util_DisplayMetrics, "density", "F"));
|
||||
@@ -78,15 +92,12 @@ static float deviceScale(uintptr_t java_vm, uintptr_t jni_env, uintptr_t ctx) {
|
||||
(*env)->DeleteLocalRef(env, windowManager);
|
||||
(*env)->DeleteLocalRef(env, display);
|
||||
(*env)->DeleteLocalRef(env, displayMetrics);
|
||||
|
||||
return density;
|
||||
}
|
||||
*/
|
||||
import "C"
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
|
||||
"github.com/ebitengine/gomobile/app"
|
||||
|
||||
@@ -95,6 +106,7 @@ import (
|
||||
)
|
||||
|
||||
type graphicsDriverCreatorImpl struct {
|
||||
colorSpace graphicsdriver.ColorSpace
|
||||
}
|
||||
|
||||
func (g *graphicsDriverCreatorImpl) newAuto() (graphicsdriver.Graphics, GraphicsLibrary, error) {
|
||||
@@ -118,18 +130,27 @@ func (*graphicsDriverCreatorImpl) newPlayStation5() (graphicsdriver.Graphics, er
|
||||
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
|
||||
}
|
||||
|
||||
func dipFromNativePixels(x float64, scale float64) float64 {
|
||||
return x / scale
|
||||
}
|
||||
|
||||
func (u *UserInterface) displayInfo() (int, int, float64, bool) {
|
||||
var cWidth, cHeight C.int
|
||||
var cScale C.float
|
||||
if err := app.RunOnJVM(func(vm, env, ctx uintptr) error {
|
||||
C.displayInfo(&cWidth, &cHeight, &cScale, C.uintptr_t(vm), C.uintptr_t(env), C.uintptr_t(ctx))
|
||||
return nil
|
||||
}); err != nil {
|
||||
// JVM is not ready yet.
|
||||
// TODO: Fix gomobile to detect the error type for this case.
|
||||
return 0, 0, 1, false
|
||||
}
|
||||
scale := float64(cScale)
|
||||
width := int(dipFromNativePixels(float64(cWidth), scale))
|
||||
height := int(dipFromNativePixels(float64(cHeight), scale))
|
||||
return width, height, scale, true
|
||||
}
|
||||
|
||||
+45
-17
@@ -20,7 +20,6 @@ import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"reflect"
|
||||
"unsafe"
|
||||
|
||||
"github.com/ebitengine/purego/objc"
|
||||
|
||||
@@ -79,8 +78,8 @@ func (u *UserInterface) initializePlatform() error {
|
||||
// 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
|
||||
Fn: func(id objc.ID, cmd objc.SEL, sender objc.ID) bool {
|
||||
return id.Send(sel_origDelegate).Send(cmd, sender) != 0
|
||||
},
|
||||
},
|
||||
{
|
||||
@@ -165,8 +164,22 @@ func (u *UserInterface) initializePlatform() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (u *UserInterface) setApplePressAndHoldEnabled(enabled bool) {
|
||||
var val int
|
||||
if enabled {
|
||||
val = 1
|
||||
}
|
||||
defaults := objc.ID(class_NSMutableDictionary).Send(sel_alloc).Send(sel_init)
|
||||
defaults.Send(sel_setObjectForKey,
|
||||
objc.ID(class_NSNumber).Send(sel_alloc).Send(sel_initWithBool, val),
|
||||
cocoa.NSString_alloc().InitWithUTF8String("ApplePressAndHoldEnabled").ID)
|
||||
ud := objc.ID(class_NSUserDefaults).Send(sel_standardUserDefaults)
|
||||
ud.Send(sel_registerDefaults, defaults)
|
||||
}
|
||||
|
||||
type graphicsDriverCreatorImpl struct {
|
||||
transparent bool
|
||||
colorSpace graphicsdriver.ColorSpace
|
||||
}
|
||||
|
||||
func (g *graphicsDriverCreatorImpl) newAuto() (graphicsdriver.Graphics, GraphicsLibrary, error) {
|
||||
@@ -189,8 +202,8 @@ 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 (g *graphicsDriverCreatorImpl) newMetal() (graphicsdriver.Graphics, error) {
|
||||
return metal.NewGraphics(g.colorSpace)
|
||||
}
|
||||
|
||||
func (*graphicsDriverCreatorImpl) newPlayStation5() (graphicsdriver.Graphics, error) {
|
||||
@@ -218,13 +231,16 @@ func dipToGLFWPixel(x float64, scale float64) float64 {
|
||||
return x
|
||||
}
|
||||
|
||||
func (u *UserInterface) adjustWindowPosition(x, y int, monitor *Monitor) (int, int) {
|
||||
return x, y
|
||||
func (u *UserInterface) adjustWindowPosition(x, y int, monitor *Monitor) (int, int, error) {
|
||||
return x, y, nil
|
||||
}
|
||||
|
||||
var (
|
||||
class_NSCursor = objc.GetClass("NSCursor")
|
||||
class_NSEvent = objc.GetClass("NSEvent")
|
||||
class_NSCursor = objc.GetClass("NSCursor")
|
||||
class_NSEvent = objc.GetClass("NSEvent")
|
||||
class_NSMutableDictionary = objc.GetClass("NSMutableDictionary")
|
||||
class_NSNumber = objc.GetClass("NSNumber")
|
||||
class_NSUserDefaults = objc.GetClass("NSUserDefaults")
|
||||
)
|
||||
|
||||
var (
|
||||
@@ -232,17 +248,21 @@ var (
|
||||
sel_collectionBehavior = objc.RegisterName("collectionBehavior")
|
||||
sel_delegate = objc.RegisterName("delegate")
|
||||
sel_init = objc.RegisterName("init")
|
||||
sel_initWithBool = objc.RegisterName("initWithBool:")
|
||||
sel_initWithOrigDelegate = objc.RegisterName("initWithOrigDelegate:")
|
||||
sel_mouseLocation = objc.RegisterName("mouseLocation")
|
||||
sel_origDelegate = objc.RegisterName("origDelegate")
|
||||
sel_origResizable = objc.RegisterName("isOrigResizable")
|
||||
sel_registerDefaults = objc.RegisterName("registerDefaults:")
|
||||
sel_setCollectionBehavior = objc.RegisterName("setCollectionBehavior:")
|
||||
sel_setDelegate = objc.RegisterName("setDelegate:")
|
||||
sel_setDocumentEdited = objc.RegisterName("setDocumentEdited:")
|
||||
sel_setObjectForKey = objc.RegisterName("setObject:forKey:")
|
||||
sel_setOrigDelegate = objc.RegisterName("setOrigDelegate:")
|
||||
sel_setOrigResizable = objc.RegisterName("setOrigResizable:")
|
||||
sel_standardUserDefaults = objc.RegisterName("standardUserDefaults")
|
||||
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:")
|
||||
@@ -256,13 +276,7 @@ var (
|
||||
)
|
||||
|
||||
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))
|
||||
point := objc.Send[cocoa.NSPoint](objc.ID(class_NSEvent), sel_mouseLocation)
|
||||
|
||||
x, y = int(point.X), int(point.Y)
|
||||
|
||||
@@ -434,3 +448,17 @@ func initializeWindowAfterCreation(w *glfw.Window) error {
|
||||
func (u *UserInterface) skipTaskbar() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// setDocumentEdited must be called from the main thread.
|
||||
func (u *UserInterface) setDocumentEdited(edited bool) error {
|
||||
w, err := u.window.GetCocoaWindow()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
objc.ID(w).Send(sel_setDocumentEdited, edited)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (u *UserInterface) afterWindowCreation() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
+81
-13
@@ -24,8 +24,10 @@ import (
|
||||
"os"
|
||||
"runtime"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
"time"
|
||||
|
||||
"github.com/hajimehoshi/ebiten/v2/internal/clock"
|
||||
"github.com/hajimehoshi/ebiten/v2/internal/file"
|
||||
"github.com/hajimehoshi/ebiten/v2/internal/gamepad"
|
||||
"github.com/hajimehoshi/ebiten/v2/internal/glfw"
|
||||
@@ -69,7 +71,7 @@ type userInterfaceImpl struct {
|
||||
|
||||
lastDeviceScaleFactor float64
|
||||
|
||||
initMonitor *Monitor
|
||||
initMonitor atomic.Pointer[Monitor]
|
||||
initFullscreen bool
|
||||
initCursorMode CursorMode
|
||||
initWindowDecorated bool
|
||||
@@ -81,6 +83,8 @@ type userInterfaceImpl struct {
|
||||
initWindowMaximized bool
|
||||
initWindowMousePassthrough bool
|
||||
|
||||
initUnfocused bool
|
||||
|
||||
// bufferOnceSwapped must be accessed from the main thread.
|
||||
bufferOnceSwapped bool
|
||||
|
||||
@@ -96,17 +100,22 @@ type userInterfaceImpl struct {
|
||||
savedCursorX float64
|
||||
savedCursorY float64
|
||||
|
||||
sizeCallback glfw.SizeCallback
|
||||
closeCallback glfw.CloseCallback
|
||||
framebufferSizeCallback glfw.FramebufferSizeCallback
|
||||
defaultFramebufferSizeCallback glfw.FramebufferSizeCallback
|
||||
dropCallback glfw.DropCallback
|
||||
framebufferSizeCallbackCh chan struct{}
|
||||
|
||||
cachedCurrentMonitor *Monitor
|
||||
cachedCurrentMonitorTime int64
|
||||
|
||||
darwinInitOnce sync.Once
|
||||
showWindowOnce sync.Once
|
||||
bufferOnceSwappedOnce sync.Once
|
||||
|
||||
// immContext is used only in Windows.
|
||||
immContext uintptr
|
||||
|
||||
m sync.RWMutex
|
||||
}
|
||||
|
||||
@@ -246,15 +255,11 @@ func (u *UserInterface) initializeGLFW() error {
|
||||
}
|
||||
|
||||
func (u *UserInterface) setInitMonitor(m *Monitor) {
|
||||
u.m.Lock()
|
||||
defer u.m.Unlock()
|
||||
u.initMonitor = m
|
||||
u.initMonitor.Store(m)
|
||||
}
|
||||
|
||||
func (u *UserInterface) getInitMonitor() *Monitor {
|
||||
u.m.RLock()
|
||||
defer u.m.RUnlock()
|
||||
return u.initMonitor
|
||||
return u.initMonitor.Load()
|
||||
}
|
||||
|
||||
// AppendMonitors appends the current monitors to the passed in mons slice and returns it.
|
||||
@@ -569,6 +574,22 @@ func (u *UserInterface) setWindowClosingHandled(handled bool) {
|
||||
u.m.Lock()
|
||||
u.windowClosingHandled = handled
|
||||
u.m.Unlock()
|
||||
|
||||
if !u.isRunning() {
|
||||
return
|
||||
}
|
||||
if u.isTerminated() {
|
||||
return
|
||||
}
|
||||
u.mainThread.Call(func() {
|
||||
if u.isTerminated() {
|
||||
return
|
||||
}
|
||||
if err := u.setDocumentEdited(handled); err != nil {
|
||||
u.setError(err)
|
||||
return
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// isFullscreen must be called from the main thread.
|
||||
@@ -822,6 +843,8 @@ func (u *UserInterface) createWindow() error {
|
||||
return err
|
||||
}
|
||||
u.window = window
|
||||
// Set the running state true just a window is set (#2742).
|
||||
u.setRunning(true)
|
||||
|
||||
// The position must be set before the size is set (#1982).
|
||||
// setWindowSizeInDIP refers the current monitor's device scale.
|
||||
@@ -873,6 +896,17 @@ func (u *UserInterface) createWindow() error {
|
||||
return err
|
||||
}
|
||||
|
||||
u.m.Lock()
|
||||
closingHandled := u.windowClosingHandled
|
||||
u.m.Unlock()
|
||||
if err := u.setDocumentEdited(closingHandled); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := u.afterWindowCreation(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -1020,6 +1054,8 @@ event:
|
||||
}
|
||||
|
||||
func (u *UserInterface) initOnMainThread(options *RunOptions) error {
|
||||
u.setApplePressAndHoldEnabled(options.ApplePressAndHoldEnabled)
|
||||
|
||||
if err := glfw.WindowHint(glfw.AutoIconify, glfw.False); err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -1058,6 +1094,7 @@ func (u *UserInterface) initOnMainThread(options *RunOptions) error {
|
||||
|
||||
g, lib, err := newGraphicsDriver(&graphicsDriverCreatorImpl{
|
||||
transparent: options.ScreenTransparent,
|
||||
colorSpace: options.ColorSpace,
|
||||
}, options.GraphicsLibrary)
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -1087,6 +1124,7 @@ func (u *UserInterface) initOnMainThread(options *RunOptions) error {
|
||||
return err
|
||||
}
|
||||
|
||||
u.initUnfocused = options.InitUnfocused
|
||||
focused := glfw.True
|
||||
if options.InitUnfocused {
|
||||
focused = glfw.False
|
||||
@@ -1288,8 +1326,10 @@ func (u *UserInterface) update() (float64, float64, error) {
|
||||
if err = u.window.Show(); err != nil {
|
||||
return
|
||||
}
|
||||
if err = u.window.Focus(); err != nil {
|
||||
return
|
||||
if !u.initUnfocused {
|
||||
if err = u.window.Focus(); err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
if runtime.GOOS == "darwin" || runtime.GOOS == "windows" {
|
||||
@@ -1351,7 +1391,9 @@ func (u *UserInterface) update() (float64, float64, error) {
|
||||
}
|
||||
}
|
||||
|
||||
for !u.isRunnableOnUnfocused() {
|
||||
// If isRunnableOnUnfocused is false and the window is not focused, wait here.
|
||||
// For the first update, skip this check as the window might not be seen yet in some environments like ChromeOS (#3091).
|
||||
for !u.isRunnableOnUnfocused() && u.bufferOnceSwapped {
|
||||
// In the initial state on macOS, the window is not shown (#2620).
|
||||
visible, err := u.window.GetAttrib(glfw.Visible)
|
||||
if err != nil {
|
||||
@@ -1830,6 +1872,21 @@ func (u *UserInterface) minimumWindowWidth() (int, error) {
|
||||
//
|
||||
// currentMonitor must be called on the main thread.
|
||||
func (u *UserInterface) currentMonitor() (*Monitor, error) {
|
||||
if u.cachedCurrentMonitor != nil && u.cachedCurrentMonitorTime > u.Tick()-int64(clock.TPS()) && theMonitors.contains(u.cachedCurrentMonitor) {
|
||||
return u.cachedCurrentMonitor, nil
|
||||
}
|
||||
|
||||
m, err := u.currentMonitorImpl()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
u.cachedCurrentMonitor = m
|
||||
u.cachedCurrentMonitorTime = u.Tick()
|
||||
return m, nil
|
||||
}
|
||||
|
||||
// currentMonitorImpl must be called from the main thread.
|
||||
func (u *UserInterface) currentMonitorImpl() (*Monitor, error) {
|
||||
if u.window == nil {
|
||||
return u.getInitMonitor(), nil
|
||||
}
|
||||
@@ -1863,7 +1920,13 @@ func (u *UserInterface) currentMonitor() (*Monitor, error) {
|
||||
return m, nil
|
||||
}
|
||||
|
||||
return theMonitors.primaryMonitor(), nil
|
||||
if m := theMonitors.primaryMonitor(); m != nil {
|
||||
return m, nil
|
||||
}
|
||||
|
||||
// The primiary monitor might be missing even after the initialization (#3094, #3241).
|
||||
// The reason is still unknown. As a workaround, return the initial monitor.
|
||||
return u.getInitMonitor(), nil
|
||||
}
|
||||
|
||||
func (u *UserInterface) readInputState(inputState *InputState) {
|
||||
@@ -2074,7 +2137,12 @@ func (u *UserInterface) setWindowPositionInDIP(x, y int, monitor *Monitor) error
|
||||
s := monitor.DeviceScaleFactor()
|
||||
xf := dipToGLFWPixel(float64(x), s)
|
||||
yf := dipToGLFWPixel(float64(y), s)
|
||||
if x, y := u.adjustWindowPosition(mx+int(xf), my+int(yf), monitor); f {
|
||||
|
||||
x, y, err = u.adjustWindowPosition(mx+int(xf), my+int(yf), monitor)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if f {
|
||||
u.setOrigWindowPos(x, y)
|
||||
} else {
|
||||
if err := u.window.SetPos(x, y); err != nil {
|
||||
|
||||
+60
-8
@@ -19,8 +19,45 @@ package ui
|
||||
//
|
||||
// #import <UIKit/UIKit.h>
|
||||
//
|
||||
// static double devicePixelRatio() {
|
||||
// return [[UIScreen mainScreen] nativeScale];
|
||||
// static void displayInfoOnMainThread(float* width, float* height, float* scale, UIView* view) {
|
||||
// *width = 0;
|
||||
// *height = 0;
|
||||
// *scale = 1;
|
||||
// UIWindow* window = view.window;
|
||||
// if (!window) {
|
||||
// return;
|
||||
// }
|
||||
// UIWindowScene* scene = window.windowScene;
|
||||
// if (!scene) {
|
||||
// return;
|
||||
// }
|
||||
// CGRect bounds = scene.screen.bounds;
|
||||
// *width = bounds.size.width;
|
||||
// *height = bounds.size.height;
|
||||
// *scale = scene.screen.nativeScale;
|
||||
// }
|
||||
//
|
||||
// #cgo noescape displayInfo
|
||||
// #cgo nocallback displayInfo
|
||||
// static void displayInfo(float* width, float* height, float* scale, uintptr_t viewPtr) {
|
||||
// *width = 0;
|
||||
// *height = 0;
|
||||
// *scale = 1;
|
||||
// if (!viewPtr) {
|
||||
// return;
|
||||
// }
|
||||
// UIView* view = (__bridge UIView*)(void*)viewPtr;
|
||||
// if ([NSThread isMainThread]) {
|
||||
// displayInfoOnMainThread(width, height, scale, view);
|
||||
// return;
|
||||
// }
|
||||
// __block float w, h, s;
|
||||
// dispatch_sync(dispatch_get_main_queue(), ^{
|
||||
// displayInfoOnMainThread(&w, &h, &s, view);
|
||||
// });
|
||||
// *width = w;
|
||||
// *height = h;
|
||||
// *scale = s;
|
||||
// }
|
||||
import "C"
|
||||
|
||||
@@ -34,6 +71,7 @@ import (
|
||||
)
|
||||
|
||||
type graphicsDriverCreatorImpl struct {
|
||||
colorSpace graphicsdriver.ColorSpace
|
||||
}
|
||||
|
||||
func (g *graphicsDriverCreatorImpl) newAuto() (graphicsdriver.Graphics, GraphicsLibrary, error) {
|
||||
@@ -57,7 +95,7 @@ func (*graphicsDriverCreatorImpl) newDirectX() (graphicsdriver.Graphics, error)
|
||||
}
|
||||
|
||||
func (g *graphicsDriverCreatorImpl) newMetal() (graphicsdriver.Graphics, error) {
|
||||
return metal.NewGraphics()
|
||||
return metal.NewGraphics(g.colorSpace)
|
||||
}
|
||||
|
||||
func (*graphicsDriverCreatorImpl) newPlayStation5() (graphicsdriver.Graphics, error) {
|
||||
@@ -65,6 +103,7 @@ func (*graphicsDriverCreatorImpl) newPlayStation5() (graphicsdriver.Graphics, er
|
||||
}
|
||||
|
||||
func (u *UserInterface) SetUIView(uiview uintptr) error {
|
||||
u.uiView.Store(uiview)
|
||||
select {
|
||||
case err := <-u.errCh:
|
||||
return err
|
||||
@@ -88,11 +127,24 @@ func (u *UserInterface) IsGL() (bool, error) {
|
||||
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
|
||||
}
|
||||
|
||||
func dipFromNativePixels(x float64, scale float64) float64 {
|
||||
return x
|
||||
}
|
||||
|
||||
func (u *UserInterface) displayInfo() (int, int, float64, bool) {
|
||||
view := u.uiView.Load()
|
||||
if view == 0 {
|
||||
return 0, 0, 1, false
|
||||
}
|
||||
|
||||
var cWidth, cHeight, cScale C.float
|
||||
C.displayInfo(&cWidth, &cHeight, &cScale, C.uintptr_t(view))
|
||||
scale := float64(cScale)
|
||||
width := int(dipFromNativePixels(float64(cWidth), scale))
|
||||
height := int(dipFromNativePixels(float64(cHeight), scale))
|
||||
return width, height, scale, true
|
||||
}
|
||||
|
||||
+59
-22
@@ -29,7 +29,8 @@ import (
|
||||
)
|
||||
|
||||
type graphicsDriverCreatorImpl struct {
|
||||
canvas js.Value
|
||||
canvas js.Value
|
||||
colorSpace graphicsdriver.ColorSpace
|
||||
}
|
||||
|
||||
func (g *graphicsDriverCreatorImpl) newAuto() (graphicsdriver.Graphics, GraphicsLibrary, error) {
|
||||
@@ -38,7 +39,7 @@ func (g *graphicsDriverCreatorImpl) newAuto() (graphicsdriver.Graphics, Graphics
|
||||
}
|
||||
|
||||
func (g *graphicsDriverCreatorImpl) newOpenGL() (graphicsdriver.Graphics, error) {
|
||||
return opengl.NewGraphics(g.canvas)
|
||||
return opengl.NewGraphics(g.canvas, g.colorSpace)
|
||||
}
|
||||
|
||||
func (*graphicsDriverCreatorImpl) newDirectX() (graphicsdriver.Graphics, error) {
|
||||
@@ -96,15 +97,15 @@ type userInterfaceImpl struct {
|
||||
cursorShape CursorShape
|
||||
onceUpdateCalled bool
|
||||
lastCaptureExitTime time.Time
|
||||
hiDPIEnabled bool
|
||||
|
||||
context *context
|
||||
inputState InputState
|
||||
keyDurationsByKeyProperty map[Key]int
|
||||
cursorXInClient float64
|
||||
cursorYInClient float64
|
||||
origCursorXInClient float64
|
||||
origCursorYInClient float64
|
||||
touchesInClient []touchInClient
|
||||
context *context
|
||||
inputState InputState
|
||||
cursorXInClient float64
|
||||
cursorYInClient float64
|
||||
origCursorXInClient float64
|
||||
origCursorYInClient float64
|
||||
touchesInClient []touchInClient
|
||||
|
||||
savedCursorX float64
|
||||
savedCursorY float64
|
||||
@@ -369,7 +370,7 @@ func (u *UserInterface) needsUpdate() bool {
|
||||
}
|
||||
|
||||
func (u *UserInterface) loopGame() error {
|
||||
// Initialize the screen size first (#3033).
|
||||
// Initialize the screen size first (#3034).
|
||||
// If ebiten.SetRunnableOnUnfocused(false) and the canvas is not focused,
|
||||
// suspended() returns true and the update routine cannot start.
|
||||
u.updateScreenSize()
|
||||
@@ -465,6 +466,7 @@ func (u *UserInterface) init() error {
|
||||
runnableOnUnfocused: true,
|
||||
savedCursorX: math.NaN(),
|
||||
savedCursorY: math.NaN(),
|
||||
hiDPIEnabled: true,
|
||||
}
|
||||
|
||||
// document is undefined on node.js
|
||||
@@ -512,6 +514,7 @@ func (u *UserInterface) init() error {
|
||||
canvasStyle.Set("height", "100%")
|
||||
canvasStyle.Set("margin", "0")
|
||||
canvasStyle.Set("padding", "0")
|
||||
canvasStyle.Set("display", "block")
|
||||
|
||||
// Make the canvas focusable.
|
||||
canvas.Call("setAttribute", "tabindex", 1)
|
||||
@@ -535,6 +538,10 @@ func (u *UserInterface) init() error {
|
||||
}))
|
||||
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.")
|
||||
if u.cursorMode == CursorModeCaptured {
|
||||
u.recoverCursorMode()
|
||||
}
|
||||
u.recoverCursorPosition()
|
||||
return nil
|
||||
}))
|
||||
document.Call("addEventListener", "fullscreenerror", js.FuncOf(func(this js.Value, args []js.Value) any {
|
||||
@@ -697,7 +704,7 @@ func (u *UserInterface) setCanvasEventHandlers(v js.Value) {
|
||||
|
||||
// Blur
|
||||
v.Call("addEventListener", "blur", js.FuncOf(func(this js.Value, args []js.Value) any {
|
||||
u.inputState.resetForBlur()
|
||||
u.inputState.releaseAllButtons(u.InputTime())
|
||||
return nil
|
||||
}))
|
||||
}
|
||||
@@ -707,14 +714,21 @@ func (u *UserInterface) appendDroppedFiles(data js.Value) {
|
||||
defer u.dropFileM.Unlock()
|
||||
items := data.Get("items")
|
||||
|
||||
var entries []js.Value
|
||||
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)
|
||||
entries = append(entries, items.Index(i).Call("webkitGetAsEntry").Get("filesystem").Get("root"))
|
||||
}
|
||||
}
|
||||
if len(entries) > 0 {
|
||||
fs, err := file.NewFileEntryFS(entries)
|
||||
if err != nil {
|
||||
u.setError(err)
|
||||
return
|
||||
}
|
||||
u.inputState.DroppedFiles = fs
|
||||
}
|
||||
}
|
||||
|
||||
@@ -732,18 +746,37 @@ func (u *UserInterface) forceUpdateOnMinimumFPSMode() {
|
||||
}()
|
||||
}
|
||||
|
||||
func (u *UserInterface) shouldFocusFirst(options *RunOptions) bool {
|
||||
if options.InitUnfocused {
|
||||
return false
|
||||
}
|
||||
if !window.Truthy() {
|
||||
return false
|
||||
}
|
||||
|
||||
// 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).
|
||||
parent := window.Get("parent")
|
||||
isInIframe := !window.Get("location").Equal(parent.Get("location"))
|
||||
if !isInIframe {
|
||||
return true
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
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")
|
||||
}
|
||||
u.setRunning(true)
|
||||
|
||||
u.hiDPIEnabled = !options.DisableHiDPI
|
||||
|
||||
if u.shouldFocusFirst(options) {
|
||||
canvas.Call("focus")
|
||||
}
|
||||
|
||||
g, lib, err := newGraphicsDriver(&graphicsDriverCreatorImpl{
|
||||
canvas: canvas,
|
||||
canvas: canvas,
|
||||
colorSpace: options.ColorSpace,
|
||||
}, options.GraphicsLibrary)
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -791,6 +824,10 @@ func (m *Monitor) Name() string {
|
||||
}
|
||||
|
||||
func (m *Monitor) DeviceScaleFactor() float64 {
|
||||
if !theUI.hiDPIEnabled {
|
||||
return 1
|
||||
}
|
||||
|
||||
if m.deviceScaleFactor != 0 {
|
||||
return m.deviceScaleFactor
|
||||
}
|
||||
|
||||
+15
-2
@@ -34,8 +34,13 @@ func (u *UserInterface) initializePlatform() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (u *UserInterface) setApplePressAndHoldEnabled(enabled bool) {
|
||||
// Do nothings.
|
||||
}
|
||||
|
||||
type graphicsDriverCreatorImpl struct {
|
||||
transparent bool
|
||||
colorSpace graphicsdriver.ColorSpace
|
||||
}
|
||||
|
||||
func (g *graphicsDriverCreatorImpl) newAuto() (graphicsdriver.Graphics, GraphicsLibrary, error) {
|
||||
@@ -130,8 +135,8 @@ 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 (u *UserInterface) adjustWindowPosition(x, y int, monitor *Monitor) (int, int, error) {
|
||||
return x, y, nil
|
||||
}
|
||||
|
||||
func initialMonitorByOS() (*Monitor, error) {
|
||||
@@ -200,3 +205,11 @@ func initializeWindowAfterCreation(w *glfw.Window) error {
|
||||
func (u *UserInterface) skipTaskbar() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (u *UserInterface) setDocumentEdited(edited bool) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (u *UserInterface) afterWindowCreation() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
+65
-36
@@ -28,6 +28,7 @@ import (
|
||||
"github.com/hajimehoshi/ebiten/v2/internal/graphicscommand"
|
||||
"github.com/hajimehoshi/ebiten/v2/internal/graphicsdriver"
|
||||
"github.com/hajimehoshi/ebiten/v2/internal/hook"
|
||||
"github.com/hajimehoshi/ebiten/v2/internal/restorable"
|
||||
)
|
||||
|
||||
var (
|
||||
@@ -40,7 +41,6 @@ var (
|
||||
|
||||
func (u *UserInterface) init() error {
|
||||
u.userInterfaceImpl = userInterfaceImpl{
|
||||
foreground: 1,
|
||||
graphicsLibraryInitCh: make(chan struct{}),
|
||||
errCh: make(chan error),
|
||||
|
||||
@@ -48,6 +48,7 @@ func (u *UserInterface) init() error {
|
||||
outsideWidth: 640,
|
||||
outsideHeight: 480,
|
||||
}
|
||||
u.foreground.Store(true)
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -89,7 +90,7 @@ type userInterfaceImpl struct {
|
||||
outsideWidth float64
|
||||
outsideHeight float64
|
||||
|
||||
foreground int32
|
||||
foreground atomic.Bool
|
||||
errCh chan error
|
||||
|
||||
context *context
|
||||
@@ -97,18 +98,20 @@ type userInterfaceImpl struct {
|
||||
inputState InputState
|
||||
touches []TouchForInput
|
||||
|
||||
fpsMode int32
|
||||
renderRequester RenderRequester
|
||||
fpsMode atomic.Int32
|
||||
renderer Renderer
|
||||
|
||||
strictContextRestoration atomic.Bool
|
||||
strictContextRestorationOnce sync.Once
|
||||
|
||||
// uiView is used only on iOS.
|
||||
uiView atomic.Uintptr
|
||||
|
||||
m sync.RWMutex
|
||||
}
|
||||
|
||||
func (u *UserInterface) SetForeground(foreground bool) error {
|
||||
var v int32
|
||||
if foreground {
|
||||
v = 1
|
||||
}
|
||||
atomic.StoreInt32(&u.foreground, v)
|
||||
u.foreground.Store(foreground)
|
||||
|
||||
if foreground {
|
||||
return hook.ResumeAudio()
|
||||
@@ -147,13 +150,20 @@ func (u *UserInterface) runMobile(game Game, options *RunOptions) (err error) {
|
||||
|
||||
u.context = newContext(game)
|
||||
|
||||
g, lib, err := newGraphicsDriver(&graphicsDriverCreatorImpl{}, options.GraphicsLibrary)
|
||||
g, lib, err := newGraphicsDriver(&graphicsDriverCreatorImpl{
|
||||
colorSpace: options.ColorSpace,
|
||||
}, options.GraphicsLibrary)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
u.graphicsDriver = g
|
||||
u.setGraphicsLibrary(lib)
|
||||
close(u.graphicsLibraryInitCh)
|
||||
if options.StrictContextRestoration {
|
||||
u.strictContextRestoration.Store(true)
|
||||
} else {
|
||||
restorable.Disable()
|
||||
}
|
||||
|
||||
for {
|
||||
if err := u.update(); err != nil {
|
||||
@@ -220,7 +230,7 @@ func (u *UserInterface) SetFullscreen(fullscreen bool) {
|
||||
}
|
||||
|
||||
func (u *UserInterface) IsFocused() bool {
|
||||
return atomic.LoadInt32(&u.foreground) != 0
|
||||
return u.foreground.Load()
|
||||
}
|
||||
|
||||
func (u *UserInterface) IsRunnableOnUnfocused() bool {
|
||||
@@ -232,19 +242,19 @@ func (u *UserInterface) SetRunnableOnUnfocused(runnableOnUnfocused bool) {
|
||||
}
|
||||
|
||||
func (u *UserInterface) FPSMode() FPSModeType {
|
||||
return FPSModeType(atomic.LoadInt32(&u.fpsMode))
|
||||
return FPSModeType(u.fpsMode.Load())
|
||||
}
|
||||
|
||||
func (u *UserInterface) SetFPSMode(mode FPSModeType) {
|
||||
atomic.StoreInt32(&u.fpsMode, int32(mode))
|
||||
u.fpsMode.Store(int32(mode))
|
||||
u.updateExplicitRenderingModeIfNeeded(mode)
|
||||
}
|
||||
|
||||
func (u *UserInterface) updateExplicitRenderingModeIfNeeded(fpsMode FPSModeType) {
|
||||
if u.renderRequester == nil {
|
||||
if u.renderer == nil {
|
||||
return
|
||||
}
|
||||
u.renderRequester.SetExplicitRenderingMode(fpsMode == FPSModeVsyncOffMinimum)
|
||||
u.renderer.SetExplicitRenderingMode(fpsMode == FPSModeVsyncOffMinimum)
|
||||
}
|
||||
|
||||
func (u *UserInterface) readInputState(inputState *InputState) {
|
||||
@@ -258,8 +268,10 @@ func (u *UserInterface) Window() Window {
|
||||
}
|
||||
|
||||
type Monitor struct {
|
||||
deviceScaleFactor float64
|
||||
deviceScaleFactorOnce sync.Once
|
||||
width int
|
||||
height int
|
||||
deviceScaleFactor float64
|
||||
inited atomic.Bool
|
||||
|
||||
m sync.Mutex
|
||||
}
|
||||
@@ -270,22 +282,35 @@ func (m *Monitor) Name() string {
|
||||
return ""
|
||||
}
|
||||
|
||||
func (m *Monitor) DeviceScaleFactor() float64 {
|
||||
func (m *Monitor) ensureInit() {
|
||||
if m.inited.Load() {
|
||||
return
|
||||
}
|
||||
|
||||
m.m.Lock()
|
||||
defer m.m.Unlock()
|
||||
// Re-check the state since the state might be changed while locking.
|
||||
if m.inited.Load() {
|
||||
return
|
||||
}
|
||||
width, height, scale, ok := theUI.displayInfo()
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
m.width = width
|
||||
m.height = height
|
||||
m.deviceScaleFactor = scale
|
||||
m.inited.Store(true)
|
||||
}
|
||||
|
||||
// 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()
|
||||
})
|
||||
func (m *Monitor) DeviceScaleFactor() float64 {
|
||||
m.ensureInit()
|
||||
return m.deviceScaleFactor
|
||||
}
|
||||
|
||||
func (m *Monitor) Size() (int, int) {
|
||||
// TODO: Return a valid value.
|
||||
return 0, 0
|
||||
m.ensureInit()
|
||||
return m.width, m.height
|
||||
}
|
||||
|
||||
func (u *UserInterface) AppendMonitors(mons []*Monitor) []*Monitor {
|
||||
@@ -296,26 +321,26 @@ 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()
|
||||
func (u *UserInterface) UpdateInput(keyPressedTimes, keyReleasedTimes [KeyMax + 1]InputTime, runes []rune, touches []TouchForInput) {
|
||||
u.updateInputStateFromOutside(keyPressedTimes, keyReleasedTimes, runes, touches)
|
||||
if FPSModeType(u.fpsMode.Load()) == FPSModeVsyncOffMinimum {
|
||||
u.renderer.RequestRenderIfNeeded()
|
||||
}
|
||||
}
|
||||
|
||||
type RenderRequester interface {
|
||||
type Renderer interface {
|
||||
SetExplicitRenderingMode(explicitRendering bool)
|
||||
RequestRenderIfNeeded()
|
||||
}
|
||||
|
||||
func (u *UserInterface) SetRenderRequester(renderRequester RenderRequester) {
|
||||
u.renderRequester = renderRequester
|
||||
u.updateExplicitRenderingModeIfNeeded(FPSModeType(atomic.LoadInt32(&u.fpsMode)))
|
||||
func (u *UserInterface) SetRenderer(renderer Renderer) {
|
||||
u.renderer = renderer
|
||||
u.updateExplicitRenderingModeIfNeeded(FPSModeType(u.fpsMode.Load()))
|
||||
}
|
||||
|
||||
func (u *UserInterface) ScheduleFrame() {
|
||||
if u.renderRequester != nil && FPSModeType(atomic.LoadInt32(&u.fpsMode)) == FPSModeVsyncOffMinimum {
|
||||
u.renderRequester.RequestRenderIfNeeded()
|
||||
if u.renderer != nil && FPSModeType(u.fpsMode.Load()) == FPSModeVsyncOffMinimum {
|
||||
u.renderer.RequestRenderIfNeeded()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -323,6 +348,10 @@ func (u *UserInterface) updateIconIfNeeded() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (u *UserInterface) UsesStrictContextRestoration() bool {
|
||||
return u.strictContextRestoration.Load()
|
||||
}
|
||||
|
||||
func IsScreenTransparentAvailable() bool {
|
||||
return false
|
||||
}
|
||||
|
||||
+2
@@ -73,6 +73,8 @@ func (u *UserInterface) init() error {
|
||||
}
|
||||
|
||||
func (u *UserInterface) initOnMainThread(options *RunOptions) error {
|
||||
u.setRunning(true)
|
||||
|
||||
n := C.ebitengine_Initialize()
|
||||
g, lib, err := newGraphicsDriver(&graphicsDriverCreatorImpl{
|
||||
nativeWindow: n,
|
||||
|
||||
+2
@@ -69,6 +69,8 @@ func (u *UserInterface) init() error {
|
||||
}
|
||||
|
||||
func (u *UserInterface) initOnMainThread(options *RunOptions) error {
|
||||
u.setRunning(true)
|
||||
|
||||
g, lib, err := newGraphicsDriver(&graphicsDriverCreatorImpl{}, options.GraphicsLibrary)
|
||||
if err != nil {
|
||||
return err
|
||||
|
||||
+54
-4
@@ -34,8 +34,13 @@ func (u *UserInterface) initializePlatform() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (u *UserInterface) setApplePressAndHoldEnabled(enabled bool) {
|
||||
// Do nothings.
|
||||
}
|
||||
|
||||
type graphicsDriverCreatorImpl struct {
|
||||
transparent bool
|
||||
colorSpace graphicsdriver.ColorSpace
|
||||
}
|
||||
|
||||
func (g *graphicsDriverCreatorImpl) newAuto() (graphicsdriver.Graphics, GraphicsLibrary, error) {
|
||||
@@ -109,9 +114,18 @@ func dipToGLFWPixel(x float64, deviceScaleFactor float64) float64 {
|
||||
return x * deviceScaleFactor
|
||||
}
|
||||
|
||||
func (u *UserInterface) adjustWindowPosition(x, y int, monitor *Monitor) (int, int) {
|
||||
func (u *UserInterface) adjustWindowPosition(x, y int, monitor *Monitor) (int, int, error) {
|
||||
if microsoftgdk.IsXbox() {
|
||||
return x, y
|
||||
return x, y, nil
|
||||
}
|
||||
|
||||
// If a window is not decorated, the window should be able to reach the top of the screen (#3118).
|
||||
d, err := u.window.GetAttrib(glfw.Decorated)
|
||||
if err != nil {
|
||||
return 0, 0, err
|
||||
}
|
||||
if d == glfw.False {
|
||||
return x, y, nil
|
||||
}
|
||||
|
||||
mx := monitor.boundsInGLFWPixels.Min.X
|
||||
@@ -123,12 +137,12 @@ func (u *UserInterface) adjustWindowPosition(x, y int, monitor *Monitor) (int, i
|
||||
}
|
||||
t, err := _GetSystemMetrics(_SM_CYCAPTION)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
return 0, 0, err
|
||||
}
|
||||
if y < my+int(t) {
|
||||
y = my + int(t)
|
||||
}
|
||||
return x, y
|
||||
return x, y, nil
|
||||
}
|
||||
|
||||
func initialMonitorByOS() (*Monitor, error) {
|
||||
@@ -242,6 +256,42 @@ func (u *UserInterface) skipTaskbar() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (u *UserInterface) setDocumentEdited(edited bool) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (u *UserInterface) afterWindowCreation() error {
|
||||
if microsoftgdk.IsXbox() {
|
||||
return nil
|
||||
}
|
||||
|
||||
// By default, IME should be disabled (#2918).
|
||||
w, err := u.window.GetWin32Window()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
c, err := _ImmAssociateContext(w, 0)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
u.immContext = c
|
||||
return nil
|
||||
}
|
||||
|
||||
// RestoreIMMContextOnMainThread is called from the main thread.
|
||||
// The textinput package invokes RestoreIMMContextOnMainThread to enable IME inputting.
|
||||
func (u *UserInterface) RestoreIMMContextOnMainThread() error {
|
||||
w, err := u.window.GetWin32Window()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if _, err := _ImmAssociateContext(w, u.immContext); err != nil {
|
||||
return err
|
||||
}
|
||||
u.immContext = 0
|
||||
return nil
|
||||
}
|
||||
|
||||
func init() {
|
||||
if microsoftgdk.IsXbox() {
|
||||
// TimeBeginPeriod might not be defined in Xbox.
|
||||
|
||||
+4
@@ -43,6 +43,7 @@ type Window interface {
|
||||
IsClosingHandled() bool
|
||||
SetMousePassthrough(enabled bool)
|
||||
IsMousePassthrough() bool
|
||||
RequestAttention()
|
||||
}
|
||||
|
||||
type nullWindow struct{}
|
||||
@@ -128,3 +129,6 @@ func (*nullWindow) SetMousePassthrough(enabled bool) {
|
||||
func (*nullWindow) IsMousePassthrough() bool {
|
||||
return false
|
||||
}
|
||||
|
||||
func (*nullWindow) RequestAttention() {
|
||||
}
|
||||
|
||||
+19
@@ -506,3 +506,22 @@ func (w *glfwWindow) IsMousePassthrough() bool {
|
||||
})
|
||||
return v
|
||||
}
|
||||
|
||||
func (w *glfwWindow) RequestAttention() {
|
||||
if w.ui.isTerminated() {
|
||||
return
|
||||
}
|
||||
if !w.ui.isRunning() {
|
||||
// Do nothing
|
||||
return
|
||||
}
|
||||
w.ui.mainThread.Call(func() {
|
||||
if w.ui.isTerminated() {
|
||||
return
|
||||
}
|
||||
if err := w.ui.window.RequestAttention(); err != nil {
|
||||
w.ui.setError(err)
|
||||
return
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user