Add a decimals option, fix dropdown overflow and the texture filter
- NumberField takes a decimals option (default 3); frame rects and the slice dialog use 0, matching their integer model. - Button truncates its label to the drawn width; Dropdown reserves the chevron slot. NodeView reuses the shared truncate(). - Setting scaleMode leaves the style's shared resource id in place, so the old GPU sampler stayed in use until a reload. Call style.update(). - Graph gets a header bar like the Texture tab: GraphEditor is now the frame, the former editor is GraphCanvas. A selection the tab does not concern no longer unloads the behavior. - New e2e step 14 guards text overflow across all widgets. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
1 parent
35a1a23af8
commit
f16801aef3
22 files changed
+874
-493
No files matched your search
@@ -3,6 +3,7 @@ import { drawControlBox } from '../draw';
|
||||
import { centreY } from '../math';
|
||||
import { Theme, textStyle } from '../Theme';
|
||||
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. */
|
||||
@@ -21,6 +22,8 @@ export interface ButtonOptions {
|
||||
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. */
|
||||
private fullText: string;
|
||||
private pressed = false;
|
||||
/** Caller-owned toggle state: renders like a primary button but is not flipped by clicks. */
|
||||
toggled = false;
|
||||
@@ -37,6 +40,7 @@ export class Button extends Widget {
|
||||
this.cursor = 'pointer';
|
||||
this.minHeight = Theme.controlHeight;
|
||||
if (options.width) this.minWidth = options.width;
|
||||
this.fullText = text;
|
||||
this.labelText = new Text({ text, style: textStyle(this.variant === 'primary' ? 'accent' : 'body') });
|
||||
this.addChild(this.bg, this.labelText);
|
||||
|
||||
@@ -61,13 +65,15 @@ export class Button extends Widget {
|
||||
});
|
||||
}
|
||||
|
||||
/** The full label, not the truncated version on screen. */
|
||||
get text(): string {
|
||||
return this.labelText.text;
|
||||
return this.fullText;
|
||||
}
|
||||
|
||||
/** Repaints only; the parent must relayout when the measured width matters. */
|
||||
setText(text: string): void {
|
||||
this.labelText.text = text;
|
||||
if (text === this.fullText) return;
|
||||
this.fullText = text;
|
||||
this.invalidate();
|
||||
}
|
||||
|
||||
@@ -83,13 +89,23 @@ export class Button extends Widget {
|
||||
}
|
||||
|
||||
override measure(): Size {
|
||||
const metrics = CanvasTextMetrics.measureText(this.labelText.text, this.labelText.style);
|
||||
// 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 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),
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* 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.
|
||||
*/
|
||||
protected labelSlot(): number {
|
||||
return this.w - Theme.padding * 2;
|
||||
}
|
||||
|
||||
override onKeyDown(event: KeyboardEvent): boolean {
|
||||
if (event.key === 'Enter' || event.key === ' ') {
|
||||
this.onClick?.();
|
||||
@@ -131,8 +147,10 @@ export class Button extends Widget {
|
||||
} else if (stroke !== null) {
|
||||
this.bg.roundRect(0.5, 0.5, this.w - 1, this.h - 1, Theme.radius).stroke({ width: 1, color: stroke });
|
||||
}
|
||||
// Style first: it decides the glyph widths that the truncation then measures against.
|
||||
this.labelText.style = textStyle(this.onAccent ? 'accent' : 'body');
|
||||
const label = this.labelText;
|
||||
label.text = truncate(this.fullText, label.style, this.labelSlot());
|
||||
const x = this.align === 'left' ? Theme.padding : (this.w - label.width) / 2;
|
||||
label.position.set(Math.round(x), centreY(this.h, label.height));
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user