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>
50 lines
2.0 KiB
TypeScript
50 lines
2.0 KiB
TypeScript
import type { PropertyBag, Vec2 } from '../core/model/Properties';
|
||
import { cameraFrame, rectAround, type WorldRect } from '../core/properties/camera';
|
||
|
||
/**
|
||
* The world point (`x`, `y`) sits at the centre of the screen rectangle `rect` (viewport-local
|
||
* pixels) at `scale` pixels per world unit.
|
||
*/
|
||
export interface View {
|
||
x: number;
|
||
y: number;
|
||
scale: number;
|
||
rect: { x: number; y: number; w: number; h: number };
|
||
}
|
||
|
||
/** Free-panning view: the whole viewport is the rectangle. */
|
||
export function freeView(x: number, y: number, zoom: number, w: number, h: number): View {
|
||
return { x, y, scale: zoom, rect: { x: 0, y: 0, w, h } };
|
||
}
|
||
|
||
/** The camera's frame fitted into `w`×`h` and centred; the remaining margin is letterbox. */
|
||
export function cameraView(camera: { x: number; y: number; props: PropertyBag }, w: number, h: number): View {
|
||
const frame = cameraFrame(camera.props);
|
||
const scale = Math.min(w / frame.w, h / frame.h);
|
||
const fw = frame.w * scale;
|
||
const fh = frame.h * scale;
|
||
return { x: camera.x, y: camera.y, scale, rect: { x: (w - fw) / 2, y: (h - fh) / 2, w: fw, h: fh } };
|
||
}
|
||
|
||
export function screenToWorld(view: View, sx: number, sy: number): Vec2 {
|
||
const cx = view.rect.x + view.rect.w / 2;
|
||
const cy = view.rect.y + view.rect.h / 2;
|
||
return { x: (sx - cx) / view.scale + view.x, y: (sy - cy) / view.scale + view.y };
|
||
}
|
||
|
||
export function worldToScreen(view: View, wx: number, wy: number): Vec2 {
|
||
const cx = view.rect.x + view.rect.w / 2;
|
||
const cy = view.rect.y + view.rect.h / 2;
|
||
return { x: (wx - view.x) * view.scale + cx, y: (wy - view.y) * view.scale + cy };
|
||
}
|
||
|
||
export function worldBounds(view: View): WorldRect {
|
||
return rectAround(view.x, view.y, view.rect.w / view.scale, view.rect.h / view.scale);
|
||
}
|
||
|
||
/** Whether a viewport-local point lies on the drawn rectangle rather than in the letterbox. */
|
||
export function isInside(view: View, sx: number, sy: number): boolean {
|
||
const { x, y, w, h } = view.rect;
|
||
return sx >= x && sx <= x + w && sy >= y && sy <= y + h;
|
||
}
|