• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

fyne-io / fyne / 13155932935

05 Feb 2025 11:03AM UTC coverage: 62.565% (-0.07%) from 62.633%
13155932935

push

github

web-flow
Merge pull request #5499 from fyne-io/dweymouth-patch-1

Fix a bug with previous GridWrap PR

2 of 2 new or added lines in 1 file covered. (100.0%)

87 existing lines in 4 files now uncovered.

24750 of 39559 relevant lines covered (62.56%)

843.3 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

82.71
/data/binding/trees.go
1
package binding
2

3
import (
4
        "bytes"
5

6
        "fyne.io/fyne/v2"
7
        "fyne.io/fyne/v2/storage"
8
)
9

10
// DataTreeRootID const is the value used as ID for the root of any tree binding.
11
const DataTreeRootID = ""
12

13
// DataTree is the base interface for all bindable data trees.
14
//
15
// Since: 2.4
16
type DataTree interface {
17
        DataItem
18
        GetItem(id string) (DataItem, error)
19
        ChildIDs(string) []string
20
}
21

22
// BoolTree supports binding a tree of bool values.
23
//
24
// Since: 2.4
25
type BoolTree interface {
26
        DataTree
27

28
        Append(parent, id string, value bool) error
29
        Get() (map[string][]string, map[string]bool, error)
30
        GetValue(id string) (bool, error)
31
        Prepend(parent, id string, value bool) error
32
        Remove(id string) error
33
        Set(ids map[string][]string, values map[string]bool) error
34
        SetValue(id string, value bool) error
35
}
36

37
// ExternalBoolTree supports binding a tree of bool values from an external variable.
38
//
39
// Since: 2.4
40
type ExternalBoolTree interface {
41
        BoolTree
42

43
        Reload() error
44
}
45

46
// NewBoolTree returns a bindable tree of bool values.
47
//
48
// Since: 2.4
49
func NewBoolTree() BoolTree {
×
50
        return newTreeComparable[bool]()
×
51
}
×
52

53
// BindBoolTree returns a bound tree of bool values, based on the contents of the passed values.
54
// The ids map specifies how each item relates to its parent (with id ""), with the values being in the v map.
55
// If your code changes the content of the maps this refers to you should call Reload() to inform the bindings.
56
//
57
// Since: 2.4
58
func BindBoolTree(ids *map[string][]string, v *map[string]bool) ExternalBoolTree {
×
59
        return bindTreeComparable(ids, v)
×
60
}
×
61

62
// BytesTree supports binding a tree of []byte values.
63
//
64
// Since: 2.4
65
type BytesTree interface {
66
        DataTree
67

68
        Append(parent, id string, value []byte) error
69
        Get() (map[string][]string, map[string][]byte, error)
70
        GetValue(id string) ([]byte, error)
71
        Prepend(parent, id string, value []byte) error
72
        Remove(id string) error
73
        Set(ids map[string][]string, values map[string][]byte) error
74
        SetValue(id string, value []byte) error
75
}
76

77
// ExternalBytesTree supports binding a tree of []byte values from an external variable.
78
//
79
// Since: 2.4
80
type ExternalBytesTree interface {
81
        BytesTree
82

83
        Reload() error
84
}
85

86
// NewBytesTree returns a bindable tree of []byte values.
87
//
88
// Since: 2.4
89
func NewBytesTree() BytesTree {
×
90
        return newTree(bytes.Equal)
×
91
}
×
92

93
// BindBytesTree returns a bound tree of []byte values, based on the contents of the passed values.
94
// The ids map specifies how each item relates to its parent (with id ""), with the values being in the v map.
95
// If your code changes the content of the maps this refers to you should call Reload() to inform the bindings.
96
//
97
// Since: 2.4
98
func BindBytesTree(ids *map[string][]string, v *map[string][]byte) ExternalBytesTree {
×
99
        return bindTree(ids, v, bytes.Equal)
×
100
}
×
101

102
// FloatTree supports binding a tree of float64 values.
103
//
104
// Since: 2.4
105
type FloatTree interface {
106
        DataTree
107

108
        Append(parent, id string, value float64) error
109
        Get() (map[string][]string, map[string]float64, error)
110
        GetValue(id string) (float64, error)
111
        Prepend(parent, id string, value float64) error
112
        Remove(id string) error
113
        Set(ids map[string][]string, values map[string]float64) error
114
        SetValue(id string, value float64) error
115
}
116

117
// ExternalFloatTree supports binding a tree of float64 values from an external variable.
118
//
119
// Since: 2.4
120
type ExternalFloatTree interface {
121
        FloatTree
122

123
        Reload() error
124
}
125

126
// NewFloatTree returns a bindable tree of float64 values.
127
//
128
// Since: 2.4
129
func NewFloatTree() FloatTree {
1✔
130
        return newTreeComparable[float64]()
1✔
131
}
1✔
132

133
// BindFloatTree returns a bound tree of float64 values, based on the contents of the passed values.
134
// The ids map specifies how each item relates to its parent (with id ""), with the values being in the v map.
135
// If your code changes the content of the maps this refers to you should call Reload() to inform the bindings.
136
//
137
// Since: 2.4
138
func BindFloatTree(ids *map[string][]string, v *map[string]float64) ExternalFloatTree {
2✔
139
        return bindTreeComparable(ids, v)
2✔
140
}
2✔
141

142
// IntTree supports binding a tree of int values.
143
//
144
// Since: 2.4
145
type IntTree interface {
146
        DataTree
147

148
        Append(parent, id string, value int) error
149
        Get() (map[string][]string, map[string]int, error)
150
        GetValue(id string) (int, error)
151
        Prepend(parent, id string, value int) error
152
        Remove(id string) error
153
        Set(ids map[string][]string, values map[string]int) error
154
        SetValue(id string, value int) error
155
}
156

157
// ExternalIntTree supports binding a tree of int values from an external variable.
158
//
159
// Since: 2.4
160
type ExternalIntTree interface {
161
        IntTree
162

163
        Reload() error
164
}
165

166
// NewIntTree returns a bindable tree of int values.
167
//
168
// Since: 2.4
169
func NewIntTree() IntTree {
×
170
        return newTreeComparable[int]()
×
171
}
×
172

173
// BindIntTree returns a bound tree of int values, based on the contents of the passed values.
174
// The ids map specifies how each item relates to its parent (with id ""), with the values being in the v map.
175
// If your code changes the content of the maps this refers to you should call Reload() to inform the bindings.
176
//
177
// Since: 2.4
178
func BindIntTree(ids *map[string][]string, v *map[string]int) ExternalIntTree {
×
179
        return bindTreeComparable(ids, v)
×
180
}
×
181

182
// RuneTree supports binding a tree of rune values.
183
//
184
// Since: 2.4
185
type RuneTree interface {
186
        DataTree
187

188
        Append(parent, id string, value rune) error
189
        Get() (map[string][]string, map[string]rune, error)
190
        GetValue(id string) (rune, error)
191
        Prepend(parent, id string, value rune) error
192
        Remove(id string) error
193
        Set(ids map[string][]string, values map[string]rune) error
194
        SetValue(id string, value rune) error
195
}
196

197
// ExternalRuneTree supports binding a tree of rune values from an external variable.
198
//
199
// Since: 2.4
200
type ExternalRuneTree interface {
201
        RuneTree
202

203
        Reload() error
204
}
205

206
// NewRuneTree returns a bindable tree of rune values.
207
//
208
// Since: 2.4
209
func NewRuneTree() RuneTree {
×
210
        return newTreeComparable[rune]()
×
211
}
×
212

213
// BindRuneTree returns a bound tree of rune values, based on the contents of the passed values.
214
// The ids map specifies how each item relates to its parent (with id ""), with the values being in the v map.
215
// If your code changes the content of the maps this refers to you should call Reload() to inform the bindings.
216
//
217
// Since: 2.4
218
func BindRuneTree(ids *map[string][]string, v *map[string]rune) ExternalRuneTree {
×
219
        return bindTreeComparable(ids, v)
×
220
}
×
221

222
// StringTree supports binding a tree of string values.
223
//
224
// Since: 2.4
225
type StringTree interface {
226
        DataTree
227

228
        Append(parent, id string, value string) error
229
        Get() (map[string][]string, map[string]string, error)
230
        GetValue(id string) (string, error)
231
        Prepend(parent, id string, value string) error
232
        Remove(id string) error
233
        Set(ids map[string][]string, values map[string]string) error
234
        SetValue(id string, value string) error
235
}
236

237
// ExternalStringTree supports binding a tree of string values from an external variable.
238
//
239
// Since: 2.4
240
type ExternalStringTree interface {
241
        StringTree
242

243
        Reload() error
244
}
245

246
// NewStringTree returns a bindable tree of string values.
247
//
248
// Since: 2.4
249
func NewStringTree() StringTree {
4✔
250
        return newTreeComparable[string]()
4✔
251
}
4✔
252

253
// BindStringTree returns a bound tree of string values, based on the contents of the passed values.
254
// The ids map specifies how each item relates to its parent (with id ""), with the values being in the v map.
255
// If your code changes the content of the maps this refers to you should call Reload() to inform the bindings.
256
//
257
// Since: 2.4
258
func BindStringTree(ids *map[string][]string, v *map[string]string) ExternalStringTree {
1✔
259
        return bindTreeComparable(ids, v)
1✔
260
}
1✔
261

262
// UntypedTree supports binding a tree of any values.
263
//
264
// Since: 2.5
265
type UntypedTree interface {
266
        DataTree
267

268
        Append(parent, id string, value any) error
269
        Get() (map[string][]string, map[string]any, error)
270
        GetValue(id string) (any, error)
271
        Prepend(parent, id string, value any) error
272
        Remove(id string) error
273
        Set(ids map[string][]string, values map[string]any) error
274
        SetValue(id string, value any) error
275
}
276

277
// ExternalUntypedTree supports binding a tree of any values from an external variable.
278
//
279
// Since: 2.5
280
type ExternalUntypedTree interface {
281
        UntypedTree
282

283
        Reload() error
284
}
285

286
// NewUntypedTree returns a bindable tree of any values.
287
//
288
// Since: 2.5
289
func NewUntypedTree() UntypedTree {
×
290
        return newTree(func(a1, a2 any) bool { return a1 == a2 })
×
291
}
292

293
// BindUntypedTree returns a bound tree of any values, based on the contents of the passed values.
294
// The ids map specifies how each item relates to its parent (with id ""), with the values being in the v map.
295
// If your code changes the content of the maps this refers to you should call Reload() to inform the bindings.
296
//
297
// Since: 2.4
298
func BindUntypedTree(ids *map[string][]string, v *map[string]any) ExternalUntypedTree {
×
299
        return bindTree(ids, v, func(a1, a2 any) bool { return a1 == a2 })
×
300
}
301

302
// URITree supports binding a tree of fyne.URI values.
303
//
304
// Since: 2.4
305
type URITree interface {
306
        DataTree
307

308
        Append(parent, id string, value fyne.URI) error
309
        Get() (map[string][]string, map[string]fyne.URI, error)
310
        GetValue(id string) (fyne.URI, error)
311
        Prepend(parent, id string, value fyne.URI) error
312
        Remove(id string) error
313
        Set(ids map[string][]string, values map[string]fyne.URI) error
314
        SetValue(id string, value fyne.URI) error
315
}
316

317
// ExternalURITree supports binding a tree of fyne.URI values from an external variable.
318
//
319
// Since: 2.4
320
type ExternalURITree interface {
321
        URITree
322

323
        Reload() error
324
}
325

326
// NewURITree returns a bindable tree of fyne.URI values.
327
//
328
// Since: 2.4
329
func NewURITree() URITree {
×
330
        return newTree(storage.EqualURI)
×
331
}
×
332

333
// BindURITree returns a bound tree of fyne.URI values, based on the contents of the passed values.
334
// The ids map specifies how each item relates to its parent (with id ""), with the values being in the v map.
335
// If your code changes the content of the maps this refers to you should call Reload() to inform the bindings.
336
//
337
// Since: 2.4
338
func BindURITree(ids *map[string][]string, v *map[string]fyne.URI) ExternalURITree {
×
339
        return bindTree(ids, v, storage.EqualURI)
×
340
}
×
341

342
type treeBase struct {
343
        base
344

345
        ids   map[string][]string
346
        items map[string]DataItem
347
}
348

349
// GetItem returns the DataItem at the specified id.
350
func (t *treeBase) GetItem(id string) (DataItem, error) {
6✔
351
        t.lock.RLock()
6✔
352
        defer t.lock.RUnlock()
6✔
353

6✔
354
        if item, ok := t.items[id]; ok {
11✔
355
                return item, nil
5✔
356
        }
5✔
357

358
        return nil, errOutOfBounds
1✔
359
}
360

361
// ChildIDs returns the ordered IDs of items in this data tree that are children of the specified ID.
362
func (t *treeBase) ChildIDs(id string) []string {
15✔
363
        t.lock.RLock()
15✔
364
        defer t.lock.RUnlock()
15✔
365

15✔
366
        if ids, ok := t.ids[id]; ok {
26✔
367
                return ids
11✔
368
        }
11✔
369

370
        return []string{}
4✔
371
}
372

373
func (t *treeBase) appendItem(i DataItem, id, parent string) {
23✔
374
        t.items[id] = i
23✔
375
        ids, ok := t.ids[parent]
23✔
376
        if !ok {
30✔
377
                ids = make([]string, 0)
7✔
378
        }
7✔
379

380
        for _, in := range ids {
45✔
381
                if in == id {
33✔
382
                        return
11✔
383
                }
11✔
384
        }
385
        t.ids[parent] = append(ids, id)
12✔
386
}
387

388
func (t *treeBase) deleteItem(id, parent string) {
8✔
389
        delete(t.items, id)
8✔
390

8✔
391
        ids, ok := t.ids[parent]
8✔
392
        if !ok {
8✔
393
                return
×
394
        }
×
395

396
        off := -1
8✔
397
        for i, id2 := range ids {
18✔
398
                if id2 == id {
12✔
399
                        off = i
2✔
400
                        break
2✔
401
                }
402
        }
403
        if off == -1 {
14✔
404
                return
6✔
405
        }
6✔
406
        t.ids[parent] = append(ids[:off], ids[off+1:]...)
2✔
407
}
408

409
func parentIDFor(id string, ids map[string][]string) string {
20✔
410
        for parent, list := range ids {
45✔
411
                for _, child := range list {
60✔
412
                        if child == id {
48✔
413
                                return parent
13✔
414
                        }
13✔
415
                }
416
        }
417

418
        return ""
7✔
419
}
420

421
func newTree[T any](comparator func(T, T) bool) *boundTree[T] {
5✔
422
        t := &boundTree[T]{val: &map[string]T{}, comparator: comparator}
5✔
423
        t.ids = make(map[string][]string)
5✔
424
        t.items = make(map[string]DataItem)
5✔
425
        return t
5✔
426
}
5✔
427

428
func newTreeComparable[T bool | float64 | int | rune | string]() *boundTree[T] {
5✔
429
        return newTree(func(t1, t2 T) bool { return t1 == t2 })
5✔
430
}
431

432
func bindTree[T any](ids *map[string][]string, v *map[string]T, comparator func(T, T) bool) *boundTree[T] {
3✔
433
        if v == nil {
3✔
UNCOV
434
                return newTree[T](comparator)
×
UNCOV
435
        }
×
436

437
        t := &boundTree[T]{val: v, updateExternal: true, comparator: comparator}
3✔
438
        t.ids = make(map[string][]string)
3✔
439
        t.items = make(map[string]DataItem)
3✔
440

3✔
441
        for parent, children := range *ids {
8✔
442
                for _, leaf := range children {
14✔
443
                        t.appendItem(bindTreeItem(v, leaf, t.updateExternal, t.comparator), leaf, parent)
9✔
444
                }
9✔
445
        }
446

447
        return t
3✔
448
}
449

450
func bindTreeComparable[T bool | float64 | int | rune | string](ids *map[string][]string, v *map[string]T) *boundTree[T] {
3✔
451
        return bindTree(ids, v, func(t1, t2 T) bool { return t1 == t2 })
17✔
452
}
453

454
type boundTree[T any] struct {
455
        treeBase
456

457
        comparator     func(T, T) bool
458
        val            *map[string]T
459
        updateExternal bool
460
}
461

462
func (t *boundTree[T]) Append(parent, id string, val T) error {
7✔
463
        t.lock.Lock()
7✔
464
        ids, ok := t.ids[parent]
7✔
465
        if !ok {
12✔
466
                ids = make([]string, 0)
5✔
467
        }
5✔
468

469
        t.ids[parent] = append(ids, id)
7✔
470
        v := *t.val
7✔
471
        v[id] = val
7✔
472

7✔
473
        trigger, err := t.doReload()
7✔
474
        t.lock.Unlock()
7✔
475

7✔
476
        if trigger {
14✔
477
                t.trigger()
7✔
478
        }
7✔
479

480
        return err
7✔
481
}
482

UNCOV
483
func (t *boundTree[T]) Get() (map[string][]string, map[string]T, error) {
×
UNCOV
484
        t.lock.RLock()
×
UNCOV
485
        defer t.lock.RUnlock()
×
UNCOV
486

×
UNCOV
487
        return t.ids, *t.val, nil
×
UNCOV
488
}
×
489

490
func (t *boundTree[T]) GetValue(id string) (T, error) {
14✔
491
        t.lock.RLock()
14✔
492
        defer t.lock.RUnlock()
14✔
493

14✔
494
        if item, ok := (*t.val)[id]; ok {
25✔
495
                return item, nil
11✔
496
        }
11✔
497

498
        return *new(T), errOutOfBounds
3✔
499
}
500

501
func (t *boundTree[T]) Prepend(parent, id string, val T) error {
1✔
502
        t.lock.Lock()
1✔
503
        ids, ok := t.ids[parent]
1✔
504
        if !ok {
1✔
UNCOV
505
                ids = make([]string, 0)
×
UNCOV
506
        }
×
507

508
        t.ids[parent] = append([]string{id}, ids...)
1✔
509
        v := *t.val
1✔
510
        v[id] = val
1✔
511

1✔
512
        trigger, err := t.doReload()
1✔
513
        t.lock.Unlock()
1✔
514

1✔
515
        if trigger {
2✔
516
                t.trigger()
1✔
517
        }
1✔
518

519
        return err
1✔
520
}
521

522
func (t *boundTree[T]) Remove(id string) error {
1✔
523
        t.lock.Lock()
1✔
524
        t.removeChildren(id)
1✔
525
        delete(t.ids, id)
1✔
526
        v := *t.val
1✔
527
        delete(v, id)
1✔
528

1✔
529
        trigger, err := t.doReload()
1✔
530
        t.lock.Unlock()
1✔
531

1✔
532
        if trigger {
2✔
533
                t.trigger()
1✔
534
        }
1✔
535

536
        return err
1✔
537
}
538

539
func (t *boundTree[T]) removeChildren(id string) {
2✔
540
        for _, cid := range t.ids[id] {
3✔
541
                t.removeChildren(cid)
1✔
542

1✔
543
                delete(t.ids, cid)
1✔
544
                v := *t.val
1✔
545
                delete(v, cid)
1✔
546
        }
1✔
547
}
548

549
func (t *boundTree[T]) Reload() error {
3✔
550
        t.lock.Lock()
3✔
551
        trigger, err := t.doReload()
3✔
552
        t.lock.Unlock()
3✔
553

3✔
554
        if trigger {
5✔
555
                t.trigger()
2✔
556
        }
2✔
557

558
        return err
3✔
559
}
560

561
func (t *boundTree[T]) Set(ids map[string][]string, v map[string]T) error {
5✔
562
        t.lock.Lock()
5✔
563
        t.ids = ids
5✔
564
        *t.val = v
5✔
565

5✔
566
        trigger, err := t.doReload()
5✔
567
        t.lock.Unlock()
5✔
568

5✔
569
        if trigger {
9✔
570
                t.trigger()
4✔
571
        }
4✔
572

573
        return err
5✔
574
}
575

576
func (t *boundTree[T]) doReload() (fire bool, retErr error) {
17✔
577
        updated := []string{}
17✔
578
        for id := range *t.val {
54✔
579
                found := false
37✔
580
                for child := range t.items {
96✔
581
                        if child == id { // update existing
84✔
582
                                updated = append(updated, id)
25✔
583
                                found = true
25✔
584
                                break
25✔
585
                        }
586
                }
587
                if found {
62✔
588
                        continue
25✔
589
                }
590

591
                // append new
592
                t.appendItem(bindTreeItem(t.val, id, t.updateExternal, t.comparator), id, parentIDFor(id, t.ids))
12✔
593
                updated = append(updated, id)
12✔
594
                fire = true
12✔
595
        }
596

597
        for id := range t.items {
62✔
598
                remove := true
45✔
599
                for _, done := range updated {
123✔
600
                        if done == id {
115✔
601
                                remove = false
37✔
602
                                break
37✔
603
                        }
604
                }
605

606
                if remove { // remove item no longer present
53✔
607
                        fire = true
8✔
608
                        t.deleteItem(id, parentIDFor(id, t.ids))
8✔
609
                }
8✔
610
        }
611

612
        for id, item := range t.items {
54✔
613
                var err error
37✔
614
                if t.updateExternal {
51✔
615
                        err = item.(*boundExternalTreeItem[T]).setIfChanged((*t.val)[id])
14✔
616
                } else {
37✔
617
                        err = item.(*boundTreeItem[T]).doSet((*t.val)[id])
23✔
618
                }
23✔
619
                if err != nil {
37✔
UNCOV
620
                        retErr = err
×
UNCOV
621
                }
×
622
        }
623
        return
17✔
624
}
625

626
func (t *boundTree[T]) SetValue(id string, v T) error {
2✔
627
        t.lock.Lock()
2✔
628
        (*t.val)[id] = v
2✔
629
        t.lock.Unlock()
2✔
630

2✔
631
        item, err := t.GetItem(id)
2✔
632
        if err != nil {
2✔
UNCOV
633
                return err
×
UNCOV
634
        }
×
635
        return item.(bindableItem[T]).Set(v)
2✔
636
}
637

638
func bindTreeItem[T any](v *map[string]T, id string, external bool, comparator func(T, T) bool) bindableItem[T] {
21✔
639
        if external {
32✔
640
                ret := &boundExternalTreeItem[T]{old: (*v)[id], comparator: comparator}
11✔
641
                ret.val = v
11✔
642
                ret.id = id
11✔
643
                return ret
11✔
644
        }
11✔
645

646
        return &boundTreeItem[T]{id: id, val: v}
10✔
647
}
648

649
type boundTreeItem[T any] struct {
650
        base
651

652
        val *map[string]T
653
        id  string
654
}
655

656
func (t *boundTreeItem[T]) Get() (T, error) {
3✔
657
        t.lock.Lock()
3✔
658
        defer t.lock.Unlock()
3✔
659

3✔
660
        v := *t.val
3✔
661
        if item, ok := v[t.id]; ok {
6✔
662
                return item, nil
3✔
663
        }
3✔
664

UNCOV
665
        return *new(T), errOutOfBounds
×
666
}
667

668
func (t *boundTreeItem[T]) Set(val T) error {
2✔
669
        return t.doSet(val)
2✔
670
}
2✔
671

672
func (t *boundTreeItem[T]) doSet(val T) error {
25✔
673
        t.lock.Lock()
25✔
674
        (*t.val)[t.id] = val
25✔
675
        t.lock.Unlock()
25✔
676

25✔
677
        t.trigger()
25✔
678
        return nil
25✔
679
}
25✔
680

681
type boundExternalTreeItem[T any] struct {
682
        boundTreeItem[T]
683

684
        comparator func(T, T) bool
685
        old        T
686
}
687

688
func (t *boundExternalTreeItem[T]) setIfChanged(val T) error {
14✔
689
        t.lock.Lock()
14✔
690
        if t.comparator(val, t.old) {
21✔
691
                t.lock.Unlock()
7✔
692
                return nil
7✔
693
        }
7✔
694
        (*t.val)[t.id] = val
7✔
695
        t.old = val
7✔
696
        t.lock.Unlock()
7✔
697

7✔
698
        t.trigger()
7✔
699
        return nil
7✔
700
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc