Files
8bitMyGoUI/text/typewriter.go
T

102 lines
2.2 KiB
Go

package text
import (
"log/slog"
"github.com/egoist/mygo/ui"
"git.zacharyzhang.com/ZacharyZhang-NY/8bitMyGoUI/pixel"
)
var more = pixel.MustSprite(`
#####
.###.
..#..
`)
type TypewriterOptions struct {
// CPS is characters per second; zero means 20.
CPS int
Slot pixel.Slot
Disabled bool
}
type TypewriterResult struct {
Element *ui.Element
// Done is true in the frame the whole text shows.
Done bool
}
type typeKey struct{}
type typeState struct {
text string
start, last int
done bool
}
// Typewriter reveals s; a click or Enter shows the rest.
func Typewriter(c *ui.Context, s string, o TypewriterOptions) TypewriterResult {
k := pixel.K(c)
cps := orDef("Typewriter CPS", o.CPS, 20)
shell, base, st := pixel.Shell(c, func() *ui.Element {
e := ui.Box(c).Role(ui.RoleText).Label(s).Disabled(o.Disabled)
if !o.Disabled {
e.Focusable()
}
return e
})
ts := ui.Local(shell, typeKey{}, func() typeState { return typeState{start: -1} })
if ts.text != s {
*ts = typeState{text: s, start: -1}
}
// Lines leave room for the mark; glyphs count, not runes.
w, check := pixel.WrapWidth(c, shell)
lines := pixel.Wrap(s, max(0, w-8), 0)
total := 0
for _, l := range lines {
total += len([]rune(l))
}
res := TypewriterResult{Element: shell}
if !ts.done {
ts.last = total
if !k.Reduce() {
tick := k.Tick(c, cps)
if ts.start < 0 {
ts.start = tick
}
ts.last = min(total, tick-ts.start)
}
if base.Clicked() {
ts.last = total
}
if ts.last == total {
ts.done, res.Done = true, true
slog.Debug("8bit: typewriter done", "glyphs", total)
}
}
shown := ts.last
if ts.done {
shown = total
}
shell.FillWidth().Height(k.D(len(lines) * pixel.LineHeight)).Draw(func(p *ui.Painter, r ui.Rect) {
check(r)
g := k.Canvas(p, r)
left, end := shown, 0
for i, l := range lines {
lr := []rune(l)
n := min(len(lr), max(0, left))
g.Text(0, i*pixel.LineHeight, string(lr[:n]), o.Slot, 1)
left -= len(lr)
end = pixel.Measure(string(lr[:n]))
}
if shown == total && g.Step(2)%2 == 0 {
g.Sprite(end+3, len(lines)*pixel.LineHeight-6, more, 0, pixel.Accent, 1)
}
if st.Focus {
g.FocusMark(0, 0, g.W, g.H)
}
})
return res
}