• 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

92.49
/widget/check_group.go
1
package widget
2

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

7
        "fyne.io/fyne/v2"
8
        "fyne.io/fyne/v2/canvas"
9
        "fyne.io/fyne/v2/internal/widget"
10
)
11

12
var _ fyne.Widget = (*CheckGroup)(nil)
13

14
// CheckGroup widget has a list of text labels and checkbox icons next to each.
15
// Changing the selection (any number can be selected) will trigger the changed func.
16
//
17
// Since: 2.1
18
type CheckGroup struct {
19
        DisableableWidget
20
        Horizontal bool
21
        Required   bool
22
        OnChanged  func([]string) `json:"-"`
23
        Options    []string
24
        Selected   []string
25

26
        items []*Check
27
}
28

29
// NewCheckGroup creates a new check group widget with the set options and change handler
30
//
31
// Since: 2.1
32
func NewCheckGroup(options []string, changed func([]string)) *CheckGroup {
1✔
33
        r := &CheckGroup{
1✔
34
                Options:   options,
1✔
35
                OnChanged: changed,
1✔
36
        }
1✔
37
        r.ExtendBaseWidget(r)
1✔
38
        r.update()
1✔
39
        return r
1✔
40
}
1✔
41

42
// Append adds a new option to the end of a CheckGroup widget.
43
func (r *CheckGroup) Append(option string) {
3✔
44
        r.Options = append(r.Options, option)
3✔
45

3✔
46
        r.Refresh()
3✔
47
}
3✔
48

49
// CreateRenderer is a private method to Fyne which links this widget to its renderer
50
func (r *CheckGroup) CreateRenderer() fyne.WidgetRenderer {
20✔
51
        r.ExtendBaseWidget(r)
20✔
52

20✔
53
        r.update()
20✔
54
        objects := make([]fyne.CanvasObject, len(r.items))
20✔
55
        for i, item := range r.items {
55✔
56
                objects[i] = item
35✔
57
        }
35✔
58

59
        return &checkGroupRenderer{widget.NewBaseRenderer(objects), r.items, r}
20✔
60
}
61

62
// MinSize returns the size that this widget should not shrink below
63
func (r *CheckGroup) MinSize() fyne.Size {
79✔
64
        r.ExtendBaseWidget(r)
79✔
65
        return r.BaseWidget.MinSize()
79✔
66
}
79✔
67

68
// Refresh causes this widget to be redrawn in it's current state.
69
func (r *CheckGroup) Refresh() {
14✔
70
        r.update()
14✔
71
        r.BaseWidget.Refresh()
14✔
72
}
14✔
73

74
// Remove removes the first occurrence of the specified option found from a CheckGroup widget.
75
// Return true if an option was removed.
76
//
77
// Since: 2.3
78
func (r *CheckGroup) Remove(option string) bool {
2✔
79
        for i, o := range r.Options {
5✔
80
                if strings.EqualFold(option, o) {
4✔
81
                        r.Options = append(r.Options[:i], r.Options[i+1:]...)
1✔
82
                        for j, s := range r.Selected {
2✔
83
                                if strings.EqualFold(option, s) {
2✔
84
                                        r.Selected = append(r.Selected[:j], r.Selected[j+1:]...)
1✔
85
                                        break
1✔
86
                                }
87
                        }
88
                        r.Refresh()
1✔
89
                        return true
1✔
90
                }
91
        }
92
        return false
1✔
93
}
94

95
// SetSelected sets the checked options, it can be used to set a default option.
96
func (r *CheckGroup) SetSelected(options []string) {
2✔
97
        //if r.Selected == options {
2✔
98
        //        return
2✔
99
        //}
2✔
100

2✔
101
        r.Selected = options
2✔
102

2✔
103
        if r.OnChanged != nil {
2✔
104
                r.OnChanged(options)
×
105
        }
×
106

107
        r.Refresh()
2✔
108
}
109

110
func (r *CheckGroup) itemTapped(item *Check) {
7✔
111
        if r.Disabled() {
7✔
112
                return
×
113
        }
×
114

115
        contains := false
7✔
116
        for i, s := range r.Selected {
15✔
117
                if s == item.Text {
12✔
118
                        contains = true
4✔
119
                        if len(r.Selected) <= 1 {
6✔
120
                                if r.Required {
4✔
121
                                        item.SetChecked(true)
2✔
122
                                        return
2✔
123
                                }
2✔
NEW
124
                                r.Selected = nil
×
125
                        } else {
2✔
126
                                r.Selected = append(r.Selected[:i], r.Selected[i+1:]...)
2✔
127
                        }
2✔
128
                        break
2✔
129
                }
130
        }
131

132
        if !contains {
8✔
133
                r.Selected = append(r.Selected, item.Text)
3✔
134
        }
3✔
135

136
        if r.OnChanged != nil {
5✔
UNCOV
137
                r.OnChanged(r.Selected)
×
UNCOV
138
        }
×
139
        r.Refresh()
5✔
140
}
141

142
func (r *CheckGroup) update() {
35✔
143
        r.Options = removeDuplicates(r.Options)
35✔
144
        if len(r.items) < len(r.Options) {
58✔
145
                for i := len(r.items); i < len(r.Options); i++ {
61✔
146
                        var item *Check
38✔
147
                        item = NewCheck(r.Options[i], func(bool) {
45✔
148
                                r.itemTapped(item)
7✔
149
                        })
7✔
150
                        r.items = append(r.items, item)
38✔
151
                }
152
        } else if len(r.items) > len(r.Options) {
13✔
153
                r.items = r.items[:len(r.Options)]
1✔
154
        }
1✔
155
        for i, item := range r.items {
107✔
156
                contains := slices.Contains(r.Selected, item.Text)
72✔
157

72✔
158
                item.Text = r.Options[i]
72✔
159
                item.Checked = contains
72✔
160
                item.DisableableWidget.disabled = r.Disabled()
72✔
161
                item.Refresh()
72✔
162
        }
72✔
163
}
164

165
type checkGroupRenderer struct {
166
        widget.BaseRenderer
167
        items  []*Check
168
        checks *CheckGroup
169
}
170

171
// Layout the components of the checks widget
172
func (r *checkGroupRenderer) Layout(_ fyne.Size) {
40✔
173
        count := 1
40✔
174
        if len(r.items) > 0 {
80✔
175
                count = len(r.items)
40✔
176
        }
40✔
177
        var itemHeight, itemWidth float32
40✔
178
        minSize := r.checks.MinSize()
40✔
179
        if r.checks.Horizontal {
56✔
180
                itemHeight = minSize.Height
16✔
181
                itemWidth = minSize.Width / float32(count)
16✔
182
        } else {
40✔
183
                itemHeight = minSize.Height / float32(count)
24✔
184
                itemWidth = minSize.Width
24✔
185
        }
24✔
186

187
        itemSize := fyne.NewSize(itemWidth, itemHeight)
40✔
188
        x, y := float32(0), float32(0)
40✔
189
        for _, item := range r.items {
110✔
190
                item.Resize(itemSize)
70✔
191
                item.Move(fyne.NewPos(x, y))
70✔
192
                if r.checks.Horizontal {
94✔
193
                        x += itemWidth
24✔
194
                } else {
70✔
195
                        y += itemHeight
46✔
196
                }
46✔
197
        }
198
}
199

200
// MinSize calculates the minimum size of a checks item.
201
// This is based on the contained text, the checks icon and a standard amount of padding
202
// between each item.
203
func (r *checkGroupRenderer) MinSize() fyne.Size {
79✔
204
        width := float32(0)
79✔
205
        height := float32(0)
79✔
206
        for _, item := range r.items {
216✔
207
                itemMin := item.MinSize()
137✔
208

137✔
209
                width = max(width, itemMin.Width)
137✔
210
                height = max(height, itemMin.Height)
137✔
211
        }
137✔
212

213
        if r.checks.Horizontal {
111✔
214
                width = width * float32(len(r.items))
32✔
215
        } else {
79✔
216
                height = height * float32(len(r.items))
47✔
217
        }
47✔
218

219
        return fyne.NewSize(width, height)
79✔
220
}
221

222
func (r *checkGroupRenderer) Refresh() {
10✔
223
        r.updateItems()
10✔
224
        canvas.Refresh(r.checks.super())
10✔
225
}
10✔
226

227
func (r *checkGroupRenderer) updateItems() {
10✔
228
        if len(r.items) < len(r.checks.Options) {
11✔
229
                for i := len(r.items); i < len(r.checks.Options); i++ {
2✔
230
                        var item *Check
1✔
231
                        item = NewCheck(r.checks.Options[i], func(bool) {
1✔
UNCOV
232
                                r.checks.itemTapped(item)
×
233
                        })
×
234
                        r.SetObjects(append(r.Objects(), item))
1✔
235
                        r.items = append(r.items, item)
1✔
236
                }
237
                r.Layout(r.checks.Size())
1✔
238
        } else if len(r.items) > len(r.checks.Options) {
9✔
UNCOV
239
                total := len(r.checks.Options)
×
UNCOV
240
                r.items = r.items[:total]
×
UNCOV
241
                r.SetObjects(r.Objects()[:total])
×
UNCOV
242
        }
×
243
        for i, item := range r.items {
39✔
244
                contains := slices.Contains(r.checks.Selected, item.Text)
29✔
245
                item.Text = r.checks.Options[i]
29✔
246
                item.Checked = contains
29✔
247
                item.disabled = r.checks.Disabled()
29✔
248
                item.Refresh()
29✔
249
        }
29✔
250
}
251

252
func removeDuplicates(options []string) []string {
35✔
253
        var result []string
35✔
254
        found := make(map[string]bool)
35✔
255

35✔
256
        for _, option := range options {
107✔
257
                if _, ok := found[option]; !ok {
144✔
258
                        found[option] = true
72✔
259
                        result = append(result, option)
72✔
260
                }
72✔
261
        }
262

263
        return result
35✔
264
}
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