262 lines
7.5 KiB
Go
262 lines
7.5 KiB
Go
package binding
|
|
|
|
import (
|
|
"sync/atomic"
|
|
|
|
"fyne.io/fyne/v2"
|
|
)
|
|
|
|
// Work around Go not supporting generic methods on non-generic types:
|
|
type preferenceLookupSetter[T any] func(fyne.Preferences) (func(string) T, func(string, T))
|
|
|
|
const keyTypeMismatchError = "A previous preference binding exists with different type for key: "
|
|
|
|
// BindPreferenceBool returns a bindable bool value that is managed by the application preferences.
|
|
// Changes to this value will be saved to application storage and when the app starts the previous values will be read.
|
|
//
|
|
// Since: 2.0
|
|
func BindPreferenceBool(key string, p fyne.Preferences) Bool {
|
|
return bindPreferenceItem(key, p,
|
|
func(p fyne.Preferences) (func(string) bool, func(string, bool)) {
|
|
return p.Bool, p.SetBool
|
|
})
|
|
}
|
|
|
|
// BindPreferenceBoolList returns a bound list of bool values that is managed by the application preferences.
|
|
// Changes to this value will be saved to application storage and when the app starts the previous values will be read.
|
|
//
|
|
// Since: 2.6
|
|
func BindPreferenceBoolList(key string, p fyne.Preferences) BoolList {
|
|
return bindPreferenceListComparable[bool](key, p,
|
|
func(p fyne.Preferences) (func(string) []bool, func(string, []bool)) {
|
|
return p.BoolList, p.SetBoolList
|
|
},
|
|
)
|
|
}
|
|
|
|
// BindPreferenceFloat returns a bindable float64 value that is managed by the application preferences.
|
|
// Changes to this value will be saved to application storage and when the app starts the previous values will be read.
|
|
//
|
|
// Since: 2.0
|
|
func BindPreferenceFloat(key string, p fyne.Preferences) Float {
|
|
return bindPreferenceItem(key, p,
|
|
func(p fyne.Preferences) (func(string) float64, func(string, float64)) {
|
|
return p.Float, p.SetFloat
|
|
})
|
|
}
|
|
|
|
// BindPreferenceFloatList returns a bound list of float64 values that is managed by the application preferences.
|
|
// Changes to this value will be saved to application storage and when the app starts the previous values will be read.
|
|
//
|
|
// Since: 2.6
|
|
func BindPreferenceFloatList(key string, p fyne.Preferences) FloatList {
|
|
return bindPreferenceListComparable[float64](key, p,
|
|
func(p fyne.Preferences) (func(string) []float64, func(string, []float64)) {
|
|
return p.FloatList, p.SetFloatList
|
|
},
|
|
)
|
|
}
|
|
|
|
// BindPreferenceInt returns a bindable int value that is managed by the application preferences.
|
|
// Changes to this value will be saved to application storage and when the app starts the previous values will be read.
|
|
//
|
|
// Since: 2.0
|
|
func BindPreferenceInt(key string, p fyne.Preferences) Int {
|
|
return bindPreferenceItem(key, p,
|
|
func(p fyne.Preferences) (func(string) int, func(string, int)) {
|
|
return p.Int, p.SetInt
|
|
})
|
|
}
|
|
|
|
// BindPreferenceIntList returns a bound list of int values that is managed by the application preferences.
|
|
// Changes to this value will be saved to application storage and when the app starts the previous values will be read.
|
|
//
|
|
// Since: 2.6
|
|
func BindPreferenceIntList(key string, p fyne.Preferences) IntList {
|
|
return bindPreferenceListComparable[int](key, p,
|
|
func(p fyne.Preferences) (func(string) []int, func(string, []int)) {
|
|
return p.IntList, p.SetIntList
|
|
},
|
|
)
|
|
}
|
|
|
|
// BindPreferenceString returns a bindable string value that is managed by the application preferences.
|
|
// Changes to this value will be saved to application storage and when the app starts the previous values will be read.
|
|
//
|
|
// Since: 2.0
|
|
func BindPreferenceString(key string, p fyne.Preferences) String {
|
|
return bindPreferenceItem(key, p,
|
|
func(p fyne.Preferences) (func(string) string, func(string, string)) {
|
|
return p.String, p.SetString
|
|
})
|
|
}
|
|
|
|
// BindPreferenceStringList returns a bound list of string values that is managed by the application preferences.
|
|
// Changes to this value will be saved to application storage and when the app starts the previous values will be read.
|
|
//
|
|
// Since: 2.6
|
|
func BindPreferenceStringList(key string, p fyne.Preferences) StringList {
|
|
return bindPreferenceListComparable[string](key, p,
|
|
func(p fyne.Preferences) (func(string) []string, func(string, []string)) {
|
|
return p.StringList, p.SetStringList
|
|
},
|
|
)
|
|
}
|
|
|
|
func bindPreferenceItem[T bool | float64 | int | string](key string, p fyne.Preferences, setLookup preferenceLookupSetter[T]) Item[T] {
|
|
if found, ok := lookupExistingBinding[T](key, p); ok {
|
|
return found
|
|
}
|
|
|
|
listen := &prefBoundBase[T]{key: key, setLookup: setLookup}
|
|
listen.replaceProvider(p)
|
|
binds := prefBinds.ensurePreferencesAttached(p)
|
|
binds.Store(key, listen)
|
|
return listen
|
|
}
|
|
|
|
func lookupExistingBinding[T any](key string, p fyne.Preferences) (Item[T], bool) {
|
|
binds := prefBinds.getBindings(p)
|
|
if binds == nil {
|
|
return nil, false
|
|
}
|
|
|
|
if listen, ok := binds.Load(key); listen != nil && ok {
|
|
if l, ok := listen.(Item[T]); ok {
|
|
return l, ok
|
|
}
|
|
fyne.LogError(keyTypeMismatchError+key, nil)
|
|
}
|
|
|
|
return nil, false
|
|
}
|
|
|
|
func lookupExistingListBinding[T bool | float64 | int | string](key string, p fyne.Preferences) (*prefBoundList[T], bool) {
|
|
binds := prefBinds.getBindings(p)
|
|
if binds == nil {
|
|
return nil, false
|
|
}
|
|
|
|
if listen, ok := binds.Load(key); listen != nil && ok {
|
|
if l, ok := listen.(*prefBoundList[T]); ok {
|
|
return l, ok
|
|
}
|
|
fyne.LogError(keyTypeMismatchError+key, nil)
|
|
}
|
|
|
|
return nil, false
|
|
}
|
|
|
|
type prefBoundBase[T bool | float64 | int | string] struct {
|
|
base
|
|
key string
|
|
|
|
get func(string) T
|
|
set func(string, T)
|
|
setLookup preferenceLookupSetter[T]
|
|
cache atomic.Pointer[T]
|
|
}
|
|
|
|
func (b *prefBoundBase[T]) Get() (T, error) {
|
|
cache := b.get(b.key)
|
|
b.cache.Store(&cache)
|
|
return cache, nil
|
|
}
|
|
|
|
func (b *prefBoundBase[T]) Set(v T) error {
|
|
b.set(b.key, v)
|
|
|
|
b.lock.RLock()
|
|
defer b.lock.RUnlock()
|
|
b.trigger()
|
|
return nil
|
|
}
|
|
|
|
func (b *prefBoundBase[T]) checkForChange() {
|
|
val := b.cache.Load()
|
|
if val != nil && b.get(b.key) == *val {
|
|
return
|
|
}
|
|
b.trigger()
|
|
}
|
|
|
|
func (b *prefBoundBase[T]) replaceProvider(p fyne.Preferences) {
|
|
b.get, b.set = b.setLookup(p)
|
|
}
|
|
|
|
type prefBoundList[T bool | float64 | int | string] struct {
|
|
boundList[T]
|
|
key string
|
|
|
|
get func(string) []T
|
|
set func(string, []T)
|
|
setLookup preferenceLookupSetter[[]T]
|
|
}
|
|
|
|
func (b *prefBoundList[T]) checkForChange() {
|
|
val := *b.val
|
|
updated := b.get(b.key)
|
|
if val == nil || len(updated) != len(val) {
|
|
b.Set(updated)
|
|
return
|
|
}
|
|
if val == nil {
|
|
return
|
|
}
|
|
|
|
// incoming changes to a preference list are not at the child level
|
|
for i, v := range val {
|
|
if i >= len(updated) {
|
|
break
|
|
}
|
|
|
|
if !b.comparator(v, updated[i]) {
|
|
_ = b.items[i].(Item[T]).Set(updated[i])
|
|
}
|
|
}
|
|
}
|
|
|
|
func (b *prefBoundList[T]) replaceProvider(p fyne.Preferences) {
|
|
b.get, b.set = b.setLookup(p)
|
|
}
|
|
|
|
type internalPrefs = interface{ WriteValues(func(map[string]any)) }
|
|
|
|
func bindPreferenceListComparable[T bool | float64 | int | string](key string, p fyne.Preferences,
|
|
setLookup preferenceLookupSetter[[]T]) *prefBoundList[T] {
|
|
if found, ok := lookupExistingListBinding[T](key, p); ok {
|
|
return found
|
|
}
|
|
|
|
listen := &prefBoundList[T]{key: key, setLookup: setLookup}
|
|
listen.replaceProvider(p)
|
|
|
|
items := listen.get(listen.key)
|
|
listen.boundList = *bindList(nil, func(t1, t2 T) bool { return t1 == t2 })
|
|
|
|
listen.boundList.AddListener(NewDataListener(func() {
|
|
cached := *listen.val
|
|
replaced := listen.get(listen.key)
|
|
if len(cached) == len(replaced) {
|
|
return
|
|
}
|
|
|
|
listen.set(listen.key, *listen.val)
|
|
listen.trigger()
|
|
}))
|
|
|
|
listen.boundList.parentListener = func(index int) {
|
|
listen.set(listen.key, *listen.val)
|
|
|
|
// the child changes are not seen on the write end so force it
|
|
if prefs, ok := p.(internalPrefs); ok {
|
|
prefs.WriteValues(func(map[string]any) {})
|
|
}
|
|
}
|
|
listen.boundList.Set(items)
|
|
|
|
binds := prefBinds.ensurePreferencesAttached(p)
|
|
binds.Store(key, listen)
|
|
return listen
|
|
}
|