From 64d4ffdb1aa52bac2a5eddcf53f3f4d60192f6d4 Mon Sep 17 00:00:00 2001 From: Jacalz Date: Fri, 31 Jan 2025 11:46:00 +0100 Subject: [PATCH 1/5] Rename files to not start with "bind" --- data/binding/{bindlists.go => lists.go} | 0 data/binding/{bindlists_test.go => lists_test.go} | 0 data/binding/{bindtrees.go => trees.go} | 0 data/binding/{bindtrees_test.go => trees_test.go} | 0 4 files changed, 0 insertions(+), 0 deletions(-) rename data/binding/{bindlists.go => lists.go} (100%) rename data/binding/{bindlists_test.go => lists_test.go} (100%) rename data/binding/{bindtrees.go => trees.go} (100%) rename data/binding/{bindtrees_test.go => trees_test.go} (100%) diff --git a/data/binding/bindlists.go b/data/binding/lists.go similarity index 100% rename from data/binding/bindlists.go rename to data/binding/lists.go diff --git a/data/binding/bindlists_test.go b/data/binding/lists_test.go similarity index 100% rename from data/binding/bindlists_test.go rename to data/binding/lists_test.go diff --git a/data/binding/bindtrees.go b/data/binding/trees.go similarity index 100% rename from data/binding/bindtrees.go rename to data/binding/trees.go diff --git a/data/binding/bindtrees_test.go b/data/binding/trees_test.go similarity index 100% rename from data/binding/bindtrees_test.go rename to data/binding/trees_test.go From 184df2d22300541b5f2bec2c1961c1d38e1fefb2 Mon Sep 17 00:00:00 2001 From: Jacalz Date: Fri, 31 Jan 2025 11:48:36 +0100 Subject: [PATCH 2/5] Move list stuff into one file --- data/binding/listbinding.go | 43 --------------------- data/binding/listbinding_test.go | 66 -------------------------------- data/binding/lists.go | 42 ++++++++++++++++++++ data/binding/lists_test.go | 59 ++++++++++++++++++++++++++++ 4 files changed, 101 insertions(+), 109 deletions(-) delete mode 100644 data/binding/listbinding.go delete mode 100644 data/binding/listbinding_test.go diff --git a/data/binding/listbinding.go b/data/binding/listbinding.go deleted file mode 100644 index 539f307c5a..0000000000 --- a/data/binding/listbinding.go +++ /dev/null @@ -1,43 +0,0 @@ -package binding - -// DataList is the base interface for all bindable data lists. -// -// Since: 2.0 -type DataList interface { - DataItem - GetItem(index int) (DataItem, error) - Length() int -} - -type listBase struct { - base - items []DataItem -} - -// GetItem returns the DataItem at the specified index. -func (b *listBase) GetItem(i int) (DataItem, error) { - b.lock.RLock() - defer b.lock.RUnlock() - - if i < 0 || i >= len(b.items) { - return nil, errOutOfBounds - } - - return b.items[i], nil -} - -// Length returns the number of items in this data list. -func (b *listBase) Length() int { - b.lock.RLock() - defer b.lock.RUnlock() - - return len(b.items) -} - -func (b *listBase) appendItem(i DataItem) { - b.items = append(b.items, i) -} - -func (b *listBase) deleteItem(i int) { - b.items = append(b.items[:i], b.items[i+1:]...) -} diff --git a/data/binding/listbinding_test.go b/data/binding/listbinding_test.go deleted file mode 100644 index a67081e0f4..0000000000 --- a/data/binding/listbinding_test.go +++ /dev/null @@ -1,66 +0,0 @@ -package binding - -import ( - "testing" - - "github.com/stretchr/testify/assert" -) - -type simpleList struct { - listBase -} - -func TestListBase_AddListener(t *testing.T) { - data := &simpleList{} - assert.Equal(t, 0, data.listeners.Len()) - - called := false - fn := NewDataListener(func() { - called = true - }) - data.AddListener(fn) - assert.Equal(t, 1, data.listeners.Len()) - - data.trigger() - assert.True(t, called) -} - -func TestListBase_GetItem(t *testing.T) { - data := &simpleList{} - f := 0.5 - data.appendItem(BindFloat(&f)) - assert.Equal(t, 1, len(data.items)) - - item, err := data.GetItem(0) - assert.Nil(t, err) - val, err := item.(Float).Get() - assert.Nil(t, err) - assert.Equal(t, f, val) - - _, err = data.GetItem(5) - assert.NotNil(t, err) -} - -func TestListBase_Length(t *testing.T) { - data := &simpleList{} - assert.Equal(t, 0, data.Length()) - - data.appendItem(NewFloat()) - assert.Equal(t, 1, data.Length()) -} - -func TestListBase_RemoveListener(t *testing.T) { - called := false - fn := NewDataListener(func() { - called = true - }) - data := &simpleList{} - data.listeners.Store(fn, true) - - assert.Equal(t, 1, data.listeners.Len()) - data.RemoveListener(fn) - assert.Equal(t, 0, data.listeners.Len()) - - data.trigger() - assert.False(t, called) -} diff --git a/data/binding/lists.go b/data/binding/lists.go index 38ee6e4e48..184e46185b 100644 --- a/data/binding/lists.go +++ b/data/binding/lists.go @@ -6,6 +6,15 @@ import ( "fyne.io/fyne/v2" ) +// DataList is the base interface for all bindable data lists. +// +// Since: 2.0 +type DataList interface { + DataItem + GetItem(index int) (DataItem, error) + Length() int +} + // BoolList supports binding a list of bool values. // // Since: 2.0 @@ -339,3 +348,36 @@ func NewURIList() URIList { func BindURIList(v *[]fyne.URI) ExternalURIList { return bindList(v, compareURI) } + +type listBase struct { + base + items []DataItem +} + +// GetItem returns the DataItem at the specified index. +func (b *listBase) GetItem(i int) (DataItem, error) { + b.lock.RLock() + defer b.lock.RUnlock() + + if i < 0 || i >= len(b.items) { + return nil, errOutOfBounds + } + + return b.items[i], nil +} + +// Length returns the number of items in this data list. +func (b *listBase) Length() int { + b.lock.RLock() + defer b.lock.RUnlock() + + return len(b.items) +} + +func (b *listBase) appendItem(i DataItem) { + b.items = append(b.items, i) +} + +func (b *listBase) deleteItem(i int) { + b.items = append(b.items[:i], b.items[i+1:]...) +} diff --git a/data/binding/lists_test.go b/data/binding/lists_test.go index 3ba96ae98d..ed3632100a 100644 --- a/data/binding/lists_test.go +++ b/data/binding/lists_test.go @@ -6,6 +6,65 @@ import ( "github.com/stretchr/testify/assert" ) +type simpleList struct { + listBase +} + +func TestListBase_AddListener(t *testing.T) { + data := &simpleList{} + assert.Equal(t, 0, data.listeners.Len()) + + called := false + fn := NewDataListener(func() { + called = true + }) + data.AddListener(fn) + assert.Equal(t, 1, data.listeners.Len()) + + data.trigger() + assert.True(t, called) +} + +func TestListBase_GetItem(t *testing.T) { + data := &simpleList{} + f := 0.5 + data.appendItem(BindFloat(&f)) + assert.Equal(t, 1, len(data.items)) + + item, err := data.GetItem(0) + assert.Nil(t, err) + val, err := item.(Float).Get() + assert.Nil(t, err) + assert.Equal(t, f, val) + + _, err = data.GetItem(5) + assert.NotNil(t, err) +} + +func TestListBase_Length(t *testing.T) { + data := &simpleList{} + assert.Equal(t, 0, data.Length()) + + data.appendItem(NewFloat()) + assert.Equal(t, 1, data.Length()) +} + +func TestListBase_RemoveListener(t *testing.T) { + called := false + fn := NewDataListener(func() { + called = true + }) + data := &simpleList{} + data.listeners.Store(fn, true) + + assert.Equal(t, 1, data.listeners.Len()) + data.RemoveListener(fn) + assert.Equal(t, 0, data.listeners.Len()) + + data.trigger() + assert.False(t, called) +} + func TestBindFloatList(t *testing.T) { l := []float64{1.0, 5.0, 2.3} f := BindFloatList(&l) From bd52517e9c0be007661c1db5c670d4f5253c50a8 Mon Sep 17 00:00:00 2001 From: Jacalz Date: Fri, 31 Jan 2025 11:50:40 +0100 Subject: [PATCH 3/5] Move tree binding stuff into one file as well --- data/binding/treebinding.go | 92 -------------------------------- data/binding/treebinding_test.go | 75 -------------------------- data/binding/trees.go | 91 +++++++++++++++++++++++++++++++ data/binding/trees_test.go | 68 +++++++++++++++++++++++ 4 files changed, 159 insertions(+), 167 deletions(-) delete mode 100644 data/binding/treebinding.go delete mode 100644 data/binding/treebinding_test.go diff --git a/data/binding/treebinding.go b/data/binding/treebinding.go deleted file mode 100644 index 6d9c817a87..0000000000 --- a/data/binding/treebinding.go +++ /dev/null @@ -1,92 +0,0 @@ -package binding - -// 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 -} - -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 "" -} diff --git a/data/binding/treebinding_test.go b/data/binding/treebinding_test.go deleted file mode 100644 index eba1739224..0000000000 --- a/data/binding/treebinding_test.go +++ /dev/null @@ -1,75 +0,0 @@ -package binding - -import ( - "testing" - - "github.com/stretchr/testify/assert" -) - -func TestTreeBase_AddListener(t *testing.T) { - data := newSimpleTree() - assert.Equal(t, 0, data.listeners.Len()) - - called := false - fn := NewDataListener(func() { - called = true - }) - data.AddListener(fn) - assert.Equal(t, 1, data.listeners.Len()) - - data.trigger() - assert.True(t, called) -} - -func TestTreeBase_GetItem(t *testing.T) { - data := newSimpleTree() - f := 0.5 - data.appendItem(BindFloat(&f), "f", "") - assert.Equal(t, 1, len(data.items)) - - item, err := data.GetItem("f") - assert.Nil(t, err) - val, err := item.(Float).Get() - assert.Nil(t, err) - assert.Equal(t, f, val) - - _, err = data.GetItem("g") - assert.NotNil(t, err) -} - -func TestListBase_IDs(t *testing.T) { - data := newSimpleTree() - assert.Equal(t, 0, len(data.ChildIDs(""))) - - data.appendItem(NewFloat(), "1", "") - assert.Equal(t, 1, len(data.ChildIDs(""))) - assert.Equal(t, "1", data.ChildIDs("")[0]) -} - -func TestTreeBase_RemoveListener(t *testing.T) { - called := false - fn := NewDataListener(func() { - called = true - }) - data := newSimpleTree() - data.listeners.Store(fn, true) - - assert.Equal(t, 1, data.listeners.Len()) - data.RemoveListener(fn) - assert.Equal(t, 0, data.listeners.Len()) - - data.trigger() - assert.False(t, called) -} - -type simpleTree struct { - treeBase -} - -func newSimpleTree() *simpleTree { - t := &simpleTree{} - t.ids = map[string][]string{} - t.items = map[string]DataItem{} - - return t -} diff --git a/data/binding/trees.go b/data/binding/trees.go index 60e50f1a09..c00f70ab8b 100644 --- a/data/binding/trees.go +++ b/data/binding/trees.go @@ -6,6 +6,18 @@ import ( "fyne.io/fyne/v2" ) +// 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 @@ -325,3 +337,82 @@ func NewURITree() URITree { func BindURITree(ids *map[string][]string, v *map[string]fyne.URI) ExternalURITree { return bindTree(ids, v, compareURI) } + +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 "" +} diff --git a/data/binding/trees_test.go b/data/binding/trees_test.go index 52ac478f65..9106bc10f2 100644 --- a/data/binding/trees_test.go +++ b/data/binding/trees_test.go @@ -6,6 +6,74 @@ import ( "github.com/stretchr/testify/assert" ) +func TestTreeBase_AddListener(t *testing.T) { + data := newSimpleTree() + assert.Equal(t, 0, data.listeners.Len()) + + called := false + fn := NewDataListener(func() { + called = true + }) + data.AddListener(fn) + assert.Equal(t, 1, data.listeners.Len()) + + data.trigger() + assert.True(t, called) +} + +func TestTreeBase_GetItem(t *testing.T) { + data := newSimpleTree() + f := 0.5 + data.appendItem(BindFloat(&f), "f", "") + assert.Equal(t, 1, len(data.items)) + + item, err := data.GetItem("f") + assert.Nil(t, err) + val, err := item.(Float).Get() + assert.Nil(t, err) + assert.Equal(t, f, val) + + _, err = data.GetItem("g") + assert.NotNil(t, err) +} + +func TestListBase_IDs(t *testing.T) { + data := newSimpleTree() + assert.Equal(t, 0, len(data.ChildIDs(""))) + + data.appendItem(NewFloat(), "1", "") + assert.Equal(t, 1, len(data.ChildIDs(""))) + assert.Equal(t, "1", data.ChildIDs("")[0]) +} + +func TestTreeBase_RemoveListener(t *testing.T) { + called := false + fn := NewDataListener(func() { + called = true + }) + data := newSimpleTree() + data.listeners.Store(fn, true) + + assert.Equal(t, 1, data.listeners.Len()) + data.RemoveListener(fn) + assert.Equal(t, 0, data.listeners.Len()) + + data.trigger() + assert.False(t, called) +} + +type simpleTree struct { + treeBase +} + +func newSimpleTree() *simpleTree { + t := &simpleTree{} + t.ids = map[string][]string{} + t.items = map[string]DataItem{} + + return t +} + func TestBindStringTree(t *testing.T) { ids := map[string][]string{DataTreeRootID: {"1", "5", "2"}} l := map[string]string{"1": "one", "5": "five", "2": "two and a half"} From 277eaa193b7281c344f0cbf5b1f97647232b6cd9 Mon Sep 17 00:00:00 2001 From: Jacalz Date: Fri, 31 Jan 2025 11:51:45 +0100 Subject: [PATCH 4/5] Rename map binding files as well --- data/binding/{mapbinding.go => maps.go} | 0 data/binding/{mapbinding_test.go => maps_test.go} | 0 2 files changed, 0 insertions(+), 0 deletions(-) rename data/binding/{mapbinding.go => maps.go} (100%) rename data/binding/{mapbinding_test.go => maps_test.go} (100%) diff --git a/data/binding/mapbinding.go b/data/binding/maps.go similarity index 100% rename from data/binding/mapbinding.go rename to data/binding/maps.go diff --git a/data/binding/mapbinding_test.go b/data/binding/maps_test.go similarity index 100% rename from data/binding/mapbinding_test.go rename to data/binding/maps_test.go From 4b41c070d229f8d1d27eea81accff8449da6b648 Mon Sep 17 00:00:00 2001 From: Jacalz Date: Fri, 31 Jan 2025 11:53:52 +0100 Subject: [PATCH 5/5] Move benchmarks into convert tests --- data/binding/convert_benchmark_test.go | 107 ------------------------- data/binding/convert_test.go | 102 +++++++++++++++++++++++ 2 files changed, 102 insertions(+), 107 deletions(-) delete mode 100644 data/binding/convert_benchmark_test.go diff --git a/data/binding/convert_benchmark_test.go b/data/binding/convert_benchmark_test.go deleted file mode 100644 index b2bd985897..0000000000 --- a/data/binding/convert_benchmark_test.go +++ /dev/null @@ -1,107 +0,0 @@ -package binding - -import ( - "testing" -) - -func BenchmarkBoolToString(b *testing.B) { - for i := 0; i < b.N; i++ { - bo := NewBool() - s := BoolToString(bo) - s.Get() - - bo.Set(true) - s.Get() - - s.Set("trap") - bo.Get() - - s.Set("false") - bo.Get() - } -} - -func BenchmarkFloatToString(b *testing.B) { - for i := 0; i < b.N; i++ { - f := NewFloat() - s := FloatToString(f) - s.Get() - - f.Set(0.3) - s.Get() - - s.Set("wrong") - f.Get() - - s.Set("5.00") - f.Get() - } -} - -func BenchmarkIntToString(b *testing.B) { - for i := 0; i < b.N; i++ { - i := NewInt() - s := IntToString(i) - s.Get() - - i.Set(3) - s.Get() - - s.Set("wrong") - i.Get() - - s.Set("5") - i.Get() - } -} - -func BenchmarkStringToBool(b *testing.B) { - for i := 0; i < b.N; i++ { - s := NewString() - b := StringToBool(s) - b.Get() - - s.Set("true") - b.Get() - - s.Set("trap") // bug in fmt.SScanf means "wrong" parses as "false" - b.Get() - - b.Set(false) - s.Get() - } -} - -func BenchmarkStringToFloat(b *testing.B) { - for i := 0; i < b.N; i++ { - s := NewString() - f := StringToFloat(s) - f.Get() - - s.Set("3") - f.Get() - - s.Set("wrong") - f.Get() - - f.Set(5) - s.Get() - } -} - -func BenchmarkStringToInt(b *testing.B) { - for i := 0; i < b.N; i++ { - s := NewString() - i := StringToInt(s) - i.Get() - - s.Set("3") - i.Get() - - s.Set("wrong") - i.Get() - - i.Set(5) - s.Get() - } -} diff --git a/data/binding/convert_test.go b/data/binding/convert_test.go index bd7dfa7a9a..8c4243cb93 100644 --- a/data/binding/convert_test.go +++ b/data/binding/convert_test.go @@ -8,6 +8,108 @@ import ( "fyne.io/fyne/v2/storage" ) +func BenchmarkBoolToString(b *testing.B) { + for i := 0; i < b.N; i++ { + bo := NewBool() + s := BoolToString(bo) + s.Get() + + bo.Set(true) + s.Get() + + s.Set("trap") + bo.Get() + + s.Set("false") + bo.Get() + } +} + +func BenchmarkFloatToString(b *testing.B) { + for i := 0; i < b.N; i++ { + f := NewFloat() + s := FloatToString(f) + s.Get() + + f.Set(0.3) + s.Get() + + s.Set("wrong") + f.Get() + + s.Set("5.00") + f.Get() + } +} + +func BenchmarkIntToString(b *testing.B) { + for i := 0; i < b.N; i++ { + i := NewInt() + s := IntToString(i) + s.Get() + + i.Set(3) + s.Get() + + s.Set("wrong") + i.Get() + + s.Set("5") + i.Get() + } +} + +func BenchmarkStringToBool(b *testing.B) { + for i := 0; i < b.N; i++ { + s := NewString() + b := StringToBool(s) + b.Get() + + s.Set("true") + b.Get() + + s.Set("trap") // bug in fmt.SScanf means "wrong" parses as "false" + b.Get() + + b.Set(false) + s.Get() + } +} + +func BenchmarkStringToFloat(b *testing.B) { + for i := 0; i < b.N; i++ { + s := NewString() + f := StringToFloat(s) + f.Get() + + s.Set("3") + f.Get() + + s.Set("wrong") + f.Get() + + f.Set(5) + s.Get() + } +} + +func BenchmarkStringToInt(b *testing.B) { + for i := 0; i < b.N; i++ { + s := NewString() + i := StringToInt(s) + i.Get() + + s.Set("3") + i.Get() + + s.Set("wrong") + i.Get() + + i.Set(5) + s.Get() + } +} + func TestBoolToString(t *testing.T) { b := NewBool() s := BoolToString(b)