Align shop sub-bar to span full action deck width
- Widen the tab sub-bar from a button-width pill to the full deck span (left/right edges of the main action bar), centering it on that span. - Pack tabs at the left edge and Clear/Confirm + NET readout at the right edge, compressing gaps rather than button widths when the deck is narrow. - Right-anchor the NET value (origin 1,0.5) and reserve its widest slot so growing totals never shift the label or overflow the bar. - Extend SurfaceScene.shopAnchor to expose left/right edges for the sub-bar layout. - Add sub-bar probe assertions in dev/shop-shot.mjs and unit tests in dev/shop-ui.test.mjs covering span, alignment, non-overlap, and NET math. - Add carol character image and lava-land video assets.
This commit is contained in:
parent
e9eda003b4
commit
071f3ac0e4
Binary file not shown.
|
After Width: | Height: | Size: 1.3 MiB |
Binary file not shown.
|
|
@ -364,6 +364,70 @@ const bounds = (o) => {
|
|||
};
|
||||
out.pass = out.pass && out.buyClip.ok;
|
||||
|
||||
// ---- SUB-BAR PROBE (full deck span + right-side actions/NET, no overlap) ----
|
||||
// The ask: the sub-bar's LEFT edge aligns with the main action bar's left
|
||||
// edge, its RIGHT edge with the action bar's right edge, and the credit
|
||||
// readout (NET) + Confirm/Clear sit on the RIGHT — without overlapping the
|
||||
// tabs (left) or each other. Add a few items to the purchasable section
|
||||
// (BUY rows) and verify the NET math updates while the layout holds.
|
||||
const ab = ss.actionBar;
|
||||
const deck = ab?.rect ? { x: ab.rect.x, y: ab.rect.y, w: ab.rect.w, h: ab.rect.h } : null;
|
||||
// A few items in the purchasable section (first three BUY rows).
|
||||
const buys = [];
|
||||
for (let i = 0; i < Math.min(3, bl.rows.length); i++) {
|
||||
const id = bl.rows[i].id;
|
||||
const qty = (i % 2) + 1; // 1, 2, 1
|
||||
bl.buyQty.set(id, qty);
|
||||
const price = round(bl.stock.find((s) => s.id === id)?.price ?? 0);
|
||||
buys.push({ id, qty, price });
|
||||
}
|
||||
sv._syncPreview();
|
||||
await sleep(80);
|
||||
const expectedNet = -buys.reduce((s, b) => s + b.qty * b.price, 0);
|
||||
const netText = sv._netValue.text ?? null;
|
||||
const netNum = parseInt(String(netText).replace(/[^\-0-9]/g, ''), 10);
|
||||
// World-space x ranges of the bar's items (bar local x + bar world x),
|
||||
// honouring each object's originX (the NET value is right-anchored, 1).
|
||||
const barCx = sv.subBar.x;
|
||||
const barHalf = sv.subBarW / 2;
|
||||
const barWx = (localX) => barCx + localX;
|
||||
const span = (el) => {
|
||||
const w = (el?.style?.width ?? el?.width) || 0;
|
||||
const ox = (typeof el?.originX === 'number') ? el.originX : 0.5;
|
||||
const cx = barWx(el.x);
|
||||
return [round(cx - w * ox), round(cx + w * (1 - ox))];
|
||||
};
|
||||
const tabTrade = span(sv._tabTrade);
|
||||
const tabBuy = span(sv._tabBuyback);
|
||||
const clearSp = span(sv._clearBtn);
|
||||
const confirmSp = span(sv._confirmBtn);
|
||||
const netLabelSp = span(sv._netLabel);
|
||||
const netValueSp = span(sv._netValue);
|
||||
const barLeft = round(barCx - barHalf);
|
||||
const barRight = round(barCx + barHalf);
|
||||
const subBarOk = !!deck
|
||||
&& Math.abs((deck.x + deck.w / 2) - barCx) < 1.5 // centred on the deck span
|
||||
&& Math.abs(deck.w - sv.subBarW) < 1.5 // full deck width
|
||||
&& tabTrade[0] >= barLeft + 8 // tabs inside the bar
|
||||
&& tabBuy[1] < clearSp[0] // tabs │ Clear (no overlap)
|
||||
&& clearSp[1] < confirmSp[0] // Clear │ Confirm
|
||||
&& confirmSp[1] < netLabelSp[0] // Confirm │ NET label
|
||||
&& netLabelSp[1] < netValueSp[0] // NET label │ live value
|
||||
&& netLabelSp[1] <= barWx(sv._netValue.x) - (sv._netValueW || 0) + 1 // …and of the WIDEST value (reserved slot)
|
||||
&& netValueSp[1] <= barRight - 8 // value inside the bar's end
|
||||
&& netText !== null && netNum === expectedNet // the math is right
|
||||
&& (sv._clearBtn.alpha ?? 0) > 0.9 && (sv._confirmBtn.alpha ?? 0) > 0.9; // revealed
|
||||
out.subBar = {
|
||||
deck,
|
||||
bar: { cx: round(barCx), left: barLeft, right: barRight, w: round(sv.subBarW), h: sv.subBarH },
|
||||
buys,
|
||||
net: { text: netText, expected: expectedNet, valueColor: sv._netValue?.style?.color ?? sv._netValue?._style?.color ?? null },
|
||||
spans: { tabTrade, tabBuyback: tabBuy, clear: clearSp, confirm: confirmSp, netLabel: netLabelSp, netValue: netValueSp },
|
||||
alphas: { clear: sv._clearBtn.alpha, confirm: sv._confirmBtn.alpha, disabled: sv._confirmBtn.disabled },
|
||||
ok: subBarOk,
|
||||
};
|
||||
out.pass = out.pass && out.subBar.ok;
|
||||
|
||||
out.screen = { w: ss.scale.width, h: ss.scale.height };
|
||||
out.topY = sv.topY ?? null;
|
||||
out.hudName = (() => { const t = ss.hudName; if (t?.getTopLeft) { const v = t.getTopLeft(undefined, true); return { x: round(v.x), y: round(v.y) }; } return null; })();
|
||||
|
|
|
|||
|
|
@ -215,6 +215,30 @@ console.log('== CONSTRUCT — entry state shows only the tabs; Buy/Sell reveals
|
|||
assert.equal(view._confirmBtn.alpha, 0, 'entry state: Confirm is hidden (no selection yet)');
|
||||
assert.equal(view._confirmBtn.disabled, true, 'entry state: Confirm is grayed');
|
||||
|
||||
// The bar spans the FULL DECK — the anchor's left/right edges (the main
|
||||
// action bar's own span): tabs packed at the LEFT edge, the trade actions
|
||||
// + NET readout packed at the RIGHT edge, the value right-aligned at the
|
||||
// bar's end (a growing number never shifts the label or the buttons).
|
||||
const pad = config.get('save.subBar.padding', 16);
|
||||
assert.ok(view.subBarW > 0 && view.anchor.left < view.anchor.right, 'anchor carries the deck span (left < right)');
|
||||
assert.equal(view.subBarW, view.anchor.right - view.anchor.left, 'sub-bar spans the full deck width (left edge → right edge)');
|
||||
assert.equal(view.subBar.x, (view.anchor.left + view.anchor.right) / 2, 'sub-bar is centred on the deck span');
|
||||
const half = view.subBarW / 2;
|
||||
const tw = view._tabTrade.style.width;
|
||||
assert.ok(Math.abs(view._tabTrade.x - (-half + pad + tw / 2)) < 1, `tabs pack at the bar's LEFT edge (tabTrade.x=${view._tabTrade.x}, want ${(-half + pad + tw / 2).toFixed(1)})`);
|
||||
// The NET value is RIGHT-ANCHORED (origin 1, 0.5) at the bar's end: its
|
||||
// anchor sits at barRight − pad and its width extends leftward, so a
|
||||
// longer total never overflows the bar. The label stays clear of even the
|
||||
// WIDEST value (the reserved slot, _netValueW), so nothing overlaps.
|
||||
assert.ok(Math.abs(view._netValue.x - (half - pad)) < 1, `NET value right-anchored at the bar's RIGHT edge (netValue.x=${view._netValue.x}, want ${(half - pad).toFixed(1)})`);
|
||||
assert.ok(view._netLabel.x + view._netLabel.width / 2 < view._netValue.x - view._netValueW + 0.001, 'NET label sits clear of the widest value (reserved slot)');
|
||||
// No overlap, left to right: tabs │ Clear │ Confirm │ NET label.
|
||||
const rightEdge = (el) => el.x + (el.style?.width ?? el.width) / 2;
|
||||
const leftEdge = (el) => el.x - (el.style?.width ?? el.width) / 2;
|
||||
assert.ok(rightEdge(view._tabBuyback) < leftEdge(view._clearBtn), 'BUYBACK tab does not overlap Clear');
|
||||
assert.ok(rightEdge(view._clearBtn) < leftEdge(view._confirmBtn), 'Clear does not overlap Confirm');
|
||||
assert.ok(rightEdge(view._confirmBtn) < leftEdge(view._netLabel), 'Confirm does not overlap the NET label');
|
||||
|
||||
// Reveal the trade panel (what clicking "Buy/Sell" does) — both grids appear.
|
||||
view.setTab('trade');
|
||||
assert.equal(view.tab, 'trade', 'Buy/Sell reveals the trade tab');
|
||||
|
|
|
|||
|
|
@ -403,11 +403,17 @@ export class SurfaceScene extends Phaser.Scene {
|
|||
* if the deck is missing (dev).
|
||||
*/
|
||||
shopAnchor() {
|
||||
const slot = this.actionBar?.slots?.find((s) => s.id === 'shop');
|
||||
const bar = this.actionBar;
|
||||
const slot = bar?.slots?.find((s) => s.id === 'shop');
|
||||
// The bar spans the FULL DECK — left edge on the deck's left, right
|
||||
// edge on its right (the main action bar's own rect), resting on its
|
||||
// top rail. Falls back to a bottom-center span if the deck is missing.
|
||||
const left = bar?.rect ? bar.rect.x : this.scale.width * 0.06;
|
||||
const right = bar?.rect ? bar.rect.x + bar.rect.w : this.scale.width * 0.94;
|
||||
if (slot) {
|
||||
return { x: slot.slot.x, y: this.actionBar.rect.y, w: this.actionBar.style.bw };
|
||||
return { x: slot.slot.x, y: bar.rect.y, w: bar.style.bw, left, right };
|
||||
}
|
||||
return { x: this.scale.width / 2, y: this.scale.height - 58, w: 120 };
|
||||
return { x: this.scale.width / 2, y: this.scale.height - 58, w: 120, left, right };
|
||||
}
|
||||
|
||||
destroyShopView() {
|
||||
|
|
|
|||
|
|
@ -11,7 +11,8 @@
|
|||
* │ └────────────────────────┘ │
|
||||
* └────────────────────────────────────────────────────────────────┘
|
||||
* ┌────────────────────────────────────────────────────────────────┐
|
||||
* │ BUY/SELL BUYBACK │ NET +420 cr ← tab sub-bar │
|
||||
* │ BUY/SELL BUYBACK ···console··· CLEAR CONFIRM │ NET +420 cr │
|
||||
* │ ← spans the FULL command deck (left + right edges) → │
|
||||
* └────────────────────────────────────────────────────────────────┘
|
||||
*
|
||||
* The SELL side (left) is a TradeGrid (js/ui/shop/TradeGrid.js): the
|
||||
|
|
@ -21,11 +22,13 @@
|
|||
* stock as WIDE ROWS — icon + name + one-line description (the good's
|
||||
* flavor) + price + a −/qty/+ stepper — SCROLLABLE (drag / wheel) so a
|
||||
* full 8–16 stock always fits. The live NET preview (green ≥ 0, red < 0)
|
||||
* sits on the TAB SUB-BAR, right of the Buy/Sell + Buyback buttons past a
|
||||
* hairline divider (tabs AND the pending trade's credit math on one
|
||||
* console); Confirm/Clear stay in the center. Confirm is disabled while
|
||||
* the trade is invalid (can't go negative, hold would overflow). SELL→BUY
|
||||
* happens atomically (sell to free room for a buy).
|
||||
* sits on the TAB SUB-BAR at its RIGHT edge: the tabs pack at the bar's
|
||||
* LEFT, the trade actions (Clear + Confirm) and the credit readout pack
|
||||
* at the RIGHT — the value right-aligned at the bar's end, so a growing
|
||||
* number never shifts the label, the buttons, or the tabs (no overlap).
|
||||
* Confirm is disabled while the trade is invalid (can't go negative, hold
|
||||
* would overflow). SELL→BUY happens atomically (sell to free room for a
|
||||
* buy).
|
||||
*
|
||||
* BUYBACK (session-local only) — the second tab. After the player SELLs
|
||||
* something, the goods they sold are listed here and can be bought BACK —
|
||||
|
|
@ -59,10 +62,12 @@ export class ShopView extends Phaser.GameObjects.Container {
|
|||
* gameScene — to refresh the credits/minerals HUD after a trade.
|
||||
* onLeave — called by close() so the SurfaceScene can restore the
|
||||
* surface loop (the deck's "Leave Shop" does this too).
|
||||
* anchor — { x, y, w } the sub-bar's anchor: the deck's "Shop"
|
||||
* button center x, the deck's top-rail y (the bar's bottom
|
||||
* edge rests on it), and the button width (right-aligns the
|
||||
* bar) — the MenuSubBar recipe (see SurfaceScene.shopAnchor).
|
||||
* anchor — { x, y, w, left, right } the sub-bar's anchor: the
|
||||
* deck's "Shop" button (x, w — legacy), the deck's
|
||||
* top-rail y (the bar's bottom edge rests on it), and
|
||||
* left/right — the deck's left/right edges. The bar
|
||||
* spans the FULL deck — the same span as the main
|
||||
* action bar (see SurfaceScene.shopAnchor).
|
||||
*/
|
||||
constructor(scene, o) {
|
||||
super(scene, 0, 0);
|
||||
|
|
@ -73,9 +78,9 @@ export class ShopView extends Phaser.GameObjects.Container {
|
|||
this.systemId = o.systemId;
|
||||
this.gameScene = o.gameScene;
|
||||
this.onLeave = o.onLeave;
|
||||
// The tab sub-bar anchor (the deck's Shop button). Falls back to a
|
||||
// The tab sub-bar anchor (the deck's span). Falls back to a
|
||||
// bottom-center bar if the scene doesn't provide one (headless/dev).
|
||||
this.anchor = o.anchor ?? { x: this._W() / 2, y: this._H() - 58, w: 120 };
|
||||
this.anchor = o.anchor ?? { x: this._W() / 2, y: this._H() - 58, w: 120, left: 20, right: this._W() - 20 };
|
||||
// The trade panel's TOP edge — just under the surface HUD's planet text
|
||||
// (the scene computes it from its own hudName/hudLine). Falls back to a
|
||||
// small top margin if the scene doesn't provide one (headless/dev).
|
||||
|
|
@ -144,10 +149,11 @@ export class ShopView extends Phaser.GameObjects.Container {
|
|||
//
|
||||
// EXTENDED (data/shopui.json → preview + buttons): the trade ACTIONS
|
||||
// (Clear + Confirm) and the live NET credit readout ride on the same
|
||||
// console — [tabs] │ [clear][confirm] │ [NET value]. Tabs AND the trade
|
||||
// math AND its actions on ONE bar: one glance sees state, the pending
|
||||
// credit delta, and the button that executes it (entry state included,
|
||||
// where the actions hide and the readout dims to "0 cr").
|
||||
// console, packed at the RIGHT edge —
|
||||
// [tabs ···console··· actions] │ [NET value]
|
||||
// while the tabs pack at the LEFT edge. One glance sees state, the
|
||||
// pending credit delta, and the button that executes it (entry state
|
||||
// included, where the actions hide and the readout dims to "0 cr").
|
||||
const a = this.anchor;
|
||||
const H = config.get('save.subBar.height', 58);
|
||||
const pad = config.get('save.subBar.padding', 16);
|
||||
|
|
@ -199,40 +205,76 @@ export class ShopView extends Phaser.GameObjects.Container {
|
|||
|
||||
// The NET readout — created BEFORE the panel body so its REAL width feeds
|
||||
// the bar width (a headless fake without text metrics falls back to an
|
||||
// estimate). The value is RIGHT-aligned at the bar's end, so longer
|
||||
// numbers ("+1,240 cr") never need the bar to grow.
|
||||
// estimate). The value is RIGHT-ANCHORED (origin 1, 0.5) at the bar's
|
||||
// end, so a longer number ("−9,999 cr") extends LEFT and never spills
|
||||
// past the bar or shifts the label. The label sits clear of the WIDEST
|
||||
// plausible value (reserved below) so nothing overlaps at any total.
|
||||
const labelStr = String(pv.label ?? 'NET');
|
||||
const valueFont = Math.max(8, Number(pv.valueFontSize) || 22);
|
||||
// Reserve the value slot with the widest plausible reading so the label
|
||||
// never overlaps a long total (the value itself only grows leftward).
|
||||
const reserveStr = String(pv.reserve ?? '\u221299,999 cr');
|
||||
this._netLabel = this._makeText(0, -H / 2, labelStr, {
|
||||
fontSize: `${Math.max(8, Number(pv.labelFontSize) || 12)}px`,
|
||||
color: toCss(pv.labelColor ?? c.dim ?? '#7d92c4'),
|
||||
align: 'left',
|
||||
});
|
||||
this._netValue = this._makeText(0, -H / 2, '0 cr', {
|
||||
this._netValue = this._makeText(0, -H / 2, reserveStr, {
|
||||
fontSize: `${valueFont}px`,
|
||||
color: toCss(pv.zeroColor ?? pv.labelColor ?? '#7d92c4'),
|
||||
align: 'left',
|
||||
});
|
||||
const labelW = (Number(this._netLabel?.width) > 0 ? this._netLabel.width : labelStr.length * 8) || 30;
|
||||
const valueW = (Number(this._netValue?.width) > 0 ? this._netValue.width : 4 * valueFont * 0.6) || 40;
|
||||
const valueW = (Number(this._netValue?.width) > 0 ? this._netValue.width : reserveStr.length * valueFont * 0.55) || 90;
|
||||
this._netValueW = valueW; // reserved (widest) width — the label keeps clear of it
|
||||
// Reset to the live (zero) reading and anchor the RIGHT edge so the
|
||||
// value stays pinned to the bar's end as its length changes.
|
||||
this._netValue.setText?.('0 cr');
|
||||
this._netValue.setOrigin?.(1, 0.5);
|
||||
const divW = Math.max(1, Number(pv.divider?.width) || 2);
|
||||
const divH = Math.max(10, Number(pv.divider?.height) || Math.round(H * 0.45));
|
||||
|
||||
// Row: [ BUY/SELL ][ BUYBACK ] │ [ CLEAR ][ CONFIRM ] │ NET value
|
||||
const W = Math.min(
|
||||
this._W() - 24,
|
||||
pad * 2 + tabsW + gap + divW + gap + actionsW + gap + divW + gap + labelW + gap + valueW,
|
||||
);
|
||||
|
||||
// Position: bottom edge on the deck's top rail (a.y), right edge on the
|
||||
// Shop button's right edge — the MenuSubBar recipe. The sub-bar is a
|
||||
// CHILD of the ShopView (which stays at the screen origin, 0,0) at
|
||||
// (cx, a.y) — so the trade panel (a SIBLING at 0,0) still renders in
|
||||
// screen space, not offset down to the deck.
|
||||
// Row: [ BUY/SELL ][ BUYBACK ] ···console··· [ CLEAR ][ CONFIRM ] │ NET value
|
||||
// The bar spans the FULL DECK (anchor.left/right — the main action
|
||||
// bar's left and right edges): tabs packed at the LEFT edge, the trade
|
||||
// actions + credit readout packed at the RIGHT edge, the value
|
||||
// right-aligned at the bar's end (a growing number never shifts the
|
||||
// label, the buttons, or the tabs — nothing overlaps on either end).
|
||||
const sceneW = this._W();
|
||||
let cx = a.x + a.w / 2 - W / 2;
|
||||
cx = Phaser.Math.Clamp(cx, W / 2 + 12, sceneW - W / 2 - 12);
|
||||
const fixedW = pad * 2 + tabsW + divW * 2 + actionsW + labelW + valueW;
|
||||
const span = (Number.isFinite(a.left) && Number.isFinite(a.right) && a.right - a.left > fixedW + 28)
|
||||
? { left: a.left, right: a.right }
|
||||
: null;
|
||||
// Seven gap slots: tab1·tab2, tab2·div1, div1·clear, clear·confirm,
|
||||
// confirm·div2, div2·label, label·value. If the content can't fit the
|
||||
// span (a narrow deck), the gaps compress — never the button widths.
|
||||
let W, g;
|
||||
if (span) {
|
||||
W = span.right - span.left;
|
||||
g = Math.min(gap, Math.max(4, (W - fixedW) / 7));
|
||||
} else {
|
||||
g = gap;
|
||||
W = Math.min(sceneW - 24, fixedW + 7 * g);
|
||||
}
|
||||
const cx = span ? (span.left + span.right) / 2 : sceneW / 2;
|
||||
|
||||
// All the x's, bar-local (the bar's origin is its BOTTOM CENTER, a.y):
|
||||
// the right group packs inward from the bar's right edge; the tabs
|
||||
// pack outward from the left; the dividers sit between the groups.
|
||||
const lEdge = -W / 2 + pad;
|
||||
const rEdge = W / 2 - pad;
|
||||
const tX = [lEdge + widths[0] / 2, lEdge + widths[0] + g + widths[1] / 2];
|
||||
const vX = rEdge; // NET value — right edge anchored at the bar's end (origin 1,0.5)
|
||||
const lCx = rEdge - valueW - g - labelW / 2; // NET label, clear of the widest value
|
||||
const d2X = lCx - labelW / 2 - g - divW / 2; // divider: actions │ NET
|
||||
const cCx = d2X - divW / 2 - g - confirmW / 2; // Confirm
|
||||
const qCx = cCx - confirmW / 2 - g - clearW / 2; // Clear
|
||||
const d1X = qCx - clearW / 2 - g - divW / 2; // divider: tabs │ actions
|
||||
|
||||
// Position: bottom edge on the deck's top rail (a.y), centred on the
|
||||
// deck's span. The sub-bar is a CHILD of the ShopView (which stays at
|
||||
// the screen origin, 0,0) at (cx, a.y) — so the trade panel (a SIBLING
|
||||
// at 0,0) still renders in screen space, not offset down to the deck.
|
||||
const bar = new Phaser.GameObjects.Container(this.scene, cx, a.y);
|
||||
bar.setScrollFactor?.(0);
|
||||
this.subBar = bar;
|
||||
|
|
@ -243,38 +285,28 @@ export class ShopView extends Phaser.GameObjects.Container {
|
|||
// The body FIRST (v4 paints children in list order — the opaque panel
|
||||
// fill must come before the buttons or it would cover them). The
|
||||
// hairline dividers are part of the body, so they paint under both.
|
||||
const divX1 = -W / 2 + pad + tabsW + gap + divW / 2;
|
||||
const divX2 = divX1 + divW / 2 + gap + actionsW + gap + divW / 2;
|
||||
const bodyG = this.scene.add?.graphics?.();
|
||||
if (bodyG) {
|
||||
bodyG.setScrollFactor?.(0);
|
||||
bar.add?.(bodyG);
|
||||
this._drawSubBarBody(bodyG, W, H, c, [divX1, divX2], divH);
|
||||
this._drawSubBarBody(bodyG, W, H, c, [d1X, d2X], divH);
|
||||
}
|
||||
|
||||
// The buttons LAST (above the body), left-anchored in the row.
|
||||
let bx = -W / 2 + pad + widths[0] / 2;
|
||||
// The buttons LAST (above the body): tabs at the left edge, the trade
|
||||
// actions + NET readout at the right edge.
|
||||
items.forEach((it, i) => {
|
||||
const btn = btns[i];
|
||||
btn.setX?.(bx);
|
||||
btn.setX?.(tX[i]);
|
||||
bar.add?.(btn);
|
||||
if (it.id === 'trade') this._tabTrade = btn;
|
||||
else this._tabBuyback = btn;
|
||||
bx += widths[i] / 2 + gap + (i < items.length - 1 ? widths[i + 1] / 2 : 0);
|
||||
});
|
||||
|
||||
// The actions: Clear then Confirm, right after the first divider.
|
||||
const ax0 = divX1 + divW / 2 + gap;
|
||||
this._clearBtn.setX?.(ax0 + clearW / 2);
|
||||
this._confirmBtn.setX?.(ax0 + clearW + gap + confirmW / 2);
|
||||
this._clearBtn.setX?.(qCx);
|
||||
this._confirmBtn.setX?.(cCx);
|
||||
bar.add?.(this._clearBtn);
|
||||
bar.add?.(this._confirmBtn);
|
||||
|
||||
// The NET block: the label right after the second divider, the value
|
||||
// right-aligned to the bar's end (so a growing number never shifts the
|
||||
// label).
|
||||
this._netLabel.setPosition?.(divX2 + divW / 2 + gap + labelW / 2, -H / 2);
|
||||
this._netValue.setPosition?.(W / 2 - pad - valueW / 2, -H / 2);
|
||||
this._netLabel.setPosition?.(lCx, -H / 2);
|
||||
this._netValue.setPosition?.(vX, -H / 2);
|
||||
bar.add?.(this._netLabel);
|
||||
bar.add?.(this._netValue);
|
||||
|
||||
|
|
|
|||
Loading…
Reference in New Issue