switch.go raw
1 // SPDX-License-Identifier: Unlicense OR MIT
2
3 package material
4
5 import (
6 "image"
7 "image/color"
8
9 "github.com/p9c/p9/pkg/gel/gio/f32"
10 "github.com/p9c/p9/pkg/gel/gio/internal/f32color"
11 "github.com/p9c/p9/pkg/gel/gio/io/pointer"
12 "github.com/p9c/p9/pkg/gel/gio/layout"
13 "github.com/p9c/p9/pkg/gel/gio/op"
14 "github.com/p9c/p9/pkg/gel/gio/op/clip"
15 "github.com/p9c/p9/pkg/gel/gio/op/paint"
16 "github.com/p9c/p9/pkg/gel/gio/unit"
17 "github.com/p9c/p9/pkg/gel/gio/widget"
18 )
19
20 type SwitchStyle struct {
21 Color struct {
22 Enabled color.NRGBA
23 Disabled color.NRGBA
24 Track color.NRGBA
25 }
26 Switch *widget.Bool
27 }
28
29 // Switch is for selecting a boolean value.
30 func Switch(th *Theme, swtch *widget.Bool) SwitchStyle {
31 sw := SwitchStyle{
32 Switch: swtch,
33 }
34 sw.Color.Enabled = th.Palette.ContrastBg
35 sw.Color.Disabled = th.Palette.Bg
36 sw.Color.Track = f32color.MulAlpha(th.Palette.Fg, 0x88)
37 return sw
38 }
39
40 // Layout updates the switch and displays it.
41 func (s SwitchStyle) Layout(gtx layout.Context) layout.Dimensions {
42 trackWidth := gtx.Px(unit.Dp(36))
43 trackHeight := gtx.Px(unit.Dp(16))
44 thumbSize := gtx.Px(unit.Dp(20))
45 trackOff := float32(thumbSize-trackHeight) * .5
46
47 // Draw track.
48 stack := op.Save(gtx.Ops)
49 trackCorner := float32(trackHeight) / 2
50 trackRect := f32.Rectangle{Max: f32.Point{
51 X: float32(trackWidth),
52 Y: float32(trackHeight),
53 }}
54 col := s.Color.Disabled
55 if s.Switch.Value {
56 col = s.Color.Enabled
57 }
58 if gtx.Queue == nil {
59 col = f32color.Disabled(col)
60 }
61 trackColor := s.Color.Track
62 op.Offset(f32.Point{Y: trackOff}).Add(gtx.Ops)
63 clip.UniformRRect(trackRect, trackCorner).Add(gtx.Ops)
64 paint.ColorOp{Color: trackColor}.Add(gtx.Ops)
65 paint.PaintOp{}.Add(gtx.Ops)
66 stack.Load()
67
68 // Draw thumb ink.
69 stack = op.Save(gtx.Ops)
70 inkSize := gtx.Px(unit.Dp(44))
71 rr := float32(inkSize) * .5
72 inkOff := f32.Point{
73 X: float32(trackWidth)*.5 - rr,
74 Y: -rr + float32(trackHeight)*.5 + trackOff,
75 }
76 op.Offset(inkOff).Add(gtx.Ops)
77 gtx.Constraints.Min = image.Pt(inkSize, inkSize)
78 clip.UniformRRect(f32.Rectangle{Max: layout.FPt(gtx.Constraints.Min)}, rr).Add(gtx.Ops)
79 for _, p := range s.Switch.History() {
80 drawInk(gtx, p)
81 }
82 stack.Load()
83
84 // Compute thumb offset and color.
85 stack = op.Save(gtx.Ops)
86 if s.Switch.Value {
87 off := trackWidth - thumbSize
88 op.Offset(f32.Point{X: float32(off)}).Add(gtx.Ops)
89 }
90
91 thumbRadius := float32(thumbSize) / 2
92
93 // Draw hover.
94 if s.Switch.Hovered() {
95 r := 1.7 * thumbRadius
96 background := f32color.MulAlpha(s.Color.Enabled, 70)
97 paint.FillShape(gtx.Ops, background,
98 clip.Circle{
99 Center: f32.Point{X: thumbRadius, Y: thumbRadius},
100 Radius: r,
101 }.Op(gtx.Ops))
102 }
103
104 // Draw thumb shadow, a translucent disc slightly larger than the
105 // thumb itself.
106 // Center shadow horizontally and slightly adjust its Y.
107 paint.FillShape(gtx.Ops, argb(0x55000000),
108 clip.Circle{
109 Center: f32.Point{X: thumbRadius, Y: thumbRadius + .25},
110 Radius: thumbRadius + 1,
111 }.Op(gtx.Ops))
112
113 // Draw thumb.
114 paint.FillShape(gtx.Ops, col,
115 clip.Circle{
116 Center: f32.Point{X: thumbRadius, Y: thumbRadius},
117 Radius: thumbRadius,
118 }.Op(gtx.Ops))
119
120 // Set up click area.
121 stack = op.Save(gtx.Ops)
122 clickSize := gtx.Px(unit.Dp(40))
123 clickOff := f32.Point{
124 X: (float32(trackWidth) - float32(clickSize)) * .5,
125 Y: (float32(trackHeight)-float32(clickSize))*.5 + trackOff,
126 }
127 op.Offset(clickOff).Add(gtx.Ops)
128 sz := image.Pt(clickSize, clickSize)
129 pointer.Ellipse(image.Rectangle{Max: sz}).Add(gtx.Ops)
130 gtx.Constraints.Min = sz
131 s.Switch.Layout(gtx)
132 stack.Load()
133
134 dims := image.Point{X: trackWidth, Y: thumbSize}
135 return layout.Dimensions{Size: dims}
136 }
137