-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathwidgets.go
More file actions
221 lines (200 loc) · 5.84 KB
/
widgets.go
File metadata and controls
221 lines (200 loc) · 5.84 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
package main
import (
"image"
"image/color"
"gioui.org/io/event"
"gioui.org/io/key"
"gioui.org/io/pointer"
"gioui.org/layout"
"gioui.org/op/clip"
"gioui.org/op/paint"
"gioui.org/unit"
"gioui.org/widget"
)
const maxLineWidth = 10 * 1024
// FixedWidth constrains the widget to a fixed pixel width.
func FixedWidth(gtx layout.Context, width int, w layout.Widget) layout.Dimensions {
gtx.Constraints.Min.X = width
gtx.Constraints.Max.X = width
return w(gtx)
}
// ScrollByKey adjusts pos based on a keyboard event name.
// It returns true if the position changed.
func ScrollByKey(pos *layout.Position, name key.Name, totalItems int) bool {
before := *pos
switch name {
case key.NameSpace, key.NamePageDown:
pos.First += pos.Count
case key.NamePageUp:
pos.First -= pos.Count
case key.NameHome:
*pos = layout.Position{}
case key.NameEnd:
pos.First = totalItems - pos.Count
case key.NameDownArrow:
pos.First += 3
case key.NameUpArrow:
pos.First -= 3
default:
return false
}
pos.First = max(0, min(pos.First, max(0, totalItems-1)))
return *pos != before
}
// FocusBorderStyle implements styling for a focused widget.
type FocusBorderStyle struct {
Focused bool
BorderWidth unit.Dp
Color color.NRGBA
}
// FocusBorder creates a focus border style for a focused widget.
func FocusBorder(th *Theme, focused bool) FocusBorderStyle {
return FocusBorderStyle{
Focused: focused,
BorderWidth: unit.Dp(2),
Color: th.ContrastBg,
}
}
// Layout draws the border and the inner widget.
func (focus FocusBorderStyle) Layout(gtx layout.Context, w layout.Widget) layout.Dimensions {
inset := layout.UniformInset(focus.BorderWidth)
if !focus.Focused {
return inset.Layout(gtx, w)
}
return widget.Border{
Color: focus.Color,
Width: focus.BorderWidth,
}.Layout(gtx, func(gtx layout.Context) layout.Dimensions {
return inset.Layout(gtx, w)
})
}
// VerticalLine draws a vertical line divider.
type VerticalLine struct {
Width unit.Dp
Color color.NRGBA
}
// Layout draws the vertical line.
func (line VerticalLine) Layout(gtx layout.Context) layout.Dimensions {
size := image.Point{
X: gtx.Metric.Dp(line.Width),
Y: gtx.Constraints.Min.Y,
}
paint.FillShape(gtx.Ops, line.Color, clip.Rect{Max: size}.Op())
return layout.Dimensions{Size: size}
}
// HorizontalLine draws a horizontal line divider.
type HorizontalLine struct {
Height unit.Dp
Color color.NRGBA
}
// Layout draws the horizontal line.
func (line HorizontalLine) Layout(gtx layout.Context) layout.Dimensions {
size := image.Point{
X: gtx.Constraints.Min.X,
Y: gtx.Metric.Dp(line.Height),
}
paint.FillShape(gtx.Ops, line.Color, clip.Rect{Max: size}.Op())
return layout.Dimensions{Size: size}
}
// Splitter lays out two widgets separated by a draggable divider.
// Ratio is the fraction [0,1] of space given to the first widget.
type Splitter struct {
// Ratio is the fraction of space allocated to the first widget.
Ratio float32
// Axis is the split direction. Vertical splits top/bottom;
// Horizontal splits left/right.
Axis layout.Axis
dragging bool
handle bool
}
// Layout draws first, a divider, then second along the splitter axis.
func (s *Splitter) Layout(gtx layout.Context, barColor color.NRGBA, first, second layout.Widget) layout.Dimensions {
size := gtx.Constraints.Max
barPx := gtx.Metric.Dp(4)
// Process drag events.
defer clip.Rect{Max: size}.Push(gtx.Ops).Pop()
event.Op(gtx.Ops, s)
for {
ev, ok := gtx.Event(
pointer.Filter{
Target: s,
Kinds: pointer.Press | pointer.Drag | pointer.Release,
},
)
if !ok {
break
}
if pe, ok := ev.(pointer.Event); ok {
switch pe.Kind {
case pointer.Press:
// Only grab if pointer is near the bar.
var barPos float32
if s.Axis == layout.Vertical {
barPos = s.Ratio * float32(size.Y)
s.handle = pe.Position.Y >= barPos-float32(barPx) && pe.Position.Y <= barPos+float32(barPx)*2
} else {
barPos = s.Ratio * float32(size.X)
s.handle = pe.Position.X >= barPos-float32(barPx) && pe.Position.X <= barPos+float32(barPx)*2
}
if s.handle {
s.dragging = true
}
case pointer.Drag:
if s.dragging {
if s.Axis == layout.Vertical {
s.Ratio = pe.Position.Y / float32(size.Y)
} else {
s.Ratio = pe.Position.X / float32(size.X)
}
s.Ratio = max(0.1, min(0.9, s.Ratio))
}
case pointer.Release:
s.dragging = false
s.handle = false
}
}
}
// Compute panel and bar sizes.
var firstSize, secondSize image.Point
var barRect image.Rectangle
if s.Axis == layout.Vertical {
firstH := int(s.Ratio*float32(size.Y)) - barPx/2
secondH := size.Y - firstH - barPx
firstSize = image.Pt(size.X, firstH)
secondSize = image.Pt(size.X, secondH)
barRect = image.Rect(0, firstH, size.X, firstH+barPx)
} else {
firstW := int(s.Ratio*float32(size.X)) - barPx/2
secondW := size.X - firstW - barPx
firstSize = image.Pt(firstW, size.Y)
secondSize = image.Pt(secondW, size.Y)
barRect = image.Rect(firstW, 0, firstW+barPx, size.Y)
}
// Set resize cursor over the bar area.
barStack := clip.Rect(barRect).Push(gtx.Ops)
cursor := pointer.CursorRowResize
if s.Axis == layout.Horizontal {
cursor = pointer.CursorColResize
}
cursor.Add(gtx.Ops)
barStack.Pop()
// Layout panels and bar.
barDp := unit.Dp(float32(barPx) / gtx.Metric.PxPerDp)
layout.Flex{Axis: s.Axis}.Layout(gtx,
layout.Rigid(func(gtx layout.Context) layout.Dimensions {
gtx.Constraints = layout.Exact(firstSize)
return first(gtx)
}),
layout.Rigid(func(gtx layout.Context) layout.Dimensions {
if s.Axis == layout.Vertical {
return HorizontalLine{Height: barDp, Color: barColor}.Layout(gtx)
}
return VerticalLine{Width: barDp, Color: barColor}.Layout(gtx)
}),
layout.Rigid(func(gtx layout.Context) layout.Dimensions {
gtx.Constraints = layout.Exact(secondSize)
return second(gtx)
}),
)
return layout.Dimensions{Size: size}
}