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

fyne-io / fyne / 13153257891

05 Feb 2025 08:32AM UTC coverage: 62.656% (+0.04%) from 62.619%
13153257891

push

github

web-flow
Merge pull request #5490 from Jacalz/binding-trigger-faster

15 of 15 new or added lines in 1 file covered. (100.0%)

64 existing lines in 2 files now uncovered.

24919 of 39771 relevant lines covered (62.66%)

839.64 hits per line

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

96.69
/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 {
15✔
35
        return newSplitContainer(true, leading, trailing)
15✔
36
}
15✔
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 {
11✔
43
        return newSplitContainer(false, top, bottom)
11✔
44
}
11✔
45

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

57
// CreateRenderer is a private method to Fyne which links this widget to its renderer
58
func (s *Split) CreateRenderer() fyne.WidgetRenderer {
26✔
59
        s.BaseWidget.ExtendBaseWidget(s)
26✔
60
        d := newDivider(s)
26✔
61
        return &splitContainerRenderer{
26✔
62
                split:   s,
26✔
63
                divider: d,
26✔
64
                objects: []fyne.CanvasObject{s.Leading, d, s.Trailing},
26✔
65
        }
26✔
66
}
26✔
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
UNCOV
71
func (s *Split) ExtendBaseWidget(wid fyne.Widget) {
×
UNCOV
72
        s.BaseWidget.ExtendBaseWidget(wid)
×
UNCOV
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) {
29✔
80
        if s.Offset == offset {
31✔
81
                return
2✔
82
        }
2✔
83
        s.Offset = offset
27✔
84
        s.offsetUpdated = true
27✔
85
        s.Refresh()
27✔
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) {
58✔
100
        var dividerPos, leadingPos, trailingPos fyne.Position
58✔
101
        var dividerSize, leadingSize, trailingSize fyne.Size
58✔
102

58✔
103
        if r.split.Horizontal {
93✔
104
                lw, tw := r.computeSplitLengths(size.Width, r.minLeadingWidth(), r.minTrailingWidth())
35✔
105
                leadingPos.X = 0
35✔
106
                leadingSize.Width = lw
35✔
107
                leadingSize.Height = size.Height
35✔
108
                dividerPos.X = lw
35✔
109
                dividerSize.Width = dividerThickness(r.divider)
35✔
110
                dividerSize.Height = size.Height
35✔
111
                trailingPos.X = lw + dividerSize.Width
35✔
112
                trailingSize.Width = tw
35✔
113
                trailingSize.Height = size.Height
35✔
114
        } else {
58✔
115
                lh, th := r.computeSplitLengths(size.Height, r.minLeadingHeight(), r.minTrailingHeight())
23✔
116
                leadingPos.Y = 0
23✔
117
                leadingSize.Width = size.Width
23✔
118
                leadingSize.Height = lh
23✔
119
                dividerPos.Y = lh
23✔
120
                dividerSize.Width = size.Width
23✔
121
                dividerSize.Height = dividerThickness(r.divider)
23✔
122
                trailingPos.Y = lh + dividerSize.Height
23✔
123
                trailingSize.Width = size.Width
23✔
124
                trailingSize.Height = th
23✔
125
        }
23✔
126

127
        r.divider.Move(dividerPos)
58✔
128
        r.divider.Resize(dividerSize)
58✔
129
        r.split.Leading.Move(leadingPos)
58✔
130
        r.split.Leading.Resize(leadingSize)
58✔
131
        r.split.Trailing.Move(trailingPos)
58✔
132
        r.split.Trailing.Resize(trailingSize)
58✔
133
        canvas.Refresh(r.divider)
58✔
134
}
135

136
func (r *splitContainerRenderer) MinSize() fyne.Size {
8✔
137
        s := fyne.NewSize(0, 0)
8✔
138
        for _, o := range r.objects {
32✔
139
                min := o.MinSize()
24✔
140
                if r.split.Horizontal {
45✔
141
                        s.Width += min.Width
21✔
142
                        s.Height = fyne.Max(s.Height, min.Height)
21✔
143
                } else {
24✔
144
                        s.Width = fyne.Max(s.Width, min.Width)
3✔
145
                        s.Height += min.Height
3✔
146
                }
3✔
147
        }
148
        return s
8✔
149
}
150

151
func (r *splitContainerRenderer) Objects() []fyne.CanvasObject {
6✔
152
        return r.objects
6✔
153
}
6✔
154

155
func (r *splitContainerRenderer) Refresh() {
32✔
156
        if r.split.offsetUpdated {
59✔
157
                r.Layout(r.split.Size())
27✔
158
                r.split.offsetUpdated = false
27✔
159
                return
27✔
160
        }
27✔
161

162
        r.objects[0] = r.split.Leading
5✔
163
        // [1] is divider which doesn't change
5✔
164
        r.objects[2] = r.split.Trailing
5✔
165
        r.Layout(r.split.Size())
5✔
166

5✔
167
        r.split.Leading.Refresh()
5✔
168
        r.divider.Refresh()
5✔
169
        r.split.Trailing.Refresh()
5✔
170
        canvas.Refresh(r.split)
5✔
171
}
172

173
func (r *splitContainerRenderer) computeSplitLengths(total, lMin, tMin float32) (float32, float32) {
58✔
174
        available := float64(total - dividerThickness(r.divider))
58✔
175
        if available <= 0 {
66✔
176
                return 0, 0
8✔
177
        }
8✔
178
        ld := float64(lMin)
50✔
179
        tr := float64(tMin)
50✔
180
        offset := r.split.Offset
50✔
181

50✔
182
        min := ld / available
50✔
183
        max := 1 - tr/available
50✔
184
        if min <= max {
98✔
185
                if offset < min {
52✔
186
                        offset = min
4✔
187
                }
4✔
188
                if offset > max {
52✔
189
                        offset = max
4✔
190
                }
4✔
191
        } else {
2✔
192
                offset = ld / (ld + tr)
2✔
193
        }
2✔
194

195
        ld = offset * available
50✔
196
        tr = available - ld
50✔
197
        return float32(ld), float32(tr)
50✔
198
}
199

200
func (r *splitContainerRenderer) minLeadingWidth() float32 {
35✔
201
        if r.split.Leading.Visible() {
67✔
202
                return r.split.Leading.MinSize().Width
32✔
203
        }
32✔
204
        return 0
3✔
205
}
206

207
func (r *splitContainerRenderer) minLeadingHeight() float32 {
23✔
208
        if r.split.Leading.Visible() {
42✔
209
                return r.split.Leading.MinSize().Height
19✔
210
        }
19✔
211
        return 0
4✔
212
}
213

214
func (r *splitContainerRenderer) minTrailingWidth() float32 {
35✔
215
        if r.split.Trailing.Visible() {
69✔
216
                return r.split.Trailing.MinSize().Width
34✔
217
        }
34✔
218
        return 0
1✔
219
}
220

221
func (r *splitContainerRenderer) minTrailingHeight() float32 {
23✔
222
        if r.split.Trailing.Visible() {
42✔
223
                return r.split.Trailing.MinSize().Height
19✔
224
        }
19✔
225
        return 0
4✔
226
}
227

228
// Declare conformity with interfaces
229
var _ fyne.CanvasObject = (*divider)(nil)
230
var _ fyne.Draggable = (*divider)(nil)
231
var _ desktop.Cursorable = (*divider)(nil)
232
var _ desktop.Hoverable = (*divider)(nil)
233

234
type divider struct {
235
        widget.BaseWidget
236
        split          *Split
237
        hovered        bool
238
        startDragOff   *fyne.Position
239
        currentDragPos fyne.Position
240
}
241

242
func newDivider(split *Split) *divider {
32✔
243
        d := &divider{
32✔
244
                split: split,
32✔
245
        }
32✔
246
        d.ExtendBaseWidget(d)
32✔
247
        return d
32✔
248
}
32✔
249

250
// CreateRenderer is a private method to Fyne which links this widget to its renderer
251
func (d *divider) CreateRenderer() fyne.WidgetRenderer {
28✔
252
        d.ExtendBaseWidget(d)
28✔
253
        th := d.Theme()
28✔
254
        v := fyne.CurrentApp().Settings().ThemeVariant()
28✔
255

28✔
256
        background := canvas.NewRectangle(th.Color(theme.ColorNameShadow, v))
28✔
257
        foreground := canvas.NewRectangle(th.Color(theme.ColorNameForeground, v))
28✔
258
        return &dividerRenderer{
28✔
259
                divider:    d,
28✔
260
                background: background,
28✔
261
                foreground: foreground,
28✔
262
                objects:    []fyne.CanvasObject{background, foreground},
28✔
263
        }
28✔
264
}
28✔
265

266
func (d *divider) Cursor() desktop.Cursor {
2✔
267
        if d.split.Horizontal {
3✔
268
                return desktop.HResizeCursor
1✔
269
        }
1✔
270
        return desktop.VResizeCursor
1✔
271
}
272

273
func (d *divider) DragEnd() {
8✔
274
        d.startDragOff = nil
8✔
275
}
8✔
276

277
func (d *divider) Dragged(e *fyne.DragEvent) {
12✔
278
        if d.startDragOff == nil {
20✔
279
                d.currentDragPos = d.Position().Add(e.Position)
8✔
280
                start := e.Position.Subtract(e.Dragged)
8✔
281
                d.startDragOff = &start
8✔
282
        } else {
12✔
283
                d.currentDragPos = d.currentDragPos.Add(e.Dragged)
4✔
284
        }
4✔
285

286
        x, y := d.currentDragPos.Components()
12✔
287
        var offset, leadingRatio, trailingRatio float64
12✔
288
        if d.split.Horizontal {
18✔
289
                widthFree := float64(d.split.Size().Width - dividerThickness(d))
6✔
290
                leadingRatio = float64(d.split.Leading.MinSize().Width) / widthFree
6✔
291
                trailingRatio = 1. - (float64(d.split.Trailing.MinSize().Width) / widthFree)
6✔
292
                offset = float64(x-d.startDragOff.X) / widthFree
6✔
293
        } else {
12✔
294
                heightFree := float64(d.split.Size().Height - dividerThickness(d))
6✔
295
                leadingRatio = float64(d.split.Leading.MinSize().Height) / heightFree
6✔
296
                trailingRatio = 1. - (float64(d.split.Trailing.MinSize().Height) / heightFree)
6✔
297
                offset = float64(y-d.startDragOff.Y) / heightFree
6✔
298
        }
6✔
299

300
        if offset < leadingRatio {
12✔
UNCOV
301
                offset = leadingRatio
×
302
        }
×
303
        if offset > trailingRatio {
16✔
304
                offset = trailingRatio
4✔
305
        }
4✔
306
        d.split.SetOffset(offset)
12✔
307
}
308

309
func (d *divider) MouseIn(event *desktop.MouseEvent) {
2✔
310
        d.hovered = true
2✔
311
        d.split.Refresh()
2✔
312
}
2✔
313

UNCOV
314
func (d *divider) MouseMoved(event *desktop.MouseEvent) {}
×
315

316
func (d *divider) MouseOut() {
2✔
317
        d.hovered = false
2✔
318
        d.split.Refresh()
2✔
319
}
2✔
320

321
var _ fyne.WidgetRenderer = (*dividerRenderer)(nil)
322

323
type dividerRenderer struct {
324
        divider    *divider
325
        background *canvas.Rectangle
326
        foreground *canvas.Rectangle
327
        objects    []fyne.CanvasObject
328
}
329

UNCOV
330
func (r *dividerRenderer) Destroy() {
×
UNCOV
331
}
×
332

333
func (r *dividerRenderer) Layout(size fyne.Size) {
39✔
334
        r.background.Resize(size)
39✔
335
        var x, y, w, h float32
39✔
336
        if r.divider.split.Horizontal {
67✔
337
                x = (dividerThickness(r.divider) - handleThickness(r.divider)) / 2
28✔
338
                y = (size.Height - handleLength(r.divider)) / 2
28✔
339
                w = handleThickness(r.divider)
28✔
340
                h = handleLength(r.divider)
28✔
341
        } else {
39✔
342
                x = (size.Width - handleLength(r.divider)) / 2
11✔
343
                y = (dividerThickness(r.divider) - handleThickness(r.divider)) / 2
11✔
344
                w = handleLength(r.divider)
11✔
345
                h = handleThickness(r.divider)
11✔
346
        }
11✔
347
        r.foreground.Move(fyne.NewPos(x, y))
39✔
348
        r.foreground.Resize(fyne.NewSize(w, h))
39✔
349
}
350

351
func (r *dividerRenderer) MinSize() fyne.Size {
10✔
352
        if r.divider.split.Horizontal {
18✔
353
                return fyne.NewSize(dividerThickness(r.divider), dividerLength(r.divider))
8✔
354
        }
8✔
355
        return fyne.NewSize(dividerLength(r.divider), dividerThickness(r.divider))
2✔
356
}
357

358
func (r *dividerRenderer) Objects() []fyne.CanvasObject {
6✔
359
        return r.objects
6✔
360
}
6✔
361

362
func (r *dividerRenderer) Refresh() {
6✔
363
        th := r.divider.Theme()
6✔
364
        v := fyne.CurrentApp().Settings().ThemeVariant()
6✔
365

6✔
366
        if r.divider.hovered {
6✔
UNCOV
367
                r.background.FillColor = th.Color(theme.ColorNameHover, v)
×
368
        } else {
6✔
369
                r.background.FillColor = th.Color(theme.ColorNameShadow, v)
6✔
370
        }
6✔
371
        r.background.Refresh()
6✔
372
        r.foreground.FillColor = th.Color(theme.ColorNameForeground, v)
6✔
373
        r.foreground.Refresh()
6✔
374
        r.Layout(r.divider.Size())
6✔
375
}
376

377
func dividerTheme(d *divider) fyne.Theme {
381✔
378
        if d == nil {
419✔
379
                return theme.Current()
38✔
380
        }
38✔
381

382
        return d.Theme()
343✔
383
}
384

385
func dividerThickness(d *divider) float32 {
210✔
386
        th := dividerTheme(d)
210✔
387
        return th.Size(theme.SizeNamePadding) * 2
210✔
388
}
210✔
389

390
func dividerLength(d *divider) float32 {
15✔
391
        th := dividerTheme(d)
15✔
392
        return th.Size(theme.SizeNamePadding) * 6
15✔
393
}
15✔
394

395
func handleThickness(d *divider) float32 {
78✔
396
        th := dividerTheme(d)
78✔
397
        return th.Size(theme.SizeNamePadding) / 2
78✔
398

78✔
399
}
78✔
400

401
func handleLength(d *divider) float32 {
78✔
402
        th := dividerTheme(d)
78✔
403
        return th.Size(theme.SizeNamePadding) * 4
78✔
404
}
78✔
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