Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add a fyne.Do method for developers #5392

Merged
merged 4 commits into from
Jan 11, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion app/app.go
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,7 @@ func (a *fyneApp) NewWindow(title string) fyne.Window {
}

func (a *fyneApp) Run() {
go a.lifecycle.RunEventQueue(a.driver.CallFromGoroutine)
go a.lifecycle.RunEventQueue(a.driver.DoFromGoroutine)
a.driver.Run()
}

Expand Down
2 changes: 1 addition & 1 deletion app/preferences.go
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ func (p *preferences) resetSavedRecently() {
time.Sleep(time.Millisecond * 100) // writes are not always atomic. 10ms worked, 100 is safer.

// For test reasons we need to use current app not what we were initialised with as they can differ
fyne.CurrentApp().Driver().CallFromGoroutine(func() {
fyne.Do(func() {
p.prefLock.Lock()
p.savedRecently = false
changedDuringSaving := p.changedDuringSaving
Expand Down
2 changes: 1 addition & 1 deletion app/preferences_other.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,6 @@ func (p *preferences) watch() {
return
}

fyne.CurrentApp().Driver().CallFromGoroutine(p.load)
fyne.Do(p.load)
})
}
2 changes: 1 addition & 1 deletion app/settings_desktop.go
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ func watchFile(path string, callback func()) *fsnotify.Watcher {

watchFileAddTarget(watcher, path)
} else {
callback()
fyne.Do(callback)
}
}

Expand Down
2 changes: 1 addition & 1 deletion cmd/fyne_demo/tutorials/advanced.go
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ func advancedScreen(win fyne.Window) fyne.CanvasObject {

go func(canvas fyne.Canvas) {
for range time.NewTicker(time.Second).C {
fyne.CurrentApp().Driver().CallFromGoroutine(func() {
fyne.Do(func() {
scale.SetText(scaleToString(canvas))
tex.SetText(textureScaleToString(canvas))
})
Expand Down
2 changes: 1 addition & 1 deletion cmd/fyne_demo/tutorials/canvas.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ func canvasScreen(_ fyne.Window) fyne.CanvasObject {
gradient := canvas.NewHorizontalGradient(color.NRGBA{0x80, 0, 0, 0xff}, color.NRGBA{0, 0x80, 0, 0xff})
go func() {
for range time.NewTicker(time.Second).C {
fyne.CurrentApp().Driver().CallFromGoroutine(func() {
fyne.Do(func() {
gradient.Angle += 45
if gradient.Angle >= 360 {
gradient.Angle -= 360
Expand Down
2 changes: 1 addition & 1 deletion cmd/fyne_demo/tutorials/welcome.go
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@ func welcomeScreen(_ fyne.Window) fyne.CanvasObject {
fyne.CurrentApp().Settings().AddChangeListener(listen)
go func() {
for range listen {
fyne.CurrentApp().Driver().CallFromGoroutine(func() {
fyne.Do(func() {
bgColor = withAlpha(theme.Color(theme.ColorNameBackground), 0xe0)
bg.FillColor = bgColor
bg.Refresh()
Expand Down
4 changes: 2 additions & 2 deletions cmd/fyne_demo/tutorials/widget.go
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@ func makeActivityTab(win fyne.Window) fyne.CanvasObject {
a2.Show()

time.AfterFunc(10*time.Second, func() {
fyne.CurrentApp().Driver().CallFromGoroutine(func() {
fyne.Do(func() {
a1.Stop()
a1.Hide()
a2.Stop()
Expand Down Expand Up @@ -90,7 +90,7 @@ func makeActivityTab(win fyne.Window) fyne.CanvasObject {
d.Show()

time.AfterFunc(5*time.Second, func() {
fyne.CurrentApp().Driver().CallFromGoroutine(func() {
fyne.Do(func() {
a3.Stop()
d.Hide()
})
Expand Down
2 changes: 1 addition & 1 deletion cmd/fyne_demo/tutorials/window.go
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,7 @@ func windowScreen(_ fyne.Window) fyne.CanvasObject {
w.Show()

time.AfterFunc(3*time.Second, func() {
fyne.CurrentApp().Driver().CallFromGoroutine(w.Close)
fyne.Do(w.Close)
})
}))
}
Expand Down
2 changes: 1 addition & 1 deletion data/binding/queue.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,5 +11,5 @@ func queueItem(f func()) {
return
}

fyne.CurrentApp().Driver().CallFromGoroutine(f)
fyne.Do(f)
}
12 changes: 9 additions & 3 deletions driver.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,8 @@ import "time"
// Driver defines an abstract concept of a Fyne render driver.
// Any implementation must provide at least these methods.
type Driver interface {
// CreateWindow creates a new UI Window.
// CreateWindow creates a new UI Window for a certain implementation.
// Developers should use [App.NewWindow].
CreateWindow(string) Window
// AllWindows returns a slice containing all app windows.
AllWindows() []Window
Expand All @@ -29,8 +30,10 @@ type Driver interface {
Quit()

// StartAnimation registers a new animation with this driver and requests it be started.
// Developers should use the [Animation.Start] function.
StartAnimation(*Animation)
// StopAnimation stops an animation and unregisters from this driver.
// Developers should use the [Animation.Stop] function.
StopAnimation(*Animation)

// DoubleTapDelay returns the maximum duration where a second tap after a first one
Expand All @@ -44,8 +47,11 @@ type Driver interface {
// Since: 2.5
SetDisableScreenBlanking(bool)

// CallFromGoroutine provides a way to queue a function that is running on a goroutine back to the main thread.
// DoFromGoroutine provides a way to queue a function that is running on a goroutine back to
// the central thread for Fyne updates.
// The driver provides the implementation normally accessed through [fyne.Do].
// This is required when background tasks want to execute code safely in the graphical context.
//
// Since: 2.6
CallFromGoroutine(func())
DoFromGoroutine(func())
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we want to un-export this one for public use to avoid confusion, so we just have fyne.Do? Or at least have the documentation here make clear that it is entirely equivalent to calling fyne.Do and fyne.Do should generally be preferred (for brevity and for standardization)?

}
14 changes: 9 additions & 5 deletions internal/driver/glfw/canvas_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -411,16 +411,20 @@ func TestGlCanvas_ResizeWithOtherOverlay(t *testing.T) {
w.Canvas().Overlays().Add(over)
ensureCanvasSize(t, w, fyne.NewSize(69, 36))
// TODO: address #707; overlays should always be canvas size
over.Resize(w.Canvas().Size())
size := w.Canvas().Size()
runOnMain(func() {
over.Resize(size)
})

size := fyne.NewSize(200, 100)
size = fyne.NewSize(200, 100)
assert.NotEqual(t, size, content.Size())
assert.NotEqual(t, size, over.Size())

w.Resize(size)
ensureCanvasSize(t, w, size)
assert.Equal(t, size, content.Size(), "canvas content is resized")
assert.Equal(t, size, over.Size(), "canvas overlay is resized")
runOnMain(func() {
assert.Equal(t, size, content.Size(), "canvas content is resized")
assert.Equal(t, size, over.Size(), "canvas overlay is resized")
})
}

func TestGlCanvas_ResizeWithOverlays(t *testing.T) {
Expand Down
2 changes: 1 addition & 1 deletion internal/driver/glfw/driver.go
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ func toOSIcon(icon []byte) ([]byte, error) {
return buf.Bytes(), nil
}

func (d *gLDriver) CallFromGoroutine(f func()) {
func (d *gLDriver) DoFromGoroutine(f func()) {
runOnMain(f)
}

Expand Down
13 changes: 0 additions & 13 deletions internal/driver/glfw/window_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@ import (
"net/url"
"os"
"runtime"
"sync/atomic"
"testing"
"time"

Expand Down Expand Up @@ -1215,14 +1214,11 @@ func TestWindow_TappedAndDoubleTapped(t *testing.T) {
w := createWindow("Test")
waitSingleTapped := make(chan struct{}, 1)
waitDoubleTapped := make(chan struct{}, 1)
tapped := atomic.Int32{}
but := newDoubleTappableButton()
but.OnTapped = func() {
tapped.Store(1)
waitSingleTapped <- struct{}{}
}
but.onDoubleTap = func() {
tapped.Store(2)
waitDoubleTapped <- struct{}{}
}
w.SetContent(but)
Expand All @@ -1233,24 +1229,15 @@ func TestWindow_TappedAndDoubleTapped(t *testing.T) {
w.mouseClicked(w.viewport, glfw.MouseButton1, glfw.Press, 0)
w.mouseClicked(w.viewport, glfw.MouseButton1, glfw.Release, 0)
})

<-waitSingleTapped
time.Sleep(500 * time.Millisecond)

assert.Equal(t, int32(1), tapped.Load(), "Single tap should have fired")
tapped.Store(0)

runOnMain(func() {
w.mouseClicked(w.viewport, glfw.MouseButton1, glfw.Press, 0)
w.mouseClicked(w.viewport, glfw.MouseButton1, glfw.Release, 0)
w.mouseClicked(w.viewport, glfw.MouseButton1, glfw.Press, 0)
w.mouseClicked(w.viewport, glfw.MouseButton1, glfw.Release, 0)
})

<-waitDoubleTapped
time.Sleep(500 * time.Millisecond)

assert.Equal(t, int32(2), tapped.Load(), "Double tap should have fired")
}

func TestWindow_MouseEventContainsModifierKeys(t *testing.T) {
Expand Down
2 changes: 1 addition & 1 deletion internal/driver/mobile/canvas.go
Original file line number Diff line number Diff line change
Expand Up @@ -386,7 +386,7 @@ func (c *canvas) waitForDoubleTap(co fyne.CanvasObject, ev *fyne.PointEvent, tap
defer cancel()

<-ctx.Done()
fyne.CurrentApp().Driver().CallFromGoroutine(func() {
fyne.CurrentApp().Driver().DoFromGoroutine(func() {
c.touchCancelLock.Lock()
touchCount := c.touchTapCount
touchLast := c.touchLastTapped
Expand Down
8 changes: 4 additions & 4 deletions internal/driver/mobile/canvas_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -137,7 +137,7 @@ func Test_canvas_Focusable(t *testing.T) {
content.Resize(fyne.NewSize(25, 25))

pos := fyne.NewPos(10, 10)
d.CallFromGoroutine(func() {
d.DoFromGoroutine(func() {
c.tapDown(pos, 0)
c.tapUp(pos, 0, func(wid fyne.Tappable, ev *fyne.PointEvent) {
wid.Tapped(ev)
Expand All @@ -149,7 +149,7 @@ func Test_canvas_Focusable(t *testing.T) {
assert.Equal(t, 0, content.unfocusedTimes)
})

d.CallFromGoroutine(func() {
d.DoFromGoroutine(func() {
c.tapDown(pos, 1)
c.tapUp(pos, 1, func(wid fyne.Tappable, ev *fyne.PointEvent) {
wid.Tapped(ev)
Expand All @@ -160,7 +160,7 @@ func Test_canvas_Focusable(t *testing.T) {
assert.Equal(t, 0, content.unfocusedTimes)
})

d.CallFromGoroutine(func() {
d.DoFromGoroutine(func() {
c.Focus(content)
assert.Equal(t, 1, content.focusedTimes)
assert.Equal(t, 0, content.unfocusedTimes)
Expand Down Expand Up @@ -530,7 +530,7 @@ func waitAndCheck(msWait time.Duration, fn func()) {
defer common.DonePool.Put(waitForCheck)

time.Sleep(msWait * time.Millisecond)
d.CallFromGoroutine(func() {
d.DoFromGoroutine(func() {
fn()

waitForCheck <- struct{}{}
Expand Down
8 changes: 3 additions & 5 deletions internal/driver/mobile/driver.go
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,7 @@ func init() {
runtime.LockOSThread()
}

func (d *driver) CallFromGoroutine(fn func()) {
func (d *driver) DoFromGoroutine(fn func()) {
done := common.DonePool.Get()
defer common.DonePool.Put(done)

Expand Down Expand Up @@ -433,15 +433,13 @@ func (d *driver) tapUpCanvas(w *window, x, y float32, tapID touch.Sequence) {
ev.Dragged.DY *= tapMoveDecay
}

d.CallFromGoroutine(func() {
d.DoFromGoroutine(func() {
wid.Dragged(ev)
})
time.Sleep(time.Millisecond * 16)
}

d.CallFromGoroutine(func() {
wid.DragEnd()
})
d.DoFromGoroutine(wid.DragEnd)
}()
})
}
Expand Down
2 changes: 1 addition & 1 deletion test/app.go
Original file line number Diff line number Diff line change
Expand Up @@ -247,7 +247,7 @@ func (s *testSettings) apply() {
listener <- s
}

s.app.driver.CallFromGoroutine(func() {
s.app.driver.DoFromGoroutine(func() {
s.app.propertyLock.Lock()
painter.ClearFontCache()
cache.ResetThemeCaches()
Expand Down
2 changes: 1 addition & 1 deletion test/driver.go
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ func NewDriverWithPainter(painter SoftwarePainter) fyne.Driver {
return &driver{painter: painter}
}

func (d *driver) CallFromGoroutine(f func()) {
func (d *driver) DoFromGoroutine(f func()) {
f() // Tests all run on a single (but potentially different per-test) thread
}

Expand Down
10 changes: 10 additions & 0 deletions thread.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
package fyne

// Do is used to execute a specified function in the main Fyne runtime context.
// This is required when a background process wishes to adjust graphical elements of a running app.
// Developers should use this only from within goroutines they have created.
//
// Since: 2.6
func Do(fn func()) {
CurrentApp().Driver().DoFromGoroutine(fn)
}
Loading