Add shop clip toggle to surface deck
- SHOP button now swaps the looping surface clip for the world's `shop` video (e.g. terran-shop-01.mp4); pressing again swaps back. The slot relabels between "Shop" and "Leave Shop" via a new ActionBar.setLabel. - data/landing.json gains a per-frame `shop` entry; worlds without one get a console note instead of a clip. - SurfaceScene: toggleShop/leaveShop manage the shop video (pause/resume, visibility), error handling falls back to the surface loop, and cleanup on takeoff/shutdown disposes the new video and note glyphs. - ActionBar: slots store their base fontSize so setLabel can shrink-to-fit long labels; paintSlot re-asserts hover state after relabeling.
This commit is contained in:
parent
70f4954c69
commit
d7f8e2101f
|
|
@ -20,9 +20,9 @@
|
||||||
{ "id": "menu", "label": "Menu", "accent": "#ff2d6f" }
|
{ "id": "menu", "label": "Menu", "accent": "#ff2d6f" }
|
||||||
],
|
],
|
||||||
"surface": {
|
"surface": {
|
||||||
"_comment": "Deck layout for the surface (SurfaceScene): `research` is replaced by `shop` (no-op for now) and `takeoff` sits to the left of `menu`. SurfaceScene passes this list to ActionBar via its `buttons` option; everything else (height, colors, animation) is shared with the flight deck.",
|
"_comment": "Deck layout for the surface (SurfaceScene): `research` is replaced by `shop` (toggles the world's shop clip — while the clip is up the slot relabels to `labelActive`, and pressing it swaps back) and `takeoff` sits to the left of `menu`. SurfaceScene passes this list to ActionBar via its `buttons` option; everything else (height, colors, animation) is shared with the flight deck.",
|
||||||
"buttons": [
|
"buttons": [
|
||||||
{ "id": "shop", "label": "Shop", "accent": "#00e5ff" },
|
{ "id": "shop", "label": "Shop", "labelActive": "Leave Shop", "accent": "#00e5ff" },
|
||||||
{ "id": "build", "label": "Build", "accent": "#ffc94d" },
|
{ "id": "build", "label": "Build", "accent": "#ffc94d" },
|
||||||
{ "id": "ship", "label": "Ship", "accent": "#7ce8a4" },
|
{ "id": "ship", "label": "Ship", "accent": "#7ce8a4" },
|
||||||
{ "id": null, "label": null },
|
{ "id": null, "label": null },
|
||||||
|
|
|
||||||
|
|
@ -6,6 +6,9 @@
|
||||||
"",
|
"",
|
||||||
"`land` — the full-screen one-shot clip played when the ship lands.",
|
"`land` — the full-screen one-shot clip played when the ship lands.",
|
||||||
"`surface` — the looping clip shown on the surface deck behind the action bar.",
|
"`surface` — the looping clip shown on the surface deck behind the action bar.",
|
||||||
|
"`shop` — the looping clip the SHOP button swaps in over the surface",
|
||||||
|
" clip (press SHOP again to swap back). null/missing = no shop",
|
||||||
|
" on that world (a console note instead of a clip).",
|
||||||
"`takeoff` — the full-screen one-shot clip played on TAKE OFF, before the",
|
"`takeoff` — the full-screen one-shot clip played on TAKE OFF, before the",
|
||||||
" flight world resumes. A null/missing entry takes off instantly.",
|
" flight world resumes. A null/missing entry takes off instantly.",
|
||||||
"",
|
"",
|
||||||
|
|
@ -37,11 +40,11 @@
|
||||||
"lineStrokeWidth": 2
|
"lineStrokeWidth": 2
|
||||||
},
|
},
|
||||||
"videos": [
|
"videos": [
|
||||||
{ "land": "terran-land-01.mp4", "surface": "terran-surface-01.mp4", "takeoff": "terran-takeoff-01.mp4" },
|
{ "land": "terran-land-01.mp4", "surface": "terran-surface-01.mp4", "takeoff": "terran-takeoff-01.mp4", "shop": "terran-shop-01.mp4" },
|
||||||
{ "land": "terran-land-02.mp4", "surface": "terran-surface-01.mp4", "takeoff": "terran-takeoff-02.mp4" },
|
{ "land": "terran-land-02.mp4", "surface": "terran-surface-01.mp4", "takeoff": "terran-takeoff-02.mp4", "shop": "terran-shop-01.mp4" },
|
||||||
{ "land": "terran-land-01.mp4", "surface": "terran-surface-01.mp4", "takeoff": "terran-takeoff-01.mp4" },
|
{ "land": "terran-land-01.mp4", "surface": "terran-surface-01.mp4", "takeoff": "terran-takeoff-01.mp4", "shop": "terran-shop-01.mp4" },
|
||||||
{ "land": "gasgiant-land-01.mp4", "surface": "terran-surface-01.mp4", "takeoff": "gasgiant-takeoff-01.mp4" },
|
{ "land": "gasgiant-land-01.mp4", "surface": "terran-surface-01.mp4", "takeoff": "gasgiant-takeoff-01.mp4", "shop": "terran-shop-01.mp4" },
|
||||||
{ "land": "gasgiant-land-02.mp4", "surface": "terran-surface-01.mp4", "takeoff": "gasgiant-takeoff-02.mp4" },
|
{ "land": "gasgiant-land-02.mp4", "surface": "terran-surface-01.mp4", "takeoff": "gasgiant-takeoff-02.mp4", "shop": "terran-shop-01.mp4" },
|
||||||
{ "land": "gasgiant-land-01.mp4", "surface": "terran-surface-01.mp4", "takeoff": "gasgiant-takeoff-01.mp4" }
|
{ "land": "gasgiant-land-01.mp4", "surface": "terran-surface-01.mp4", "takeoff": "gasgiant-takeoff-01.mp4", "shop": "terran-shop-01.mp4" }
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -18,8 +18,9 @@ import { SaveManager } from '../save/SaveManager.js';
|
||||||
* plate. No UI during the flight down.
|
* plate. No UI during the flight down.
|
||||||
* 2. SURFACE — the clip is swapped for the looping world clip (`surface`),
|
* 2. SURFACE — the clip is swapped for the looping world clip (`surface`),
|
||||||
* and the command deck appears: the GameScene deck with Research
|
* and the command deck appears: the GameScene deck with Research
|
||||||
* replaced by SHOP (a seam for now) and TAKE OFF left of MENU
|
* replaced by SHOP (swaps the loop for the world's `shop` clip —
|
||||||
* (data/actionbar.json → surface.buttons). Menu keeps the GameScene's
|
* e.g. terran-shop-01.mp4 — press again to swap back) and TAKE OFF
|
||||||
|
* left of MENU (data/actionbar.json → surface.buttons). Menu keeps the GameScene's
|
||||||
* save sub-bar — Save/Load operate on the paused GameScene's state
|
* save sub-bar — Save/Load operate on the paused GameScene's state
|
||||||
* (SavePanel `stateScene`). While the clip loops, the planet's name
|
* (SavePanel `stateScene`). While the clip loops, the planet's name
|
||||||
* decodes in at the upper-left (Ethnocentric, the menu title's shade,
|
* decodes in at the upper-left (Ethnocentric, the menu title's shade,
|
||||||
|
|
@ -36,7 +37,9 @@ import { SaveManager } from '../save/SaveManager.js';
|
||||||
* giants, per data/planets.json → frames), so a planet always lands with
|
* giants, per data/planets.json → frames), so a planet always lands with
|
||||||
* the clip matching the art it is drawn with. Missing clips fall back to
|
* the clip matching the art it is drawn with. Missing clips fall back to
|
||||||
* the existing stubs in data/landing.json; a broken/missing file skips
|
* the existing stubs in data/landing.json; a broken/missing file skips
|
||||||
* straight to the surface instead of stranding the player.
|
* straight to the surface instead of stranding the player. SHOP swaps the
|
||||||
|
* surface loop for the frame's `shop` clip (null on worlds without one —
|
||||||
|
* a console note instead).
|
||||||
*/
|
*/
|
||||||
export class SurfaceScene extends Phaser.Scene {
|
export class SurfaceScene extends Phaser.Scene {
|
||||||
constructor() {
|
constructor() {
|
||||||
|
|
@ -67,6 +70,10 @@ export class SurfaceScene extends Phaser.Scene {
|
||||||
this.takeoffKey = null;
|
this.takeoffKey = null;
|
||||||
this.takeoffVideo = null;
|
this.takeoffVideo = null;
|
||||||
this.takeoffDone = false; // finishTakeoff idempotency ('complete' + a guard can both fire)
|
this.takeoffDone = false; // finishTakeoff idempotency ('complete' + a guard can both fire)
|
||||||
|
this.shopKey = null;
|
||||||
|
this.shopVideo = null; // the SHOP clip (created on first press, then paused/resumed)
|
||||||
|
this.shopMode = false; // true while the shop clip is on screen (SHOP toggles it)
|
||||||
|
this.noteG = null; // console-note glyphs (consoleNote)
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Pick this world's clips (data/landing.json) and queue them. */
|
/** Pick this world's clips (data/landing.json) and queue them. */
|
||||||
|
|
@ -75,12 +82,15 @@ export class SurfaceScene extends Phaser.Scene {
|
||||||
this.landKey = `__surf_land_${this.planetFrame}`;
|
this.landKey = `__surf_land_${this.planetFrame}`;
|
||||||
this.surfaceKey = `__surf_loop_${this.planetFrame}`;
|
this.surfaceKey = `__surf_loop_${this.planetFrame}`;
|
||||||
this.takeoffKey = `__surf_takeoff_${this.planetFrame}`;
|
this.takeoffKey = `__surf_takeoff_${this.planetFrame}`;
|
||||||
|
this.shopKey = `__surf_shop_${this.planetFrame}`;
|
||||||
this.landUrl = this.resolveUrl(entry.land ?? null);
|
this.landUrl = this.resolveUrl(entry.land ?? null);
|
||||||
this.surfaceUrl = this.resolveUrl(entry.surface ?? null);
|
this.surfaceUrl = this.resolveUrl(entry.surface ?? null);
|
||||||
this.takeoffUrl = this.resolveUrl(entry.takeoff ?? null);
|
this.takeoffUrl = this.resolveUrl(entry.takeoff ?? null);
|
||||||
|
this.shopUrl = this.resolveUrl(entry.shop ?? null);
|
||||||
if (this.landUrl) this.load.video(this.landKey, this.landUrl);
|
if (this.landUrl) this.load.video(this.landKey, this.landUrl);
|
||||||
if (this.surfaceUrl) this.load.video(this.surfaceKey, this.surfaceUrl);
|
if (this.surfaceUrl) this.load.video(this.surfaceKey, this.surfaceUrl);
|
||||||
if (this.takeoffUrl) this.load.video(this.takeoffKey, this.takeoffUrl);
|
if (this.takeoffUrl) this.load.video(this.takeoffKey, this.takeoffUrl);
|
||||||
|
if (this.shopUrl) this.load.video(this.shopKey, this.shopUrl);
|
||||||
|
|
||||||
// The surface's music loop (data/music.json → frames[planets.png frame])
|
// The surface's music loop (data/music.json → frames[planets.png frame])
|
||||||
// — the same key as the videos, so a world hums the track matching the
|
// — the same key as the videos, so a world hums the track matching the
|
||||||
|
|
@ -190,6 +200,141 @@ export class SurfaceScene extends Phaser.Scene {
|
||||||
this.buildDeck();
|
this.buildDeck();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ------------------------------------------------------------------
|
||||||
|
// Stage 2b — the shop clip (SHOP swaps the loop, and back)
|
||||||
|
// ------------------------------------------------------------------
|
||||||
|
|
||||||
|
/**
|
||||||
|
* SHOP — swap the looping surface clip for this world's shop clip
|
||||||
|
* (data/landing.json → videos[frame].shop, e.g. terran-shop-01.mp4);
|
||||||
|
* the next press swaps back. The surface clip is PAUSED, not
|
||||||
|
* destroyed, so it resumes exactly where it left off; the shop clip
|
||||||
|
* is created on the first press (the file was preloaded) and then
|
||||||
|
* just paused/resumed, and the deck's SHOP slot relabels to
|
||||||
|
* `labelActive` ("Leave Shop", data/actionbar.json → surface.buttons)
|
||||||
|
* and back. The upper-left HUD stays up in both states — the shop is
|
||||||
|
* on the same world — and the surface hum keeps humming.
|
||||||
|
* A world whose frame has no shop clip (data/landing.json → shop null)
|
||||||
|
* gets a console note instead.
|
||||||
|
*/
|
||||||
|
toggleShop() {
|
||||||
|
if (this.phase !== 'surface') return; // no deck, no shop
|
||||||
|
if (this.shopMode) {
|
||||||
|
this.leaveShop();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (!this.hasVideo(this.shopKey)) {
|
||||||
|
this.consoleNote('NO SHOP ON THIS WORLD');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (!this.shopVideo) {
|
||||||
|
// First press of this stay: bring the clip up (only the <video>
|
||||||
|
// element is new — the file was preloaded).
|
||||||
|
const s = this.add.video(0, 0, this.shopKey).setOrigin(0.5).setDepth(10);
|
||||||
|
this.attachClip(s);
|
||||||
|
s.setLoop(true);
|
||||||
|
this.shopVideo = s;
|
||||||
|
s.on('error', () => {
|
||||||
|
// Broken clip — drop back to the surface loop, don't strand the view.
|
||||||
|
this.shopMode = false;
|
||||||
|
this.destroyVideo(this.shopVideo);
|
||||||
|
this.shopVideo = null;
|
||||||
|
this.surfaceVideo?.resume(); // v4 quirk: play() no-ops a clip that has played before
|
||||||
|
this.actionBar?.setLabel('shop', this.shopLabel(false)); // the slot is back to SHOP
|
||||||
|
this.consoleNote('SHOP CLIP UNAVAILABLE');
|
||||||
|
});
|
||||||
|
}
|
||||||
|
this.shopVideo.setVisible(true); // may be hidden from a previous visit
|
||||||
|
this.surfaceVideo?.pause(); // the loop pauses where it is (resume() brings it back)
|
||||||
|
// resume(), not play(): the first start is a no-op-safe superset (it
|
||||||
|
// falls through to the first-play path), and on a RE-entry the clip
|
||||||
|
// has played before, where play() would silently no-op.
|
||||||
|
this.shopVideo.resume();
|
||||||
|
this.shopMode = true;
|
||||||
|
this.actionBar?.setLabel('shop', this.shopLabel(true)); // the slot now reads LEAVE SHOP
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The second SHOP press — back to the looping surface clip.
|
||||||
|
* v4 quirk: for a clip that has already played once, `play()` is a
|
||||||
|
* no-op (its createPlayPromise gate only fires on the first start) —
|
||||||
|
* `resume()` (setPaused(false)) is what actually restarts it. And a
|
||||||
|
* paused <video> keeps showing its last frame, so the shop clip —
|
||||||
|
* painted over the surface loop — must be hidden, not just paused.
|
||||||
|
*/
|
||||||
|
leaveShop() {
|
||||||
|
this.shopMode = false;
|
||||||
|
this.shopVideo?.pause();
|
||||||
|
this.shopVideo?.setVisible(false); // its frozen last frame would cover the surface
|
||||||
|
this.surfaceVideo?.resume(); // resumes where it paused (no surface clip → the plate)
|
||||||
|
this.actionBar?.setLabel('shop', this.shopLabel(false)); // the slot is back to SHOP
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The SHOP slot's label for the given state (data/actionbar.json →
|
||||||
|
* surface.buttons: `label` out, `labelActive` in), with plain fallbacks
|
||||||
|
* if the config entry is missing.
|
||||||
|
*/
|
||||||
|
shopLabel(inShop) {
|
||||||
|
const buttons = config.get('actionbar.surface.buttons');
|
||||||
|
const b = Array.isArray(buttons) ? buttons.find((x) => x && x.id === 'shop') : null;
|
||||||
|
const active = b?.labelActive ?? 'Leave Shop';
|
||||||
|
const base = b?.label ?? 'Shop';
|
||||||
|
return inShop ? String(active) : String(base);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The console note — this scene's small cousin of GameScene.consoleToast:
|
||||||
|
* an accent glyph + console-caps line, top-center, fading in, holding,
|
||||||
|
* fading out. One at a time (a new note replaces an old one). SHOP
|
||||||
|
* feedback lives here.
|
||||||
|
*/
|
||||||
|
consoleNote(label, { glyph = '\u25b8', glyphColor, durationMs = 2400 } = {}) {
|
||||||
|
if (Array.isArray(this.noteG)) {
|
||||||
|
for (const g of this.noteG) g.destroy();
|
||||||
|
this.noteG = null;
|
||||||
|
}
|
||||||
|
const fam = fontStack('body');
|
||||||
|
const g1 = this.add
|
||||||
|
.text(0, 0, glyph, {
|
||||||
|
fontFamily: fam,
|
||||||
|
fontSize: '13px',
|
||||||
|
color: glyphColor ?? toCss(themeColor('neon', 0x00e5ff)),
|
||||||
|
})
|
||||||
|
.setOrigin(0, 0.5)
|
||||||
|
.setScrollFactor(0); // UI — pinned to the screen
|
||||||
|
const g2 = this.add
|
||||||
|
.text(0, 0, String(label ?? '').toUpperCase(), {
|
||||||
|
fontFamily: fam,
|
||||||
|
fontSize: '11px',
|
||||||
|
color: toCss(themeColor('dim', 0x7d92c4)),
|
||||||
|
letterSpacing: 2,
|
||||||
|
})
|
||||||
|
.setOrigin(0, 0.5)
|
||||||
|
.setScrollFactor(0); // UI — pinned to the screen
|
||||||
|
const total = g1.width + 10 + g2.width;
|
||||||
|
const x0 = this.scale.width / 2 - total / 2;
|
||||||
|
const yy = this.scale.height / 2 - 75; // the flight world's console slot
|
||||||
|
g1.setPosition(x0, yy).setDepth(60).setAlpha(0);
|
||||||
|
g2.setPosition(x0 + g1.width + 10, yy).setDepth(60).setAlpha(0);
|
||||||
|
this.noteG = [g1, g2];
|
||||||
|
this.tweens.add({ targets: this.noteG, alpha: 1, duration: 180, ease: 'Sine.easeOut' });
|
||||||
|
this.time.delayedCall(durationMs, () => {
|
||||||
|
if (!Array.isArray(this.noteG)) return;
|
||||||
|
const [a, b] = this.noteG;
|
||||||
|
this.noteG = null;
|
||||||
|
this.tweens.add({
|
||||||
|
targets: [a, b],
|
||||||
|
alpha: 0,
|
||||||
|
duration: 350,
|
||||||
|
onComplete: () => {
|
||||||
|
a.destroy();
|
||||||
|
b.destroy();
|
||||||
|
},
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
// ------------------------------------------------------------------
|
// ------------------------------------------------------------------
|
||||||
// HUD (upper-left) — the planet's name + type + tether level
|
// HUD (upper-left) — the planet's name + type + tether level
|
||||||
// ------------------------------------------------------------------
|
// ------------------------------------------------------------------
|
||||||
|
|
@ -321,11 +466,16 @@ export class SurfaceScene extends Phaser.Scene {
|
||||||
// ------------------------------------------------------------------
|
// ------------------------------------------------------------------
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The deck's button presses (ActionBar → onAction). SHOP / BUILD /
|
* The deck's button presses (ActionBar → onAction). SHOP swaps the
|
||||||
|
* looping surface clip for the world's shop clip (and back); BUILD /
|
||||||
* SHIP are seams for the surface economy (data/builds.json plugs in
|
* SHIP are seams for the surface economy (data/builds.json plugs in
|
||||||
* later); TAKE OFF goes back to the ship; MENU is the save door.
|
* later); TAKE OFF goes back to the ship; MENU is the save door.
|
||||||
*/
|
*/
|
||||||
deckAction(id) {
|
deckAction(id) {
|
||||||
|
if (id === 'shop') {
|
||||||
|
this.toggleShop();
|
||||||
|
return;
|
||||||
|
}
|
||||||
if (id === 'menu') {
|
if (id === 'menu') {
|
||||||
this.menuAction();
|
this.menuAction();
|
||||||
return;
|
return;
|
||||||
|
|
@ -416,6 +566,11 @@ export class SurfaceScene extends Phaser.Scene {
|
||||||
this.destroyVideo(this.surfaceVideo); // the opaque clip hides it — free the decoder
|
this.destroyVideo(this.surfaceVideo); // the opaque clip hides it — free the decoder
|
||||||
this.surfaceVideo = null;
|
this.surfaceVideo = null;
|
||||||
}
|
}
|
||||||
|
if (this.shopVideo) {
|
||||||
|
this.destroyVideo(this.shopVideo); // same — the take-off clip owns the screen
|
||||||
|
this.shopVideo = null;
|
||||||
|
}
|
||||||
|
this.shopMode = false;
|
||||||
|
|
||||||
const v = this.add.video(0, 0, this.takeoffKey).setOrigin(0.5).setDepth(20);
|
const v = this.add.video(0, 0, this.takeoffKey).setOrigin(0.5).setDepth(20);
|
||||||
this.attachClip(v);
|
this.attachClip(v);
|
||||||
|
|
@ -569,9 +724,16 @@ export class SurfaceScene extends Phaser.Scene {
|
||||||
this.destroyVideo(this.landVideo);
|
this.destroyVideo(this.landVideo);
|
||||||
this.destroyVideo(this.surfaceVideo);
|
this.destroyVideo(this.surfaceVideo);
|
||||||
this.destroyVideo(this.takeoffVideo);
|
this.destroyVideo(this.takeoffVideo);
|
||||||
|
this.destroyVideo(this.shopVideo);
|
||||||
this.landVideo = null;
|
this.landVideo = null;
|
||||||
this.surfaceVideo = null;
|
this.surfaceVideo = null;
|
||||||
this.takeoffVideo = null;
|
this.takeoffVideo = null;
|
||||||
|
this.shopVideo = null;
|
||||||
|
this.shopMode = false;
|
||||||
|
if (Array.isArray(this.noteG)) {
|
||||||
|
for (const g of this.noteG) g.destroy();
|
||||||
|
this.noteG = null;
|
||||||
|
}
|
||||||
this.destroyHud();
|
this.destroyHud();
|
||||||
this.actionBar?.destroy();
|
this.actionBar?.destroy();
|
||||||
this.menuSubBar?.destroy();
|
this.menuSubBar?.destroy();
|
||||||
|
|
|
||||||
|
|
@ -238,6 +238,7 @@ export class ActionBar extends Phaser.GameObjects.Container {
|
||||||
id: live ? b.id : null,
|
id: live ? b.id : null,
|
||||||
live,
|
live,
|
||||||
slot,
|
slot,
|
||||||
|
fontSize, // setLabel's shrink-to-fit needs the slot's base size
|
||||||
accent: toColor(b.accent ?? themeColor('neon', 0x00e5ff)),
|
accent: toColor(b.accent ?? themeColor('neon', 0x00e5ff)),
|
||||||
hoverOn: false,
|
hoverOn: false,
|
||||||
pressing: false,
|
pressing: false,
|
||||||
|
|
@ -458,6 +459,29 @@ export class ActionBar extends Phaser.GameObjects.Container {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Relabel a live slot in place (main text + both RGB ghosts) — e.g. the
|
||||||
|
* surface deck's SHOP slot reads LEAVE SHOP while the shop clip is up
|
||||||
|
* (`setLabel('shop', 'Leave Shop')`). The slot's id — and therefore the
|
||||||
|
* onAction press — is unchanged, so the scene's toggle keeps working.
|
||||||
|
* A label too long for the button face shrinks to fit. Returns the
|
||||||
|
* slot, or null if the id has no live slot.
|
||||||
|
*/
|
||||||
|
setLabel(id, text) {
|
||||||
|
const s = (this.slots ?? []).find((sl) => sl.id === id);
|
||||||
|
if (!s || !s.label) return null;
|
||||||
|
const t = String(text ?? '').toUpperCase();
|
||||||
|
for (const o of [s.label, s.ghostCyan, s.ghostMagenta]) o?.setText(t);
|
||||||
|
const max = this.style.bw - 18;
|
||||||
|
if (s.label.width > max) {
|
||||||
|
const k = Math.max(10, (s.fontSize * max) / s.label.width);
|
||||||
|
for (const o of [s.label, s.ghostCyan, s.ghostMagenta]) o?.setFontSize(`${k}px`);
|
||||||
|
}
|
||||||
|
// Re-assert the current visual state (hover keeps the white label).
|
||||||
|
this.paintSlot(s, s.hoverOn ? 'hover' : 'base');
|
||||||
|
return s;
|
||||||
|
}
|
||||||
|
|
||||||
// ------------------------------------------------------------------
|
// ------------------------------------------------------------------
|
||||||
// Boot
|
// Boot
|
||||||
// ------------------------------------------------------------------
|
// ------------------------------------------------------------------
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue