Files
gio-patched/widget/material/button.go
T
Elias Naur fd2cb4a7a1 widget,widget/material: use constraints for setting up hit area
Before this change, the widget.Button.Layout method assumed the caller had set
up the pointer hit area before. Further, the very common rectangular hit
areas needed both an AreaOp and a widget.Button.Layout call.

Make widget.Button less subtle and more useful by setting up a
pointer hit area given by the incoming minimum constraints.

Drop a pointer.AreaOp made redundant by the change.

Signed-off-by: Elias Naur <mail@eliasnaur.com>
2020-05-11 12:37:08 +02:00

192 lines
4.6 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 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),
}
}
func (b ButtonStyle) Layout(gtx *layout.Context, button *widget.Button) {
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.Button, w layout.Widget) {
hmin := gtx.Constraints.Width.Min
vmin := gtx.Constraints.Height.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.Width.Min),
Y: float32(gtx.Constraints.Height.Min),
}},
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.Width.Min = hmin
gtx.Constraints.Height.Min = vmin
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.Button) {
layout.Stack{Alignment: layout.Center}.Layout(gtx,
layout.Expanded(func() {
size := gtx.Constraints.Width.Min
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)
}