forked from joejulian/gio
ae8a377cda
The funcs replace stack.Push and macro.Record, which become private. This makes stack and macro faster to write, in particular for stacks where you can just write the following line to save and restore the state : defer op.Push(ops).Pop() This usage requires Push to return a pointer (since Pop has a pointer receiver), or else the code doesn't compile. For consistancy, I tried to do the same for op.Record, but this implied to turn all the MacroOp fields into pointers, and this caused some panics. As a result, op.Record doesn't return a pointer. An other side effect pointed by Larry Clapp: StackOp and MacroOp are not re-usable any more, you have to allocate a new one for each usage, using the described funcs above. Signed-off-by: Thomas Bruyelle <thomas.bruyelle@gmail.com>
212 lines
5.5 KiB
Go
212 lines
5.5 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
|
|
Button *widget.Clickable
|
|
shaper text.Shaper
|
|
}
|
|
|
|
type ButtonLayoutStyle struct {
|
|
Background color.RGBA
|
|
CornerRadius unit.Value
|
|
Inset layout.Inset
|
|
Button *widget.Clickable
|
|
}
|
|
|
|
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
|
|
Button *widget.Clickable
|
|
}
|
|
|
|
func Button(th *Theme, button *widget.Clickable, 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),
|
|
},
|
|
Button: button,
|
|
shaper: th.Shaper,
|
|
}
|
|
}
|
|
|
|
func ButtonLayout(th *Theme, button *widget.Clickable) ButtonLayoutStyle {
|
|
return ButtonLayoutStyle{
|
|
Button: button,
|
|
Background: th.Color.Primary,
|
|
CornerRadius: unit.Dp(4),
|
|
Inset: layout.UniformInset(unit.Dp(12)),
|
|
}
|
|
}
|
|
|
|
func IconButton(th *Theme, button *widget.Clickable, 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)),
|
|
Button: button,
|
|
}
|
|
}
|
|
|
|
// 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) layout.Dimensions {
|
|
return ButtonLayoutStyle{
|
|
Background: b.Background,
|
|
CornerRadius: b.CornerRadius,
|
|
Inset: b.Inset,
|
|
Button: b.Button,
|
|
}.Layout(gtx, 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, 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 b.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(b.Button.Layout),
|
|
)
|
|
}
|
|
|
|
func (b IconButtonStyle) Layout(gtx layout.Context) 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 b.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 b.Button.Layout(gtx)
|
|
}),
|
|
)
|
|
}
|
|
|
|
func drawInk(gtx layout.Context, c widget.Press) {
|
|
d := gtx.Now().Sub(c.Time)
|
|
t := float32(d.Seconds())
|
|
const duration = 0.5
|
|
if t > duration {
|
|
return
|
|
}
|
|
t = t / duration
|
|
stack := op.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)
|
|
}
|