import { CanvasTextMetrics, type FederatedPointerEvent, Rectangle, Text } from 'pixi.js'; import { centreY, Theme, textStyle } from '../ui'; import { TabButton } from './TabButton'; const GAP = 4; const CLOSE = '×'; /** The × is small; the hit around it is not, or closing a tab would take aim. */ const CLOSE_SLOP = 4; /** A long name is cut here rather than pushing every other tab behind the `»` button. */ const MAX_NAME_WIDTH = 160; /** * A tab that stands for an open document (#171): the icon of its kind before the name, so three * assets called "Player" can be told apart, and an × that closes it – as does a middle click. */ export class DocumentTab extends TabButton { private readonly closeText: Text; constructor( label: string, private readonly kind: string, icon: string, onPick: () => void, private readonly onClose: () => void, ) { super(label, { align: 'left', icon, onClick: onPick }); this.closeText = new Text({ text: CLOSE, style: textStyle('muted') }); this.closeText.eventMode = 'static'; this.closeText.cursor = Theme.hoverCursor; // Stopped on the way down as well, or the tab under it would count the press as its own click. this.closeText.on('pointerdown', (e: FederatedPointerEvent) => e.stopPropagation()); this.closeText.on('pointerup', (e: FederatedPointerEvent) => { if (e.button !== 0) return; e.stopPropagation(); this.onClose(); }); this.on('pointerup', (e: FederatedPointerEvent) => { if (e.button === 1) this.onClose(); }); this.addChild(this.closeText); } /** The × in global coordinates, for the check scripts. */ closePoint(): { x: number; y: number } { const at = this.closeText.getGlobalPosition(); return { x: at.x + this.closeText.width / 2, y: at.y + this.closeText.height / 2 }; } /** The kind the icon stands for, as a word – the icon itself says nothing to a check script. */ get kindLabel(): string { return this.kind; } override measure(): { w: number; h: number } { const own = super.measure(); const cap = MAX_NAME_WIDTH + this.padding * 2 + this.iconSpace(); return { w: Math.min(own.w, cap) + this.closeWidth(), h: own.h }; } protected override labelSlot(): number { return super.labelSlot() - this.closeWidth(); } override redraw(): void { super.redraw(); this.closeText.style = textStyle(this.toggled ? 'body' : 'muted'); const closeX = this.w - this.padding - this.closeText.width; this.closeText.position.set(closeX, centreY(this.h, this.closeText.height)); this.closeText.hitArea = new Rectangle( -CLOSE_SLOP, -CLOSE_SLOP, this.closeText.width + CLOSE_SLOP * 2, this.closeText.height + CLOSE_SLOP * 2, ); } private closeWidth(): number { return Math.ceil(CanvasTextMetrics.measureText(CLOSE, textStyle('muted')).width) + GAP; } }