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 (any), 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 (any). // // Since: 2.0 type UntypedMap interface { DataMap Delete(string) Get() (map[string]any, error) GetValue(string) (any, error) Set(map[string]any) error SetValue(string, any) error } // NewUntypedMap creates a new, empty map binding of string to any. // // Since: 2.0 func NewUntypedMap() UntypedMap { return &mapBase{items: make(map[string]reflectUntyped), val: &map[string]any{}} } // BindUntypedMap creates a new map binding of string to any 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]any) 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) (any, error) SetValue(string, any) error Reload() error } // BindStruct creates a new map binding of string to any 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 any. // Only exported fields are included. // // Since: 2.0 func BindStruct(i any) 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]any{} 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() (any, error) set(any) error } type mapBase struct { base updateExternal bool items map[string]reflectUntyped val *map[string]any } 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]any, error) { b.lock.RLock() defer b.lock.RUnlock() if b.val == nil { return map[string]any{}, nil } return *b.val, nil } func (b *mapBase) GetValue(key string) (any, 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]any) 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 any) 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 retErr } func (b *mapBase) setItem(key string, d reflectUntyped) { b.items[key] = d b.trigger() } type boundStruct struct { mapBase orig any } 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].(*boundReflect[bool]).Set(f.Bool()) case reflect.Float32, reflect.Float64: err = b.items[key].(*boundReflect[float64]).Set(f.Float()) case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: err = b.items[key].(*boundReflect[int]).Set(int(f.Int())) case reflect.String: err = b.items[key].(*boundReflect[string]).Set(f.String()) } if err != nil { retErr = err } (*b.val)[key] = f.Interface() } return retErr } func bindUntypedMapValue(m *map[string]any, 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]any key string } func (b *boundMapValue) get() (any, error) { if v, ok := (*b.val)[b.key]; ok { return v, nil } return nil, errKeyNotFound } func (b *boundMapValue) set(val any) error { (*b.val)[b.key] = val b.trigger() return nil } type boundExternalMapValue struct { boundMapValue old any } func (b *boundExternalMapValue) setIfChanged(val any) error { if val == b.old { return nil } b.old = val return b.set(val) } type boundReflect[T any] struct { base val reflect.Value } func (b *boundReflect[T]) Get() (T, error) { var zero T val, err := b.get() if err != nil { return zero, err } casted, ok := val.(T) if !ok { return zero, errors.New("unable to convert value to type") } return casted, nil } func (b *boundReflect[T]) Set(val T) error { return b.set(val) } func (b *boundReflect[T]) get() (any, error) { if !b.val.CanInterface() { return nil, errors.New("unable to get value from data binding") } return b.val.Interface(), nil } func (b *boundReflect[T]) set(val any) error { if !b.val.CanSet() { return errors.New("unable to set value in data binding") } b.val.Set(reflect.ValueOf(val)) b.trigger() return nil } func bindReflect(field reflect.Value) reflectUntyped { switch field.Kind() { case reflect.Bool: return &boundReflect[bool]{val: field} case reflect.Float32, reflect.Float64: return &boundReflect[float64]{val: field} case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: return &boundReflect[int]{val: field} case reflect.String: return &boundReflect[string]{val: field} } return &boundReflect[any]{val: field} }