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

fyne-io / fyne / 18630721232

19 Oct 2025 12:57PM UTC coverage: 61.075% (-1.3%) from 62.41%
18630721232

push

github

andydotxyz
Fix old instructions for tools install

25630 of 41965 relevant lines covered (61.07%)

693.73 hits per line

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

95.77
/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
// Split defines a container whose size is split between two children.
15
//
16
// Since: 1.4
17
type Split struct {
18
        widget.BaseWidget
19
        Offset     float64
20
        Horizontal bool
21
        Leading    fyne.CanvasObject
22
        Trailing   fyne.CanvasObject
23

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

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

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

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

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

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

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

88
var _ fyne.WidgetRenderer = (*splitContainerRenderer)(nil)
89

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

96
func (r *splitContainerRenderer) Destroy() {
4✔
97
}
4✔
98

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

331
func (d *divider) MouseMoved(event *desktop.MouseEvent) {}
×
332

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

338
var _ fyne.WidgetRenderer = (*dividerRenderer)(nil)
339

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

347
func (r *dividerRenderer) Destroy() {
×
348
}
×
349

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

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

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

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

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

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

399
        return d.Theme()
398✔
400
}
401

402
func dividerThickness(d *divider) float32 {
213✔
403
        th := dividerTheme(d)
213✔
404
        return th.Size(theme.SizeNamePadding) * 2
213✔
405
}
213✔
406

407
func dividerLength(d *divider) float32 {
15✔
408
        th := dividerTheme(d)
15✔
409
        return th.Size(theme.SizeNamePadding) * 6
15✔
410
}
15✔
411

412
func handleThickness(d *divider) float32 {
104✔
413
        th := dividerTheme(d)
104✔
414
        return th.Size(theme.SizeNamePadding) / 2
104✔
415
}
104✔
416

417
func handleLength(d *divider) float32 {
104✔
418
        th := dividerTheme(d)
104✔
419
        return th.Size(theme.SizeNamePadding) * 4
104✔
420
}
104✔
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