523 lines
9.8 KiB
Go
523 lines
9.8 KiB
Go
package binding
|
|
|
|
import (
|
|
"errors"
|
|
"reflect"
|
|
|
|
"fyne.io/fyne/v2"
|
|
)
|
|
|
|
// DataMap is the base interface for all bindable data maps.
|
|
//
|
|
// Since: 2.0
|
|
type DataMap interface {
|
|
DataItem
|
|
GetItem(string) (DataItem, error)
|
|
Keys() []string
|
|
}
|
|
|
|
// ExternalUntypedMap is a map data binding with all values untyped (interface{}), connected to an external data source.
|
|
//
|
|
// Since: 2.0
|
|
type ExternalUntypedMap interface {
|
|
UntypedMap
|
|
Reload() error
|
|
}
|
|
|
|
// UntypedMap is a map data binding with all values Untyped (interface{}).
|
|
//
|
|
// Since: 2.0
|
|
type UntypedMap interface {
|
|
DataMap
|
|
Delete(string)
|
|
Get() (map[string]interface{}, error)
|
|
GetValue(string) (interface{}, error)
|
|
Set(map[string]interface{}) error
|
|
SetValue(string, interface{}) error
|
|
}
|
|
|
|
// NewUntypedMap creates a new, empty map binding of string to interface{}.
|
|
//
|
|
// Since: 2.0
|
|
func NewUntypedMap() UntypedMap {
|
|
return &mapBase{items: make(map[string]reflectUntyped), val: &map[string]interface{}{}}
|
|
}
|
|
|
|
// BindUntypedMap creates a new map binding of string to interface{} based on the data passed.
|
|
// If your code changes the content of the map this refers to you should call Reload() to inform the bindings.
|
|
//
|
|
// Since: 2.0
|
|
func BindUntypedMap(d *map[string]interface{}) ExternalUntypedMap {
|
|
if d == nil {
|
|
return NewUntypedMap().(ExternalUntypedMap)
|
|
}
|
|
m := &mapBase{items: make(map[string]reflectUntyped), val: d, updateExternal: true}
|
|
|
|
for k := range *d {
|
|
m.setItem(k, bindUntypedMapValue(d, k, m.updateExternal))
|
|
}
|
|
|
|
return m
|
|
}
|
|
|
|
// Struct is the base interface for a bound struct type.
|
|
//
|
|
// Since: 2.0
|
|
type Struct interface {
|
|
DataMap
|
|
GetValue(string) (interface{}, error)
|
|
SetValue(string, interface{}) error
|
|
Reload() error
|
|
}
|
|
|
|
// BindStruct creates a new map binding of string to interface{} using the struct passed as data.
|
|
// The key for each item is a string representation of each exported field with the value set as an interface{}.
|
|
// Only exported fields are included.
|
|
//
|
|
// Since: 2.0
|
|
func BindStruct(i interface{}) Struct {
|
|
if i == nil {
|
|
return NewUntypedMap().(Struct)
|
|
}
|
|
t := reflect.TypeOf(i)
|
|
if t.Kind() != reflect.Ptr ||
|
|
(reflect.TypeOf(reflect.ValueOf(i).Elem()).Kind() != reflect.Struct) {
|
|
fyne.LogError("Invalid type passed to BindStruct, must be pointer to struct", nil)
|
|
return NewUntypedMap().(Struct)
|
|
}
|
|
|
|
s := &boundStruct{orig: i}
|
|
s.items = make(map[string]reflectUntyped)
|
|
s.val = &map[string]interface{}{}
|
|
s.updateExternal = true
|
|
|
|
v := reflect.ValueOf(i).Elem()
|
|
t = v.Type()
|
|
for j := 0; j < v.NumField(); j++ {
|
|
f := v.Field(j)
|
|
if !f.CanSet() {
|
|
continue
|
|
}
|
|
|
|
key := t.Field(j).Name
|
|
s.items[key] = bindReflect(f)
|
|
(*s.val)[key] = f.Interface()
|
|
}
|
|
|
|
return s
|
|
}
|
|
|
|
type reflectUntyped interface {
|
|
DataItem
|
|
get() (interface{}, error)
|
|
set(interface{}) error
|
|
}
|
|
|
|
type mapBase struct {
|
|
base
|
|
|
|
updateExternal bool
|
|
items map[string]reflectUntyped
|
|
val *map[string]interface{}
|
|
}
|
|
|
|
func (b *mapBase) GetItem(key string) (DataItem, error) {
|
|
b.lock.RLock()
|
|
defer b.lock.RUnlock()
|
|
|
|
if v, ok := b.items[key]; ok {
|
|
return v, nil
|
|
}
|
|
|
|
return nil, errKeyNotFound
|
|
}
|
|
|
|
func (b *mapBase) Keys() []string {
|
|
b.lock.Lock()
|
|
defer b.lock.Unlock()
|
|
|
|
ret := make([]string, len(b.items))
|
|
i := 0
|
|
for k := range b.items {
|
|
ret[i] = k
|
|
i++
|
|
}
|
|
|
|
return ret
|
|
}
|
|
|
|
func (b *mapBase) Delete(key string) {
|
|
b.lock.Lock()
|
|
defer b.lock.Unlock()
|
|
|
|
delete(b.items, key)
|
|
|
|
b.trigger()
|
|
}
|
|
|
|
func (b *mapBase) Get() (map[string]interface{}, error) {
|
|
b.lock.RLock()
|
|
defer b.lock.RUnlock()
|
|
|
|
if b.val == nil {
|
|
return map[string]interface{}{}, nil
|
|
}
|
|
|
|
return *b.val, nil
|
|
}
|
|
|
|
func (b *mapBase) GetValue(key string) (interface{}, error) {
|
|
b.lock.RLock()
|
|
defer b.lock.RUnlock()
|
|
|
|
if i, ok := b.items[key]; ok {
|
|
return i.get()
|
|
}
|
|
|
|
return nil, errKeyNotFound
|
|
}
|
|
|
|
func (b *mapBase) Reload() error {
|
|
b.lock.Lock()
|
|
defer b.lock.Unlock()
|
|
|
|
return b.doReload()
|
|
}
|
|
|
|
func (b *mapBase) Set(v map[string]interface{}) error {
|
|
b.lock.Lock()
|
|
defer b.lock.Unlock()
|
|
|
|
if b.val == nil { // was not initialized with a blank value, recover
|
|
b.val = &v
|
|
b.trigger()
|
|
return nil
|
|
}
|
|
|
|
*b.val = v
|
|
return b.doReload()
|
|
}
|
|
|
|
func (b *mapBase) SetValue(key string, d interface{}) error {
|
|
b.lock.Lock()
|
|
defer b.lock.Unlock()
|
|
|
|
if i, ok := b.items[key]; ok {
|
|
return i.set(d)
|
|
}
|
|
|
|
(*b.val)[key] = d
|
|
item := bindUntypedMapValue(b.val, key, b.updateExternal)
|
|
b.setItem(key, item)
|
|
return nil
|
|
}
|
|
|
|
func (b *mapBase) doReload() (retErr error) {
|
|
changed := false
|
|
// add new
|
|
for key := range *b.val {
|
|
_, found := b.items[key]
|
|
if !found {
|
|
b.setItem(key, bindUntypedMapValue(b.val, key, b.updateExternal))
|
|
changed = true
|
|
}
|
|
}
|
|
|
|
// remove old
|
|
for key := range b.items {
|
|
_, found := (*b.val)[key]
|
|
if !found {
|
|
delete(b.items, key)
|
|
changed = true
|
|
}
|
|
}
|
|
if changed {
|
|
b.trigger()
|
|
}
|
|
|
|
for k, item := range b.items {
|
|
var err error
|
|
|
|
if b.updateExternal {
|
|
err = item.(*boundExternalMapValue).setIfChanged((*b.val)[k])
|
|
} else {
|
|
err = item.(*boundMapValue).set((*b.val)[k])
|
|
}
|
|
|
|
if err != nil {
|
|
retErr = err
|
|
}
|
|
}
|
|
return
|
|
}
|
|
|
|
func (b *mapBase) setItem(key string, d reflectUntyped) {
|
|
b.items[key] = d
|
|
|
|
b.trigger()
|
|
}
|
|
|
|
type boundStruct struct {
|
|
mapBase
|
|
|
|
orig interface{}
|
|
}
|
|
|
|
func (b *boundStruct) Reload() (retErr error) {
|
|
b.lock.Lock()
|
|
defer b.lock.Unlock()
|
|
|
|
v := reflect.ValueOf(b.orig).Elem()
|
|
t := v.Type()
|
|
for j := 0; j < v.NumField(); j++ {
|
|
f := v.Field(j)
|
|
if !f.CanSet() {
|
|
continue
|
|
}
|
|
kind := f.Kind()
|
|
if kind == reflect.Slice || kind == reflect.Struct {
|
|
fyne.LogError("Data binding does not yet support slice or struct elements in a struct", nil)
|
|
continue
|
|
}
|
|
|
|
key := t.Field(j).Name
|
|
old := (*b.val)[key]
|
|
if f.Interface() == old {
|
|
continue
|
|
}
|
|
|
|
var err error
|
|
switch kind {
|
|
case reflect.Bool:
|
|
err = b.items[key].(*reflectBool).Set(f.Bool())
|
|
case reflect.Float32, reflect.Float64:
|
|
err = b.items[key].(*reflectFloat).Set(f.Float())
|
|
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
|
|
err = b.items[key].(*reflectInt).Set(int(f.Int()))
|
|
case reflect.String:
|
|
err = b.items[key].(*reflectString).Set(f.String())
|
|
}
|
|
if err != nil {
|
|
retErr = err
|
|
}
|
|
(*b.val)[key] = f.Interface()
|
|
}
|
|
return
|
|
}
|
|
|
|
func bindUntypedMapValue(m *map[string]interface{}, k string, external bool) reflectUntyped {
|
|
if external {
|
|
ret := &boundExternalMapValue{old: (*m)[k]}
|
|
ret.val = m
|
|
ret.key = k
|
|
return ret
|
|
}
|
|
|
|
return &boundMapValue{val: m, key: k}
|
|
}
|
|
|
|
type boundMapValue struct {
|
|
base
|
|
|
|
val *map[string]interface{}
|
|
key string
|
|
}
|
|
|
|
func (b *boundMapValue) get() (interface{}, error) {
|
|
if v, ok := (*b.val)[b.key]; ok {
|
|
return v, nil
|
|
}
|
|
|
|
return nil, errKeyNotFound
|
|
}
|
|
|
|
func (b *boundMapValue) set(val interface{}) error {
|
|
(*b.val)[b.key] = val
|
|
|
|
b.trigger()
|
|
return nil
|
|
}
|
|
|
|
type boundExternalMapValue struct {
|
|
boundMapValue
|
|
|
|
old interface{}
|
|
}
|
|
|
|
func (b *boundExternalMapValue) setIfChanged(val interface{}) error {
|
|
if val == b.old {
|
|
return nil
|
|
}
|
|
b.old = val
|
|
|
|
return b.set(val)
|
|
}
|
|
|
|
type boundReflect struct {
|
|
base
|
|
|
|
val reflect.Value
|
|
}
|
|
|
|
func (b *boundReflect) get() (interface{}, error) {
|
|
return b.val.Interface(), nil
|
|
}
|
|
|
|
func (b *boundReflect) set(val interface{}) (err error) {
|
|
defer func() {
|
|
if r := recover(); r != nil {
|
|
err = errors.New("unable to set bool in data binding")
|
|
}
|
|
}()
|
|
b.val.Set(reflect.ValueOf(val))
|
|
|
|
b.trigger()
|
|
return nil
|
|
}
|
|
|
|
type reflectBool struct {
|
|
boundReflect
|
|
}
|
|
|
|
func (r *reflectBool) Get() (val bool, err error) {
|
|
defer func() {
|
|
if r := recover(); r != nil {
|
|
err = errors.New("invalid bool value in data binding")
|
|
}
|
|
}()
|
|
|
|
val = r.val.Bool()
|
|
return
|
|
}
|
|
|
|
func (r *reflectBool) Set(b bool) (err error) {
|
|
defer func() {
|
|
if r := recover(); r != nil {
|
|
err = errors.New("unable to set bool in data binding")
|
|
}
|
|
}()
|
|
|
|
r.val.SetBool(b)
|
|
r.trigger()
|
|
return
|
|
}
|
|
|
|
func bindReflectBool(f reflect.Value) reflectUntyped {
|
|
r := &reflectBool{}
|
|
r.val = f
|
|
return r
|
|
}
|
|
|
|
type reflectFloat struct {
|
|
boundReflect
|
|
}
|
|
|
|
func (r *reflectFloat) Get() (val float64, err error) {
|
|
defer func() {
|
|
if r := recover(); r != nil {
|
|
err = errors.New("invalid float64 value in data binding")
|
|
}
|
|
}()
|
|
|
|
val = r.val.Float()
|
|
return
|
|
}
|
|
|
|
func (r *reflectFloat) Set(f float64) (err error) {
|
|
defer func() {
|
|
if r := recover(); r != nil {
|
|
err = errors.New("unable to set float64 in data binding")
|
|
}
|
|
}()
|
|
|
|
r.val.SetFloat(f)
|
|
r.trigger()
|
|
return
|
|
}
|
|
|
|
func bindReflectFloat(f reflect.Value) reflectUntyped {
|
|
r := &reflectFloat{}
|
|
r.val = f
|
|
return r
|
|
}
|
|
|
|
type reflectInt struct {
|
|
boundReflect
|
|
}
|
|
|
|
func (r *reflectInt) Get() (val int, err error) {
|
|
defer func() {
|
|
if r := recover(); r != nil {
|
|
err = errors.New("invalid int value in data binding")
|
|
}
|
|
}()
|
|
|
|
val = int(r.val.Int())
|
|
return
|
|
}
|
|
|
|
func (r *reflectInt) Set(i int) (err error) {
|
|
defer func() {
|
|
if r := recover(); r != nil {
|
|
err = errors.New("unable to set int in data binding")
|
|
}
|
|
}()
|
|
|
|
r.val.SetInt(int64(i))
|
|
r.trigger()
|
|
return
|
|
}
|
|
|
|
func bindReflectInt(f reflect.Value) reflectUntyped {
|
|
r := &reflectInt{}
|
|
r.val = f
|
|
return r
|
|
}
|
|
|
|
type reflectString struct {
|
|
boundReflect
|
|
}
|
|
|
|
func (r *reflectString) Get() (val string, err error) {
|
|
defer func() {
|
|
if r := recover(); r != nil {
|
|
err = errors.New("invalid string value in data binding")
|
|
}
|
|
}()
|
|
|
|
val = r.val.String()
|
|
return
|
|
}
|
|
|
|
func (r *reflectString) Set(s string) (err error) {
|
|
defer func() {
|
|
if r := recover(); r != nil {
|
|
err = errors.New("unable to set string in data binding")
|
|
}
|
|
}()
|
|
|
|
r.val.SetString(s)
|
|
r.trigger()
|
|
return
|
|
}
|
|
|
|
func bindReflectString(f reflect.Value) reflectUntyped {
|
|
r := &reflectString{}
|
|
r.val = f
|
|
return r
|
|
}
|
|
|
|
func bindReflect(field reflect.Value) reflectUntyped {
|
|
switch field.Kind() {
|
|
case reflect.Bool:
|
|
return bindReflectBool(field)
|
|
case reflect.Float32, reflect.Float64:
|
|
return bindReflectFloat(field)
|
|
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
|
|
return bindReflectInt(field)
|
|
case reflect.String:
|
|
return bindReflectString(field)
|
|
}
|
|
return &boundReflect{val: field}
|
|
}
|