Upgrade MyGo to v0.5.5

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>
This commit is contained in:
ZacharyZhang-NYandClaude Opus 5.5 committed 2026-10-17 01:31:08 -04:00
1 parent 1f3d38110e
commit 03e1f91706
532 files changed
+828 -735

No files matched your search

+2 -2
View File
@@ -24,7 +24,7 @@ type AlertDialogOptions struct {
}
type AlertDialogResult struct {
Element *ui.Element
Element ui.Element
// Pressed is the button pressed this frame, or -1.
Pressed int
Closed bool
@@ -46,7 +46,7 @@ func AlertDialog(c *ui.Context, title, message string, open *bool, o AlertDialog
was := *open
res := AlertDialogResult{Pressed: -1}
press := func(i int) { res.Pressed, *open = i, false }
res.Element = ui.DialogBase(c, open, func(back, panel *ui.Element) {
res.Element = ui.DialogBase(c, open, func(back, panel ui.Element) {
dim(k, back, pixel.Back, 8)
panel.Width(k.D(220)).Role(ui.RoleAlertDialog).Label(title).Description(message).Focusable().FocusRing(false)
if pixel.Shortcut(panel, 0, ui.KeyEnter) && !o.Disabled && !pixel.Current(c).Inert {
+2 -2
View File
@@ -15,7 +15,7 @@ type ChoicePromptOptions struct {
}
type ChoicePromptResult struct {
Element *ui.Element
Element ui.Element
// Chosen is the choice taken this frame, or -1.
Chosen int
// Canceled is Escape or a click outside.
@@ -34,7 +34,7 @@ func ChoicePrompt(c *ui.Context, question string, choices []string, open *bool,
cur := ui.Local(home, choiceKey{}, func() int { return 0 })
was := *open
res := ChoicePromptResult{Chosen: -1}
res.Element = ui.DialogBase(c, open, func(back, panel *ui.Element) {
res.Element = ui.DialogBase(c, open, func(back, panel ui.Element) {
dim(k, back, pixel.Back, 4)
panel.Width(k.D(180)).Label(question).Focusable().FocusRing(false).Children(func() {
pixel.Harbor(c, panel, func() {
+2 -2
View File
@@ -16,7 +16,7 @@ type CoachmarkOptions struct {
}
type CoachmarkResult struct {
Element *ui.Element
Element ui.Element
Next bool
}
@@ -24,7 +24,7 @@ type CoachmarkResult struct {
const coachWidth = 140
// Coachmark points a bubble at target: step count, NEXT ▶.
func Coachmark(c *ui.Context, target *ui.Element, message string, step, steps int, o CoachmarkOptions) CoachmarkResult {
func Coachmark(c *ui.Context, target ui.Element, message string, step, steps int, o CoachmarkOptions) CoachmarkResult {
k := pixel.K(c)
if step < 1 || step > steps {
panic(fmt.Sprintf("overlay: Coachmark step %d of %d", step, steps))
+2 -2
View File
@@ -15,7 +15,7 @@ type ConfirmDialogOptions struct {
}
type ConfirmDialogResult struct {
Element *ui.Element
Element ui.Element
// Canceled is NO, Escape or a click outside.
Confirmed, Canceled bool
}
@@ -29,7 +29,7 @@ func ConfirmDialog(c *ui.Context, message string, open *bool, o ConfirmDialogOpt
cur := ui.Local(home, confirmKey{}, func() int { return 1 })
was := *open
var res ConfirmDialogResult
res.Element = ui.DialogBase(c, open, func(back, panel *ui.Element) {
res.Element = ui.DialogBase(c, open, func(back, panel ui.Element) {
dim(k, back, pixel.Back, 8)
panel.Width(k.D(180)).Label(k.Msg("sure")).Description(message).Focusable().FocusRing(false).Children(func() {
pixel.Harbor(c, panel, func() {
+2 -2
View File
@@ -20,7 +20,7 @@ type DialogOptions struct {
type DialogResult struct {
// Element is the window, nil while closed.
Element *ui.Element
Element ui.Element
// Action is the button clicked this frame, or -1.
Action int
// Closed is Escape or a click outside.
@@ -33,7 +33,7 @@ func Dialog(c *ui.Context, title string, open *bool, o DialogOptions, content fu
w := pixel.Or("Dialog Width", o.Width, 220)
was := *open
res := DialogResult{Action: -1}
res.Element = ui.DialogBase(c, open, func(back, panel *ui.Element) {
res.Element = ui.DialogBase(c, open, func(back, panel ui.Element) {
dim(k, back, pixel.Back, 8)
// The panel holds the focus when nothing inside can.
panel.Width(k.D(w)).Label(title).Focusable().FocusRing(false).Children(func() {
+1 -1
View File
@@ -29,7 +29,7 @@ type DialogueBoxOptions struct {
}
type DialogueBoxResult struct {
Element *ui.Element
Element ui.Element
// Changed turns a page; Done passes the last one.
Changed, Done bool
}
+2 -2
View File
@@ -19,7 +19,7 @@ type DrawerOptions struct {
}
type DrawerResult struct {
Element *ui.Element
Element ui.Element
// Closed is Escape or a click outside.
Closed bool
}
@@ -35,7 +35,7 @@ func Drawer(c *ui.Context, text string, open *bool, o DrawerOptions, content fun
off := k.D(arriving(c, home, *open) * size / 4)
was := *open
var res DrawerResult
res.Element = ui.DialogBase(c, open, func(back, panel *ui.Element) {
res.Element = ui.DialogBase(c, open, func(back, panel ui.Element) {
dim(k, back, pixel.Back, 8)
panel.Absolute().Label(text).Padding(k.D(8)).Focusable().FocusRing(false)
switch o.Side {
+42
View File
@@ -0,0 +1,42 @@
package overlay_test
import (
"testing"
"github.com/egoist/mygo/ui"
"git.zacharyzhang.com/ZacharyZhang-NY/8bitMyGoUI/input"
"git.zacharyzhang.com/ZacharyZhang-NY/8bitMyGoUI/overlay"
)
func TestDialogsHandTheFocusBack(t *testing.T) {
kinds := map[string]func(c *ui.Context, open *bool){
"confirm": func(c *ui.Context, open *bool) {
overlay.ConfirmDialog(c, "Sure?", open, overlay.ConfirmDialogOptions{})
},
"choice": func(c *ui.Context, open *bool) {
overlay.ChoicePrompt(c, "Pick", []string{"A", "B"}, open, overlay.ChoicePromptOptions{})
},
"prompt": func(c *ui.Context, open *bool) {
v := ""
overlay.PromptDialog(c, "Name", &v, open, overlay.PromptDialogOptions{})
},
"pause": func(c *ui.Context, open *bool) { overlay.PauseMenu(c, open, overlay.PauseMenuOptions{}) },
}
for name, show := range kinds {
open := false
tt := view(500, 400, newClock(), false, func(c *ui.Context) {
if input.Button(c, "Opener", input.ButtonOptions{}).Clicked {
open = true
}
show(c, &open)
})
if err := tt.Click("Opener"); err != nil || !open || tt.Focused("Opener") {
t.Fatalf("%s: did not open with the focus inside (%v)", name, err)
}
tt.Key(0, ui.KeyEscape)
if open || !tt.Focused("Opener") {
t.Fatalf("%s: Escape left it open or the focus lost", name)
}
}
}
+1 -1
View File
@@ -14,7 +14,7 @@ type HoverCardOptions struct {
type hoverKey struct{}
// HoverCard shows card below trigger while either is hovered.
func HoverCard(c *ui.Context, text string, o HoverCardOptions, trigger func(), card func()) *ui.Element {
func HoverCard(c *ui.Context, text string, o HoverCardOptions, trigger func(), card func()) ui.Element {
k := pixel.K(c)
w := pixel.Or("HoverCard Width", o.Width, 160)
wrap := ui.Box(c)
+1 -1
View File
@@ -15,7 +15,7 @@ type KeyHint struct {
type KeyHintsOptions struct{}
// KeyHints shows controls, as Ⓐ CONFIRM Ⓑ BACK.
func KeyHints(c *ui.Context, hints []KeyHint, o KeyHintsOptions) *ui.Element {
func KeyHints(c *ui.Context, hints []KeyHint, o KeyHintsOptions) ui.Element {
k := pixel.K(c)
return ui.Row(c).Gap(k.D(10)).Padding(k.D(3), k.D(6)).AlignItems(ui.Center).Role(ui.RoleGroup).Draw(func(p *ui.Painter, r ui.Rect) {
g := k.Canvas(p, r)
+2 -2
View File
@@ -16,7 +16,7 @@ type LightboxOptions struct {
}
type LightboxResult struct {
Element *ui.Element
Element ui.Element
// Changed turns the picture; Closed is ×, Escape or outside.
Changed, Closed bool
}
@@ -31,7 +31,7 @@ func Lightbox(c *ui.Context, text string, n int, at *int, open *bool, o Lightbox
off := o.Disabled || pixel.Current(c).Inert
turn := func(d int) { *at = (*at + d + n) % n }
var res LightboxResult
res.Element = ui.DialogBase(c, open, func(back, panel *ui.Element) {
res.Element = ui.DialogBase(c, open, func(back, panel ui.Element) {
deep, light := dark(k)
dim(k, back, deep, 13)
left, right := pixel.Shortcut(panel, 0, ui.KeyLeft), pixel.Shortcut(panel, 0, ui.KeyRight)
+1 -1
View File
@@ -31,7 +31,7 @@ type NotificationsOptions struct {
type NotificationsResult struct {
// Element holds the cards on screen.
Element *ui.Element
Element ui.Element
// Dismissed is the card × closed this frame, or nil.
Dismissed *Notification
}
+9 -8
View File
@@ -10,7 +10,7 @@ import (
)
// dim checkers the backdrop of a dialog.
func dim(k pixel.Kit, back *ui.Element, s pixel.Slot, level int) {
func dim(k pixel.Kit, back ui.Element, s pixel.Slot, level int) {
back.Draw(func(p *ui.Painter, r ui.Rect) {
g := k.Canvas(p, r)
g.Veil(0, 0, g.W, g.H, s, level)
@@ -33,7 +33,7 @@ func choose(c *ui.Context, text string, items []string, cur *int, digits, off bo
off = off || pixel.Current(c).Inert
box := ui.Column(c).FocusRing(false).Role(ui.RoleList).Label(text)
if !off {
box.Focusable().AutoFocus()
pixel.AutoFocus(c, box.Focusable())
}
pixel.Park(c, box, off)
picked := -1
@@ -92,23 +92,24 @@ type hintKey struct{}
// hinting is when hover began, and a press-given focus.
type hinting struct {
since time.Time
pressed bool
since time.Time
focus, pressed bool
}
// hint holds after resting wait, or on keyboard focus.
func hint(c *ui.Context, e *ui.Element, wait time.Duration) bool {
func hint(c *ui.Context, e ui.Element, wait time.Duration) bool {
k := pixel.K(c)
s := ui.Local(e, hintKey{}, func() hinting { return hinting{} })
_, _, over := e.PointerPosition()
hovered, focus := e.Hovered(), e.FocusWithin()
switch {
case over && !hovered:
// A press inside: its focus is not the keyboard's.
case focus && !s.focus && over:
// Focus arriving under the pointer came from a press.
s.pressed = true
case !focus:
s.pressed = false
}
s.focus = focus
rested := false
if hovered {
if s.since.IsZero() {
@@ -123,7 +124,7 @@ func hint(c *ui.Context, e *ui.Element, wait time.Duration) bool {
}
// arriving counts the steps, of four, a panel still slides.
func arriving(c *ui.Context, home *ui.Element, open bool) int {
func arriving(c *ui.Context, home ui.Element, open bool) int {
k := pixel.K(c)
on := 0
if open {
+2 -2
View File
@@ -13,7 +13,7 @@ type PauseMenuOptions struct {
}
type PauseMenuResult struct {
Element *ui.Element
Element ui.Element
// Chosen is RESUME 0, OPTIONS 1, QUIT 2, or -1.
Chosen int
}
@@ -28,7 +28,7 @@ func PauseMenu(c *ui.Context, paused *bool, o PauseMenuOptions) PauseMenuResult
was := *paused
res := PauseMenuResult{Chosen: -1}
items := []string{k.Msg("resume"), k.Msg("options"), k.Msg("quit")}
res.Element = ui.DialogBase(c, paused, func(back, panel *ui.Element) {
res.Element = ui.DialogBase(c, paused, func(back, panel ui.Element) {
deep, light := dark(k)
dim(k, back, deep, 10)
title := k.Msg("paused")
+1 -1
View File
@@ -14,7 +14,7 @@ type PopconfirmOptions struct {
}
type PopconfirmResult struct {
Element *ui.Element
Element ui.Element
// Canceled is NO, Escape or a click outside.
Confirmed, Canceled bool
}
+2 -2
View File
@@ -15,11 +15,11 @@ type PopoverOptions struct {
const notch = 4
// Popover opens a panel below anchor, notched toward it.
func Popover(c *ui.Context, text string, anchor *ui.Element, open *bool, o PopoverOptions, content func()) *ui.Element {
func Popover(c *ui.Context, text string, anchor ui.Element, open *bool, o PopoverOptions, content func()) ui.Element {
k := pixel.K(c)
w := pixel.Or("Popover Width", o.Width, 0)
ab := anchor.Bounds()
return ui.PopoverBase(c, anchor, open, func(panel *ui.Element) {
return ui.PopoverBase(c, anchor, open, func(panel ui.Element) {
if w > 0 {
panel.Width(k.D(w))
}
+2 -2
View File
@@ -15,7 +15,7 @@ type ProgressDialogOptions struct {
}
type ProgressDialogResult struct {
Element *ui.Element
Element ui.Element
// Canceled is CANCEL, Escape or a click outside.
Canceled bool
}
@@ -25,7 +25,7 @@ func ProgressDialog(c *ui.Context, title string, value float64, open *bool, o Pr
k := pixel.K(c)
was := *open
var res ProgressDialogResult
res.Element = ui.DialogBase(c, open, func(back, panel *ui.Element) {
res.Element = ui.DialogBase(c, open, func(back, panel ui.Element) {
dim(k, back, pixel.Back, 8)
panel.Width(k.D(200)).Label(title).Focusable().FocusRing(false).Children(func() {
pixel.Harbor(c, panel, func() {
+3 -3
View File
@@ -17,7 +17,7 @@ type PromptDialogOptions struct {
}
type PromptDialogResult struct {
Element *ui.Element
Element ui.Element
// Canceled is CANCEL, Escape or a click outside.
Submitted, Canceled bool
}
@@ -27,14 +27,14 @@ func PromptDialog(c *ui.Context, title string, value *string, open *bool, o Prom
k := pixel.K(c)
was := *open
var res PromptDialogResult
res.Element = ui.DialogBase(c, open, func(back, panel *ui.Element) {
res.Element = ui.DialogBase(c, open, func(back, panel ui.Element) {
dim(k, back, pixel.Back, 8)
panel.Width(k.D(220)).Label(title).Focusable().FocusRing(false).Children(func() {
pixel.Harbor(c, panel, func() {
layout.Window(c, layout.WindowOptions{Title: title}, func() {
ui.Column(c).Gap(k.D(6)).AlignItems(ui.Stretch).Children(func() {
f := input.TextInput(c, title, value, input.TextInputOptions{Placeholder: o.Placeholder, MaxLength: o.MaxLength, Width: 196, Disabled: o.Disabled})
f.Input.AutoFocus()
pixel.AutoFocus(c, f.Input)
// A Shortcut, not Submitted: the frame builds again closed.
if pixel.Shortcut(f.Element, 0, ui.KeyEnter) && !o.Disabled {
res.Submitted, *open = true, false
+2 -2
View File
@@ -15,7 +15,7 @@ type SheetOptions struct {
}
type SheetResult struct {
Element *ui.Element
Element ui.Element
// Closed is a drag down, Escape or a click outside.
Closed bool
}
@@ -32,7 +32,7 @@ func Sheet(c *ui.Context, text string, open *bool, o SheetOptions, content func(
rise := k.D(arriving(c, home, *open) * h / 4)
was := *open
var res SheetResult
res.Element = ui.DialogBase(c, open, func(back, panel *ui.Element) {
res.Element = ui.DialogBase(c, open, func(back, panel ui.Element) {
dim(k, back, pixel.Back, 8)
panel.Label(text).Focusable().FocusRing(false).Draw(func(p *ui.Painter, r ui.Rect) {
g := k.Canvas(p, r)
+2 -2
View File
@@ -9,11 +9,11 @@ import (
type SpotlightOptions struct{}
// Spotlight dithers all but target and captions it.
func Spotlight(c *ui.Context, target *ui.Element, caption string, o SpotlightOptions) *ui.Element {
func Spotlight(c *ui.Context, target ui.Element, caption string, o SpotlightOptions) ui.Element {
k := pixel.K(c)
b := target.Bounds()
deep, _ := dark(k)
var veil *ui.Element
var veil ui.Element
ui.Overlay(c, func() {
veil = ui.Box(c).Absolute().Left(0).Top(0).Right(0).Bottom(0).PassThrough().Role(ui.RoleStatus).Label(caption)
veil.Draw(func(p *ui.Painter, r ui.Rect) {
+1 -1
View File
@@ -16,7 +16,7 @@ type TitleCardOptions struct {
}
// TitleCard dithers in, holds title, dithers out; fills its parent.
func TitleCard(c *ui.Context, title string, o TitleCardOptions) *ui.Element {
func TitleCard(c *ui.Context, title string, o TitleCardOptions) ui.Element {
k := pixel.K(c)
if o.Hold < 0 {
panic("overlay: TitleCard Hold is negative")
+3 -3
View File
@@ -15,7 +15,7 @@ type TooltipOptions struct{}
const tipWidth = 160
// Tooltip bubbles text after 500 ms, or on keyboard focus.
func Tooltip(c *ui.Context, text string, o TooltipOptions, trigger func()) *ui.Element {
func Tooltip(c *ui.Context, text string, o TooltipOptions, trigger func()) ui.Element {
k := pixel.K(c)
wrap := ui.Box(c)
show := hint(c, wrap, 500*time.Millisecond)
@@ -32,12 +32,12 @@ func Tooltip(c *ui.Context, text string, o TooltipOptions, trigger func()) *ui.E
}
// bubble floats content by b; w keeps it inside.
func bubble(c *ui.Context, b ui.Rect, content func(), w int) *ui.Element {
func bubble(c *ui.Context, b ui.Rect, content func(), w int) ui.Element {
k := pixel.K(c)
ww, wh := c.Size()
x := max(0, min(b.X+b.W/2-k.D(8), ww-k.D(w)))
at := max(3, int((b.X+b.W/2-x)/k.Dot()))
var e *ui.Element
var e ui.Element
ui.Overlay(c, func() {
e = ui.Box(c).Absolute().Left(x).PassThrough()
tail := display.Bottom