Files
prevara/switch.go
2025-11-30 16:45:22 +00:00

139 lines
3.7 KiB
Go

package gel
import (
"image"
"image/color"
l "github.com/p9c/p9/pkg/gel/gio/layout"
"github.com/p9c/p9/pkg/gel/gio/op"
"github.com/p9c/p9/pkg/gel/gio/op/clip"
"github.com/p9c/p9/pkg/gel/gio/op/paint"
"github.com/p9c/p9/pkg/gel/gio/unit"
"github.com/p9c/p9/pkg/gel/f32color"
)
type Switch struct {
*Window
color struct {
enabled color.NRGBA
disabled color.NRGBA
}
swtch *Bool
}
// Switch creates a boolean switch widget (basically a checkbox but looks like a switch)
func (w *Window) Switch(swtch *Bool) *Switch {
sw := &Switch{
Window: w,
swtch: swtch,
}
sw.color.enabled = w.Colors.GetNRGBAFromName("Primary")
sw.color.disabled = w.Colors.GetNRGBAFromName("scrim")
return sw
}
// EnabledColor sets the color to draw for the enabled state
func (s *Switch) EnabledColor(color string) *Switch {
s.color.enabled = s.Theme.Colors.GetNRGBAFromName(color)
return s
}
// DisabledColor sets the color to draw for the disabled state
func (s *Switch) DisabledColor(color string) *Switch {
s.color.disabled = s.Theme.Colors.GetNRGBAFromName(color)
return s
}
func (s *Switch) SetHook(fn func(b bool)) *Switch {
s.swtch.SetOnChange(fn)
return s
}
// Fn updates the switch and displays it.
func (s *Switch) Fn(gtx l.Context) l.Dimensions {
return s.Inset(0.25, func(gtx l.Context) l.Dimensions {
trackWidth := gtx.Dp(unit.Dp(float32(s.Theme.TextSize) * 2.5))
trackHeight := gtx.Dp(unit.Dp(16))
thumbSize := gtx.Dp(unit.Dp(20))
trackOff := (thumbSize - trackHeight) / 2
// Draw track.
trackCorner := trackHeight / 2
trackRect := image.Rectangle{Max: image.Pt(trackWidth, trackHeight)}
col := s.color.disabled
if s.swtch.value {
col = s.color.enabled
}
if !gtx.Source.Enabled() {
col = f32color.MulAlpha(col, 150)
}
trackColor := f32color.MulAlpha(col, 200)
stack := op.Offset(image.Pt(0, trackOff)).Push(gtx.Ops)
clip.UniformRRect(trackRect, trackCorner).Push(gtx.Ops).Pop()
paint.ColorOp{Color: trackColor}.Add(gtx.Ops)
paint.PaintOp{}.Add(gtx.Ops)
stack.Pop()
// Draw thumb ink.
inkSize := gtx.Dp(unit.Dp(44))
rr := inkSize / 2
inkOff := image.Pt(
trackWidth/2-rr,
-rr+trackHeight/2+trackOff,
)
stack2 := op.Offset(inkOff).Push(gtx.Ops)
gtx.Constraints.Min = image.Pt(inkSize, inkSize)
clip.UniformRRect(image.Rectangle{Max: gtx.Constraints.Min}, rr).Push(gtx.Ops).Pop()
for _, p := range s.swtch.History() {
drawInk(gtx, p)
}
stack2.Pop()
// Compute thumb offset and color.
var stack3 op.TransformStack
if s.swtch.value {
off := trackWidth - thumbSize
stack3 = op.Offset(image.Pt(off, 0)).Push(gtx.Ops)
} else {
stack3 = op.Offset(image.Pt(0, 0)).Push(gtx.Ops)
}
// Draw thumb shadow, a translucent disc slightly larger than the
// thumb itself.
shadowSize := 2
// Center shadow horizontally and slightly adjust its Y.
shadowStack := op.Offset(image.Pt(-shadowSize/2, -1)).Push(gtx.Ops)
drawDisc(gtx.Ops, thumbSize+shadowSize, color.NRGBA(argb(0x55000000)))
shadowStack.Pop()
// Draw thumb.
drawDisc(gtx.Ops, thumbSize, col)
stack3.Pop()
// Set up click area.
clickSize := gtx.Dp(unit.Dp(40))
clickOff := image.Pt(
(trackWidth-clickSize)/2,
(trackHeight-clickSize)/2+trackOff,
)
stack4 := op.Offset(clickOff).Push(gtx.Ops)
sz := image.Pt(clickSize, clickSize)
defer clip.Ellipse(image.Rectangle{Max: sz}).Push(gtx.Ops).Pop()
gtx.Constraints.Min = sz
s.swtch.Fn(gtx)
stack4.Pop()
dims := image.Point{X: trackWidth, Y: thumbSize}
return l.Dimensions{Size: dims}
}).Fn(gtx)
}
func drawDisc(ops *op.Ops, sz int, col color.NRGBA) {
rr := sz / 2
r := image.Rectangle{Max: image.Pt(sz, sz)}
defer clip.UniformRRect(r, rr).Push(ops).Pop()
paint.ColorOp{Color: col}.Add(ops)
paint.PaintOp{}.Add(ops)
}