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

fyne-io / fyne / 17516456766

06 Sep 2025 03:47PM UTC coverage: 62.317% (+0.02%) from 62.295%
17516456766

push

github

web-flow
Merge pull request #5933 from Jacalz/moduke-update

25338 of 40660 relevant lines covered (62.32%)

714.01 hits per line

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

81.25
/data/binding/maps.go
1
package binding
2

3
import (
4
        "errors"
5
        "reflect"
6

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

10
// DataMap is the base interface for all bindable data maps.
11
//
12
// Since: 2.0
13
type DataMap interface {
14
        DataItem
15
        GetItem(string) (DataItem, error)
16
        Keys() []string
17
}
18

19
// ExternalUntypedMap is a map data binding with all values untyped (any), connected to an external data source.
20
//
21
// Since: 2.0
22
type ExternalUntypedMap interface {
23
        UntypedMap
24
        Reload() error
25
}
26

27
// UntypedMap is a map data binding with all values Untyped (any).
28
//
29
// Since: 2.0
30
type UntypedMap interface {
31
        DataMap
32
        Delete(string)
33
        Get() (map[string]any, error)
34
        GetValue(string) (any, error)
35
        Set(map[string]any) error
36
        SetValue(string, any) error
37
}
38

39
// NewUntypedMap creates a new, empty map binding of string to any.
40
//
41
// Since: 2.0
42
func NewUntypedMap() UntypedMap {
×
43
        return &mapBase{items: make(map[string]reflectUntyped), val: &map[string]any{}}
×
44
}
×
45

46
// BindUntypedMap creates a new map binding of string to any based on the data passed.
47
// If your code changes the content of the map this refers to you should call Reload() to inform the bindings.
48
//
49
// Since: 2.0
50
func BindUntypedMap(d *map[string]any) ExternalUntypedMap {
4✔
51
        if d == nil {
4✔
52
                return NewUntypedMap().(ExternalUntypedMap)
×
53
        }
×
54
        m := &mapBase{items: make(map[string]reflectUntyped), val: d, updateExternal: true}
4✔
55

4✔
56
        for k := range *d {
14✔
57
                m.setItem(k, bindUntypedMapValue(d, k, m.updateExternal))
10✔
58
        }
10✔
59

60
        return m
4✔
61
}
62

63
// Struct is the base interface for a bound struct type.
64
//
65
// Since: 2.0
66
type Struct interface {
67
        DataMap
68
        GetValue(string) (any, error)
69
        SetValue(string, any) error
70
        Reload() error
71
}
72

73
// BindStruct creates a new map binding of string to any using the struct passed as data.
74
// The key for each item is a string representation of each exported field with the value set as an any.
75
// Only exported fields are included.
76
//
77
// Since: 2.0
78
func BindStruct(i any) Struct {
2✔
79
        if i == nil {
2✔
80
                return NewUntypedMap().(Struct)
×
81
        }
×
82
        t := reflect.TypeOf(i)
2✔
83
        if t.Kind() != reflect.Ptr ||
2✔
84
                (reflect.TypeOf(reflect.ValueOf(i).Elem()).Kind() != reflect.Struct) {
2✔
85
                fyne.LogError("Invalid type passed to BindStruct, must be pointer to struct", nil)
×
86
                return NewUntypedMap().(Struct)
×
87
        }
×
88

89
        s := &boundStruct{orig: i}
2✔
90
        s.items = make(map[string]reflectUntyped)
2✔
91
        s.val = &map[string]any{}
2✔
92
        s.updateExternal = true
2✔
93

2✔
94
        v := reflect.ValueOf(i).Elem()
2✔
95
        t = v.Type()
2✔
96
        for j := 0; j < v.NumField(); j++ {
8✔
97
                f := v.Field(j)
6✔
98
                if !f.CanSet() {
6✔
99
                        continue
×
100
                }
101

102
                key := t.Field(j).Name
6✔
103
                s.items[key] = bindReflect(f)
6✔
104
                (*s.val)[key] = f.Interface()
6✔
105
        }
106

107
        return s
2✔
108
}
109

110
type reflectUntyped interface {
111
        DataItem
112
        get() (any, error)
113
        set(any) error
114
}
115

116
type mapBase struct {
117
        base
118

119
        updateExternal bool
120
        items          map[string]reflectUntyped
121
        val            *map[string]any
122
}
123

124
func (b *mapBase) GetItem(key string) (DataItem, error) {
5✔
125
        b.lock.RLock()
5✔
126
        defer b.lock.RUnlock()
5✔
127

5✔
128
        if v, ok := b.items[key]; ok {
9✔
129
                return v, nil
4✔
130
        }
4✔
131

132
        return nil, errKeyNotFound
1✔
133
}
134

135
func (b *mapBase) Keys() []string {
9✔
136
        b.lock.Lock()
9✔
137
        defer b.lock.Unlock()
9✔
138

9✔
139
        ret := make([]string, len(b.items))
9✔
140
        i := 0
9✔
141
        for k := range b.items {
30✔
142
                ret[i] = k
21✔
143
                i++
21✔
144
        }
21✔
145

146
        return ret
9✔
147
}
148

149
func (b *mapBase) Delete(key string) {
1✔
150
        b.lock.Lock()
1✔
151
        defer b.lock.Unlock()
1✔
152

1✔
153
        delete(b.items, key)
1✔
154

1✔
155
        b.trigger()
1✔
156
}
1✔
157

158
func (b *mapBase) Get() (map[string]any, error) {
×
159
        b.lock.RLock()
×
160
        defer b.lock.RUnlock()
×
161

×
162
        if b.val == nil {
×
163
                return map[string]any{}, nil
×
164
        }
×
165

166
        return *b.val, nil
×
167
}
168

169
func (b *mapBase) GetValue(key string) (any, error) {
18✔
170
        b.lock.RLock()
18✔
171
        defer b.lock.RUnlock()
18✔
172

18✔
173
        if i, ok := b.items[key]; ok {
35✔
174
                return i.get()
17✔
175
        }
17✔
176

177
        return nil, errKeyNotFound
1✔
178
}
179

180
func (b *mapBase) Reload() error {
3✔
181
        b.lock.Lock()
3✔
182
        defer b.lock.Unlock()
3✔
183

3✔
184
        return b.doReload()
3✔
185
}
3✔
186

187
func (b *mapBase) Set(v map[string]any) error {
2✔
188
        b.lock.Lock()
2✔
189
        defer b.lock.Unlock()
2✔
190

2✔
191
        if b.val == nil { // was not initialized with a blank value, recover
2✔
192
                b.val = &v
×
193
                b.trigger()
×
194
                return nil
×
195
        }
×
196

197
        *b.val = v
2✔
198
        return b.doReload()
2✔
199
}
200

201
func (b *mapBase) SetValue(key string, d any) error {
2✔
202
        b.lock.Lock()
2✔
203
        defer b.lock.Unlock()
2✔
204

2✔
205
        if i, ok := b.items[key]; ok {
3✔
206
                return i.set(d)
1✔
207
        }
1✔
208

209
        (*b.val)[key] = d
1✔
210
        item := bindUntypedMapValue(b.val, key, b.updateExternal)
1✔
211
        b.setItem(key, item)
1✔
212
        return nil
1✔
213
}
214

215
func (b *mapBase) doReload() (retErr error) {
5✔
216
        changed := false
5✔
217
        // add new
5✔
218
        for key := range *b.val {
17✔
219
                _, found := b.items[key]
12✔
220
                if !found {
14✔
221
                        b.setItem(key, bindUntypedMapValue(b.val, key, b.updateExternal))
2✔
222
                        changed = true
2✔
223
                }
2✔
224
        }
225

226
        // remove old
227
        for key := range b.items {
20✔
228
                _, found := (*b.val)[key]
15✔
229
                if !found {
18✔
230
                        delete(b.items, key)
3✔
231
                        changed = true
3✔
232
                }
3✔
233
        }
234
        if changed {
9✔
235
                b.trigger()
4✔
236
        }
4✔
237

238
        for k, item := range b.items {
17✔
239
                var err error
12✔
240

12✔
241
                if b.updateExternal {
24✔
242
                        err = item.(*boundExternalMapValue).setIfChanged((*b.val)[k])
12✔
243
                } else {
12✔
244
                        err = item.(*boundMapValue).set((*b.val)[k])
×
245
                }
×
246

247
                if err != nil {
12✔
248
                        retErr = err
×
249
                }
×
250
        }
251
        return retErr
5✔
252
}
253

254
func (b *mapBase) setItem(key string, d reflectUntyped) {
13✔
255
        b.items[key] = d
13✔
256

13✔
257
        b.trigger()
13✔
258
}
13✔
259

260
type boundStruct struct {
261
        mapBase
262

263
        orig any
264
}
265

266
func (b *boundStruct) Reload() (retErr error) {
2✔
267
        b.lock.Lock()
2✔
268
        defer b.lock.Unlock()
2✔
269

2✔
270
        v := reflect.ValueOf(b.orig).Elem()
2✔
271
        t := v.Type()
2✔
272
        for j := 0; j < v.NumField(); j++ {
8✔
273
                f := v.Field(j)
6✔
274
                if !f.CanSet() {
6✔
275
                        continue
×
276
                }
277
                kind := f.Kind()
6✔
278
                if kind == reflect.Slice || kind == reflect.Struct {
6✔
279
                        fyne.LogError("Data binding does not yet support slice or struct elements in a struct", nil)
×
280
                        continue
×
281
                }
282

283
                key := t.Field(j).Name
6✔
284
                old := (*b.val)[key]
6✔
285
                if f.Interface() == old {
9✔
286
                        continue
3✔
287
                }
288

289
                var err error
3✔
290
                switch kind {
3✔
291
                case reflect.Bool:
×
292
                        err = b.items[key].(*boundReflect[bool]).Set(f.Bool())
×
293
                case reflect.Float32, reflect.Float64:
1✔
294
                        err = b.items[key].(*boundReflect[float64]).Set(f.Float())
1✔
295
                case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
1✔
296
                        err = b.items[key].(*boundReflect[int]).Set(int(f.Int()))
1✔
297
                case reflect.String:
1✔
298
                        err = b.items[key].(*boundReflect[string]).Set(f.String())
1✔
299
                }
300
                if err != nil {
3✔
301
                        retErr = err
×
302
                }
×
303
                (*b.val)[key] = f.Interface()
3✔
304
        }
305
        return retErr
2✔
306
}
307

308
func bindUntypedMapValue(m *map[string]any, k string, external bool) reflectUntyped {
13✔
309
        if external {
26✔
310
                ret := &boundExternalMapValue{old: (*m)[k]}
13✔
311
                ret.val = m
13✔
312
                ret.key = k
13✔
313
                return ret
13✔
314
        }
13✔
315

316
        return &boundMapValue{val: m, key: k}
×
317
}
318

319
type boundMapValue struct {
320
        base
321

322
        val *map[string]any
323
        key string
324
}
325

326
func (b *boundMapValue) get() (any, error) {
17✔
327
        if v, ok := (*b.val)[b.key]; ok {
34✔
328
                return v, nil
17✔
329
        }
17✔
330

331
        return nil, errKeyNotFound
×
332
}
333

334
func (b *boundMapValue) set(val any) error {
6✔
335
        (*b.val)[b.key] = val
6✔
336

6✔
337
        b.trigger()
6✔
338
        return nil
6✔
339
}
6✔
340

341
type boundExternalMapValue struct {
342
        boundMapValue
343

344
        old any
345
}
346

347
func (b *boundExternalMapValue) setIfChanged(val any) error {
12✔
348
        if val == b.old {
19✔
349
                return nil
7✔
350
        }
7✔
351
        b.old = val
5✔
352

5✔
353
        return b.set(val)
5✔
354
}
355

356
type boundReflect[T any] struct {
357
        base
358

359
        val reflect.Value
360
}
361

362
func (b *boundReflect[T]) Get() (T, error) {
8✔
363
        var zero T
8✔
364
        val, err := b.get()
8✔
365
        if err != nil {
8✔
366
                return zero, err
×
367
        }
×
368

369
        casted, ok := val.(T)
8✔
370
        if !ok {
9✔
371
                return zero, errors.New("unable to convert value to type")
1✔
372
        }
1✔
373

374
        return casted, nil
7✔
375
}
376

377
func (b *boundReflect[T]) Set(val T) error {
6✔
378
        return b.set(val)
6✔
379
}
6✔
380

381
func (b *boundReflect[T]) get() (any, error) {
11✔
382
        if !b.val.CanInterface() {
11✔
383
                return nil, errors.New("unable to get value from data binding")
×
384
        }
×
385

386
        return b.val.Interface(), nil
11✔
387
}
388

389
func (b *boundReflect[T]) set(val any) error {
6✔
390
        if !b.val.CanSet() {
6✔
391
                return errors.New("unable to set value in data binding")
×
392
        }
×
393

394
        b.val.Set(reflect.ValueOf(val))
6✔
395
        b.trigger()
6✔
396
        return nil
6✔
397
}
398

399
func bindReflect(field reflect.Value) reflectUntyped {
6✔
400
        switch field.Kind() {
6✔
401
        case reflect.Bool:
×
402
                return &boundReflect[bool]{val: field}
×
403
        case reflect.Float32, reflect.Float64:
2✔
404
                return &boundReflect[float64]{val: field}
2✔
405
        case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
2✔
406
                return &boundReflect[int]{val: field}
2✔
407
        case reflect.String:
2✔
408
                return &boundReflect[string]{val: field}
2✔
409
        }
410
        return &boundReflect[any]{val: field}
×
411
}
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