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 new callback-based settings listener API #5431

Merged
merged 14 commits into from
Feb 2, 2025
Merged
4 changes: 3 additions & 1 deletion app/app_windows.go
Original file line number Diff line number Diff line change
Expand Up @@ -90,5 +90,7 @@ func runScript(name, script string) {
}

func watchTheme(s *settings) {
go internalapp.WatchTheme(s.setupTheme)
go internalapp.WatchTheme(func() {
fyne.Do(s.setupTheme)
})
}
4 changes: 2 additions & 2 deletions app/app_xdg.go
Original file line number Diff line number Diff line change
Expand Up @@ -118,13 +118,13 @@ func watchTheme(s *settings) {
// Theme lookup hangs on some desktops. Update theme variant cache from within goroutine.
themeVariant := findFreedesktopColorScheme()
internalapp.CurrentVariant.Store(uint64(themeVariant))
s.applyVariant(themeVariant)
fyne.Do(func() { s.applyVariant(themeVariant) })

portalSettings.OnSignalSettingChanged(func(changed portalSettings.Changed) {
if changed.Namespace == appearance.Namespace && changed.Key == "color-scheme" {
themeVariant := colorSchemeToThemeVariant(appearance.ColorScheme(changed.Value.(uint32)))
internalapp.CurrentVariant.Store(uint64(themeVariant))
s.applyVariant(themeVariant)
fyne.Do(func() { s.applyVariant(themeVariant) })
}
})
}()
Expand Down
4 changes: 3 additions & 1 deletion app/cloud_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,9 @@ func TestFyneApp_transitionCloud(t *testing.T) {
a.Preferences().AddChangeListener(func() {
preferenceChanged = true
})
a.Settings().AddChangeListener(settingsChan)
a.Settings().AddListener(func(s fyne.Settings) {
go func() { settingsChan <- s }()
})

done := make(chan struct{})
go func() {
Expand Down
24 changes: 10 additions & 14 deletions app/settings.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@ import (
"bytes"
"os"
"path/filepath"
"sync"

"fyne.io/fyne/v2"
"fyne.io/fyne/v2/internal/app"
Expand Down Expand Up @@ -33,11 +32,11 @@ func (sc *SettingsSchema) StoragePath() string {
var _ fyne.Settings = (*settings)(nil)

type settings struct {
propertyLock sync.RWMutex
theme fyne.Theme
themeSpecified bool
variant fyne.ThemeVariant

listeners []func(fyne.Settings)
changeListeners async.Map[chan fyne.Settings, bool]
watcher any // normally *fsnotify.Watcher or nil - avoid import in this file

Expand All @@ -49,8 +48,6 @@ func (s *settings) BuildType() fyne.BuildType {
}

func (s *settings) PrimaryColor() string {
s.propertyLock.RLock()
defer s.propertyLock.RUnlock()
return s.schema.PrimaryColor
}

Expand All @@ -59,8 +56,6 @@ func (s *settings) PrimaryColor() string {
//
// Deprecated: Use container.NewThemeOverride to change the appearance of part of your application.
func (s *settings) OverrideTheme(theme fyne.Theme, name string) {
s.propertyLock.Lock()
defer s.propertyLock.Unlock()
s.schema.PrimaryColor = name
s.theme = theme
}
Expand All @@ -70,8 +65,6 @@ func (s *settings) Theme() fyne.Theme {
fyne.LogError("Attempt to access current Fyne theme when no app is started", nil)
return nil
}
s.propertyLock.RLock()
defer s.propertyLock.RUnlock()
return s.theme
}

Expand All @@ -89,23 +82,17 @@ func (s *settings) ThemeVariant() fyne.ThemeVariant {
}

func (s *settings) applyTheme(theme fyne.Theme, variant fyne.ThemeVariant) {
s.propertyLock.Lock()
defer s.propertyLock.Unlock()
s.variant = variant
s.theme = theme
s.apply()
}

func (s *settings) applyVariant(variant fyne.ThemeVariant) {
s.propertyLock.Lock()
defer s.propertyLock.Unlock()
s.variant = variant
s.apply()
}

func (s *settings) Scale() float32 {
s.propertyLock.RLock()
defer s.propertyLock.RUnlock()
if s.schema.Scale < 0.0 {
return 1.0 // catching any really old data still using the `-1` value for "auto" scale
}
Expand All @@ -116,6 +103,11 @@ func (s *settings) AddChangeListener(listener chan fyne.Settings) {
s.changeListeners.Store(listener, true) // the boolean is just a dummy value here.
}

func (s *settings) AddListener(listener func(fyne.Settings)) {
s.listeners = append(s.listeners, listener)
}

// must be called from main goroutine
Jacalz marked this conversation as resolved.
Show resolved Hide resolved
dweymouth marked this conversation as resolved.
Show resolved Hide resolved
func (s *settings) apply() {
s.changeListeners.Range(func(listener chan fyne.Settings, _ bool) bool {
select {
Expand All @@ -126,6 +118,10 @@ func (s *settings) apply() {
}
return true
})

for _, l := range s.listeners {
l(s)
}
}

func (s *settings) fileChanged() {
Expand Down
24 changes: 9 additions & 15 deletions cmd/fyne_demo/tutorials/welcome.go
Original file line number Diff line number Diff line change
Expand Up @@ -59,21 +59,15 @@ func welcomeScreen(_ fyne.Window) fyne.CanvasObject {
slideBG := container.New(underlayer, underlay)
footerBG := canvas.NewRectangle(shadowColor)

listen := make(chan fyne.Settings)
fyne.CurrentApp().Settings().AddChangeListener(listen)
go func() {
for range listen {
fyne.Do(func() {
bgColor = withAlpha(theme.Color(theme.ColorNameBackground), 0xe0)
bg.FillColor = bgColor
bg.Refresh()

shadowColor = withAlpha(theme.Color(theme.ColorNameBackground), 0x33)
footerBG.FillColor = bgColor
footer.Refresh()
})
}
}()
fyne.CurrentApp().Settings().AddListener(func(fyne.Settings) {
bgColor = withAlpha(theme.Color(theme.ColorNameBackground), 0xe0)
bg.FillColor = bgColor
bg.Refresh()

shadowColor = withAlpha(theme.Color(theme.ColorNameBackground), 0x33)
footerBG.FillColor = bgColor
footer.Refresh()
})

underlay.Resize(fyne.NewSize(1024, 1024))
scroll.OnScrolled = func(p fyne.Position) {
Expand Down
4 changes: 3 additions & 1 deletion internal/driver/glfw/canvas_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -541,7 +541,9 @@ func TestGlCanvas_Scale(t *testing.T) {
}

func TestGlCanvas_SetContent(t *testing.T) {
fyne.CurrentApp().Settings().SetTheme(internalTest.DarkTheme(theme.DefaultTheme()))
runOnMain(func() {
fyne.CurrentApp().Settings().SetTheme(internalTest.DarkTheme(theme.DefaultTheme()))
})
var menuHeight float32
if build.HasNativeMenu {
menuHeight = 0
Expand Down
30 changes: 14 additions & 16 deletions internal/driver/glfw/loop.go
Original file line number Diff line number Diff line change
Expand Up @@ -99,13 +99,24 @@ func (d *gLDriver) runGL() {
if d.trayStart != nil {
d.trayStart()
}

fyne.CurrentApp().Settings().AddListener(func(set fyne.Settings) {
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()
})
})

if f := fyne.CurrentApp().Lifecycle().(*app.Lifecycle).OnStarted(); f != nil {
f()
}

settingsChange := make(chan fyne.Settings)
fyne.CurrentApp().Settings().AddChangeListener(settingsChange)

eventTick := time.NewTicker(time.Second / 60)
for {
select {
Expand Down Expand Up @@ -149,23 +160,10 @@ func (d *gLDriver) runGL() {
view.SetSize(w.shouldWidth, w.shouldHeight)
}
}

}

d.animation.TickAnimations()
d.drawSingleFrame()
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()
})

}
}
}
Expand Down
25 changes: 13 additions & 12 deletions internal/driver/mobile/driver.go
Original file line number Diff line number Diff line change
Expand Up @@ -168,9 +168,20 @@ func (d *driver) Run() {

app.Main(func(a app.App) {
d.app = a
settingsChange := make(chan fyne.Settings)
d.queuedFuncs = async.NewUnboundedChan[func()]()
fyne.CurrentApp().Settings().AddChangeListener(settingsChange)

fyne.CurrentApp().Settings().AddListener(func(s fyne.Settings) {
painter.ClearFontCache()
cache.ResetThemeCaches()
intapp.ApplySettingsWithCallback(s, fyne.CurrentApp(), func(w fyne.Window) {
c, ok := w.Canvas().(*canvas)
if !ok {
return
}
c.applyThemeOutOfTreeObjects()
})
})

draw := time.NewTicker(time.Second / 60)
defer func() {
l := fyne.CurrentApp().Lifecycle().(*intapp.Lifecycle)
Expand All @@ -191,16 +202,6 @@ func (d *driver) Run() {
select {
case <-draw.C:
d.sendPaintEvent()
case set := <-settingsChange:
painter.ClearFontCache()
cache.ResetThemeCaches()
intapp.ApplySettingsWithCallback(set, fyne.CurrentApp(), func(w fyne.Window) {
c, ok := w.Canvas().(*canvas)
if !ok {
return
}
c.applyThemeOutOfTreeObjects()
})
case fn := <-d.queuedFuncs.Out():
fn()
case e, ok := <-a.Events():
Expand Down
10 changes: 10 additions & 0 deletions settings.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,17 @@ type Settings interface {
// Since: 1.4
PrimaryColor() string

// AddChangeListener subscribes to settings change events over a channel.
//
// Deprecated: Use AddListener instead, which uses a callback-based API
// with the callback guaranteed to be invoked on the app goroutine.
AddChangeListener(chan Settings)

// AddListener registers a callback that is invoked whenever the settings change.
//
// Since: 2.6
AddListener(func(Settings))

BuildType() BuildType

ShowAnimations() bool
Expand Down
14 changes: 14 additions & 0 deletions test/app.go
Original file line number Diff line number Diff line change
Expand Up @@ -182,6 +182,8 @@ type testSettings struct {
scale float32
theme fyne.Theme

listenersLock sync.Mutex
listeners []func(fyne.Settings)
changeListeners []chan fyne.Settings
propertyLock sync.RWMutex
app *app
Expand All @@ -193,6 +195,12 @@ func (s *testSettings) AddChangeListener(listener chan fyne.Settings) {
s.changeListeners = append(s.changeListeners, listener)
}

func (s *testSettings) AddListener(listener func(fyne.Settings)) {
s.propertyLock.Lock()
defer s.propertyLock.Unlock()
s.listeners = append(s.listeners, listener)
}

func (s *testSettings) BuildType() fyne.BuildType {
return fyne.BuildStandard
}
Expand Down Expand Up @@ -253,6 +261,12 @@ func (s *testSettings) apply() {
cache.ResetThemeCaches()
intapp.ApplySettings(s, s.app)
s.app.propertyLock.Unlock()

s.listenersLock.Lock()
defer s.listenersLock.Unlock()
for _, l := range s.listeners {
l(s)
}
dweymouth marked this conversation as resolved.
Show resolved Hide resolved
}, false)

s.app.propertyLock.Lock()
Expand Down
4 changes: 3 additions & 1 deletion test/app_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,9 @@ func TestFyneApp_transitionCloud(t *testing.T) {
a.Preferences().AddChangeListener(func() {
preferenceChanged = true
})
a.Settings().AddChangeListener(settingsChan)
a.Settings().AddListener(func(s fyne.Settings) {
go func() { settingsChan <- s }()
})
a.SetCloudProvider(p)

<-settingsChan // settings were updated
Expand Down
3 changes: 3 additions & 0 deletions theme/themedtestapp_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,9 @@ func (t *themedApp) ShowAnimations() bool {
func (t *themedApp) AddChangeListener(chan fyne.Settings) {
}

func (t *themedApp) AddListener(func(fyne.Settings)) {
}

func (t *themedApp) Clipboard() fyne.Clipboard {
return nil
}
Loading