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

fyne-io / fyne / 30240283751

27 Jul 2026 05:37AM UTC coverage: 59.896% (-0.02%) from 59.912%
30240283751

push

github

web-flow
Merge pull request #6440 from toaster/lint/trivial_checks

enable a couple of linter checks which only require trivial adjustments

589 of 969 new or added lines in 144 files covered. (60.78%)

14 existing lines in 10 files now uncovered.

27103 of 45250 relevant lines covered (59.9%)

815.37 hits per line

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

95.12
/container/split.go
1
package container
2

3
import (
4
        "fyne.io/fyne/v2"
5
        "fyne.io/fyne/v2/canvas"
6
        "fyne.io/fyne/v2/driver/desktop"
7
        "fyne.io/fyne/v2/theme"
8
        "fyne.io/fyne/v2/widget"
9
)
10

11
// Declare conformity with CanvasObject interface
12
var _ fyne.CanvasObject = (*Split)(nil)
13

14
var splitDefaultThickness = float32(5) // a sane constant if the theme does not know the new value nor delegate
15

16
// Split defines a container whose size is split between two children.
17
//
18
// Since: 1.4
19
type Split struct {
20
        widget.BaseWidget
21
        Offset     float64
22
        Horizontal bool
23
        Leading    fyne.CanvasObject
24
        Trailing   fyne.CanvasObject
25

26
        // to communicate to the renderer that the next refresh
27
        // is just an offset update (ie a resize and move only)
28
        // cleared by renderer in Refresh()
29
        offsetUpdated bool
30
}
31

32
// NewHSplit creates a horizontally arranged container with the specified leading and trailing elements.
33
// A vertical split bar that can be dragged will be added between the elements.
34
//
35
// Since: 1.4
36
func NewHSplit(leading, trailing fyne.CanvasObject) *Split {
16✔
37
        return newSplitContainer(true, leading, trailing)
16✔
38
}
16✔
39

40
// NewVSplit creates a vertically arranged container with the specified top and bottom elements.
41
// A horizontal split bar that can be dragged will be added between the elements.
42
//
43
// Since: 1.4
44
func NewVSplit(top, bottom fyne.CanvasObject) *Split {
12✔
45
        return newSplitContainer(false, top, bottom)
12✔
46
}
12✔
47

48
func newSplitContainer(horizontal bool, leading, trailing fyne.CanvasObject) *Split {
28✔
49
        s := &Split{
28✔
50
                Offset:     0.5, // Sensible default, can be overridden with SetOffset
28✔
51
                Horizontal: horizontal,
28✔
52
                Leading:    leading,
28✔
53
                Trailing:   trailing,
28✔
54
        }
28✔
55
        s.BaseWidget.ExtendBaseWidget(s)
28✔
56
        return s
28✔
57
}
28✔
58

59
// CreateRenderer is a private method to Fyne which links this widget to its renderer
60
func (s *Split) CreateRenderer() fyne.WidgetRenderer {
28✔
61
        s.BaseWidget.ExtendBaseWidget(s)
28✔
62
        d := newDivider(s)
28✔
63
        return &splitContainerRenderer{
28✔
64
                split:   s,
28✔
65
                divider: d,
28✔
66
                objects: []fyne.CanvasObject{s.Leading, d, s.Trailing},
28✔
67
        }
28✔
68
}
28✔
69

70
// ExtendBaseWidget is used by an extending widget to make use of BaseWidget functionality.
71
//
72
// Deprecated: Support for extending containers is being removed
73
func (s *Split) ExtendBaseWidget(wid fyne.Widget) {
×
74
        s.BaseWidget.ExtendBaseWidget(wid)
×
75
}
×
76

77
// SetOffset sets the offset (0.0 to 1.0) of the Split divider.
78
// 0.0 - Leading is min size, Trailing uses all remaining space.
79
// 0.5 - Leading & Trailing equally share the available space.
80
// 1.0 - Trailing is min size, Leading uses all remaining space.
81
func (s *Split) SetOffset(offset float64) {
25✔
82
        if s.Offset == offset {
27✔
83
                return
2✔
84
        }
2✔
85
        s.Offset = offset
23✔
86
        s.offsetUpdated = true
23✔
87
        s.Refresh()
23✔
88
}
89

90
var _ fyne.WidgetRenderer = (*splitContainerRenderer)(nil)
91

92
type splitContainerRenderer struct {
93
        split   *Split
94
        divider *divider
95
        objects []fyne.CanvasObject
96
}
97

98
func (*splitContainerRenderer) Destroy() {
4✔
99
}
4✔
100

101
func (r *splitContainerRenderer) Layout(size fyne.Size) {
57✔
102
        var dividerPos, leadingPos, trailingPos fyne.Position
57✔
103
        var dividerSize, leadingSize, trailingSize fyne.Size
57✔
104

57✔
105
        dividerVisible := r.split.Leading.Visible() && r.split.Trailing.Visible()
57✔
106
        if !r.split.Leading.Visible() {
59✔
107
                trailingPos = fyne.NewPos(0, 0)
2✔
108
                trailingSize = size
2✔
109
        } else if !r.split.Trailing.Visible() {
59✔
110
                leadingPos = fyne.NewPos(0, 0)
2✔
111
                leadingSize = size
2✔
112
        } else if dividerVisible {
108✔
113
                if r.split.Horizontal {
85✔
114
                        lw, tw := r.computeSplitLengths(size.Width, r.minLeadingWidth(), r.minTrailingWidth())
32✔
115
                        leadingPos.X = 0
32✔
116
                        leadingSize.Width = lw
32✔
117
                        leadingSize.Height = size.Height
32✔
118
                        dividerPos.X = lw
32✔
119
                        dividerSize.Width = dividerThickness(r.divider)
32✔
120
                        dividerSize.Height = size.Height
32✔
121
                        trailingPos.X = lw + dividerSize.Width
32✔
122
                        trailingSize.Width = tw
32✔
123
                        trailingSize.Height = size.Height
32✔
124
                } else {
53✔
125
                        lh, th := r.computeSplitLengths(size.Height, r.minLeadingHeight(), r.minTrailingHeight())
21✔
126
                        leadingPos.Y = 0
21✔
127
                        leadingSize.Width = size.Width
21✔
128
                        leadingSize.Height = lh
21✔
129
                        dividerPos.Y = lh
21✔
130
                        dividerSize.Width = size.Width
21✔
131
                        dividerSize.Height = dividerThickness(r.divider)
21✔
132
                        trailingPos.Y = lh + dividerSize.Height
21✔
133
                        trailingSize.Width = size.Width
21✔
134
                        trailingSize.Height = th
21✔
135
                }
21✔
136
        }
137

138
        r.divider.Move(dividerPos)
57✔
139
        r.divider.Resize(dividerSize)
57✔
140
        r.divider.Hidden = !dividerVisible
57✔
141

57✔
142
        r.split.Leading.Move(leadingPos)
57✔
143
        r.split.Leading.Resize(leadingSize)
57✔
144
        r.split.Trailing.Move(trailingPos)
57✔
145
        r.split.Trailing.Resize(trailingSize)
57✔
146
        canvas.Refresh(r.divider)
57✔
147
}
148

149
func (r *splitContainerRenderer) MinSize() fyne.Size {
9✔
150
        s := fyne.NewSize(0, 0)
9✔
151
        dividerVisible := r.split.Leading.Visible() && r.split.Trailing.Visible()
9✔
152
        for i, o := range r.objects {
36✔
153
                if (i == 1 /* divider */ && !dividerVisible) || (i != 1 && !o.Visible()) {
29✔
154
                        continue
2✔
155
                }
156
                minSize := o.MinSize()
25✔
157
                if r.split.Horizontal {
46✔
158
                        s.Width += minSize.Width
21✔
159
                        s.Height = fyne.Max(s.Height, minSize.Height)
21✔
160
                } else {
25✔
161
                        s.Width = fyne.Max(s.Width, minSize.Width)
4✔
162
                        s.Height += minSize.Height
4✔
163
                }
4✔
164
        }
165
        return s
9✔
166
}
167

168
func (r *splitContainerRenderer) Objects() []fyne.CanvasObject {
6✔
169
        return r.objects
6✔
170
}
6✔
171

172
func (r *splitContainerRenderer) Refresh() {
33✔
173
        if r.split.offsetUpdated {
56✔
174
                r.Layout(r.split.Size())
23✔
175
                r.split.offsetUpdated = false
23✔
176
                return
23✔
177
        }
23✔
178

179
        r.objects[0] = r.split.Leading
10✔
180
        // [1] is divider which doesn't change
10✔
181
        r.objects[2] = r.split.Trailing
10✔
182
        r.Layout(r.split.Size())
10✔
183

10✔
184
        r.split.Leading.Refresh()
10✔
185
        r.divider.Refresh()
10✔
186
        r.split.Trailing.Refresh()
10✔
187
        canvas.Refresh(r.split)
10✔
188
}
189

190
func (r *splitContainerRenderer) computeSplitLengths(total, lMin, tMin float32) (leading, trailing float32) {
53✔
191
        available := float64(total - dividerThickness(r.divider))
53✔
192
        if available <= 0 {
62✔
193
                return 0, 0
9✔
194
        }
9✔
195
        ld := float64(lMin)
44✔
196
        tr := float64(tMin)
44✔
197
        offset := r.split.Offset
44✔
198

44✔
199
        minOffset := ld / available
44✔
200
        maxOffset := 1 - tr/available
44✔
201
        if minOffset <= maxOffset {
86✔
202
                if offset < minOffset {
46✔
203
                        offset = minOffset
4✔
204
                }
4✔
205
                if offset > maxOffset {
46✔
206
                        offset = maxOffset
4✔
207
                }
4✔
208
        } else {
2✔
209
                offset = ld / (ld + tr)
2✔
210
        }
2✔
211

212
        ld = offset * available
44✔
213
        tr = available - ld
44✔
214
        return float32(ld), float32(tr)
44✔
215
}
216

217
func (r *splitContainerRenderer) minLeadingWidth() float32 {
32✔
218
        if r.split.Leading.Visible() {
64✔
219
                return r.split.Leading.MinSize().Width
32✔
220
        }
32✔
221
        return 0
×
222
}
223

224
func (r *splitContainerRenderer) minLeadingHeight() float32 {
21✔
225
        if r.split.Leading.Visible() {
42✔
226
                return r.split.Leading.MinSize().Height
21✔
227
        }
21✔
228
        return 0
×
229
}
230

231
func (r *splitContainerRenderer) minTrailingWidth() float32 {
32✔
232
        if r.split.Trailing.Visible() {
64✔
233
                return r.split.Trailing.MinSize().Width
32✔
234
        }
32✔
235
        return 0
×
236
}
237

238
func (r *splitContainerRenderer) minTrailingHeight() float32 {
21✔
239
        if r.split.Trailing.Visible() {
42✔
240
                return r.split.Trailing.MinSize().Height
21✔
241
        }
21✔
242
        return 0
×
243
}
244

245
// Declare conformity with interfaces
246
var (
247
        _ fyne.CanvasObject  = (*divider)(nil)
248
        _ fyne.Draggable     = (*divider)(nil)
249
        _ desktop.Cursorable = (*divider)(nil)
250
        _ desktop.Hoverable  = (*divider)(nil)
251
)
252

253
type divider struct {
254
        widget.BaseWidget
255
        split          *Split
256
        hovered        bool
257
        startDragOff   *fyne.Position
258
        currentDragPos fyne.Position
259
}
260

261
func newDivider(split *Split) *divider {
34✔
262
        d := &divider{
34✔
263
                split: split,
34✔
264
        }
34✔
265
        d.ExtendBaseWidget(d)
34✔
266
        return d
34✔
267
}
34✔
268

269
// CreateRenderer is a private method to Fyne which links this widget to its renderer
270
func (d *divider) CreateRenderer() fyne.WidgetRenderer {
31✔
271
        d.ExtendBaseWidget(d)
31✔
272
        th := d.Theme()
31✔
273
        v := fyne.CurrentApp().Settings().ThemeVariant()
31✔
274

31✔
275
        background := canvas.NewRectangle(th.Color(theme.ColorNameShadow, v))
31✔
276
        foreground := canvas.NewRectangle(th.Color(theme.ColorNameForeground, v))
31✔
277
        return &dividerRenderer{
31✔
278
                divider:    d,
31✔
279
                background: background,
31✔
280
                foreground: foreground,
31✔
281
                objects:    []fyne.CanvasObject{background, foreground},
31✔
282
        }
31✔
283
}
31✔
284

285
func (d *divider) Cursor() desktop.Cursor {
2✔
286
        if d.split.Horizontal {
3✔
287
                return desktop.HResizeCursor
1✔
288
        }
1✔
289
        return desktop.VResizeCursor
1✔
290
}
291

292
func (d *divider) DragEnd() {
8✔
293
        d.startDragOff = nil
8✔
294
}
8✔
295

296
func (d *divider) Dragged(e *fyne.DragEvent) {
12✔
297
        if d.startDragOff == nil {
20✔
298
                d.currentDragPos = d.Position().Add(e.Position)
8✔
299
                start := e.Position.Subtract(e.Dragged)
8✔
300
                d.startDragOff = &start
8✔
301
        } else {
12✔
302
                d.currentDragPos = d.currentDragPos.Add(e.Dragged)
4✔
303
        }
4✔
304

305
        x, y := d.currentDragPos.Components()
12✔
306
        var offset, leadingRatio, trailingRatio float64
12✔
307
        if d.split.Horizontal {
18✔
308
                widthFree := float64(d.split.Size().Width - dividerThickness(d))
6✔
309
                leadingRatio = float64(d.split.Leading.MinSize().Width) / widthFree
6✔
310
                trailingRatio = 1.0 - (float64(d.split.Trailing.MinSize().Width) / widthFree)
6✔
311
                offset = float64(x-d.startDragOff.X) / widthFree
6✔
312
        } else {
12✔
313
                heightFree := float64(d.split.Size().Height - dividerThickness(d))
6✔
314
                leadingRatio = float64(d.split.Leading.MinSize().Height) / heightFree
6✔
315
                trailingRatio = 1.0 - (float64(d.split.Trailing.MinSize().Height) / heightFree)
6✔
316
                offset = float64(y-d.startDragOff.Y) / heightFree
6✔
317
        }
6✔
318

319
        if offset < leadingRatio {
12✔
320
                offset = leadingRatio
×
321
        }
×
322
        if offset > trailingRatio {
16✔
323
                offset = trailingRatio
4✔
324
        }
4✔
325
        d.split.SetOffset(offset)
12✔
326
}
327

328
func (d *divider) MouseIn(*desktop.MouseEvent) {
3✔
329
        d.hovered = true
3✔
330
        d.Refresh()
3✔
331
}
3✔
332

NEW
333
func (*divider) MouseMoved(*desktop.MouseEvent) {}
×
334

335
func (d *divider) MouseOut() {
2✔
336
        d.hovered = false
2✔
337
        d.Refresh()
2✔
338
}
2✔
339

340
var _ fyne.WidgetRenderer = (*dividerRenderer)(nil)
341

342
type dividerRenderer struct {
343
        divider    *divider
344
        background *canvas.Rectangle
345
        foreground *canvas.Rectangle
346
        objects    []fyne.CanvasObject
347
}
348

NEW
349
func (*dividerRenderer) Destroy() {
×
350
}
×
351

352
func (r *dividerRenderer) Layout(size fyne.Size) {
52✔
353
        r.background.Resize(size)
52✔
354
        var x, y, w, h float32
52✔
355
        if r.divider.split.Horizontal {
87✔
356
                x = (dividerThickness(r.divider) - handleThickness(r.divider)) / 2
35✔
357
                y = (size.Height - handleLength(r.divider)) / 2
35✔
358
                w = handleThickness(r.divider)
35✔
359
                h = handleLength(r.divider)
35✔
360
        } else {
52✔
361
                x = (size.Width - handleLength(r.divider)) / 2
17✔
362
                y = (dividerThickness(r.divider) - handleThickness(r.divider)) / 2
17✔
363
                w = handleLength(r.divider)
17✔
364
                h = handleThickness(r.divider)
17✔
365
        }
17✔
366
        r.foreground.Move(fyne.NewPos(x, y))
52✔
367
        r.foreground.Resize(fyne.NewSize(w, h))
52✔
368
}
369

370
func (r *dividerRenderer) MinSize() fyne.Size {
10✔
371
        if r.divider.split.Horizontal {
18✔
372
                return fyne.NewSize(dividerThickness(r.divider), dividerLength(r.divider))
8✔
373
        }
8✔
374
        return fyne.NewSize(dividerLength(r.divider), dividerThickness(r.divider))
2✔
375
}
376

377
func (r *dividerRenderer) Objects() []fyne.CanvasObject {
6✔
378
        return r.objects
6✔
379
}
6✔
380

381
func (r *dividerRenderer) Refresh() {
16✔
382
        th := r.divider.Theme()
16✔
383
        v := fyne.CurrentApp().Settings().ThemeVariant()
16✔
384

16✔
385
        if r.divider.hovered {
20✔
386
                r.background.FillColor = th.Color(theme.ColorNameHover, v)
4✔
387
        } else {
16✔
388
                r.background.FillColor = th.Color(theme.ColorNameShadow, v)
12✔
389
        }
12✔
390
        r.background.Refresh()
16✔
391
        r.foreground.FillColor = th.Color(theme.ColorNameForeground, v)
16✔
392
        r.foreground.Refresh()
16✔
393
        r.Layout(r.divider.Size())
16✔
394
}
395

396
func dividerTheme(d *divider) fyne.Theme {
436✔
397
        if d == nil {
474✔
398
                return theme.Current()
38✔
399
        }
38✔
400

401
        return d.Theme()
398✔
402
}
403

404
func dividerThickness(d *divider) float32 {
213✔
405
        th := dividerTheme(d)
213✔
406
        pad := th.Size(theme.SizeNameSplitThickness)
213✔
407
        if pad == 0 {
213✔
408
                pad = splitDefaultThickness
×
409
        }
×
410
        return pad
213✔
411
}
412

413
func dividerLength(d *divider) float32 {
15✔
414
        th := dividerTheme(d)
15✔
415
        return th.Size(theme.SizeNamePadding) * 6 //revive:disable-line:add-constant
15✔
416
}
15✔
417

418
func handleThickness(d *divider) float32 {
104✔
419
        th := dividerTheme(d)
104✔
420
        return th.Size(theme.SizeNameSeparatorThickness)
104✔
421
}
104✔
422

423
func handleLength(d *divider) float32 {
104✔
424
        th := dividerTheme(d)
104✔
425
        return th.Size(theme.SizeNamePadding) * 4
104✔
426
}
104✔
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE TRIAL · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc