Widen the BUY list right up against the SELL grid; the row description drops to 11px and wraps to two lines without touching the price/stepper
The center column is gone: Confirm + Clear move onto the tab sub-bar ([tabs] | [Clear][Confirm] | NET value), serving both the trade and buyback tabs, so the two panels sit side by side with just a bit of padding between (grid.centerW 208 -> 16). The row description keeps the smaller font, word-wraps inside the text column (wrap width stops before the price), and the char-count budget now spans two lines so the headless path stays bounded; rowH grows 64 -> 72 to fit both lines. The buttons persist across tab switches (they were content before and died with it), Confirm stays grayed while the trade is invalid, and the stub gains setX/setY so the bar layout is testable.
This commit is contained in:
parent
3fdd825570
commit
cc4a15cafd
|
|
@ -1,13 +1,11 @@
|
|||
{
|
||||
"_comment": "SHOP UI — the buy/sell (and buyback) screen over the shop clip (data/shopui.json). LAYOUT: the SELL grid (the player's cargo bays) sits on the LEFT, the BUY list (the shop's stock, as a scrollable list of wide rows) on the RIGHT, Confirm/Clear sit in the center column, the live NET-credits readout rides on the TAB SUB-BAR (right of the Buy/Sell + Buyback buttons, past a hairline divider), and the tab sub-bar toggles Buy/Sell ↔ Buyback (Buyback is disabled until the player has SOLD something this session). The panels are semi-transparent so the shop clip shows through; items + text are full alpha. SELL CELLS show the good's ICON only — the name + price appear in a hover TOOLTIP (→ tooltip) instead of over the icon. BUY ROWS carry their name + description + price INLINE (no tooltip — the row is wide enough). GEOMETRY (slot/gap/grid/centerW) is primitives the code (js/ui/shop/ShopView.js) lays out against the live scene size — not pixel positions. The ECONOMY itself (prices, stock) is Economy.js + data/goods.json + data/economy.json; this file is only presentation.",
|
||||
"_comment": "SHOP UI — the buy/sell (and buyback) screen over the shop clip (data/shopui.json). LAYOUT: the SELL grid (the player's cargo bays) sits on the LEFT, the BUY list (the shop's stock, as a scrollable list of wide rows) RIGHT NEXT TO it with just a bit of padding between (grid.centerW) — the panel is pure selection. The trade ACTIONS (Confirm/Clear) + the live NET-credits readout ride on the TAB SUB-BAR (→ tabs/preview/buttons), and the tab sub-bar toggles Buy/Sell ↔ Buyback (Buyback is disabled until the player has SOLD something this session). The panels are semi-transparent so the shop clip shows through; items + text are full alpha. SELL CELLS show the good's ICON only — the name + price appear in a hover TOOLTIP (→ tooltip) instead of over the icon. BUY ROWS carry their name + description (wraps to two lines, never over the price/stepper) + price INLINE (no tooltip — the row is wide enough). GEOMETRY (slot/gap/grid/centerW) is primitives the code (js/ui/shop/ShopView.js) lays out against the live scene size — not pixel positions. The ECONOMY itself (prices, stock) is Economy.js + data/goods.json + data/economy.json; this file is only presentation.",
|
||||
"enabled": true,
|
||||
|
||||
"tabs": {
|
||||
"_comment": "The tab sub-bar (two MenuButtons, the deck's cyberpunk style — js/ui/MenuButton.js), EXTENDED at its right end by the live NET readout (→ preview) past a hairline divider. Buy/Sell opens the trade panel; Buyback opens the buyback panel (goods sold this session, session-local only). Size/palette come from data/save.json → subBar + colors (the Menu sub-bar recipe); this section just holds the labels + Buy/Sell accent. The sub-bar rests above the command deck, anchored to the deck's Shop button (SurfaceScene.shopAnchor).",
|
||||
"_comment": "The tab sub-bar (the deck's cyberpunk style — js/ui/MenuButton.js): [Buy/Sell][Buyback] │ [Clear][Confirm] │ NET <value> — the tabs, the trade ACTIONS (→ buttons), and the live NET readout (→ preview), past hairline dividers, on ONE console. Buy/Sell opens the trade panel; Buyback opens the buyback panel (goods sold this session, session-local only). Size/palette come from data/save.json → subBar + colors (the Menu sub-bar recipe); this section just holds the labels + Buy/Sell accent. The sub-bar rests above the command deck, anchored to the deck's Shop button (SurfaceScene.shopAnchor).",
|
||||
"buySell": "Buy/Sell",
|
||||
"buyback": "Buyback",
|
||||
"w": 184, "h": 46, "gap": 14,
|
||||
"y": 44,
|
||||
"activeAccent": "#00e5ff",
|
||||
"disabledAlpha": 0.34
|
||||
},
|
||||
|
|
@ -22,10 +20,10 @@
|
|||
},
|
||||
|
||||
"grid": {
|
||||
"_comment": "The SELL side's 4×4 bay grid (data/economy.json → cargo.maxSlots = 16 slots): the first `capacity` cells are live cargo bays (one good each), the rest are locked (the hold's upgrade room). `centerW` is the center column (Confirm/Clear) shared by the trade-panel layout. (The BUY side is a list now — → buyList.)",
|
||||
"_comment": "The SELL side's 4×4 bay grid (data/economy.json → cargo.maxSlots = 16 slots): the first `capacity` cells are live cargo bays (one good each), the rest are locked (the hold's upgrade room). `centerW` is the small PADDING gap between the SELL grid and the BUY list (the panels sit side by side; the trade actions live on the tab sub-bar — → buttons). (The BUY side is a list now — → buyList.)",
|
||||
"cols": 4, "rows": 4,
|
||||
"slot": 96, "gap": 10,
|
||||
"centerW": 208
|
||||
"centerW": 16
|
||||
},
|
||||
|
||||
"slot": {
|
||||
|
|
@ -42,15 +40,15 @@
|
|||
},
|
||||
|
||||
"buyList": {
|
||||
"_comment": "The BUY side (js/ui/shop/BuyList.js): the shop's stock as a SCROLLABLE LIST of WIDE ROWS — [icon] NAME / one-line description (the good's flavor, data/goods.json) … PRICE, − qty +. Sized to the SELL grid's height so the panels align top AND bottom; the list scrolls (drag on the body / mouse wheel) when the stock outgrows the viewport — rows outside it are hidden (row-based clipping; this Phaser build has no camera/mask clip). A row in the pending trade (qty > 0) gets the accent border + lit qty. `pad` insets rows from the panel edge; the title bar reuses panel.title.",
|
||||
"_comment": "The BUY side (js/ui/shop/BuyList.js): the shop's stock as a SCROLLABLE LIST of WIDE ROWS — [icon] NAME / description (the good's flavor, data/goods.json, wraps to up to desc.maxLines lines and NEVER over the price/stepper) … PRICE, − qty +. Sized to the SELL grid's height so the panels align top AND bottom; the list scrolls (drag on the body / mouse wheel) when the stock outgrows the viewport — rows outside it are hidden (row-based clipping; this Phaser build has no camera/mask clip). A row in the pending trade (qty > 0) gets the accent border + lit qty. `rowH` is tall enough for the two-line description. `pad` insets rows from the panel edge; the title bar reuses panel.title.",
|
||||
"w": 560,
|
||||
"pad": 14,
|
||||
"rowH": 64,
|
||||
"rowH": 72,
|
||||
"rowGap": 6,
|
||||
"icon": 44,
|
||||
"iconGap": 12,
|
||||
"name": { "fontSize": 14, "color": "#eaf6ff" },
|
||||
"desc": { "fontSize": 12, "color": "#7d92c4" },
|
||||
"desc": { "fontSize": 11, "color": "#7d92c4", "maxLines": 2 },
|
||||
"price": { "fontSize": 14, "color": "#ffc94d", "w": 64 },
|
||||
"rowBg": "#0e1728",
|
||||
"rowBgAlpha": 0.55,
|
||||
|
|
@ -94,19 +92,15 @@
|
|||
},
|
||||
|
||||
"buttons": {
|
||||
"_comment": "Confirm (execute the trade) + Clear (reset this side's selection) — they appear once the player starts checking/stepping items. `disabled` dims Confirm while the trade is invalid. `back` is a reserved return-to-menu label (the deck's Leave Shop is the real exit). Separate *Label (text) and *Accent (color) keys so they don't collide.",
|
||||
"w": 158, "h": 54,
|
||||
"_comment": "Confirm (execute the trade) + Clear (reset this side's selection) — on the TAB SUB-BAR (→ tabs), between the tabs and the NET readout: they appear once the player starts checking/stepping items, and serve BOTH the trade and the buyback tabs. Confirm is grayed (disabled) while the trade is invalid. Button size/palette come from data/save.json → subBar (the same MenuButtons as the tabs); this section just holds the labels + accents.",
|
||||
"confirmLabel": "Confirm",
|
||||
"clearLabel": "Clear",
|
||||
"backLabel": "‹ Menu",
|
||||
"accent": "#00e5ff",
|
||||
"confirmAccent": "#7ce8a4",
|
||||
"clearAccent": "#7d92c4",
|
||||
"disabled": "#7d92c4"
|
||||
"clearAccent": "#7d92c4"
|
||||
},
|
||||
|
||||
"animation": {
|
||||
"_comment": "Reveal: the SELL grid slides in from the left, the BUY list from the right, the center column fades up (data/shopui.json → animation). A short stagger keeps it feeling assembled rather than popped.",
|
||||
"_comment": "Reveal: the SELL grid slides in from the left, the BUY list from the right (data/shopui.json → animation). A short stagger keeps it feeling assembled rather than popped.",
|
||||
"slideMs": 340,
|
||||
"staggerMs": 26,
|
||||
"ease": "Cubic.Out"
|
||||
|
|
|
|||
|
|
@ -21,6 +21,8 @@ class GameObject {
|
|||
setTint(t) { this.tint = t; return this; }
|
||||
setBlendMode() { return this; }
|
||||
setPosition(x, y) { this.x = x; this.y = y; return this; }
|
||||
setX(x) { this.x = x; return this; }
|
||||
setY(y) { this.y = y; return this; }
|
||||
setStrokeStyle() { return this; }
|
||||
setSize(w, h) { this.width = w; this.height = h; return this; }
|
||||
set() { return this; }
|
||||
|
|
|
|||
|
|
@ -83,6 +83,7 @@ function stubObject(extra = {}) {
|
|||
setColor(c) { this.color = c; return this; },
|
||||
setFontSize() { return this; },
|
||||
setWordWrap() { return this; },
|
||||
setWordWrapWidth(w) { this._wrapW = w; return this; },
|
||||
setLineSpacing() { return this; },
|
||||
setPadding() { return this; },
|
||||
setScrollFactor() { return this; },
|
||||
|
|
@ -198,6 +199,19 @@ console.log('== CONSTRUCT — entry state shows only the tabs; Buy/Sell reveals
|
|||
assert.ok(view.y === 0, 'ShopView root stays at the screen origin (0,0) so the trade panel renders in screen space');
|
||||
assert.ok(view.subBarW > 0 && view.subBarH > 0, 'sub-bar has a measured width + height');
|
||||
|
||||
// The trade ACTIONS live on the sub-bar too — [tabs] │ [Clear][Confirm] │
|
||||
// [NET value] — created up front (persistent across tabs), hidden until a
|
||||
// selection exists (_syncPreview reveals them), Confirm grayed while the
|
||||
// trade is invalid.
|
||||
assert.ok(view._clearBtn, 'Clear lives on the sub-bar (persistent, not in the panel)');
|
||||
assert.ok(view._confirmBtn, 'Confirm lives on the sub-bar (persistent, not in the panel)');
|
||||
assert.ok(view._clearBtn.x < view._confirmBtn.x, 'Clear sits left of Confirm on the bar');
|
||||
assert.ok(view._tabBuyback.x < view._clearBtn.x, '… after the tabs');
|
||||
assert.ok(view._confirmBtn.x < view._netLabel.x, '… and the NET readout rides to their right');
|
||||
assert.equal(view._clearBtn.alpha, 0, 'entry state: Clear is hidden (no selection yet)');
|
||||
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');
|
||||
|
||||
// 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');
|
||||
|
|
@ -259,6 +273,12 @@ console.log('\n== INTERACTION — icons bound per good + taps carry a hitAreaCal
|
|||
assert.ok(view.buyList.x > view.sellGrid.x, `buy panel is to the right of the sell panel (sell ${view.sellGrid.x} < buy ${view.buyList.x})`);
|
||||
assert.notEqual(view.sellGrid.x, view.buyList.x, 'the panels do not overlap at the origin');
|
||||
assert.ok(view.buyList.width > view.sellGrid.width, 'the BUY list is the wide panel (rows, not 4×4 cells)');
|
||||
|
||||
// The two panels sit NEXT TO each other with just a bit of padding between
|
||||
// (grid.centerW — the actions moved onto the sub-bar, so there's no center
|
||||
// column anymore).
|
||||
const gapPx = view.buyList.x - (view.sellGrid.x + view.sellGrid.width);
|
||||
assert.ok(Math.abs(gapPx - 16) < 1, `BUY is right next to SELL, small padding (got gap ${gapPx.toFixed(1)} px, want ~16)`);
|
||||
}
|
||||
console.log('INTERACTION: OK');
|
||||
|
||||
|
|
@ -309,9 +329,24 @@ console.log('\n== HOVER TOOLTIP — name + price on hover, NOT over the icon =='
|
|||
assert.equal(row0.objs.name.text, nameB, 'row carries the good name inline');
|
||||
assert.equal(row0.objs.price.text, `${Math.round(buyB)} cr`, 'row carries the buy price inline');
|
||||
const flavor0 = goods.find((g) => g.id === buyId)?.flavor ?? '';
|
||||
assert.ok(row0.objs.desc.text.length >= 3, 'row carries the one-line description');
|
||||
assert.ok(row0.objs.desc.text.length >= 3, 'row carries the description');
|
||||
assert.ok(row0.objs.desc.text.startsWith(flavor0.slice(0, 6)), 'description is the good flavor (truncated to fit)');
|
||||
|
||||
// DESCRIPTION WRAP: smaller font (11px), wraps to up to 2 lines, and the
|
||||
// wrap width stops BEFORE the price/stepper so a wrapped line can never
|
||||
// overlap them (the char-count budget is the headless fail-safe).
|
||||
assert.equal(row0.objs.desc._style?.fontSize, '11px', 'desc uses the smaller 11px font');
|
||||
assert.ok(row0.objs.desc._wrapW > 0, 'desc has a word-wrap width set (real Phaser wraps inside the text column)');
|
||||
const descMaxW = view.buyList._descMaxW();
|
||||
assert.ok(descMaxW > 0, 'the text column has a positive width');
|
||||
assert.ok(row0.objs.desc._wrapW <= descMaxW + 0.001, `… and the wrap width fits inside it (wrap=${row0.objs.desc._wrapW}, col=${descMaxW.toFixed(1)})`);
|
||||
const budget = Math.max(4, Math.floor(descMaxW / (11 * 0.55))) * 2 + 1; // 2 lines + ellipsis room
|
||||
assert.ok(row0.objs.desc.text.length <= budget, `desc stays inside the two-line budget (len=${row0.objs.desc.text.length} ≤ ${budget})`);
|
||||
// The wrapped desc never bleeds into the price: name/desc start at the text
|
||||
// column's left edge, well clear of the price's right-aligned x.
|
||||
const priceX = view.buyList._btnXs().price;
|
||||
assert.ok(view.buyList._textX() + descMaxW < priceX, 'text column + max desc width ends before the price');
|
||||
|
||||
// THEME FONTS — the shop UI speaks the project's type (data/theme.json):
|
||||
// Ethnocentric for section headers, Centauri for body text + buttons.
|
||||
const fam = (o) => o?._style?.fontFamily ?? '';
|
||||
|
|
@ -340,10 +375,14 @@ console.log('\n== SELL — check a bay, Confirm earns credits + empties the hold
|
|||
assert.equal(pv.valid, true, '… and it\u2019s legal');
|
||||
|
||||
// The live NET readout rides on the TAB SUB-BAR (not the center column) —
|
||||
// it mirrors the pending trade's credit delta, one glance from the tabs.
|
||||
// it mirrors the pending trade's credit delta, one glance from the tabs —
|
||||
// alongside the Confirm/Clear actions (which light up with a selection).
|
||||
assert.ok(view._netValue, 'the sub-bar carries the NET value readout');
|
||||
assert.ok(view._netLabel, '… with the NET label');
|
||||
assert.equal(view._netValue.text, `+${sellA} cr`, '… it shows the pending +cr');
|
||||
assert.ok(view._confirmBtn.alpha > 0.5, 'a selection reveals the sub-bar Confirm');
|
||||
assert.ok(view._clearBtn.alpha > 0.5, '… and Clear');
|
||||
assert.equal(view._confirmBtn.disabled, false, '… and Confirm is live (valid trade)');
|
||||
|
||||
const before = ship.credits;
|
||||
assert.equal(view.confirm(), true, 'Confirm goes through');
|
||||
|
|
@ -352,6 +391,8 @@ console.log('\n== SELL — check a bay, Confirm earns credits + empties the hold
|
|||
assert.equal(view.isBuybackEnabled(), true, '… and A is now buyback-eligible');
|
||||
assert.equal(gs.refreshed, 1, 'the credits HUD was refreshed');
|
||||
assert.equal(view._netValue.text, '0 cr', '… and the bar’s NET settles back to 0 cr');
|
||||
assert.equal(view._confirmBtn.alpha, 0, '… and the Confirm/Clear actions hide again (selection gone)');
|
||||
assert.equal(view._clearBtn.alpha, 0, '… Clear hides too');
|
||||
}
|
||||
console.log('SELL: OK');
|
||||
|
||||
|
|
@ -425,6 +466,7 @@ console.log('\n== CAPS — blocked when negative or over the hold ==');
|
|||
assert.equal(pv.valid, false, 'buying with 0 credits is INVALID');
|
||||
assert.ok(pv.errors.includes('insufficient-credits'), '… flagged insufficient-credits');
|
||||
assert.equal(vb.confirm(), false, '… and Confirm is refused');
|
||||
assert.equal(vb._confirmBtn.disabled, true, '… and the sub-bar Confirm is grayed (invalid trade)');
|
||||
assert.equal(broke.credits, 0, '… nothing spent');
|
||||
assert.equal(broke.cargo.used, 0, '… nothing bought');
|
||||
|
||||
|
|
@ -443,6 +485,7 @@ console.log('\n== CAPS — blocked when negative or over the hold ==');
|
|||
pv = vf.preview();
|
||||
assert.equal(pv.cargoAfter, 4, '4 − 1 (sold) + 1 (bought) = 4 → fits');
|
||||
assert.equal(pv.valid, true, '… now it\u2019s legal (room freed)');
|
||||
assert.equal(vf._confirmBtn.disabled, false, '… and Confirm un-grays (the trade is valid again)');
|
||||
const before = full.credits;
|
||||
assert.equal(vf.confirm(), true, '… and it goes through');
|
||||
assert.deepEqual(full.cargo.snapshot().sort(), [A, B, B, C].sort(), 'one A left, one C arrived (net hold 4)');
|
||||
|
|
@ -501,6 +544,8 @@ console.log('\n== BUYBACK is locked until a sale + Clear resets the selection ==
|
|||
view.clear();
|
||||
assert.equal(view.sellGrid.hasSelection(), false, 'Clear drops the selection');
|
||||
assert.equal(view.preview().net, 0, '… and the net returns to zero');
|
||||
assert.equal(view._confirmBtn.alpha, 0, '… and the sub-bar Confirm hides again');
|
||||
assert.equal(view._clearBtn.alpha, 0, '… and Clear');
|
||||
}
|
||||
console.log('LOCK + CLEAR: OK');
|
||||
|
||||
|
|
|
|||
|
|
@ -300,70 +300,81 @@ it. It's one component — `js/ui/shop/ShopView.js` — composing a `TradeGrid`
|
|||
side) + a `BuyList` (the BUY side).
|
||||
|
||||
**Entry state (showcase the clip).** When you first open the shop, **only the tab sub-bar**
|
||||
is up — the cyberpunk **MenuButton** pair `[ BUY/SELL | BUYBACK ]` (Buyback grayed out),
|
||||
extended at its right end by the live **NET** readout (dim `0 cr` until you start picking), the
|
||||
same style + placement as the deck's **Menu** sub-bar (`js/ui/MenuSubBar.js`). It rests **above
|
||||
is up — one console-blue bar in the deck's cyberpunk **MenuButton** style
|
||||
(`[ BUY/SELL | BUYBACK ] │ [ CLEAR | CONFIRM ] │ NET 0 cr` — the tabs, the trade
|
||||
actions, and the live **NET** readout, past hairline dividers; Buyback grayed, the
|
||||
actions hidden until you start picking, the readout dim `0 cr`), the same style +
|
||||
placement as the deck's **Menu** sub-bar (`js/ui/MenuSubBar.js`). It rests **above
|
||||
the command deck**, right-aligned to the deck's **Shop** button — so it reads as the same family
|
||||
of UI as the Menu drawer. The screen stays open on the shop video so the clip plays. Clicking
|
||||
**Buy/Sell** reveals the trade panel in the middle of the screen (the panels slide in).
|
||||
**Buy/Sell** reveals the trade panel in the middle of the screen: the **SELL grid** and the
|
||||
**BUY list** side by side with just a bit of padding between (the panels slide in).
|
||||
|
||||
```
|
||||
# first click — shop video + the tab sub-bar (rests above the deck):
|
||||
┌─────────────────────────────────────────────────────────────────┐
|
||||
│ (clip plays) │
|
||||
│ ┌──────────────────────────────────┐ │
|
||||
│ │ BUY/SELL │ BUYBACK │ NET 0 cr │ ← sub-bar │
|
||||
│ └──────────────────────────────────┘ │
|
||||
│ ┌────────────────────────────────────────────────┐ │
|
||||
│ │ BUY/SELL │ BUYBACK │ CLEAR CONFIRM │ NET 0 cr │ │
|
||||
│ └────────────────────────────────────────────────┘ │
|
||||
├─────────────────────────────────────────────────────────────────┤
|
||||
│ SHOP │ BUILD │ SHIP │ QUESTS │ TAKE OFF │ MENU ← command deck│
|
||||
└─────────────────────────────────────────────────────────────────┘
|
||||
|
||||
# after clicking Buy/Sell — the trade panel reveals (middle of the screen):
|
||||
│ SELL and BUY sit NEXT TO each other (a small gap between);
|
||||
│ the trade actions + NET readout ride on the sub-bar below.
|
||||
┌─────────────────────────────────────────────────────────────────┐
|
||||
│ ┌SELL 4×4┐ [ CONFIRM ] ┌──────────────────────────────┐ │
|
||||
│ │ bays │ [ CLEAR ] │[i] Fresh Produce 24 cr − 0 +│ │
|
||||
│ │(cargo) │ │ Crisp, clean, alive… │ │
|
||||
│ └────────┘ │[i] Refined Fuel 41 cr − 1 +│ │
|
||||
│ │ The lifeblood of the fleet │ │
|
||||
│ │ … (scrolls) … │ │
|
||||
│ └──────────────────────────────┘ │
|
||||
│ ┌─────────────────┐ ┌───────────────────────────────────┐ │
|
||||
│ │ SELL (4×4 bays) │ │[i] Fresh Produce 24 cr − 0 +│ │
|
||||
│ │ your cargo │ │ Crisp, clean, alive. A small mir… │ │
|
||||
│ │ (tap = sell) │ │[i] Refined Fuel 41 cr − 1 +│ │
|
||||
│ │ │ │ The lifeblood of the fleet. │ │
|
||||
│ └─────────────────┘ │ … (scrolls) … │ │
|
||||
│ └───────────────────────────────────┘ │
|
||||
├─────────────────────────────────────────────────────────────────┤
|
||||
│ ┌──────────────────────────────────┐ │
|
||||
│ │ BUY/SELL │ BUYBACK │ NET +420 cr │ ← sub-bar │
|
||||
│ └──────────────────────────────────┘ │
|
||||
│ ┌────────────────────────────────────────────────┐ │
|
||||
│ │ BUY/SELL │ BUYBACK │ CLEAR CONFIRM │ NET +420 cr │ │
|
||||
│ └────────────────────────────────────────────────┘ │
|
||||
├─────────────────────────────────────────────────────────────────┤
|
||||
│ SHOP │ BUILD │ SHIP │ QUESTS │ TAKE OFF │ MENU ← command deck│
|
||||
└─────────────────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
- **Tab sub-bar** — the **Buy/Sell** + **Buyback** buttons are `MenuButton`s (the deck's
|
||||
cyberpunk style: cut-corner panel, neon edge, hover sweep, press punch) in a console-blue
|
||||
panel that rests above the command deck, anchored to the Shop button — the exact recipe of
|
||||
the Menu sub-bar. Buyback is **disabled** (grayed) until you've sold something this visit.
|
||||
The bar is **extended** past a hairline divider to carry the live **NET** readout —
|
||||
one glance sees the tabs *and* the pending trade's credit delta.
|
||||
- **Tab sub-bar** — the **Buy/Sell** + **Buyback** buttons, the **Confirm/Clear**
|
||||
trade actions, and the live **NET** readout — all `MenuButton`s (the deck's cyberpunk
|
||||
style: cut-corner panel, neon edge, hover sweep, press punch) in one console-blue panel
|
||||
that rests above the command deck, anchored to the Shop button — the exact recipe of
|
||||
the Menu sub-bar. Buyback is **disabled** (grayed) until you've sold something this
|
||||
visit. The bar is **extended** past hairline dividers to carry the actions *and* the
|
||||
**NET** readout — one glance sees the tabs, the pending trade's credit delta, and the
|
||||
button that executes it.
|
||||
- **Deck lock (while the shop is up)** — the command deck's other slots (Build, Ship,
|
||||
Quests, Take Off, Menu) go **standby**: dim, no hover, no press — the same inert
|
||||
language as the comm-hold pause (`ActionBar.setSlotEnabled`). **LEAVE SHOP is the only
|
||||
live button on the deck**; the press that opened the shop is the only way out. They
|
||||
re-arm when you leave. (`ActionBar.setSlotEnabled` + `SurfaceScene.setDeckLocked`; the
|
||||
deck-wide comm hold still owns input if it fires mid-shop.)
|
||||
- **Confirm / Clear** — the same `MenuButton` style, in the center column between the panels;
|
||||
Confirm is **disabled** (grayed) while the trade is invalid (can't go negative, hold would
|
||||
overflow), and both appear once you start picking.
|
||||
- **Confirm / Clear** — the same `MenuButton` style, on the **tab sub-bar** between the
|
||||
tabs and the NET readout (they serve BOTH the trade and the buyback tabs, and persist
|
||||
while you switch between them); Confirm is **disabled** (grayed) while the trade is
|
||||
invalid (can't go negative, hold would overflow), and both appear once you start
|
||||
picking.
|
||||
|
||||
- **SELL (left)** — your **cargo bays**. 16 cells (the fixed grid); the first `cargoHold`
|
||||
are live bays (filled with a good, or empty), the rest are **locked** (the hold's upgrade
|
||||
room). Tap a filled bay to **check it off to sell** (a bay = 1 unit). No stepper — the
|
||||
quantity is simply the number of its bays that are checked.
|
||||
- **BUY (right)** — the shop's **stock** (12–16 distinct goods from `Economy.shopFor`) as a
|
||||
**scrollable list of WIDE ROWS**: each row is the good's **icon**, its **name** + a
|
||||
one-line **description** (the good's `flavor`), and the **price** + a **−/qty/+ stepper**
|
||||
on the right. The stock is longer than the panel, so the list **scrolls** (drag on the
|
||||
list body or the mouse wheel) — rows outside the window are clipped; the selection is
|
||||
data, so stepping a good works whether its row is on screen or not. Buy as many as you
|
||||
like (capped by credits + hold room).
|
||||
- **NET readout (tab sub-bar)** — the live credit delta, right of the tabs past the divider.
|
||||
- **BUY (right, next to the SELL grid)** — the shop's **stock** (12–16 distinct goods from
|
||||
`Economy.shopFor`) as a **scrollable list of WIDE ROWS**: each row is the good's
|
||||
**icon**, its **name** + a **description** (the good's `flavor`, a smaller font that
|
||||
**wraps to up to two lines** and never overlaps the price/stepper), and the **price** +
|
||||
a **−/qty/+ stepper** on the right. The stock is longer than the panel, so the list
|
||||
**scrolls** (drag on the list body or the mouse wheel) — rows outside the window are
|
||||
clipped; the selection is data, so stepping a good works whether its row is on screen
|
||||
or not. Buy as many as you like (capped by credits + hold room).
|
||||
- **NET readout (tab sub-bar)** — the live credit delta, at the bar's right end past the
|
||||
second divider (right of the tabs and the Confirm/Clear actions).
|
||||
Green when you're up (a net sale), red when you'd spend, dim at zero. Updates as you check
|
||||
bays / step goods (it's the same `preview()` the Confirm button is gated on).
|
||||
- **SELL → BUY is atomic** — check bays to sell *and* step goods to buy, then one Confirm
|
||||
|
|
|
|||
|
|
@ -59,10 +59,15 @@ export class BuyList extends Phaser.GameObjects.Container {
|
|||
this.height = Math.max(160, Number(o.height) || 480);
|
||||
this.pad = bl.pad ?? 14;
|
||||
this.titleH = config.get('shopui.panel.title.h', 34);
|
||||
this.rowH = bl.rowH ?? 64;
|
||||
this.rowH = bl.rowH ?? 72;
|
||||
this.rowGap = bl.rowGap ?? 6;
|
||||
this.iconSize = bl.icon ?? 44;
|
||||
this.iconGap = bl.iconGap ?? 12;
|
||||
// The description may WRAP onto up to `maxLines` lines (real Phaser
|
||||
// word-wraps it at the text column's width — see _addRow; the headless
|
||||
// char-count fit below budgets maxLines lines so it never bleeds into
|
||||
// the price/stepper either way).
|
||||
this.descLines = Math.max(1, Math.floor(Number(bl.desc?.maxLines) || 2));
|
||||
this.st = config.section('shopui.stepper', {}); // shared stepper knobs
|
||||
this.stepperW = this.st.w ?? 88;
|
||||
this.priceW = bl.price?.w ?? 64;
|
||||
|
|
@ -213,6 +218,7 @@ export class BuyList extends Phaser.GameObjects.Container {
|
|||
}
|
||||
|
||||
// name + description (left of the price/stepper block)
|
||||
const descFont = Number(bl.desc?.fontSize) || 12;
|
||||
const textX = this._textX();
|
||||
const nameEl = this._makeText(
|
||||
textX, 0, name,
|
||||
|
|
@ -220,12 +226,21 @@ export class BuyList extends Phaser.GameObjects.Container {
|
|||
);
|
||||
if (nameEl) { nameEl.setOrigin?.(0, 0.5); row.objs.name = nameEl; this._objs.push(nameEl); this.add?.(nameEl); }
|
||||
|
||||
row.desc = this._fitText(flavor, bl.desc?.fontSize ?? 12);
|
||||
row.desc = this._fitText(flavor, descFont, this.descLines);
|
||||
const descEl = this._makeText(
|
||||
textX, 0, row.desc,
|
||||
{ fontSize: `${bl.desc?.fontSize ?? 12}px`, color: bl.desc?.color ?? '#7d92c4', align: 'left' },
|
||||
{ fontSize: `${descFont}px`, color: bl.desc?.color ?? '#7d92c4', align: 'left' },
|
||||
);
|
||||
if (descEl) { descEl.setOrigin?.(0, 0.5); row.objs.desc = descEl; this._objs.push(descEl); this.add?.(descEl); }
|
||||
if (descEl) {
|
||||
// Word-wrap inside the text column — the wrap width stops BEFORE the
|
||||
// price and stepper (_descMaxW()), so a wrapped second line can never
|
||||
// overlap them. The stub no-ops these (the char-count fit keeps it
|
||||
// bounded there).
|
||||
descEl.setWordWrap?.(true);
|
||||
descEl.setWordWrapWidth?.(this._descMaxW());
|
||||
descEl.setOrigin?.(0, 0.5);
|
||||
row.objs.desc = descEl; this._objs.push(descEl); this.add?.(descEl);
|
||||
}
|
||||
|
||||
// price (right of the text, left of the stepper) — "24 cr"
|
||||
const priceEl = this._makeText(
|
||||
|
|
@ -296,12 +311,15 @@ export class BuyList extends Phaser.GameObjects.Container {
|
|||
return this._btnXs().price - this.priceW - 14 - this._textX();
|
||||
}
|
||||
|
||||
// Truncate `str` to fit ~maxW px at fontSize (deterministic char estimate
|
||||
// — no text metrics needed; the headless stubs have none).
|
||||
_fitText(str, fontSize) {
|
||||
// Truncate `str` to fit ~maxW px over `maxLines` line(s) at fontSize
|
||||
// (deterministic char estimate — no text metrics needed; the headless
|
||||
// stubs have none). Real Phaser word-wraps at _descMaxW() per line;
|
||||
// this cap is the fail-safe that keeps the total inside maxLines lines.
|
||||
_fitText(str, fontSize, maxLines = 1) {
|
||||
const s = String(str ?? '');
|
||||
if (!s) return '';
|
||||
const maxChars = Math.max(4, Math.floor(this._descMaxW() / (fontSize * 0.55)));
|
||||
const perLine = Math.max(4, Math.floor(this._descMaxW() / (fontSize * 0.55)));
|
||||
const maxChars = perLine * Math.max(1, Math.floor(Number(maxLines) || 1));
|
||||
if (s.length <= maxChars) return s;
|
||||
return `${s.slice(0, Math.max(0, maxChars - 1)).trimEnd()}…`;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -142,11 +142,12 @@ export class ShopView extends Phaser.GameObjects.Container {
|
|||
// command deck, right-aligned to the deck's "Shop" button (this.anchor),
|
||||
// so it reads as the same family of UI as the Menu drawer.
|
||||
//
|
||||
// EXTENDED (data/shopui.json → preview): the live NET credit readout —
|
||||
// the pending trade's credit delta — sits at the bar's right end past a
|
||||
// hairline divider. Tabs AND the credit math on ONE console: the readout
|
||||
// is one glance away from the tabs at every moment (entry state included,
|
||||
// where it dims to "0 cr").
|
||||
// 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").
|
||||
const a = this.anchor;
|
||||
const H = config.get('save.subBar.height', 58);
|
||||
const pad = config.get('save.subBar.padding', 16);
|
||||
|
|
@ -163,8 +164,8 @@ export class ShopView extends Phaser.GameObjects.Container {
|
|||
|
||||
// Build the buttons first (they measure their own width) so the row
|
||||
// layout uses the REAL widths — no second, divergent text-measure pass.
|
||||
const mkBtn = (item) => new MenuButton(
|
||||
this.scene, 0, -H / 2, String(item.label ?? item.id), () => this._onTabTap(item.id),
|
||||
const mkBtn = (label, accent, onClick) => new MenuButton(
|
||||
this.scene, 0, -H / 2, String(label), onClick,
|
||||
{
|
||||
screenFixed: true,
|
||||
fontSize: btnCfg.fontSize ?? 13,
|
||||
|
|
@ -175,14 +176,27 @@ export class ShopView extends Phaser.GameObjects.Container {
|
|||
textColor: c.ink ?? '#eaf6ff',
|
||||
bgColor: c.cardBg ?? '#0a1222',
|
||||
hoverColor: c.cardBg ?? '#0a1222',
|
||||
borderColor: toCss(item.accent ?? c.neon ?? '#00e5ff'),
|
||||
borderColor: toCss(accent ?? c.neon ?? '#00e5ff'),
|
||||
},
|
||||
);
|
||||
|
||||
const btns = items.map(mkBtn);
|
||||
const btns = items.map((it) => mkBtn(String(it.label ?? it.id), it.accent, () => this._onTabTap(it.id)));
|
||||
const widths = btns.map((b) => b.style?.width ?? 120);
|
||||
const tabsW = widths.reduce((s, w) => s + w, 0) + gap * (widths.length - 1);
|
||||
|
||||
// The trade actions — Clear (secondary, dim) + Confirm (primary, green)
|
||||
// — live on the bar too (both trade AND buyback confirm here, since
|
||||
// preview()/confirm() already branch on the tab).
|
||||
const buttons = config.section('shopui.buttons', {});
|
||||
this._clearBtn = mkBtn(buttons.clearLabel ?? 'Clear', buttons.clearAccent ?? c.dim ?? '#7d92c4', () => this.clear());
|
||||
this._confirmBtn = mkBtn(buttons.confirmLabel ?? 'Confirm', buttons.confirmAccent ?? '#7ce8a4', () => this.confirm());
|
||||
this._confirmBtn.setAlpha?.(0); // hidden until a selection exists (_syncPreview)
|
||||
this._clearBtn.setAlpha?.(0);
|
||||
this._confirmBtn.setDisabled?.(true); // + invalid-trade gray, via _syncPreview
|
||||
const clearW = this._clearBtn.style?.width ?? 90;
|
||||
const confirmW = this._confirmBtn.style?.width ?? 110;
|
||||
const actionsW = clearW + gap + confirmW;
|
||||
|
||||
// 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
|
||||
|
|
@ -204,8 +218,11 @@ export class ShopView extends Phaser.GameObjects.Container {
|
|||
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 ] │ NET value
|
||||
const W = Math.min(this._W() - 24, pad * 2 + tabsW + gap + divW + gap + labelW + gap + valueW);
|
||||
// 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
|
||||
|
|
@ -224,15 +241,15 @@ export class ShopView extends Phaser.GameObjects.Container {
|
|||
this.add?.(bar);
|
||||
|
||||
// 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
|
||||
// divider between the tabs and the NET block is part of the body, so it
|
||||
// paints under both.
|
||||
const divX = -W / 2 + pad + tabsW + gap + divW / 2;
|
||||
// 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, divX, divH);
|
||||
this._drawSubBarBody(bodyG, W, H, c, [divX1, divX2], divH);
|
||||
}
|
||||
|
||||
// The buttons LAST (above the body), left-anchored in the row.
|
||||
|
|
@ -246,9 +263,17 @@ export class ShopView extends Phaser.GameObjects.Container {
|
|||
bx += widths[i] / 2 + gap + (i < items.length - 1 ? widths[i + 1] / 2 : 0);
|
||||
});
|
||||
|
||||
// The NET block: the label right after the divider, the value right-
|
||||
// aligned to the bar's end (so a growing number never shifts the label).
|
||||
this._netLabel.setPosition?.(divX + divW / 2 + gap + labelW / 2, -H / 2);
|
||||
// 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);
|
||||
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);
|
||||
bar.add?.(this._netLabel);
|
||||
bar.add?.(this._netValue);
|
||||
|
|
@ -259,10 +284,11 @@ export class ShopView extends Phaser.GameObjects.Container {
|
|||
|
||||
/**
|
||||
* The sub-bar panel body (the MenuSubBar recipe at full height).
|
||||
* `divX`/`divH` (optional) add the hairline divider between the tabs and
|
||||
* the NET readout — drawn as body so it paints under the buttons + text.
|
||||
* `dividers` (optional) — hairline divider x's (one between the tabs and
|
||||
* the actions, one between the actions and the NET readout) — drawn as
|
||||
* body so they paint under the buttons + text.
|
||||
*/
|
||||
_drawSubBarBody(g, W, H, c, divX, divH) {
|
||||
_drawSubBarBody(g, W, H, c, dividers, divH) {
|
||||
if (!g || typeof g.clear !== 'function') return;
|
||||
const panelTop = toColor(c.panelTop ?? '#0e1930');
|
||||
const panelBottom = toColor(c.panelBottom ?? '#050912');
|
||||
|
|
@ -289,9 +315,10 @@ export class ShopView extends Phaser.GameObjects.Container {
|
|||
g.fillStyle(ink, 0.8); g.fillRect(-W / 2 + cut, -H + 0.5, W - cut * 2, 0.8);
|
||||
g.fillStyle(magenta, 0.5); g.fillRect(-W / 2 + cut, -H + rail, W - cut * 2, 1);
|
||||
g.fillStyle(magenta, 0.35); g.fillRect(-W / 2, -1.5, W, 1.5); // seam
|
||||
if (typeof divX === 'number' && divH > 0) {
|
||||
const divs = Array.isArray(dividers) ? dividers : (typeof dividers === 'number' ? [dividers] : []);
|
||||
if (divs.length > 0 && divH > 0) {
|
||||
g.fillStyle(toColor(c.border ?? '#22405f'), 0.9);
|
||||
g.fillRect(divX - 1, -H / 2 - divH / 2, 2, divH); // tabs │ NET
|
||||
for (const dx of divs) g.fillRect(dx - 1, -H / 2 - divH / 2, 2, divH); // section dividers
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -309,11 +336,14 @@ export class ShopView extends Phaser.GameObjects.Container {
|
|||
}
|
||||
|
||||
// ---------------------------------------------------------------------
|
||||
// CONTENT — the trade panel (SELL grid + BUY list + buttons)
|
||||
// CONTENT — the trade panel (SELL grid + BUY list, side by side)
|
||||
// ---------------------------------------------------------------------
|
||||
_showTrade() {
|
||||
const grid = config.get('shopui.grid', {});
|
||||
const centerW = grid.centerW ?? 208;
|
||||
// The two panels sit NEXT TO each other with just a bit of padding
|
||||
// between (the trade actions + NET readout ride on the tab sub-bar —
|
||||
// see _buildTabs — so the panel is pure selection).
|
||||
const centerW = grid.centerW ?? 16;
|
||||
const W = this._W();
|
||||
|
||||
const sellGrid = new TradeGrid(this.scene, {
|
||||
|
|
@ -333,7 +363,7 @@ export class ShopView extends Phaser.GameObjects.Container {
|
|||
this.sellGrid = sellGrid;
|
||||
this.buyList = buyList;
|
||||
|
||||
// Layout: [SELL grid] [center] [BUY list]. The panel's TOP edge sits at
|
||||
// Layout: [SELL grid] [gap] [BUY list]. The panel's TOP edge sits at
|
||||
// this.topY — just under the surface HUD's planet text (upper-left) — so
|
||||
// the whole panel reads as one unit near the top of the screen (not
|
||||
// hanging down to the deck/sub-bar).
|
||||
|
|
@ -345,7 +375,6 @@ export class ShopView extends Phaser.GameObjects.Container {
|
|||
// the panel half a grid lower and clips the bottom rows.
|
||||
const totalW = sellGrid.width + centerW + buyList.width;
|
||||
const leftX = Math.max(8, (W - totalW) / 2);
|
||||
const gridH = sellGrid.height;
|
||||
sellGrid.setPosition(leftX, this.topY);
|
||||
const rightX = leftX + sellGrid.width + centerW;
|
||||
buyList.setPosition(rightX, this.topY);
|
||||
|
|
@ -355,41 +384,8 @@ export class ShopView extends Phaser.GameObjects.Container {
|
|||
content.add?.(sellGrid);
|
||||
content.add?.(buyList);
|
||||
|
||||
// center column: Confirm + Clear — vertically centered with the grids,
|
||||
// so the whole panel is one cohesive block near the top of the screen.
|
||||
// (The live NET preview now rides on the tab sub-bar — see _buildTabs.)
|
||||
const cx = leftX + sellGrid.width + centerW / 2;
|
||||
const cy = this.topY + gridH / 2;
|
||||
|
||||
// Confirm / Clear — the same cyberpunk MenuButton as the tab bar.
|
||||
const c = config.section('save.colors', {});
|
||||
const btnCfg = config.section('save.subBar.button', {});
|
||||
this._confirmBtn = new MenuButton(
|
||||
this.scene, cx, cy + 16, config.get('shopui.buttons.confirmLabel', 'Confirm'), () => this.confirm(),
|
||||
{
|
||||
screenFixed: true,
|
||||
fontSize: btnCfg.fontSize ?? 13, paddingX: 26, paddingY: 12,
|
||||
letterSpacing: btnCfg.letterSpacing ?? 2, upper: true,
|
||||
textColor: c.ink ?? '#eaf6ff', bgColor: c.cardBg ?? '#0a1222',
|
||||
hoverColor: c.cardBg ?? '#0a1222', borderColor: toCss(config.get('shopui.buttons.confirmAccent', '#7ce8a4')),
|
||||
},
|
||||
);
|
||||
this._confirmBtn.setAlpha?.(0);
|
||||
content.add?.(this._confirmBtn);
|
||||
|
||||
this._clearBtn = new MenuButton(
|
||||
this.scene, cx, cy + 86, config.get('shopui.buttons.clearLabel', 'Clear'), () => this.clear(),
|
||||
{
|
||||
screenFixed: true,
|
||||
fontSize: btnCfg.fontSize ?? 13, paddingX: 26, paddingY: 12,
|
||||
letterSpacing: btnCfg.letterSpacing ?? 2, upper: true,
|
||||
textColor: c.ink ?? '#eaf6ff', bgColor: c.cardBg ?? '#0a1222',
|
||||
hoverColor: c.cardBg ?? '#0a1222', borderColor: toCss(config.get('shopui.buttons.clearAccent', '#7d92c4')),
|
||||
},
|
||||
);
|
||||
this._clearBtn.setAlpha?.(0);
|
||||
content.add?.(this._clearBtn);
|
||||
|
||||
// Confirm / Clear live on the tab sub-bar (persistent — see
|
||||
// _buildTabs); the panel is pure selection.
|
||||
this._content = content;
|
||||
this.add?.(content);
|
||||
|
||||
|
|
@ -436,16 +432,9 @@ export class ShopView extends Phaser.GameObjects.Container {
|
|||
y += rowH;
|
||||
}
|
||||
|
||||
const cy = y + 24;
|
||||
// (The live NET readout rides on the tab sub-bar — see _buildTabs.)
|
||||
|
||||
const bw = config.get('shopui.buttons.w', 158), bh = config.get('shopui.buttons.h', 54);
|
||||
this._confirmBtn = this._makeButton(config.get('shopui.buttons.confirmLabel', 'Confirm'), bw, bh, config.get('shopui.buttons.confirmAccent', '#7ce8a4'));
|
||||
this._confirmBtn.setPosition(cx, cy + 40);
|
||||
this._confirmBtn.tap(() => this.confirm());
|
||||
this._confirmBtn.setAlpha(0);
|
||||
content.add?.(this._confirmBtn.container);
|
||||
|
||||
// Confirm / Clear / the live NET readout all ride on the tab sub-bar
|
||||
// (persistent — see _buildTabs); the buyback panel is the pure list of
|
||||
// sold goods.
|
||||
this._content = content;
|
||||
this.add?.(content);
|
||||
this._syncPreview();
|
||||
|
|
@ -621,6 +610,8 @@ export class ShopView extends Phaser.GameObjects.Container {
|
|||
}
|
||||
|
||||
_destroyContent() {
|
||||
// The panel content goes; the sub-bar (tabs + actions + NET readout)
|
||||
// PERSISTS across tab switches — it's not part of the content.
|
||||
if (this._content) {
|
||||
this._content.destroy?.();
|
||||
this._content = null;
|
||||
|
|
@ -629,18 +620,24 @@ export class ShopView extends Phaser.GameObjects.Container {
|
|||
this.buyList?.destroy?.();
|
||||
this.sellGrid = null;
|
||||
this.buyList = null;
|
||||
this._confirmBtn = null;
|
||||
this._clearBtn = null;
|
||||
}
|
||||
|
||||
destroy() {
|
||||
this._destroyContent();
|
||||
this._tabTrade?.destroy?.();
|
||||
this._tabBuyback?.destroy?.();
|
||||
this._confirmBtn?.destroy?.();
|
||||
this._clearBtn?.destroy?.();
|
||||
this._netLabel?.destroy?.();
|
||||
this._netValue?.destroy?.();
|
||||
this._tabTrade = null;
|
||||
this._tabBuyback = null;
|
||||
this._confirmBtn = null;
|
||||
this._clearBtn = null;
|
||||
this._netLabel = null;
|
||||
this._netValue = null;
|
||||
this.subBar?.destroy?.();
|
||||
this.subBar = null;
|
||||
if (typeof super.destroy === 'function') super.destroy();
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Reference in New Issue