701 lines
16 KiB
Go
701 lines
16 KiB
Go
package binding
|
|
|
|
import (
|
|
"bytes"
|
|
|
|
"fyne.io/fyne/v2"
|
|
"fyne.io/fyne/v2/storage"
|
|
)
|
|
|
|
// DataTreeRootID const is the value used as ID for the root of any tree binding.
|
|
const DataTreeRootID = ""
|
|
|
|
// DataTree is the base interface for all bindable data trees.
|
|
//
|
|
// Since: 2.4
|
|
type DataTree interface {
|
|
DataItem
|
|
GetItem(id string) (DataItem, error)
|
|
ChildIDs(string) []string
|
|
}
|
|
|
|
// BoolTree supports binding a tree of bool values.
|
|
//
|
|
// Since: 2.4
|
|
type BoolTree interface {
|
|
DataTree
|
|
|
|
Append(parent, id string, value bool) error
|
|
Get() (map[string][]string, map[string]bool, error)
|
|
GetValue(id string) (bool, error)
|
|
Prepend(parent, id string, value bool) error
|
|
Remove(id string) error
|
|
Set(ids map[string][]string, values map[string]bool) error
|
|
SetValue(id string, value bool) error
|
|
}
|
|
|
|
// ExternalBoolTree supports binding a tree of bool values from an external variable.
|
|
//
|
|
// Since: 2.4
|
|
type ExternalBoolTree interface {
|
|
BoolTree
|
|
|
|
Reload() error
|
|
}
|
|
|
|
// NewBoolTree returns a bindable tree of bool values.
|
|
//
|
|
// Since: 2.4
|
|
func NewBoolTree() BoolTree {
|
|
return newTreeComparable[bool]()
|
|
}
|
|
|
|
// BindBoolTree returns a bound tree of bool values, based on the contents of the passed values.
|
|
// The ids map specifies how each item relates to its parent (with id ""), with the values being in the v map.
|
|
// If your code changes the content of the maps this refers to you should call Reload() to inform the bindings.
|
|
//
|
|
// Since: 2.4
|
|
func BindBoolTree(ids *map[string][]string, v *map[string]bool) ExternalBoolTree {
|
|
return bindTreeComparable(ids, v)
|
|
}
|
|
|
|
// BytesTree supports binding a tree of []byte values.
|
|
//
|
|
// Since: 2.4
|
|
type BytesTree interface {
|
|
DataTree
|
|
|
|
Append(parent, id string, value []byte) error
|
|
Get() (map[string][]string, map[string][]byte, error)
|
|
GetValue(id string) ([]byte, error)
|
|
Prepend(parent, id string, value []byte) error
|
|
Remove(id string) error
|
|
Set(ids map[string][]string, values map[string][]byte) error
|
|
SetValue(id string, value []byte) error
|
|
}
|
|
|
|
// ExternalBytesTree supports binding a tree of []byte values from an external variable.
|
|
//
|
|
// Since: 2.4
|
|
type ExternalBytesTree interface {
|
|
BytesTree
|
|
|
|
Reload() error
|
|
}
|
|
|
|
// NewBytesTree returns a bindable tree of []byte values.
|
|
//
|
|
// Since: 2.4
|
|
func NewBytesTree() BytesTree {
|
|
return newTree(bytes.Equal)
|
|
}
|
|
|
|
// BindBytesTree returns a bound tree of []byte values, based on the contents of the passed values.
|
|
// The ids map specifies how each item relates to its parent (with id ""), with the values being in the v map.
|
|
// If your code changes the content of the maps this refers to you should call Reload() to inform the bindings.
|
|
//
|
|
// Since: 2.4
|
|
func BindBytesTree(ids *map[string][]string, v *map[string][]byte) ExternalBytesTree {
|
|
return bindTree(ids, v, bytes.Equal)
|
|
}
|
|
|
|
// FloatTree supports binding a tree of float64 values.
|
|
//
|
|
// Since: 2.4
|
|
type FloatTree interface {
|
|
DataTree
|
|
|
|
Append(parent, id string, value float64) error
|
|
Get() (map[string][]string, map[string]float64, error)
|
|
GetValue(id string) (float64, error)
|
|
Prepend(parent, id string, value float64) error
|
|
Remove(id string) error
|
|
Set(ids map[string][]string, values map[string]float64) error
|
|
SetValue(id string, value float64) error
|
|
}
|
|
|
|
// ExternalFloatTree supports binding a tree of float64 values from an external variable.
|
|
//
|
|
// Since: 2.4
|
|
type ExternalFloatTree interface {
|
|
FloatTree
|
|
|
|
Reload() error
|
|
}
|
|
|
|
// NewFloatTree returns a bindable tree of float64 values.
|
|
//
|
|
// Since: 2.4
|
|
func NewFloatTree() FloatTree {
|
|
return newTreeComparable[float64]()
|
|
}
|
|
|
|
// BindFloatTree returns a bound tree of float64 values, based on the contents of the passed values.
|
|
// The ids map specifies how each item relates to its parent (with id ""), with the values being in the v map.
|
|
// If your code changes the content of the maps this refers to you should call Reload() to inform the bindings.
|
|
//
|
|
// Since: 2.4
|
|
func BindFloatTree(ids *map[string][]string, v *map[string]float64) ExternalFloatTree {
|
|
return bindTreeComparable(ids, v)
|
|
}
|
|
|
|
// IntTree supports binding a tree of int values.
|
|
//
|
|
// Since: 2.4
|
|
type IntTree interface {
|
|
DataTree
|
|
|
|
Append(parent, id string, value int) error
|
|
Get() (map[string][]string, map[string]int, error)
|
|
GetValue(id string) (int, error)
|
|
Prepend(parent, id string, value int) error
|
|
Remove(id string) error
|
|
Set(ids map[string][]string, values map[string]int) error
|
|
SetValue(id string, value int) error
|
|
}
|
|
|
|
// ExternalIntTree supports binding a tree of int values from an external variable.
|
|
//
|
|
// Since: 2.4
|
|
type ExternalIntTree interface {
|
|
IntTree
|
|
|
|
Reload() error
|
|
}
|
|
|
|
// NewIntTree returns a bindable tree of int values.
|
|
//
|
|
// Since: 2.4
|
|
func NewIntTree() IntTree {
|
|
return newTreeComparable[int]()
|
|
}
|
|
|
|
// BindIntTree returns a bound tree of int values, based on the contents of the passed values.
|
|
// The ids map specifies how each item relates to its parent (with id ""), with the values being in the v map.
|
|
// If your code changes the content of the maps this refers to you should call Reload() to inform the bindings.
|
|
//
|
|
// Since: 2.4
|
|
func BindIntTree(ids *map[string][]string, v *map[string]int) ExternalIntTree {
|
|
return bindTreeComparable(ids, v)
|
|
}
|
|
|
|
// RuneTree supports binding a tree of rune values.
|
|
//
|
|
// Since: 2.4
|
|
type RuneTree interface {
|
|
DataTree
|
|
|
|
Append(parent, id string, value rune) error
|
|
Get() (map[string][]string, map[string]rune, error)
|
|
GetValue(id string) (rune, error)
|
|
Prepend(parent, id string, value rune) error
|
|
Remove(id string) error
|
|
Set(ids map[string][]string, values map[string]rune) error
|
|
SetValue(id string, value rune) error
|
|
}
|
|
|
|
// ExternalRuneTree supports binding a tree of rune values from an external variable.
|
|
//
|
|
// Since: 2.4
|
|
type ExternalRuneTree interface {
|
|
RuneTree
|
|
|
|
Reload() error
|
|
}
|
|
|
|
// NewRuneTree returns a bindable tree of rune values.
|
|
//
|
|
// Since: 2.4
|
|
func NewRuneTree() RuneTree {
|
|
return newTreeComparable[rune]()
|
|
}
|
|
|
|
// BindRuneTree returns a bound tree of rune values, based on the contents of the passed values.
|
|
// The ids map specifies how each item relates to its parent (with id ""), with the values being in the v map.
|
|
// If your code changes the content of the maps this refers to you should call Reload() to inform the bindings.
|
|
//
|
|
// Since: 2.4
|
|
func BindRuneTree(ids *map[string][]string, v *map[string]rune) ExternalRuneTree {
|
|
return bindTreeComparable(ids, v)
|
|
}
|
|
|
|
// StringTree supports binding a tree of string values.
|
|
//
|
|
// Since: 2.4
|
|
type StringTree interface {
|
|
DataTree
|
|
|
|
Append(parent, id string, value string) error
|
|
Get() (map[string][]string, map[string]string, error)
|
|
GetValue(id string) (string, error)
|
|
Prepend(parent, id string, value string) error
|
|
Remove(id string) error
|
|
Set(ids map[string][]string, values map[string]string) error
|
|
SetValue(id string, value string) error
|
|
}
|
|
|
|
// ExternalStringTree supports binding a tree of string values from an external variable.
|
|
//
|
|
// Since: 2.4
|
|
type ExternalStringTree interface {
|
|
StringTree
|
|
|
|
Reload() error
|
|
}
|
|
|
|
// NewStringTree returns a bindable tree of string values.
|
|
//
|
|
// Since: 2.4
|
|
func NewStringTree() StringTree {
|
|
return newTreeComparable[string]()
|
|
}
|
|
|
|
// BindStringTree returns a bound tree of string values, based on the contents of the passed values.
|
|
// The ids map specifies how each item relates to its parent (with id ""), with the values being in the v map.
|
|
// If your code changes the content of the maps this refers to you should call Reload() to inform the bindings.
|
|
//
|
|
// Since: 2.4
|
|
func BindStringTree(ids *map[string][]string, v *map[string]string) ExternalStringTree {
|
|
return bindTreeComparable(ids, v)
|
|
}
|
|
|
|
// UntypedTree supports binding a tree of any values.
|
|
//
|
|
// Since: 2.5
|
|
type UntypedTree interface {
|
|
DataTree
|
|
|
|
Append(parent, id string, value any) error
|
|
Get() (map[string][]string, map[string]any, error)
|
|
GetValue(id string) (any, error)
|
|
Prepend(parent, id string, value any) error
|
|
Remove(id string) error
|
|
Set(ids map[string][]string, values map[string]any) error
|
|
SetValue(id string, value any) error
|
|
}
|
|
|
|
// ExternalUntypedTree supports binding a tree of any values from an external variable.
|
|
//
|
|
// Since: 2.5
|
|
type ExternalUntypedTree interface {
|
|
UntypedTree
|
|
|
|
Reload() error
|
|
}
|
|
|
|
// NewUntypedTree returns a bindable tree of any values.
|
|
//
|
|
// Since: 2.5
|
|
func NewUntypedTree() UntypedTree {
|
|
return newTree(func(a1, a2 any) bool { return a1 == a2 })
|
|
}
|
|
|
|
// BindUntypedTree returns a bound tree of any values, based on the contents of the passed values.
|
|
// The ids map specifies how each item relates to its parent (with id ""), with the values being in the v map.
|
|
// If your code changes the content of the maps this refers to you should call Reload() to inform the bindings.
|
|
//
|
|
// Since: 2.4
|
|
func BindUntypedTree(ids *map[string][]string, v *map[string]any) ExternalUntypedTree {
|
|
return bindTree(ids, v, func(a1, a2 any) bool { return a1 == a2 })
|
|
}
|
|
|
|
// URITree supports binding a tree of fyne.URI values.
|
|
//
|
|
// Since: 2.4
|
|
type URITree interface {
|
|
DataTree
|
|
|
|
Append(parent, id string, value fyne.URI) error
|
|
Get() (map[string][]string, map[string]fyne.URI, error)
|
|
GetValue(id string) (fyne.URI, error)
|
|
Prepend(parent, id string, value fyne.URI) error
|
|
Remove(id string) error
|
|
Set(ids map[string][]string, values map[string]fyne.URI) error
|
|
SetValue(id string, value fyne.URI) error
|
|
}
|
|
|
|
// ExternalURITree supports binding a tree of fyne.URI values from an external variable.
|
|
//
|
|
// Since: 2.4
|
|
type ExternalURITree interface {
|
|
URITree
|
|
|
|
Reload() error
|
|
}
|
|
|
|
// NewURITree returns a bindable tree of fyne.URI values.
|
|
//
|
|
// Since: 2.4
|
|
func NewURITree() URITree {
|
|
return newTree(storage.EqualURI)
|
|
}
|
|
|
|
// BindURITree returns a bound tree of fyne.URI values, based on the contents of the passed values.
|
|
// The ids map specifies how each item relates to its parent (with id ""), with the values being in the v map.
|
|
// If your code changes the content of the maps this refers to you should call Reload() to inform the bindings.
|
|
//
|
|
// Since: 2.4
|
|
func BindURITree(ids *map[string][]string, v *map[string]fyne.URI) ExternalURITree {
|
|
return bindTree(ids, v, storage.EqualURI)
|
|
}
|
|
|
|
type treeBase struct {
|
|
base
|
|
|
|
ids map[string][]string
|
|
items map[string]DataItem
|
|
}
|
|
|
|
// GetItem returns the DataItem at the specified id.
|
|
func (t *treeBase) GetItem(id string) (DataItem, error) {
|
|
t.lock.RLock()
|
|
defer t.lock.RUnlock()
|
|
|
|
if item, ok := t.items[id]; ok {
|
|
return item, nil
|
|
}
|
|
|
|
return nil, errOutOfBounds
|
|
}
|
|
|
|
// ChildIDs returns the ordered IDs of items in this data tree that are children of the specified ID.
|
|
func (t *treeBase) ChildIDs(id string) []string {
|
|
t.lock.RLock()
|
|
defer t.lock.RUnlock()
|
|
|
|
if ids, ok := t.ids[id]; ok {
|
|
return ids
|
|
}
|
|
|
|
return []string{}
|
|
}
|
|
|
|
func (t *treeBase) appendItem(i DataItem, id, parent string) {
|
|
t.items[id] = i
|
|
ids, ok := t.ids[parent]
|
|
if !ok {
|
|
ids = make([]string, 0)
|
|
}
|
|
|
|
for _, in := range ids {
|
|
if in == id {
|
|
return
|
|
}
|
|
}
|
|
t.ids[parent] = append(ids, id)
|
|
}
|
|
|
|
func (t *treeBase) deleteItem(id, parent string) {
|
|
delete(t.items, id)
|
|
|
|
ids, ok := t.ids[parent]
|
|
if !ok {
|
|
return
|
|
}
|
|
|
|
off := -1
|
|
for i, id2 := range ids {
|
|
if id2 == id {
|
|
off = i
|
|
break
|
|
}
|
|
}
|
|
if off == -1 {
|
|
return
|
|
}
|
|
t.ids[parent] = append(ids[:off], ids[off+1:]...)
|
|
}
|
|
|
|
func parentIDFor(id string, ids map[string][]string) string {
|
|
for parent, list := range ids {
|
|
for _, child := range list {
|
|
if child == id {
|
|
return parent
|
|
}
|
|
}
|
|
}
|
|
|
|
return ""
|
|
}
|
|
|
|
func newTree[T any](comparator func(T, T) bool) *boundTree[T] {
|
|
t := &boundTree[T]{val: &map[string]T{}, comparator: comparator}
|
|
t.ids = make(map[string][]string)
|
|
t.items = make(map[string]DataItem)
|
|
return t
|
|
}
|
|
|
|
func newTreeComparable[T bool | float64 | int | rune | string]() *boundTree[T] {
|
|
return newTree(func(t1, t2 T) bool { return t1 == t2 })
|
|
}
|
|
|
|
func bindTree[T any](ids *map[string][]string, v *map[string]T, comparator func(T, T) bool) *boundTree[T] {
|
|
if v == nil {
|
|
return newTree[T](comparator)
|
|
}
|
|
|
|
t := &boundTree[T]{val: v, updateExternal: true, comparator: comparator}
|
|
t.ids = make(map[string][]string)
|
|
t.items = make(map[string]DataItem)
|
|
|
|
for parent, children := range *ids {
|
|
for _, leaf := range children {
|
|
t.appendItem(bindTreeItem(v, leaf, t.updateExternal, t.comparator), leaf, parent)
|
|
}
|
|
}
|
|
|
|
return t
|
|
}
|
|
|
|
func bindTreeComparable[T bool | float64 | int | rune | string](ids *map[string][]string, v *map[string]T) *boundTree[T] {
|
|
return bindTree(ids, v, func(t1, t2 T) bool { return t1 == t2 })
|
|
}
|
|
|
|
type boundTree[T any] struct {
|
|
treeBase
|
|
|
|
comparator func(T, T) bool
|
|
val *map[string]T
|
|
updateExternal bool
|
|
}
|
|
|
|
func (t *boundTree[T]) Append(parent, id string, val T) error {
|
|
t.lock.Lock()
|
|
ids, ok := t.ids[parent]
|
|
if !ok {
|
|
ids = make([]string, 0)
|
|
}
|
|
|
|
t.ids[parent] = append(ids, id)
|
|
v := *t.val
|
|
v[id] = val
|
|
|
|
trigger, err := t.doReload()
|
|
t.lock.Unlock()
|
|
|
|
if trigger {
|
|
t.trigger()
|
|
}
|
|
|
|
return err
|
|
}
|
|
|
|
func (t *boundTree[T]) Get() (map[string][]string, map[string]T, error) {
|
|
t.lock.RLock()
|
|
defer t.lock.RUnlock()
|
|
|
|
return t.ids, *t.val, nil
|
|
}
|
|
|
|
func (t *boundTree[T]) GetValue(id string) (T, error) {
|
|
t.lock.RLock()
|
|
defer t.lock.RUnlock()
|
|
|
|
if item, ok := (*t.val)[id]; ok {
|
|
return item, nil
|
|
}
|
|
|
|
return *new(T), errOutOfBounds
|
|
}
|
|
|
|
func (t *boundTree[T]) Prepend(parent, id string, val T) error {
|
|
t.lock.Lock()
|
|
ids, ok := t.ids[parent]
|
|
if !ok {
|
|
ids = make([]string, 0)
|
|
}
|
|
|
|
t.ids[parent] = append([]string{id}, ids...)
|
|
v := *t.val
|
|
v[id] = val
|
|
|
|
trigger, err := t.doReload()
|
|
t.lock.Unlock()
|
|
|
|
if trigger {
|
|
t.trigger()
|
|
}
|
|
|
|
return err
|
|
}
|
|
|
|
func (t *boundTree[T]) Remove(id string) error {
|
|
t.lock.Lock()
|
|
t.removeChildren(id)
|
|
delete(t.ids, id)
|
|
v := *t.val
|
|
delete(v, id)
|
|
|
|
trigger, err := t.doReload()
|
|
t.lock.Unlock()
|
|
|
|
if trigger {
|
|
t.trigger()
|
|
}
|
|
|
|
return err
|
|
}
|
|
|
|
func (t *boundTree[T]) removeChildren(id string) {
|
|
for _, cid := range t.ids[id] {
|
|
t.removeChildren(cid)
|
|
|
|
delete(t.ids, cid)
|
|
v := *t.val
|
|
delete(v, cid)
|
|
}
|
|
}
|
|
|
|
func (t *boundTree[T]) Reload() error {
|
|
t.lock.Lock()
|
|
trigger, err := t.doReload()
|
|
t.lock.Unlock()
|
|
|
|
if trigger {
|
|
t.trigger()
|
|
}
|
|
|
|
return err
|
|
}
|
|
|
|
func (t *boundTree[T]) Set(ids map[string][]string, v map[string]T) error {
|
|
t.lock.Lock()
|
|
t.ids = ids
|
|
*t.val = v
|
|
|
|
trigger, err := t.doReload()
|
|
t.lock.Unlock()
|
|
|
|
if trigger {
|
|
t.trigger()
|
|
}
|
|
|
|
return err
|
|
}
|
|
|
|
func (t *boundTree[T]) doReload() (fire bool, retErr error) {
|
|
updated := []string{}
|
|
for id := range *t.val {
|
|
found := false
|
|
for child := range t.items {
|
|
if child == id { // update existing
|
|
updated = append(updated, id)
|
|
found = true
|
|
break
|
|
}
|
|
}
|
|
if found {
|
|
continue
|
|
}
|
|
|
|
// append new
|
|
t.appendItem(bindTreeItem(t.val, id, t.updateExternal, t.comparator), id, parentIDFor(id, t.ids))
|
|
updated = append(updated, id)
|
|
fire = true
|
|
}
|
|
|
|
for id := range t.items {
|
|
remove := true
|
|
for _, done := range updated {
|
|
if done == id {
|
|
remove = false
|
|
break
|
|
}
|
|
}
|
|
|
|
if remove { // remove item no longer present
|
|
fire = true
|
|
t.deleteItem(id, parentIDFor(id, t.ids))
|
|
}
|
|
}
|
|
|
|
for id, item := range t.items {
|
|
var err error
|
|
if t.updateExternal {
|
|
err = item.(*boundExternalTreeItem[T]).setIfChanged((*t.val)[id])
|
|
} else {
|
|
err = item.(*boundTreeItem[T]).doSet((*t.val)[id])
|
|
}
|
|
if err != nil {
|
|
retErr = err
|
|
}
|
|
}
|
|
return
|
|
}
|
|
|
|
func (t *boundTree[T]) SetValue(id string, v T) error {
|
|
t.lock.Lock()
|
|
(*t.val)[id] = v
|
|
t.lock.Unlock()
|
|
|
|
item, err := t.GetItem(id)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
return item.(Item[T]).Set(v)
|
|
}
|
|
|
|
func bindTreeItem[T any](v *map[string]T, id string, external bool, comparator func(T, T) bool) Item[T] {
|
|
if external {
|
|
ret := &boundExternalTreeItem[T]{old: (*v)[id], comparator: comparator}
|
|
ret.val = v
|
|
ret.id = id
|
|
return ret
|
|
}
|
|
|
|
return &boundTreeItem[T]{id: id, val: v}
|
|
}
|
|
|
|
type boundTreeItem[T any] struct {
|
|
base
|
|
|
|
val *map[string]T
|
|
id string
|
|
}
|
|
|
|
func (t *boundTreeItem[T]) Get() (T, error) {
|
|
t.lock.Lock()
|
|
defer t.lock.Unlock()
|
|
|
|
v := *t.val
|
|
if item, ok := v[t.id]; ok {
|
|
return item, nil
|
|
}
|
|
|
|
return *new(T), errOutOfBounds
|
|
}
|
|
|
|
func (t *boundTreeItem[T]) Set(val T) error {
|
|
return t.doSet(val)
|
|
}
|
|
|
|
func (t *boundTreeItem[T]) doSet(val T) error {
|
|
t.lock.Lock()
|
|
(*t.val)[t.id] = val
|
|
t.lock.Unlock()
|
|
|
|
t.trigger()
|
|
return nil
|
|
}
|
|
|
|
type boundExternalTreeItem[T any] struct {
|
|
boundTreeItem[T]
|
|
|
|
comparator func(T, T) bool
|
|
old T
|
|
}
|
|
|
|
func (t *boundExternalTreeItem[T]) setIfChanged(val T) error {
|
|
t.lock.Lock()
|
|
if t.comparator(val, t.old) {
|
|
t.lock.Unlock()
|
|
return nil
|
|
}
|
|
(*t.val)[t.id] = val
|
|
t.old = val
|
|
t.lock.Unlock()
|
|
|
|
t.trigger()
|
|
return nil
|
|
}
|