// 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 color.RGBA Icon *widget.Icon Size unit.Value Padding 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(56), Padding: unit.Dp(16), } } // Clickable lays out a rectangular clickable widget without further // decoration. func Clickable(gtx *layout.Context, button *widget.Clickable, w layout.Widget) { layout.Stack{}.Layout(gtx, layout.Expanded(func() { button.Layout(gtx) }), layout.Expanded(func() { 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) } }), layout.Stacked(w), ) } func (b ButtonStyle) Layout(gtx *layout.Context, button *widget.Clickable) { ButtonLayoutStyle{ Background: b.Background, CornerRadius: b.CornerRadius, Inset: b.Inset, }.Layout(gtx, button, func() { paint.ColorOp{Color: b.Color}.Add(gtx.Ops) 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) { min := gtx.Constraints.Min layout.Stack{Alignment: layout.Center}.Layout(gtx, layout.Expanded(func() { 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) fill(gtx, b.Background) for _, c := range button.History() { drawInk(gtx, c) } }), layout.Stacked(func() { gtx.Constraints.Min = min layout.Center.Layout(gtx, func() { b.Inset.Layout(gtx, func() { w() }) }) }), layout.Expanded(func() { button.Layout(gtx) }), ) } func (b IconButtonStyle) Layout(gtx *layout.Context, button *widget.Clickable) { layout.Stack{Alignment: layout.Center}.Layout(gtx, layout.Expanded(func() { 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) fill(gtx, b.Background) for _, c := range button.History() { drawInk(gtx, c) } }), layout.Stacked(func() { layout.UniformInset(b.Padding).Layout(gtx, func() { size := gtx.Px(b.Size) - 2*gtx.Px(b.Padding) if b.Icon != nil { b.Icon.Color = b.Color b.Icon.Layout(gtx, unit.Px(float32(size))) } gtx.Dimensions = layout.Dimensions{ Size: image.Point{X: size, Y: size}, } }) }), layout.Expanded(func() { pointer.Ellipse(image.Rectangle{Max: gtx.Constraints.Min}).Add(gtx.Ops) button.Layout(gtx) }), ) } func toPointF(p image.Point) f32.Point { return f32.Point{X: float32(p.X), Y: float32(p.Y)} } 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) }