import { Graphics } from 'pixi.js'; import { Button, drawHeaderBar, HStack, Label, Theme, type Widget } from '../../ui'; /** Past this a crumb cuts its name short, so a deep trail of long names still fits the tab. */ const CRUMB_MAX_WIDTH = 160; /** "Game › Move › Other": every crumb but the last one leads back; empty hides the bar. */ export class Breadcrumb extends HStack { private readonly bg = new Graphics(); private crumbs: Widget[] = []; onPick: (index: number) => void = () => undefined; constructor() { super({ gap: 2, padding: 3, align: 'center' }); this.addChild(this.bg); this.visible = false; } /** Returns whether the bar changed, so the caller relayouts only then. */ show(names: readonly string[]): boolean { if (names.length === this.crumbs.length && names.every((n, i) => crumbText(this.crumbs[i]!) === n)) return false; for (const widget of this.items) widget.destroy({ children: true }); this.crumbs = names.map((name, i) => { if (i > 0) this.add(new Label('›', { style: 'muted' })); const last = i === names.length - 1; return this.add( capped((width) => last ? new Label(name, { width, padding: Theme.padding }) : new Button(name, { variant: 'ghost', width, onClick: () => this.onPick(i) }), ), ); }); this.visible = names.length > 0; // The bar keeps its size across a rebuild, and `resize` lays out only on a new size. this.relayout(); return true; } /** For the check scripts: what each crumb says, where it sits, and whether it leads anywhere. */ crumbsDrawn(): { name: string; x: number; y: number; left: number; right: number; clickable: boolean }[] { if (!this.visible) return []; return this.crumbs.map((widget) => { const at = widget.getGlobalPosition(); return { name: crumbText(widget), x: at.x + widget.w / 2, y: at.y + widget.h / 2, left: at.x, right: at.x + widget.w, clickable: widget instanceof Button, }; }); } override redraw(): void { super.redraw(); drawHeaderBar(this.bg, this.w, this.h); } } function crumbText(widget: Widget): string { return widget instanceof Button || widget instanceof Label ? widget.text : ''; } function capped(build: (width?: number) => Widget): Widget { const natural = build(); if (natural.measure().w <= CRUMB_MAX_WIDTH) return natural; natural.destroy({ children: true }); return build(CRUMB_MAX_WIDTH); }