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

fyne-io / fyne / 18807176232

25 Oct 2025 06:34PM UTC coverage: 61.057% (-0.004%) from 61.061%
18807176232

Pull #5989

github

Jacalz
Fix TODO regarding comparable map key
Pull Request #5989: RFC: Proof of concept for upgrading Go to 1.24

155 of 188 new or added lines in 62 files covered. (82.45%)

27 existing lines in 6 files now uncovered.

25609 of 41943 relevant lines covered (61.06%)

692.99 hits per line

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

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

3
import (
4
        "bytes"
5
        "slices"
6

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

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

14
// Tree supports binding a tree of values with type T.
15
//
16
// Since: 2.7
17
type Tree[T any] interface {
18
        DataTree
19

20
        Append(parent, id string, value T) error
21
        Get() (map[string][]string, map[string]T, error)
22
        GetValue(id string) (T, error)
23
        Prepend(parent, id string, value T) error
24
        Remove(id string) error
25
        Set(ids map[string][]string, values map[string]T) error
26
        SetValue(id string, value T) error
27
}
28

29
// ExternalTree supports binding a tree of values, of type T, from an external variable.
30
//
31
// Since: 2.7
32
type ExternalTree[T any] interface {
33
        Tree[T]
34

35
        Reload() error
36
}
37

38
// NewTree returns a bindable tree of values with type T.
39
//
40
// Since: 2.7
41
func NewTree[T any](comparator func(T, T) bool) Tree[T] {
×
42
        return newTree[T](comparator)
×
43
}
×
44

45
// BindTree returns a bound tree of values with type T, based on the contents of the passed values.
46
// The ids map specifies how each item relates to its parent (with id ""), with the values being in the v map.
47
// If your code changes the content of the maps this refers to you should call Reload() to inform the bindings.
48
//
49
// Since: 2.7
50
func BindTree[T any](ids *map[string][]string, v *map[string]T, comparator func(T, T) bool) ExternalTree[T] {
×
51
        return bindTree(ids, v, comparator)
×
52
}
×
53

54
// DataTree is the base interface for all bindable data trees.
55
//
56
// Since: 2.4
57
type DataTree interface {
58
        DataItem
59
        GetItem(id string) (DataItem, error)
60
        ChildIDs(string) []string
61
}
62

63
// BoolTree supports binding a tree of bool values.
64
//
65
// Since: 2.4
66
type BoolTree = Tree[bool]
67

68
// ExternalBoolTree supports binding a tree of bool values from an external variable.
69
//
70
// Since: 2.4
71
type ExternalBoolTree = ExternalTree[bool]
72

73
// NewBoolTree returns a bindable tree of bool values.
74
//
75
// Since: 2.4
76
func NewBoolTree() Tree[bool] {
×
77
        return newTreeComparable[bool]()
×
78
}
×
79

80
// BindBoolTree returns a bound tree of bool values, based on the contents of the passed values.
81
// The ids map specifies how each item relates to its parent (with id ""), with the values being in the v map.
82
// If your code changes the content of the maps this refers to you should call Reload() to inform the bindings.
83
//
84
// Since: 2.4
85
func BindBoolTree(ids *map[string][]string, v *map[string]bool) ExternalTree[bool] {
×
86
        return bindTreeComparable(ids, v)
×
87
}
×
88

89
// BytesTree supports binding a tree of []byte values.
90
//
91
// Since: 2.4
92
type BytesTree = Tree[[]byte]
93

94
// ExternalBytesTree supports binding a tree of []byte values from an external variable.
95
//
96
// Since: 2.4
97
type ExternalBytesTree = ExternalTree[[]byte]
98

99
// NewBytesTree returns a bindable tree of []byte values.
100
//
101
// Since: 2.4
102
func NewBytesTree() Tree[[]byte] {
×
103
        return newTree(bytes.Equal)
×
104
}
×
105

106
// BindBytesTree returns a bound tree of []byte values, based on the contents of the passed values.
107
// The ids map specifies how each item relates to its parent (with id ""), with the values being in the v map.
108
// If your code changes the content of the maps this refers to you should call Reload() to inform the bindings.
109
//
110
// Since: 2.4
111
func BindBytesTree(ids *map[string][]string, v *map[string][]byte) ExternalTree[[]byte] {
×
112
        return bindTree(ids, v, bytes.Equal)
×
113
}
×
114

115
// FloatTree supports binding a tree of float64 values.
116
//
117
// Since: 2.4
118
type FloatTree = Tree[float64]
119

120
// ExternalFloatTree supports binding a tree of float64 values from an external variable.
121
//
122
// Since: 2.4
123
type ExternalFloatTree = ExternalTree[float64]
124

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

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

141
// IntTree supports binding a tree of int values.
142
//
143
// Since: 2.4
144
type IntTree = Tree[int]
145

146
// ExternalIntTree supports binding a tree of int values from an external variable.
147
//
148
// Since: 2.4
149
type ExternalIntTree = ExternalTree[int]
150

151
// NewIntTree returns a bindable tree of int values.
152
//
153
// Since: 2.4
154
func NewIntTree() Tree[int] {
×
155
        return newTreeComparable[int]()
×
156
}
×
157

158
// BindIntTree returns a bound tree of int values, based on the contents of the passed values.
159
// The ids map specifies how each item relates to its parent (with id ""), with the values being in the v map.
160
// If your code changes the content of the maps this refers to you should call Reload() to inform the bindings.
161
//
162
// Since: 2.4
163
func BindIntTree(ids *map[string][]string, v *map[string]int) ExternalTree[int] {
×
164
        return bindTreeComparable(ids, v)
×
165
}
×
166

167
// RuneTree supports binding a tree of rune values.
168
//
169
// Since: 2.4
170
type RuneTree = Tree[rune]
171

172
// ExternalRuneTree supports binding a tree of rune values from an external variable.
173
//
174
// Since: 2.4
175
type ExternalRuneTree = ExternalTree[rune]
176

177
// NewRuneTree returns a bindable tree of rune values.
178
//
179
// Since: 2.4
180
func NewRuneTree() Tree[rune] {
×
181
        return newTreeComparable[rune]()
×
182
}
×
183

184
// BindRuneTree returns a bound tree of rune values, based on the contents of the passed values.
185
// The ids map specifies how each item relates to its parent (with id ""), with the values being in the v map.
186
// If your code changes the content of the maps this refers to you should call Reload() to inform the bindings.
187
//
188
// Since: 2.4
189
func BindRuneTree(ids *map[string][]string, v *map[string]rune) ExternalTree[rune] {
×
190
        return bindTreeComparable(ids, v)
×
191
}
×
192

193
// StringTree supports binding a tree of string values.
194
//
195
// Since: 2.4
196
type StringTree = Tree[string]
197

198
// ExternalStringTree supports binding a tree of string values from an external variable.
199
//
200
// Since: 2.4
201
type ExternalStringTree = ExternalTree[string]
202

203
// NewStringTree returns a bindable tree of string values.
204
//
205
// Since: 2.4
206
func NewStringTree() Tree[string] {
4✔
207
        return newTreeComparable[string]()
4✔
208
}
4✔
209

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

219
// UntypedTree supports binding a tree of any values.
220
//
221
// Since: 2.5
222
type UntypedTree = Tree[any]
223

224
// ExternalUntypedTree supports binding a tree of any values from an external variable.
225
//
226
// Since: 2.5
227
type ExternalUntypedTree = ExternalTree[any]
228

229
// NewUntypedTree returns a bindable tree of any values.
230
//
231
// Since: 2.5
232
func NewUntypedTree() Tree[any] {
×
233
        return newTree(func(a1, a2 any) bool { return a1 == a2 })
×
234
}
235

236
// BindUntypedTree returns a bound tree of any values, based on the contents of the passed values.
237
// The ids map specifies how each item relates to its parent (with id ""), with the values being in the v map.
238
// If your code changes the content of the maps this refers to you should call Reload() to inform the bindings.
239
//
240
// Since: 2.4
241
func BindUntypedTree(ids *map[string][]string, v *map[string]any) ExternalTree[any] {
×
242
        return bindTree(ids, v, func(a1, a2 any) bool { return a1 == a2 })
×
243
}
244

245
// URITree supports binding a tree of fyne.URI values.
246
//
247
// Since: 2.4
248
type URITree = Tree[fyne.URI]
249

250
// ExternalURITree supports binding a tree of fyne.URI values from an external variable.
251
//
252
// Since: 2.4
253
type ExternalURITree = ExternalTree[fyne.URI]
254

255
// NewURITree returns a bindable tree of fyne.URI values.
256
//
257
// Since: 2.4
258
func NewURITree() Tree[fyne.URI] {
×
259
        return newTree(storage.EqualURI)
×
260
}
×
261

262
// BindURITree returns a bound tree of fyne.URI values, based on the contents of the passed values.
263
// The ids map specifies how each item relates to its parent (with id ""), with the values being in the v map.
264
// If your code changes the content of the maps this refers to you should call Reload() to inform the bindings.
265
//
266
// Since: 2.4
267
func BindURITree(ids *map[string][]string, v *map[string]fyne.URI) ExternalTree[fyne.URI] {
×
268
        return bindTree(ids, v, storage.EqualURI)
×
269
}
×
270

271
type treeBase struct {
272
        base
273

274
        ids   map[string][]string
275
        items map[string]DataItem
276
}
277

278
// GetItem returns the DataItem at the specified id.
279
func (t *treeBase) GetItem(id string) (DataItem, error) {
6✔
280
        t.lock.RLock()
6✔
281
        defer t.lock.RUnlock()
6✔
282

6✔
283
        if item, ok := t.items[id]; ok {
11✔
284
                return item, nil
5✔
285
        }
5✔
286

287
        return nil, errOutOfBounds
1✔
288
}
289

290
// ChildIDs returns the ordered IDs of items in this data tree that are children of the specified ID.
291
func (t *treeBase) ChildIDs(id string) []string {
15✔
292
        t.lock.RLock()
15✔
293
        defer t.lock.RUnlock()
15✔
294

15✔
295
        if ids, ok := t.ids[id]; ok {
26✔
296
                return ids
11✔
297
        }
11✔
298

299
        return []string{}
4✔
300
}
301

302
func (t *treeBase) appendItem(i DataItem, id, parent string) {
23✔
303
        t.items[id] = i
23✔
304

23✔
305
        ids := t.ids[parent]
23✔
306
        if slices.Contains(ids, id) {
34✔
307
                return
11✔
308
        }
11✔
309
        t.ids[parent] = append(ids, id)
12✔
310
}
311

312
func (t *treeBase) deleteItem(id, parent string) {
8✔
313
        delete(t.items, id)
8✔
314

8✔
315
        ids, ok := t.ids[parent]
8✔
316
        if !ok {
8✔
317
                return
×
318
        }
×
319

320
        off := -1
8✔
321
        for i, id2 := range ids {
18✔
322
                if id2 == id {
12✔
323
                        off = i
2✔
324
                        break
2✔
325
                }
326
        }
327
        if off == -1 {
14✔
328
                return
6✔
329
        }
6✔
330
        t.ids[parent] = append(ids[:off], ids[off+1:]...)
2✔
331
}
332

333
func parentIDFor(id string, ids map[string][]string) string {
20✔
334
        for parent, list := range ids {
45✔
335
                if slices.Contains(list, id) {
38✔
336
                        return parent
13✔
337
                }
13✔
338
        }
339

340
        return ""
7✔
341
}
342

343
func newTree[T any](comparator func(T, T) bool) *boundTree[T] {
5✔
344
        t := &boundTree[T]{val: &map[string]T{}, comparator: comparator}
5✔
345
        t.ids = make(map[string][]string)
5✔
346
        t.items = make(map[string]DataItem)
5✔
347
        return t
5✔
348
}
5✔
349

350
func newTreeComparable[T comparable]() *boundTree[T] {
5✔
351
        return newTree(func(t1, t2 T) bool { return t1 == t2 })
5✔
352
}
353

354
func bindTree[T any](ids *map[string][]string, v *map[string]T, comparator func(T, T) bool) *boundTree[T] {
3✔
355
        if v == nil {
3✔
356
                return newTree(comparator)
×
357
        }
×
358

359
        t := &boundTree[T]{val: v, updateExternal: true, comparator: comparator}
3✔
360
        t.ids = make(map[string][]string)
3✔
361
        t.items = make(map[string]DataItem)
3✔
362

3✔
363
        for parent, children := range *ids {
8✔
364
                for _, leaf := range children {
14✔
365
                        t.appendItem(bindTreeItem(v, leaf, t.updateExternal, t.comparator), leaf, parent)
9✔
366
                }
9✔
367
        }
368

369
        return t
3✔
370
}
371

372
func bindTreeComparable[T comparable](ids *map[string][]string, v *map[string]T) *boundTree[T] {
3✔
373
        return bindTree(ids, v, func(t1, t2 T) bool { return t1 == t2 })
17✔
374
}
375

376
type boundTree[T any] struct {
377
        treeBase
378

379
        comparator     func(T, T) bool
380
        val            *map[string]T
381
        updateExternal bool
382
}
383

384
func (t *boundTree[T]) Append(parent, id string, val T) error {
7✔
385
        t.lock.Lock()
7✔
386

7✔
387
        t.ids[parent] = append(t.ids[parent], id)
7✔
388
        v := *t.val
7✔
389
        v[id] = val
7✔
390

7✔
391
        trigger, err := t.doReload()
7✔
392
        t.lock.Unlock()
7✔
393

7✔
394
        if trigger {
14✔
395
                t.trigger()
7✔
396
        }
7✔
397

398
        return err
7✔
399
}
400

401
func (t *boundTree[T]) Get() (map[string][]string, map[string]T, error) {
×
402
        t.lock.RLock()
×
403
        defer t.lock.RUnlock()
×
404

×
405
        return t.ids, *t.val, nil
×
406
}
×
407

408
func (t *boundTree[T]) GetValue(id string) (T, error) {
14✔
409
        t.lock.RLock()
14✔
410
        defer t.lock.RUnlock()
14✔
411

14✔
412
        if item, ok := (*t.val)[id]; ok {
25✔
413
                return item, nil
11✔
414
        }
11✔
415

416
        return *new(T), errOutOfBounds
3✔
417
}
418

419
func (t *boundTree[T]) Prepend(parent, id string, val T) error {
1✔
420
        t.lock.Lock()
1✔
421

1✔
422
        t.ids[parent] = append([]string{id}, t.ids[parent]...)
1✔
423
        v := *t.val
1✔
424
        v[id] = val
1✔
425

1✔
426
        trigger, err := t.doReload()
1✔
427
        t.lock.Unlock()
1✔
428

1✔
429
        if trigger {
2✔
430
                t.trigger()
1✔
431
        }
1✔
432

433
        return err
1✔
434
}
435

436
func (t *boundTree[T]) Remove(id string) error {
1✔
437
        t.lock.Lock()
1✔
438
        t.removeChildren(id)
1✔
439
        delete(t.ids, id)
1✔
440
        v := *t.val
1✔
441
        delete(v, id)
1✔
442

1✔
443
        trigger, err := t.doReload()
1✔
444
        t.lock.Unlock()
1✔
445

1✔
446
        if trigger {
2✔
447
                t.trigger()
1✔
448
        }
1✔
449

450
        return err
1✔
451
}
452

453
func (t *boundTree[T]) removeChildren(id string) {
2✔
454
        for _, cid := range t.ids[id] {
3✔
455
                t.removeChildren(cid)
1✔
456

1✔
457
                delete(t.ids, cid)
1✔
458
                v := *t.val
1✔
459
                delete(v, cid)
1✔
460
        }
1✔
461
}
462

463
func (t *boundTree[T]) Reload() error {
3✔
464
        t.lock.Lock()
3✔
465
        trigger, err := t.doReload()
3✔
466
        t.lock.Unlock()
3✔
467

3✔
468
        if trigger {
5✔
469
                t.trigger()
2✔
470
        }
2✔
471

472
        return err
3✔
473
}
474

475
func (t *boundTree[T]) Set(ids map[string][]string, v map[string]T) error {
5✔
476
        t.lock.Lock()
5✔
477
        t.ids = ids
5✔
478
        *t.val = v
5✔
479

5✔
480
        trigger, err := t.doReload()
5✔
481
        t.lock.Unlock()
5✔
482

5✔
483
        if trigger {
9✔
484
                t.trigger()
4✔
485
        }
4✔
486

487
        return err
5✔
488
}
489

490
func (t *boundTree[T]) doReload() (fire bool, retErr error) {
17✔
491
        updated := []string{}
17✔
492
        for id := range *t.val {
54✔
493
                found := false
37✔
494
                for child := range t.items {
92✔
495
                        if child == id { // update existing
80✔
496
                                updated = append(updated, id)
25✔
497
                                found = true
25✔
498
                                break
25✔
499
                        }
500
                }
501
                if found {
62✔
502
                        continue
25✔
503
                }
504

505
                // append new
506
                t.appendItem(bindTreeItem(t.val, id, t.updateExternal, t.comparator), id, parentIDFor(id, t.ids))
12✔
507
                updated = append(updated, id)
12✔
508
                fire = true
12✔
509
        }
510

511
        for id := range t.items {
62✔
512
                remove := true
45✔
513
                if slices.Contains(updated, id) {
82✔
514
                        remove = false
37✔
515
                }
37✔
516

517
                if remove { // remove item no longer present
53✔
518
                        fire = true
8✔
519
                        t.deleteItem(id, parentIDFor(id, t.ids))
8✔
520
                }
8✔
521
        }
522

523
        for id, item := range t.items {
54✔
524
                var err error
37✔
525
                if t.updateExternal {
51✔
526
                        err = item.(*boundExternalTreeItem[T]).setIfChanged((*t.val)[id])
14✔
527
                } else {
37✔
528
                        err = item.(*boundTreeItem[T]).doSet((*t.val)[id])
23✔
529
                }
23✔
530
                if err != nil {
37✔
UNCOV
531
                        retErr = err
×
UNCOV
532
                }
×
533
        }
534
        return fire, retErr
17✔
535
}
536

537
func (t *boundTree[T]) SetValue(id string, v T) error {
2✔
538
        t.lock.Lock()
2✔
539
        (*t.val)[id] = v
2✔
540
        t.lock.Unlock()
2✔
541

2✔
542
        item, err := t.GetItem(id)
2✔
543
        if err != nil {
2✔
UNCOV
544
                return err
×
UNCOV
545
        }
×
546
        return item.(Item[T]).Set(v)
2✔
547
}
548

549
func bindTreeItem[T any](v *map[string]T, id string, external bool, comparator func(T, T) bool) Item[T] {
21✔
550
        if external {
32✔
551
                ret := &boundExternalTreeItem[T]{old: (*v)[id], comparator: comparator}
11✔
552
                ret.val = v
11✔
553
                ret.id = id
11✔
554
                return ret
11✔
555
        }
11✔
556

557
        return &boundTreeItem[T]{id: id, val: v}
10✔
558
}
559

560
type boundTreeItem[T any] struct {
561
        base
562

563
        val *map[string]T
564
        id  string
565
}
566

567
func (t *boundTreeItem[T]) Get() (T, error) {
3✔
568
        t.lock.Lock()
3✔
569
        defer t.lock.Unlock()
3✔
570

3✔
571
        v := *t.val
3✔
572
        if item, ok := v[t.id]; ok {
6✔
573
                return item, nil
3✔
574
        }
3✔
575

UNCOV
576
        return *new(T), errOutOfBounds
×
577
}
578

579
func (t *boundTreeItem[T]) Set(val T) error {
2✔
580
        return t.doSet(val)
2✔
581
}
2✔
582

583
func (t *boundTreeItem[T]) doSet(val T) error {
25✔
584
        t.lock.Lock()
25✔
585
        (*t.val)[t.id] = val
25✔
586
        t.lock.Unlock()
25✔
587

25✔
588
        t.trigger()
25✔
589
        return nil
25✔
590
}
25✔
591

592
type boundExternalTreeItem[T any] struct {
593
        boundTreeItem[T]
594

595
        comparator func(T, T) bool
596
        old        T
597
}
598

599
func (t *boundExternalTreeItem[T]) setIfChanged(val T) error {
14✔
600
        t.lock.Lock()
14✔
601
        if t.comparator(val, t.old) {
21✔
602
                t.lock.Unlock()
7✔
603
                return nil
7✔
604
        }
7✔
605
        (*t.val)[t.id] = val
7✔
606
        t.old = val
7✔
607
        t.lock.Unlock()
7✔
608

7✔
609
        t.trigger()
7✔
610
        return nil
7✔
611
}
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

© 2025 Coveralls, Inc