ui: skin poinbase spot and perp pages with crosshair pickers and right-aligned tables

This commit is contained in:
ZHU MINCHAO committed 2026-11-04 10:01:41 +08:00
1 parent 698849c112
commit 68a53f6097
1 file changed
+48 -31
+48 -31
View File
@@ -251,7 +251,7 @@ namespace Decentralized
{ {
var p = pool; 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.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.buy"), () => Trade(p, buy: true));
b.AddButton(L.T("trade.sell"), () => Trade(p, buy: false)); b.AddButton(L.T("trade.sell"), () => Trade(p, buy: false));
b.End(); b.End();
@@ -259,19 +259,25 @@ namespace Decentralized
} }
private static readonly float[] HoldW = { 80f, 100f, 110f, 110f }; 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<string, bool> show) private static void Holdings(CustomUIBuilder b, Wallet w, Func<string, bool> 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; bool any = false;
foreach (var kv in w.Balances.OrderBy(k => k.Key).Where(k => show(k.Key))) foreach (var kv in w.Balances.OrderBy(k => k.Key).Where(k => show(k.Key)))
{ {
any = true; 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))) 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");
} }
/// <summary>Poinbase:合规交易所。左边账户持仓,右边交易;信用点的存取在银行。</summary> /// <summary>Poinbase:合规交易所。左边账户持仓,右边交易;信用点的存取在银行。</summary>
@@ -282,25 +288,25 @@ namespace Decentralized
{ {
var d = Session.Data; var d = Session.Data;
var w = d.Poinbase; var w = d.Poinbase;
b.BeginRow(); b.BeginRow(4f);
foreach (PoinTab t in Enum.GetValues(typeof(PoinTab))) foreach (PoinTab t in Enum.GetValues(typeof(PoinTab)))
{ {
var tab = t; var tab = t;
string name = L.T("poin.sub." + t.ToString().ToLowerInvariant()); 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(); b.End();
if (_poinTab == PoinTab.Perp) { PerpPage(b); return; } if (_poinTab == PoinTab.Perp) { PerpPage(b); return; }
Columns(b, l => Columns(b, l =>
{ {
CandleChart.AddPicker(l, "poin", Assets.PSC, Open); CandleChart.AddPicker(l, "poin", Assets.PSC, Open, _picked);
l.AddSeparator(); l.AddSeparator();
l.AddHeader(L.T("poin.account.h")); l.AddHeader(L.T("poin.account.h"));
Holdings(l, w, a => true); 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.AddSeparator();
l.BeginRow(8f, TextAnchor.MiddleLeft); 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.AddButton(L.T("hw.copy"), () => { Clipboard.Copy(d.Address("poinbase")); _message = L.T("hw.copied.addr", d.Address("poinbase")); Open(); });
l.End(); l.End();
}, r => }, r =>
@@ -309,11 +315,11 @@ namespace Decentralized
AmountRow(r); AmountRow(r);
TradeRows(r, venue); TradeRows(r, venue);
r.AddSeparator(); r.AddSeparator();
r.BeginRow(); r.BeginRow(4f);
foreach (var a in new[] { Assets.CRC, Assets.PSC }) foreach (var a in new[] { Assets.CRC, Assets.PSC })
{ {
var asset = a; 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.End();
r.AddButton(L.T("poin.out", WalletWindow.NameOf(d.Hot)), PoinbaseOut); r.AddButton(L.T("poin.out", WalletWindow.NameOf(d.Hot)), PoinbaseOut);
@@ -326,6 +332,7 @@ namespace Decentralized
private static bool _perpLong = true; private static bool _perpLong = true;
private static int _perpLev = 3; private static int _perpLev = 3;
private static readonly float[] PerpW = { 118f, 60f, 104f, 60f, 58f }; 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; 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.AddHeader(L.T("perp.h"));
l.AddLabel(L.T("perp.acct", Fmt.Num(acct.Avail(Assets.CRC)))); 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.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)); if (bk.Banned(day)) l.AddLabel(L.T("perp.banned", bk.BannedUntil - day + 1), "perp_banned_note");
l.AddSeparator(); 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 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) foreach (var p in bk.Positions)
{ {
double spot = PerpSpot(p.Product); var pos = p;
TableRow(l, PerpW, p.Product + " " + L.T(p.Long ? "perp.long" : "perp.short") + " " + Math.Round(p.Leverage, 1) + "×", Fmt.Credits(p.Notional(spot)), double spot = PerpSpot(pos.Product);
Fmt.Num(p.Entry) + " → " + Fmt.Num(spot), Fmt.Credits(p.Pnl(spot)), Fmt.Num(p.LiqPrice)); 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(); l.AddSeparator();
foreach (var p in PerpBook.Products) 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.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))); l.AddLabel(L.T("perp.fund", Fmt.Credits(bk.InsuranceFund)), "perp_fund_dim");
l.AddSeparator(); l.AddSeparator();
CandleChart.Add(l, "perp", _perpProduct); CandleChart.Add(l, "perp", _perpProduct);
}, r => }, r =>
{ {
r.AddHeader(L.T("perp.open")); r.AddHeader(L.T("perp.open"));
r.BeginRow(); r.BeginRow(4f);
foreach (var p in PerpBook.Products) foreach (var p in PerpBook.Products)
{ {
var product = p; 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.End();
r.BeginRow(); r.BeginRow(4f);
r.AddButton(_perpLong ? "[" + L.T("perp.long") + "]" : L.T("perp.long"), () => { _perpLong = true; Open(); }); PledgerSkin.Pick(r, L.T("perp.long"), _perpLong, () => { _perpLong = true; Open(); }, _picked);
r.AddButton(!_perpLong ? "[" + L.T("perp.short") + "]" : L.T("perp.short"), () => { _perpLong = false; Open(); }); PledgerSkin.Pick(r, L.T("perp.short"), !_perpLong, () => { _perpLong = false; Open(); }, _picked);
r.End(); r.End();
r.BeginRow(); r.BeginRow(4f);
foreach (int lev in new[] { 1, 2, 3, 5 }) foreach (int lev in new[] { 1, 2, 3, 5 })
{ {
if (lev > PerpBook.MaxLeverage(_perpProduct)) continue; if (lev > PerpBook.MaxLeverage(_perpProduct)) continue;
int lv = lev; 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.End();
r.AddLabel(L.T("perp.margin")); 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); 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 }; 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.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))); 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.open"), PerpOpen);
r.AddButton(L.T("perp.do.add"), PerpAdd); r.AddButton(L.T("perp.do.add"), PerpAdd);
r.AddButton(L.T("perp.do.close"), PerpClose); r.AddButton(L.T("perp.do.close"), PerpClose);