Files
feuerwerk/src/editor/DocumentTab.ts
T

81 lines
2.8 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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;
}
}