90 lines
2.2 KiB
Go
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
|
|
}
|