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
@@ -56,7 +56,7 @@ type ArchiveViewerOptions struct {
|
||||
|
||||
// ArchiveViewerResult is the viewer and the entry opened this frame.
|
||||
type ArchiveViewerResult struct {
|
||||
Element *ui.Element
|
||||
Element ui.Element
|
||||
Opened *ArchiveEntry
|
||||
Retried bool
|
||||
}
|
||||
|
||||
@@ -83,7 +83,7 @@ func (s *FileExplorerState) step(from, to *[]string) {
|
||||
|
||||
// FileExplorerResult is a FileExplorer and what happened in this frame.
|
||||
type FileExplorerResult struct {
|
||||
Element *ui.Element
|
||||
Element ui.Element
|
||||
// Navigated reports a new current folder; Changed a new selection.
|
||||
Navigated, Changed bool
|
||||
// Opened is a file the user opened.
|
||||
|
||||
+1
-1
@@ -23,7 +23,7 @@ type FileGridOptions struct {
|
||||
|
||||
// FileGridResult is a FileGrid and what happened in this frame.
|
||||
type FileGridResult struct {
|
||||
Element *ui.Element
|
||||
Element ui.Element
|
||||
// Changed reports a new selection; Opened is the entry opened.
|
||||
Changed bool
|
||||
Opened *FileEntry
|
||||
|
||||
+1
-1
@@ -75,7 +75,7 @@ func fileKindColor(k theme.Tokens, kind FileKind) ui.Color {
|
||||
}
|
||||
|
||||
// FileIcon shows file type or folder opening state.
|
||||
func FileIcon(c *ui.Context, name string, opts FileIconOptions) *ui.Element {
|
||||
func FileIcon(c *ui.Context, name string, opts FileIconOptions) ui.Element {
|
||||
if opts.Size < 0 {
|
||||
panic("mujica: FileIcon Size is negative")
|
||||
}
|
||||
|
||||
@@ -47,7 +47,7 @@ const (
|
||||
|
||||
// FileOperationResult is the panel and the user's answer this frame.
|
||||
type FileOperationResult struct {
|
||||
Element *ui.Element
|
||||
Element ui.Element
|
||||
Canceled bool
|
||||
// Resolved answers a conflict; ApplyAll extends that decision.
|
||||
Resolved ConflictChoice
|
||||
|
||||
@@ -37,7 +37,7 @@ type FilePreviewOptions struct {
|
||||
|
||||
// FilePreviewResult exposes the panel and requested actions.
|
||||
type FilePreviewResult struct {
|
||||
Element *ui.Element
|
||||
Element ui.Element
|
||||
// Closed, Opened and Retried report requested actions.
|
||||
Closed, Opened, Retried bool
|
||||
}
|
||||
|
||||
+2
-2
@@ -24,7 +24,7 @@ type PathBarOptions struct {
|
||||
|
||||
// PathBarResult exposes the toolbar and requested path.
|
||||
type PathBarResult struct {
|
||||
Element *ui.Element
|
||||
Element ui.Element
|
||||
// Navigated is the chosen ancestor path, or empty.
|
||||
Navigated string
|
||||
}
|
||||
@@ -140,7 +140,7 @@ func pathBarFold(c *ui.Context, segs []pathSegment, room float32) int {
|
||||
}
|
||||
|
||||
// pathBarCrumb builds a button, emphasizing the current segment.
|
||||
func pathBarCrumb(c *ui.Context, label string, current bool) *ui.Element {
|
||||
func pathBarCrumb(c *ui.Context, label string, current bool) ui.Element {
|
||||
k := core.Tokens(c)
|
||||
d := core.Density(c)
|
||||
b := ui.ButtonBase(c).MinHeight(theme.MinHitSize).MinWidth(theme.MinHitSize).Padding(0, d.Unit()*2).
|
||||
|
||||
@@ -34,7 +34,7 @@ type PresenceAvatarsOptions struct {
|
||||
|
||||
// PresenceAvatarsResult is the avatars and the follow request this frame.
|
||||
type PresenceAvatarsResult struct {
|
||||
Element *ui.Element
|
||||
Element ui.Element
|
||||
// Follow identifies a collaborator; Unfollow requests stopping.
|
||||
Follow string
|
||||
Unfollow bool
|
||||
@@ -94,7 +94,7 @@ func PresenceAvatars(c *ui.Context, people []Collaborator, opts PresenceAvatarsO
|
||||
rows.Key = func(i int) any { return people[limit+i].ID }
|
||||
ui.List(c, rows, extra, func(i int) {
|
||||
p := people[limit+i]
|
||||
ui.Row(c).Key(p.ID).Gap(u * 2).AlignItems(ui.Center).Children(func() {
|
||||
ui.Row(c.Key(p.ID)).Gap(u * 2).AlignItems(ui.Center).Children(func() {
|
||||
if presenceAvatar(c, p, 24, p.ID == opts.Following).Clicked() {
|
||||
pick(p)
|
||||
}
|
||||
@@ -108,7 +108,7 @@ func PresenceAvatars(c *ui.Context, people []Collaborator, opts PresenceAvatarsO
|
||||
}
|
||||
|
||||
// presenceAvatar builds a collaborator button with presence status.
|
||||
func presenceAvatar(c *ui.Context, p Collaborator, size float32, following bool) *ui.Element {
|
||||
func presenceAvatar(c *ui.Context, p Collaborator, size float32, following bool) ui.Element {
|
||||
if p.ID == "" {
|
||||
panic("mujica: Collaborator needs an ID")
|
||||
}
|
||||
@@ -117,7 +117,7 @@ func presenceAvatar(c *ui.Context, p Collaborator, size float32, following bool)
|
||||
if following {
|
||||
key = "presence_avatars.unfollow"
|
||||
}
|
||||
b := ui.ButtonBase(c).Key(p.ID).Size(max(size+4, theme.MinHitSize), max(size+4, theme.MinHitSize)).Radius((size + 4) / 2).Label(fmt.Sprintf(core.Msg(c, key), p.Name)).Tooltip(p.Name)
|
||||
b := ui.ButtonBase(c.Key(p.ID)).Size(max(size+4, theme.MinHitSize), max(size+4, theme.MinHitSize)).Radius((size + 4) / 2).Label(fmt.Sprintf(core.Msg(c, key), p.Name)).Tooltip(p.Name)
|
||||
if p.Away {
|
||||
b.Description(core.Msg(c, "presence_avatars.away"))
|
||||
}
|
||||
@@ -134,7 +134,7 @@ func presenceAvatar(c *ui.Context, p Collaborator, size float32, following bool)
|
||||
}
|
||||
|
||||
// presenceStyle styles hover, focus and following rings.
|
||||
func presenceStyle(c *ui.Context, b *ui.Element, size float32, following bool) {
|
||||
func presenceStyle(c *ui.Context, b ui.Element, size float32, following bool) {
|
||||
k := core.Tokens(c)
|
||||
b.Shrink(0).Background(k.Surface).FocusRing(false)
|
||||
if b.Hovered() {
|
||||
|
||||
+4
-4
@@ -33,7 +33,7 @@ type ReactionsOptions struct {
|
||||
|
||||
// ReactionsResult exposes reactions and requested changes.
|
||||
type ReactionsResult struct {
|
||||
Element *ui.Element
|
||||
Element ui.Element
|
||||
// Toggled identifies a reaction; Added identifies a picker choice.
|
||||
Toggled, Added string
|
||||
}
|
||||
@@ -65,7 +65,7 @@ func Reactions(c *ui.Context, reactions []Reaction, opts ReactionsOptions) React
|
||||
overlay.Popover(c, add, open, overlay.PopoverOptions{Label: core.Msg(c, "reactions.pick")}, func() {
|
||||
ui.Grid(c).Columns(min(len(opts.Choices), 6)).Gap(u).FocusGroup(ui.Horizontal).Children(func() {
|
||||
for _, e := range opts.Choices {
|
||||
b := ui.ButtonBase(c).Key(e).Size(36, 36).Radius(theme.ControlRadius).Label(e)
|
||||
b := ui.ButtonBase(c.Key(e)).Size(36, 36).Radius(theme.ControlRadius).Label(e)
|
||||
internal.Ring(b, core.Tokens(c).Focus, theme.ControlRadius)
|
||||
if b.Hovered() {
|
||||
b.Background(core.Tokens(c).SurfaceHover)
|
||||
@@ -85,14 +85,14 @@ func Reactions(c *ui.Context, reactions []Reaction, opts ReactionsOptions) React
|
||||
}
|
||||
|
||||
// reactionChip frames and marks the current user's reaction.
|
||||
func reactionChip(c *ui.Context, r Reaction) *ui.Element {
|
||||
func reactionChip(c *ui.Context, r Reaction) ui.Element {
|
||||
k := core.Tokens(c)
|
||||
u := core.Density(c).Unit()
|
||||
label := fmt.Sprintf(core.Msg(c, "reactions.count"), r.Emoji, r.Count)
|
||||
if r.Mine {
|
||||
label += ", " + core.Msg(c, "reactions.mine")
|
||||
}
|
||||
b := ui.ButtonBase(c).Key(r.Emoji).MinHeight(theme.MinHitSize).MinWidth(theme.MinHitSize).Padding(0, u*2).Gap(u).
|
||||
b := ui.ButtonBase(c.Key(r.Emoji)).MinHeight(theme.MinHitSize).MinWidth(theme.MinHitSize).Padding(0, u*2).Gap(u).
|
||||
Radius(theme.MinHitSize / 2).Role(ui.RoleToggleButton).Selected(r.Mine).Label(label).Transition(core.ColorFade)
|
||||
if len(r.People) > 0 {
|
||||
b.Tooltip(strings.Join(r.People, ", "))
|
||||
|
||||
@@ -49,7 +49,7 @@ type recentRow struct{ group, file int }
|
||||
|
||||
// RecentFilesResult is the list and what happened this frame.
|
||||
type RecentFilesResult struct {
|
||||
Element *ui.Element
|
||||
Element ui.Element
|
||||
Opened *RecentFile
|
||||
// Removed identifies one file; Cleared requests clearing all files.
|
||||
Removed string
|
||||
|
||||
@@ -30,7 +30,7 @@ type RemoteCursorOptions struct {
|
||||
|
||||
// RemoteCursor overlays collaborator carets, selections and names; input passes through.
|
||||
// Place it filling the editor's box, after the editor.
|
||||
func RemoteCursor(c *ui.Context, carets []RemoteCaret, opts RemoteCursorOptions) *ui.Element {
|
||||
func RemoteCursor(c *ui.Context, carets []RemoteCaret, opts RemoteCursorOptions) ui.Element {
|
||||
if opts.Caret == nil {
|
||||
panic("mujica: RemoteCursor needs Caret")
|
||||
}
|
||||
|
||||
@@ -23,7 +23,7 @@ type RenameInlineOptions struct {
|
||||
|
||||
// RenameInlineResult is a RenameInline and its outcome in this frame.
|
||||
type RenameInlineResult struct {
|
||||
Element *ui.Element
|
||||
Element ui.Element
|
||||
// Renamed reports a saved name; Canceled reports an abandoned edit.
|
||||
Renamed, Canceled bool
|
||||
}
|
||||
@@ -104,10 +104,10 @@ func RenameInline(c *ui.Context, name *string, editing *bool, opts RenameInlineO
|
||||
*editing, st.active = false, false
|
||||
slog.Debug("mujica: rename_inline end", "renamed", res.Renamed)
|
||||
}
|
||||
ui.Box(c).Key(st.gen).Grow(1).MinWidth(0).Children(func() {
|
||||
ui.Box(c.Key(st.gen)).Grow(1).MinWidth(0).Children(func() {
|
||||
in := input.TextInput(c, &st.draft, input.TextInputOptions{Label: core.Msg(c, "rename_inline.label"), Error: st.err})
|
||||
in.Element.FillWidth()
|
||||
in.Input.AutoFocus()
|
||||
core.AutoFocus(c, in.Input)
|
||||
if in.Changed() {
|
||||
st.err = ""
|
||||
renameTyped(st, *name, opts.Folder)
|
||||
@@ -140,7 +140,7 @@ func renameCheck(c *ui.Context, name string, validate func(string) string) strin
|
||||
}
|
||||
|
||||
// renameSelection marks the stem for replacement on initial input.
|
||||
func renameSelection(c *ui.Context, in *ui.Element, st *renameState) {
|
||||
func renameSelection(c *ui.Context, in ui.Element, st *renameState) {
|
||||
k := core.Tokens(c)
|
||||
stem := renameStem(st.draft, st.folder)
|
||||
if st.stemSel && st.tail == "" {
|
||||
@@ -149,8 +149,9 @@ func renameSelection(c *ui.Context, in *ui.Element, st *renameState) {
|
||||
p.Fill(ui.Rect{X: r.X, Y: r.Y + 2, W: w, H: r.H - 4}, k.Selection, 2)
|
||||
})
|
||||
}
|
||||
disabled := in.IsDisabled()
|
||||
in.HandleInput(func(ev ui.InputEvent) bool {
|
||||
if !st.stemSel || in.IsDisabled() {
|
||||
if !st.stemSel || disabled {
|
||||
return false
|
||||
}
|
||||
switch {
|
||||
|
||||
@@ -43,7 +43,7 @@ type ShareDialogOptions struct {
|
||||
|
||||
// ShareDialogResult is what happened in the dialog this frame.
|
||||
type ShareDialogResult struct {
|
||||
Element *ui.Element
|
||||
Element ui.Element
|
||||
// Invited requests membership; Changed and Removed identify applied changes.
|
||||
Invited *ShareInvite
|
||||
Changed, Removed string
|
||||
@@ -119,7 +119,7 @@ func shareMembers(c *ui.Context, members *[]ShareMember, res *ShareDialogResult,
|
||||
if m.ID == "" {
|
||||
panic("mujica: ShareMember needs an ID")
|
||||
}
|
||||
ui.Row(c).Key(m.ID).Gap(u * 2).AlignItems(ui.Center).MinHeight(core.Density(c).ControlHeight()).Children(func() {
|
||||
ui.Row(c.Key(m.ID)).Gap(u * 2).AlignItems(ui.Center).MinHeight(core.Density(c).ControlHeight()).Children(func() {
|
||||
display.Avatar(c, m.Name, display.AvatarOptions{Size: 28})
|
||||
ui.Column(c).Grow(1).MinWidth(0).Children(func() {
|
||||
ui.Text(c, m.Name).SingleLine()
|
||||
|
||||
@@ -39,7 +39,7 @@ type storageUsageState struct {
|
||||
}
|
||||
|
||||
// StorageUsage displays segmented capacity with keyboard readouts and details.
|
||||
func StorageUsage(c *ui.Context, segments []StorageSegment, opts StorageUsageOptions) *ui.Element {
|
||||
func StorageUsage(c *ui.Context, segments []StorageSegment, opts StorageUsageOptions) ui.Element {
|
||||
if opts.Capacity <= 0 {
|
||||
panic("mujica: StorageUsage needs a positive Capacity")
|
||||
}
|
||||
|
||||
@@ -54,7 +54,7 @@ type TransferQueueOptions struct {
|
||||
|
||||
// TransferQueueResult exposes the queue and requested item action.
|
||||
type TransferQueueResult struct {
|
||||
Element *ui.Element
|
||||
Element ui.Element
|
||||
Action TransferAction
|
||||
// ID is the transfer the action is for.
|
||||
ID string
|
||||
|
||||
Reference in new issue
Block a user