From 68a53f60976cd4b4b17a6c2286cb95d83bbe7945 Mon Sep 17 00:00:00 2001 From: ZHU MINCHAO <48154958+MinchaoZhu@users.noreply.github.com> Date: Wed, 4 Nov 2026 10:01:41 +0800 Subject: [PATCH] ui: skin poinbase spot and perp pages with crosshair pickers and right-aligned tables --- src/Decentralized/TerminalWindow.cs | 79 ++++++++++++++++++----------- 1 file changed, 48 insertions(+), 31 deletions(-) diff --git a/src/Decentralized/TerminalWindow.cs b/src/Decentralized/TerminalWindow.cs index 061c0e7..2eeec6a 100644 --- a/src/Decentralized/TerminalWindow.cs +++ b/src/Decentralized/TerminalWindow.cs @@ -251,7 +251,7 @@ namespace Decentralized { var p = pool; b.AddLabel(L.T("trade.pair", p.Base, p.Quote, L.T("venue." + p.Venue)) + " " + L.T("trade.price", Fmt.Num(p.Price))); - b.BeginRow(); + b.BeginRow(4f); b.AddButton(L.T("trade.buy"), () => Trade(p, buy: true)); b.AddButton(L.T("trade.sell"), () => Trade(p, buy: false)); b.End(); @@ -259,19 +259,25 @@ namespace Decentralized } private static readonly float[] HoldW = { 80f, 100f, 110f, 110f }; + private static readonly TextAnchor[] HoldAligns = { TextAnchor.MiddleLeft, TextAnchor.MiddleRight, TextAnchor.MiddleRight, TextAnchor.MiddleRight }; private static void Holdings(CustomUIBuilder b, Wallet w, Func show) { - TableHeader(b, HoldW, L.T("col.asset"), L.T("col.amount"), L.T("col.value"), L.T("col.avg")); + TableHeader(b, HoldW.Sum() + 8f, HoldW, HoldAligns, L.T("col.asset"), L.T("col.amount"), L.T("col.value"), L.T("col.avg")); bool any = false; foreach (var kv in w.Balances.OrderBy(k => k.Key).Where(k => show(k.Key))) { any = true; - TableRow(b, HoldW, kv.Key, Fmt.Num(kv.Value), Fmt.Credits(kv.Value * Session.Price(kv.Key)), AvgText(w, kv.Key)); + string asset = kv.Key; + bool selected = (_page == Page.Poinbase && asset == _poinAsset); + TableRow(b, HoldW.Sum() + 8f, HoldW, HoldAligns, + new[] { kv.Key, Fmt.Num(kv.Value), Fmt.Credits(kv.Value * Session.Price(kv.Key)), AvgText(w, kv.Key) }, + selected, + () => { if (_page == Page.Poinbase) { _poinAsset = asset; Open(); } }); } - if (!any) b.AddLabel(L.T(w == Session.Data.Poinbase ? "poin.empty" : "wallet.empty")); + if (!any) b.AddLabel(L.T(w == Session.Data.Poinbase ? "poin.empty" : "wallet.empty"), "hold_empty_dim"); foreach (var kv in w.Frozen.OrderBy(k => k.Key).Where(k => show(k.Key))) - b.AddLabel(L.T("wallet.frozen", kv.Key, Fmt.Num(kv.Value), w.FrozenUntil.TryGetValue(kv.Key, out var u) ? u : 0)); + b.AddLabel(L.T("wallet.frozen", kv.Key, Fmt.Num(kv.Value), w.FrozenUntil.TryGetValue(kv.Key, out var u) ? u : 0), "hold_frozen_note"); } /// Poinbase:合规交易所。左边账户持仓,右边交易;信用点的存取在银行。 @@ -282,25 +288,25 @@ namespace Decentralized { var d = Session.Data; var w = d.Poinbase; - b.BeginRow(); + b.BeginRow(4f); foreach (PoinTab t in Enum.GetValues(typeof(PoinTab))) { var tab = t; string name = L.T("poin.sub." + t.ToString().ToLowerInvariant()); - b.AddButton(tab == _poinTab ? $"[{name}]" : name, () => { _poinTab = tab; _message = null; Open(); }); + PledgerSkin.Pick(b, name, tab == _poinTab, () => { _poinTab = tab; _message = null; Open(); }, _picked); } b.End(); if (_poinTab == PoinTab.Perp) { PerpPage(b); return; } Columns(b, l => { - CandleChart.AddPicker(l, "poin", Assets.PSC, Open); + CandleChart.AddPicker(l, "poin", Assets.PSC, Open, _picked); l.AddSeparator(); l.AddHeader(L.T("poin.account.h")); Holdings(l, w, a => true); - if (w.Taint > 0.5) l.AddLabel(L.T("wallet.taint", Fmt.Credits(w.Taint))); + if (w.Taint > 0.5) l.AddLabel(L.T("wallet.taint", Fmt.Credits(w.Taint)), "taint_note"); l.AddSeparator(); l.BeginRow(8f, TextAnchor.MiddleLeft); - l.AddLabel(L.T("poin.deposit", d.Address("poinbase"))); + l.AddLabel(L.T("poin.deposit", d.Address("poinbase")), "deposit_dim"); l.AddButton(L.T("hw.copy"), () => { Clipboard.Copy(d.Address("poinbase")); _message = L.T("hw.copied.addr", d.Address("poinbase")); Open(); }); l.End(); }, r => @@ -309,11 +315,11 @@ namespace Decentralized AmountRow(r); TradeRows(r, venue); r.AddSeparator(); - r.BeginRow(); + r.BeginRow(4f); foreach (var a in new[] { Assets.CRC, Assets.PSC }) { var asset = a; - r.AddButton(asset == _poinAsset ? $"[{asset}]" : asset, () => { _poinAsset = asset; Open(); }); + PledgerSkin.Pick(r, asset, asset == _poinAsset, () => { _poinAsset = asset; Open(); }, _picked); } r.End(); r.AddButton(L.T("poin.out", WalletWindow.NameOf(d.Hot)), PoinbaseOut); @@ -326,6 +332,7 @@ namespace Decentralized private static bool _perpLong = true; private static int _perpLev = 3; private static readonly float[] PerpW = { 118f, 60f, 104f, 60f, 58f }; + private static readonly TextAnchor[] PerpAligns = { TextAnchor.MiddleLeft, TextAnchor.MiddleRight, TextAnchor.MiddleRight, TextAnchor.MiddleRight, TextAnchor.MiddleRight }; private static double PerpSpot(string p) => p == "SPARK" ? Session.Data.Stable.Spark : Session.Data.Market.Price; @@ -341,46 +348,56 @@ namespace Decentralized { l.AddHeader(L.T("perp.h")); l.AddLabel(L.T("perp.acct", Fmt.Num(acct.Avail(Assets.CRC)))); - if (bk.Debt > 0) l.AddLabel(L.T("perp.debt", Fmt.Credits(bk.Debt), Math.Max(0, Tuning.PerpDebtDays - (day - bk.DebtSince)))); - if (bk.Banned(day)) l.AddLabel(L.T("perp.banned", bk.BannedUntil - day + 1)); + if (bk.Debt > 0) l.AddLabel(L.T("perp.debt", Fmt.Credits(bk.Debt), Math.Max(0, Tuning.PerpDebtDays - (day - bk.DebtSince))), "perp_debt_note"); + if (bk.Banned(day)) l.AddLabel(L.T("perp.banned", bk.BannedUntil - day + 1), "perp_banned_note"); l.AddSeparator(); - if (bk.Positions.Count == 0) l.AddLabel(L.T("perp.none")); + if (bk.Positions.Count == 0) l.AddLabel(L.T("perp.none"), "perp_none_dim"); else { - TableHeader(l, PerpW, L.T("perp.col.pos"), L.T("perp.col.notional"), L.T("perp.col.price"), L.T("perp.col.pnl"), L.T("perp.col.liq")); + TableHeader(l, PerpW.Sum() + 8f, PerpW, PerpAligns, L.T("perp.col.pos"), L.T("perp.col.notional"), L.T("perp.col.price"), L.T("perp.col.pnl"), L.T("perp.col.liq")); foreach (var p in bk.Positions) { - double spot = PerpSpot(p.Product); - TableRow(l, PerpW, p.Product + " " + L.T(p.Long ? "perp.long" : "perp.short") + " " + Math.Round(p.Leverage, 1) + "×", Fmt.Credits(p.Notional(spot)), - Fmt.Num(p.Entry) + " → " + Fmt.Num(spot), Fmt.Credits(p.Pnl(spot)), Fmt.Num(p.LiqPrice)); + var pos = p; + double spot = PerpSpot(pos.Product); + bool selPos = pos.Product == _perpProduct; + TableRow(l, PerpW.Sum() + 8f, PerpW, PerpAligns, + new[] { + pos.Product + " " + L.T(pos.Long ? "perp.long" : "perp.short") + " " + Math.Round(pos.Leverage, 1) + "×", + Fmt.Credits(pos.Notional(spot)), + Fmt.Num(pos.Entry) + " → " + Fmt.Num(spot), + Fmt.Credits(pos.Pnl(spot)), + Fmt.Num(pos.LiqPrice) + }, + selPos, + () => { _perpProduct = pos.Product; Open(); }); } } l.AddSeparator(); foreach (var p in PerpBook.Products) - l.AddLabel(L.T("perp.rate", p, Fmt.Num(PerpSpot(p)), (bk.Rate(p) * 100).ToString("+0.00;-0.00;0.00", CultureInfo.InvariantCulture))); - l.AddLabel(L.T("perp.fund", Fmt.Credits(bk.InsuranceFund))); + l.AddLabel(L.T("perp.rate", p, Fmt.Num(PerpSpot(p)), (bk.Rate(p) * 100).ToString("+0.00;-0.00;0.00", CultureInfo.InvariantCulture)), "perp_rate_dim"); + l.AddLabel(L.T("perp.fund", Fmt.Credits(bk.InsuranceFund)), "perp_fund_dim"); l.AddSeparator(); CandleChart.Add(l, "perp", _perpProduct); }, r => { r.AddHeader(L.T("perp.open")); - r.BeginRow(); + r.BeginRow(4f); foreach (var p in PerpBook.Products) { var product = p; - r.AddButton(product == _perpProduct ? $"[{product}]" : product, () => { _perpProduct = product; Open(); }); + PledgerSkin.Pick(r, product, product == _perpProduct, () => { _perpProduct = product; Open(); }, _picked); } r.End(); - r.BeginRow(); - r.AddButton(_perpLong ? "[" + L.T("perp.long") + "]" : L.T("perp.long"), () => { _perpLong = true; Open(); }); - r.AddButton(!_perpLong ? "[" + L.T("perp.short") + "]" : L.T("perp.short"), () => { _perpLong = false; Open(); }); + r.BeginRow(4f); + PledgerSkin.Pick(r, L.T("perp.long"), _perpLong, () => { _perpLong = true; Open(); }, _picked); + PledgerSkin.Pick(r, L.T("perp.short"), !_perpLong, () => { _perpLong = false; Open(); }, _picked); r.End(); - r.BeginRow(); + r.BeginRow(4f); foreach (int lev in new[] { 1, 2, 3, 5 }) { if (lev > PerpBook.MaxLeverage(_perpProduct)) continue; int lv = lev; - r.AddButton(lv == _perpLev ? $"[{lv}×]" : lv + "×", () => { _perpLev = lv; Open(); }); + PledgerSkin.Pick(r, lv + "×", lv == _perpLev, () => { _perpLev = lv; Open(); }, _picked); } r.End(); r.AddLabel(L.T("perp.margin")); @@ -389,10 +406,10 @@ namespace Decentralized { double notional = m * _perpLev, spot = PerpSpot(_perpProduct), exec = bk.Exec(spot, notional, _perpLong, day); var ghost = new PerpPosition { Long = _perpLong, Entry = exec, Size = notional / exec, Margin = m }; - r.AddLabel(L.T("perp.preview", Fmt.Credits(notional), Fmt.Num(exec))); - r.AddLabel(L.T("perp.preview2", Fmt.Num(notional * Tuning.PerpFee), Fmt.Num(ghost.LiqPrice))); + r.AddLabel(L.T("perp.preview", Fmt.Credits(notional), Fmt.Num(exec)), "perp_preview_dim"); + r.AddLabel(L.T("perp.preview2", Fmt.Num(notional * Tuning.PerpFee), Fmt.Num(ghost.LiqPrice)), "perp_preview_dim"); } - r.BeginRow(); + r.BeginRow(6f); r.AddButton(L.T("perp.do.open"), PerpOpen); r.AddButton(L.T("perp.do.add"), PerpAdd); r.AddButton(L.T("perp.do.close"), PerpClose);