adam-gui/vendor/fyne.io/fyne/v2/data/binding/binding.go
2024-04-29 19:13:50 +02:00

179 lines
3.9 KiB
Go

//go:generate go run gen.go
// Package binding provides support for binding data to widgets.
package binding
import (
"errors"
"reflect"
"sync"
"fyne.io/fyne/v2"
)
var (
errKeyNotFound = errors.New("key not found")
errOutOfBounds = errors.New("index out of bounds")
errParseFailed = errors.New("format did not match 1 value")
// As an optimisation we connect any listeners asking for the same key, so that there is only 1 per preference item.
prefBinds = newPreferencesMap()
)
// DataItem is the base interface for all bindable data items.
//
// Since: 2.0
type DataItem interface {
// AddListener attaches a new change listener to this DataItem.
// Listeners are called each time the data inside this DataItem changes.
// Additionally the listener will be triggered upon successful connection to get the current value.
AddListener(DataListener)
// RemoveListener will detach the specified change listener from the DataItem.
// Disconnected listener will no longer be triggered when changes occur.
RemoveListener(DataListener)
}
// DataListener is any object that can register for changes in a bindable DataItem.
// See NewDataListener to define a new listener using just an inline function.
//
// Since: 2.0
type DataListener interface {
DataChanged()
}
// NewDataListener is a helper function that creates a new listener type from a simple callback function.
//
// Since: 2.0
func NewDataListener(fn func()) DataListener {
return &listener{fn}
}
type listener struct {
callback func()
}
func (l *listener) DataChanged() {
l.callback()
}
type base struct {
listeners sync.Map // map[DataListener]bool
lock sync.RWMutex
}
// AddListener allows a data listener to be informed of changes to this item.
func (b *base) AddListener(l DataListener) {
b.listeners.Store(l, true)
queueItem(l.DataChanged)
}
// RemoveListener should be called if the listener is no longer interested in being informed of data change events.
func (b *base) RemoveListener(l DataListener) {
b.listeners.Delete(l)
}
func (b *base) trigger() {
b.listeners.Range(func(key, _ interface{}) bool {
queueItem(key.(DataListener).DataChanged)
return true
})
}
// Untyped supports binding a interface{} value.
//
// Since: 2.1
type Untyped interface {
DataItem
Get() (interface{}, error)
Set(interface{}) error
}
// NewUntyped returns a bindable interface{} value that is managed internally.
//
// Since: 2.1
func NewUntyped() Untyped {
var blank interface{} = nil
v := &blank
return &boundUntyped{val: reflect.ValueOf(v).Elem()}
}
type boundUntyped struct {
base
val reflect.Value
}
func (b *boundUntyped) Get() (interface{}, error) {
b.lock.RLock()
defer b.lock.RUnlock()
return b.val.Interface(), nil
}
func (b *boundUntyped) Set(val interface{}) error {
b.lock.Lock()
defer b.lock.Unlock()
if b.val.Interface() == val {
return nil
}
b.val.Set(reflect.ValueOf(val))
b.trigger()
return nil
}
// ExternalUntyped supports binding a interface{} value to an external value.
//
// Since: 2.1
type ExternalUntyped interface {
Untyped
Reload() error
}
// BindUntyped returns a bindable interface{} value that is bound to an external type.
// The parameter must be a pointer to the type you wish to bind.
//
// Since: 2.1
func BindUntyped(v interface{}) ExternalUntyped {
t := reflect.TypeOf(v)
if t.Kind() != reflect.Ptr {
fyne.LogError("Invalid type passed to BindUntyped, must be a pointer", nil)
v = nil
}
if v == nil {
var blank interface{}
v = &blank // never allow a nil value pointer
}
b := &boundExternalUntyped{}
b.val = reflect.ValueOf(v).Elem()
b.old = b.val.Interface()
return b
}
type boundExternalUntyped struct {
boundUntyped
old interface{}
}
func (b *boundExternalUntyped) Set(val interface{}) error {
b.lock.Lock()
defer b.lock.Unlock()
if b.old == val {
return nil
}
b.val.Set(reflect.ValueOf(val))
b.old = val
b.trigger()
return nil
}
func (b *boundExternalUntyped) Reload() error {
return b.Set(b.val.Interface())
}