button.go raw
1 package gel
2
3 import (
4 "image/color"
5 "math"
6 "strings"
7
8 "github.com/p9c/gio/f32"
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/text"
14 "github.com/p9c/gio/unit"
15
16 "github.com/p9c/gel/f32color"
17 )
18
19 // Button is a material text label icon with options to change all features
20 type Button struct {
21 *Window
22 background color.NRGBA
23 color color.NRGBA
24 cornerRadius unit.Value
25 font text.Font
26 inset *l.Inset
27 text string
28 textSize unit.Value
29 button *Clickable
30 shaper text.Shaper
31 }
32
33 // Button is a regular material text button where all the dimensions, colors, corners and font can be changed
34 func (w *Window) Button(btn *Clickable) *Button {
35 var font text.Font
36 var e error
37 if font, e = w.collection.Font("plan9"); E.Chk(e) {
38 }
39 return &Button{
40 Window: w,
41 text: strings.ToUpper("text unset"),
42 // default sets
43 font: font,
44 color: w.Colors.GetNRGBAFromName("DocBg"),
45 cornerRadius: w.TextSize.Scale(0.25),
46 background: w.Colors.GetNRGBAFromName("Primary"),
47 textSize: w.TextSize,
48 inset: &l.Inset{
49 Top: w.TextSize.Scale(0.5),
50 Bottom: w.TextSize.Scale(0.5),
51 Left: w.TextSize.Scale(0.5),
52 Right: w.TextSize.Scale(0.5),
53 },
54 button: btn,
55 shaper: w.shaper,
56 }
57 }
58
59 // Background sets the background color
60 func (b *Button) Background(background string) *Button {
61 b.background = b.Theme.Colors.GetNRGBAFromName(background)
62 return b
63 }
64
65 // Color sets the text color
66 func (b *Button) Color(color string) *Button {
67 b.color = b.Theme.Colors.GetNRGBAFromName(color)
68 return b
69 }
70
71 // CornerRadius sets the corner radius (all measurements are scaled from the base text size)
72 func (b *Button) CornerRadius(cornerRadius float32) *Button {
73 b.cornerRadius = b.TextSize.Scale(cornerRadius)
74 return b
75 }
76
77 // Font sets the font style
78 func (b *Button) Font(font string) *Button {
79 var fon text.Font
80 var e error
81 if fon, e = b.collection.Font(font); !E.Chk(e) {
82 b.font = fon
83 } else {
84 panic(e)
85 }
86 return b
87 }
88
89 // Inset sets the inset between the button border and the text
90 func (b *Button) Inset(scale float32) *Button {
91 b.inset = &l.Inset{
92 Top: b.TextSize.Scale(scale),
93 Right: b.TextSize.Scale(scale),
94 Bottom: b.TextSize.Scale(scale),
95 Left: b.TextSize.Scale(scale),
96 }
97 return b
98 }
99
100 // Text sets the text on the button
101 func (b *Button) Text(text string) *Button {
102 b.text = text
103 return b
104 }
105
106 // TextScale sets the dimensions of the text as a fraction of the base text size
107 func (b *Button) TextScale(scale float32) *Button {
108 b.textSize = b.Theme.TextSize.Scale(scale)
109 return b
110 }
111
112 // SetClick defines the callback to run on a click (mouse up) event
113 func (b *Button) SetClick(fn func()) *Button {
114 b.button.SetClick(fn)
115 return b
116 }
117
118 // SetCancel sets the callback to run when the user presses down over the button
119 // but then moves out of its hitbox before release (click)
120 func (b *Button) SetCancel(fn func()) *Button {
121 b.button.SetCancel(fn)
122 return b
123 }
124
125 func (b *Button) SetPress(fn func()) *Button {
126 b.button.SetPress(fn)
127 return b
128 }
129
130 // Fn renders the button
131 func (b *Button) Fn(gtx l.Context) l.Dimensions {
132 bl := &ButtonLayout{
133 background: b.background,
134 cornerRadius: b.cornerRadius,
135 button: b.button,
136 }
137 fn := func(gtx l.Context) l.Dimensions {
138 return b.inset.Layout(
139 gtx, func(gtx l.Context) l.Dimensions {
140 // paint.ColorOp{Color: b.color}.Add(gtx.Ops)
141 return b.Flex().Rigid(b.Label().Text(b.text).TextScale(b.textSize.V / b.TextSize.V).Fn).Fn(gtx)
142 // b.Window.Text().
143 // Alignment(text.Middle).
144 // Fn(gtx, b.shaper, b.font, b.textSize, b.text)
145 },
146 )
147 }
148 bl.Embed(fn)
149 return bl.Fn(gtx)
150 }
151
152 func drawInk(c l.Context, p press) {
153 // duration is the number of seconds for the completed animation: expand while
154 // fading in, then out.
155 const (
156 expandDuration = float32(0.5)
157 fadeDuration = float32(0.9)
158 )
159 now := c.Now
160 t := float32(now.Sub(p.Start).Seconds())
161 end := p.End
162 if end.IsZero() {
163 // If the press hasn't ended, don't fade-out.
164 end = now
165 }
166 endt := float32(end.Sub(p.Start).Seconds())
167 // Compute the fade-in/out position in [0;1].
168 var alphat float32
169 {
170 var haste float32
171 if p.Cancelled {
172 // If the press was cancelled before the inkwell was fully faded in, fast
173 // forward the animation to match the fade-out.
174 if h := 0.5 - endt/fadeDuration; h > 0 {
175 haste = h
176 }
177 }
178 // Fade in.
179 half1 := t/fadeDuration + haste
180 if half1 > 0.5 {
181 half1 = 0.5
182 }
183 // Fade out.
184 half2 := float32(now.Sub(end).Seconds())
185 half2 /= fadeDuration
186 half2 += haste
187 if half2 > 0.5 {
188 // Too old.
189 return
190 }
191
192 alphat = half1 + half2
193 }
194 // Compute the expand position in [0;1].
195 sizet := t
196 if p.Cancelled {
197 // Freeze expansion of cancelled presses.
198 sizet = endt
199 }
200 sizet /= expandDuration
201 // Animate only ended presses, and presses that are fading in.
202 if !p.End.IsZero() || sizet <= 1.0 {
203 op.InvalidateOp{}.Add(c.Ops)
204 }
205 if sizet > 1.0 {
206 sizet = 1.0
207 }
208 if alphat > .5 {
209 // Start fadeout after half the animation.
210 alphat = 1.0 - alphat
211 }
212 // Twice the speed to attain fully faded in at 0.5.
213 t2 := alphat * 2
214 // BeziƩr ease-in curve.
215 alphaBezier := t2 * t2 * (3.0 - 2.0*t2)
216 sizeBezier := sizet * sizet * (3.0 - 2.0*sizet)
217 size := float32(c.Constraints.Min.X)
218 if h := float32(c.Constraints.Min.Y); h > size {
219 size = h
220 }
221 // Cover the entire constraints min rectangle.
222 size *= 2 * float32(math.Sqrt(2))
223 // Apply curve values to size and color.
224 size *= sizeBezier
225 alpha := 0.7 * alphaBezier
226 const col = 0.8
227 ba, bc := byte(alpha*0xff), byte(col*0xff)
228 defer op.Save(c.Ops).Load()
229 rgba := f32color.MulAlpha(color.NRGBA{A: 0xff, R: bc, G: bc, B: bc}, ba)
230 ink := paint.ColorOp{Color: rgba}
231 ink.Add(c.Ops)
232 rr := size * .5
233 op.Offset(
234 p.Position.Add(
235 f32.Point{
236 X: -rr,
237 Y: -rr,
238 },
239 ),
240 ).Add(c.Ops)
241 clip.RRect{
242 Rect: f32.Rectangle{
243 Max: f32.Point{
244 X: size,
245 Y: size,
246 },
247 },
248 NE: rr, NW: rr, SE: rr, SW: rr,
249 }.Add(c.Ops)
250 paint.PaintOp{}.Add(c.Ops)
251 }
252