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:
ZacharyZhang-NYandClaude Opus 5.5 committed 2026-10-17 01:37:54 -04:00
1 parent bc716d27be
commit bcce34cda1
603 files changed
+1366 -1277

No files matched your search

+1 -1
View File
@@ -99,7 +99,7 @@ func AssetAllocationChart(c *ui.Context, items []AllocationItem, opts AssetAlloc
ui.Grid(c).ColumnTracks(ui.Fr(2), ui.Fr(1), ui.Fr(1), ui.Fr(2)).GapX(u * 2).MinHeight(core.Density(c).ControlHeight()).Children(func() {
w := it.Value / total * 100
drift := w - it.Target
ui.Row(c).Key(it.Name).Gap(u * 2).AlignItems(ui.Center).Children(func() {
ui.Row(c.Key(it.Name)).Gap(u * 2).AlignItems(ui.Center).Children(func() {
ui.Box(c).Size(12, 12).Draw(func(p *ui.Painter, r ui.Rect) {
internal.Marker(p, memo.markers[i], r.X+6, r.Y+6, 10, pal[memo.markers[i]%len(pal)])
})
+1 -1
View File
@@ -20,7 +20,7 @@ type BidAskBarOptions struct {
}
// BidAskBar compares buy and sell volume in a split bar.
func BidAskBar(c *ui.Context, bid, ask float64, opts BidAskBarOptions) *ui.Element {
func BidAskBar(c *ui.Context, bid, ask float64, opts BidAskBarOptions) ui.Element {
if bid < 0 || ask < 0 || math.IsNaN(bid+ask) {
panic("mujica: BidAskBar volumes must be zero or more")
}
+4 -4
View File
@@ -39,7 +39,7 @@ type CurrencyConverterOptions struct {
}
type CurrencyConverterView struct {
Element *ui.Element
Element ui.Element
Result float64
// PairChanged asks the caller for the new pair's rate.
PairChanged bool
@@ -92,12 +92,12 @@ func CurrencyConverter(c *ui.Context, s *CurrencyConverterState, currencies []Cu
}
v.Element.Children(func() {
ui.Row(c).Gap(u * 2).AlignItems(ui.End).Wrap().Children(func() {
input.FormField(c, core.Msg(c, "currency_converter.amount"), input.FormFieldOptions{}, func() *ui.Element {
input.FormField(c, core.Msg(c, "currency_converter.amount"), input.FormFieldOptions{}, func() ui.Element {
r := input.NumberInput(c, &s.Amount, input.NumberInputOptions{Min: 0, Max: 1e15, Step: math.Pow(10, -float64(from.Decimals)),
Precision: from.Decimals, Label: core.Msg(c, "currency_converter.amount"), Disabled: opts.Disabled})
return r.Element.Width(170)
})
input.FormField(c, core.Msg(c, "currency_converter.from"), input.FormFieldOptions{}, func() *ui.Element {
input.FormField(c, core.Msg(c, "currency_converter.from"), input.FormFieldOptions{}, func() ui.Element {
r := input.Select(c, &s.From, options, input.SelectOptions{Label: core.Msg(c, "currency_converter.from"), Disabled: opts.Disabled})
v.PairChanged = v.PairChanged || r.Changed()
return r.Element
@@ -106,7 +106,7 @@ func CurrencyConverter(c *ui.Context, s *CurrencyConverterState, currencies []Cu
if swap.Clicked() && !swap.IsDisabled() {
s.From, s.To, v.PairChanged = s.To, s.From, true
}
input.FormField(c, core.Msg(c, "currency_converter.to"), input.FormFieldOptions{}, func() *ui.Element {
input.FormField(c, core.Msg(c, "currency_converter.to"), input.FormFieldOptions{}, func() ui.Element {
r := input.Select(c, &s.To, options, input.SelectOptions{Label: core.Msg(c, "currency_converter.to"), Disabled: opts.Disabled})
v.PairChanged = v.PairChanged || r.Changed()
return r.Element
+2 -2
View File
@@ -38,7 +38,7 @@ type depthLadderCache struct {
// DepthLadderResult is a built DepthLadder and its clicked cell.
type DepthLadderResult struct {
Element *ui.Element
Element ui.Element
price float64
side OrderSide
}
@@ -118,7 +118,7 @@ func depthLadderRow(c *ui.Context, r *DepthLadderResult, s *DepthLadderState, t
d := core.Density(c)
price := float64(t) / scale
text := data.StatisticFormat(price, dec)
ui.Row(c).Key(t).MinHeight(d.ControlHeight()).AlignItems(ui.Stretch).BorderWidth(0, 0, theme.BorderWidth, 0).BorderColor(k.Border).Children(func() {
ui.Row(c.Key(t)).MinHeight(d.ControlHeight()).AlignItems(ui.Stretch).BorderWidth(0, 0, theme.BorderWidth, 0).BorderColor(k.Border).Children(func() {
cell := func(side OrderSide, size float64) {
label := fmt.Sprintf(core.Msg(c, fmt.Sprintf("depth_ladder.order%d", side)), text)
b := ui.ButtonBase(c).Grow(1).Basis(0).Padding(0, d.Unit()*2).Label(label)
+4 -4
View File
@@ -33,7 +33,7 @@ type GreeksTableOptions struct {
var greekNames = [5]string{"Delta", "Gamma", "Theta", "Vega", "Rho"}
func GreeksTable(c *ui.Context, rows []GreeksRow, opts GreeksTableOptions) *ui.Element {
func GreeksTable(c *ui.Context, rows []GreeksRow, opts GreeksTableOptions) ui.Element {
if opts.Decimals < 0 || opts.Decimals > 12 {
panic("mujica: GreeksTable Decimals is negative")
}
@@ -50,7 +50,7 @@ func GreeksTable(c *ui.Context, rows []GreeksRow, opts GreeksTableOptions) *ui.E
ui.Text(c, core.Msg(c, "greeks_table.contract")).TextColor(k.TextMuted).FontSize(core.FontSize(c, theme.CaptionSize)).
MinHeight(d.ControlHeight()).Role(ui.RoleColumnHeader)
for i, name := range greekNames {
h := ui.Row(c).Key(name).Gap(2).AlignItems(ui.Center).Justify(ui.End).MinHeight(d.ControlHeight()).
h := ui.Row(c.Key(name)).Gap(2).AlignItems(ui.Center).Justify(ui.End).MinHeight(d.ControlHeight()).
Role(ui.RoleColumnHeader).Label(name).Description(core.Msg(c, "greeks_table.note_"+greekKey(i)))
h.Tooltip(core.Msg(c, "greeks_table.note_"+greekKey(i)))
h.Children(func() {
@@ -65,13 +65,13 @@ func GreeksTable(c *ui.Context, rows []GreeksRow, opts GreeksTableOptions) *ui.E
ui.Grid(c).ColumnTracks(ui.Fr(2), ui.Fr(1), ui.Fr(1), ui.Fr(1), ui.Fr(1), ui.Fr(1)).GapX(u*2).Padding(0, u*3).MinHeight(d.ControlHeight()).Children(func() {
r := rows[ri]
vals := [5]float64{r.Greeks.Delta, r.Greeks.Gamma, r.Greeks.Theta, r.Greeks.Vega, r.Greeks.Rho}
ui.Text(c, r.Label).Key(ri).SingleLine().MinHeight(d.ControlHeight()).Role(ui.RoleCell)
ui.Text(c.Key(ri), r.Label).SingleLine().MinHeight(d.ControlHeight()).Role(ui.RoleCell)
for i, v := range vals {
s := "—"
if !math.IsNaN(v) {
s = data.StatisticSigned(v, dec)
}
ui.Text(c, s).Key([2]int{ri, i}).JustifySelf(ui.End).FontFeatures("tnum").TextColor(k.Text).
ui.Text(c.Key([2]int{ri, i}), s).JustifySelf(ui.End).FontFeatures("tnum").TextColor(k.Text).
Role(ui.RoleCell).Label(greekNames[i] + " " + s)
}
})
+1 -1
View File
@@ -36,7 +36,7 @@ func IndexCard(c *ui.Context, q IndexQuote, opts IndexCardOptions) core.ChoiceRe
dec := core.PriceDecimals("IndexCard", opts.Decimals)
k := core.Tokens(c)
u := core.Density(c).Unit()
var e *ui.Element
var e ui.Element
ui.Row(c).Shrink(0).Disabled(opts.Disabled).Children(func() {
if opts.Clickable {
e = ui.ButtonBase(c).Justify(ui.Start)
+1 -1
View File
@@ -40,7 +40,7 @@ type level2Mark struct {
}
// Level2Quotes lists bid and ask levels; changes flash and mark.
func Level2Quotes(c *ui.Context, data Level2Data, opts Level2QuotesOptions) *ui.Element {
func Level2Quotes(c *ui.Context, data Level2Data, opts Level2QuotesOptions) ui.Element {
dec := core.PriceDecimals("Level2Quotes", opts.Decimals)
if opts.SizeDecimals < 0 || opts.Levels < 0 {
panic("mujica: Level2Quotes SizeDecimals and Levels cannot be negative")
+1 -1
View File
@@ -21,7 +21,7 @@ type MarginIndicatorOptions struct {
}
// MarginIndicator shows maintenance over equity against its two lines.
func MarginIndicator(c *ui.Context, ratio float64, opts MarginIndicatorOptions) *ui.Element {
func MarginIndicator(c *ui.Context, ratio float64, opts MarginIndicatorOptions) ui.Element {
warn, liq := opts.Warning, opts.Liquidation
if warn == 0 && liq == 0 {
warn, liq = 0.8, 1
+2 -2
View File
@@ -23,7 +23,7 @@ type MarketOverviewOptions struct {
}
type MarketOverviewView struct {
Element *ui.Element
Element ui.Element
Chosen int
}
@@ -39,7 +39,7 @@ func MarketOverview(c *ui.Context, indices []IndexQuote, opts MarketOverviewOpti
v.Element.Children(func() {
ui.Row(c).Wrap().Gap(u * 3).Children(func() {
for i, q := range indices {
ui.Column(c).Key(q.Name).Children(func() {
ui.Column(c.Key(q.Name)).Children(func() {
if IndexCard(c, q, IndexCardOptions{Decimals: opts.Decimals, Clickable: true, Disabled: opts.Disabled}).Changed() {
v.Chosen = i
}
+1 -1
View File
@@ -36,7 +36,7 @@ type MarketStatusOptions struct {
Now time.Time
}
func MarketStatus(c *ui.Context, phase MarketPhase, opts MarketStatusOptions) *ui.Element {
func MarketStatus(c *ui.Context, phase MarketPhase, opts MarketStatusOptions) ui.Element {
marketPhaseCheck(phase)
marketPhaseCheck(opts.NextPhase)
k := core.Tokens(c)
+2 -2
View File
@@ -58,7 +58,7 @@ type NewsFeedOptions struct {
// NewsFeedView is a built NewsFeed and this frame's events.
type NewsFeedView struct {
Element *ui.Element
Element ui.Element
Opened string
// Symbol is the related code clicked; empty for none.
Symbol string
@@ -136,7 +136,7 @@ func NewsFeed(c *ui.Context, s *NewsFeedState, items []NewsItem, opts NewsFeedOp
ui.Row(c).Gap(d.Unit() * 2).AlignItems(ui.Center).Wrap().Children(func() {
ui.Text(c, it.Source+" · "+chat.DateSeparatorWhen(c, it.Time, today)).FontSize(core.FontSize(c, theme.CaptionSize)).TextColor(k.TextMuted)
for _, sym := range it.Symbols {
ui.Row(c).Key(sym).Children(func() {
ui.Row(c.Key(sym)).Children(func() {
if SymbolBadge(c, MarketSymbol{Code: sym}, SymbolBadgeOptions{Clickable: true, Disabled: opts.Disabled}).Changed() {
v.Symbol = sym
}
+1 -1
View File
@@ -54,7 +54,7 @@ type OptionChainOptions struct {
// OptionChainView is a built OptionChain and this frame's events.
type OptionChainView struct {
Element *ui.Element
Element ui.Element
// ExpiryChanged asks the caller for the strikes of State.Expiry.
ExpiryChanged bool
Changed, Submitted bool
+2 -2
View File
@@ -90,7 +90,7 @@ func bookKeyOf(book OrderBookData, group float64) bookKey {
// OrderBookResult is a built OrderBook and its clicked level.
type OrderBookResult struct {
Element *ui.Element
Element ui.Element
price float64
side OrderSide
}
@@ -191,7 +191,7 @@ func orderBookRow(c *ui.Context, r *OrderBookResult, l BookLevel, cum, total flo
d := core.Density(c)
col := core.TrendColor(c, side.sign())
price := data.StatisticFormat(l.Price, dec)
b := ui.ButtonBase(c).Key(fmt.Sprint(side, price)).MinHeight(d.ControlHeight()).
b := ui.ButtonBase(c.Key(fmt.Sprint(side, price))).MinHeight(d.ControlHeight()).
Padding(0, d.Unit()*2).Radius(theme.ControlRadius).
Label(fmt.Sprintf(core.Msg(c, "order_book.level"), core.Msg(c, fmt.Sprintf("order_book.side%d", side)), price, data.StatisticFormat(l.Size, sizeDec)))
internal.Ring(b, k.Focus, theme.ControlRadius)
+1 -1
View File
@@ -50,7 +50,7 @@ func OrderConfirmDialog(c *ui.Context, open *bool, opts OrderConfirmDialogOption
was := *open
var st *orderConfirmState
overlay.Dialog(c, open, overlay.DialogOptions{Title: title, KeepOnBackdrop: true, HideClose: true, Actions: func() {
if input.Button(c, core.Msg(c, "order_confirm_dialog.cancel"), input.ButtonOptions{Variant: input.Secondary}).AutoFocus().Clicked() {
if core.AutoFocus(c, input.Button(c, core.Msg(c, "order_confirm_dialog.cancel"), input.ButtonOptions{Variant: input.Secondary})).Clicked() {
res.Canceled = true
}
locked := opts.Acknowledge != "" && !st.ack
+5 -5
View File
@@ -78,7 +78,7 @@ func (s *OrderEntryState) EndSubmit(formError string) { s.Form.EndSubmit(formErr
// OrderEntryResult is a built OrderEntry and its submitted order.
type OrderEntryResult struct {
Element *ui.Element
Element ui.Element
order OrderDraft
submitted bool
}
@@ -167,13 +167,13 @@ func orderEntryFields(c *ui.Context, f *input.FormScope, s *OrderEntryState, opt
input.FormItem(c, f, "quantity", &s.Draft, input.FormItemOptions[OrderDraft]{Label: core.Msg(c, "order_entry.quantity"), Required: true, Initial: initial,
Validate: func(o OrderDraft) string { return orderEntryCheck(c, o, opts, maxQ) },
Async: opts.Check,
}, func() *ui.Element {
}, func() ui.Element {
return input.NumberInput(c, &s.Draft.Quantity, input.NumberInputOptions{Step: lot, Disabled: disabled, Error: s.Form.Error("quantity")}).Input
})
if s.Draft.Type == OrderStop || s.Draft.Type == OrderStopLimit {
input.FormItem(c, f, "trigger", &s.Draft.Trigger, input.FormItemOptions[float64]{Label: core.Msg(c, "order_entry.trigger"), Required: true, Initial: initial.Trigger,
Validate: func(v float64) string { return orderEntryTrigger(c, s.Draft, opts.Last) },
}, func() *ui.Element {
}, func() ui.Element {
return input.NumberInput(c, &s.Draft.Trigger, input.NumberInputOptions{Step: tick, Disabled: disabled, Error: s.Form.Error("trigger")}).Input
})
}
@@ -185,7 +185,7 @@ func orderEntryFields(c *ui.Context, f *input.FormScope, s *OrderEntryState, opt
}
return ""
},
}, func() *ui.Element {
}, func() ui.Element {
return input.NumberInput(c, &s.Draft.Price, input.NumberInputOptions{Step: tick, Disabled: disabled, Error: s.Form.Error("price")}).Input
})
}
@@ -193,7 +193,7 @@ func orderEntryFields(c *ui.Context, f *input.FormScope, s *OrderEntryState, opt
for t := TIFDay; t <= TIFFillOrKill; t++ {
tifs = append(tifs, input.SelectOption[TimeInForce]{Value: t, Label: core.Msg(c, fmt.Sprintf("order_entry.tif%d", t))})
}
input.FormItem(c, f, "tif", &s.Draft.TIF, input.FormItemOptions[TimeInForce]{Label: core.Msg(c, "order_entry.tif"), Initial: initial.TIF}, func() *ui.Element {
input.FormItem(c, f, "tif", &s.Draft.TIF, input.FormItemOptions[TimeInForce]{Label: core.Msg(c, "order_entry.tif"), Initial: initial.TIF}, func() ui.Element {
return input.Select(c, &s.Draft.TIF, tifs, input.SelectOptions{Label: core.Msg(c, "order_entry.tif"), Disabled: disabled}).Element
})
}
+1 -1
View File
@@ -46,7 +46,7 @@ type PositionTableOptions struct {
// TableActionResult is a trading table and its acted row.
type TableActionResult struct {
Element *ui.Element
Element ui.Element
Table data.DataTableView
id string
}
+1 -1
View File
@@ -17,7 +17,7 @@ type PriceChangeBadgeOptions struct {
}
// PriceChangeBadge shows a signed change and percent with an arrow.
func PriceChangeBadge(c *ui.Context, change, percent float64, opts PriceChangeBadgeOptions) *ui.Element {
func PriceChangeBadge(c *ui.Context, change, percent float64, opts PriceChangeBadgeOptions) ui.Element {
dec := core.PriceDecimals("PriceChangeBadge", opts.Decimals)
sign := core.TrendSign(change)
if sign == 0 {
+1 -1
View File
@@ -37,7 +37,7 @@ type priceTextState struct {
}
// PriceText shows a price; a change tints it once.
func PriceText(c *ui.Context, value float64, opts PriceTextOptions) *ui.Element {
func PriceText(c *ui.Context, value float64, opts PriceTextOptions) ui.Element {
dec := core.PriceDecimals("PriceText", opts.Decimals)
if opts.Size < 0 {
panic("mujica: PriceText with negative Size")
+1 -2
View File
@@ -79,8 +79,7 @@ func TestPriceTextFlashesOnceAndNotUnderReducedMotion(t *testing.T) {
if same == !still {
t.Fatalf("reduced motion %v: flashed %v", still, !same)
}
time.Sleep(priceTextFlash + 20*time.Millisecond)
tt.Frame()
tt.Advance(priceTextFlash + 20*time.Millisecond)
if !bytes.Equal(tt.Image().Pix, fresh.Image().Pix) {
t.Fatalf("reduced motion %v: flash did not end", still)
}
+2 -2
View File
@@ -29,7 +29,7 @@ type QuickTradeButtonsOptions struct {
// QuickTradeResult is built QuickTradeButtons and their trade.
type QuickTradeResult struct {
Element *ui.Element
Element ui.Element
side OrderSide
quantity float64
}
@@ -110,7 +110,7 @@ func QuickTradeButtons(c *ui.Context, quantity *float64, opts QuickTradeButtonsO
}
// quickTradeButton is a large button tinted by its side.
func quickTradeButton(c *ui.Context, label string, side OrderSide) *ui.Element {
func quickTradeButton(c *ui.Context, label string, side OrderSide) ui.Element {
k := core.Tokens(c)
d := core.Density(c)
col := core.TrendColor(c, side.sign())
+1 -1
View File
@@ -24,7 +24,7 @@ type QuoteCardOptions struct {
}
// QuoteCard shows a symbol, name, price, change and mini chart.
func QuoteCard(c *ui.Context, q Quote, opts QuoteCardOptions) *ui.Element {
func QuoteCard(c *ui.Context, q Quote, opts QuoteCardOptions) ui.Element {
if q.Symbol == "" {
panic("mujica: QuoteCard needs a Symbol")
}
+1 -1
View File
@@ -25,7 +25,7 @@ type RiskMeterOptions struct {
}
// RiskMeter shows level, from 1, on a named graded scale.
func RiskMeter(c *ui.Context, level int, opts RiskMeterOptions) *ui.Element {
func RiskMeter(c *ui.Context, level int, opts RiskMeterOptions) ui.Element {
levels := opts.Levels
if levels == nil {
for i := range 5 {
+2 -2
View File
@@ -87,7 +87,7 @@ type ScreenerOptions struct {
// ScreenerView is a built Screener and this frame's events.
type ScreenerView struct {
Element *ui.Element
Element ui.Element
Changed, Save bool
Table data.DataTableView
}
@@ -137,7 +137,7 @@ func Screener(c *ui.Context, s *ScreenerState, fields []ScreenerField, rows []Sc
cd := &s.Conditions[i]
f := screenerField(fields, cd.Field)
removed := false
ui.Row(c).Key(i).Gap(u * 2).AlignItems(ui.Center).Children(func() {
ui.Row(c.Key(i)).Gap(u * 2).AlignItems(ui.Center).Children(func() {
input.Select(c, &cd.Field, fieldItems, input.SelectOptions{Label: core.Msg(c, "screener.field"), Disabled: opts.Disabled})
input.Select(c, &cd.Op, opItems, input.SelectOptions{Label: core.Msg(c, "screener.compare"), Disabled: opts.Disabled}).Element.MinWidth(120)
input.NumberInput(c, &cd.Value, input.NumberInputOptions{Step: math.Pow(10, -float64(f.Decimals)), Precision: f.Decimals,
+1 -1
View File
@@ -30,7 +30,7 @@ var sentimentBands = [5]string{"sentiment_gauge.extreme_fear", "sentiment_gauge.
func sentimentBand(v float64) int { return min(4, int(v/20)) }
func SentimentGauge(c *ui.Context, value float64, opts SentimentGaugeOptions) *ui.Element {
func SentimentGauge(c *ui.Context, value float64, opts SentimentGaugeOptions) ui.Element {
if math.IsNaN(value) || value < 0 || value > 100 {
panic("mujica: SentimentGauge value must be in 0..100")
}
+8 -7
View File
@@ -40,15 +40,16 @@ func TestSentimentGaugeStatesKeepSize(t *testing.T) {
status := data.DataReady
empty := false
retried := 0
var element *ui.Element
var bounds ui.Rect
tt := chatTester(400, 300, func(c *ui.Context) {
element = SentimentGauge(c, 72, SentimentGaugeOptions{Label: "Sentiment", Status: status, Empty: empty, Error: "Caller failed", Retry: func() { retried++ }})
bounds = SentimentGauge(c, 72, SentimentGaugeOptions{Label: "Sentiment", Status: status, Empty: empty, Error: "Caller failed", Retry: func() { retried++ }}).Bounds()
})
height := element.Bounds().H
settled := func() float32 { tt.Frame(); return bounds.H } // Bounds reads the previous frame
height := settled()
status = data.DataLoading
tt.Frame()
if element.Bounds().H != height {
t.Fatalf("loading height %v, ready %v", element.Bounds().H, height)
if h := settled(); h != height {
t.Fatalf("loading height %v, ready %v", h, height)
}
status = data.DataFailed
tt.Frame()
@@ -59,7 +60,7 @@ func TestSentimentGaugeStatesKeepSize(t *testing.T) {
status = data.DataReady
empty = true
tt.Frame()
if !tt.HasText("No data") || element.Bounds().H != height {
t.Fatalf("empty height %v, ready %v", element.Bounds().H, height)
if h := settled(); !tt.HasText("No data") || h != height {
t.Fatalf("empty height %v, ready %v", h, height)
}
}
+1 -1
View File
@@ -20,7 +20,7 @@ type SpreadIndicatorOptions struct {
}
// SpreadIndicator shows ask minus bid and its percent.
func SpreadIndicator(c *ui.Context, bid, ask float64, opts SpreadIndicatorOptions) *ui.Element {
func SpreadIndicator(c *ui.Context, bid, ask float64, opts SpreadIndicatorOptions) ui.Element {
if opts.WarnPercent < 0 {
panic("mujica: SpreadIndicator with negative WarnPercent")
}
+1 -1
View File
@@ -33,7 +33,7 @@ func SymbolBadge(c *ui.Context, s MarketSymbol, opts SymbolBadgeOptions) core.Ch
}
k := core.Tokens(c)
u := core.Density(c).Unit()
var e *ui.Element
var e ui.Element
wrap := ui.Row(c).Shrink(0).Disabled(opts.Disabled)
wrap.Children(func() {
if opts.Clickable {
+2 -2
View File
@@ -33,7 +33,7 @@ type SymbolSearchOptions struct {
// SymbolSearchView is a built SymbolSearch and this frame's events.
type SymbolSearchView struct {
Element *ui.Element
Element ui.Element
Changed bool
// Chosen is the symbol picked when Picked is set.
Chosen MarketSymbol
@@ -131,7 +131,7 @@ func SymbolSearch(c *ui.Context, s *SymbolSearchState, results []MarketSymbol, o
func symbolSearchRow(c *ui.Context, sym MarketSymbol, selected bool) bool {
k := core.Tokens(c)
d := core.Density(c)
b := ui.ButtonBase(c).Key(sym.Market+":"+sym.Code).MinHeight(d.ControlHeight()).Padding(0, d.Unit()*2).Gap(d.Unit() * 3).
b := ui.ButtonBase(c.Key(sym.Market+":"+sym.Code)).MinHeight(d.ControlHeight()).Padding(0, d.Unit()*2).Gap(d.Unit() * 3).
Justify(ui.Start).Radius(theme.ControlRadius).Label(symbolBadgeName(sym)).Role(ui.RoleListItem)
internal.Ring(b, k.Focus, theme.ControlRadius)
+3 -3
View File
@@ -41,7 +41,7 @@ type tickerTapeState struct {
}
// TickerTape loops quotes, pausing on hover or focus.
func TickerTape(c *ui.Context, items []TickerItem, opts TickerTapeOptions) *ui.Element {
func TickerTape(c *ui.Context, items []TickerItem, opts TickerTapeOptions) ui.Element {
if opts.Speed < 0 || opts.PerPage < 0 {
panic("mujica: TickerTape Speed and PerPage cannot be negative")
}
@@ -91,7 +91,7 @@ func TickerTape(c *ui.Context, items []TickerItem, opts TickerTapeOptions) *ui.E
strip.Children(func() {
// The second copy fills in as the first scrolls out.
for n := range 2 {
r := ui.Row(c).Key(n).AlignItems(ui.Center).Shrink(0)
r := ui.Row(c.Key(n)).AlignItems(ui.Center).Shrink(0)
if n == 1 {
r.Role(ui.RoleNone)
}
@@ -107,7 +107,7 @@ func TickerTape(c *ui.Context, items []TickerItem, opts TickerTapeOptions) *ui.E
}
// tickerTapePages shows one page of items, for reduced motion.
func tickerTapePages(c *ui.Context, e *ui.Element, st *tickerTapeState, items []TickerItem, per, dec int) {
func tickerTapePages(c *ui.Context, e ui.Element, st *tickerTapeState, items []TickerItem, per, dec int) {
pages := max(1, (len(items)+per-1)/per)
st.page = min(st.page, pages-1)
turn := func(by int) {
+3 -6
View File
@@ -32,8 +32,7 @@ func TestTickerTapeScrollsAndPausesOnHover(t *testing.T) {
ui.Box(c).Width(380).Children(func() { TickerTape(c, tickerTapeItems(6), TickerTapeOptions{Speed: 200}) })
})
x0 := tickerTapeX(t, tt)
time.Sleep(60 * time.Millisecond)
tt.Frame()
tt.Advance(60 * time.Millisecond)
x1 := tickerTapeX(t, tt)
if x1 >= x0 {
t.Fatalf("did not scroll: %v -> %v", x0, x1)
@@ -41,8 +40,7 @@ func TestTickerTapeScrollsAndPausesOnHover(t *testing.T) {
tt.Move(100, 25)
tt.Frame()
x2 := tickerTapeX(t, tt)
time.Sleep(60 * time.Millisecond)
tt.Frame()
tt.Advance(60 * time.Millisecond)
if x3 := tickerTapeX(t, tt); x3 != x2 {
t.Fatalf("moved under the pointer: %v -> %v", x2, x3)
}
@@ -57,8 +55,7 @@ func TestTickerTapePausesOnFocus(t *testing.T) {
t.Fatal("tape not focusable")
}
x0 := tickerTapeX(t, tt)
time.Sleep(60 * time.Millisecond)
tt.Frame()
tt.Advance(60 * time.Millisecond)
if x1 := tickerTapeX(t, tt); x1 != x0 {
t.Fatalf("moved while focused: %v -> %v", x0, x1)
}
+1 -1
View File
@@ -47,7 +47,7 @@ type TradeHistoryTableState struct {
// TradeHistoryResult is a built table and its export request.
type TradeHistoryResult struct {
Element *ui.Element
Element ui.Element
Table data.DataTableView
exported []FillRow
export bool
+2 -2
View File
@@ -29,7 +29,7 @@ type TradingSessionClockOptions struct {
Now time.Time
}
func TradingSessionClock(c *ui.Context, markets []SessionMarket, opts TradingSessionClockOptions) *ui.Element {
func TradingSessionClock(c *ui.Context, markets []SessionMarket, opts TradingSessionClockOptions) ui.Element {
now := opts.Now
if now.IsZero() {
now = c.Now()
@@ -43,7 +43,7 @@ func TradingSessionClock(c *ui.Context, markets []SessionMarket, opts TradingSes
tradingSessionCheck(m)
local := now.In(m.Location)
phase, next, nextPhase := tradingSessionPhase(m, local)
ui.Column(c).Key(m.Name).Gap(2).Children(func() {
ui.Column(c.Key(m.Name)).Gap(2).Children(func() {
ui.Text(c, m.Name).Font(theme.SerifFont).FontWeight(600)
ui.Text(c, local.Format("15:04 MST")).FontSize(size).TextColor(k.TextMuted).FontFeatures("tnum")
})
+1 -1
View File
@@ -58,7 +58,7 @@ type WatchlistOptions struct {
// WatchlistView is a built Watchlist and this frame's events.
type WatchlistView struct {
Element *ui.Element
Element ui.Element
Changed, Submitted bool
Moved bool
Rows []int