Files
MujicaUI/skeleton.go
T

92 lines
2.5 KiB
Go

package mujica
import (
"fmt"
"math"
"github.com/egoist/mygo/ui"
"mujicaui/theme"
)
// SkeletonShape is the placeholder's form. The zero value is SkeletonText.
type SkeletonShape int
const (
SkeletonText SkeletonShape = iota
SkeletonCircle
SkeletonRect
)
// SkeletonOptions configure a Skeleton. Sizes are fixed so content does not shift.
type SkeletonOptions struct {
Shape SkeletonShape
// Width fills the parent when zero; a circle uses it as its diameter (40 DIP when zero).
Width float32
// Height is the rect height (ControlHeight when zero); text derives it from Lines.
Height float32
// Lines is the text line count, 1 when zero; the last of several is shorter.
Lines int
}
// Skeleton stands in for loading content; it pulses while painted, static under reduced motion.
func Skeleton(c *ui.Context, opts SkeletonOptions) *ui.Element {
if opts.Shape != SkeletonText && opts.Shape != SkeletonCircle && opts.Shape != SkeletonRect {
panic(fmt.Sprintf("mujica: unknown SkeletonShape %d", int(opts.Shape)))
}
if !(opts.Width >= 0) || !(opts.Height >= 0) || opts.Lines < 0 || math.IsInf(float64(opts.Width+opts.Height), 1) {
panic(fmt.Sprintf("mujica: Skeleton size %vx%v, %d lines invalid", opts.Width, opts.Height, opts.Lines))
}
k := Tokens(c)
still := c.Preferences().ReduceMotion
fill := k.SurfaceHover
if k.Dark {
fill = k.Border
}
paint := func(p *ui.Painter, r ui.Rect, radius float32) {
a := float32(1)
if !still {
p.AnimationFrame()
t := float64(p.Now().UnixMilli()%1600) / 1600
a = 0.55 + 0.45*float32(0.5+0.5*math.Cos(2*math.Pi*t))
}
p.Fill(r, fill.Alpha(a), radius)
}
e := ui.Box(c).Role(ui.RoleNone)
if opts.Width > 0 {
e.Width(opts.Width)
} else if opts.Shape != SkeletonCircle {
e.FillWidth()
}
switch opts.Shape {
case SkeletonCircle:
d := opts.Width
if d == 0 {
d = 40
}
e.Size(d, d).Shrink(0).Draw(func(p *ui.Painter, r ui.Rect) { paint(p, r, r.W/2) })
case SkeletonRect:
h := opts.Height
if h == 0 {
h = Density(c).ControlHeight()
}
e.Height(h).Draw(func(p *ui.Painter, r ui.Rect) { paint(p, r, theme.CardRadius) })
default:
lines := max(opts.Lines, 1)
size := FontSize(c, theme.BodySize)
line := size * theme.LineHeight
e.Height(line * float32(lines)).Draw(func(p *ui.Painter, r ui.Rect) {
bar := size * 0.8
for i := range lines {
w := r.W
if lines > 1 && i == lines-1 {
w *= 0.6
}
y := r.Y + float32(i)*line + (line-bar)/2
paint(p, ui.Rect{X: r.X, Y: y, W: w, H: bar}, theme.ControlRadius)
}
})
}
return e
}