• 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

67.65
/data/binding/lists.go
1
package binding
2

3
import (
4
        "bytes"
5

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

10
// DataList is the base interface for all bindable data lists.
11
//
12
// Since: 2.0
13
type DataList interface {
14
        DataItem
15
        GetItem(index int) (DataItem, error)
16
        Length() int
17
}
18

19
// BoolList supports binding a list of bool values.
20
//
21
// Since: 2.0
22
type BoolList interface {
23
        DataList
24

25
        Append(value bool) error
26
        Get() ([]bool, error)
27
        GetValue(index int) (bool, error)
28
        Prepend(value bool) error
29
        Remove(value bool) error
30
        Set(list []bool) error
31
        SetValue(index int, value bool) error
32
}
33

34
// ExternalBoolList supports binding a list of bool values from an external variable.
35
//
36
// Since: 2.0
37
type ExternalBoolList interface {
38
        BoolList
39

40
        Reload() error
41
}
42

43
// NewBoolList returns a bindable list of bool values.
44
//
45
// Since: 2.0
46
func NewBoolList() BoolList {
×
47
        return newListComparable[bool]()
×
48
}
×
49

50
// BindBoolList returns a bound list of bool values, based on the contents of the passed slice.
51
// If your code changes the content of the slice this refers to you should call Reload() to inform the bindings.
52
//
53
// Since: 2.0
54
func BindBoolList(v *[]bool) ExternalBoolList {
×
55
        return bindListComparable(v)
×
56
}
×
57

58
// BytesList supports binding a list of []byte values.
59
//
60
// Since: 2.2
61
type BytesList interface {
62
        DataList
63

64
        Append(value []byte) error
65
        Get() ([][]byte, error)
66
        GetValue(index int) ([]byte, error)
67
        Prepend(value []byte) error
68
        Remove(value []byte) error
69
        Set(list [][]byte) error
70
        SetValue(index int, value []byte) error
71
}
72

73
// ExternalBytesList supports binding a list of []byte values from an external variable.
74
//
75
// Since: 2.2
76
type ExternalBytesList interface {
77
        BytesList
78

79
        Reload() error
80
}
81

82
// NewBytesList returns a bindable list of []byte values.
83
//
84
// Since: 2.2
85
func NewBytesList() BytesList {
×
86
        return newList(bytes.Equal)
×
87
}
×
88

89
// BindBytesList returns a bound list of []byte values, based on the contents of the passed slice.
90
// If your code changes the content of the slice this refers to you should call Reload() to inform the bindings.
91
//
92
// Since: 2.2
93
func BindBytesList(v *[][]byte) ExternalBytesList {
×
94
        return bindList(v, bytes.Equal)
×
95
}
×
96

97
// FloatList supports binding a list of float64 values.
98
//
99
// Since: 2.0
100
type FloatList interface {
101
        DataList
102

103
        Append(value float64) error
104
        Get() ([]float64, error)
105
        GetValue(index int) (float64, error)
106
        Prepend(value float64) error
107
        Remove(value float64) error
108
        Set(list []float64) error
109
        SetValue(index int, value float64) error
110
}
111

112
// ExternalFloatList supports binding a list of float64 values from an external variable.
113
//
114
// Since: 2.0
115
type ExternalFloatList interface {
116
        FloatList
117

118
        Reload() error
119
}
120

121
// NewFloatList returns a bindable list of float64 values.
122
//
123
// Since: 2.0
124
func NewFloatList() FloatList {
5✔
125
        return newListComparable[float64]()
5✔
126
}
5✔
127

128
// BindFloatList returns a bound list of float64 values, based on the contents of the passed slice.
129
// If your code changes the content of the slice this refers to you should call Reload() to inform the bindings.
130
//
131
// Since: 2.0
132
func BindFloatList(v *[]float64) ExternalFloatList {
3✔
133
        return bindListComparable(v)
3✔
134
}
3✔
135

136
// IntList supports binding a list of int values.
137
//
138
// Since: 2.0
139
type IntList interface {
140
        DataList
141

142
        Append(value int) error
143
        Get() ([]int, error)
144
        GetValue(index int) (int, error)
145
        Prepend(value int) error
146
        Remove(value int) error
147
        Set(list []int) error
148
        SetValue(index int, value int) error
149
}
150

151
// ExternalIntList supports binding a list of int values from an external variable.
152
//
153
// Since: 2.0
154
type ExternalIntList interface {
155
        IntList
156

157
        Reload() error
158
}
159

160
// NewIntList returns a bindable list of int values.
161
//
162
// Since: 2.0
163
func NewIntList() IntList {
×
164
        return newListComparable[int]()
×
165
}
×
166

167
// BindIntList returns a bound list of int values, based on the contents of the passed slice.
168
// If your code changes the content of the slice this refers to you should call Reload() to inform the bindings.
169
//
170
// Since: 2.0
171
func BindIntList(v *[]int) ExternalIntList {
×
172
        return bindListComparable(v)
×
173
}
×
174

175
// RuneList supports binding a list of rune values.
176
//
177
// Since: 2.0
178
type RuneList interface {
179
        DataList
180

181
        Append(value rune) error
182
        Get() ([]rune, error)
183
        GetValue(index int) (rune, error)
184
        Prepend(value rune) error
185
        Remove(value rune) error
186
        Set(list []rune) error
187
        SetValue(index int, value rune) error
188
}
189

190
// ExternalRuneList supports binding a list of rune values from an external variable.
191
//
192
// Since: 2.0
193
type ExternalRuneList interface {
194
        RuneList
195

196
        Reload() error
197
}
198

199
// NewRuneList returns a bindable list of rune values.
200
//
201
// Since: 2.0
202
func NewRuneList() RuneList {
×
203
        return newListComparable[rune]()
×
204
}
×
205

206
// BindRuneList returns a bound list of rune values, based on the contents of the passed slice.
207
// If your code changes the content of the slice this refers to you should call Reload() to inform the bindings.
208
//
209
// Since: 2.0
210
func BindRuneList(v *[]rune) ExternalRuneList {
×
211
        if v == nil {
×
212
                return NewRuneList().(ExternalRuneList)
×
213
        }
×
214

215
        b := newListComparable[rune]()
×
216
        b.val = v
×
217
        b.updateExternal = true
×
218

×
219
        for i := range *v {
×
220
                b.appendItem(bindListItemComparable(v, i, b.updateExternal))
×
221
        }
×
222

223
        return b
×
224
}
225

226
// StringList supports binding a list of string values.
227
//
228
// Since: 2.0
229
type StringList interface {
230
        DataList
231

232
        Append(value string) error
233
        Get() ([]string, error)
234
        GetValue(index int) (string, error)
235
        Prepend(value string) error
236
        Remove(value string) error
237
        Set(list []string) error
238
        SetValue(index int, value string) error
239
}
240

241
// ExternalStringList supports binding a list of string values from an external variable.
242
//
243
// Since: 2.0
244
type ExternalStringList interface {
245
        StringList
246

247
        Reload() error
248
}
249

250
// NewStringList returns a bindable list of string values.
251
//
252
// Since: 2.0
253
func NewStringList() StringList {
×
254
        return newListComparable[string]()
×
255
}
×
256

257
// BindStringList returns a bound list of string values, based on the contents of the passed slice.
258
// If your code changes the content of the slice this refers to you should call Reload() to inform the bindings.
259
//
260
// Since: 2.0
261
func BindStringList(v *[]string) ExternalStringList {
×
262
        return bindListComparable(v)
×
263
}
×
264

265
// UntypedList supports binding a list of any values.
266
//
267
// Since: 2.1
268
type UntypedList interface {
269
        DataList
270

271
        Append(value any) error
272
        Get() ([]any, error)
273
        GetValue(index int) (any, error)
274
        Prepend(value any) error
275
        Remove(value any) error
276
        Set(list []any) error
277
        SetValue(index int, value any) error
278
}
279

280
// ExternalUntypedList supports binding a list of any values from an external variable.
281
//
282
// Since: 2.1
283
type ExternalUntypedList interface {
284
        UntypedList
285

286
        Reload() error
287
}
288

289
// NewUntypedList returns a bindable list of any values.
290
//
291
// Since: 2.1
292
func NewUntypedList() UntypedList {
×
293
        return newList(func(t1, t2 any) bool { return t1 == t2 })
×
294
}
295

296
// BindUntypedList returns a bound list of any values, based on the contents of the passed slice.
297
// If your code changes the content of the slice this refers to you should call Reload() to inform the bindings.
298
//
299
// Since: 2.1
300
func BindUntypedList(v *[]any) ExternalUntypedList {
×
301
        if v == nil {
×
302
                return NewUntypedList().(ExternalUntypedList)
×
303
        }
×
304

305
        comparator := func(t1, t2 any) bool { return t1 == t2 }
×
306
        b := newExternalList(v, comparator)
×
307
        for i := range *v {
×
308
                b.appendItem(bindListItem(v, i, b.updateExternal, comparator))
×
309
        }
×
310

311
        return b
×
312
}
313

314
// URIList supports binding a list of fyne.URI values.
315
//
316
// Since: 2.1
317
type URIList interface {
318
        DataList
319

320
        Append(value fyne.URI) error
321
        Get() ([]fyne.URI, error)
322
        GetValue(index int) (fyne.URI, error)
323
        Prepend(value fyne.URI) error
324
        Remove(value fyne.URI) error
325
        Set(list []fyne.URI) error
326
        SetValue(index int, value fyne.URI) error
327
}
328

329
// ExternalURIList supports binding a list of fyne.URI values from an external variable.
330
//
331
// Since: 2.1
332
type ExternalURIList interface {
333
        URIList
334

335
        Reload() error
336
}
337

338
// NewURIList returns a bindable list of fyne.URI values.
339
//
340
// Since: 2.1
341
func NewURIList() URIList {
×
342
        return newList(storage.EqualURI)
×
343
}
×
344

345
// BindURIList returns a bound list of fyne.URI values, based on the contents of the passed slice.
346
// If your code changes the content of the slice this refers to you should call Reload() to inform the bindings.
347
//
348
// Since: 2.1
349
func BindURIList(v *[]fyne.URI) ExternalURIList {
×
350
        return bindList(v, storage.EqualURI)
×
351
}
×
352

353
type listBase struct {
354
        base
355
        items []DataItem
356
}
357

358
// GetItem returns the DataItem at the specified index.
359
func (b *listBase) GetItem(i int) (DataItem, error) {
6✔
360
        b.lock.RLock()
6✔
361
        defer b.lock.RUnlock()
6✔
362

6✔
363
        if i < 0 || i >= len(b.items) {
7✔
364
                return nil, errOutOfBounds
1✔
365
        }
1✔
366

367
        return b.items[i], nil
5✔
368
}
369

370
// Length returns the number of items in this data list.
371
func (b *listBase) Length() int {
26✔
372
        b.lock.RLock()
26✔
373
        defer b.lock.RUnlock()
26✔
374

26✔
375
        return len(b.items)
26✔
376
}
26✔
377

378
func (b *listBase) appendItem(i DataItem) {
23✔
379
        b.items = append(b.items, i)
23✔
380
}
23✔
381

382
func (b *listBase) deleteItem(i int) {
8✔
383
        b.items = append(b.items[:i], b.items[i+1:]...)
8✔
384
}
8✔
385

386
func newList[T any](comparator func(T, T) bool) *boundList[T] {
5✔
387
        return &boundList[T]{val: new([]T), comparator: comparator}
5✔
388
}
5✔
389

390
func newListComparable[T bool | float64 | int | rune | string]() *boundList[T] {
5✔
391
        return newList(func(t1, t2 T) bool { return t1 == t2 })
8✔
392
}
393

394
func newExternalList[T any](v *[]T, comparator func(T, T) bool) *boundList[T] {
3✔
395
        return &boundList[T]{val: v, comparator: comparator, updateExternal: true}
3✔
396
}
3✔
397

398
func bindList[T any](v *[]T, comparator func(T, T) bool) *boundList[T] {
3✔
399
        if v == nil {
3✔
UNCOV
400
                return newList(comparator)
×
UNCOV
401
        }
×
402

403
        l := newExternalList(v, comparator)
3✔
404
        for i := range *v {
12✔
405
                l.appendItem(bindListItem(v, i, l.updateExternal, comparator))
9✔
406
        }
9✔
407

408
        return l
3✔
409
}
410

411
func bindListComparable[T bool | float64 | int | rune | string](v *[]T) *boundList[T] {
3✔
412
        return bindList(v, func(t1, t2 T) bool { return t1 == t2 })
17✔
413
}
414

415
type boundList[T any] struct {
416
        listBase
417

418
        comparator     func(T, T) bool
419
        updateExternal bool
420
        val            *[]T
421
}
422

423
func (l *boundList[T]) Append(val T) error {
7✔
424
        l.lock.Lock()
7✔
425
        *l.val = append(*l.val, val)
7✔
426

7✔
427
        trigger, err := l.doReload()
7✔
428
        l.lock.Unlock()
7✔
429

7✔
430
        if trigger {
14✔
431
                l.trigger()
7✔
432
        }
7✔
433

434
        return err
7✔
435
}
436

UNCOV
437
func (l *boundList[T]) Get() ([]T, error) {
×
UNCOV
438
        l.lock.RLock()
×
UNCOV
439
        defer l.lock.RUnlock()
×
UNCOV
440

×
UNCOV
441
        return *l.val, nil
×
UNCOV
442
}
×
443

444
func (l *boundList[T]) GetValue(i int) (T, error) {
14✔
445
        l.lock.RLock()
14✔
446
        defer l.lock.RUnlock()
14✔
447

14✔
448
        if i < 0 || i >= l.Length() {
17✔
449
                return *new(T), errOutOfBounds
3✔
450
        }
3✔
451

452
        return (*l.val)[i], nil
11✔
453
}
454

455
func (l *boundList[T]) Prepend(val T) error {
1✔
456
        l.lock.Lock()
1✔
457
        *l.val = append([]T{val}, *l.val...)
1✔
458

1✔
459
        trigger, err := l.doReload()
1✔
460
        l.lock.Unlock()
1✔
461

1✔
462
        if trigger {
2✔
463
                l.trigger()
1✔
464
        }
1✔
465

466
        return err
1✔
467
}
468

469
func (l *boundList[T]) Reload() error {
3✔
470
        l.lock.Lock()
3✔
471
        trigger, err := l.doReload()
3✔
472
        l.lock.Unlock()
3✔
473

3✔
474
        if trigger {
5✔
475
                l.trigger()
2✔
476
        }
2✔
477

478
        return err
3✔
479
}
480

481
func (l *boundList[T]) Remove(val T) error {
2✔
482
        l.lock.Lock()
2✔
483

2✔
484
        v := *l.val
2✔
485
        if len(v) == 0 {
2✔
UNCOV
486
                l.lock.Unlock()
×
UNCOV
487
                return nil
×
UNCOV
488
        }
×
489
        if l.comparator(v[0], val) {
3✔
490
                *l.val = v[1:]
1✔
491
        } else if l.comparator(v[len(v)-1], val) {
3✔
492
                *l.val = v[:len(v)-1]
1✔
493
        } else {
1✔
UNCOV
494
                id := -1
×
UNCOV
495
                for i, v := range v {
×
UNCOV
496
                        if l.comparator(v, val) {
×
UNCOV
497
                                id = i
×
UNCOV
498
                                break
×
499
                        }
500
                }
501

UNCOV
502
                if id == -1 {
×
UNCOV
503
                        l.lock.Unlock()
×
UNCOV
504
                        return nil
×
UNCOV
505
                }
×
UNCOV
506
                *l.val = append(v[:id], v[id+1:]...)
×
507
        }
508

509
        trigger, err := l.doReload()
2✔
510
        l.lock.Unlock()
2✔
511

2✔
512
        if trigger {
4✔
513
                l.trigger()
2✔
514
        }
2✔
515

516
        return err
2✔
517
}
518

519
func (l *boundList[T]) Set(v []T) error {
5✔
520
        l.lock.Lock()
5✔
521
        *l.val = v
5✔
522
        trigger, err := l.doReload()
5✔
523
        l.lock.Unlock()
5✔
524

5✔
525
        if trigger {
9✔
526
                l.trigger()
4✔
527
        }
4✔
528

529
        return err
5✔
530
}
531

532
func (l *boundList[T]) doReload() (trigger bool, retErr error) {
18✔
533
        oldLen := len(l.items)
18✔
534
        newLen := len(*l.val)
18✔
535
        if oldLen > newLen {
23✔
536
                for i := oldLen - 1; i >= newLen; i-- {
13✔
537
                        l.deleteItem(i)
8✔
538
                }
8✔
539
                trigger = true
5✔
540
        } else if oldLen < newLen {
24✔
541
                for i := oldLen; i < newLen; i++ {
23✔
542
                        l.appendItem(bindListItem(l.val, i, l.updateExternal, l.comparator))
12✔
543
                }
12✔
544
                trigger = true
11✔
545
        }
546

547
        for i, item := range l.items {
57✔
548
                if i > oldLen || i > newLen {
40✔
549
                        break
1✔
550
                }
551

552
                var err error
38✔
553
                if l.updateExternal {
52✔
554
                        err = item.(*boundExternalListItem[T]).setIfChanged((*l.val)[i])
14✔
555
                } else {
38✔
556
                        err = item.(*boundListItem[T]).doSet((*l.val)[i])
24✔
557
                }
24✔
558
                if err != nil {
38✔
UNCOV
559
                        retErr = err
×
UNCOV
560
                }
×
561
        }
562
        return
18✔
563
}
564

565
func (l *boundList[T]) SetValue(i int, v T) error {
2✔
566
        l.lock.RLock()
2✔
567
        len := l.Length()
2✔
568
        l.lock.RUnlock()
2✔
569

2✔
570
        if i < 0 || i >= len {
2✔
UNCOV
571
                return errOutOfBounds
×
UNCOV
572
        }
×
573

574
        l.lock.Lock()
2✔
575
        (*l.val)[i] = v
2✔
576
        l.lock.Unlock()
2✔
577

2✔
578
        item, err := l.GetItem(i)
2✔
579
        if err != nil {
2✔
UNCOV
580
                return err
×
UNCOV
581
        }
×
582
        return item.(bindableItem[T]).Set(v)
2✔
583
}
584

585
func bindListItem[T any](v *[]T, i int, external bool, comparator func(T, T) bool) bindableItem[T] {
21✔
586
        if external {
32✔
587
                ret := &boundExternalListItem[T]{old: (*v)[i]}
11✔
588
                ret.val = v
11✔
589
                ret.index = i
11✔
590
                ret.comparator = comparator
11✔
591
                return ret
11✔
592
        }
11✔
593

594
        return &boundListItem[T]{val: v, index: i, comparator: comparator}
10✔
595
}
596

UNCOV
597
func bindListItemComparable[T bool | float64 | int | rune | string](v *[]T, i int, external bool) bindableItem[T] {
×
UNCOV
598
        return bindListItem(v, i, external, func(t1, t2 T) bool { return t1 == t2 })
×
599
}
600

601
type boundListItem[T any] struct {
602
        base
603

604
        comparator func(T, T) bool
605
        val        *[]T
606
        index      int
607
}
608

609
func (b *boundListItem[T]) Get() (T, error) {
3✔
610
        b.lock.Lock()
3✔
611
        defer b.lock.Unlock()
3✔
612

3✔
613
        if b.index < 0 || b.index >= len(*b.val) {
3✔
UNCOV
614
                return *new(T), errOutOfBounds
×
UNCOV
615
        }
×
616

617
        return (*b.val)[b.index], nil
3✔
618
}
619

620
func (b *boundListItem[T]) Set(val T) error {
2✔
621
        return b.doSet(val)
2✔
622
}
2✔
623

624
func (b *boundListItem[T]) doSet(val T) error {
26✔
625
        b.lock.Lock()
26✔
626
        (*b.val)[b.index] = val
26✔
627
        b.lock.Unlock()
26✔
628

26✔
629
        b.trigger()
26✔
630
        return nil
26✔
631
}
26✔
632

633
type boundExternalListItem[T any] struct {
634
        boundListItem[T]
635

636
        old T
637
}
638

639
func (b *boundExternalListItem[T]) setIfChanged(val T) error {
14✔
640
        b.lock.Lock()
14✔
641
        if b.comparator(val, b.old) {
21✔
642
                b.lock.Unlock()
7✔
643
                return nil
7✔
644
        }
7✔
645
        (*b.val)[b.index] = val
7✔
646
        b.old = val
7✔
647

7✔
648
        b.lock.Unlock()
7✔
649
        b.trigger()
7✔
650
        return nil
7✔
651
}
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