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

+3 -3
View File
@@ -97,7 +97,7 @@ type heading struct {
}
// Toward steps 0–n toward on, turning from where it is.
func (k Kit) Toward(c *ui.Context, e *ui.Element, on bool, fps, n int) int {
func (k Kit) Toward(c *ui.Context, e ui.Element, on bool, fps, n int) int {
checkFPS(fps)
if n < 1 {
panic(fmt.Sprintf("pixel: Toward needs steps, got %d", n))
@@ -137,7 +137,7 @@ type shot struct {
}
// Shot steps a cued one-shot; Reduce Motion spends it whole.
func (k Kit) Shot(c *ui.Context, e *ui.Element, cue, fps, done int) int {
func (k Kit) Shot(c *ui.Context, e ui.Element, cue, fps, done int) int {
checkFPS(fps)
// Before any change the zero start reads as done.
s := ui.Local(e, shotKey{}, func() shot { return shot{cue: cue} })
@@ -162,7 +162,7 @@ type cued struct {
}
// Cue is when n last changed on e, zero before.
func (k Kit) Cue(e *ui.Element, n int) time.Time {
func (k Kit) Cue(e ui.Element, n int) time.Time {
s := ui.Local(e, cueKey{}, func() cued { return cued{n: n} })
if s.n != n {
s.n, s.at = n, k.now
+3 -3
View File
@@ -23,7 +23,7 @@ type EditorOptions struct {
type Edit struct {
// Element is laid out; Input takes the focus.
Element, Input *ui.Element
Element, Input ui.Element
Changed, Submitted, Focused bool
// Line is the caret's line, from 0.
Line int
@@ -53,7 +53,7 @@ func Editor(c *ui.Context, value *string, o EditorOptions) Edit {
if o.EnterSends && !o.Multiline {
panic("pixel: EnterSends needs a multi-line field")
}
shell, base, _ := Shell(c, func() *ui.Element {
shell, base, _ := Shell(c, func() ui.Element {
return ui.Box(c).Focusable().Role(ui.RoleTextField).Label(o.Label).Disabled(o.Disabled)
})
s := ui.Local(base, editKey{}, func() editState { return editState{} })
@@ -73,7 +73,7 @@ func Editor(c *ui.Context, value *string, o EditorOptions) Edit {
base.Description(*value)
}
width, check := WrapWidth(c, shell)
env := editEnv{multi: o.Multiline, sends: o.EnterSends, maxLen: o.MaxLength, mask: o.Mask, width: width, dot: k.dot, read: c.ReadClipboard, write: func(v string) { s.clip, s.clipped = v, true }}
env := editEnv{multi: o.Multiline, sends: o.EnterSends, maxLen: o.MaxLength, mask: o.Mask, width: width, dot: k.dot, read: c.Services().ReadClipboard, write: func(v string) { s.clip, s.clipped = v, true }}
shown := s.view(o.Mask, true)
caret := s.caret + s.composeCaret
lines := editLayout(shown, width, o.Multiline)
+19 -7
View File
@@ -17,7 +17,7 @@ type TextOptions struct {
}
// Text is a pixel-font label that screen readers read.
func Text(c *ui.Context, s string, o TextOptions) *ui.Element {
func Text(c *ui.Context, s string, o TextOptions) ui.Element {
k := K(c)
scale := scaleOr1(o.Scale)
e := ui.Box(c).Label(s).Role(ui.RoleText)
@@ -65,7 +65,7 @@ func Wrap(s string, width, maxLines int) []string {
type wrapKey struct{}
// WrapWidth is e's last painted width; Draw must call check.
func WrapWidth(c *ui.Context, e *ui.Element) (int, func(r ui.Rect)) {
func WrapWidth(c *ui.Context, e ui.Element) (int, func(r ui.Rect)) {
dot := K(c).dot
w := ui.Local(e, wrapKey{}, func() int { return 0 })
used := *w
@@ -89,7 +89,7 @@ func scaleOr1(s int) int {
type State struct{ Hover, Press, Focus, Disabled bool }
// Shell wraps base in an enabled box that paints.
func Shell(c *ui.Context, base func() *ui.Element) (shell, b *ui.Element, st State) {
func Shell(c *ui.Context, base func() ui.Element) (shell, b ui.Element, st State) {
shell = ui.Box(c)
shell.Children(func() { b = base().Fill().FocusRing(false) })
own, inert := b.IsDisabled(), current(c).cur.Inert
@@ -103,7 +103,7 @@ func Shell(c *ui.Context, base func() *ui.Element) (shell, b *ui.Element, st Sta
}
// Shortcut is e's key this frame, while e is enabled.
func Shortcut(e *ui.Element, mods ui.Modifiers, key ui.Key) bool {
func Shortcut(e ui.Element, mods ui.Modifiers, key ui.Key) bool {
// MyGo still hands keys to a base just turned off.
return e.Shortcut(mods, key) && !e.IsDisabled()
}
@@ -111,10 +111,10 @@ func Shortcut(e *ui.Element, mods ui.Modifiers, key ui.Key) bool {
type parkKey struct{}
// Park moves focus off e while off, back once on.
func Park(c *ui.Context, e *ui.Element, off bool) {
func Park(c *ui.Context, e ui.Element, off bool) {
parked := ui.Local(e, parkKey{}, func() bool { return false })
spot := c.Root()
if h := current(c).harbor; h != nil {
if h := current(c).harbor; h.Valid() {
spot = h
}
switch {
@@ -130,8 +130,20 @@ func Park(c *ui.Context, e *ui.Element, off bool) {
}
}
type autoKey struct{}
// AutoFocus focuses e as it appears, after overlays note openers.
func AutoFocus(c *ui.Context, e ui.Element) {
first := false
h := ui.Local(e, autoKey{}, func() ui.Handle { first = true; return ui.Handle{} })
e.Bind(h)
if first {
h.Focus(c)
}
}
// Harbor keeps focus in e while controls in fn rest.
func Harbor(c *ui.Context, e *ui.Element, fn func()) {
func Harbor(c *ui.Context, e ui.Element, fn func()) {
f := current(c)
prev := f.harbor
f.harbor = e
+3 -3
View File
@@ -85,8 +85,8 @@ func TestClock(t *testing.T) {
if step != 0 || busy == 0 {
t.Fatalf("under Reduce Motion: Step %d (want 0), Busy %d", step, busy)
}
if due := time.Until(pixel.Due()); due <= 0 || due > 125*time.Millisecond {
t.Fatalf("Due in %v, want the 8 fps wait on the real clock", due)
if due := pixel.Due().Sub(tt.Now()); due <= 0 || due > 125*time.Millisecond {
t.Fatalf("Due in %v, want the 8 fps wait on the frame clock", due)
}
pixel.ResetDue()
if !pixel.Due().IsZero() {
@@ -166,7 +166,7 @@ func shellView(disabled *bool, clicks *int, st *pixel.State) func(c *ui.Context)
ui.Box(c).Size(k.D(16), k.D(4))
ui.Row(c).Children(func() {
ui.Box(c).Size(k.D(16), k.D(8))
shell, base, s := pixel.Shell(c, func() *ui.Element {
shell, base, s := pixel.Shell(c, func() ui.Element {
return ui.ButtonBase(c).Label("go").Disabled(*disabled)
})
*st = s
+2 -2
View File
@@ -3,9 +3,9 @@ package pixel
import "github.com/egoist/mygo/ui"
// Pad is a focusable grid; it returns the picked cell.
func Pad(c *ui.Context, text string, cols, n, cw, ch int, cur *int, disabled bool) (shell, base *ui.Element, st State, picked int) {
func Pad(c *ui.Context, text string, cols, n, cw, ch int, cur *int, disabled bool) (shell, base ui.Element, st State, picked int) {
k := K(c)
shell, base, st = Shell(c, func() *ui.Element {
shell, base, st = Shell(c, func() ui.Element {
return ui.Box(c).Focusable().Role(ui.RoleGroup).Label(text).Disabled(disabled)
})
shell.Size(k.D(cols*cw), k.D((n+cols-1)/cols*ch))
+6 -5
View File
@@ -47,11 +47,12 @@ func (k Kit) Now() time.Time { return k.now }
func (k Kit) Reduce() bool { return k.reduce }
type frame struct {
cur Settings
at time.Time
cur Settings
// pass is the root of the pass Use ran in.
pass ui.Element
typing bool
// harbor holds the focus of controls turning off.
harbor *ui.Element
harbor ui.Element
}
type frameKey struct{}
@@ -63,7 +64,7 @@ func Use(c *ui.Context, s Settings) {
if f.cur.Palette != s.Palette {
slog.Debug("8bit: palette", "name", s.Palette.Name)
}
f.cur, f.at, f.typing, f.harbor = s, c.Now(), false, nil
f.cur, f.pass, f.typing, f.harbor = s, c.Root(), false, ui.Element{}
c.SetTheme(theme(s.Palette))
}
@@ -103,7 +104,7 @@ func frameOf(c *ui.Context) *frame {
func current(c *ui.Context) *frame {
f := frameOf(c)
if !f.at.Equal(c.Now()) {
if f.pass != c.Root() {
panic("pixel: Use was not called in this frame")
}
return f