104 lines
2.7 KiB
Go
104 lines
2.7 KiB
Go
package input
|
|
|
|
import (
|
|
"fmt"
|
|
"log/slog"
|
|
"strconv"
|
|
"time"
|
|
|
|
"github.com/egoist/mygo/ui"
|
|
|
|
"git.zacharyzhang.com/ZacharyZhang-NY/8bitMyGoUI/pixel"
|
|
)
|
|
|
|
type StepperOptions struct {
|
|
Min, Max int
|
|
// Step is zero for 1.
|
|
Step int
|
|
Disabled bool
|
|
}
|
|
|
|
type StepperResult struct {
|
|
Element *ui.Element
|
|
Changed bool
|
|
}
|
|
|
|
// Stepper is ◀ value ▶; held arrows repeat, keys step.
|
|
func Stepper(c *ui.Context, text string, value *int, o StepperOptions) StepperResult {
|
|
if o.Max < o.Min {
|
|
panic(fmt.Sprintf("input: Stepper range %d to %d is empty", o.Min, o.Max))
|
|
}
|
|
k := pixel.K(c)
|
|
step := pixel.Or("Stepper Step", o.Step, 1)
|
|
old := *value
|
|
set := func(v int) { *value = max(o.Min, min(o.Max, v)) }
|
|
w := max(pixel.Measure(strconv.Itoa(o.Min)), pixel.Measure(strconv.Itoa(o.Max))) + 8
|
|
e := ui.Row(c).Gap(k.D(2)).AlignItems(ui.Center).Children(func() {
|
|
set(*value - step*held(c, k, tinyLeft, o.Disabled))
|
|
shell, base, st := pixel.Shell(c, func() *ui.Element {
|
|
return ui.Box(c).Focusable().Role(ui.RoleStepper).Label(text).Description(strconv.Itoa(*value)).Disabled(o.Disabled)
|
|
})
|
|
if !o.Disabled {
|
|
if base.Shortcut(0, ui.KeyLeft) || base.Shortcut(0, ui.KeyDown) {
|
|
set(*value - step)
|
|
}
|
|
if base.Shortcut(0, ui.KeyRight) || base.Shortcut(0, ui.KeyUp) {
|
|
set(*value + step)
|
|
}
|
|
}
|
|
shell.Size(k.D(w), k.D(pixel.LineHeight+4)).Draw(func(p *ui.Painter, r ui.Rect) {
|
|
g := k.Canvas(p, r)
|
|
shown := strconv.Itoa(*value)
|
|
g.Frame(0, 0, g.W, g.H, pixel.Sunken, pixel.Well)
|
|
g.Text((g.W-pixel.Measure(shown))/2, 2, shown, pixel.Ink, 1)
|
|
g.Finish(st)
|
|
})
|
|
set(*value + step*held(c, k, tinyRight, o.Disabled))
|
|
base.Description(strconv.Itoa(*value))
|
|
})
|
|
res := StepperResult{Element: e, Changed: *value != old}
|
|
if res.Changed {
|
|
slog.Debug("8bit: stepper", "label", text, "value", *value)
|
|
}
|
|
return res
|
|
}
|
|
|
|
type holdKey struct{}
|
|
|
|
type hold struct {
|
|
since time.Time
|
|
n int
|
|
}
|
|
|
|
// held counts an arrow's steps: one, then ten a second.
|
|
func held(c *ui.Context, k pixel.Kit, sp *pixel.Sprite, disabled bool) int {
|
|
b := ui.Box(c).Size(k.D(12), k.D(pixel.LineHeight+4)).Role(ui.RoleNone)
|
|
down := b.Pressed() && !disabled
|
|
h := ui.Local(b, holdKey{}, func() hold { return hold{} })
|
|
b.Draw(func(p *ui.Painter, r ui.Rect) {
|
|
g := k.Canvas(p, r)
|
|
style, dy := pixel.Raised, 0
|
|
if down {
|
|
style, dy = pixel.Sunken, 1
|
|
}
|
|
g.Frame(0, 0, g.W, g.H, style, pixel.Face)
|
|
g.Sprite((g.W-sp.W)/2, (g.H-sp.H)/2+dy, sp, 0, pixel.Ink, 1)
|
|
if disabled {
|
|
g.Checker(0, 0, g.W, g.H, pixel.Face)
|
|
}
|
|
})
|
|
if !down {
|
|
*h = hold{}
|
|
return 0
|
|
}
|
|
k.Tick(c, 10)
|
|
if h.since.IsZero() {
|
|
*h = hold{since: k.Now(), n: 1}
|
|
return 1
|
|
}
|
|
want := 1 + max(0, int((k.Now().Sub(h.since)-400*time.Millisecond)/(100*time.Millisecond)))
|
|
n := want - h.n
|
|
h.n = want
|
|
return n
|
|
}
|