242 lines
6.1 KiB
Go
242 lines
6.1 KiB
Go
package gel
|
|
|
|
import (
|
|
"image"
|
|
"image/color"
|
|
"math"
|
|
"strings"
|
|
|
|
"github.com/p9c/p9/pkg/gel/gio/font"
|
|
l "github.com/p9c/p9/pkg/gel/gio/layout"
|
|
"github.com/p9c/p9/pkg/gel/gio/op"
|
|
"github.com/p9c/p9/pkg/gel/gio/op/clip"
|
|
"github.com/p9c/p9/pkg/gel/gio/op/paint"
|
|
"github.com/p9c/p9/pkg/gel/gio/text"
|
|
"github.com/p9c/p9/pkg/gel/gio/unit"
|
|
|
|
"github.com/p9c/p9/pkg/gel/f32color"
|
|
)
|
|
|
|
// Button is a material text label icon with options to change all features
|
|
type Button struct {
|
|
*Window
|
|
background color.NRGBA
|
|
color color.NRGBA
|
|
cornerRadius unit.Dp
|
|
font font.Font
|
|
inset *l.Inset
|
|
text string
|
|
textSize unit.Sp
|
|
button *Clickable
|
|
shaper *text.Shaper
|
|
}
|
|
|
|
// Button is a regular material text button where all the dimensions, colors, corners and font can be changed
|
|
func (w *Window) Button(btn *Clickable) *Button {
|
|
var fnt font.Font
|
|
var e error
|
|
if fnt, e = w.collection.Font("plan9"); E.Chk(e) {
|
|
}
|
|
insetVal := unit.Dp(float32(w.TextSize) * 0.5)
|
|
return &Button{
|
|
Window: w,
|
|
text: strings.ToUpper("text unset"),
|
|
// default sets
|
|
font: fnt,
|
|
color: w.Colors.GetNRGBAFromName("DocBg"),
|
|
cornerRadius: unit.Dp(float32(w.TextSize) * 0.25),
|
|
background: w.Colors.GetNRGBAFromName("Primary"),
|
|
textSize: w.TextSize,
|
|
inset: &l.Inset{
|
|
Top: insetVal,
|
|
Bottom: insetVal,
|
|
Left: insetVal,
|
|
Right: insetVal,
|
|
},
|
|
button: btn,
|
|
shaper: w.shaper,
|
|
}
|
|
}
|
|
|
|
// Background sets the background color
|
|
func (b *Button) Background(background string) *Button {
|
|
b.background = b.Theme.Colors.GetNRGBAFromName(background)
|
|
return b
|
|
}
|
|
|
|
// Color sets the text color
|
|
func (b *Button) Color(color string) *Button {
|
|
b.color = b.Theme.Colors.GetNRGBAFromName(color)
|
|
return b
|
|
}
|
|
|
|
// CornerRadius sets the corner radius (all measurements are scaled from the base text size)
|
|
func (b *Button) CornerRadius(cornerRadius float32) *Button {
|
|
b.cornerRadius = unit.Dp(float32(b.TextSize) * cornerRadius)
|
|
return b
|
|
}
|
|
|
|
// Font sets the font style. If font not found, keeps current font.
|
|
func (b *Button) Font(fontName string) *Button {
|
|
if fon, e := b.collection.Font(fontName); e == nil {
|
|
b.font = fon
|
|
} else {
|
|
D.Ln("font not found:", fontName, "- keeping current font")
|
|
}
|
|
return b
|
|
}
|
|
|
|
// Inset sets the inset between the button border and the text
|
|
func (b *Button) Inset(scale float32) *Button {
|
|
insetVal := unit.Dp(float32(b.TextSize) * scale)
|
|
b.inset = &l.Inset{
|
|
Top: insetVal,
|
|
Right: insetVal,
|
|
Bottom: insetVal,
|
|
Left: insetVal,
|
|
}
|
|
return b
|
|
}
|
|
|
|
// Text sets the text on the button
|
|
func (b *Button) Text(text string) *Button {
|
|
b.text = text
|
|
return b
|
|
}
|
|
|
|
// TextScale sets the dimensions of the text as a fraction of the base text size
|
|
func (b *Button) TextScale(scale float32) *Button {
|
|
b.textSize = unit.Sp(float32(b.Theme.TextSize) * scale)
|
|
return b
|
|
}
|
|
|
|
// SetClick defines the callback to run on a click (mouse up) event
|
|
func (b *Button) SetClick(fn func()) *Button {
|
|
b.button.SetClick(fn)
|
|
return b
|
|
}
|
|
|
|
// SetCancel sets the callback to run when the user presses down over the button
|
|
// but then moves out of its hitbox before release (click)
|
|
func (b *Button) SetCancel(fn func()) *Button {
|
|
b.button.SetCancel(fn)
|
|
return b
|
|
}
|
|
|
|
func (b *Button) SetPress(fn func()) *Button {
|
|
b.button.SetPress(fn)
|
|
return b
|
|
}
|
|
|
|
// Fn renders the button
|
|
func (b *Button) Fn(gtx l.Context) l.Dimensions {
|
|
bl := &ButtonLayout{
|
|
Window: b.Window,
|
|
background: b.background,
|
|
cornerRadius: b.cornerRadius,
|
|
button: b.button,
|
|
}
|
|
fn := func(gtx l.Context) l.Dimensions {
|
|
return b.inset.Layout(
|
|
gtx, func(gtx l.Context) l.Dimensions {
|
|
// paint.ColorOp{Color: b.color}.Add(gtx.Ops)
|
|
return b.Flex().Rigid(b.Label().Text(b.text).TextScale(float32(b.textSize) / float32(b.TextSize)).Fn).Fn(gtx)
|
|
// b.Window.Text().
|
|
// Alignment(text.Middle).
|
|
// Fn(gtx, b.shaper, b.font, b.textSize, b.text)
|
|
},
|
|
)
|
|
}
|
|
bl.Embed(fn)
|
|
return bl.Fn(gtx)
|
|
}
|
|
|
|
func drawInk(c l.Context, p press) {
|
|
// duration is the number of seconds for the completed animation: expand while
|
|
// fading in, then out.
|
|
const (
|
|
expandDuration = float32(0.5)
|
|
fadeDuration = float32(0.9)
|
|
)
|
|
now := c.Now
|
|
t := float32(now.Sub(p.Start).Seconds())
|
|
end := p.End
|
|
if end.IsZero() {
|
|
// If the press hasn't ended, don't fade-out.
|
|
end = now
|
|
}
|
|
endt := float32(end.Sub(p.Start).Seconds())
|
|
// Compute the fade-in/out position in [0;1].
|
|
var alphat float32
|
|
{
|
|
var haste float32
|
|
if p.Cancelled {
|
|
// If the press was cancelled before the inkwell was fully faded in, fast
|
|
// forward the animation to match the fade-out.
|
|
if h := 0.5 - endt/fadeDuration; h > 0 {
|
|
haste = h
|
|
}
|
|
}
|
|
// Fade in.
|
|
half1 := t/fadeDuration + haste
|
|
if half1 > 0.5 {
|
|
half1 = 0.5
|
|
}
|
|
// Fade out.
|
|
half2 := float32(now.Sub(end).Seconds())
|
|
half2 /= fadeDuration
|
|
half2 += haste
|
|
if half2 > 0.5 {
|
|
// Too old.
|
|
return
|
|
}
|
|
|
|
alphat = half1 + half2
|
|
}
|
|
// Compute the expand position in [0;1].
|
|
sizet := t
|
|
if p.Cancelled {
|
|
// Freeze expansion of cancelled presses.
|
|
sizet = endt
|
|
}
|
|
sizet /= expandDuration
|
|
|
|
// Animate only ended presses, and presses that are fading in.
|
|
if !p.End.IsZero() || sizet <= 1.0 {
|
|
c.Execute(op.InvalidateCmd{})
|
|
}
|
|
|
|
if sizet > 1.0 {
|
|
sizet = 1.0
|
|
}
|
|
if alphat > .5 {
|
|
// Start fadeout after half the animation.
|
|
alphat = 1.0 - alphat
|
|
}
|
|
// Twice the speed to attain fully faded in at 0.5.
|
|
t2 := alphat * 2
|
|
// Beziér ease-in curve.
|
|
alphaBezier := t2 * t2 * (3.0 - 2.0*t2)
|
|
sizeBezier := sizet * sizet * (3.0 - 2.0*sizet)
|
|
size := c.Constraints.Min.X
|
|
if h := c.Constraints.Min.Y; h > size {
|
|
size = h
|
|
}
|
|
// Cover the entire constraints min rectangle and
|
|
// apply curve values to size and color.
|
|
size = int(float32(size) * 2 * float32(math.Sqrt(2)) * sizeBezier)
|
|
alpha := 0.7 * alphaBezier
|
|
const col = 0.8
|
|
ba, bc := byte(alpha*0xff), byte(col*0xff)
|
|
rgba := f32color.MulAlpha(color.NRGBA{A: 0xff, R: bc, G: bc, B: bc}, ba)
|
|
ink := paint.ColorOp{Color: rgba}
|
|
ink.Add(c.Ops)
|
|
rr := size / 2
|
|
defer op.Offset(p.Position.Add(image.Point{
|
|
X: -rr,
|
|
Y: -rr,
|
|
})).Push(c.Ops).Pop()
|
|
defer clip.UniformRRect(image.Rectangle{Max: image.Pt(size, size)}, rr).Push(c.Ops).Pop()
|
|
paint.PaintOp{}.Add(c.Ops)
|
|
}
|