Files
8bitMyGoUI/text/odometer.go
T

90 lines
2.2 KiB
Go

package text
import (
"fmt"
"math"
"strconv"
"github.com/egoist/mygo/ui"
"git.zacharyzhang.com/ZacharyZhang-NY/8bitMyGoUI/pixel"
)
type OdometerOptions struct {
// Digits is zero for as many as value has.
Digits int
Slot pixel.Slot
}
type odoKey struct{}
type odoState struct {
shown float64
last int
}
const odoCell = 10
// Odometer rolls its digits toward value, step by step.
func Odometer(c *ui.Context, value int, o OdometerOptions) *ui.Element {
if value < 0 {
panic(fmt.Sprintf("text: Odometer value %d is negative", value))
}
k := pixel.K(c)
digits := orDef("Odometer Digits", o.Digits, len(strconv.Itoa(value)))
if value >= int(math.Pow10(digits)) {
panic(fmt.Sprintf("text: Odometer value %d needs more than %d digits", value, digits))
}
h := pixel.LineHeight + 2
e := ui.Box(c).Size(k.D(digits*odoCell), k.D(h)).Label(strconv.Itoa(value)).Role(ui.RoleText)
st := ui.Local(e, odoKey{}, func() odoState { return odoState{shown: float64(value), last: -1} })
target := float64(value)
switch {
case k.Reduce() || st.shown == target:
st.shown, st.last = target, -1
default:
tick := k.Tick(c, 30)
if st.last >= 0 {
for range tick - st.last {
st.shown = roll(st.shown, target)
}
}
st.last = tick
}
shown := st.shown
return e.Draw(func(p *ui.Painter, r ui.Rect) {
g := k.Canvas(p, r)
for i := range digits {
x := (digits - 1 - i) * odoCell
d, frac := wheel(shown, i)
g.Frame(x, 0, odoCell-1, h, pixel.Sunken, pixel.Well)
g.Clip(x+2, 2, odoCell-5, h-4, func() {
y := 1 - int(frac*float64(h))
g.Text(x+2, y, strconv.Itoa(d), o.Slot, 1)
g.Text(x+2, y+h, strconv.Itoa((d+1)%10), o.Slot, 1)
})
}
})
}
// roll moves shown a step toward target, faster when far.
func roll(shown, target float64) float64 {
diff := target - shown
stride := max(0.25, math.Abs(diff)/6)
if math.Abs(diff) <= stride {
return target
}
return shown + math.Copysign(stride, diff)
}
// wheel is digit i of shown and its roll.
func wheel(shown float64, i int) (d int, frac float64) {
place := math.Pow10(i)
v := shown / place
d = int(math.Floor(v)) % 10
if lower := math.Mod(shown, place); lower > place-1 {
frac = lower - (place - 1)
}
return d, frac
}