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

+13 -41
View File
@@ -43,7 +43,6 @@ const MIN_LABEL_SIZE = 28;
const LABEL_INSET = 3;
const LABEL_PAD = 2;
/** A drag of an existing frame: which one, from what, and by which handle. */
interface FrameDrag {
frameId: string;
handle: FrameHandle;
@@ -52,7 +51,7 @@ interface FrameDrag {
moved: boolean;
}
/** A new frame being spanned on empty space (image pixels; nothing is in the model yet). */
/** A new frame being spanned, in image pixels; nothing is in the model yet. */
interface NewFrame {
start: Vec2;
current: Vec2;
@@ -60,15 +59,8 @@ interface NewFrame {
}
/**
* The Texture tab's canvas: one texture's picture with its frames drawn on top, where frames
* are created by dragging on empty space and moved or resized by their handles.
*
* Everything but the picture is drawn in widget-local pixels rather than in a zoomed
* container: outlines, handles and names have to keep a constant size on screen, which a
* scaled container would defeat. The picture itself is a Sprite whose scale is the zoom, so
* the GPU does the sampling – and `scaleMode` follows the texture's own Filter property, so a
* pixel-art sheet is inspected as crisp pixels rather than as the mush a smoothed preview
* would show.
* The Texture tab's canvas. Everything but the picture is drawn in widget-local pixels rather
* than in a zoomed container: outlines, handles and names must keep a constant size on screen.
*/
export class TextureCanvas extends Widget {
private readonly bg = new Graphics();
@@ -127,7 +119,7 @@ export class TextureCanvas extends Widget {
return { w: Math.max(this.minWidth, 200), h: Math.max(this.minHeight, 200) };
}
/** Which texture is shown. Switching resets the view, since the next picture is a new subject. */
/** Switching resets the view, since the next picture is a new subject. */
show(assetId: string): void {
if (this.assetId === assetId) return;
this.assetId = assetId;
@@ -139,17 +131,12 @@ export class TextureCanvas extends Widget {
return this.assetId;
}
/** Refits the picture into the widget – the "reset view" the toolbar offers. */
fit(): void {
this.needsFit = true;
this.redraw();
}
/**
* Where an image pixel currently sits on screen. Only the check scripts need this: dragging
* a frame is done in image coordinates, so a test that cannot convert them can drag
* somewhere but cannot aim at a known part of the picture.
*/
/** Where an image pixel sits on screen; only the check scripts need it, to aim drags. */
imageToScreen(x: number, y: number): Vec2 {
const local = imageToLocal(this.view, x, y);
const origin = this.getGlobalPosition();
@@ -176,9 +163,8 @@ export class TextureCanvas extends Widget {
private onPointerDown(e: FederatedPointerEvent): void {
const asset = this.asset();
// Nothing is cut from a picture whose size is unknown: `normalizeFrameRect` has no bounds
// to hold such a frame inside, so it would keep a rectangle that lies off the sheet for
// good. An unmeasured texture is a failed upload, and the hint says so.
// Without a known picture size `normalizeFrameRect` has no bounds, and would keep a frame
// that lies off the sheet.
if (!asset || asset.width <= 0 || asset.height <= 0) return;
const local = this.toLocal(e.global);
if (e.button === 1 || (e.button === 0 && e.shiftKey)) {
@@ -191,8 +177,7 @@ export class TextureCanvas extends Widget {
const point = localToImage(this.view, local.x, local.y);
const tolerance = HANDLE_SIZE / this.view.zoom;
// The selected frame is tested first and by its handles: it is the one being worked on,
// and its handles must stay reachable even where a frame drawn later overlaps them.
// The selected frame is tested first, so its handles stay reachable under a frame drawn later.
const selected = this.frame(this.selectedFrameId());
const onSelected = selected ? handleAt(selected, point, tolerance) : null;
if (selected && onSelected) {
@@ -309,7 +294,6 @@ export class TextureCanvas extends Widget {
e.preventDefault();
}
/** Announces what a press would do before it happens (resize arrows, move, crosshair). */
private updateCursor(local: Vec2): void {
const asset = this.asset();
if (!asset) {
@@ -345,7 +329,6 @@ export class TextureCanvas extends Widget {
if (asset && texture) {
this.image.texture = texture;
this.image.position.set(this.view.panX, this.view.panY);
// The sprite is drawn at the picture's own pixel size, so the zoom is the whole scale.
this.image.width = asset.width * this.view.zoom;
this.image.height = asset.height * this.view.zoom;
}
@@ -365,11 +348,7 @@ export class TextureCanvas extends Widget {
});
}
/**
* The checkerboard behind the picture. It is clipped to the picture rather than filling the
* widget: its job is to make transparent pixels of *this image* visible, and a board that
* ran past the edges would hide where the image actually ends.
*/
/** Clipped to the picture rather than filling the widget, which would hide where it ends. */
private drawChecker(asset: TextureAsset | null, hasPicture: boolean): void {
this.checker.clear();
if (!asset || !hasPicture || asset.width <= 0) return;
@@ -377,8 +356,8 @@ export class TextureCanvas extends Widget {
const w = asset.width * this.view.zoom;
const h = asset.height * this.view.zoom;
this.checker.rect(origin.x, origin.y, w, h).fill(CHECKER_DARK);
// Only the squares that land inside the widget are worth emitting; a 4000 px sheet at a
// high zoom would otherwise cost tens of thousands of rectangles per redraw.
// Only the squares inside the widget: a 4000 px sheet zoomed in would otherwise cost tens of
// thousands of rectangles per redraw.
const from = (start: number, limit: number) => Math.max(0, Math.floor((limit - start) / CHECKER_SIZE));
const x0 = from(origin.x, 0);
const y0 = from(origin.y, 0);
@@ -467,15 +446,8 @@ export class TextureCanvas extends Widget {
const x = at.x + LABEL_INSET;
const y = at.y + LABEL_INSET;
label.position.set(x + LABEL_PAD, y + LABEL_PAD);
/*
* A dark plate behind the name, not just a colour choice for the text.
*
* The name is drawn *on the user's picture*, which may be any colour – the grey used at
* first vanished on the light quadrants of a test sheet, and no fixed colour can be safe on
* an arbitrary image. The plate makes the background known, so light text on it always
* reads. Same reasoning as the scene grid, which is drawn white at a low alpha rather than
* in a theme colour so that it survives whatever the scene puts behind it.
*/
// A dark plate, not just a text colour: the name is drawn on the user's picture, where no
// fixed colour is legible against every background.
this.overlay
.roundRect(x, y, label.width + LABEL_PAD * 2, label.height + LABEL_PAD * 2, 2)
.fill({ color: 0x000000, alpha: selected ? 0.75 : 0.55 });