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
@@ -66,7 +66,7 @@ func (k callKey) String() string {
|
||||
}
|
||||
|
||||
// CallControls provides call actions and window shortcuts.
|
||||
func CallControls(c *ui.Context, st CallState, opts CallControlsOptions) *ui.Element {
|
||||
func CallControls(c *ui.Context, st CallState, opts CallControlsOptions) ui.Element {
|
||||
if opts.OnMute == nil || opts.OnCamera == nil || opts.OnShare == nil || opts.OnHangUp == nil {
|
||||
panic("mujica: CallControls needs every callback")
|
||||
}
|
||||
|
||||
@@ -175,7 +175,7 @@ func channelRow(c *ui.Context, k theme.Tokens, ch Channel, on bool) bool {
|
||||
if ch.Unread > 0 {
|
||||
label = fmt.Sprintf(core.Msg(c, "channel_list.unread"), ch.Name, ch.Unread)
|
||||
}
|
||||
b := ui.ButtonBase(c).Key(ch.ID).MinHeight(d.ControlHeight()).Justify(ui.Start).Gap(u*2).Padding(0, u*2, 0, u*5).
|
||||
b := ui.ButtonBase(c.Key(ch.ID)).MinHeight(d.ControlHeight()).Justify(ui.Start).Gap(u*2).Padding(0, u*2, 0, u*5).
|
||||
Radius(theme.ControlRadius).Label(label).Role(ui.RoleTreeItem).Selected(on).Background(ui.Transparent).
|
||||
TextColor(k.Text).Transition(core.ColorFade)
|
||||
internal.Ring(b, k.Focus, theme.ControlRadius)
|
||||
|
||||
@@ -43,7 +43,7 @@ func ChatContinues(prev, cur ChatEntry) bool {
|
||||
}
|
||||
|
||||
// ChatMessage displays text, timestamps, edits and interaction slots.
|
||||
func ChatMessage(c *ui.Context, m ChatEntry, opts ChatMessageOptions) *ui.Element {
|
||||
func ChatMessage(c *ui.Context, m ChatEntry, opts ChatMessageOptions) ui.Element {
|
||||
if m.Author == "" {
|
||||
panic("mujica: ChatMessage needs an Author")
|
||||
}
|
||||
|
||||
@@ -26,7 +26,7 @@ type EmojiPickerOptions struct {
|
||||
|
||||
// EmojiResult exposes the element and this frame’s chosen emoji.
|
||||
type EmojiResult struct {
|
||||
Element *ui.Element
|
||||
Element ui.Element
|
||||
// Picked contains the chosen emoji, or an empty string.
|
||||
Picked string
|
||||
}
|
||||
@@ -139,7 +139,7 @@ func emojiTabs(c *ui.Context, k theme.Tokens, tab *int, recent bool) {
|
||||
*tab = min(*tab, len(tabs)-1)
|
||||
ui.Row(c).FocusGroup(ui.Horizontal).Role(ui.RoleTabList).BorderWidth(0, 0, 1, 0).BorderColor(k.Border).Children(func() {
|
||||
for i, t := range tabs {
|
||||
b := ui.ButtonBase(c).Key(t.key).Size(32, 32).Radius(theme.ControlRadius).Role(ui.RoleTab).Label(core.Msg(c, t.key)).
|
||||
b := ui.ButtonBase(c.Key(t.key)).Size(32, 32).Radius(theme.ControlRadius).Role(ui.RoleTab).Label(core.Msg(c, t.key)).
|
||||
Selected(i == *tab).Background(ui.Transparent).Tooltip(core.Msg(c, t.key))
|
||||
internal.Ring(b, k.Focus, theme.ControlRadius)
|
||||
switch {
|
||||
@@ -160,7 +160,7 @@ func emojiToneRow(c *ui.Context, k theme.Tokens, tone *int) {
|
||||
ui.Row(c).Gap(2).AlignItems(ui.Center).FocusGroup(ui.Horizontal).Role(ui.RoleRadioGroup).Label(core.Msg(c, "emoji_picker.tone")).Children(func() {
|
||||
ui.Text(c, core.Msg(c, "emoji_picker.tone")).FontSize(core.FontSize(c, theme.CaptionSize)).TextColor(k.TextMuted).Margin(0, 6, 0, 0)
|
||||
for i := range emojiTones {
|
||||
b := ui.ButtonBase(c).Key(i).Size(theme.MinHitSize, theme.MinHitSize).Radius(theme.ControlRadius).Role(ui.RoleRadio).
|
||||
b := ui.ButtonBase(c.Key(i)).Size(theme.MinHitSize, theme.MinHitSize).Radius(theme.ControlRadius).Role(ui.RoleRadio).
|
||||
Label(fmt.Sprintf(core.Msg(c, "emoji_picker.toneN"), i)).Selected(i == *tone).Background(ui.Transparent)
|
||||
internal.Ring(b, k.Focus, theme.ControlRadius)
|
||||
if i == *tone {
|
||||
@@ -179,7 +179,7 @@ func emojiToneRow(c *ui.Context, k theme.Tokens, tone *int) {
|
||||
|
||||
func emojiCell(c *ui.Context, k theme.Tokens, e emoji, tone int) string {
|
||||
s := emojiToned(e, tone)
|
||||
b := ui.ButtonBase(c).Key(e.char).Size(36, 36).Radius(theme.ControlRadius).Label(emojiLabel(c, e)).Tooltip(emojiLabel(c, e)).Transition(core.ColorFade)
|
||||
b := ui.ButtonBase(c.Key(e.char)).Size(36, 36).Radius(theme.ControlRadius).Label(emojiLabel(c, e)).Tooltip(emojiLabel(c, e)).Transition(core.ColorFade)
|
||||
internal.Ring(b, k.Focus, theme.ControlRadius)
|
||||
if b.Hovered() || b.Focused() {
|
||||
b.Background(k.SurfaceHover)
|
||||
|
||||
@@ -49,7 +49,7 @@ func IncomingCallDialog(c *ui.Context, open *bool, call IncomingCall, opts Incom
|
||||
u := core.Density(c).Unit()
|
||||
title := fmt.Sprintf(core.Msg(c, "incoming_call_dialog.title"), call.Caller)
|
||||
shown, accepted, video, declined, backClicked := true, false, false, false, false
|
||||
panel := ui.DialogBase(c, &shown, func(back, panel *ui.Element) {
|
||||
panel := ui.DialogBase(c, &shown, func(back, panel ui.Element) {
|
||||
backClicked = back.Clicked() // a click outside neither answers nor refuses
|
||||
p := core.OverlayEnter(c, panel)
|
||||
back.Background(overlay.OverlayScrim(k).Alpha(p)).Padding(16)
|
||||
@@ -68,9 +68,9 @@ func IncomingCallDialog(c *ui.Context, open *bool, call IncomingCall, opts Incom
|
||||
decline := input.Button(c, core.Msg(c, "incoming_call_dialog.decline"), input.ButtonOptions{Variant: input.Danger, Icon: msgHangUp, Label: core.Msg(c, "incoming_call_dialog.decline")})
|
||||
accept := input.Button(c, core.Msg(c, "incoming_call_dialog.accept"), input.ButtonOptions{Icon: msgPhone, Label: core.Msg(c, "incoming_call_dialog.accept")})
|
||||
if opts.FocusAccept {
|
||||
accept.AutoFocus()
|
||||
core.AutoFocus(c, accept)
|
||||
} else {
|
||||
decline.AutoFocus()
|
||||
core.AutoFocus(c, decline)
|
||||
}
|
||||
declined, accepted = decline.Clicked(), accept.Clicked()
|
||||
if call.Video && input.Button(c, core.Msg(c, "incoming_call_dialog.acceptVideo"), input.ButtonOptions{Variant: input.Secondary, Icon: core.MediaCamera, Label: core.Msg(c, "incoming_call_dialog.acceptVideo")}).Clicked() {
|
||||
|
||||
@@ -35,7 +35,7 @@ type MailComposerOptions struct {
|
||||
|
||||
// MailComposerResult is the composer's element and this frame's events.
|
||||
type MailComposerResult struct {
|
||||
Element *ui.Element
|
||||
Element ui.Element
|
||||
// Sent requests sending; Discarded requests discarding the draft.
|
||||
Sent, Discarded bool
|
||||
}
|
||||
@@ -178,7 +178,7 @@ func composerAttachments(c *ui.Context, k theme.Tokens, d *MailDraft, disabled b
|
||||
ui.Row(c).Gap(core.Density(c).Unit() * 2).Wrap().Children(func() {
|
||||
for i, a := range d.Attachments {
|
||||
var v display.TagView
|
||||
ui.Box(c).Key(fmt.Sprint(i, a.Name)).Children(func() {
|
||||
ui.Box(c.Key(fmt.Sprint(i, a.Name))).Children(func() {
|
||||
v = display.Tag(c, a.Name, display.TagOptions{Icon: "file", Closable: true, Disabled: disabled})
|
||||
})
|
||||
if v.Closed {
|
||||
|
||||
@@ -31,7 +31,7 @@ type MailListOptions struct {
|
||||
|
||||
// MailListResult is a MailList's element and this frame's events.
|
||||
type MailListResult struct {
|
||||
Element *ui.Element
|
||||
Element ui.Element
|
||||
changed bool
|
||||
// Opened contains the ID activated by Enter or double-click.
|
||||
Opened string
|
||||
|
||||
@@ -42,7 +42,7 @@ type Mail struct {
|
||||
|
||||
// MailReaderResult is a MailReader's element and this frame's events.
|
||||
type MailReaderResult struct {
|
||||
Element *ui.Element
|
||||
Element ui.Element
|
||||
// Link contains the activated URL, or an empty string.
|
||||
Link string
|
||||
// Opened and Downloaded are attachment indexes, -1 for none.
|
||||
@@ -122,7 +122,7 @@ func mailPlainBody(c *ui.Context, open *map[string]bool, body string) {
|
||||
j++
|
||||
}
|
||||
chunk := strings.Join(lines[i:j], "\n")
|
||||
ui.Box(c).Key(i).Children(func() {
|
||||
ui.Box(c.Key(i)).Children(func() {
|
||||
if quoted {
|
||||
// Fold state is kept per quote text.
|
||||
o := (*open)[chunk]
|
||||
|
||||
@@ -51,7 +51,7 @@ type memberRows struct {
|
||||
|
||||
// MemberListResult is a MemberList's element and this frame's events.
|
||||
type MemberListResult struct {
|
||||
Element *ui.Element
|
||||
Element ui.Element
|
||||
changed bool
|
||||
// Opened contains the ID activated by Enter or double-click.
|
||||
Opened string
|
||||
|
||||
@@ -43,7 +43,7 @@ type OnlineStatusOptions struct {
|
||||
}
|
||||
|
||||
// OnlineStatus shows presence as a distinct shape plus its name.
|
||||
func OnlineStatus(c *ui.Context, p PresenceStatus, opts OnlineStatusOptions) *ui.Element {
|
||||
func OnlineStatus(c *ui.Context, p PresenceStatus, opts OnlineStatusOptions) ui.Element {
|
||||
name := core.Msg(c, p.key())
|
||||
if opts.Size < 0 {
|
||||
panic("mujica: OnlineStatus Size must not be negative")
|
||||
|
||||
@@ -13,7 +13,7 @@ import (
|
||||
|
||||
// PinnedMessagesResult is the list's element and this frame's events.
|
||||
type PinnedMessagesResult struct {
|
||||
Element *ui.Element
|
||||
Element ui.Element
|
||||
// Jump requests navigation; Unpinned requests removing a pin.
|
||||
Jump, Unpinned string
|
||||
}
|
||||
|
||||
@@ -30,7 +30,7 @@ type ReadReceiptOptions struct {
|
||||
}
|
||||
|
||||
// ReadReceipt displays delivery marks, text and reader avatars.
|
||||
func ReadReceipt(c *ui.Context, state ReceiptState, opts ReadReceiptOptions) *ui.Element {
|
||||
func ReadReceipt(c *ui.Context, state ReceiptState, opts ReadReceiptOptions) ui.Element {
|
||||
if state < ReceiptSent || state > ReceiptRead || opts.Max < 0 {
|
||||
panic(fmt.Sprintf("mujica: bad ReadReceipt state %d or Max %d", state, opts.Max))
|
||||
}
|
||||
@@ -62,7 +62,7 @@ func ReadReceipt(c *ui.Context, state ReceiptState, opts ReadReceiptOptions) *ui
|
||||
}
|
||||
ui.Row(c).Children(func() {
|
||||
for i, r := range opts.Readers[:min(limit, len(opts.Readers))] {
|
||||
cell := ui.Box(c).Key(i).Padding(1).Radius(9).Background(k.Background)
|
||||
cell := ui.Box(c.Key(i)).Padding(1).Radius(9).Background(k.Background)
|
||||
if i > 0 {
|
||||
cell.Margin(0, 0, 0, -5)
|
||||
}
|
||||
|
||||
@@ -124,7 +124,7 @@ func RecipientInput(c *ui.Context, recipients *[]string, opts RecipientInputOpti
|
||||
panel.Children(func() {
|
||||
ui.Scroll(c).Shrink(1).Padding(4).Children(func() {
|
||||
for i, ct := range s.matches {
|
||||
row := ui.ButtonBase(c).Key(ct.Email).MinHeight(core.Density(c).ControlHeight()).Justify(ui.Start).Gap(8).Padding(0, 8).
|
||||
row := ui.ButtonBase(c.Key(ct.Email)).MinHeight(core.Density(c).ControlHeight()).Justify(ui.Start).Gap(8).Padding(0, 8).
|
||||
Radius(theme.ControlRadius).Role(ui.RoleListItem).Label(recipientFormat(ct)).FocusRing(false)
|
||||
if i == s.hi || row.Hovered() {
|
||||
row.Background(k.Selection)
|
||||
|
||||
@@ -105,7 +105,7 @@ func SnoozePicker(c *ui.Context, until *time.Time, opts SnoozePickerOptions) cor
|
||||
return b.Clicked() && !b.IsDisabled()
|
||||
}
|
||||
for _, ch := range SnoozeChoices(now, morning) {
|
||||
ui.Box(c).Key(ch.Key).Children(func() {
|
||||
ui.Box(c.Key(ch.Key)).Children(func() {
|
||||
if row(core.Msg(c, ch.Key), snoozeWhen(c, now, ch.At), until.Equal(ch.At)) {
|
||||
*until, s.custom = ch.At, false
|
||||
}
|
||||
|
||||
@@ -99,7 +99,7 @@ func StatusSetter(c *ui.Context, st *CustomStatus, opts StatusSetterOptions) cor
|
||||
for i, o := range statusClear {
|
||||
options[i] = input.SelectOption[int]{Value: i, Label: core.Msg(c, o.key)}
|
||||
}
|
||||
ui.Box(c).Key(live).Children(func() {
|
||||
ui.Box(c.Key(live)).Children(func() {
|
||||
if input.Select(c, &s.clear, options, input.SelectOptions{Label: core.Msg(c, "status_setter.clearAfter"), Placeholder: core.MessagingDate(c, st.Until.In(loc), "status_setter.until"), Disabled: !live}).Changed() {
|
||||
st.Until = statusUntil(c.Now().In(loc), statusClear[s.clear].after)
|
||||
}
|
||||
|
||||
@@ -22,7 +22,7 @@ type ThreadPanelOptions struct {
|
||||
|
||||
// ThreadPanelResult is the panel's element and this frame's events.
|
||||
type ThreadPanelResult struct {
|
||||
Element *ui.Element
|
||||
Element ui.Element
|
||||
// Sent is a reply the user sent; empty is none.
|
||||
Sent string
|
||||
}
|
||||
|
||||
@@ -10,7 +10,7 @@ import (
|
||||
)
|
||||
|
||||
// UnreadDivider marks where unread messages begin; count zero shows "New".
|
||||
func UnreadDivider(c *ui.Context, count int) *ui.Element {
|
||||
func UnreadDivider(c *ui.Context, count int) ui.Element {
|
||||
if count < 0 {
|
||||
panic("mujica: UnreadDivider count must not be negative")
|
||||
}
|
||||
|
||||
@@ -31,7 +31,7 @@ type UserProfileCardOptions struct {
|
||||
}
|
||||
|
||||
// UserProfileCard displays identity, presence, role and action slots.
|
||||
func UserProfileCard(c *ui.Context, prof UserProfile, opts UserProfileCardOptions) *ui.Element {
|
||||
func UserProfileCard(c *ui.Context, prof UserProfile, opts UserProfileCardOptions) ui.Element {
|
||||
if prof.Name == "" {
|
||||
panic("mujica: UserProfileCard needs a Name")
|
||||
}
|
||||
|
||||
@@ -31,7 +31,7 @@ type VideoCallGridOptions struct {
|
||||
}
|
||||
|
||||
// VideoCallGrid displays participant frames and speaker marks.
|
||||
func VideoCallGrid(c *ui.Context, people []CallParticipant, opts VideoCallGridOptions) *ui.Element {
|
||||
func VideoCallGrid(c *ui.Context, people []CallParticipant, opts VideoCallGridOptions) ui.Element {
|
||||
if opts.Columns < 0 {
|
||||
panic("mujica: VideoCallGrid Columns must not be negative")
|
||||
}
|
||||
@@ -78,7 +78,7 @@ func callTile(c *ui.Context, k theme.Tokens, p CallParticipant) {
|
||||
if len(notes) > 0 {
|
||||
label += ", " + strings.Join(notes, ", ")
|
||||
}
|
||||
tile := ui.Box(c).Key(p.ID).AspectRatio(16.0 / 9).Radius(theme.CardRadius).Clip().Background(media.VideoBlack).Center().
|
||||
tile := ui.Box(c.Key(p.ID)).AspectRatio(16.0 / 9).Radius(theme.CardRadius).Clip().Background(media.VideoBlack).Center().
|
||||
Focusable().Role(ui.RoleImage).Label(label)
|
||||
internal.Ring(tile, k.Focus, theme.CardRadius)
|
||||
light := ui.Hex("#F2EBDD")
|
||||
|
||||
Reference in new issue
Block a user