switch.go raw
1 package gel
2
3 import (
4 "image"
5 "image/color"
6
7 "github.com/p9c/gio/f32"
8 "github.com/p9c/gio/io/pointer"
9 l "github.com/p9c/gio/layout"
10 "github.com/p9c/gio/op"
11 "github.com/p9c/gio/op/clip"
12 "github.com/p9c/gio/op/paint"
13 "github.com/p9c/gio/unit"
14
15 "github.com/p9c/gel/f32color"
16 )
17
18 type Switch struct {
19 *Window
20 color struct {
21 enabled color.NRGBA
22 disabled color.NRGBA
23 }
24 swtch *Bool
25 }
26
27 // Switch creates a boolean switch widget (basically a checkbox but looks like a switch)
28 func (w *Window) Switch(swtch *Bool) *Switch {
29 sw := &Switch{
30 Window: w,
31 swtch: swtch,
32 }
33 sw.color.enabled = w.Colors.GetNRGBAFromName("Primary")
34 sw.color.disabled = w.Colors.GetNRGBAFromName("scrim")
35 return sw
36 }
37
38 // EnabledColor sets the color to draw for the enabled state
39 func (s *Switch) EnabledColor(color string) *Switch {
40 s.color.enabled = s.Theme.Colors.GetNRGBAFromName(color)
41 return s
42 }
43
44 // DisabledColor sets the color to draw for the disabled state
45 func (s *Switch) DisabledColor(color string) *Switch {
46 s.color.disabled = s.Theme.Colors.GetNRGBAFromName(color)
47 return s
48 }
49
50 func (s *Switch) SetHook(fn func(b bool)) *Switch {
51 s.swtch.SetOnChange(fn)
52 return s
53 }
54
55 // Fn updates the switch and displays it.
56 func (s *Switch) Fn(gtx l.Context) l.Dimensions {
57 return s.Inset(0.25, func(gtx l.Context) l.Dimensions {
58 trackWidth := gtx.Px(s.Theme.TextSize.Scale(2.5))
59 trackHeight := gtx.Px(unit.Dp(16))
60 thumbSize := gtx.Px(unit.Dp(20))
61 trackOff := float32(thumbSize-trackHeight) * .5
62
63 // Draw track.
64 stack := op.Save(gtx.Ops)
65 trackCorner := float32(trackHeight) / 2
66 trackRect := f32.Rectangle{Max: f32.Point{
67 X: float32(trackWidth),
68 Y: float32(trackHeight),
69 }}
70 col := s.color.disabled
71 if s.swtch.value {
72 col = s.color.enabled
73 }
74 if gtx.Queue == nil {
75 col = f32color.MulAlpha(col, 150)
76 }
77 trackColor := f32color.MulAlpha(col, 200)
78 op.Offset(f32.Point{Y: trackOff}).Add(gtx.Ops)
79 clip.RRect{
80 Rect: trackRect,
81 NE: trackCorner, NW: trackCorner, SE: trackCorner, SW: trackCorner,
82 }.Add(gtx.Ops)
83 paint.ColorOp{Color: trackColor}.Add(gtx.Ops)
84 paint.PaintOp{}.Add(gtx.Ops)
85 stack.Load()
86
87 // Draw thumb ink.
88 stack = op.Save(gtx.Ops)
89 inkSize := gtx.Px(unit.Dp(44))
90 rr := float32(inkSize) * .5
91 inkOff := f32.Point{
92 X: float32(trackWidth)*.5 - rr,
93 Y: -rr + float32(trackHeight)*.5 + trackOff,
94 }
95 op.Offset(inkOff).Add(gtx.Ops)
96 gtx.Constraints.Min = image.Pt(inkSize, inkSize)
97 clip.RRect{
98 Rect: f32.Rectangle{
99 Max: l.FPt(gtx.Constraints.Min),
100 },
101 NE: rr, NW: rr, SE: rr, SW: rr,
102 }.Add(gtx.Ops)
103 for _, p := range s.swtch.History() {
104 drawInk(gtx, p)
105 }
106 stack.Load()
107
108 // Compute thumb offset and color.
109 stack = op.Save(gtx.Ops)
110 if s.swtch.value {
111 off := trackWidth - thumbSize
112 op.Offset(f32.Point{X: float32(off)}).Add(gtx.Ops)
113 }
114
115 // Draw thumb shadow, a translucent disc slightly larger than the
116 // thumb itself.
117 shadowStack := op.Save(gtx.Ops)
118 shadowSize := float32(2)
119 // Center shadow horizontally and slightly adjust its Y.
120 op.Offset(f32.Point{X: -shadowSize / 2, Y: -.75}).Add(gtx.Ops)
121 drawDisc(gtx.Ops, float32(thumbSize)+shadowSize, color.NRGBA(argb(0x55000000)))
122 shadowStack.Load()
123
124 // Draw thumb.
125 drawDisc(gtx.Ops, float32(thumbSize), col)
126 stack.Load()
127
128 // Set up click area.
129 stack = op.Save(gtx.Ops)
130 clickSize := gtx.Px(unit.Dp(40))
131 clickOff := f32.Point{
132 X: (float32(trackWidth) - float32(clickSize)) * .5,
133 Y: (float32(trackHeight)-float32(clickSize))*.5 + trackOff,
134 }
135 op.Offset(clickOff).Add(gtx.Ops)
136 sz := image.Pt(clickSize, clickSize)
137 pointer.Ellipse(image.Rectangle{Max: sz}).Add(gtx.Ops)
138 gtx.Constraints.Min = sz
139 s.swtch.Fn(gtx)
140 stack.Load()
141
142 dims := image.Point{X: trackWidth, Y: thumbSize}
143 return l.Dimensions{Size: dims}
144 }).Fn(gtx)
145 }
146
147 func drawDisc(ops *op.Ops, sz float32, col color.NRGBA) {
148 defer op.Save(ops).Load()
149 rr := sz / 2
150 r := f32.Rectangle{Max: f32.Point{X: sz, Y: sz}}
151 clip.RRect{
152 Rect: r,
153 NE: rr, NW: rr, SE: rr, SW: rr,
154 }.Add(ops)
155 paint.ColorOp{Color: col}.Add(ops)
156 paint.PaintOp{}.Add(ops)
157 }
158