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
@@ -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)])
|
||||
})
|
||||
|
||||
@@ -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")
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
})
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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")
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
})
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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")
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
@@ -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())
|
||||
|
||||
@@ -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")
|
||||
}
|
||||
|
||||
@@ -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
@@ -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,
|
||||
|
||||
@@ -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")
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
@@ -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")
|
||||
}
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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)
|
||||
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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")
|
||||
})
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in new issue
Block a user