Add input components 086-105; park focus off disabled controls

This commit is contained in:
ZacharyZhang-NY committed 2026-10-11 20:13:21 -04:00
1 parent 095eab2b4c
commit 03b789f533
34 files changed
+3058 -43

No files matched your search

+118
View File
@@ -0,0 +1,118 @@
package input
import (
"fmt"
"log/slog"
"slices"
"github.com/egoist/mygo/ui"
"git.zacharyzhang.com/ZacharyZhang-NY/8bitMyGoUI/pixel"
)
type CheatCodeOptions struct {
// Sequence is the code; nil means ↑↑↓↓←→←→BA.
Sequence []ui.Key
Disabled bool
}
type CheatCodeResult struct {
Element *ui.Element
Unlocked bool
}
var konami = []ui.Key{ui.KeyUp, ui.KeyUp, ui.KeyDown, ui.KeyDown, ui.KeyLeft, ui.KeyRight, ui.KeyLeft, ui.KeyRight, ui.KeyB, ui.KeyA}
type cheatKey struct{}
// cheat is the key trail and an unreported win.
type cheat struct {
trail []ui.Key
won, fresh bool
}
// CheatCode watches keys for a sequence and shows the trail.
func CheatCode(c *ui.Context, text string, o CheatCodeOptions) CheatCodeResult {
k := pixel.K(c)
seq := o.Sequence
if seq == nil {
seq = konami
}
shell, base, st := pixel.Shell(c, func() *ui.Element {
return ui.Box(c).Focusable().Role(ui.RoleGroup).Label(text).Disabled(o.Disabled)
})
s := ui.Local(base, cheatKey{}, func() cheat { return cheat{} })
// Keys come in order here, however fast they are typed.
base.HandleInput(func(ev ui.InputEvent) bool {
if ev.Kind != ui.InputKeyDown || ev.Mods != 0 {
return false
}
s.won, s.trail = false, append(s.trail, ev.Key)
s.trail = s.trail[max(0, len(s.trail)-len(seq)):]
if slices.Equal(s.trail, seq) {
s.won, s.fresh, s.trail = true, true, nil
}
// Other keys break the code yet pass on, like Tab.
return slices.Contains(seq, ev.Key)
})
res := CheatCodeResult{Element: shell, Unlocked: s.fresh}
s.fresh = false
if res.Unlocked {
slog.Debug("8bit: cheat unlocked", "label", text)
}
got := matched(s.trail, seq)
base.Description(fmt.Sprintf("%d/%d", got, len(seq)))
trail, won := s.trail, s.won
shell.Size(k.D(len(seq)*10+4), k.D(14+pixel.LineHeight)).Draw(func(p *ui.Painter, r ui.Rect) {
g := k.Canvas(p, r)
g.Frame(0, 0, g.W, 14, pixel.Sunken, pixel.Well)
// With no trail yet, the code itself waits in Shade.
keys, ink := trail, pixel.Faint
switch {
case won:
keys, ink = seq, pixel.Good
g.Text(2, 14, k.Msg("lives"), pixel.Good, 1)
case len(trail) == 0:
keys, ink = seq, pixel.Shade
}
for i, key := range keys {
kink := ink
if len(trail) > 0 && i >= len(trail)-got {
kink = pixel.Accent
}
glyph(g, 2+i*10, 4, key, kink)
}
finish(g, st)
})
return res
}
// matched is the longest end of trail that starts seq.
func matched(trail, seq []ui.Key) int {
for n := min(len(trail), len(seq)); n > 0; n-- {
if slices.Equal(trail[len(trail)-n:], seq[:n]) {
return n
}
}
return 0
}
// glyph draws a key as an arrow or micro letter.
func glyph(g pixel.Canvas, x, y int, key ui.Key, ink pixel.Slot) {
switch {
case key == ui.KeyUp:
g.Sprite(x+2, y+1, tinyUp, 0, ink, 1)
case key == ui.KeyDown:
g.Sprite(x+2, y+1, tinyDown, 0, ink, 1)
case key == ui.KeyLeft:
g.Sprite(x+3, y, tinyLeft, 0, ink, 1)
case key == ui.KeyRight:
g.Sprite(x+3, y, tinyRight, 0, ink, 1)
case key >= ui.KeyA && key <= ui.KeyZ:
g.Micro(x+2, y, string(rune('A'+key-ui.KeyA)), ink, 1)
case key >= ui.Key0 && key <= ui.Key9:
g.Micro(x+2, y, string(rune('0'+key-ui.Key0)), ink, 1)
default:
g.Rect(x+2, y+1, 3, 3, ink)
}
}