slider.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/layout"
12 "github.com/p9c/p9/pkg/gel/gio/op"
13 "github.com/p9c/p9/pkg/gel/gio/op/clip"
14 "github.com/p9c/p9/pkg/gel/gio/op/paint"
15 "github.com/p9c/p9/pkg/gel/gio/unit"
16 "github.com/p9c/p9/pkg/gel/gio/widget"
17 )
18
19 // Slider is for selecting a value in a range.
20 func Slider(th *Theme, float *widget.Float, min, max float32) SliderStyle {
21 return SliderStyle{
22 Min: min,
23 Max: max,
24 Color: th.Palette.ContrastBg,
25 Float: float,
26 FingerSize: th.FingerSize,
27 }
28 }
29
30 type SliderStyle struct {
31 Min, Max float32
32 Color color.NRGBA
33 Float *widget.Float
34
35 FingerSize unit.Value
36 }
37
38 func (s SliderStyle) Layout(gtx layout.Context) layout.Dimensions {
39 thumbRadius := gtx.Px(unit.Dp(6))
40 trackWidth := gtx.Px(unit.Dp(2))
41
42 axis := s.Float.Axis
43 // Keep a minimum length so that the track is always visible.
44 minLength := thumbRadius + 3*thumbRadius + thumbRadius
45 // Try to expand to finger size, but only if the constraints
46 // allow for it.
47 touchSizePx := min(gtx.Px(s.FingerSize), axis.Convert(gtx.Constraints.Max).Y)
48 sizeMain := max(axis.Convert(gtx.Constraints.Min).X, minLength)
49 sizeCross := max(2*thumbRadius, touchSizePx)
50 size := axis.Convert(image.Pt(sizeMain, sizeCross))
51
52 st := op.Save(gtx.Ops)
53 o := axis.Convert(image.Pt(thumbRadius, 0))
54 op.Offset(layout.FPt(o)).Add(gtx.Ops)
55 gtx.Constraints.Min = axis.Convert(image.Pt(sizeMain-2*thumbRadius, sizeCross))
56 s.Float.Layout(gtx, thumbRadius, s.Min, s.Max)
57 gtx.Constraints.Min = gtx.Constraints.Min.Add(axis.Convert(image.Pt(0, sizeCross)))
58 thumbPos := thumbRadius + int(s.Float.Pos())
59 st.Load()
60
61 color := s.Color
62 if gtx.Queue == nil {
63 color = f32color.Disabled(color)
64 }
65
66 // Draw track before thumb.
67 st = op.Save(gtx.Ops)
68 track := image.Rectangle{
69 Min: axis.Convert(image.Pt(thumbRadius, sizeCross/2-trackWidth/2)),
70 Max: axis.Convert(image.Pt(thumbPos, sizeCross/2+trackWidth/2)),
71 }
72 clip.Rect(track).Add(gtx.Ops)
73 paint.Fill(gtx.Ops, color)
74 st.Load()
75
76 // Draw track after thumb.
77 st = op.Save(gtx.Ops)
78 track = image.Rectangle{
79 Min: axis.Convert(image.Pt(thumbPos, axis.Convert(track.Min).Y)),
80 Max: axis.Convert(image.Pt(sizeMain-thumbRadius, axis.Convert(track.Max).Y)),
81 }
82 clip.Rect(track).Add(gtx.Ops)
83 paint.Fill(gtx.Ops, f32color.MulAlpha(color, 96))
84 st.Load()
85
86 // Draw thumb.
87 pt := axis.Convert(image.Pt(thumbPos, sizeCross/2))
88 paint.FillShape(gtx.Ops, color,
89 clip.Circle{
90 Center: f32.Point{X: float32(pt.X), Y: float32(pt.Y)},
91 Radius: float32(thumbRadius),
92 }.Op(gtx.Ops))
93
94 return layout.Dimensions{Size: size}
95 }
96
97 func max(a, b int) int {
98 if a > b {
99 return a
100 }
101 return b
102 }
103
104 func min(a, b int) int {
105 if a < b {
106 return a
107 }
108 return b
109 }
110