mirror of
https://git.sr.ht/~eliasnaur/gio
synced 2026-07-01 07:35:40 +00:00
3af01a3f43
Change the definition of Widget from the implicit
type Widget func()
to the explicit functional
type Widget func(gtx layout.Context) layout.Dimensions
The advantages are numerous:
- Clearer connection between the incoming context and the output dimensions.
- Returning the Dimensions are impossible to omit.
- Contexts passed by value, so its fields can be exported
and freely mutated by the program.
The only disadvantage is the longer function literals and the many "returns".
What tipped the scales in favour of the explicit Widget variant is that type
aliases can dramatically shorten the literals:
type (
C = layout.Context
D = layout.Dimensions
)
widget := func(gtx C) D {
...
}
Note that the aliases are not part of the Gio API and it is up to each user
whether they want to use them.
Finally the Go proposal for lightweight function literals,
https://github.com/golang/go/issues/21498, may remove the disadvantage
completely in future.
Context becomes a plain struct with only public fields, and its Reset is
replaced by a NewContext convenience constructor.
Signed-off-by: Elias Naur <mail@eliasnaur.com>
206 lines
5.4 KiB
Go
206 lines
5.4 KiB
Go
// SPDX-License-Identifier: Unlicense OR MIT
|
|
|
|
package material
|
|
|
|
import (
|
|
"image"
|
|
"image/color"
|
|
|
|
"gioui.org/f32"
|
|
"gioui.org/io/pointer"
|
|
"gioui.org/layout"
|
|
"gioui.org/op"
|
|
"gioui.org/op/clip"
|
|
"gioui.org/op/paint"
|
|
"gioui.org/text"
|
|
"gioui.org/unit"
|
|
"gioui.org/widget"
|
|
)
|
|
|
|
type ButtonStyle struct {
|
|
Text string
|
|
// Color is the text color.
|
|
Color color.RGBA
|
|
Font text.Font
|
|
TextSize unit.Value
|
|
Background color.RGBA
|
|
CornerRadius unit.Value
|
|
Inset layout.Inset
|
|
shaper text.Shaper
|
|
}
|
|
|
|
type ButtonLayoutStyle struct {
|
|
Background color.RGBA
|
|
CornerRadius unit.Value
|
|
Inset layout.Inset
|
|
}
|
|
|
|
type IconButtonStyle struct {
|
|
Background color.RGBA
|
|
// Color is the icon color.
|
|
Color color.RGBA
|
|
Icon *widget.Icon
|
|
// Size is the icon size.
|
|
Size unit.Value
|
|
Inset layout.Inset
|
|
}
|
|
|
|
func Button(th *Theme, txt string) ButtonStyle {
|
|
return ButtonStyle{
|
|
Text: txt,
|
|
Color: rgb(0xffffff),
|
|
CornerRadius: unit.Dp(4),
|
|
Background: th.Color.Primary,
|
|
TextSize: th.TextSize.Scale(14.0 / 16.0),
|
|
Inset: layout.Inset{
|
|
Top: unit.Dp(10), Bottom: unit.Dp(10),
|
|
Left: unit.Dp(12), Right: unit.Dp(12),
|
|
},
|
|
shaper: th.Shaper,
|
|
}
|
|
}
|
|
|
|
func ButtonLayout(th *Theme) ButtonLayoutStyle {
|
|
return ButtonLayoutStyle{
|
|
Background: th.Color.Primary,
|
|
CornerRadius: unit.Dp(4),
|
|
Inset: layout.UniformInset(unit.Dp(12)),
|
|
}
|
|
}
|
|
|
|
func IconButton(th *Theme, icon *widget.Icon) IconButtonStyle {
|
|
return IconButtonStyle{
|
|
Background: th.Color.Primary,
|
|
Color: th.Color.InvText,
|
|
Icon: icon,
|
|
Size: unit.Dp(24),
|
|
Inset: layout.UniformInset(unit.Dp(12)),
|
|
}
|
|
}
|
|
|
|
// Clickable lays out a rectangular clickable widget without further
|
|
// decoration.
|
|
func Clickable(gtx layout.Context, button *widget.Clickable, w layout.Widget) layout.Dimensions {
|
|
return layout.Stack{}.Layout(gtx,
|
|
layout.Expanded(button.Layout),
|
|
layout.Expanded(func(gtx layout.Context) layout.Dimensions {
|
|
clip.Rect{
|
|
Rect: f32.Rectangle{Max: f32.Point{
|
|
X: float32(gtx.Constraints.Min.X),
|
|
Y: float32(gtx.Constraints.Min.Y),
|
|
}},
|
|
}.Op(gtx.Ops).Add(gtx.Ops)
|
|
for _, c := range button.History() {
|
|
drawInk(gtx, c)
|
|
}
|
|
return layout.Dimensions{Size: gtx.Constraints.Min}
|
|
}),
|
|
layout.Stacked(w),
|
|
)
|
|
}
|
|
|
|
func (b ButtonStyle) Layout(gtx layout.Context, button *widget.Clickable) layout.Dimensions {
|
|
return ButtonLayoutStyle{
|
|
Background: b.Background,
|
|
CornerRadius: b.CornerRadius,
|
|
Inset: b.Inset,
|
|
}.Layout(gtx, button, func(gtx layout.Context) layout.Dimensions {
|
|
paint.ColorOp{Color: b.Color}.Add(gtx.Ops)
|
|
return widget.Label{Alignment: text.Middle}.Layout(gtx, b.shaper, b.Font, b.TextSize, b.Text)
|
|
})
|
|
}
|
|
|
|
func (b ButtonLayoutStyle) Layout(gtx layout.Context, button *widget.Clickable, w layout.Widget) layout.Dimensions {
|
|
min := gtx.Constraints.Min
|
|
return layout.Stack{Alignment: layout.Center}.Layout(gtx,
|
|
layout.Expanded(func(gtx layout.Context) layout.Dimensions {
|
|
rr := float32(gtx.Px(b.CornerRadius))
|
|
clip.Rect{
|
|
Rect: f32.Rectangle{Max: f32.Point{
|
|
X: float32(gtx.Constraints.Min.X),
|
|
Y: float32(gtx.Constraints.Min.Y),
|
|
}},
|
|
NE: rr, NW: rr, SE: rr, SW: rr,
|
|
}.Op(gtx.Ops).Add(gtx.Ops)
|
|
dims := fill(gtx, b.Background)
|
|
for _, c := range button.History() {
|
|
drawInk(gtx, c)
|
|
}
|
|
return dims
|
|
}),
|
|
layout.Stacked(func(gtx layout.Context) layout.Dimensions {
|
|
gtx.Constraints.Min = min
|
|
return layout.Center.Layout(gtx, func(gtx layout.Context) layout.Dimensions {
|
|
return b.Inset.Layout(gtx, w)
|
|
})
|
|
}),
|
|
layout.Expanded(button.Layout),
|
|
)
|
|
}
|
|
|
|
func (b IconButtonStyle) Layout(gtx layout.Context, button *widget.Clickable) layout.Dimensions {
|
|
return layout.Stack{Alignment: layout.Center}.Layout(gtx,
|
|
layout.Expanded(func(gtx layout.Context) layout.Dimensions {
|
|
size := gtx.Constraints.Min.X
|
|
sizef := float32(size)
|
|
rr := sizef * .5
|
|
clip.Rect{
|
|
Rect: f32.Rectangle{Max: f32.Point{X: sizef, Y: sizef}},
|
|
NE: rr, NW: rr, SE: rr, SW: rr,
|
|
}.Op(gtx.Ops).Add(gtx.Ops)
|
|
dims := fill(gtx, b.Background)
|
|
for _, c := range button.History() {
|
|
drawInk(gtx, c)
|
|
}
|
|
return dims
|
|
}),
|
|
layout.Stacked(func(gtx layout.Context) layout.Dimensions {
|
|
return b.Inset.Layout(gtx, func(gtx layout.Context) layout.Dimensions {
|
|
size := gtx.Px(b.Size)
|
|
if b.Icon != nil {
|
|
b.Icon.Color = b.Color
|
|
b.Icon.Layout(gtx, unit.Px(float32(size)))
|
|
}
|
|
return layout.Dimensions{
|
|
Size: image.Point{X: size, Y: size},
|
|
}
|
|
})
|
|
}),
|
|
layout.Expanded(func(gtx layout.Context) layout.Dimensions {
|
|
pointer.Ellipse(image.Rectangle{Max: gtx.Constraints.Min}).Add(gtx.Ops)
|
|
return button.Layout(gtx)
|
|
}),
|
|
)
|
|
}
|
|
|
|
func drawInk(gtx layout.Context, c widget.Click) {
|
|
d := gtx.Now().Sub(c.Time)
|
|
t := float32(d.Seconds())
|
|
const duration = 0.5
|
|
if t > duration {
|
|
return
|
|
}
|
|
t = t / duration
|
|
var stack op.StackOp
|
|
stack.Push(gtx.Ops)
|
|
size := float32(gtx.Px(unit.Dp(700))) * t
|
|
rr := size * .5
|
|
col := byte(0xaa * (1 - t*t))
|
|
ink := paint.ColorOp{Color: color.RGBA{A: col, R: col, G: col, B: col}}
|
|
ink.Add(gtx.Ops)
|
|
op.TransformOp{}.Offset(c.Position).Offset(f32.Point{
|
|
X: -rr,
|
|
Y: -rr,
|
|
}).Add(gtx.Ops)
|
|
clip.Rect{
|
|
Rect: f32.Rectangle{Max: f32.Point{
|
|
X: float32(size),
|
|
Y: float32(size),
|
|
}},
|
|
NE: rr, NW: rr, SE: rr, SW: rr,
|
|
}.Op(gtx.Ops).Add(gtx.Ops)
|
|
paint.PaintOp{Rect: f32.Rectangle{Max: f32.Point{X: float32(size), Y: float32(size)}}}.Add(gtx.Ops)
|
|
stack.Pop()
|
|
op.InvalidateOp{}.Add(gtx.Ops)
|
|
}
|