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

fyne-io / fyne / 13039703786

29 Jan 2025 08:16PM UTC coverage: 62.656% (+1.8%) from 60.858%
13039703786

Pull #5480

github

Jacalz
Cleanup the generator script to not generate lists and trees
Pull Request #5480: Clean up tree bindings with generics

188 of 216 new or added lines in 2 files covered. (87.04%)

1 existing line in 1 file now uncovered.

24922 of 39776 relevant lines covered (62.66%)

845.16 hits per line

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

88.89
/data/binding/generic.go
1
package binding
2

3
import (
4
        "sync/atomic"
5

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

9
type bindableItem[T any] interface {
10
        DataItem
11
        Get() (T, error)
12
        Set(T) error
13
}
14

15
func newBaseItem[T any](comparator func(T, T) bool) *baseItem[T] {
24✔
16
        return &baseItem[T]{val: new(T), comparator: comparator}
24✔
17
}
24✔
18

19
func newBaseItemComparable[T bool | float64 | int | rune | string]() *baseItem[T] {
21✔
20
        return newBaseItem[T](func(a, b T) bool { return a == b })
84✔
21
}
22

23
type baseItem[T any] struct {
24
        base
25

26
        comparator func(T, T) bool
27
        val        *T
28
}
29

30
func (b *baseItem[T]) Get() (T, error) {
359✔
31
        b.lock.RLock()
359✔
32
        defer b.lock.RUnlock()
359✔
33

359✔
34
        if b.val == nil {
359✔
35
                return *new(T), nil
×
36
        }
×
37
        return *b.val, nil
359✔
38
}
39

40
func (b *baseItem[T]) Set(val T) error {
73✔
41
        b.lock.Lock()
73✔
42
        equal := b.comparator(*b.val, val)
73✔
43
        *b.val = val
73✔
44
        b.lock.Unlock()
73✔
45

73✔
46
        if !equal {
127✔
47
                b.trigger()
54✔
48
        }
54✔
49

50
        return nil
73✔
51
}
52

53
func baseBindExternal[T any](val *T, comparator func(T, T) bool) *baseExternalItem[T] {
4✔
54
        if val == nil {
4✔
55
                val = new(T) // never allow a nil value pointer
×
56
        }
×
57
        b := &baseExternalItem[T]{}
4✔
58
        b.comparator = comparator
4✔
59
        b.val = val
4✔
60
        b.old = *val
4✔
61
        return b
4✔
62
}
63

64
func baseBindExternalComparable[T bool | float64 | int | rune | string](val *T) *baseExternalItem[T] {
29✔
65
        if val == nil {
29✔
66
                val = new(T) // never allow a nil value pointer
×
67
        }
×
68
        b := &baseExternalItem[T]{}
29✔
69
        b.comparator = func(a, b T) bool { return a == b }
381✔
70
        b.val = val
29✔
71
        b.old = *val
29✔
72
        return b
29✔
73
}
74

75
type baseExternalItem[T any] struct {
76
        baseItem[T]
77

78
        old T
79
}
80

81
func (b *baseExternalItem[T]) Set(val T) error {
364✔
82
        b.lock.Lock()
364✔
83
        if b.comparator(b.old, val) {
580✔
84
                b.lock.Unlock()
216✔
85
                return nil
216✔
86
        }
216✔
87
        *b.val = val
148✔
88
        b.old = val
148✔
89
        b.lock.Unlock()
148✔
90

148✔
91
        b.trigger()
148✔
92
        return nil
148✔
93
}
94

95
func (b *baseExternalItem[T]) Reload() error {
6✔
96
        return b.Set(*b.val)
6✔
97
}
6✔
98

99
type prefBoundBase[T bool | float64 | int | string] struct {
100
        base
101
        key   string
102
        get   func(string) T
103
        set   func(string, T)
104
        cache atomic.Pointer[T]
105
}
106

107
func (b *prefBoundBase[T]) Get() (T, error) {
12✔
108
        cache := b.get(b.key)
12✔
109
        b.cache.Store(&cache)
12✔
110
        return cache, nil
12✔
111
}
12✔
112

113
func (b *prefBoundBase[T]) Set(v T) error {
6✔
114
        b.set(b.key, v)
6✔
115

6✔
116
        b.lock.RLock()
6✔
117
        defer b.lock.RUnlock()
6✔
118
        b.trigger()
6✔
119
        return nil
6✔
120
}
6✔
121

122
func (b *prefBoundBase[T]) setKey(key string) {
6✔
123
        b.key = key
6✔
124
}
6✔
125

126
func (b *prefBoundBase[T]) checkForChange() {
17✔
127
        val := b.cache.Load()
17✔
128
        if val != nil && b.get(b.key) == *val {
21✔
129
                return
4✔
130
        }
4✔
131
        b.trigger()
13✔
132
}
133

134
type genericItem[T any] interface {
135
        DataItem
136
        Get() (T, error)
137
        Set(T) error
138
}
139

140
func lookupExistingBinding[T any](key string, p fyne.Preferences) (genericItem[T], bool) {
108✔
141
        binds := prefBinds.getBindings(p)
108✔
142
        if binds == nil {
110✔
143
                return nil, false
2✔
144
        }
2✔
145

146
        if listen, ok := binds.Load(key); listen != nil && ok {
208✔
147
                if l, ok := listen.(genericItem[T]); ok {
204✔
148
                        return l, ok
102✔
149
                }
102✔
150
                fyne.LogError(keyTypeMismatchError+key, nil)
×
151
        }
152

153
        return nil, false
4✔
154
}
155

156
func newList[T any](comparator func(T, T) bool) *boundList[T] {
5✔
157
        return &boundList[T]{val: new([]T), comparator: comparator}
5✔
158
}
5✔
159

160
func newListComparable[T bool | float64 | int | rune | string]() *boundList[T] {
5✔
161
        return newList(func(t1, t2 T) bool { return t1 == t2 })
8✔
162
}
163

164
func newExternalList[T any](v *[]T, comparator func(T, T) bool) *boundList[T] {
3✔
165
        return &boundList[T]{val: v, comparator: comparator, updateExternal: true}
3✔
166
}
3✔
167

168
func bindList[T any](v *[]T, comparator func(T, T) bool) *boundList[T] {
3✔
169
        if v == nil {
3✔
170
                return newList(comparator)
×
171
        }
×
172

173
        l := newExternalList(v, comparator)
3✔
174
        for i := range *v {
12✔
175
                l.appendItem(bindListItem(v, i, l.updateExternal, comparator))
9✔
176
        }
9✔
177

178
        return l
3✔
179
}
180

181
func bindListComparable[T bool | float64 | int | rune | string](v *[]T) *boundList[T] {
3✔
182
        return bindList(v, func(t1, t2 T) bool { return t1 == t2 })
17✔
183
}
184

185
type boundList[T any] struct {
186
        listBase
187

188
        comparator     func(T, T) bool
189
        updateExternal bool
190
        val            *[]T
191
}
192

193
func (l *boundList[T]) Append(val T) error {
7✔
194
        l.lock.Lock()
7✔
195
        *l.val = append(*l.val, val)
7✔
196

7✔
197
        trigger, err := l.doReload()
7✔
198
        l.lock.Unlock()
7✔
199

7✔
200
        if trigger {
14✔
201
                l.trigger()
7✔
202
        }
7✔
203

204
        return err
7✔
205
}
206

207
func (l *boundList[T]) Get() ([]T, error) {
×
208
        l.lock.RLock()
×
209
        defer l.lock.RUnlock()
×
210

×
211
        return *l.val, nil
×
212
}
×
213

214
func (l *boundList[T]) GetValue(i int) (T, error) {
14✔
215
        l.lock.RLock()
14✔
216
        defer l.lock.RUnlock()
14✔
217

14✔
218
        if i < 0 || i >= l.Length() {
17✔
219
                return *new(T), errOutOfBounds
3✔
220
        }
3✔
221

222
        return (*l.val)[i], nil
11✔
223
}
224

225
func (l *boundList[T]) Prepend(val T) error {
1✔
226
        l.lock.Lock()
1✔
227
        *l.val = append([]T{val}, *l.val...)
1✔
228

1✔
229
        trigger, err := l.doReload()
1✔
230
        l.lock.Unlock()
1✔
231

1✔
232
        if trigger {
2✔
233
                l.trigger()
1✔
234
        }
1✔
235

236
        return err
1✔
237
}
238

239
func (l *boundList[T]) Reload() error {
3✔
240
        l.lock.Lock()
3✔
241
        trigger, err := l.doReload()
3✔
242
        l.lock.Unlock()
3✔
243

3✔
244
        if trigger {
5✔
245
                l.trigger()
2✔
246
        }
2✔
247

248
        return err
3✔
249
}
250

251
func (l *boundList[T]) Remove(val T) error {
2✔
252
        l.lock.Lock()
2✔
253

2✔
254
        v := *l.val
2✔
255
        if len(v) == 0 {
2✔
256
                l.lock.Unlock()
×
257
                return nil
×
258
        }
×
259
        if l.comparator(v[0], val) {
3✔
260
                *l.val = v[1:]
1✔
261
        } else if l.comparator(v[len(v)-1], val) {
3✔
262
                *l.val = v[:len(v)-1]
1✔
263
        } else {
1✔
264
                id := -1
×
265
                for i, v := range v {
×
266
                        if l.comparator(v, val) {
×
267
                                id = i
×
268
                                break
×
269
                        }
270
                }
271

272
                if id == -1 {
×
273
                        l.lock.Unlock()
×
274
                        return nil
×
275
                }
×
276
                *l.val = append(v[:id], v[id+1:]...)
×
277
        }
278

279
        trigger, err := l.doReload()
2✔
280
        l.lock.Unlock()
2✔
281

2✔
282
        if trigger {
4✔
283
                l.trigger()
2✔
284
        }
2✔
285

286
        return err
2✔
287
}
288

289
func (l *boundList[T]) Set(v []T) error {
5✔
290
        l.lock.Lock()
5✔
291
        *l.val = v
5✔
292
        trigger, err := l.doReload()
5✔
293
        l.lock.Unlock()
5✔
294

5✔
295
        if trigger {
9✔
296
                l.trigger()
4✔
297
        }
4✔
298

299
        return err
5✔
300
}
301

302
func (l *boundList[T]) doReload() (trigger bool, retErr error) {
18✔
303
        oldLen := len(l.items)
18✔
304
        newLen := len(*l.val)
18✔
305
        if oldLen > newLen {
23✔
306
                for i := oldLen - 1; i >= newLen; i-- {
13✔
307
                        l.deleteItem(i)
8✔
308
                }
8✔
309
                trigger = true
5✔
310
        } else if oldLen < newLen {
24✔
311
                for i := oldLen; i < newLen; i++ {
23✔
312
                        l.appendItem(bindListItem(l.val, i, l.updateExternal, l.comparator))
12✔
313
                }
12✔
314
                trigger = true
11✔
315
        }
316

317
        for i, item := range l.items {
57✔
318
                if i > oldLen || i > newLen {
40✔
319
                        break
1✔
320
                }
321

322
                var err error
38✔
323
                if l.updateExternal {
52✔
324
                        err = item.(*boundExternalListItem[T]).setIfChanged((*l.val)[i])
14✔
325
                } else {
38✔
326
                        err = item.(*boundListItem[T]).doSet((*l.val)[i])
24✔
327
                }
24✔
328
                if err != nil {
38✔
329
                        retErr = err
×
330
                }
×
331
        }
332
        return
18✔
333
}
334

335
func (l *boundList[T]) SetValue(i int, v T) error {
2✔
336
        l.lock.RLock()
2✔
337
        len := l.Length()
2✔
338
        l.lock.RUnlock()
2✔
339

2✔
340
        if i < 0 || i >= len {
2✔
341
                return errOutOfBounds
×
342
        }
×
343

344
        l.lock.Lock()
2✔
345
        (*l.val)[i] = v
2✔
346
        l.lock.Unlock()
2✔
347

2✔
348
        item, err := l.GetItem(i)
2✔
349
        if err != nil {
2✔
350
                return err
×
351
        }
×
352
        return item.(genericItem[T]).Set(v)
2✔
353
}
354

355
func bindListItem[T any](v *[]T, i int, external bool, comparator func(T, T) bool) genericItem[T] {
21✔
356
        if external {
32✔
357
                ret := &boundExternalListItem[T]{old: (*v)[i]}
11✔
358
                ret.val = v
11✔
359
                ret.index = i
11✔
360
                ret.comparator = comparator
11✔
361
                return ret
11✔
362
        }
11✔
363

364
        return &boundListItem[T]{val: v, index: i, comparator: comparator}
10✔
365
}
366

367
func bindListItemComparable[T bool | float64 | int | rune | string](v *[]T, i int, external bool) genericItem[T] {
×
368
        return bindListItem(v, i, external, func(t1, t2 T) bool { return t1 == t2 })
×
369
}
370

371
type boundListItem[T any] struct {
372
        base
373

374
        comparator func(T, T) bool
375
        val        *[]T
376
        index      int
377
}
378

379
func (b *boundListItem[T]) Get() (T, error) {
3✔
380
        b.lock.Lock()
3✔
381
        defer b.lock.Unlock()
3✔
382

3✔
383
        if b.index < 0 || b.index >= len(*b.val) {
3✔
384
                return *new(T), errOutOfBounds
×
385
        }
×
386

387
        return (*b.val)[b.index], nil
3✔
388
}
389

390
func (b *boundListItem[T]) Set(val T) error {
2✔
391
        return b.doSet(val)
2✔
392
}
2✔
393

394
func (b *boundListItem[T]) doSet(val T) error {
26✔
395
        b.lock.Lock()
26✔
396
        (*b.val)[b.index] = val
26✔
397
        b.lock.Unlock()
26✔
398

26✔
399
        b.trigger()
26✔
400
        return nil
26✔
401
}
26✔
402

403
type boundExternalListItem[T any] struct {
404
        boundListItem[T]
405

406
        old T
407
}
408

409
func (b *boundExternalListItem[T]) setIfChanged(val T) error {
14✔
410
        b.lock.Lock()
14✔
411
        if b.comparator(val, b.old) {
21✔
412
                b.lock.Unlock()
7✔
413
                return nil
7✔
414
        }
7✔
415
        (*b.val)[b.index] = val
7✔
416
        b.old = val
7✔
417

7✔
418
        b.lock.Unlock()
7✔
419
        b.trigger()
7✔
420
        return nil
7✔
421
}
422

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

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

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

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

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

449
        return t
3✔
450
}
451

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

456
type boundTree[T any] struct {
457
        treeBase
458

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

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

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

7✔
475
        trigger, err := t.doReload()
7✔
476
        t.lock.Unlock()
7✔
477

7✔
478
        if trigger {
14✔
479
                t.trigger()
7✔
480
        }
7✔
481

482
        return err
7✔
483
}
484

NEW
485
func (t *boundTree[T]) Get() (map[string][]string, map[string]T, error) {
×
NEW
486
        t.lock.RLock()
×
NEW
487
        defer t.lock.RUnlock()
×
NEW
488

×
NEW
489
        return t.ids, *t.val, nil
×
NEW
490
}
×
491

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

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

500
        return *new(T), errOutOfBounds
3✔
501
}
502

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

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

1✔
514
        trigger, err := t.doReload()
1✔
515
        t.lock.Unlock()
1✔
516

1✔
517
        if trigger {
2✔
518
                t.trigger()
1✔
519
        }
1✔
520

521
        return err
1✔
522
}
523

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

1✔
531
        trigger, err := t.doReload()
1✔
532
        t.lock.Unlock()
1✔
533

1✔
534
        if trigger {
2✔
535
                t.trigger()
1✔
536
        }
1✔
537

538
        return err
1✔
539
}
540

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

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

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

3✔
556
        if trigger {
5✔
557
                t.trigger()
2✔
558
        }
2✔
559

560
        return err
3✔
561
}
562

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

5✔
568
        trigger, err := t.doReload()
5✔
569
        t.lock.Unlock()
5✔
570

5✔
571
        if trigger {
9✔
572
                t.trigger()
4✔
573
        }
4✔
574

575
        return err
5✔
576
}
577

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

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

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

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

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

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

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

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

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

651
type boundTreeItem[T any] struct {
652
        base
653

654
        val *map[string]T
655
        id  string
656
}
657

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

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

NEW
667
        return *new(T), errOutOfBounds
×
668
}
669

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

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

25✔
679
        t.trigger()
25✔
680
        return nil
25✔
681
}
25✔
682

683
type boundExternalTreeItem[T any] struct {
684
        boundTreeItem[T]
685

686
        comparator func(T, T) bool
687
        old        T
688
}
689

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

7✔
700
        t.trigger()
7✔
701
        return nil
7✔
702
}
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