Fix SET DESTINATION button overflow in shared ConfirmOverlay

- Add per-show width/height overrides so the SYSTEM tab's longer
  "SET DESTINATION" label gets a wider dialog than AUTOPILOT's ENGAGE
- Grow each button panel to fit its label (_fitBtn) and re-flow title,
  body and buttons into the bottom band (_layout) instead of using fixed
  positions that let long text run past the panel edge
- Record baseWidth so a grown button never stays wide for the next dialog
- Wire map.json systemConfirm/confirm size config through MapWindow
- Add ss-takeoff-02.mp4 asset
This commit is contained in:
Brian Fertig 2026-09-07 19:23:18 -06:00
parent efe17f3d2e
commit 6609df2736
4 changed files with 74 additions and 2 deletions

Binary file not shown.

View File

@ -281,8 +281,10 @@
"label": "ENGAGE"
},
"systemConfirm": {
"_comment": "The SYSTEM tab's SET DESTINATION dialog (same shared ConfirmOverlay — the clicked object on a CHARTED star's chart; the ship isn't in that system, so no distance row / autopilot). The destination model is still being defined — CONFIRM acknowledges the pick through onSetDestination (systemId + objectId), CANCEL / scrim / ESC leave the map as it was.",
"_comment": "The SYSTEM tab's SET DESTINATION dialog (same shared ConfirmOverlay — the clicked object on a CHARTED star's chart; the ship isn't in that system, so no distance row / autopilot). The destination model is still being defined — CONFIRM acknowledges the pick through onSetDestination (systemId + objectId), CANCEL / scrim / ESC leave the map as it was. width/height: wider than the AUTOPILOT dialog — the SET DESTINATION button is a long label, so the window gives the button room and the button grows to fit its text (ConfirmOverlay.show sizes it per label).",
"title": "SET DESTINATION",
"label": "SET DESTINATION"
"label": "SET DESTINATION",
"width": 520,
"height": 176
}
}

View File

@ -120,6 +120,11 @@ export class ConfirmOverlay extends Phaser.GameObjects.Container {
);
this.confirmBtn.setAlpha(0);
this.cancelBtn.setAlpha(0);
// the natural widths (show() grows a button only as wide as its label
// needs — and never leaves it grown for the NEXT dialog, which may
// have a shorter label)
this.confirmBtn.style.baseWidth = this.confirmBtn.style.width;
this.cancelBtn.style.baseWidth = this.cancelBtn.style.width;
this.add([this.cancelBtn, this.confirmBtn]);
// Hidden dialog = inert buttons (same v4 `input.enabled` rule as the
// scrim — otherwise the invisible CONFIRM/CANCEL swallow centre-
@ -130,7 +135,49 @@ export class ConfirmOverlay extends Phaser.GameObjects.Container {
this._titleDec = null;
this._btnsUp = false;
this._confirmUp = false;
this._layout(); // size/label land in show(); positions derive from w/h
}
/**
* Place the title, body lines and the two buttons for the CURRENT
* w/h + button sizes: the buttons sit in the panel's bottom band
* (CANCEL left, CONFIRM right each kept inside the panel edge with
* a 20px margin, whatever its label grows it to).
*/
_layout() {
this.title.setPosition(0, -this.h / 2 + 24);
this.bodyTexts.forEach((t, i) => t.setPosition(0, -this.h / 2 + 52 + i * 17));
const margin = 20;
const cw = this.cancelBtn.style.width;
const bw = this.confirmBtn.style.width;
const bh = this.confirmBtn.style.height;
const cy = this.h / 2 - margin - bh / 2;
this.cancelBtn.setPosition(-this.w / 2 + margin + cw / 2, cy);
this.confirmBtn.setPosition(this.w / 2 - margin - bw / 2, cy);
}
/**
* Grow a button's panel to FIT its label (the SET DESTINATION label is
* wider than the fixed 148px the CONFIRM button was built for the
* text used to run past the panel edge). Re-measures with the button's
* own text style, then repaints + resets the hit area.
*/
_fitBtn(btn, label, minWidth) {
const s = btn.style;
const m = this.scene.add.text(0, 0, String(label), s.textStyle);
const w = Math.max(s.baseWidth ?? s.width, minWidth, Math.ceil(m.width) + 28);
m.destroy();
if (w === s.width) return;
s.width = w;
btn.setSize(w, s.height);
btn.panel.setInteractive({
useHandCursor: true,
hitArea: new Phaser.Geom.Rectangle(-w / 2, -s.height / 2, w, s.height),
hitAreaCallback: (p, px, py) => Phaser.Geom.Rectangle.Contains(p, px, py),
});
btn.paint('base');
}
// ------------------------------------------------------------------
// Showing
// ------------------------------------------------------------------
@ -139,6 +186,9 @@ export class ConfirmOverlay extends Phaser.GameObjects.Container {
* spec: {
* title, body: string[], accent?: number,
* confirmLabel?, cancelLabel?,
* width?, height? override the dialog's size for THIS show (the
* SYSTEM tab's SET DESTINATION is wider than AUTOPILOT's ENGAGE
* its confirm label is longer); the layout re-flows for it,
* onConfirm?, onCancel? fired after the close animation lands
* time the engine time (for the open animation)
* }
@ -148,6 +198,11 @@ export class ConfirmOverlay extends Phaser.GameObjects.Container {
this.onConfirm = typeof spec.onConfirm === 'function' ? spec.onConfirm : null;
this.onCancel = typeof spec.onCancel === 'function' ? spec.onCancel : null;
// Per-show size (the SET DESTINATION dialog is wider than ENGAGE).
if (Number(spec.width) > 0) this.w = Number(spec.width);
if (Number(spec.height) > 0) this.h = Number(spec.height);
this.setSize(this.w, this.h);
const setBtn = (btn, label) => {
const s = String(label ?? '').toUpperCase();
btn.labelText.setText(s);
@ -156,6 +211,12 @@ export class ConfirmOverlay extends Phaser.GameObjects.Container {
};
setBtn(this.confirmBtn, spec.confirmLabel ?? 'CONFIRM');
setBtn(this.cancelBtn, spec.cancelLabel ?? 'CANCEL');
// The confirm button must FIT its label (the longest one is
// SET DESTINATION) — grow the panel, then place both in the bottom
// band, inside the panel edges.
this._fitBtn(this.confirmBtn, String(spec.confirmLabel ?? 'CONFIRM').toUpperCase(), 110);
this._fitBtn(this.cancelBtn, String(spec.cancelLabel ?? 'CANCEL').toUpperCase(), 88);
this._layout();
// Accent the confirm button's edge + hover glow.
this.confirmBtn.style.stroke = this.accent;
this.confirmBtn.style.neon = this.accent;

View File

@ -2530,6 +2530,11 @@ export class MapWindow extends Phaser.GameObjects.Container {
body: [`DESTINATION — ${label}`, sysName ? `SYSTEM — ${sysName}` : ''].filter(Boolean),
accent: C.neon,
confirmLabel: config.get('map.systemConfirm.label', 'SET DESTINATION'),
// wider than AUTOPILOT's dialog — the SET DESTINATION label is
// long, and it gets the room (button fits the text, text fits
// the panel)
width: config.get('map.systemConfirm.width', 520),
height: config.get('map.systemConfirm.height', 176),
onConfirm: () => {
// the pick ripples once while the window fades out
this._flash = { x: o.x + this.geo.mapX, y: o.y + this.geo.mapY, r: o.r, t0: this.scene.time.now };
@ -2548,6 +2553,10 @@ export class MapWindow extends Phaser.GameObjects.Container {
body,
accent: C.amber,
confirmLabel: config.get('map.confirm.label', 'ENGAGE'),
// its own (narrower) size — the dialog instance is shared with the
// wider SET DESTINATION one, so each call states its own footprint
width: config.get('map.confirm.width', 440),
height: config.get('map.confirm.height', 168),
onConfirm: () => {
// the pick ripples once while the window fades out
this._flash = { x: o.x + this.geo.mapX, y: o.y + this.geo.mapY, r: o.r, t0: this.scene.time.now };