Upgrade MyGo to v0.5.5
MyGo v0.5.5 returns ui.Element as a checked value handle instead of a
pointer. Migrated with `mygo migrate-ui`, then fixed what the codemod
leaves to review:
- Element nil checks become .Valid(); absent elements are ui.Element{}.
- Input handlers and paint callbacks run outside the build pass, where
elements expire and Context methods are inert: read IsDisabled into a
local during the build, keep a ui.Handle for Focus, use c.Services()
for clipboard access, create ui.Local state during the build.
- Add core.AutoFocus: v0.5.5 records an overlay's opener after its
content builds, so same-frame autofocus lost focus restore on close.
It focuses one frame later; cascader rows defer their focus likewise.
- core.OverlayLayer identifies build passes by the root element, not
c.Now(), which repeats across passes and under the Tester clock.
- Context menus survive the opening press counted as PressedOutside;
split button tooltip no longer reorders its open menu; disabled
menubar rows pass presses to the panel; Form defers blur-started
async checks so the growing message no longer moves Submit away
from the release; Combobox drops its disabled copy now that MyGo
blocks typing under a disabled wrapper (canary flipped to a guard).
- The Tester clock is virtual: tests use Advance/Now instead of
time.Sleep/time.Now and read element state inside the view. The web
mirror advances the clock to wall time before each frame and input.
- AGENTS.md and docs describe the value Element API and the new pin.
Snapshot goldens are darwin-only and were not re-verified here.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This commit is contained in:
1 parent
bc716d27be
commit
bcce34cda1
603 files changed
+1366
-1277
No files matched your search
@@ -29,7 +29,7 @@ type BreadcrumbOptions struct {
|
||||
|
||||
// BreadcrumbResult is the built Breadcrumb.
|
||||
type BreadcrumbResult struct {
|
||||
Element *ui.Element
|
||||
Element ui.Element
|
||||
chosen int
|
||||
}
|
||||
|
||||
@@ -135,14 +135,14 @@ func breadcrumbMore(c *ui.Context, hidden []BreadcrumbItem, choose func(int)) {
|
||||
*open = !*open
|
||||
}
|
||||
b.Children(func() { ui.Icon(c, icons.Must("ellipsis")).Size(16, 16) })
|
||||
ui.PopoverBase(c, b, open, func(panel *ui.Element) {
|
||||
ui.PopoverBase(c, b, open, func(panel ui.Element) {
|
||||
core.OverlayStyle(c, panel, core.OverlayEnter(c, panel))
|
||||
panel.Margin(u, 0, 0, 0).Padding(u).Gap(u * 0.5).MinWidth(160).MaxWidth(320)
|
||||
for j, it := range hidden {
|
||||
item := ui.ButtonBase(c).Role(ui.RoleLink).Justify(ui.Start).Gap(u*2).Padding(u*1.5, u*2).
|
||||
Radius(theme.ControlRadius).MinHeight(core.Density(c).ControlHeight() - u*2).Label(it.Label).TextColor(k.Text)
|
||||
if j == 0 {
|
||||
item.AutoFocus()
|
||||
core.AutoFocus(c, item)
|
||||
}
|
||||
internal.Ring(item, k.Focus, theme.ControlRadius)
|
||||
if item.Hovered() {
|
||||
|
||||
@@ -33,7 +33,7 @@ type CommandPaletteOptions struct {
|
||||
|
||||
// CommandPaletteResult is the built CommandPalette; Element is nil while closed.
|
||||
type CommandPaletteResult struct {
|
||||
Element *ui.Element
|
||||
Element ui.Element
|
||||
chosen string
|
||||
}
|
||||
|
||||
@@ -79,7 +79,7 @@ func CommandPalette(c *ui.Context, open *bool, commands []Command, opts CommandP
|
||||
k := core.Tokens(c)
|
||||
d := core.Density(c)
|
||||
u := d.Unit()
|
||||
res.Element = ui.DialogBase(c, open, func(back, panel *ui.Element) {
|
||||
res.Element = ui.DialogBase(c, open, func(back, panel ui.Element) {
|
||||
ww, wh := c.Size()
|
||||
p := core.OverlayEnter(c, panel)
|
||||
back.Background(ui.RGBA(0, 0, 0, 0.4*p)).Justify(ui.Start)
|
||||
@@ -87,10 +87,10 @@ func CommandPalette(c *ui.Context, open *bool, commands []Command, opts CommandP
|
||||
panel.Margin(wh*0.12, 0, 0, 0).Width(560).MaxWidth(ww - 32).MaxHeight(wh * 0.76).
|
||||
AlignItems(ui.Stretch).Label(core.Msg(c, "command_palette.label"))
|
||||
found := commandPaletteFilter(commands, st.query)
|
||||
var in *ui.Element
|
||||
var in ui.Element
|
||||
ui.Row(c).AlignItems(ui.Center).Gap(u*3).Padding(u*3, u*4).Children(func() {
|
||||
ui.Icon(c, icons.Must("search")).Size(16, 16).TextColor(k.TextMuted)
|
||||
in = ui.TextInputBase(c, &st.query).FillWidth().AutoFocus().Label(core.Msg(c, "command_palette.search"))
|
||||
in = core.AutoFocus(c, ui.TextInputBase(c, &st.query).FillWidth()).Label(core.Msg(c, "command_palette.search"))
|
||||
})
|
||||
ui.Box(c).Height(1).Background(k.Ornament).Role(ui.RoleNone)
|
||||
if in.Changed() {
|
||||
|
||||
@@ -57,7 +57,7 @@ type MenubarOptions struct {
|
||||
|
||||
// MenubarResult is the built Menubar.
|
||||
type MenubarResult struct {
|
||||
Element *ui.Element
|
||||
Element ui.Element
|
||||
chosen string
|
||||
}
|
||||
|
||||
@@ -167,7 +167,7 @@ func (m *menubarRun) current() string {
|
||||
return ""
|
||||
}
|
||||
|
||||
func (m *menubarRun) title(i int, label string) *ui.Element {
|
||||
func (m *menubarRun) title(i int, label string) ui.Element {
|
||||
c, st := m.c, m.st
|
||||
k := core.Tokens(c)
|
||||
d := core.Density(c)
|
||||
@@ -211,7 +211,7 @@ func (m *menubarRun) title(i int, label string) *ui.Element {
|
||||
}
|
||||
|
||||
// keys drives an open menu from the focused title.
|
||||
func (m *menubarRun) keys(t *ui.Element) {
|
||||
func (m *menubarRun) keys(t ui.Element) {
|
||||
st := m.st
|
||||
l := len(st.hl) - 1
|
||||
items := m.items(l)
|
||||
|
||||
@@ -50,7 +50,7 @@ func (m *menubarRun) overlay(titles []ui.Rect) {
|
||||
var prev ui.Rect
|
||||
for l := 0; l < len(st.hl) && st.open >= 0; l++ {
|
||||
panel, anchor := m.panel(l, x, y, prev)
|
||||
if anchor == nil || l+1 >= len(st.hl) {
|
||||
if !anchor.Valid() || l+1 >= len(st.hl) {
|
||||
break
|
||||
}
|
||||
a := anchor.Bounds()
|
||||
@@ -61,7 +61,7 @@ func (m *menubarRun) overlay(titles []ui.Rect) {
|
||||
}
|
||||
|
||||
// panel builds level l at (x, y) and returns it with its highlighted row.
|
||||
func (m *menubarRun) panel(l int, x, y float32, parent ui.Rect) (*ui.Element, *ui.Element) {
|
||||
func (m *menubarRun) panel(l int, x, y float32, parent ui.Rect) (ui.Element, ui.Element) {
|
||||
c, st := m.c, m.st
|
||||
k := core.Tokens(c)
|
||||
u := core.Density(c).Unit()
|
||||
@@ -84,7 +84,7 @@ func (m *menubarRun) panel(l int, x, y float32, parent ui.Rect) (*ui.Element, *u
|
||||
if panel.Clicked() || panel.Pressed() {
|
||||
m.focus(st.open)
|
||||
}
|
||||
var anchor *ui.Element
|
||||
var anchor ui.Element
|
||||
panel.Children(func() {
|
||||
for j, it := range items {
|
||||
if it.Kind == MenubarSeparator {
|
||||
@@ -92,6 +92,9 @@ func (m *menubarRun) panel(l int, x, y float32, parent ui.Rect) (*ui.Element, *u
|
||||
continue
|
||||
}
|
||||
row := m.item(it, st.hl[l] == j)
|
||||
if it.Disabled {
|
||||
row.PassThrough() // its press reaches the panel, which keeps the title focused
|
||||
}
|
||||
if st.hl[l] == j {
|
||||
anchor = row
|
||||
if st.reveal && l == len(st.hl)-1 {
|
||||
@@ -126,7 +129,7 @@ func (m *menubarRun) panel(l int, x, y float32, parent ui.Rect) (*ui.Element, *u
|
||||
return panel, anchor
|
||||
}
|
||||
|
||||
func (m *menubarRun) item(it MenubarItem, hl bool) *ui.Element {
|
||||
func (m *menubarRun) item(it MenubarItem, hl bool) ui.Element {
|
||||
c := m.c
|
||||
k := core.Tokens(c)
|
||||
d := core.Density(c)
|
||||
|
||||
@@ -31,7 +31,7 @@ type NavigationMenuOptions struct {
|
||||
}
|
||||
|
||||
// NavigationMenu shows links pushing pages on router; the router's path marks the current one.
|
||||
func NavigationMenu(c *ui.Context, router *ui.Router, links []NavigationLink, opts NavigationMenuOptions) *ui.Element {
|
||||
func NavigationMenu(c *ui.Context, router *ui.Router, links []NavigationLink, opts NavigationMenuOptions) ui.Element {
|
||||
if router == nil {
|
||||
panic("mujica: NavigationMenu needs a router")
|
||||
}
|
||||
@@ -69,7 +69,7 @@ func NavigationMenu(c *ui.Context, router *ui.Router, links []NavigationLink, op
|
||||
return row
|
||||
}
|
||||
|
||||
func navigationMenuHistory(c *ui.Context, b *ui.Element, name string) {
|
||||
func navigationMenuHistory(c *ui.Context, b ui.Element, name string) {
|
||||
k := core.Tokens(c)
|
||||
d := core.Density(c)
|
||||
b.Label(name).MinHeight(d.ControlHeight()).MinWidth(d.ControlHeight()).Radius(theme.ControlRadius).Background(k.Surface)
|
||||
@@ -147,7 +147,7 @@ func navigationMenuTop(c *ui.Context, l NavigationLink, path string, push func(s
|
||||
} else if b.Shortcut(0, ui.KeyDown) {
|
||||
*open = true
|
||||
}
|
||||
ui.PopoverBase(c, b, open, func(panel *ui.Element) {
|
||||
ui.PopoverBase(c, b, open, func(panel ui.Element) {
|
||||
core.OverlayStyle(c, panel, core.OverlayEnter(c, panel))
|
||||
// Capped to the room beside the anchor; the links scroll inside.
|
||||
_, wh := c.Size()
|
||||
@@ -189,13 +189,13 @@ func navigationMenuPanel(c *ui.Context, items []NavigationLink, path string, ope
|
||||
list(items)
|
||||
}
|
||||
|
||||
func navigationMenuItem(c *ui.Context, l NavigationLink, on, first bool) *ui.Element {
|
||||
func navigationMenuItem(c *ui.Context, l NavigationLink, on, first bool) ui.Element {
|
||||
k := core.Tokens(c)
|
||||
u := core.Density(c).Unit()
|
||||
e := ui.ButtonBase(c).Role(ui.RoleLink).Justify(ui.Start).AlignItems(ui.Start).Gap(u*3).
|
||||
Padding(u*2, u*3).Radius(theme.ControlRadius).Label(l.Label).TextColor(k.Text).Cursor(ui.CursorPointer)
|
||||
if first {
|
||||
e.AutoFocus()
|
||||
core.AutoFocus(c, e)
|
||||
}
|
||||
internal.Ring(e, k.Focus, theme.ControlRadius)
|
||||
switch {
|
||||
|
||||
@@ -36,7 +36,6 @@ func TestNavigationPopupsFadeIn(t *testing.T) {
|
||||
CommandPalette(c, &open, commandPaletteSample(), CommandPaletteOptions{})
|
||||
}},
|
||||
} {
|
||||
opened := time.Now()
|
||||
tt := ui.NewTester(func(c *ui.Context) {
|
||||
core.Use(c, core.Settings{Mode: core.Light})
|
||||
ui.Column(c).Fill().Padding(16).AlignItems(ui.Start).Children(func() { tc.view(c) })
|
||||
@@ -51,12 +50,11 @@ func TestNavigationPopupsFadeIn(t *testing.T) {
|
||||
t.Fatalf("%s: no %q", tc.name, tc.sample)
|
||||
}
|
||||
x, y := int(r.X-3), int(r.Y+r.H/2)
|
||||
// The Tester clock stands still, so a quarter of the way in is exact.
|
||||
tt.Advance(theme.PopupDuration / 4)
|
||||
early := tt.Image().RGBAAt(x, y)
|
||||
mid := time.Since(opened) < theme.PopupDuration/2
|
||||
time.Sleep(200 * time.Millisecond)
|
||||
tt.Frame()
|
||||
// A slow run may finish the fade before the first sample.
|
||||
if late := tt.Image().RGBAAt(x, y); mid && early == late {
|
||||
tt.Advance(200 * time.Millisecond)
|
||||
if late := tt.Image().RGBAAt(x, y); early == late {
|
||||
t.Errorf("%s: popup appeared at once (%v)", tc.name, late)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -126,7 +126,7 @@ func paginationItems(cur, pages, sib int) []int {
|
||||
return append(out, pages)
|
||||
}
|
||||
|
||||
func paginationButton(c *ui.Context, text string, icon *ui.SVG, name string, on, disabled bool) *ui.Element {
|
||||
func paginationButton(c *ui.Context, text string, icon *ui.SVG, name string, on, disabled bool) ui.Element {
|
||||
k := core.Tokens(c)
|
||||
h := core.Density(c).ControlHeight()
|
||||
b := ui.ButtonBase(c).MinHeight(h).MinWidth(h).Padding(0, core.Density(c).Unit()*2).Radius(theme.ControlRadius).
|
||||
@@ -175,7 +175,7 @@ func paginationSizes(c *ui.Context, page, pageSize *int, sizes []int, res *core.
|
||||
ui.Text(c, fmt.Sprintf(core.Msg(c, "pagination.per_page"), *pageSize)).SingleLine()
|
||||
ui.Icon(c, icons.Must("chevron-down")).Size(14, 14).TextColor(k.TextMuted)
|
||||
})
|
||||
sel.Popup(func(panel *ui.Element) {
|
||||
sel.Popup(func(panel ui.Element) {
|
||||
core.OverlayStyle(c, panel, core.OverlayEnter(c, panel))
|
||||
panel.Margin(u, 0, 0, 0).Padding(u)
|
||||
for _, s := range sizes {
|
||||
@@ -211,7 +211,7 @@ func paginationJump(c *ui.Context, pages int, set func(int)) {
|
||||
Radius(theme.ControlRadius).Background(k.Surface)
|
||||
text := ui.Local(box, "text", func() string { return "" })
|
||||
bad := ui.Local(box, "bad", func() bool { return false })
|
||||
var in *ui.Element
|
||||
var in ui.Element
|
||||
box.Children(func() {
|
||||
in = ui.TextInputBase(c, text).FillWidth().Label(core.Msg(c, "pagination.field"))
|
||||
})
|
||||
|
||||
@@ -28,7 +28,7 @@ type SegmentedControlOptions struct {
|
||||
}
|
||||
|
||||
// SegmentedControl chooses one of segments into *selected; Changed reports a new choice.
|
||||
func SegmentedControl(c *ui.Context, selected *int, segments []Segment, opts SegmentedControlOptions) *ui.Element {
|
||||
func SegmentedControl(c *ui.Context, selected *int, segments []Segment, opts SegmentedControlOptions) ui.Element {
|
||||
if len(segments) == 0 {
|
||||
panic("mujica: SegmentedControl needs at least one segment")
|
||||
}
|
||||
|
||||
+1
-1
@@ -86,7 +86,7 @@ func Steps(c *ui.Context, current *int, steps []Step, opts StepsOptions) core.Ch
|
||||
func stepsItem(c *ui.Context, i, n int, s Step, st stepsState, vertical, tail, doneLine bool) bool {
|
||||
k := core.Tokens(c)
|
||||
u := core.Density(c).Unit()
|
||||
var e *ui.Element
|
||||
var e ui.Element
|
||||
if s.Clickable {
|
||||
e = ui.ButtonBase(c).Cursor(ui.CursorPointer)
|
||||
internal.Ring(e, k.Focus, theme.ControlRadius)
|
||||
|
||||
+4
-4
@@ -65,7 +65,7 @@ func Tabs(c *ui.Context, selected *int, tabs []Tab, opts TabsOptions) core.Choic
|
||||
}
|
||||
res := core.ChoiceResult{Element: root}
|
||||
root.Children(func() {
|
||||
var strip *ui.Element
|
||||
var strip ui.Element
|
||||
if opts.Vertical {
|
||||
strip = ui.Scroll(c).Shrink(0).Padding(pad)
|
||||
} else {
|
||||
@@ -87,11 +87,11 @@ func Tabs(c *ui.Context, selected *int, tabs []Tab, opts TabsOptions) core.Choic
|
||||
if opts.Label != "" {
|
||||
list.Label(opts.Label)
|
||||
}
|
||||
built := make([]*ui.Element, len(tabs))
|
||||
built := make([]ui.Element, len(tabs))
|
||||
list.Children(func() {
|
||||
n := 0
|
||||
for i, t := range tabs {
|
||||
var tab *ui.Element
|
||||
var tab ui.Element
|
||||
if t.Disabled {
|
||||
tab = ui.Row(c).Shrink(0).Role(ui.RoleTab).Disabled(true)
|
||||
} else {
|
||||
@@ -121,7 +121,7 @@ func Tabs(c *ui.Context, selected *int, tabs []Tab, opts TabsOptions) core.Choic
|
||||
return res
|
||||
}
|
||||
|
||||
func TabsStyle(c *ui.Context, tab *ui.Element, t Tab, on, vertical bool) {
|
||||
func TabsStyle(c *ui.Context, tab ui.Element, t Tab, on, vertical bool) {
|
||||
k := core.Tokens(c)
|
||||
d := core.Density(c)
|
||||
u := d.Unit()
|
||||
|
||||
@@ -186,8 +186,7 @@ func TestTabsStateColorsEase(t *testing.T) {
|
||||
if got := tt.Image().RGBAAt(x, y); got == hover {
|
||||
t.Fatal("hover color applied at once")
|
||||
}
|
||||
time.Sleep(theme.StateDuration + 40*time.Millisecond)
|
||||
tt.Frame()
|
||||
tt.Advance(theme.StateDuration + 40*time.Millisecond)
|
||||
if got := tt.Image().RGBAAt(x, y); got != hover {
|
||||
t.Fatalf("hover color %v after the transition, want %v", got, hover)
|
||||
}
|
||||
|
||||
@@ -15,7 +15,7 @@ type ToolbarOptions struct {
|
||||
}
|
||||
|
||||
// Toolbar is one Tab stop of actions; those that do not fit go to an overflow menu.
|
||||
func Toolbar(c *ui.Context, opts ToolbarOptions, fn func()) *ui.Element {
|
||||
func Toolbar(c *ui.Context, opts ToolbarOptions, fn func()) ui.Element {
|
||||
k := core.Tokens(c)
|
||||
tb := ui.Toolbar(c, fn).Background(k.Surface).Border(theme.BorderWidth, k.Border).Radius(theme.CardRadius).
|
||||
MinHeight(core.Density(c).ControlHeight() + 8)
|
||||
@@ -34,7 +34,7 @@ type ToolbarButtonOptions struct {
|
||||
}
|
||||
|
||||
// ToolbarButton is an action of a Toolbar; label names it and its overflow menu item.
|
||||
func ToolbarButton(c *ui.Context, label string, opts ToolbarButtonOptions) *ui.Element {
|
||||
func ToolbarButton(c *ui.Context, label string, opts ToolbarButtonOptions) ui.Element {
|
||||
if label == "" {
|
||||
panic("mujica: ToolbarButton needs a label")
|
||||
}
|
||||
@@ -70,7 +70,7 @@ func ToolbarButton(c *ui.Context, label string, opts ToolbarButtonOptions) *ui.E
|
||||
}
|
||||
|
||||
// ToolbarGroup keeps related actions together; the overflow menu sets groups apart.
|
||||
func ToolbarGroup(c *ui.Context, fn func()) *ui.Element {
|
||||
func ToolbarGroup(c *ui.Context, fn func()) ui.Element {
|
||||
return ui.Row(c).AlignItems(ui.Center).Gap(core.Density(c).Unit() * 0.5).Shrink(0).Role(ui.RoleGroup).Children(fn)
|
||||
}
|
||||
|
||||
|
||||
Reference in new issue
Block a user