Trim needless comments and fix the picker width

Remove comments that only restate the code, keeping the section banners
and the notes that carry a reason, a measured fact or an external
contract.

ButtonOptions.width claimed to be a fixed width but only set minWidth,
so the Graph and Texture pickers grew with the name they showed. It is
now what it promises, with a separate minWidth for the buttons that have
two forms. Narrow buttons shrink their padding, otherwise a 24px icon
button had 8px left for its glyph and truncate dropped it entirely.

__gm.labelsDropped() guards that class: the overflow guard only saw text
that was too wide, never a label truncated to nothing.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
rootandClaude Opus 5 committed 2026-09-08 15:52:05 +02:00
1 parent f16801aef3
commit 4d84231b9f
115 files changed
+707 -1564

No files matched your search

+23 -23
View File
@@ -6,29 +6,26 @@ import { type Size, Widget } from '../Widget';
import { truncate } from './Label';
export interface ButtonOptions {
/** Visual weight: 'primary' uses the accent color, 'ghost' has no background until hovered. */
variant?: 'default' | 'primary' | 'ghost' | 'danger';
/** Fixed width; otherwise measured from the label. */
/** Fixed width; otherwise measured from the label, which is truncated when too long. */
width?: number;
/**
* Label position in a button wider than its text. 'center' suits standalone buttons,
* 'left' the ones that read as a list or a field (menu entries, dropdowns).
*/
/** Floor only – for a button whose label changes between forms and must grow with them. */
minWidth?: number;
align?: 'center' | 'left';
onClick?: () => void;
}
/** Push button; Enter/Space activate it while focused. Clicks fire only for the primary pointer button. */
export class Button extends Widget {
private readonly bg = new Graphics();
private readonly labelText: Text;
/** The label as set. `labelText` holds the possibly shortened version that is on screen. */
/** The label as set; `labelText` holds the possibly truncated version on screen. */
private fullText: string;
private pressed = false;
/** Caller-owned toggle state: renders like a primary button but is not flipped by clicks. */
/** Caller-owned: renders like a primary button but is never flipped by a click. */
toggled = false;
variant: NonNullable<ButtonOptions['variant']>;
protected readonly align: NonNullable<ButtonOptions['align']>;
protected readonly fixedWidth: number;
onClick?: () => void;
constructor(text: string, options: ButtonOptions = {}) {
@@ -39,7 +36,8 @@ export class Button extends Widget {
this.focusable = true;
this.cursor = 'pointer';
this.minHeight = Theme.controlHeight;
if (options.width) this.minWidth = options.width;
this.fixedWidth = options.width ?? 0;
this.minWidth = this.fixedWidth || (options.minWidth ?? 0);
this.fullText = text;
this.labelText = new Text({ text, style: textStyle(this.variant === 'primary' ? 'accent' : 'body') });
this.addChild(this.bg, this.labelText);
@@ -65,11 +63,15 @@ export class Button extends Widget {
});
}
/** The full label, not the truncated version on screen. */
get text(): string {
return this.fullText;
}
/** What is drawn – shorter than `text` when the button is too narrow to hold it. */
get shownText(): string {
return this.labelText.text;
}
/** Repaints only; the parent must relayout when the measured width matters. */
setText(text: string): void {
if (text === this.fullText) return;
@@ -83,27 +85,25 @@ export class Button extends Widget {
this.invalidate();
}
/** Whether the label sits on a filled accent surface and therefore needs the light style. */
protected get onAccent(): boolean {
return this.toggled || this.variant === 'primary' || this.variant === 'danger';
}
override measure(): Size {
// Measured from the full label, never from what is currently on screen: measuring the
// truncated text would let a button that was once too narrow stay too narrow for good.
const h = Math.max(this.minHeight, Theme.controlHeight);
// A given width is a width, not a floor: a picker must not resize around the name it shows.
if (this.fixedWidth) return { w: this.fixedWidth, h };
// From the full label, never the on-screen one: measuring truncated text would keep a button
// that was once too narrow too narrow for good.
const metrics = CanvasTextMetrics.measureText(this.fullText, this.labelText.style);
return {
w: Math.max(this.minWidth, Math.ceil(metrics.width) + Theme.padding * 2),
h: Math.max(this.minHeight, Theme.controlHeight),
};
return { w: Math.max(this.minWidth, Math.ceil(metrics.width) + Theme.padding * 2), h };
}
/**
* Width the label may occupy. Subclasses that draw something else inside the button take
* their room off here, so the text is shortened instead of running underneath it.
*/
/** Width the label may occupy; subclasses that draw inside the button take their room off here. */
protected labelSlot(): number {
return this.w - Theme.padding * 2;
// The padding is an inset, not a claim: on a 24 px icon button it would leave 8 px and
// truncate the glyph away entirely. From 48 px up this is the full padding again.
return this.w - Math.min(Theme.padding, Math.floor(this.w / 6)) * 2;
}
override onKeyDown(event: KeyboardEvent): boolean {