Elements are now pass-scoped value handles: tests that read bounds after a frame bind a ui.Handle. pixel tells frames apart by the build pass's root, since the Tester clock no longer moves on its own. Fix the regressions the upgrade brought: - Clipboard reads in input handlers go through c.Services(). - MyGo notes an overlay's opener after building it, so AutoFocus inside lost it; pixel.AutoFocus focuses through a Handle, and archtest bans the raw call. Dialogs hand the focus back again. - Elements around a press stay hovered: Tooltip tells a press-given focus by focus arriving under the pointer. - The web host moves the Tester clock with real time, at most 500 ms a call, and gives components the real clock. The gallery's source index also accepts CRLF checkouts. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
124 lines
3.3 KiB
Go
124 lines
3.3 KiB
Go
package tabletop
|
||
|
||
import (
|
||
"fmt"
|
||
"log/slog"
|
||
"strconv"
|
||
|
||
"github.com/egoist/mygo/ui"
|
||
|
||
"git.zacharyzhang.com/ZacharyZhang-NY/8bitMyGoUI/icon"
|
||
"git.zacharyzhang.com/ZacharyZhang-NY/8bitMyGoUI/pixel"
|
||
)
|
||
|
||
type BingoCardOptions struct {
|
||
Disabled bool
|
||
}
|
||
|
||
type BingoCardResult struct {
|
||
Element ui.Element
|
||
// Bingo is the first full line, this frame.
|
||
Changed, Bingo bool
|
||
}
|
||
|
||
type bingoKey struct{}
|
||
|
||
// card is the cursor, and if a line was full.
|
||
type card struct {
|
||
cur int
|
||
line bool
|
||
}
|
||
|
||
const bingoW, bingoH = 18, 16
|
||
|
||
// BingoCard marks a 5×5 card; a full line is BINGO!
|
||
func BingoCard(c *ui.Context, text string, numbers [25]int, marked *[25]bool, o BingoCardOptions) BingoCardResult {
|
||
k := pixel.K(c)
|
||
// Cells print three digits at most; the middle is FREE.
|
||
for i, n := range numbers {
|
||
if i != 12 && (n < 1 || n > 999) {
|
||
panic(fmt.Sprintf("tabletop: BingoCard number %d at %d", n, i))
|
||
}
|
||
}
|
||
col := ui.Column(c).Gap(k.D(2)).AlignItems(ui.Center).AlignSelf(ui.Start)
|
||
st := ui.Local(col, bingoKey{}, func() card { return card{cur: 12} })
|
||
// The middle is FREE: always marked.
|
||
marked[12] = true
|
||
res := BingoCardResult{}
|
||
res.Element = col.Children(func() {
|
||
ui.Box(c).Size(k.D(5*bingoW), k.D(7)).Role(ui.RoleNone).Draw(func(p *ui.Painter, r ui.Rect) {
|
||
g := k.Canvas(p, r)
|
||
for i, l := range "BINGO" {
|
||
g.Micro(i*bingoW+7, 1, string(l), pixel.Accent, 1)
|
||
}
|
||
})
|
||
shell, base, ps, picked := pixel.Pad(c, text, 5, 25, bingoW, bingoH, &st.cur, o.Disabled)
|
||
if picked >= 0 && picked != 12 {
|
||
marked[picked], res.Changed = !marked[picked], true
|
||
}
|
||
say := k.Msg("free_space")
|
||
if st.cur != 12 {
|
||
say = strconv.Itoa(numbers[st.cur])
|
||
}
|
||
if marked[st.cur] {
|
||
say += ", " + k.Msg("marked")
|
||
}
|
||
base.Description(fmt.Sprintf("%c %s", "BINGO"[st.cur%5], say))
|
||
at, shown := st.cur, *marked
|
||
shell.Draw(func(p *ui.Painter, r ui.Rect) {
|
||
g := k.Canvas(p, r)
|
||
for i := range 25 {
|
||
x, y := i%5*bingoW, i/5*bingoH
|
||
// Cells share their edges; the last ones end inside.
|
||
w, h := bingoW+1, bingoH+1
|
||
if i%5 == 4 {
|
||
w = bingoW
|
||
}
|
||
if i/5 == 4 {
|
||
h = bingoH
|
||
}
|
||
g.Frame(x, y, w, h, pixel.Flat, pixel.Lit)
|
||
if i == at && ps.Focus {
|
||
g.Cursor(x+1, y+1, bingoW-1, bingoH-1)
|
||
}
|
||
if shown[i] && i != 12 {
|
||
// A dauber's blot, dithered so the number reads.
|
||
g.Disc(x+9, y+8, 6, pixel.Magic)
|
||
g.Veil(x+3, y+2, 13, 13, pixel.Lit, 6)
|
||
}
|
||
// FREE is a star; it fits in any tongue.
|
||
if i == 12 {
|
||
g.Sprite(x+3, y+2, icon.Star, 0, pixel.Accent, 1)
|
||
continue
|
||
}
|
||
s := strconv.Itoa(numbers[i])
|
||
g.Micro(x+(bingoW+1-pixel.MicroWidth(s))/2, y+6, s, pixel.Ink, 1)
|
||
}
|
||
g.Finish(ps)
|
||
})
|
||
line := full(marked)
|
||
res.Bingo, st.line = line && !st.line, line
|
||
if line {
|
||
pixel.Text(c, k.Msg("bingo"), pixel.TextOptions{Slot: pixel.Good, Scale: 2})
|
||
}
|
||
})
|
||
if res.Changed || res.Bingo {
|
||
slog.Debug("8bit: bingo card", "label", text, "cell", st.cur, "bingo", res.Bingo)
|
||
}
|
||
return res
|
||
}
|
||
|
||
// full reports a whole row, column or diagonal marked.
|
||
func full(m *[25]bool) bool {
|
||
lines := [][5]int{{0, 6, 12, 18, 24}, {4, 8, 12, 16, 20}}
|
||
for i := range 5 {
|
||
lines = append(lines, [5]int{5 * i, 5*i + 1, 5*i + 2, 5*i + 3, 5*i + 4}, [5]int{i, i + 5, i + 10, i + 15, i + 20})
|
||
}
|
||
for _, l := range lines {
|
||
if m[l[0]] && m[l[1]] && m[l[2]] && m[l[3]] && m[l[4]] {
|
||
return true
|
||
}
|
||
}
|
||
return false
|
||
}
|