Stack through StickyHeader, with behavior tests and gallery demos. kit gains Arrive, Settle (paint-time layout check) and Shade. Several actions return named ui.Element results; mygo vet forbids elements in structs. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
106 lines
2.5 KiB
Go
106 lines
2.5 KiB
Go
// Package kit holds helpers three or more components share.
|
|
package kit
|
|
|
|
import (
|
|
"log/slog"
|
|
"slices"
|
|
"time"
|
|
|
|
"git.zacharyzhang.com/ZacharyZhang-NY/CrychicUI/sound"
|
|
"github.com/egoist/mygo/ui"
|
|
)
|
|
|
|
// Press shrinks e while it is pressed.
|
|
func Press(e ui.Element) ui.Element {
|
|
p := ui.Pose{}
|
|
if e.Pressed() {
|
|
p.Scale = 0.97
|
|
}
|
|
return e.AnimatePose(p, ui.Snappy)
|
|
}
|
|
|
|
// Lift raises e 2 DIP while it is hovered.
|
|
func Lift(e ui.Element) ui.Element {
|
|
p := ui.Pose{}
|
|
if e.Hovered() {
|
|
p.Y = -2
|
|
}
|
|
return e.AnimatePose(p, ui.Snappy)
|
|
}
|
|
|
|
// Arrive eases from a pose to rest on first build.
|
|
func Arrive(e ui.Element, from ui.Pose, delay time.Duration) ui.Element {
|
|
p := e.Keyframes("crychic.arrive", []ui.Keyframe{{Value: 1}, {At: 200 * time.Millisecond, Value: 0, Ease: ui.EaseOut}},
|
|
ui.KeyframeOptions{Delay: delay})
|
|
s := from.Scale
|
|
if s == 0 {
|
|
s = 1
|
|
}
|
|
k := 1 + (s-1)*p
|
|
return e.Translate(from.X*p, from.Y*p).Scale(k, k).Opacity(1 - from.Fade*p)
|
|
}
|
|
|
|
var popSpring = ui.Bouncy
|
|
|
|
var (
|
|
// Enter fades an element in from 4 DIP below.
|
|
Enter = ui.ElementTransition{
|
|
Duration: 160 * time.Millisecond,
|
|
Enter: &ui.Motion{Y: 4},
|
|
Exit: &ui.Motion{Y: 4},
|
|
}
|
|
// Pop scales an element in and out with a bounce.
|
|
Pop = ui.ElementTransition{
|
|
Animation: &popSpring,
|
|
Enter: &ui.Motion{Scale: 0.9},
|
|
Exit: &ui.Motion{Scale: 0.9},
|
|
}
|
|
)
|
|
|
|
// Clicked reports e.Clicked and plays cue when true.
|
|
func Clicked(e ui.Element, cue sound.Cue) bool {
|
|
if !e.Clicked() {
|
|
return false
|
|
}
|
|
sound.Play(cue)
|
|
return true
|
|
}
|
|
|
|
// Changed reports e.Changed and plays cue when true.
|
|
func Changed(e ui.Element, cue sound.Cue) bool {
|
|
if !e.Changed() {
|
|
return false
|
|
}
|
|
sound.Play(cue)
|
|
return true
|
|
}
|
|
|
|
// Card returns the shared card surface.
|
|
func Card(c *ui.Context) ui.Element {
|
|
t := c.Theme()
|
|
return ui.Column(c).Background(t.Background).Border(1, t.Border).Radius(t.Radius).
|
|
Shadow(0, 1, 3, 0, Shade(0.06))
|
|
}
|
|
|
|
// Settle rebuilds when e's laid-out read differs from used.
|
|
func Settle(c *ui.Context, e ui.Element, used []float32, read func() []float32) {
|
|
e.DrawOver(func(*ui.Painter, ui.Rect) {
|
|
if !slices.Equal(read(), used) {
|
|
c.Invalidate()
|
|
}
|
|
})
|
|
}
|
|
|
|
// Shade is black at alpha a, for shadows.
|
|
func Shade(a float32) ui.Color { return ui.RGBA(0, 0, 0, a) }
|
|
|
|
// Muted returns secondary text.
|
|
func Muted(c *ui.Context, s string) ui.Element {
|
|
return ui.Text(c, s).TextColor(c.Theme().TextMuted)
|
|
}
|
|
|
|
// Log records a component state change at debug level.
|
|
func Log(component, event string, args ...any) {
|
|
slog.Debug("crychic: "+component+" "+event, args...)
|
|
}
|