From d50c1c3074738ca406f598d7588e6a19c144e934 Mon Sep 17 00:00:00 2001 From: Jacalz Date: Thu, 16 Jan 2025 20:40:51 +0100 Subject: [PATCH 01/28] Implement more efficient run loop This uses glfw.WaitEvents() which blocks until either an event occurs or when an empty event is posted. With the new threading model, this was a simple as just posting an empty event from animations when there are animations running. Fixes #2506 --- internal/animation/runner.go | 7 +++++++ internal/driver/glfw/driver.go | 1 + internal/driver/glfw/loop_desktop.go | 6 +++++- internal/driver/glfw/loop_wasm.go | 6 +++++- 4 files changed, 18 insertions(+), 2 deletions(-) diff --git a/internal/animation/runner.go b/internal/animation/runner.go index 0acdb71776..7bc1c689d1 100644 --- a/internal/animation/runner.go +++ b/internal/animation/runner.go @@ -104,6 +104,13 @@ func (r *Runner) runOneFrame() (done bool) { r.animationMutex.Lock() oldList := r.animations r.animationMutex.Unlock() + + if len(oldList) > 0 { + if drv, ok := fyne.CurrentApp().Driver().(interface{ PostEmptyEvent() }); ok { + drv.PostEmptyEvent() + } + } + for _, a := range oldList { if !a.isStopped() && r.tickAnimation(a) { r.nextFrameAnimations = append(r.nextFrameAnimations, a) diff --git a/internal/driver/glfw/driver.go b/internal/driver/glfw/driver.go index 9a862783e8..52681babfc 100644 --- a/internal/driver/glfw/driver.go +++ b/internal/driver/glfw/driver.go @@ -101,6 +101,7 @@ func (d *gLDriver) Quit() { // Only call close once to avoid panic. if running.CompareAndSwap(true, false) { + d.PostEmptyEvent() close(d.done) } } diff --git a/internal/driver/glfw/loop_desktop.go b/internal/driver/glfw/loop_desktop.go index 83a46788b7..c6ae941465 100644 --- a/internal/driver/glfw/loop_desktop.go +++ b/internal/driver/glfw/loop_desktop.go @@ -21,9 +21,13 @@ func (d *gLDriver) initGLFW() { } func (d *gLDriver) pollEvents() { - glfw.PollEvents() // This call blocks while window is being resized, which prevents freeDirtyTextures from being called + glfw.WaitEvents() // This call blocks while window is being resized, which prevents freeDirtyTextures from being called } func (d *gLDriver) Terminate() { glfw.Terminate() } + +func (d *gLDriver) PostEmptyEvent() { + glfw.PostEmptyEvent() +} diff --git a/internal/driver/glfw/loop_wasm.go b/internal/driver/glfw/loop_wasm.go index 37659c4b5b..faacd459f2 100644 --- a/internal/driver/glfw/loop_wasm.go +++ b/internal/driver/glfw/loop_wasm.go @@ -20,9 +20,13 @@ func (d *gLDriver) initGLFW() { } func (d *gLDriver) pollEvents() { - glfw.PollEvents() // This call blocks while window is being resized, which prevents freeDirtyTextures from being called + glfw.WaitEvents() // This call blocks while window is being resized, which prevents freeDirtyTextures from being called } func (d *gLDriver) Terminate() { glfw.Terminate() } + +func (d *gLDriver) PostEmptyEvent() { + glfw.PostEmptyEvent() +} From 90bbd9bf7e8c675405ec3bf53f657a49e35098ea Mon Sep 17 00:00:00 2001 From: Jacalz Date: Thu, 16 Jan 2025 20:55:51 +0100 Subject: [PATCH 02/28] Look at posting events from runOnMain --- internal/driver/glfw/loop.go | 2 ++ internal/driver/glfw/loop_desktop.go | 4 ++++ internal/driver/glfw/loop_wasm.go | 4 ++++ 3 files changed, 10 insertions(+) diff --git a/internal/driver/glfw/loop.go b/internal/driver/glfw/loop.go index eb936f1538..f992282abd 100644 --- a/internal/driver/glfw/loop.go +++ b/internal/driver/glfw/loop.go @@ -50,9 +50,11 @@ func runOnMainWithWait(f func(), wait bool) { defer common.DonePool.Put(done) funcQueue.In() <- funcData{f: f, done: done} + postEmptyEvent() <-done } else { funcQueue.In() <- funcData{f: f} + postEmptyEvent() } } diff --git a/internal/driver/glfw/loop_desktop.go b/internal/driver/glfw/loop_desktop.go index c6ae941465..f4749d4bcb 100644 --- a/internal/driver/glfw/loop_desktop.go +++ b/internal/driver/glfw/loop_desktop.go @@ -31,3 +31,7 @@ func (d *gLDriver) Terminate() { func (d *gLDriver) PostEmptyEvent() { glfw.PostEmptyEvent() } + +func postEmptyEvent() { + glfw.PostEmptyEvent() +} diff --git a/internal/driver/glfw/loop_wasm.go b/internal/driver/glfw/loop_wasm.go index faacd459f2..f3a6b08a00 100644 --- a/internal/driver/glfw/loop_wasm.go +++ b/internal/driver/glfw/loop_wasm.go @@ -30,3 +30,7 @@ func (d *gLDriver) Terminate() { func (d *gLDriver) PostEmptyEvent() { glfw.PostEmptyEvent() } + +func postEmptyEvent() { + glfw.PostEmptyEvent() +} From 057f79ccae73db9aefad05f9fa8c58e58d45e310 Mon Sep 17 00:00:00 2001 From: Drew Weymouth Date: Thu, 16 Jan 2025 16:38:59 -0800 Subject: [PATCH 03/28] Update loop_desktop.go - add waitEvents --- internal/driver/glfw/loop_desktop.go | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/internal/driver/glfw/loop_desktop.go b/internal/driver/glfw/loop_desktop.go index f4749d4bcb..ebbfcedb96 100644 --- a/internal/driver/glfw/loop_desktop.go +++ b/internal/driver/glfw/loop_desktop.go @@ -21,6 +21,10 @@ func (d *gLDriver) initGLFW() { } func (d *gLDriver) pollEvents() { + glfw.PollEvents() // This call blocks while window is being resized, which prevents freeDirtyTextures from being called +} + +func (d *gLDriver) waitEvents() { glfw.WaitEvents() // This call blocks while window is being resized, which prevents freeDirtyTextures from being called } From 370fbcf2b3658021a829c20de078105d7801af2b Mon Sep 17 00:00:00 2001 From: Drew Weymouth Date: Thu, 16 Jan 2025 16:39:33 -0800 Subject: [PATCH 04/28] Update loop_goxjs.go - add waitEvents --- internal/driver/glfw/loop_wasm.go | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/internal/driver/glfw/loop_wasm.go b/internal/driver/glfw/loop_wasm.go index f3a6b08a00..e0cec1d0ce 100644 --- a/internal/driver/glfw/loop_wasm.go +++ b/internal/driver/glfw/loop_wasm.go @@ -20,6 +20,10 @@ func (d *gLDriver) initGLFW() { } func (d *gLDriver) pollEvents() { + glfw.PollEvents() // This call blocks while window is being resized, which prevents freeDirtyTextures from being called +} + +func (d *gLDriver) waitEvents() { glfw.WaitEvents() // This call blocks while window is being resized, which prevents freeDirtyTextures from being called } From cf2f49706100947d9c4f7c7d02ae02980c9dca26 Mon Sep 17 00:00:00 2001 From: Drew Weymouth Date: Thu, 16 Jan 2025 16:41:31 -0800 Subject: [PATCH 05/28] Update runner.go --- internal/animation/runner.go | 31 ++++++++++++++++++++----------- 1 file changed, 20 insertions(+), 11 deletions(-) diff --git a/internal/animation/runner.go b/internal/animation/runner.go index 7bc1c689d1..07d06c4171 100644 --- a/internal/animation/runner.go +++ b/internal/animation/runner.go @@ -11,7 +11,7 @@ import ( type Runner struct { // animationMutex synchronizes access to `animations` and `pendingAnimations` // between the runner goroutine and calls to Start and Stop - animationMutex sync.RWMutex + animationMutex sync.Mutex // animations is the list of animations that are being ticked in the current frame animations []*anim @@ -33,7 +33,7 @@ type Runner struct { // Start will register the passed application and initiate its ticking. func (r *Runner) Start(a *fyne.Animation) { r.animationMutex.Lock() - defer r.animationMutex.Unlock() + hadAnimations := len(r.pendingAnimations) > 0 || len(r.animations) > 0 if !r.runnerStarted { r.runnerStarted = true @@ -51,6 +51,21 @@ func (r *Runner) Start(a *fyne.Animation) { } r.pendingAnimations = append(r.pendingAnimations, newAnim(a)) } + r.animationMutex.Unlock() + + if !hadAnimations { + // wake up main thread if needed to begin running animations + if drv, ok := fyne.CurrentApp().Driver().(interface{ PostEmptyEvent() }); ok { + drv.PostEmptyEvent() + } + } +} + +func (r *Runner) HasAnimations() bool { + r.animationMutex.Lock() + defer r.animationMutex.Unlock() + + return len(r.pendingAnimations) > 0 || len(r.animations) > 0 } // Stop causes an animation to stop ticking (if it was still running) and removes it from the runner. @@ -86,31 +101,25 @@ func (r *Runner) Stop(a *fyne.Animation) { // TickAnimations progresses all running animations by one tick. // This will be called from the driver to update objects immediately before next paint. -func (r *Runner) TickAnimations() { +func (r *Runner) TickAnimations() (done bool) { if !r.runnerStarted { return } - done := r.runOneFrame() + done = r.runOneFrame() if done { r.animationMutex.Lock() r.runnerStarted = false r.animationMutex.Unlock() } + return done } func (r *Runner) runOneFrame() (done bool) { r.animationMutex.Lock() oldList := r.animations r.animationMutex.Unlock() - - if len(oldList) > 0 { - if drv, ok := fyne.CurrentApp().Driver().(interface{ PostEmptyEvent() }); ok { - drv.PostEmptyEvent() - } - } - for _, a := range oldList { if !a.isStopped() && r.tickAnimation(a) { r.nextFrameAnimations = append(r.nextFrameAnimations, a) From 25a636f69f048e52160928fe6d7c42be1a645512 Mon Sep 17 00:00:00 2001 From: Drew Weymouth Date: Thu, 16 Jan 2025 16:42:58 -0800 Subject: [PATCH 06/28] Update loop.go - switch between waitEvents and pollEvents at 60fps with animations --- internal/driver/glfw/loop.go | 136 +++++++++++++++++++++++++++++++++++ 1 file changed, 136 insertions(+) diff --git a/internal/driver/glfw/loop.go b/internal/driver/glfw/loop.go index f992282abd..438dbeec5a 100644 --- a/internal/driver/glfw/loop.go +++ b/internal/driver/glfw/loop.go @@ -136,6 +136,7 @@ func (d *gLDriver) runGL() { f() } +<<<<<<< HEAD eventTick := time.NewTicker(time.Second / 60) for { select { @@ -183,10 +184,40 @@ func (d *gLDriver) runGL() { d.animation.TickAnimations() d.drawSingleFrame() +======= + settingsChange := make(chan fyne.Settings) + fyne.CurrentApp().Settings().AddChangeListener(settingsChange) + + for { + d.waitEvents() + + if d.animation.HasAnimations() { + // Switch to running at 60 fps with d.pollEvents + // until we have no more animations to tick + t := time.NewTicker(time.Second / 60) + for range t.C { + d.pollEvents() + exit, animationsDone := d.runSingleFrame(settingsChange) + if exit { + return + } + if animationsDone { + t.Stop() + break + } + } + } else { + // idle mode: run single frame and sleep on d.waitEvents() above + exit, _ := d.runSingleFrame(settingsChange) + if exit { + return + } +>>>>>>> 58a8be1bf (Update loop.go - switch between waitEvents and pollEvents at 60fps with animations) } } } +<<<<<<< HEAD func (d *gLDriver) destroyWindow(w *window, index int) { w.visible = false w.viewport.Destroy() @@ -204,6 +235,111 @@ func (d *gLDriver) destroyWindow(w *window, index int) { } func (d *gLDriver) repaintWindow(w *window) bool { +======= +func (d *gLDriver) runSingleFrame(settingsChange <-chan fyne.Settings) (exit, animationsDone bool) { + // check if we're shutting down + select { + case <-d.done: + d.Terminate() + l := fyne.CurrentApp().Lifecycle().(*app.Lifecycle) + if f := l.OnStopped(); f != nil { + l.QueueEvent(f) + } + return true, false + default: + break + } + + // run func queued to main + select { + case f := <-funcQueue: + f.f() + f.done <- struct{}{} + default: + break + } + + // apply settings change if any + select { + case set := <-settingsChange: + painter.ClearFontCache() + cache.ResetThemeCaches() + app.ApplySettingsWithCallback(set, fyne.CurrentApp(), func(w fyne.Window) { + c, ok := w.Canvas().(*glCanvas) + if !ok { + return + } + c.applyThemeOutOfTreeObjects() + c.reloadScale() + }) + default: + break + } + + windowsToRemove := 0 + for _, win := range d.windowList() { + w := win.(*window) + if w.viewport == nil { + continue + } + + if w.viewport.ShouldClose() { + windowsToRemove++ + continue + } + + expand := w.shouldExpand + fullScreen := w.fullScreen + + if expand && !fullScreen { + w.fitContent() + shouldExpand := w.shouldExpand + w.shouldExpand = false + view := w.viewport + + if shouldExpand && runtime.GOOS != "js" { + view.SetSize(w.shouldWidth, w.shouldHeight) + } + } + } + animationsDone = d.animation.TickAnimations() + d.drawSingleFrame() + + if windowsToRemove > 0 { + oldWindows := d.windowList() + newWindows := make([]fyne.Window, 0, len(oldWindows)-windowsToRemove) + + for _, win := range oldWindows { + w := win.(*window) + if w.viewport == nil { + continue + } + + if w.viewport.ShouldClose() { + w.visible = false + v := w.viewport + + // remove window from window list + v.Destroy() + w.destroy(d) + continue + } + + newWindows = append(newWindows, win) + } + + d.windows = newWindows + + if len(newWindows) == 0 { + d.Quit() + } + } + + return false, animationsDone +} + +func (d *gLDriver) repaintWindow(w *window) { +>>>>>>> 58a8be1bf (Update loop.go - switch between waitEvents and pollEvents at 60fps with animations) canvas := w.canvas freed := false w.RunWithContext(func() { From 651577b646ef3d7c952a5f6efc99bd156e35ed04 Mon Sep 17 00:00:00 2001 From: Drew Weymouth Date: Thu, 16 Jan 2025 16:51:54 -0800 Subject: [PATCH 07/28] Update animation_test.go - RWMutex -> Mutex --- internal/animation/animation_test.go | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/internal/animation/animation_test.go b/internal/animation/animation_test.go index b3f64b049c..148c886002 100644 --- a/internal/animation/animation_test.go +++ b/internal/animation/animation_test.go @@ -54,9 +54,9 @@ func TestGLDriver_StopAnimation(t *testing.T) { t.Error("animation was not ticked") } run.Stop(a) - run.animationMutex.RLock() + run.animationMutex.Lock() assert.Zero(t, len(run.animations)) - run.animationMutex.RUnlock() + run.animationMutex.Unlock() } func TestGLDriver_StopAnimationImmediatelyAndInsideTick(t *testing.T) { @@ -107,7 +107,7 @@ func TestGLDriver_StopAnimationImmediatelyAndInsideTick(t *testing.T) { wg.Wait() // animations stopped inside tick are really stopped in the next runner cycle time.Sleep(time.Second/60 + 100*time.Millisecond) - run.animationMutex.RLock() + run.animationMutex.Lock() assert.Zero(t, len(run.animations)) - run.animationMutex.RUnlock() + run.animationMutex.Unlock() } From bf1eff64bd8e54f50b59950774ab791e4ac0c1e6 Mon Sep 17 00:00:00 2001 From: Drew Weymouth Date: Fri, 17 Jan 2025 07:24:12 -0800 Subject: [PATCH 08/28] PostEmptyEvent from Canvas.SetDirty() as part of threading transition plan --- internal/driver/common/canvas.go | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/internal/driver/common/canvas.go b/internal/driver/common/canvas.go index ed0e185c3b..39666a850e 100644 --- a/internal/driver/common/canvas.go +++ b/internal/driver/common/canvas.go @@ -349,6 +349,13 @@ func (c *Canvas) CheckDirtyAndClear() bool { // SetDirty sets canvas dirty flag atomically. func (c *Canvas) SetDirty() { c.dirty = true + + // wake up main thread in case we were called from goroutine + // TODO: hide this behind the migration flag introduced in + // https://github.com/fyne-io/fyne/pull/5425 + if drv, ok := fyne.CurrentApp().Driver().(interface{ PostEmptyEvent() }); ok { + drv.PostEmptyEvent() + } } // SetMenuTreeAndFocusMgr sets menu tree and focus manager. From 9c5351c9af7d5d865df3e4f3088da12f973aa23b Mon Sep 17 00:00:00 2001 From: Jacalz Date: Fri, 17 Jan 2025 17:04:39 +0100 Subject: [PATCH 09/28] Factor out window removal to function --- internal/driver/glfw/loop.go | 46 ++++++++++++++++++++---------------- 1 file changed, 25 insertions(+), 21 deletions(-) diff --git a/internal/driver/glfw/loop.go b/internal/driver/glfw/loop.go index 438dbeec5a..5e1e1e211c 100644 --- a/internal/driver/glfw/loop.go +++ b/internal/driver/glfw/loop.go @@ -306,36 +306,40 @@ func (d *gLDriver) runSingleFrame(settingsChange <-chan fyne.Settings) (exit, an d.drawSingleFrame() if windowsToRemove > 0 { - oldWindows := d.windowList() - newWindows := make([]fyne.Window, 0, len(oldWindows)-windowsToRemove) - - for _, win := range oldWindows { - w := win.(*window) - if w.viewport == nil { - continue - } + d.removeWindows(windowsToRemove) + } - if w.viewport.ShouldClose() { - w.visible = false - v := w.viewport + return false, animationsDone +} - // remove window from window list - v.Destroy() - w.destroy(d) - continue - } +func (d *gLDriver) removeWindows(count int) { + oldWindows := d.windowList() + newWindows := make([]fyne.Window, 0, len(oldWindows)-count) - newWindows = append(newWindows, win) + for _, win := range oldWindows { + w := win.(*window) + if w.viewport == nil { + continue } - d.windows = newWindows + if w.viewport.ShouldClose() { + w.visible = false + v := w.viewport - if len(newWindows) == 0 { - d.Quit() + // remove window from window list + v.Destroy() + w.destroy(d) + continue } + + newWindows = append(newWindows, win) } - return false, animationsDone + d.windows = newWindows + + if len(newWindows) == 0 { + d.Quit() + } } func (d *gLDriver) repaintWindow(w *window) { From f3379437c7c9041a3fcb1a9400b439551384ca6a Mon Sep 17 00:00:00 2001 From: Drew Weymouth Date: Fri, 17 Jan 2025 08:22:11 -0800 Subject: [PATCH 10/28] move done to atomic flag instead of channel; run all available queued funcs --- cmd/fyne_demo/main.go | 4 ++-- internal/driver/glfw/driver.go | 9 ++++----- internal/driver/glfw/loop.go | 22 +++++++++++----------- 3 files changed, 17 insertions(+), 18 deletions(-) diff --git a/cmd/fyne_demo/main.go b/cmd/fyne_demo/main.go index 2e56d9c21f..4d4880a6dd 100644 --- a/cmd/fyne_demo/main.go +++ b/cmd/fyne_demo/main.go @@ -246,10 +246,10 @@ func makeNav(setTutorial func(tutorial tutorials.Tutorial), loadPrevious bool) f themes := container.NewGridWithColumns(2, widget.NewButton("Dark", func() { - a.Settings().SetTheme(&forcedVariant{Theme: theme.DefaultTheme(), variant: theme.VariantDark}) + //a.Settings().SetTheme(&forcedVariant{Theme: theme.DefaultTheme(), variant: theme.VariantDark}) }), widget.NewButton("Light", func() { - a.Settings().SetTheme(&forcedVariant{Theme: theme.DefaultTheme(), variant: theme.VariantLight}) + //a.Settings().SetTheme(&forcedVariant{Theme: theme.DefaultTheme(), variant: theme.VariantLight}) }), ) diff --git a/internal/driver/glfw/driver.go b/internal/driver/glfw/driver.go index 52681babfc..aeda717402 100644 --- a/internal/driver/glfw/driver.go +++ b/internal/driver/glfw/driver.go @@ -7,6 +7,7 @@ import ( "image" "os" "runtime" + "sync/atomic" "fyne.io/fyne/v2/internal/async" "github.com/fyne-io/image/ico" @@ -28,7 +29,7 @@ var _ fyne.Driver = (*gLDriver)(nil) type gLDriver struct { windows []fyne.Window - done chan struct{} + done atomic.Bool animation animation.Runner @@ -101,8 +102,8 @@ func (d *gLDriver) Quit() { // Only call close once to avoid panic. if running.CompareAndSwap(true, false) { + d.done.Store(true) d.PostEmptyEvent() - close(d.done) } } @@ -168,7 +169,5 @@ func (d *gLDriver) SetDisableScreenBlanking(disable bool) { func NewGLDriver() *gLDriver { repository.Register("file", intRepo.NewFileRepository()) - return &gLDriver{ - done: make(chan struct{}), - } + return &gLDriver{} } diff --git a/internal/driver/glfw/loop.go b/internal/driver/glfw/loop.go index 5e1e1e211c..8b81ac9916 100644 --- a/internal/driver/glfw/loop.go +++ b/internal/driver/glfw/loop.go @@ -238,25 +238,25 @@ func (d *gLDriver) repaintWindow(w *window) bool { ======= func (d *gLDriver) runSingleFrame(settingsChange <-chan fyne.Settings) (exit, animationsDone bool) { // check if we're shutting down - select { - case <-d.done: + if d.done.Load() { d.Terminate() l := fyne.CurrentApp().Lifecycle().(*app.Lifecycle) if f := l.OnStopped(); f != nil { l.QueueEvent(f) } return true, false - default: - break } - // run func queued to main - select { - case f := <-funcQueue: - f.f() - f.done <- struct{}{} - default: - break + // run funcs queued to main + funcsDone := false + for !funcsDone { + select { + case f := <-funcQueue: + f.f() + f.done <- struct{}{} + default: + funcsDone = true + } } // apply settings change if any From 1e98760e4a414e1efcb669e0805d34e8fb401152 Mon Sep 17 00:00:00 2001 From: Drew Weymouth Date: Fri, 17 Jan 2025 08:24:45 -0800 Subject: [PATCH 11/28] undo accidentally committed fyne_demo change --- cmd/fyne_demo/main.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/cmd/fyne_demo/main.go b/cmd/fyne_demo/main.go index 4d4880a6dd..2e56d9c21f 100644 --- a/cmd/fyne_demo/main.go +++ b/cmd/fyne_demo/main.go @@ -246,10 +246,10 @@ func makeNav(setTutorial func(tutorial tutorials.Tutorial), loadPrevious bool) f themes := container.NewGridWithColumns(2, widget.NewButton("Dark", func() { - //a.Settings().SetTheme(&forcedVariant{Theme: theme.DefaultTheme(), variant: theme.VariantDark}) + a.Settings().SetTheme(&forcedVariant{Theme: theme.DefaultTheme(), variant: theme.VariantDark}) }), widget.NewButton("Light", func() { - //a.Settings().SetTheme(&forcedVariant{Theme: theme.DefaultTheme(), variant: theme.VariantLight}) + a.Settings().SetTheme(&forcedVariant{Theme: theme.DefaultTheme(), variant: theme.VariantLight}) }), ) From 521efc27f0b2d2e4cc211ceb75be4c9e3ede1150 Mon Sep 17 00:00:00 2001 From: Drew Weymouth Date: Fri, 17 Jan 2025 08:30:02 -0800 Subject: [PATCH 12/28] add missed t.Stop() --- internal/driver/glfw/loop.go | 1 + 1 file changed, 1 insertion(+) diff --git a/internal/driver/glfw/loop.go b/internal/driver/glfw/loop.go index 8b81ac9916..282facb649 100644 --- a/internal/driver/glfw/loop.go +++ b/internal/driver/glfw/loop.go @@ -199,6 +199,7 @@ func (d *gLDriver) runGL() { d.pollEvents() exit, animationsDone := d.runSingleFrame(settingsChange) if exit { + t.Stop() return } if animationsDone { From 7563ff986ecf8df1dbe9961ae633811f413c251a Mon Sep 17 00:00:00 2001 From: Drew Weymouth Date: Fri, 17 Jan 2025 08:49:48 -0800 Subject: [PATCH 13/28] remove redundant done flag to just use running --- cmd/fyne_demo/main.go | 4 ++-- internal/driver/glfw/driver.go | 3 --- internal/driver/glfw/loop.go | 2 +- 3 files changed, 3 insertions(+), 6 deletions(-) diff --git a/cmd/fyne_demo/main.go b/cmd/fyne_demo/main.go index 2e56d9c21f..4d4880a6dd 100644 --- a/cmd/fyne_demo/main.go +++ b/cmd/fyne_demo/main.go @@ -246,10 +246,10 @@ func makeNav(setTutorial func(tutorial tutorials.Tutorial), loadPrevious bool) f themes := container.NewGridWithColumns(2, widget.NewButton("Dark", func() { - a.Settings().SetTheme(&forcedVariant{Theme: theme.DefaultTheme(), variant: theme.VariantDark}) + //a.Settings().SetTheme(&forcedVariant{Theme: theme.DefaultTheme(), variant: theme.VariantDark}) }), widget.NewButton("Light", func() { - a.Settings().SetTheme(&forcedVariant{Theme: theme.DefaultTheme(), variant: theme.VariantLight}) + //a.Settings().SetTheme(&forcedVariant{Theme: theme.DefaultTheme(), variant: theme.VariantLight}) }), ) diff --git a/internal/driver/glfw/driver.go b/internal/driver/glfw/driver.go index aeda717402..f128798226 100644 --- a/internal/driver/glfw/driver.go +++ b/internal/driver/glfw/driver.go @@ -7,7 +7,6 @@ import ( "image" "os" "runtime" - "sync/atomic" "fyne.io/fyne/v2/internal/async" "github.com/fyne-io/image/ico" @@ -29,7 +28,6 @@ var _ fyne.Driver = (*gLDriver)(nil) type gLDriver struct { windows []fyne.Window - done atomic.Bool animation animation.Runner @@ -102,7 +100,6 @@ func (d *gLDriver) Quit() { // Only call close once to avoid panic. if running.CompareAndSwap(true, false) { - d.done.Store(true) d.PostEmptyEvent() } } diff --git a/internal/driver/glfw/loop.go b/internal/driver/glfw/loop.go index 282facb649..78c9d59560 100644 --- a/internal/driver/glfw/loop.go +++ b/internal/driver/glfw/loop.go @@ -239,7 +239,7 @@ func (d *gLDriver) repaintWindow(w *window) bool { ======= func (d *gLDriver) runSingleFrame(settingsChange <-chan fyne.Settings) (exit, animationsDone bool) { // check if we're shutting down - if d.done.Load() { + if !running.Load() { d.Terminate() l := fyne.CurrentApp().Lifecycle().(*app.Lifecycle) if f := l.OnStopped(); f != nil { From 57c982f249d3428f21205d1afe0d36e0dbb2d3cb Mon Sep 17 00:00:00 2001 From: Drew Weymouth Date: Fri, 17 Jan 2025 08:58:29 -0800 Subject: [PATCH 14/28] refactor runAnimation loop into its own func --- internal/driver/glfw/loop.go | 93 ++++++++---------------------------- 1 file changed, 21 insertions(+), 72 deletions(-) diff --git a/internal/driver/glfw/loop.go b/internal/driver/glfw/loop.go index 78c9d59560..9f73c8001b 100644 --- a/internal/driver/glfw/loop.go +++ b/internal/driver/glfw/loop.go @@ -136,84 +136,19 @@ func (d *gLDriver) runGL() { f() } -<<<<<<< HEAD - eventTick := time.NewTicker(time.Second / 60) - for { - select { - case <-d.done: - eventTick.Stop() - d.Terminate() - l := fyne.CurrentApp().Lifecycle().(*app.Lifecycle) - if f := l.OnStopped(); f != nil { - l.QueueEvent(f) - } - return - case f := <-funcQueue.Out(): - f.f() - if f.done != nil { - f.done <- struct{}{} - } - case <-eventTick.C: - d.pollEvents() - for i := 0; i < len(d.windows); i++ { - w := d.windows[i].(*window) - if w.viewport == nil { - continue - } - - if w.viewport.ShouldClose() { - d.destroyWindow(w, i) - i-- // Trailing windows are moved forward one step. - continue - } - - expand := w.shouldExpand - fullScreen := w.fullScreen - - if expand && !fullScreen { - w.fitContent() - shouldExpand := w.shouldExpand - w.shouldExpand = false - view := w.viewport - - if shouldExpand && runtime.GOOS != "js" { - view.SetSize(w.shouldWidth, w.shouldHeight) - } - } - } - - d.animation.TickAnimations() - d.drawSingleFrame() -======= - settingsChange := make(chan fyne.Settings) - fyne.CurrentApp().Settings().AddChangeListener(settingsChange) - for { d.waitEvents() if d.animation.HasAnimations() { - // Switch to running at 60 fps with d.pollEvents - // until we have no more animations to tick - t := time.NewTicker(time.Second / 60) - for range t.C { - d.pollEvents() - exit, animationsDone := d.runSingleFrame(settingsChange) - if exit { - t.Stop() - return - } - if animationsDone { - t.Stop() - break - } + // run animations while available + if exit := d.runAnimationLoop(settingsChange); exit { + return } } else { // idle mode: run single frame and sleep on d.waitEvents() above - exit, _ := d.runSingleFrame(settingsChange) - if exit { + if exit, _ := d.runSingleFrame(settingsChange); exit { return } ->>>>>>> 58a8be1bf (Update loop.go - switch between waitEvents and pollEvents at 60fps with animations) } } } @@ -235,8 +170,23 @@ func (d *gLDriver) destroyWindow(w *window, index int) { } } -func (d *gLDriver) repaintWindow(w *window) bool { -======= +// runs a loop that invokes runSingleFrame at 60 fps until there are no more animations +// uses pollEvents to check for events every frame +func (d *gLDriver) runAnimationLoop(settingsChange <-chan fyne.Settings) bool { + t := time.NewTicker(time.Second / 60) + defer t.Stop() + for range t.C { + d.pollEvents() + exit, animationsDone := d.runSingleFrame(settingsChange) + if exit { + return exit + } else if animationsDone { + break + } + } + return false +} + func (d *gLDriver) runSingleFrame(settingsChange <-chan fyne.Settings) (exit, animationsDone bool) { // check if we're shutting down if !running.Load() { @@ -344,7 +294,6 @@ func (d *gLDriver) removeWindows(count int) { } func (d *gLDriver) repaintWindow(w *window) { ->>>>>>> 58a8be1bf (Update loop.go - switch between waitEvents and pollEvents at 60fps with animations) canvas := w.canvas freed := false w.RunWithContext(func() { From e66b5dd674232cee7cd29f174301e787cdf70fd8 Mon Sep 17 00:00:00 2001 From: Drew Weymouth Date: Fri, 17 Jan 2025 09:00:06 -0800 Subject: [PATCH 15/28] undo fyne_demo changes (again) --- cmd/fyne_demo/main.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/cmd/fyne_demo/main.go b/cmd/fyne_demo/main.go index 4d4880a6dd..2e56d9c21f 100644 --- a/cmd/fyne_demo/main.go +++ b/cmd/fyne_demo/main.go @@ -246,10 +246,10 @@ func makeNav(setTutorial func(tutorial tutorials.Tutorial), loadPrevious bool) f themes := container.NewGridWithColumns(2, widget.NewButton("Dark", func() { - //a.Settings().SetTheme(&forcedVariant{Theme: theme.DefaultTheme(), variant: theme.VariantDark}) + a.Settings().SetTheme(&forcedVariant{Theme: theme.DefaultTheme(), variant: theme.VariantDark}) }), widget.NewButton("Light", func() { - //a.Settings().SetTheme(&forcedVariant{Theme: theme.DefaultTheme(), variant: theme.VariantLight}) + a.Settings().SetTheme(&forcedVariant{Theme: theme.DefaultTheme(), variant: theme.VariantLight}) }), ) From 3779dff1a62b8b40f0e7225758cd752ea4933c46 Mon Sep 17 00:00:00 2001 From: Drew Weymouth Date: Sat, 18 Jan 2025 07:34:23 -0800 Subject: [PATCH 16/28] setup test app for animation tests now that we need driver --- internal/animation/animation_test.go | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/internal/animation/animation_test.go b/internal/animation/animation_test.go index 148c886002..14ff5b2ed2 100644 --- a/internal/animation/animation_test.go +++ b/internal/animation/animation_test.go @@ -10,6 +10,7 @@ import ( "github.com/stretchr/testify/assert" "fyne.io/fyne/v2" + "fyne.io/fyne/v2/test" ) func tick(run *Runner) { @@ -18,6 +19,7 @@ func tick(run *Runner) { } func TestGLDriver_StartAnimation(t *testing.T) { + test.NewTempApp(t) done := make(chan float32) run := &Runner{} a := &fyne.Animation{ @@ -37,6 +39,7 @@ func TestGLDriver_StartAnimation(t *testing.T) { } func TestGLDriver_StopAnimation(t *testing.T) { + test.NewTempApp(t) done := make(chan float32) run := &Runner{} a := &fyne.Animation{ @@ -60,6 +63,7 @@ func TestGLDriver_StopAnimation(t *testing.T) { } func TestGLDriver_StopAnimationImmediatelyAndInsideTick(t *testing.T) { + test.NewTempApp(t) var wg sync.WaitGroup run := &Runner{} From e654a6c4ca1a33c9f3b00449fe490a8f2573fd59 Mon Sep 17 00:00:00 2001 From: Drew Weymouth Date: Sat, 18 Jan 2025 07:49:52 -0800 Subject: [PATCH 17/28] don't post empty event if called before GL started --- internal/driver/glfw/loop_desktop.go | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/internal/driver/glfw/loop_desktop.go b/internal/driver/glfw/loop_desktop.go index ebbfcedb96..baf7d80fc0 100644 --- a/internal/driver/glfw/loop_desktop.go +++ b/internal/driver/glfw/loop_desktop.go @@ -33,9 +33,13 @@ func (d *gLDriver) Terminate() { } func (d *gLDriver) PostEmptyEvent() { - glfw.PostEmptyEvent() + if running.Load() { + glfw.PostEmptyEvent() + } } func postEmptyEvent() { - glfw.PostEmptyEvent() + if running.Load() { + glfw.PostEmptyEvent() + } } From fb2d93cc7cbe657219a1f495e9a07172362ba179 Mon Sep 17 00:00:00 2001 From: Drew Weymouth Date: Sat, 18 Jan 2025 08:00:43 -0800 Subject: [PATCH 18/28] use func callback for settings listener so we can synchronously post empty event --- app/settings.go | 3 +++ internal/driver/glfw/loop.go | 25 +++++++++++++++---------- internal/driver/glfw/loop_desktop.go | 8 ++------ 3 files changed, 20 insertions(+), 16 deletions(-) diff --git a/app/settings.go b/app/settings.go index 6214026011..ef81dda5fe 100644 --- a/app/settings.go +++ b/app/settings.go @@ -40,6 +40,9 @@ type settings struct { changeListeners async.Map[chan fyne.Settings, bool] watcher any // normally *fsnotify.Watcher or nil - avoid import in this file + changeListenerFuncsMutex sync.Mutex + changeListenerFuncs []func(fyne.Settings) + schema SettingsSchema } diff --git a/internal/driver/glfw/loop.go b/internal/driver/glfw/loop.go index 9f73c8001b..e320b05639 100644 --- a/internal/driver/glfw/loop.go +++ b/internal/driver/glfw/loop.go @@ -109,6 +109,11 @@ func (d *gLDriver) drawSingleFrame() { refreshingCanvases = refreshingCanvases[:0] } +var ( + settingsToApply fyne.Settings + settingsMutex sync.Mutex +) + func (d *gLDriver) runGL() { if !running.CompareAndSwap(false, true) { return // Run was called twice. @@ -141,19 +146,18 @@ func (d *gLDriver) runGL() { if d.animation.HasAnimations() { // run animations while available - if exit := d.runAnimationLoop(settingsChange); exit { + if exit := d.runAnimationLoop(); exit { return } } else { // idle mode: run single frame and sleep on d.waitEvents() above - if exit, _ := d.runSingleFrame(settingsChange); exit { + if exit, _ := d.runSingleFrame(); exit { return } } } } -<<<<<<< HEAD func (d *gLDriver) destroyWindow(w *window, index int) { w.visible = false w.viewport.Destroy() @@ -172,12 +176,12 @@ func (d *gLDriver) destroyWindow(w *window, index int) { // runs a loop that invokes runSingleFrame at 60 fps until there are no more animations // uses pollEvents to check for events every frame -func (d *gLDriver) runAnimationLoop(settingsChange <-chan fyne.Settings) bool { +func (d *gLDriver) runAnimationLoop() bool { t := time.NewTicker(time.Second / 60) defer t.Stop() for range t.C { d.pollEvents() - exit, animationsDone := d.runSingleFrame(settingsChange) + exit, animationsDone := d.runSingleFrame() if exit { return exit } else if animationsDone { @@ -187,7 +191,7 @@ func (d *gLDriver) runAnimationLoop(settingsChange <-chan fyne.Settings) bool { return false } -func (d *gLDriver) runSingleFrame(settingsChange <-chan fyne.Settings) (exit, animationsDone bool) { +func (d *gLDriver) runSingleFrame() (exit, animationsDone bool) { // check if we're shutting down if !running.Load() { d.Terminate() @@ -211,8 +215,11 @@ func (d *gLDriver) runSingleFrame(settingsChange <-chan fyne.Settings) (exit, an } // apply settings change if any - select { - case set := <-settingsChange: + settingsMutex.Lock() + set := settingsToApply + settingsToApply = nil + settingsMutex.Unlock() + if set != nil { painter.ClearFontCache() cache.ResetThemeCaches() app.ApplySettingsWithCallback(set, fyne.CurrentApp(), func(w fyne.Window) { @@ -223,8 +230,6 @@ func (d *gLDriver) runSingleFrame(settingsChange <-chan fyne.Settings) (exit, an c.applyThemeOutOfTreeObjects() c.reloadScale() }) - default: - break } windowsToRemove := 0 diff --git a/internal/driver/glfw/loop_desktop.go b/internal/driver/glfw/loop_desktop.go index baf7d80fc0..ebbfcedb96 100644 --- a/internal/driver/glfw/loop_desktop.go +++ b/internal/driver/glfw/loop_desktop.go @@ -33,13 +33,9 @@ func (d *gLDriver) Terminate() { } func (d *gLDriver) PostEmptyEvent() { - if running.Load() { - glfw.PostEmptyEvent() - } + glfw.PostEmptyEvent() } func postEmptyEvent() { - if running.Load() { - glfw.PostEmptyEvent() - } + glfw.PostEmptyEvent() } From 751a44a3b03f8dd24798466334e59361b0b8e5ef Mon Sep 17 00:00:00 2001 From: Jacalz Date: Thu, 23 Jan 2025 10:03:11 +0100 Subject: [PATCH 19/28] Use the more efficient window destroy again --- internal/driver/glfw/loop.go | 12 ++++-------- 1 file changed, 4 insertions(+), 8 deletions(-) diff --git a/internal/driver/glfw/loop.go b/internal/driver/glfw/loop.go index e320b05639..1442f26963 100644 --- a/internal/driver/glfw/loop.go +++ b/internal/driver/glfw/loop.go @@ -232,15 +232,15 @@ func (d *gLDriver) runSingleFrame() (exit, animationsDone bool) { }) } - windowsToRemove := 0 - for _, win := range d.windowList() { - w := win.(*window) + for i := 0; i < len(d.windows); i++ { + w := d.windows[i].(*window) if w.viewport == nil { continue } if w.viewport.ShouldClose() { - windowsToRemove++ + d.destroyWindow(w, i) + i-- // Trailing windows are moved forward one position. continue } @@ -261,10 +261,6 @@ func (d *gLDriver) runSingleFrame() (exit, animationsDone bool) { animationsDone = d.animation.TickAnimations() d.drawSingleFrame() - if windowsToRemove > 0 { - d.removeWindows(windowsToRemove) - } - return false, animationsDone } From 8457212f91b0674971671afc46ab163c7cb0137e Mon Sep 17 00:00:00 2001 From: Jacalz Date: Thu, 23 Jan 2025 10:09:54 +0100 Subject: [PATCH 20/28] Nicer naming for waking up driver --- internal/animation/runner.go | 4 ++-- internal/driver/common/canvas.go | 4 ++-- internal/driver/glfw/driver.go | 2 +- internal/driver/glfw/loop.go | 16 ++++++++++++++-- internal/driver/glfw/loop_desktop.go | 8 +++++--- internal/driver/glfw/loop_wasm.go | 8 +++++--- 6 files changed, 29 insertions(+), 13 deletions(-) diff --git a/internal/animation/runner.go b/internal/animation/runner.go index 07d06c4171..c6c1628c5f 100644 --- a/internal/animation/runner.go +++ b/internal/animation/runner.go @@ -55,8 +55,8 @@ func (r *Runner) Start(a *fyne.Animation) { if !hadAnimations { // wake up main thread if needed to begin running animations - if drv, ok := fyne.CurrentApp().Driver().(interface{ PostEmptyEvent() }); ok { - drv.PostEmptyEvent() + if drv, ok := fyne.CurrentApp().Driver().(interface{ WakeUp() }); ok { + drv.WakeUp() } } } diff --git a/internal/driver/common/canvas.go b/internal/driver/common/canvas.go index 39666a850e..9e3df21078 100644 --- a/internal/driver/common/canvas.go +++ b/internal/driver/common/canvas.go @@ -353,8 +353,8 @@ func (c *Canvas) SetDirty() { // wake up main thread in case we were called from goroutine // TODO: hide this behind the migration flag introduced in // https://github.com/fyne-io/fyne/pull/5425 - if drv, ok := fyne.CurrentApp().Driver().(interface{ PostEmptyEvent() }); ok { - drv.PostEmptyEvent() + if drv, ok := fyne.CurrentApp().Driver().(interface{ WakeUp() }); ok { + drv.WakeUp() } } diff --git a/internal/driver/glfw/driver.go b/internal/driver/glfw/driver.go index f128798226..9d60923ced 100644 --- a/internal/driver/glfw/driver.go +++ b/internal/driver/glfw/driver.go @@ -100,7 +100,7 @@ func (d *gLDriver) Quit() { // Only call close once to avoid panic. if running.CompareAndSwap(true, false) { - d.PostEmptyEvent() + d.WakeUp() } } diff --git a/internal/driver/glfw/loop.go b/internal/driver/glfw/loop.go index 1442f26963..fca0b68ef8 100644 --- a/internal/driver/glfw/loop.go +++ b/internal/driver/glfw/loop.go @@ -50,11 +50,11 @@ func runOnMainWithWait(f func(), wait bool) { defer common.DonePool.Put(done) funcQueue.In() <- funcData{f: f, done: done} - postEmptyEvent() + wakeUpDriver() <-done } else { funcQueue.In() <- funcData{f: f} - postEmptyEvent() + wakeUpDriver() } } @@ -141,6 +141,18 @@ func (d *gLDriver) runGL() { f() } +<<<<<<< HEAD +======= + if stg, ok := fyne.CurrentApp().Settings().(interface{ AddChangeListenerFunc(func(fyne.Settings)) }); ok { + stg.AddChangeListenerFunc(func(s fyne.Settings) { + settingsMutex.Lock() + settingsToApply = s + settingsMutex.Unlock() + wakeUpDriver() + }) + } + +>>>>>>> 3174f9b66 (Nicer naming for waking up driver) for { d.waitEvents() diff --git a/internal/driver/glfw/loop_desktop.go b/internal/driver/glfw/loop_desktop.go index ebbfcedb96..3fff74a03f 100644 --- a/internal/driver/glfw/loop_desktop.go +++ b/internal/driver/glfw/loop_desktop.go @@ -32,10 +32,12 @@ func (d *gLDriver) Terminate() { glfw.Terminate() } -func (d *gLDriver) PostEmptyEvent() { - glfw.PostEmptyEvent() +// WakeUp tells the driver to wake up from an idle state. +func (d *gLDriver) WakeUp() { + wakeUpDriver() } -func postEmptyEvent() { +// wakeUpDriver wakes up the driver but in the case a reference to it is not easily available. +func wakeUpDriver() { glfw.PostEmptyEvent() } diff --git a/internal/driver/glfw/loop_wasm.go b/internal/driver/glfw/loop_wasm.go index e0cec1d0ce..24dc8abbf8 100644 --- a/internal/driver/glfw/loop_wasm.go +++ b/internal/driver/glfw/loop_wasm.go @@ -31,10 +31,12 @@ func (d *gLDriver) Terminate() { glfw.Terminate() } -func (d *gLDriver) PostEmptyEvent() { - glfw.PostEmptyEvent() +// WakeUp tells the driver to wake up from an idle state. +func (d *gLDriver) WakeUp() { + wakeUpDriver() } -func postEmptyEvent() { +// wakeUpDriver wakes up the driver but in the case a reference to it is not easily available. +func wakeUpDriver() { glfw.PostEmptyEvent() } From 63c11cfcb3889f4abaf1184d9b1b84709ffc09fd Mon Sep 17 00:00:00 2001 From: Drew Weymouth Date: Fri, 24 Jan 2025 20:21:29 -0300 Subject: [PATCH 21/28] fix bad merge of DoAndWait PR --- internal/driver/glfw/loop.go | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/internal/driver/glfw/loop.go b/internal/driver/glfw/loop.go index fca0b68ef8..ddc1b12a25 100644 --- a/internal/driver/glfw/loop.go +++ b/internal/driver/glfw/loop.go @@ -220,7 +220,9 @@ func (d *gLDriver) runSingleFrame() (exit, animationsDone bool) { select { case f := <-funcQueue: f.f() - f.done <- struct{}{} + if f.done != nil { + f.done <- struct{}{} + } default: funcsDone = true } From 1e0862973dcb28dc623596d2d74643b1650db1a9 Mon Sep 17 00:00:00 2001 From: Drew Weymouth Date: Sun, 2 Feb 2025 09:01:42 -0300 Subject: [PATCH 22/28] remove now-unneeded settingsToApplly --- internal/driver/glfw/loop.go | 5 ----- 1 file changed, 5 deletions(-) diff --git a/internal/driver/glfw/loop.go b/internal/driver/glfw/loop.go index ddc1b12a25..97948e5d73 100644 --- a/internal/driver/glfw/loop.go +++ b/internal/driver/glfw/loop.go @@ -109,11 +109,6 @@ func (d *gLDriver) drawSingleFrame() { refreshingCanvases = refreshingCanvases[:0] } -var ( - settingsToApply fyne.Settings - settingsMutex sync.Mutex -) - func (d *gLDriver) runGL() { if !running.CompareAndSwap(false, true) { return // Run was called twice. From e658a0b9bd55d591d84fac01034b54f2a922dfa1 Mon Sep 17 00:00:00 2001 From: Drew Weymouth Date: Thu, 6 Feb 2025 18:11:36 -0300 Subject: [PATCH 23/28] fix bad merge --- app/settings.go | 3 --- internal/driver/glfw/loop.go | 34 ++-------------------------------- 2 files changed, 2 insertions(+), 35 deletions(-) diff --git a/app/settings.go b/app/settings.go index ef81dda5fe..6214026011 100644 --- a/app/settings.go +++ b/app/settings.go @@ -40,9 +40,6 @@ type settings struct { changeListeners async.Map[chan fyne.Settings, bool] watcher any // normally *fsnotify.Watcher or nil - avoid import in this file - changeListenerFuncsMutex sync.Mutex - changeListenerFuncs []func(fyne.Settings) - schema SettingsSchema } diff --git a/internal/driver/glfw/loop.go b/internal/driver/glfw/loop.go index 97948e5d73..0efbea2a37 100644 --- a/internal/driver/glfw/loop.go +++ b/internal/driver/glfw/loop.go @@ -136,18 +136,6 @@ func (d *gLDriver) runGL() { f() } -<<<<<<< HEAD -======= - if stg, ok := fyne.CurrentApp().Settings().(interface{ AddChangeListenerFunc(func(fyne.Settings)) }); ok { - stg.AddChangeListenerFunc(func(s fyne.Settings) { - settingsMutex.Lock() - settingsToApply = s - settingsMutex.Unlock() - wakeUpDriver() - }) - } - ->>>>>>> 3174f9b66 (Nicer naming for waking up driver) for { d.waitEvents() @@ -213,7 +201,7 @@ func (d *gLDriver) runSingleFrame() (exit, animationsDone bool) { funcsDone := false for !funcsDone { select { - case f := <-funcQueue: + case f := <-funcQueue.Out(): f.f() if f.done != nil { f.done <- struct{}{} @@ -223,24 +211,6 @@ func (d *gLDriver) runSingleFrame() (exit, animationsDone bool) { } } - // apply settings change if any - settingsMutex.Lock() - set := settingsToApply - settingsToApply = nil - settingsMutex.Unlock() - if set != nil { - painter.ClearFontCache() - cache.ResetThemeCaches() - app.ApplySettingsWithCallback(set, fyne.CurrentApp(), func(w fyne.Window) { - c, ok := w.Canvas().(*glCanvas) - if !ok { - return - } - c.applyThemeOutOfTreeObjects() - c.reloadScale() - }) - } - for i := 0; i < len(d.windows); i++ { w := d.windows[i].(*window) if w.viewport == nil { @@ -303,7 +273,7 @@ func (d *gLDriver) removeWindows(count int) { } } -func (d *gLDriver) repaintWindow(w *window) { +func (d *gLDriver) repaintWindow(w *window) bool { canvas := w.canvas freed := false w.RunWithContext(func() { From 4b552669471c8c430458deabff862fd131948d7b Mon Sep 17 00:00:00 2001 From: Drew Weymouth Date: Thu, 6 Feb 2025 18:12:55 -0300 Subject: [PATCH 24/28] fix merge again - remove extra unused func --- internal/driver/glfw/loop.go | 30 ------------------------------ 1 file changed, 30 deletions(-) diff --git a/internal/driver/glfw/loop.go b/internal/driver/glfw/loop.go index 0efbea2a37..d6b8f4d274 100644 --- a/internal/driver/glfw/loop.go +++ b/internal/driver/glfw/loop.go @@ -243,36 +243,6 @@ func (d *gLDriver) runSingleFrame() (exit, animationsDone bool) { return false, animationsDone } -func (d *gLDriver) removeWindows(count int) { - oldWindows := d.windowList() - newWindows := make([]fyne.Window, 0, len(oldWindows)-count) - - for _, win := range oldWindows { - w := win.(*window) - if w.viewport == nil { - continue - } - - if w.viewport.ShouldClose() { - w.visible = false - v := w.viewport - - // remove window from window list - v.Destroy() - w.destroy(d) - continue - } - - newWindows = append(newWindows, win) - } - - d.windows = newWindows - - if len(newWindows) == 0 { - d.Quit() - } -} - func (d *gLDriver) repaintWindow(w *window) bool { canvas := w.canvas freed := false From 350cb1deae49ca00a9f3fd84d41accc1493c41c6 Mon Sep 17 00:00:00 2001 From: Drew Weymouth Date: Fri, 7 Feb 2025 08:58:50 -0300 Subject: [PATCH 25/28] use ring buffer func queue that lets us only wake up driver on first func --- internal/driver/common/ringbuffer.go | 81 +++++++++++++++++++++++ internal/driver/common/ringbuffer_test.go | 46 +++++++++++++ internal/driver/glfw/funcqueue.go | 60 +++++++++++++++++ internal/driver/glfw/loop.go | 34 +++------- 4 files changed, 197 insertions(+), 24 deletions(-) create mode 100644 internal/driver/common/ringbuffer.go create mode 100644 internal/driver/common/ringbuffer_test.go create mode 100644 internal/driver/glfw/funcqueue.go diff --git a/internal/driver/common/ringbuffer.go b/internal/driver/common/ringbuffer.go new file mode 100644 index 0000000000..04b9713aa4 --- /dev/null +++ b/internal/driver/common/ringbuffer.go @@ -0,0 +1,81 @@ +package common + +// RingBuffer is a growable ring buffer supporting +// enqueue and dequeue operations. It is not thread safe. +type RingBuffer[T any] struct { + buf []T + head int + len int +} + +// NewRingBuffer initializes and returns a new RingBuffer. +func NewRingBuffer[T any](initialCap int) RingBuffer[T] { + return RingBuffer[T]{ + buf: make([]T, initialCap), + } +} + +// Len returns the number of elements in the buffer. +func (r *RingBuffer[T]) Len() int { + return r.len +} + +// Push adds the value to the end of the buffer. +func (r *RingBuffer[T]) Push(value T) { + r.checkGrow() + + pos := (r.head + r.len) % len(r.buf) + r.buf[pos] = value + r.len++ +} + +// Pull removes the first item from the buffer, if any. +func (r *RingBuffer[T]) Pull() (value T, ok bool) { + if r.len == 0 { + return value, false + } + return r.pullOne(), true +} + +// PullN removes up to len(buf) items from the queue, +// copying them into the supplied buffer and returning +// the number of elements copied. +func (r *RingBuffer[T]) PullN(buf []T) int { + l := len(buf) + if r.len < l { + l = r.len + } + if l == 0 { + return 0 + } + for i := 0; i < l; i++ { + buf[i] = r.pullOne() + } + return l +} + +func (r *RingBuffer[T]) pullOne() (value T) { + emptyT := value + value = r.buf[r.head] + r.buf[r.head] = emptyT + + if r.len == 1 { + r.head = 0 + } else { + r.head = (r.head + 1) % len(r.buf) + } + r.len-- + + return value +} + +func (r *RingBuffer[T]) checkGrow() { + if l := len(r.buf); r.len == l { + newBuf := make([]T, l*2) + for i := 0; i < r.len; i++ { + newBuf[i] = r.buf[(r.head+i)%l] + } + r.head = 0 + r.buf = newBuf + } +} diff --git a/internal/driver/common/ringbuffer_test.go b/internal/driver/common/ringbuffer_test.go new file mode 100644 index 0000000000..323c67162d --- /dev/null +++ b/internal/driver/common/ringbuffer_test.go @@ -0,0 +1,46 @@ +package common + +import ( + "testing" + + "github.com/stretchr/testify/assert" +) + +func Test_RingBuffer(t *testing.T) { + buf := NewRingBuffer[int](8) + + assertDequeue := func(expect int) { + t.Helper() + got, valid := buf.Pull() + assert.True(t, valid, "got invalid pull") + assert.Equal(t, expect, got, "invalid pull result") + } + + buf.Push(1) + buf.Push(2) + buf.Push(3) + buf.Push(4) + buf.Push(5) + buf.Push(6) + buf.Push(7) + buf.Push(8) + buf.Push(9) + buf.Push(10) + + assertDequeue(1) + assertDequeue(2) + + buf.Push(11) + buf.Push(12) + buf.Push(13) + buf.Push(14) + buf.Push(15) + buf.Push(16) + buf.Push(17) + buf.Push(18) + buf.Push(19) + + for i := 3; i <= 19; i++ { + assertDequeue(i) + } +} diff --git a/internal/driver/glfw/funcqueue.go b/internal/driver/glfw/funcqueue.go new file mode 100644 index 0000000000..cebae86175 --- /dev/null +++ b/internal/driver/glfw/funcqueue.go @@ -0,0 +1,60 @@ +package glfw + +import ( + "sync" + + "fyne.io/fyne/v2/internal/driver/common" +) + +type funcData struct { + f func() + done chan struct{} // Zero allocation signalling channel +} + +// glfwFuncQueue is a queue to hold funcs posted to +// execute on the main thread +type glfwFuncQueue struct { + mutex sync.Mutex + + buffer common.RingBuffer[funcData] +} + +func newGlfwFuncQueue() *glfwFuncQueue { + return &glfwFuncQueue{ + buffer: common.NewRingBuffer[funcData](1024), + } +} + +// Push adds a new func to the queue. It wakes up +// the driver's main thread if necessary to begin +// processing queued functions. +func (g *glfwFuncQueue) Push(f func(), wait bool) { + g.mutex.Lock() + data := funcData{f: f} + if wait { + done := common.DonePool.Get() + defer common.DonePool.Put(done) + + data.done = done + } + g.buffer.Push(data) + l := g.buffer.Len() + g.mutex.Unlock() + + if l == 1 { + // only need to wake up driver if there + // were previously no funcs queued. + // otherwise, it has already been woken. + wakeUpDriver() + } + if wait { + <-data.done + } +} + +// PullN pulls up to N funcs from the queue. +func (g *glfwFuncQueue) PullN(buf []funcData) int { + g.mutex.Lock() + defer g.mutex.Unlock() + return g.buffer.PullN(buf) +} diff --git a/internal/driver/glfw/loop.go b/internal/driver/glfw/loop.go index d6b8f4d274..a9cb0b60ef 100644 --- a/internal/driver/glfw/loop.go +++ b/internal/driver/glfw/loop.go @@ -15,13 +15,8 @@ import ( "fyne.io/fyne/v2/internal/scale" ) -type funcData struct { - f func() - done chan struct{} // Zero allocation signalling channel -} - // channel for queuing functions on the main thread -var funcQueue = async.NewUnboundedChan[funcData]() +var funcQueue = newGlfwFuncQueue() var running atomic.Bool var initOnce = &sync.Once{} @@ -45,17 +40,7 @@ func runOnMainWithWait(f func(), wait bool) { return } - if wait { - done := common.DonePool.Get() - defer common.DonePool.Put(done) - - funcQueue.In() <- funcData{f: f, done: done} - wakeUpDriver() - <-done - } else { - funcQueue.In() <- funcData{f: f} - wakeUpDriver() - } + funcQueue.Push(f, wait) } // Preallocate to avoid allocations on every drawSingleFrame. @@ -198,17 +183,18 @@ func (d *gLDriver) runSingleFrame() (exit, animationsDone bool) { } // run funcs queued to main + var buf [16]funcData funcsDone := false for !funcsDone { - select { - case f := <-funcQueue.Out(): - f.f() - if f.done != nil { - f.done <- struct{}{} + n := funcQueue.PullN(buf[:]) + for i := 0; i < n; i++ { + buf[i].f() + if buf[i].done != nil { + buf[i].done <- struct{}{} } - default: - funcsDone = true + buf[i] = funcData{} } + funcsDone = n == 0 } for i := 0; i < len(d.windows); i++ { From a117e7c87f063705270193e455be3d42c0ec3d2d Mon Sep 17 00:00:00 2001 From: Drew Weymouth Date: Fri, 7 Feb 2025 10:28:20 -0300 Subject: [PATCH 26/28] queue GLFW events to be processed in main loop instead of directly in callbacks --- internal/animation/runner.go | 2 +- internal/driver/glfw/event.go | 151 +++++++++++++++++++++++++ internal/driver/glfw/loop.go | 40 ++++--- internal/driver/glfw/window_desktop.go | 64 +++++++---- internal/driver/glfw/window_wasm.go | 68 +++++++---- 5 files changed, 264 insertions(+), 61 deletions(-) create mode 100644 internal/driver/glfw/event.go diff --git a/internal/animation/runner.go b/internal/animation/runner.go index c6c1628c5f..f8ca9d0f55 100644 --- a/internal/animation/runner.go +++ b/internal/animation/runner.go @@ -103,7 +103,7 @@ func (r *Runner) Stop(a *fyne.Animation) { // This will be called from the driver to update objects immediately before next paint. func (r *Runner) TickAnimations() (done bool) { if !r.runnerStarted { - return + return true } done = r.runOneFrame() diff --git a/internal/driver/glfw/event.go b/internal/driver/glfw/event.go new file mode 100644 index 0000000000..e64ad777f0 --- /dev/null +++ b/internal/driver/glfw/event.go @@ -0,0 +1,151 @@ +package glfw + +import ( + "fyne.io/fyne/v2/internal/driver/common" +) + +type eventType int + +const ( + emptyEvent eventType = iota + moveEvent + resizeEvent + frameSizeEvent + refreshEvent + closeEvent + //contentScaleEvent + mouseMoveEvent + mouseClickEvent + mouseScrollEvent + keyPressEvent + charInputEvent + focusEvent + dropEvent +) + +// union of different GLFW events we receive +type event struct { + eventType eventType + + window *window + + intArg1 int + intArg2 int + intArg3 int + intArg4 int + + float64Arg1 float64 + float64Arg2 float64 +} + +var eventQueue = common.NewRingBuffer[event](1024) + +func newCloseEvent(win *window) event { + return event{eventType: closeEvent, window: win} +} + +func newRefreshEvent(win *window) event { + return event{eventType: refreshEvent, window: win} +} + +func newMoveEvent(win *window, x, y int) event { + return event{ + eventType: moveEvent, + window: win, + intArg1: x, + intArg2: y, + } +} + +func newFrameSizeEvent(win *window, width, height int) event { + return event{ + eventType: frameSizeEvent, + window: win, + intArg1: width, + intArg2: height, + } +} + +func newMouseMoveEvent(win *window, xpos, ypos float64) event { + return event{ + eventType: mouseMoveEvent, + window: win, + float64Arg1: xpos, + float64Arg2: ypos, + } +} + +func newMouseClickEvent(win *window, btn, action, mods int) event { + return event{ + eventType: mouseClickEvent, + window: win, + intArg1: btn, + intArg2: action, + intArg3: mods, + } +} + +func newMouseScrollEvent(win *window, xoff, yoff float64) event { + return event{ + eventType: mouseScrollEvent, + window: win, + float64Arg1: xoff, + float64Arg2: yoff, + } +} + +func newKeyPressEvent(win *window, key, scancode, action, mods int) event { + return event{ + eventType: keyPressEvent, + window: win, + intArg1: key, + intArg2: scancode, + intArg3: action, + intArg4: mods, + } +} + +func newCharInputEvent(win *window, char rune) event { + return event{ + eventType: charInputEvent, + window: win, + intArg1: int(char), + } +} + +func newFocusEvent(win *window, focused bool) event { + f := 0 + if focused { + f = 1 + } + return event{ + eventType: focusEvent, + window: win, + intArg1: f, + } +} + +func processEvent(e event) { + switch e.eventType { + case closeEvent: + e.window.closed(e.window.viewport) + case refreshEvent: + e.window.refresh(e.window.viewport) + case frameSizeEvent: + e.window.frameSized(e.window.viewport, e.intArg1, e.intArg2) + case moveEvent: + e.window.moved(e.window.viewport, e.intArg1, e.intArg2) + case mouseMoveEvent: + e.window.processMouseMoved(e.float64Arg1, e.float64Arg2) + case mouseClickEvent: + e.window.mouseClicked(e.window.viewport, e.intArg1, e.intArg2, e.intArg3) + case mouseScrollEvent: + e.window.mouseScrolled(e.window.viewport, e.float64Arg1, e.float64Arg2) + case keyPressEvent: + e.window.keyPressed(e.window.viewport, e.intArg1, e.intArg2, e.intArg3, e.intArg4) + case charInputEvent: + e.window.charInput(e.window.viewport, rune(e.intArg1)) + case focusEvent: + e.window.focused(e.window.viewport, e.intArg1 > 0) + } +} diff --git a/internal/driver/glfw/loop.go b/internal/driver/glfw/loop.go index a9cb0b60ef..0fc7ebab13 100644 --- a/internal/driver/glfw/loop.go +++ b/internal/driver/glfw/loop.go @@ -15,7 +15,7 @@ import ( "fyne.io/fyne/v2/internal/scale" ) -// channel for queuing functions on the main thread +// for queuing functions on the main thread var funcQueue = newGlfwFuncQueue() var running atomic.Bool var initOnce = &sync.Once{} @@ -122,18 +122,11 @@ func (d *gLDriver) runGL() { } for { + // idle until input events or funcs queued to main d.waitEvents() - - if d.animation.HasAnimations() { - // run animations while available - if exit := d.runAnimationLoop(); exit { - return - } - } else { - // idle mode: run single frame and sleep on d.waitEvents() above - if exit, _ := d.runSingleFrame(); exit { - return - } + // run active loop until we are exiting, or idle again + if exit := d.runActiveLoop(); exit { + return } } } @@ -154,9 +147,9 @@ func (d *gLDriver) destroyWindow(w *window, index int) { } } -// runs a loop that invokes runSingleFrame at 60 fps until there are no more animations -// uses pollEvents to check for events every frame -func (d *gLDriver) runAnimationLoop() bool { +// runs a loop that invokes runSingleFrame at 60 fps until there are no more +// animations or events. uses pollEvents to check for events every frame. +func (d *gLDriver) runActiveLoop() bool { t := time.NewTicker(time.Second / 60) defer t.Stop() for range t.C { @@ -171,7 +164,7 @@ func (d *gLDriver) runAnimationLoop() bool { return false } -func (d *gLDriver) runSingleFrame() (exit, animationsDone bool) { +func (d *gLDriver) runSingleFrame() (exit, idle bool) { // check if we're shutting down if !running.Load() { d.Terminate() @@ -182,6 +175,17 @@ func (d *gLDriver) runSingleFrame() (exit, animationsDone bool) { return true, false } + // process received glfw events + noEvents := true + for { + evt, ok := eventQueue.Pull() + if !ok { + break + } + noEvents = false + processEvent(evt) + } + // run funcs queued to main var buf [16]funcData funcsDone := false @@ -223,10 +227,10 @@ func (d *gLDriver) runSingleFrame() (exit, animationsDone bool) { } } } - animationsDone = d.animation.TickAnimations() + animationsDone := d.animation.TickAnimations() d.drawSingleFrame() - return false, animationsDone + return false, animationsDone && noEvents } func (d *gLDriver) repaintWindow(w *window) bool { diff --git a/internal/driver/glfw/window_desktop.go b/internal/driver/glfw/window_desktop.go index 21a9822208..06b20bca87 100644 --- a/internal/driver/glfw/window_desktop.go +++ b/internal/driver/glfw/window_desktop.go @@ -385,9 +385,9 @@ func (w *window) mouseMoved(_ *glfw.Window, xpos, ypos float64) { w.processMouseMoved(xpos, ypos) } -func (w *window) mouseClicked(_ *glfw.Window, btn glfw.MouseButton, action glfw.Action, mods glfw.ModifierKey) { - button, modifiers := convertMouseButton(btn, mods) - mouseAction := convertAction(action) +func (w *window) mouseClicked(_ *glfw.Window, btn int, action int, mods int) { + button, modifiers := convertMouseButton(glfw.MouseButton(btn), glfw.ModifierKey(mods)) + mouseAction := convertAction(glfw.Action(action)) w.processMouseClicked(button, mouseAction, modifiers) } @@ -618,12 +618,15 @@ func convertASCII(key glfw.Key) fyne.KeyName { return fyne.KeyName(rune(key)) } -func (w *window) keyPressed(_ *glfw.Window, key glfw.Key, scancode int, action glfw.Action, mods glfw.ModifierKey) { - keyName := keyToName(key, scancode) - keyDesktopModifier := desktopModifier(mods) - w.driver.currentKeyModifiers = desktopModifierCorrected(mods, key, action) - keyAction := convertAction(action) - keyASCII := convertASCII(key) +func (w *window) keyPressed(_ *glfw.Window, key int, scancode int, action int, mods int) { + k := glfw.Key(key) + a := glfw.Action(action) + m := glfw.ModifierKey(mods) + keyName := keyToName(k, scancode) + keyDesktopModifier := desktopModifier(m) + w.driver.currentKeyModifiers = desktopModifierCorrected(m, k, a) + keyAction := convertAction(a) + keyASCII := convertASCII(k) w.processKeyPressed(keyName, keyASCII, scancode, keyAction, keyDesktopModifier) } @@ -758,18 +761,41 @@ func (w *window) create() { w.setDarkMode() - win.SetCloseCallback(w.closed) - win.SetPosCallback(w.moved) + win.SetCloseCallback(func(_ *glfw.Window) { + eventQueue.Push(newCloseEvent(w)) + }) + win.SetPosCallback(func(_ *glfw.Window, xpos, ypos int) { + eventQueue.Push(newMoveEvent(w, xpos, ypos)) + }) + win.SetFramebufferSizeCallback(func(_ *glfw.Window, width, height int) { + eventQueue.Push(newFrameSizeEvent(w, width, height)) + }) + win.SetRefreshCallback(func(_ *glfw.Window) { + eventQueue.Push(newRefreshEvent(w)) + }) + // window size callbacks must be handled synchronously + // instead of queued, since GLFW will not allow waitEvents + // to return until a resize is *completed* win.SetSizeCallback(w.resized) - win.SetFramebufferSizeCallback(w.frameSized) - win.SetRefreshCallback(w.refresh) win.SetContentScaleCallback(w.scaled) - win.SetCursorPosCallback(w.mouseMoved) - win.SetMouseButtonCallback(w.mouseClicked) - win.SetScrollCallback(w.mouseScrolled) - win.SetKeyCallback(w.keyPressed) - win.SetCharCallback(w.charInput) - win.SetFocusCallback(w.focused) + win.SetCursorPosCallback(func(_ *glfw.Window, xpos, ypos float64) { + eventQueue.Push(newMouseMoveEvent(w, xpos, ypos)) + }) + win.SetMouseButtonCallback(func(_ *glfw.Window, button glfw.MouseButton, action glfw.Action, mods glfw.ModifierKey) { + eventQueue.Push(newMouseClickEvent(w, int(button), int(action), int(mods))) + }) + win.SetScrollCallback(func(_ *glfw.Window, xoff, yoff float64) { + eventQueue.Push(newMouseScrollEvent(w, xoff, yoff)) + }) + win.SetKeyCallback(func(_ *glfw.Window, key glfw.Key, scancode int, action glfw.Action, mods glfw.ModifierKey) { + eventQueue.Push(newKeyPressEvent(w, int(key), scancode, int(action), int(mods))) + }) + win.SetCharCallback(func(_ *glfw.Window, char rune) { + eventQueue.Push(newCharInputEvent(w, char)) + }) + win.SetFocusCallback(func(_ *glfw.Window, focused bool) { + eventQueue.Push(newFocusEvent(w, focused)) + }) w.canvas.detectedScale = w.detectScale() w.canvas.scale = w.calculatedScale() diff --git a/internal/driver/glfw/window_wasm.go b/internal/driver/glfw/window_wasm.go index 44470a1c62..6c4c767550 100644 --- a/internal/driver/glfw/window_wasm.go +++ b/internal/driver/glfw/window_wasm.go @@ -189,12 +189,15 @@ func (w *window) setCustomCursor(rawCursor *Cursor, isCustomCursor bool) { } func (w *window) mouseMoved(_ *glfw.Window, xpos, ypos float64) { - w.processMouseMoved(w.scaleInput(xpos), w.scaleInput(ypos)) + w.processMouseMoved(xpos, ypos) } -func (w *window) mouseClicked(viewport *glfw.Window, btn glfw.MouseButton, action glfw.Action, mods glfw.ModifierKey) { - button, modifiers := convertMouseButton(btn, mods) - mouseAction := convertAction(action) +func (w *window) scaled(_ *glfw.Window, x, y float32) { +} + +func (w *window) mouseClicked(viewport *glfw.Window, btn int, action int, mods int) { + button, modifiers := convertMouseButton(glfw.MouseButton(btn), glfw.ModifierKey(mods)) + mouseAction := convertAction(glfw.Action(action)) w.processMouseClicked(button, mouseAction, modifiers) } @@ -424,11 +427,14 @@ func convertASCII(key glfw.Key) fyne.KeyName { return fyne.KeyName(rune(key)) } -func (w *window) keyPressed(viewport *glfw.Window, key glfw.Key, scancode int, action glfw.Action, mods glfw.ModifierKey) { - keyName := keyToName(key, scancode) - keyDesktopModifier := desktopModifier(mods) - keyAction := convertAction(action) - keyASCII := convertASCII(key) +func (w *window) keyPressed(viewport *glfw.Window, key int, scancode int, action int, mods int) { + k := glfw.Key(key) + a := glfw.Action(action) + m := glfw.ModifierKey(mods) + keyName := keyToName(k, scancode) + keyDesktopModifier := desktopModifier(m) + keyAction := convertAction(a) + keyASCII := convertASCII(k) w.processKeyPressed(keyName, keyASCII, scancode, keyAction, keyDesktopModifier) } @@ -516,17 +522,37 @@ func (w *window) create() { w.setDarkMode() - win.SetCloseCallback(w.closed) - win.SetPosCallback(w.moved) + win.SetCloseCallback(func(_ *glfw.Window) { + eventQueue.Push(newCloseEvent(w)) + }) + win.SetPosCallback(func(_ *glfw.Window, xpos, ypos int) { + eventQueue.Push(newMoveEvent(w, xpos, ypos)) + }) + win.SetFramebufferSizeCallback(func(_ *glfw.Window, width, height int) { + eventQueue.Push(newFrameSizeEvent(w, width, height)) + }) + win.SetRefreshCallback(func(_ *glfw.Window) { + eventQueue.Push(newRefreshEvent(w)) + }) win.SetSizeCallback(w.resized) - win.SetFramebufferSizeCallback(w.frameSized) - win.SetRefreshCallback(w.refresh) - win.SetCursorPosCallback(w.mouseMoved) - win.SetMouseButtonCallback(w.mouseClicked) - win.SetScrollCallback(w.mouseScrolled) - win.SetKeyCallback(w.keyPressed) - win.SetCharCallback(w.charInput) - win.SetFocusCallback(w.focused) + win.SetCursorPosCallback(func(_ *glfw.Window, xpos, ypos float64) { + eventQueue.Push(newMouseMoveEvent(w, xpos, ypos)) + }) + win.SetMouseButtonCallback(func(_ *glfw.Window, button glfw.MouseButton, action glfw.Action, mods glfw.ModifierKey) { + eventQueue.Push(newMouseClickEvent(w, int(button), int(action), int(mods))) + }) + win.SetScrollCallback(func(_ *glfw.Window, xoff, yoff float64) { + eventQueue.Push(newMouseScrollEvent(w, xoff, yoff)) + }) + win.SetKeyCallback(func(_ *glfw.Window, key glfw.Key, scancode int, action glfw.Action, mods glfw.ModifierKey) { + eventQueue.Push(newKeyPressEvent(w, int(key), scancode, int(action), int(mods))) + }) + win.SetCharCallback(func(_ *glfw.Window, char rune) { + eventQueue.Push(newCharInputEvent(w, char)) + }) + win.SetFocusCallback(func(_ *glfw.Window, focused bool) { + eventQueue.Push(newFocusEvent(w, focused)) + }) w.canvas.detectedScale = w.detectScale() w.canvas.scale = w.calculatedScale() @@ -669,7 +695,3 @@ func (w *wrapInner) updateVisibility() { c.Overlays().Remove(multi) } } - -func (w *window) scaleInput(in float64) float64 { - return in -} From 6441f033d271296d190b0436dceb9ee47cec493d Mon Sep 17 00:00:00 2001 From: Drew Weymouth Date: Fri, 7 Feb 2025 10:47:08 -0300 Subject: [PATCH 27/28] update arg types in window_test --- internal/driver/glfw/window_test.go | 116 ++++++++++++++-------------- 1 file changed, 58 insertions(+), 58 deletions(-) diff --git a/internal/driver/glfw/window_test.go b/internal/driver/glfw/window_test.go index 5e9fffff2f..05dfe8f183 100644 --- a/internal/driver/glfw/window_test.go +++ b/internal/driver/glfw/window_test.go @@ -368,7 +368,7 @@ func TestWindow_HandleDragging(t *testing.T) { assert.Nil(t, d2.popDragEvent()) // no drag event on secondary mouseDown - w.mouseClicked(w.viewport, glfw.MouseButton2, glfw.Press, 0) + w.mouseClicked(w.viewport, int(glfw.MouseButton2), int(glfw.Press), 0) assert.Nil(t, d1.popDragEvent()) assert.Nil(t, d2.popDragEvent()) @@ -378,7 +378,7 @@ func TestWindow_HandleDragging(t *testing.T) { assert.Nil(t, d2.popDragEvent()) // no drag end event on secondary mouseUp - w.mouseClicked(w.viewport, glfw.MouseButton2, glfw.Release, 0) + w.mouseClicked(w.viewport, int(glfw.MouseButton2), int(glfw.Release), 0) assert.Nil(t, d1.popDragEndEvent()) assert.Nil(t, d2.popDragEndEvent()) @@ -388,7 +388,7 @@ func TestWindow_HandleDragging(t *testing.T) { assert.Nil(t, d2.popDragEvent()) // no drag event on secondary mouseDown - w.mouseClicked(w.viewport, glfw.MouseButton2, glfw.Press, 0) + w.mouseClicked(w.viewport, int(glfw.MouseButton2), int(glfw.Press), 0) assert.Nil(t, d1.popDragEvent()) assert.Nil(t, d2.popDragEvent()) @@ -398,7 +398,7 @@ func TestWindow_HandleDragging(t *testing.T) { assert.Nil(t, d2.popDragEvent()) // no drag end event on secondary mouseUp - w.mouseClicked(w.viewport, glfw.MouseButton2, glfw.Release, 0) + w.mouseClicked(w.viewport, int(glfw.MouseButton2), int(glfw.Release), 0) assert.Nil(t, d1.popDragEndEvent()) assert.Nil(t, d2.popDragEndEvent()) @@ -408,7 +408,7 @@ func TestWindow_HandleDragging(t *testing.T) { assert.Nil(t, d2.popDragEvent()) // no drag event on mouseDown - w.mouseClicked(w.viewport, glfw.MouseButton1, glfw.Press, 0) + w.mouseClicked(w.viewport, int(glfw.MouseButton1), int(glfw.Press), 0) assert.Nil(t, d1.popDragEvent()) assert.Nil(t, d2.popDragEvent()) @@ -451,7 +451,7 @@ func TestWindow_HandleDragging(t *testing.T) { assert.Nil(t, d2.popDragEvent()) // drag end event on mouseUp - w.mouseClicked(w.viewport, glfw.MouseButton1, glfw.Release, 0) + w.mouseClicked(w.viewport, int(glfw.MouseButton1), int(glfw.Release), 0) assert.Nil(t, d1.popDragEvent()) assert.NotNil(t, d1.popDragEndEvent()) assert.Nil(t, d2.popDragEvent()) @@ -462,7 +462,7 @@ func TestWindow_HandleDragging(t *testing.T) { assert.Nil(t, d2.popDragEvent()) // no drag event on mouseDown - w.mouseClicked(w.viewport, glfw.MouseButton1, glfw.Press, 0) + w.mouseClicked(w.viewport, int(glfw.MouseButton1), int(glfw.Press), 0) assert.Nil(t, d1.popDragEvent()) assert.Nil(t, d2.popDragEvent()) @@ -492,7 +492,7 @@ func TestWindow_DragObjectThatMoves(t *testing.T) { // drag -1,-1 w.mouseMoved(w.viewport, 12, 12) - w.mouseClicked(w.viewport, glfw.MouseButton1, glfw.Press, 0) + w.mouseClicked(w.viewport, int(glfw.MouseButton1), int(glfw.Press), 0) w.mouseMoved(w.viewport, 10, 10) assert.Equal(t, &fyne.DragEvent{ @@ -534,9 +534,9 @@ func TestWindow_DragIntoNewObjectKeepingFocus(t *testing.T) { // drag from d1 into d2 w.mouseMoved(w.viewport, 11, 11) - w.mouseClicked(w.viewport, glfw.MouseButton1, glfw.Press, 0) + w.mouseClicked(w.viewport, int(glfw.MouseButton1), int(glfw.Press), 0) w.mouseMoved(w.viewport, 21, 11) - w.mouseClicked(w.viewport, glfw.MouseButton1, glfw.Release, 0) + w.mouseClicked(w.viewport, int(glfw.MouseButton1), int(glfw.Release), 0) // we should only have 2 mouse events on d1 assert.Equal(t, @@ -573,9 +573,9 @@ func TestWindow_NoDragEndWithoutDraggedEvent(t *testing.T) { w.mouseMoved(w.viewport, 9, 9) // mouse down (potential drag) - w.mouseClicked(w.viewport, glfw.MouseButton1, glfw.Press, 0) + w.mouseClicked(w.viewport, int(glfw.MouseButton1), int(glfw.Press), 0) // mouse release without move (not really a drag) - w.mouseClicked(w.viewport, glfw.MouseButton1, glfw.Release, 0) + w.mouseClicked(w.viewport, int(glfw.MouseButton1), int(glfw.Release), 0) assert.Nil(t, do.popDragEvent(), "no drag event without move") assert.Nil(t, do.popDragEndEvent(), "no drag end event without drag event") @@ -598,7 +598,7 @@ func TestWindow_HoverableOnDragging(t *testing.T) { AbsolutePosition: fyne.NewPos(10, 10)}}, dh.popMouseInEvent(), ) - w.mouseClicked(w.viewport, glfw.MouseButton1, glfw.Press, 0) + w.mouseClicked(w.viewport, int(glfw.MouseButton1), int(glfw.Press), 0) w.mouseMoved(w.viewport, 12, 12) assert.Equal(t, &fyne.DragEvent{ @@ -636,16 +636,16 @@ func TestWindow_HoverableOnDragging(t *testing.T) { assert.Nil(t, dh.popMouseMovedEvent()) // no hover events on end of drag event - w.mouseClicked(w.viewport, glfw.MouseButton1, glfw.Release, 0) + w.mouseClicked(w.viewport, int(glfw.MouseButton1), int(glfw.Release), 0) assert.Nil(t, dh.popMouseInEvent()) assert.Nil(t, dh.popMouseMovedEvent()) assert.Nil(t, dh.popMouseOutEvent()) // mouseOut on mouse release after dragging out of area w.mouseMoved(w.viewport, 12, 12) - w.mouseClicked(w.viewport, glfw.MouseButton1, glfw.Press, 0) + w.mouseClicked(w.viewport, int(glfw.MouseButton1), int(glfw.Press), 0) w.mouseMoved(w.viewport, 28, 12) // outside the 20x20 object - w.mouseClicked(w.viewport, glfw.MouseButton1, glfw.Release, 0) + w.mouseClicked(w.viewport, int(glfw.MouseButton1), int(glfw.Release), 0) assert.NotNil(t, dh.popMouseOutEvent()) }) } @@ -692,7 +692,7 @@ func TestWindow_HoverableUnderDraggable(t *testing.T) { // - no events by draggableObject // - no events by draggableHoverableObject runOnMain(func() { - w.mouseClicked(w.viewport, glfw.MouseButton1, glfw.Press, 0) + w.mouseClicked(w.viewport, int(glfw.MouseButton1), int(glfw.Press), 0) w.mouseMoved(w.viewport, 8, 8) assert.Nil(t, h.popMouseInEvent()) assert.Equal(t, &desktop.MouseEvent{PointEvent: fyne.PointEvent{Position: fyne.NewPos(4, 4), @@ -705,7 +705,7 @@ func TestWindow_HoverableUnderDraggable(t *testing.T) { assert.Nil(t, dh.popMouseOutEvent()) assert.Nil(t, dh.popDragEvent()) assert.Nil(t, dh.popDragEndEvent()) - w.mouseClicked(w.viewport, glfw.MouseButton1, glfw.Release, 0) + w.mouseClicked(w.viewport, int(glfw.MouseButton1), int(glfw.Release), 0) }) // 2. move over to draggableObject and verify @@ -732,7 +732,7 @@ func TestWindow_HoverableUnderDraggable(t *testing.T) { // - drag begin by draggableObject // - no events by draggableHoverableObject runOnMain(func() { - w.mouseClicked(w.viewport, glfw.MouseButton1, glfw.Press, 0) + w.mouseClicked(w.viewport, int(glfw.MouseButton1), int(glfw.Press), 0) w.mouseMoved(w.viewport, 18, 18) assert.Nil(t, h.popMouseInEvent()) assert.Equal(t, &desktop.MouseEvent{PointEvent: fyne.PointEvent{Position: fyne.NewPos(14, 14), @@ -753,7 +753,7 @@ func TestWindow_HoverableUnderDraggable(t *testing.T) { // - drag end by draggableObject // - no events by draggableHoverableObject runOnMain(func() { - w.mouseClicked(w.viewport, glfw.MouseButton1, glfw.Release, 0) + w.mouseClicked(w.viewport, int(glfw.MouseButton1), int(glfw.Release), 0) assert.Nil(t, h.popMouseInEvent()) assert.Nil(t, h.popMouseMovedEvent()) assert.Nil(t, h.popMouseOutEvent()) @@ -882,7 +882,7 @@ func TestWindow_HoverableUnderDraggable_DragAcross(t *testing.T) { // - drag begin by draggableObject // - no events by draggableHoverableObject runOnMain(func() { - w.mouseClicked(w.viewport, glfw.MouseButton1, glfw.Press, 0) + w.mouseClicked(w.viewport, int(glfw.MouseButton1), int(glfw.Press), 0) w.mouseMoved(w.viewport, 18, 18) assert.Nil(t, h.popMouseInEvent()) assert.Equal(t, &desktop.MouseEvent{PointEvent: fyne.PointEvent{Position: fyne.NewPos(14, 14), @@ -943,7 +943,7 @@ func TestWindow_HoverableUnderDraggable_DragAcross(t *testing.T) { // - drag end by draggableObject // - no events by draggableHoverableObject runOnMain(func() { - w.mouseClicked(w.viewport, glfw.MouseButton1, glfw.Release, 0) + w.mouseClicked(w.viewport, int(glfw.MouseButton1), int(glfw.Release), 0) assert.Nil(t, h.popMouseInEvent()) assert.Nil(t, h.popMouseMovedEvent()) assert.Nil(t, h.popMouseOutEvent()) @@ -998,7 +998,7 @@ func TestWindow_HoverableUnderDraggable_Drag_draggableHoverable(t *testing.T) { // - no events by draggableObject // - drag begin by draggableHoverableObject runOnMain(func() { - w.mouseClicked(w.viewport, glfw.MouseButton1, glfw.Press, 0) + w.mouseClicked(w.viewport, int(glfw.MouseButton1), int(glfw.Press), 0) w.mouseMoved(w.viewport, 30, 30) assert.Nil(t, h.popMouseInEvent()) assert.Nil(t, h.popMouseMovedEvent()) @@ -1038,7 +1038,7 @@ func TestWindow_HoverableUnderDraggable_Drag_draggableHoverable(t *testing.T) { // - no events by draggableObject // - drag end by draggableHoverableObject runOnMain(func() { - w.mouseClicked(w.viewport, glfw.MouseButton1, glfw.Release, 0) + w.mouseClicked(w.viewport, int(glfw.MouseButton1), int(glfw.Release), 0) assert.Nil(t, h.popMouseInEvent()) assert.Nil(t, h.popMouseMovedEvent()) assert.Nil(t, h.popMouseOutEvent()) @@ -1065,14 +1065,14 @@ func TestWindow_DragEndWithoutTappedEvent(t *testing.T) { runOnMain(func() { w.mouseMoved(w.viewport, 11, 11) - w.mouseClicked(w.viewport, glfw.MouseButton1, glfw.Press, 0) + w.mouseClicked(w.viewport, int(glfw.MouseButton1), int(glfw.Press), 0) w.mouseMoved(w.viewport, 10, 10) // Less than drag threshold - w.mouseClicked(w.viewport, glfw.MouseButton1, glfw.Release, 0) + w.mouseClicked(w.viewport, int(glfw.MouseButton1), int(glfw.Release), 0) assert.NotNil(t, do.popTapEvent()) // it was slight drag, so call it a tap w.mouseMoved(w.viewport, 7, 7) - w.mouseClicked(w.viewport, glfw.MouseButton1, glfw.Release, 0) + w.mouseClicked(w.viewport, int(glfw.MouseButton1), int(glfw.Release), 0) assert.Nil(t, do.popTapEvent()) }) @@ -1105,8 +1105,8 @@ func TestWindow_Tapped(t *testing.T) { runOnMain(func() { w.mousePos = fyne.NewPos(50, 160) - w.mouseClicked(w.viewport, glfw.MouseButton1, glfw.Press, 0) - w.mouseClicked(w.viewport, glfw.MouseButton1, glfw.Release, 0) + w.mouseClicked(w.viewport, int(glfw.MouseButton1), int(glfw.Press), 0) + w.mouseClicked(w.viewport, int(glfw.MouseButton1), int(glfw.Release), 0) assert.Nil(t, o.popSecondaryTapEvent(), "no secondary tap") if e, _ := o.popTapEvent().(*fyne.PointEvent); assert.NotNil(t, e, "tapped") { @@ -1124,8 +1124,8 @@ func TestWindow_TappedSecondary(t *testing.T) { runOnMain(func() { w.mousePos = fyne.NewPos(50, 60) - w.mouseClicked(w.viewport, glfw.MouseButton2, glfw.Press, 0) - w.mouseClicked(w.viewport, glfw.MouseButton2, glfw.Release, 0) + w.mouseClicked(w.viewport, int(glfw.MouseButton2), int(glfw.Press), 0) + w.mouseClicked(w.viewport, int(glfw.MouseButton2), int(glfw.Release), 0) assert.Nil(t, o.popTapEvent(), "no primary tap") if e, _ := o.popSecondaryTapEvent().(*fyne.PointEvent); assert.NotNil(t, e, "tapped secondary") { @@ -1146,13 +1146,13 @@ func TestWindow_TappedSecondary_OnPrimaryOnlyTarget(t *testing.T) { runOnMain(func() { w.mousePos = fyne.NewPos(10, 25) - w.mouseClicked(w.viewport, glfw.MouseButton2, glfw.Press, 0) - w.mouseClicked(w.viewport, glfw.MouseButton2, glfw.Release, 0) + w.mouseClicked(w.viewport, int(glfw.MouseButton2), int(glfw.Press), 0) + w.mouseClicked(w.viewport, int(glfw.MouseButton2), int(glfw.Release), 0) assert.False(t, tapped) - w.mouseClicked(w.viewport, glfw.MouseButton1, glfw.Press, 0) - w.mouseClicked(w.viewport, glfw.MouseButton1, glfw.Release, 0) + w.mouseClicked(w.viewport, int(glfw.MouseButton1), int(glfw.Press), 0) + w.mouseClicked(w.viewport, int(glfw.MouseButton1), int(glfw.Release), 0) assert.True(t, tapped) }) @@ -1180,14 +1180,14 @@ func TestWindow_TappedIgnoresScrollerClip(t *testing.T) { runOnMain(func() { w.mousePos = fyne.NewPos(10, 80) - w.mouseClicked(w.viewport, glfw.MouseButton1, glfw.Press, 0) - w.mouseClicked(w.viewport, glfw.MouseButton1, glfw.Release, 0) + w.mouseClicked(w.viewport, int(glfw.MouseButton1), int(glfw.Press), 0) + w.mouseClicked(w.viewport, int(glfw.MouseButton1), int(glfw.Release), 0) assert.False(t, tapped, "Tapped button that was clipped") w.mousePos = fyne.NewPos(10, 120) - w.mouseClicked(w.viewport, glfw.MouseButton1, glfw.Press, 0) - w.mouseClicked(w.viewport, glfw.MouseButton1, glfw.Release, 0) + w.mouseClicked(w.viewport, int(glfw.MouseButton1), int(glfw.Press), 0) + w.mouseClicked(w.viewport, int(glfw.MouseButton1), int(glfw.Release), 0) assert.True(t, tapped, "Tapped button that was clipped") }) @@ -1202,20 +1202,20 @@ func TestWindow_TappedIgnoredWhenMovedOffOfTappable(t *testing.T) { runOnMain(func() { w.mouseMoved(w.viewport, 17, 27) - w.mouseClicked(w.viewport, glfw.MouseButton1, glfw.Press, 0) - w.mouseClicked(w.viewport, glfw.MouseButton1, glfw.Release, 0) + w.mouseClicked(w.viewport, int(glfw.MouseButton1), int(glfw.Press), 0) + w.mouseClicked(w.viewport, int(glfw.MouseButton1), int(glfw.Release), 0) assert.Equal(t, 1, tapped, "Button 1 should be tapped") tapped = 0 - w.mouseClicked(w.viewport, glfw.MouseButton1, glfw.Press, 0) + w.mouseClicked(w.viewport, int(glfw.MouseButton1), int(glfw.Press), 0) w.mouseMoved(w.viewport, 17, 59) - w.mouseClicked(w.viewport, glfw.MouseButton1, glfw.Release, 0) + w.mouseClicked(w.viewport, int(glfw.MouseButton1), int(glfw.Release), 0) assert.Equal(t, 0, tapped, "button was tapped without mouse press & release on it %d", tapped) - w.mouseClicked(w.viewport, glfw.MouseButton1, glfw.Press, 0) - w.mouseClicked(w.viewport, glfw.MouseButton1, glfw.Release, 0) + w.mouseClicked(w.viewport, int(glfw.MouseButton1), int(glfw.Press), 0) + w.mouseClicked(w.viewport, int(glfw.MouseButton1), int(glfw.Release), 0) assert.Equal(t, 2, tapped, "Button 2 should be tapped") }) @@ -1238,19 +1238,19 @@ func TestWindow_TappedAndDoubleTapped(t *testing.T) { but.Resize(fyne.NewSquareSize(50)) w.mouseMoved(w.viewport, 15, 25) - w.mouseClicked(w.viewport, glfw.MouseButton1, glfw.Press, 0) - w.mouseClicked(w.viewport, glfw.MouseButton1, glfw.Release, 0) + w.mouseClicked(w.viewport, int(glfw.MouseButton1), int(glfw.Press), 0) + w.mouseClicked(w.viewport, int(glfw.MouseButton1), int(glfw.Release), 0) }) <-waitSingleTapped time.Sleep(d.DoubleTapDelay()) // reset runOnMain(func() { - w.mouseClicked(w.viewport, glfw.MouseButton1, glfw.Press, 0) - w.mouseClicked(w.viewport, glfw.MouseButton1, glfw.Release, 0) + w.mouseClicked(w.viewport, int(glfw.MouseButton1), int(glfw.Press), 0) + w.mouseClicked(w.viewport, int(glfw.MouseButton1), int(glfw.Release), 0) time.Sleep(time.Millisecond * 100) - w.mouseClicked(w.viewport, glfw.MouseButton1, glfw.Press, 0) - w.mouseClicked(w.viewport, glfw.MouseButton1, glfw.Release, 0) + w.mouseClicked(w.viewport, int(glfw.MouseButton1), int(glfw.Press), 0) + w.mouseClicked(w.viewport, int(glfw.MouseButton1), int(glfw.Release), 0) }) mustBeBefore := time.NewTimer(d.DoubleTapDelay()) @@ -1355,7 +1355,7 @@ func TestWindow_MouseEventContainsModifierKeys(t *testing.T) { for name, tt := range tests { t.Run(name, func(t *testing.T) { require.Nil(t, m.popMouseEvent(), "no initial mouse event") - w.mouseClicked(w.viewport, glfw.MouseButton1, glfw.Press, tt.modifier) + w.mouseClicked(w.viewport, int(glfw.MouseButton1), int(glfw.Press), int(tt.modifier)) me, _ := m.popMouseEvent().(*desktop.MouseEvent) if assert.NotNil(t, me, "mouse event triggered") { @@ -1597,13 +1597,13 @@ func TestWindow_ManualFocus(t *testing.T) { runOnMain(func() { w.mouseMoved(w.viewport, 9, 9) - w.mouseClicked(w.viewport, glfw.MouseButton1, glfw.Press, 0) - w.mouseClicked(w.viewport, glfw.MouseButton1, glfw.Release, 0) + w.mouseClicked(w.viewport, int(glfw.MouseButton1), int(glfw.Press), 0) + w.mouseClicked(w.viewport, int(glfw.MouseButton1), int(glfw.Release), 0) assert.Equal(t, 1, content.focusedTimes) assert.Equal(t, 0, content.unfocusedTimes) - w.mouseClicked(w.viewport, glfw.MouseButton1, glfw.Press, 0) - w.mouseClicked(w.viewport, glfw.MouseButton1, glfw.Release, 0) + w.mouseClicked(w.viewport, int(glfw.MouseButton1), int(glfw.Press), 0) + w.mouseClicked(w.viewport, int(glfw.MouseButton1), int(glfw.Release), 0) assert.Equal(t, 1, content.focusedTimes) assert.Equal(t, 0, content.unfocusedTimes) }) @@ -1630,7 +1630,7 @@ func TestWindow_ManualFocus(t *testing.T) { assert.Equal(t, 1, content.focusedTimes) assert.Equal(t, 1, content.unfocusedTimes) - w.mouseClicked(w.viewport, glfw.MouseButton1, glfw.Press, 0) + w.mouseClicked(w.viewport, int(glfw.MouseButton1), int(glfw.Press), 0) assert.Equal(t, 1, content.focusedTimes) assert.Equal(t, 1, content.unfocusedTimes) }) @@ -2109,6 +2109,6 @@ func (s *safeWindow) charInput(viewport *glfw.Window, char rune) { func (s *safeWindow) keyPressed(w *glfw.Window, key glfw.Key, scancode int, action glfw.Action, mods glfw.ModifierKey) { runOnMain(func() { - s.window.keyPressed(w, key, scancode, action, mods) + s.window.keyPressed(w, int(key), scancode, int(action), int(mods)) }) } From 2f61ff4c6742356c6be270812128e6ef0ca467fd Mon Sep 17 00:00:00 2001 From: Drew Weymouth Date: Fri, 7 Feb 2025 16:06:58 -0300 Subject: [PATCH 28/28] move window resize event handling to synchronous --- internal/driver/glfw/event.go | 21 --------------------- internal/driver/glfw/loop.go | 7 ++++--- internal/driver/glfw/window_desktop.go | 20 +++++++++----------- internal/driver/glfw/window_wasm.go | 15 ++++++++------- 4 files changed, 21 insertions(+), 42 deletions(-) diff --git a/internal/driver/glfw/event.go b/internal/driver/glfw/event.go index e64ad777f0..ea13cd07a3 100644 --- a/internal/driver/glfw/event.go +++ b/internal/driver/glfw/event.go @@ -9,11 +9,7 @@ type eventType int const ( emptyEvent eventType = iota moveEvent - resizeEvent - frameSizeEvent - refreshEvent closeEvent - //contentScaleEvent mouseMoveEvent mouseClickEvent mouseScrollEvent @@ -44,10 +40,6 @@ func newCloseEvent(win *window) event { return event{eventType: closeEvent, window: win} } -func newRefreshEvent(win *window) event { - return event{eventType: refreshEvent, window: win} -} - func newMoveEvent(win *window, x, y int) event { return event{ eventType: moveEvent, @@ -57,15 +49,6 @@ func newMoveEvent(win *window, x, y int) event { } } -func newFrameSizeEvent(win *window, width, height int) event { - return event{ - eventType: frameSizeEvent, - window: win, - intArg1: width, - intArg2: height, - } -} - func newMouseMoveEvent(win *window, xpos, ypos float64) event { return event{ eventType: mouseMoveEvent, @@ -129,10 +112,6 @@ func processEvent(e event) { switch e.eventType { case closeEvent: e.window.closed(e.window.viewport) - case refreshEvent: - e.window.refresh(e.window.viewport) - case frameSizeEvent: - e.window.frameSized(e.window.viewport, e.intArg1, e.intArg2) case moveEvent: e.window.moved(e.window.viewport, e.intArg1, e.intArg2) case mouseMoveEvent: diff --git a/internal/driver/glfw/loop.go b/internal/driver/glfw/loop.go index 0fc7ebab13..36593cbe9c 100644 --- a/internal/driver/glfw/loop.go +++ b/internal/driver/glfw/loop.go @@ -122,8 +122,9 @@ func (d *gLDriver) runGL() { } for { - // idle until input events or funcs queued to main + // idle until events (or d.wakeUp()) received d.waitEvents() + // run active loop until we are exiting, or idle again if exit := d.runActiveLoop(); exit { return @@ -154,10 +155,10 @@ func (d *gLDriver) runActiveLoop() bool { defer t.Stop() for range t.C { d.pollEvents() - exit, animationsDone := d.runSingleFrame() + exit, idle := d.runSingleFrame() if exit { return exit - } else if animationsDone { + } else if idle { break } } diff --git a/internal/driver/glfw/window_desktop.go b/internal/driver/glfw/window_desktop.go index 06b20bca87..982cc3504a 100644 --- a/internal/driver/glfw/window_desktop.go +++ b/internal/driver/glfw/window_desktop.go @@ -761,23 +761,21 @@ func (w *window) create() { w.setDarkMode() + // window size and refresh callbacks are processed + // synchronously since GLFW does not return from + // waitEvents until a resize is completed + win.SetSizeCallback(w.resized) + win.SetFramebufferSizeCallback(w.frameSized) + win.SetRefreshCallback(w.refresh) + win.SetContentScaleCallback(w.scaled) + + // rest of callbacks queue events to be handled in main loop win.SetCloseCallback(func(_ *glfw.Window) { eventQueue.Push(newCloseEvent(w)) }) win.SetPosCallback(func(_ *glfw.Window, xpos, ypos int) { eventQueue.Push(newMoveEvent(w, xpos, ypos)) }) - win.SetFramebufferSizeCallback(func(_ *glfw.Window, width, height int) { - eventQueue.Push(newFrameSizeEvent(w, width, height)) - }) - win.SetRefreshCallback(func(_ *glfw.Window) { - eventQueue.Push(newRefreshEvent(w)) - }) - // window size callbacks must be handled synchronously - // instead of queued, since GLFW will not allow waitEvents - // to return until a resize is *completed* - win.SetSizeCallback(w.resized) - win.SetContentScaleCallback(w.scaled) win.SetCursorPosCallback(func(_ *glfw.Window, xpos, ypos float64) { eventQueue.Push(newMouseMoveEvent(w, xpos, ypos)) }) diff --git a/internal/driver/glfw/window_wasm.go b/internal/driver/glfw/window_wasm.go index 6c4c767550..2119cf4d0e 100644 --- a/internal/driver/glfw/window_wasm.go +++ b/internal/driver/glfw/window_wasm.go @@ -522,19 +522,20 @@ func (w *window) create() { w.setDarkMode() + // window size and refresh callbacks are processed + // synchronously since GLFW does not return from + // waitEvents until a resize is completed + win.SetSizeCallback(w.resized) + win.SetFramebufferSizeCallback(w.frameSized) + win.SetRefreshCallback(w.refresh) + + // rest of callbacks queue events to be handled in main loop win.SetCloseCallback(func(_ *glfw.Window) { eventQueue.Push(newCloseEvent(w)) }) win.SetPosCallback(func(_ *glfw.Window, xpos, ypos int) { eventQueue.Push(newMoveEvent(w, xpos, ypos)) }) - win.SetFramebufferSizeCallback(func(_ *glfw.Window, width, height int) { - eventQueue.Push(newFrameSizeEvent(w, width, height)) - }) - win.SetRefreshCallback(func(_ *glfw.Window) { - eventQueue.Push(newRefreshEvent(w)) - }) - win.SetSizeCallback(w.resized) win.SetCursorPosCallback(func(_ *glfw.Window, xpos, ypos float64) { eventQueue.Push(newMouseMoveEvent(w, xpos, ypos)) })