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

fyne-io / fyne / 12255443917

10 Dec 2024 11:46AM UTC coverage: 60.02% (+0.05%) from 59.974%
12255443917

Pull #5305

github

roffe
feedback
Pull Request #5305: allow customization of button location on InnerWindow

25 of 27 new or added lines in 1 file covered. (92.59%)

9 existing lines in 1 file now uncovered.

25622 of 42689 relevant lines covered (60.02%)

698.17 hits per line

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

89.22
/container/innerwindow.go
1
package container
2

3
import (
4
        "runtime"
5

6
        "fyne.io/fyne/v2"
7
        "fyne.io/fyne/v2/canvas"
8
        intWidget "fyne.io/fyne/v2/internal/widget"
9
        "fyne.io/fyne/v2/layout"
10
        "fyne.io/fyne/v2/theme"
11
        "fyne.io/fyne/v2/widget"
12
)
13

14
var _ fyne.Widget = (*InnerWindow)(nil)
15

16
// InnerWindow defines a container that wraps content in a window border - that can then be placed inside
17
// a regular container/canvas.
18
//
19
// Since: 2.5
20
type InnerWindow struct {
21
        widget.BaseWidget
22

23
        // ButtonAlignment specifies where the window buttons (close, minimize, maximize) should be placed.
24
        // The default is widget.ButtonAlignCenter which will auto select based on the OS.
25
        //        - On Windows and Linux this will be `widget.ButtonAlignTrailing`
26
        //        - On Darwin this will be `widget.ButtonAlignLeading`
27
        //
28
        // Since: 2.6
29
        ButtonAlignment                                     widget.ButtonAlign
30
        CloseIntercept                                      func()                `json:"-"`
31
        OnDragged, OnResized                                func(*fyne.DragEvent) `json:"-"`
32
        OnMinimized, OnMaximized, OnTappedBar, OnTappedIcon func()                `json:"-"`
33
        Icon                                                fyne.Resource
34

35
        title   string
36
        content *fyne.Container
37
}
38

39
// NewInnerWindow creates a new window border around the given `content`, displaying the `title` along the top.
40
// This will behave like a normal contain and will probably want to be added to a `MultipleWindows` parent.
41
//
42
// Since: 2.5
43
func NewInnerWindow(title string, content fyne.CanvasObject) *InnerWindow {
12✔
44
        w := &InnerWindow{
12✔
45
                title:           title,
12✔
46
                content:         NewPadded(content),
12✔
47
                ButtonAlignment: widget.ButtonAlignCenter,
12✔
48
        }
12✔
49
        w.ExtendBaseWidget(w)
12✔
50
        return w
12✔
51
}
12✔
52

53
func (w *InnerWindow) Close() {
2✔
54
        w.Hide()
2✔
55
}
2✔
56

57
func (w *InnerWindow) CreateRenderer() fyne.WidgetRenderer {
11✔
58
        w.ExtendBaseWidget(w)
11✔
59

11✔
60
        min := &widget.Button{Icon: theme.WindowMinimizeIcon(), Importance: widget.LowImportance, OnTapped: w.OnMinimized}
11✔
61
        if w.OnMinimized == nil {
22✔
62
                min.Disable()
11✔
63
        }
11✔
64
        max := &widget.Button{Icon: theme.WindowMaximizeIcon(), Importance: widget.LowImportance, OnTapped: w.OnMaximized}
11✔
65
        if w.OnMaximized == nil {
22✔
66
                max.Disable()
11✔
67
        }
11✔
68

69
        close := &widget.Button{Icon: theme.WindowCloseIcon(), Importance: widget.DangerImportance, OnTapped: func() {
15✔
70
                if f := w.CloseIntercept; f != nil {
6✔
71
                        f()
2✔
72
                } else {
4✔
73
                        w.Close()
2✔
74
                }
2✔
75
        }}
76
        close.Resize(fyne.NewSize(15, 15))
11✔
77

11✔
78
        var icon fyne.CanvasObject
11✔
79
        if w.Icon != nil {
13✔
80
                icon = &widget.Button{Icon: w.Icon, Importance: widget.LowImportance, OnTapped: func() {
4✔
81
                        if f := w.OnTappedIcon; f != nil {
4✔
82
                                f()
2✔
83
                        }
2✔
84
                }}
85
                if w.OnTappedIcon == nil {
2✔
NEW
86
                        icon.(*widget.Button).Disable()
×
NEW
87
                }
×
88
        }
89

90
        title := newDraggableLabel(w.title, w)
11✔
91
        title.Truncation = fyne.TextTruncateEllipsis
11✔
92

11✔
93
        var buttons *fyne.Container
11✔
94
        var bar *fyne.Container
11✔
95

11✔
96
        isLeading := w.ButtonAlignment == widget.ButtonAlignLeading || (w.ButtonAlignment == widget.ButtonAlignCenter && runtime.GOOS == "darwin")
11✔
97

11✔
98
        if isLeading {
13✔
99
                // Left side (darwin default or explicit left alignment)
2✔
100
                buttons = NewHBox(close, min, max)
2✔
101
                bar = NewBorder(nil, nil, buttons, icon, title)
2✔
102
        } else {
11✔
103
                // Right side (Windows/Linux default and explicit right alignment)
9✔
104
                buttons = NewHBox(min, max, close)
9✔
105
                bar = NewBorder(nil, nil, icon, buttons, title)
9✔
106
        }
9✔
107

108
        th := w.Theme()
11✔
109
        v := fyne.CurrentApp().Settings().ThemeVariant()
11✔
110

11✔
111
        bg := canvas.NewRectangle(th.Color(theme.ColorNameOverlayBackground, v))
11✔
112
        contentBG := canvas.NewRectangle(th.Color(theme.ColorNameBackground, v))
11✔
113
        corner := newDraggableCorner(w)
11✔
114

11✔
115
        objects := []fyne.CanvasObject{bg, contentBG, bar, w.content, corner}
11✔
116
        return &innerWindowRenderer{ShadowingRenderer: intWidget.NewShadowingRenderer(objects, intWidget.DialogLevel),
11✔
117
                win: w, bar: bar, bg: bg, corner: corner, contentBG: contentBG}
11✔
118
}
119

120
func (w *InnerWindow) SetContent(obj fyne.CanvasObject) {
1✔
121
        w.content.Objects[0] = obj
1✔
122

1✔
123
        w.content.Refresh()
1✔
124
}
1✔
125

126
func (w *InnerWindow) SetPadded(pad bool) {
2✔
127
        if pad {
3✔
128
                w.content.Layout = layout.NewPaddedLayout()
1✔
129
        } else {
2✔
130
                w.content.Layout = layout.NewStackLayout()
1✔
131
        }
1✔
132
        w.content.Refresh()
2✔
133
}
134

135
// Title returns the current title of the window
136
//
137
// Since: 2.6
138
func (w *InnerWindow) Title() string {
1✔
139
        return w.title
1✔
140
}
1✔
141

142
func (w *InnerWindow) SetTitle(title string) {
2✔
143
        w.title = title
2✔
144
        w.Refresh()
2✔
145
}
2✔
146

147
var _ fyne.WidgetRenderer = (*innerWindowRenderer)(nil)
148

149
type innerWindowRenderer struct {
150
        *intWidget.ShadowingRenderer
151

152
        win           *InnerWindow
153
        bar           *fyne.Container
154
        bg, contentBG *canvas.Rectangle
155
        corner        fyne.CanvasObject
156
}
157

158
func (i *innerWindowRenderer) Layout(size fyne.Size) {
13✔
159
        th := i.win.Theme()
13✔
160
        pad := th.Size(theme.SizeNamePadding)
13✔
161

13✔
162
        pos := fyne.NewSquareOffsetPos(pad / 2)
13✔
163
        size = size.Subtract(fyne.NewSquareSize(pad))
13✔
164
        i.LayoutShadow(size, pos)
13✔
165

13✔
166
        i.bg.Move(pos)
13✔
167
        i.bg.Resize(size)
13✔
168

13✔
169
        barHeight := i.bar.MinSize().Height
13✔
170
        i.bar.Move(pos.AddXY(pad, 0))
13✔
171
        i.bar.Resize(fyne.NewSize(size.Width-pad*2, barHeight))
13✔
172

13✔
173
        innerPos := pos.AddXY(pad, barHeight)
13✔
174
        innerSize := fyne.NewSize(size.Width-pad*2, size.Height-pad-barHeight)
13✔
175
        i.contentBG.Move(innerPos)
13✔
176
        i.contentBG.Resize(innerSize)
13✔
177
        i.win.content.Move(innerPos)
13✔
178
        i.win.content.Resize(innerSize)
13✔
179

13✔
180
        cornerSize := i.corner.MinSize()
13✔
181
        i.corner.Move(pos.Add(size).Subtract(cornerSize).AddXY(1, 1))
13✔
182
        i.corner.Resize(cornerSize)
13✔
183
}
13✔
184

185
func (i *innerWindowRenderer) MinSize() fyne.Size {
14✔
186
        th := i.win.Theme()
14✔
187
        pad := th.Size(theme.SizeNamePadding)
14✔
188
        contentMin := i.win.content.MinSize()
14✔
189
        barMin := i.bar.MinSize()
14✔
190

14✔
191
        innerWidth := fyne.Max(barMin.Width, contentMin.Width)
14✔
192

14✔
193
        return fyne.NewSize(innerWidth+pad*2, contentMin.Height+pad+barMin.Height).Add(fyne.NewSquareSize(pad))
14✔
194
}
14✔
195

196
func (i *innerWindowRenderer) Refresh() {
5✔
197
        th := i.win.Theme()
5✔
198
        v := fyne.CurrentApp().Settings().ThemeVariant()
5✔
199
        i.bg.FillColor = th.Color(theme.ColorNameOverlayBackground, v)
5✔
200
        i.bg.Refresh()
5✔
201
        i.contentBG.FillColor = th.Color(theme.ColorNameBackground, v)
5✔
202
        i.contentBG.Refresh()
5✔
203
        i.bar.Refresh()
5✔
204

5✔
205
        title := i.bar.Objects[0].(*draggableLabel)
5✔
206
        title.SetText(i.win.title)
5✔
207
        i.ShadowingRenderer.RefreshShadow()
5✔
208
}
5✔
209

210
type draggableLabel struct {
211
        widget.Label
212
        win *InnerWindow
213
}
214

215
func newDraggableLabel(title string, win *InnerWindow) *draggableLabel {
11✔
216
        d := &draggableLabel{win: win}
11✔
217
        d.ExtendBaseWidget(d)
11✔
218
        d.Text = title
11✔
219
        return d
11✔
220
}
11✔
221

222
func (d *draggableLabel) Dragged(ev *fyne.DragEvent) {
×
UNCOV
223
        if f := d.win.OnDragged; f != nil {
×
UNCOV
224
                f(ev)
×
225
        }
×
226
}
227

228
func (d *draggableLabel) DragEnd() {
×
229
}
×
230

231
func (d *draggableLabel) Tapped(ev *fyne.PointEvent) {
×
UNCOV
232
        if f := d.win.OnTappedBar; f != nil {
×
UNCOV
233
                f()
×
UNCOV
234
        }
×
235
}
236

237
type draggableCorner struct {
238
        widget.BaseWidget
239
        win *InnerWindow
240
}
241

242
func newDraggableCorner(w *InnerWindow) *draggableCorner {
11✔
243
        d := &draggableCorner{win: w}
11✔
244
        d.ExtendBaseWidget(d)
11✔
245
        return d
11✔
246
}
11✔
247

248
func (c *draggableCorner) CreateRenderer() fyne.WidgetRenderer {
5✔
249
        prop := canvas.NewImageFromResource(fyne.CurrentApp().Settings().Theme().Icon(theme.IconNameDragCornerIndicator))
5✔
250
        prop.SetMinSize(fyne.NewSquareSize(16))
5✔
251
        return widget.NewSimpleRenderer(prop)
5✔
252
}
5✔
253

254
func (c *draggableCorner) Dragged(ev *fyne.DragEvent) {
×
UNCOV
255
        if f := c.win.OnResized; f != nil {
×
UNCOV
256
                c.win.OnResized(ev)
×
257
        }
×
258
}
259

UNCOV
260
func (c *draggableCorner) DragEnd() {
×
UNCOV
261
}
×
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