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
@@ -14,7 +14,7 @@ type AppFrameOptions struct {
}
// AppFrame arranges header, sidebar, main and footer; nil ones vanish.
func AppFrame(c *ui.Context, o AppFrameOptions, header, sidebar, main, footer func()) *ui.Element {
func AppFrame(c *ui.Context, o AppFrameOptions, header, sidebar, main, footer func()) ui.Element {
k := pixel.K(c)
sw := pixel.Or("AppFrame SidebarWidth", o.SidebarWidth, 96)
fold := pixel.Or("AppFrame FoldBelow", o.FoldBelow, 320)
@@ -43,7 +43,7 @@ func AppFrame(c *ui.Context, o AppFrameOptions, header, sidebar, main, footer fu
if header != nil {
strip(header, true)
}
var body *ui.Element
var body ui.Element
if folded {
body = ui.Column(c)
} else {
+1 -1
View File
@@ -12,7 +12,7 @@ type AspectBoxOptions struct {
}
// AspectBox fills the width and keeps W:H in whole dots.
func AspectBox(c *ui.Context, o AspectBoxOptions, children func()) *ui.Element {
func AspectBox(c *ui.Context, o AspectBoxOptions, children func()) ui.Element {
k := pixel.K(c)
rw, rh := pixel.Or("AspectBox W", o.W, 1), pixel.Or("AspectBox H", o.H, 1)
e := ui.Box(c).FillWidth().Clip()
+1 -1
View File
@@ -14,7 +14,7 @@ type CardOptions struct {
}
// Card is a raised panel: header strip, body, footer.
func Card(c *ui.Context, o CardOptions, body func()) *ui.Element {
func Card(c *ui.Context, o CardOptions, body func()) ui.Element {
k := pixel.K(c)
e := ui.Column(c).Padding(k.D(2)).Draw(func(p *ui.Painter, r ui.Rect) {
box(k.Canvas(p, r), pixel.Raised, pixel.Face)
+1 -1
View File
@@ -15,7 +15,7 @@ type CRTFrameOptions struct {
const crtSide, crtChin = 10, 16
// CRTFrame shows children on a monitor with scanlines.
func CRTFrame(c *ui.Context, o CRTFrameOptions, children func()) *ui.Element {
func CRTFrame(c *ui.Context, o CRTFrameOptions, children func()) ui.Element {
k := pixel.K(c)
return ui.Column(c).Padding(k.D(crtSide), k.D(crtSide), k.D(crtChin), k.D(crtSide)).Draw(func(p *ui.Painter, r ui.Rect) {
g := k.Canvas(p, r)
+1 -1
View File
@@ -34,7 +34,7 @@ var diamond = pixel.MustSprite(`
`)
// Divider is a rule across its parent.
func Divider(c *ui.Context, o DividerOptions) *ui.Element {
func Divider(c *ui.Context, o DividerOptions) ui.Element {
k := pixel.K(c)
ink := o.Slot
if ink == pixel.Ink {
+1 -1
View File
@@ -25,7 +25,7 @@ type GridOptions struct {
}
// Grid lays n cells in equal columns, lines between them.
func Grid(c *ui.Context, n int, o GridOptions, cell func(i int)) *ui.Element {
func Grid(c *ui.Context, n int, o GridOptions, cell func(i int)) ui.Element {
k := pixel.K(c)
cols := pixel.Or("Grid Columns", o.Columns, 2)
pad := pixel.Or("Grid Pad", o.Pad, 4)
+2 -2
View File
@@ -18,7 +18,7 @@ type HandheldOptions struct {
}
type HandheldResult struct {
Element *ui.Element
Element ui.Element
Up, Down, Left, Right, A, B bool
}
@@ -69,7 +69,7 @@ func Handheld(c *ui.Context, o HandheldOptions, screen func()) HandheldResult {
// key is a d-pad arm, or a round button.
func key(c *ui.Context, k pixel.Kit, name string, x, y, w, h int, round, disabled bool) bool {
shell, base, st := pixel.Shell(c, func() *ui.Element { return ui.ButtonBase(c).Label(name).Disabled(disabled) })
shell, base, st := pixel.Shell(c, func() ui.Element { return ui.ButtonBase(c).Label(name).Disabled(disabled) })
shell.Absolute().Left(k.D(x)).Top(k.D(y)).Size(k.D(w), k.D(h)).Draw(func(p *ui.Painter, r ui.Rect) {
g := k.Canvas(p, r)
dy := 0
+1 -1
View File
@@ -5,7 +5,7 @@ import "github.com/egoist/mygo/ui"
type LayersOptions struct{}
// Layers stacks layers in a box; the first sizes it.
func Layers(c *ui.Context, o LayersOptions, layers ...func()) *ui.Element {
func Layers(c *ui.Context, o LayersOptions, layers ...func()) ui.Element {
if len(layers) == 0 {
panic("layout: Layers needs a layer")
}
+1 -1
View File
@@ -29,7 +29,7 @@ func rule(g pixel.Canvas, x, y, n int, vertical bool, on, off int, s pixel.Slot)
type dragKey struct{}
// drag is enabled e's pointer travel this frame, in dots.
func drag(k pixel.Kit, e *ui.Element) (dx, dy int, ok bool) {
func drag(k pixel.Kit, e ui.Element) (dx, dy int, ok bool) {
rest := ui.Local(e, dragKey{}, func() [2]float32 { return [2]float32{} })
fx, fy, ok := e.Dragged()
if !ok || e.IsDisabled() {
+15 -11
View File
@@ -302,44 +302,48 @@ func TestAppFrameFolds(t *testing.T) {
}
func TestAspectBoxAndViewport(t *testing.T) {
var aspect, screen *ui.Element
var aspect, screen ui.Handle
var ctx *ui.Context
tt := ui.NewTester(func(c *ui.Context) {
ctx = c
use(c)
ui.Column(c).Children(func() {
ui.Box(c).Width(300).Children(func() {
aspect = layout.AspectBox(c, layout.AspectBoxOptions{W: 16, H: 9}, func() {})
layout.AspectBox(c, layout.AspectBoxOptions{W: 16, H: 9}, func() {}).Bind(&aspect)
})
ui.Box(c).Size(400, 300).Children(func() {
layout.Viewport(c, layout.ViewportOptions{W: 64, H: 40}, func() { screen = ui.Box(c).Fill() })
layout.Viewport(c, layout.ViewportOptions{W: 64, H: 40}, func() { ui.Box(c).Fill().Bind(&screen) })
})
})
}, 500, 700)
tt.Frame()
if b := aspect.Bounds(); b.W != 300 || b.H != 2*(150*9/16) {
if b := aspect.Bounds(ctx); b.W != 300 || b.H != 2*(150*9/16) {
t.Fatalf("aspect box %v", b)
}
if b := screen.Bounds(); b.X != 72 || b.Y != aspect.Bounds().H+70 || b.W != 256 || b.H != 160 {
if b := screen.Bounds(ctx); b.X != 72 || b.Y != aspect.Bounds(ctx).H+70 || b.W != 256 || b.H != 160 {
t.Fatalf("viewport screen %v", b)
}
}
func TestLayersSizes(t *testing.T) {
var auto, sized, fill *ui.Element
var auto, sized, fill ui.Handle
var ctx *ui.Context
tt := ui.NewTester(func(c *ui.Context) {
ctx = c
use(c)
ui.Column(c).AlignItems(ui.Start).Children(func() {
auto = layout.Layers(c, layout.LayersOptions{}, func() { ui.Box(c).Size(50, 30) }, func() { ui.Box(c).Fill() })
sized = layout.Layers(c, layout.LayersOptions{}, func() { fill = ui.Box(c).Fill() }).Size(120, 64)
layout.Layers(c, layout.LayersOptions{}, func() { ui.Box(c).Size(50, 30) }, func() { ui.Box(c).Fill() }).Bind(&auto)
layout.Layers(c, layout.LayersOptions{}, func() { ui.Box(c).Fill().Bind(&fill) }).Size(120, 64).Bind(&sized)
})
}, 400, 300)
tt.Frame()
if b := auto.Bounds(); b.W != 50 || b.H != 30 {
if b := auto.Bounds(ctx); b.W != 50 || b.H != 30 {
t.Fatalf("first layer did not size the box: %v", b)
}
if b := fill.Bounds(); b.W != 120 || b.H != 64 {
if b := fill.Bounds(ctx); b.W != 120 || b.H != 64 {
t.Fatalf("first layer did not fill a sized box: %v", b)
}
if b := sized.Bounds(); b.H != 64 {
if b := sized.Bounds(ctx); b.H != 64 {
t.Fatalf("sized box %v", b)
}
}
+1 -1
View File
@@ -16,7 +16,7 @@ type MasonryOptions struct {
}
// Masonry puts each item into the shortest column.
func Masonry(c *ui.Context, heights []int, o MasonryOptions, item func(i int)) *ui.Element {
func Masonry(c *ui.Context, heights []int, o MasonryOptions, item func(i int)) ui.Element {
k := pixel.K(c)
gap := pixel.Or("Masonry Gap", o.Gap, 4)
n := pixel.Or("Masonry Columns", o.Columns, 3)
+1 -1
View File
@@ -15,7 +15,7 @@ type PanelOptions struct {
}
// Panel frames children in a style.
func Panel(c *ui.Context, o PanelOptions, children func()) *ui.Element {
func Panel(c *ui.Context, o PanelOptions, children func()) ui.Element {
k := pixel.K(c)
fill := o.Fill
if fill == pixel.Ink {
+2 -2
View File
@@ -19,7 +19,7 @@ type ResizableOptions struct {
}
type ResizableResult struct {
Element *ui.Element
Element ui.Element
Changed bool
}
@@ -41,7 +41,7 @@ func Resizable(c *ui.Context, w, h *int, o ResizableOptions, children func()) Re
e := ui.Box(c).Clip()
e.Children(func() {
children()
shell, base, st := pixel.Shell(c, func() *ui.Element {
shell, base, st := pixel.Shell(c, func() ui.Element {
return ui.Box(c).Focusable().Role(ui.RoleSplitter).Label(label(k, o.Label, "resize")).Disabled(o.Disabled).Cursor(ui.CursorResizeNWSE)
})
if dx, dy, ok := drag(k, base); ok {
+1 -1
View File
@@ -13,7 +13,7 @@ type ScreenOptions struct {
}
// Screen fills the window with Back; children form a column.
func Screen(c *ui.Context, o ScreenOptions, children func()) *ui.Element {
func Screen(c *ui.Context, o ScreenOptions, children func()) ui.Element {
k := pixel.K(c)
pad := pixel.Or("Screen Padding", o.Padding, 0)
return ui.Column(c).Fill().Padding(k.D(pad)).Draw(func(p *ui.Painter, r ui.Rect) {
+3 -3
View File
@@ -32,7 +32,7 @@ type ScrollAreaOptions struct {
}
type ScrollAreaResult struct {
Element *ui.Element
Element ui.Element
// Changed reports the view moved since the last frame.
Changed bool
}
@@ -66,7 +66,7 @@ func ScrollArea(c *ui.Context, s *ui.ScrollState, o ScrollAreaOptions, children
}
st.end = s.MaxY
}
var v *ui.Element
var v ui.Element
row.Children(func() {
scrollBar(c, k, s, view, o.Disabled || inert, o.Disabled)
// A disabled view is a new element; the focus leaves.
@@ -161,7 +161,7 @@ func thumb(h, view, y, maxY, dot float32) (size, at float32) {
// arrow is a scrollbar button; keys scroll the view instead.
func arrow(c *ui.Context, k pixel.Kit, sp *pixel.Sprite, label string, disabled bool) bool {
shell, base, st := pixel.Shell(c, func() *ui.Element {
shell, base, st := pixel.Shell(c, func() ui.Element {
return ui.Box(c).Role(ui.RoleButton).Label(label).Disabled(disabled)
})
shell.Size(k.D(barW), k.D(barW)).Draw(func(p *ui.Painter, r ui.Rect) {
+1 -1
View File
@@ -14,7 +14,7 @@ type SectionOptions struct {
}
// Section titles a group inside a dotted rule.
func Section(c *ui.Context, title string, o SectionOptions, children func()) *ui.Element {
func Section(c *ui.Context, title string, o SectionOptions, children func()) ui.Element {
k := pixel.K(c)
up := strings.ToUpper(title)
return ui.Column(c).Gap(k.D(pixel.Or("Section Gap", o.Gap, 4))).Role(ui.RoleGroup).Label(title).Children(func() {
+3 -3
View File
@@ -20,7 +20,7 @@ type SplitOptions struct {
}
type SplitResult struct {
Element *ui.Element
Element ui.Element
Changed bool
}
@@ -28,7 +28,7 @@ type SplitResult struct {
func Split(c *ui.Context, size *int, o SplitOptions, first, second func()) SplitResult {
k := pixel.K(c)
least := pixel.Or("Split Min", o.Min, 24)
var e *ui.Element
var e ui.Element
var total float32
back, fwd := ui.KeyLeft, ui.KeyRight
if o.Vertical {
@@ -47,7 +47,7 @@ func Split(c *ui.Context, size *int, o SplitOptions, first, second func()) Split
start := *size
e.AlignItems(ui.Stretch).Children(func() {
pane := ui.Box(c).Clip().Children(first)
shell, base, st := pixel.Shell(c, func() *ui.Element {
shell, base, st := pixel.Shell(c, func() ui.Element {
cur := ui.CursorResizeEW
if o.Vertical {
cur = ui.CursorResizeNS
+1 -1
View File
@@ -9,7 +9,7 @@ import (
type StatusBarOptions struct{}
// StatusBar shows segments split by sunken dividers; the first grows.
func StatusBar(c *ui.Context, segments []string, o StatusBarOptions) *ui.Element {
func StatusBar(c *ui.Context, segments []string, o StatusBarOptions) ui.Element {
k := pixel.K(c)
return ui.Row(c).FillWidth().Height(k.D(pixel.LineHeight + 3)).AlignItems(ui.Stretch).Role(ui.RoleStatus).Draw(func(p *ui.Painter, r ui.Rect) {
g := k.Canvas(p, r)
+1 -1
View File
@@ -14,7 +14,7 @@ type TilesOptions struct {
}
// Tiles wraps n equal tiles into rows.
func Tiles(c *ui.Context, n int, o TilesOptions, tile func(i int)) *ui.Element {
func Tiles(c *ui.Context, n int, o TilesOptions, tile func(i int)) ui.Element {
k := pixel.K(c)
w := pixel.Or("Tiles W", o.W, 48)
h := pixel.Or("Tiles H", o.H, w)
+2 -2
View File
@@ -32,7 +32,7 @@ type TitleBarOptions struct {
}
type TitleBarResult struct {
Element *ui.Element
Element ui.Element
Close bool
Minimize bool
}
@@ -67,7 +67,7 @@ func TitleBar(c *ui.Context, title string, o TitleBarOptions) TitleBarResult {
// windowBox is a framed button showing its glyph on hover.
func windowBox(c *ui.Context, k pixel.Kit, glyph *pixel.Sprite, name string, disabled bool) bool {
shell, base, st := pixel.Shell(c, func() *ui.Element { return ui.ButtonBase(c).Label(name).Disabled(disabled) })
shell, base, st := pixel.Shell(c, func() ui.Element { return ui.ButtonBase(c).Label(name).Disabled(disabled) })
shell.Size(k.D(11), k.D(11)).Draw(func(p *ui.Painter, r ui.Rect) {
g := k.Canvas(p, r)
fill := pixel.Face
+1 -1
View File
@@ -12,7 +12,7 @@ type ViewportOptions struct {
}
// Viewport fills its parent and draws children at W×H dots.
func Viewport(c *ui.Context, o ViewportOptions, children func()) *ui.Element {
func Viewport(c *ui.Context, o ViewportOptions, children func()) ui.Element {
k := pixel.K(c)
vw, vh := pixel.Or("Viewport W", o.W, 160), pixel.Or("Viewport H", o.H, 144)
base := int(k.Dot())
+1 -1
View File
@@ -16,7 +16,7 @@ type WindowOptions struct {
}
// Window is a beveled window with an optional title tab.
func Window(c *ui.Context, o WindowOptions, children func()) *ui.Element {
func Window(c *ui.Context, o WindowOptions, children func()) ui.Element {
k := pixel.K(c)
pad := 3 + pixel.Or("Window Padding", o.Padding, 6)
top := 0