Snap is a magnet button in every graph and vector art tab (#194)
The snap toggle was a checkbox with the word "Snap" next to Fit, and the vector art tab had its own "Grid" button for the same idea. Both now share one square icon button, so snapping looks and works the same wherever it exists. - SnapToggle is a square toggle button with a magnet icon and no text. It lights up blue when snapping is on, like the other toggle buttons. - The magnet is drawn with cubic curves instead of arcs, because the Pixi SVG parser draws arcs as jagged edges. - The vector art tab drops its Grid button and uses the same SnapToggle. It still snaps to 8 px, the graph tabs to 24 px. - SnapToggle accepts anything with setSnapping(on). The vector art canvas method is renamed from setSnap to setSnapping for that. - Every tab exposes the button as __gm.<tab>.snapButton(). Modules 06, 26 and 36 click through that hook instead of the label "Snap" plus 30 px. Tests: - ./check.sh: 129 files, 1903 tests (was 128 / 1897) - 3 new tests in tests/editor/snap-toggle.test.ts, 3 in the icon tests - GM_STEPS=1-6,26,36,47 fresh: ALL PASSED, 346 checks - GM_STEPS=47: ALL PASSED, 32 checks (was 29) - Counter-checks: toggle not switching its target fails 2 unit tests, the canvas ignoring the switch fails module 47 - Check total 1188 in README and wiki is computed (1184 + 4), the full run is left to CI Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
1 parent
ad62a0f525
commit
90627ccf47
17 files changed
+181
-43
No files matched your search
@@ -33,6 +33,7 @@ import { currentSoundPicker } from './sound/SoundPicker';
|
||||
import { currentImagePicker } from './texture/ImagePicker';
|
||||
import { currentTilemapWidgets } from './panels/TilemapPanel';
|
||||
import { currentShapeList } from './inspector/shapeSection';
|
||||
import type { SnapToggle } from './graph/SnapToggle';
|
||||
import type { EditorState, TilemapTool } from './state';
|
||||
import { rectOf, textProbe } from './textProbe';
|
||||
|
||||
@@ -56,6 +57,13 @@ export function installDebugHook(app: Application, state: EditorState, editor: E
|
||||
const p = widget.getGlobalPosition();
|
||||
return { x: p.x + widget.w / 2, y: p.y + widget.h / 2 };
|
||||
};
|
||||
/** A tab's magnet (#194): where to click it, how big it is drawn, and whether it is on. */
|
||||
const snapButton = (snap: SnapToggle) => ({
|
||||
...centreOf(snap.button),
|
||||
w: snap.button.w,
|
||||
h: snap.button.h,
|
||||
toggled: snap.button.toggled,
|
||||
});
|
||||
const { eachText, locate, locateAll } = textProbe(app);
|
||||
/** Fill colour of a visible text, as `#rrggbb` – what tells an override label from a plain one. */
|
||||
const textColor = (text: string): string | null => {
|
||||
@@ -338,6 +346,7 @@ export function installDebugHook(app: Application, state: EditorState, editor: E
|
||||
// drawing area.
|
||||
center: () => centreOf(graphEditor.canvas),
|
||||
snapping: () => graphEditor.canvas.snapEnabled,
|
||||
snapButton: () => snapButton(graphEditor.snap),
|
||||
zoom: () => graphEditor.canvas.zoomLevel,
|
||||
crumbs: () => graphEditor.breadcrumb.crumbsDrawn(),
|
||||
},
|
||||
@@ -349,6 +358,7 @@ export function installDebugHook(app: Application, state: EditorState, editor: E
|
||||
canvasRect: () => editor.stateMachineEditor.canvas.canvasRect(),
|
||||
center: () => centreOf(editor.stateMachineEditor.canvas),
|
||||
snapping: () => editor.stateMachineEditor.canvas.snapEnabled,
|
||||
snapButton: () => snapButton(editor.stateMachineEditor.snap),
|
||||
/** Where a state's hidden pin sits; it cannot be grabbed – `portGrabbable` is the check. */
|
||||
portAt: (nodeId: string, port: string, isOutput: boolean) =>
|
||||
editor.stateMachineEditor.canvas.portGlobal(nodeId, port, isOutput),
|
||||
@@ -372,6 +382,7 @@ export function installDebugHook(app: Application, state: EditorState, editor: E
|
||||
particles: {
|
||||
current: () => editor.particlesEditor.currentAssetId,
|
||||
info: () => editor.particlesEditor.shownInfo,
|
||||
snapButton: () => snapButton(editor.particlesEditor.snap),
|
||||
nodeAt: (nodeId: string) => editor.particlesEditor.canvas.nodeGlobal(nodeId),
|
||||
nodeLabel: (nodeId: string) => editor.particlesEditor.canvas.nodeLabel(nodeId),
|
||||
nodeRect: (nodeId: string) => editor.particlesEditor.canvas.nodeRect(nodeId),
|
||||
@@ -405,6 +416,7 @@ export function installDebugHook(app: Application, state: EditorState, editor: E
|
||||
emptySpot: () => editor.shaderEditor.canvas.emptySpotGlobal(),
|
||||
center: () => centreOf(editor.shaderEditor.canvas),
|
||||
snapping: () => editor.shaderEditor.canvas.snapEnabled,
|
||||
snapButton: () => snapButton(editor.shaderEditor.snap),
|
||||
},
|
||||
/** The console panel: its lines as kept, its drawn height, and the divider above it. */
|
||||
console: {
|
||||
@@ -456,6 +468,7 @@ export function installDebugHook(app: Application, state: EditorState, editor: E
|
||||
rect: () => vectorArtEditor.canvasRect(),
|
||||
tool: () => vectorArtEditor.canvas.currentTool,
|
||||
snapping: () => vectorArtEditor.canvas.snapping,
|
||||
snapButton: () => snapButton(vectorArtEditor.snap),
|
||||
/** Whether the pen has a line open that the next click would add a point to. */
|
||||
drawing: () => vectorArtEditor.canvas.drawing,
|
||||
/** The shapes as the model holds them, so a check can read points and handles exactly. */
|
||||
|
||||
@@ -11,7 +11,7 @@ import { backTo, descend, follow, type Trail } from './trail';
|
||||
const INFO_WIDTH = 150;
|
||||
|
||||
export class GraphEditor extends AssetTabEditor<BehaviorAsset, GraphCanvas> {
|
||||
private readonly snap = new SnapToggle(() => this.canvas);
|
||||
readonly snap = new SnapToggle(() => this.canvas);
|
||||
readonly breadcrumb = new Breadcrumb();
|
||||
private trail: Trail = [];
|
||||
/** A call followed into another behavior: the tab goes on to that document (#171). */
|
||||
|
||||
@@ -1,17 +1,40 @@
|
||||
import { Checkbox, Label, type Widget } from '../../ui';
|
||||
import type { GraphCanvas } from './GraphCanvas';
|
||||
import { Button, Theme } from '../../ui';
|
||||
import { HEADER_ICON_SVG } from '../panels/headerIcons';
|
||||
|
||||
/** The "Snap" checkbox every tab on the graph canvas carries in its header. */
|
||||
/** Whatever a tab snaps: the graph canvas, the vector art canvas. */
|
||||
export interface Snappable {
|
||||
setSnapping(on: boolean): void;
|
||||
}
|
||||
|
||||
/** The magnet every tab that snaps carries in its header (#194): an icon toggle, off at first. */
|
||||
export class SnapToggle {
|
||||
private readonly box = new Checkbox();
|
||||
readonly widgets: readonly Widget[];
|
||||
readonly button: Button;
|
||||
private on = false;
|
||||
|
||||
constructor(canvas: () => GraphCanvas) {
|
||||
this.box.onChange = (on) => canvas().setSnapping(on);
|
||||
this.widgets = [new Label('Snap', { style: 'muted' }), this.box];
|
||||
constructor(private readonly target: () => Snappable) {
|
||||
this.button = new Button('', {
|
||||
width: Theme.controlHeight,
|
||||
icon: HEADER_ICON_SVG.snap,
|
||||
toggle: true,
|
||||
onClick: () => this.set(!this.on),
|
||||
});
|
||||
}
|
||||
|
||||
get widgets(): readonly Button[] {
|
||||
return [this.button];
|
||||
}
|
||||
|
||||
get snapping(): boolean {
|
||||
return this.on;
|
||||
}
|
||||
|
||||
set(on: boolean): void {
|
||||
this.on = on;
|
||||
this.target().setSnapping(on);
|
||||
this.button.setToggled(on);
|
||||
}
|
||||
|
||||
setEnabled(on: boolean): void {
|
||||
this.box.setEnabled(on);
|
||||
this.button.setEnabled(on);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
/** Icons of the tab headers' switches (#194): a picture instead of a caption. */
|
||||
import magnet from './icons/magnet.svg?raw';
|
||||
|
||||
export const HEADER_ICON_SVG = { snap: magnet };
|
||||
@@ -0,0 +1,7 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="-8 -8 16 16" width="16" height="16">
|
||||
<!-- A horseshoe opening downwards, the two poles split off by a gap. Cubic curves, not arcs:
|
||||
Pixi's parser draws an A in the middle of a path as a spike (see plugins.svg). -->
|
||||
<path d="M-4.8 1.6 L-4.8 -1.2 C-4.8 -7.6, 4.8 -7.6, 4.8 -1.2 L4.8 1.6" fill="none" stroke="currentColor" stroke-width="3.2" />
|
||||
<path d="M-6.4 3 L-3.2 3 L-3.2 6.4 L-6.4 6.4 Z" fill="currentColor" />
|
||||
<path d="M3.2 3 L6.4 3 L6.4 6.4 L3.2 6.4 Z" fill="currentColor" />
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 550 B |
@@ -27,7 +27,7 @@ const HOLD_WIDTH = 52;
|
||||
*/
|
||||
export class ParticlesEditor extends AssetTabEditor<ParticlesAsset, GraphCanvas> {
|
||||
readonly preview: ParticlePreview;
|
||||
private readonly snap = new SnapToggle(() => this.canvas);
|
||||
readonly snap = new SnapToggle(() => this.canvas);
|
||||
/** Public for the check scripts: the one control they have to hit rather than read. */
|
||||
readonly slider = new Slider(0, 0, MIN_SPAN, 120);
|
||||
private readonly holdButton = new Button('Hold', {
|
||||
|
||||
@@ -14,7 +14,7 @@ const INFO_WIDTH = 220;
|
||||
* on every change (see `ShaderLibrary`); the readout says whether it did.
|
||||
*/
|
||||
export class ShaderEditor extends AssetTabEditor<ShaderAsset, GraphCanvas> {
|
||||
private readonly snap = new SnapToggle(() => this.canvas);
|
||||
readonly snap = new SnapToggle(() => this.canvas);
|
||||
|
||||
constructor(state: EditorState) {
|
||||
super(state, { infoWidth: INFO_WIDTH });
|
||||
|
||||
@@ -14,7 +14,7 @@ const INFO_WIDTH = 90;
|
||||
* and undo are all the graph's.
|
||||
*/
|
||||
export class StateMachineEditor extends AssetTabEditor<StateMachineAsset, GraphCanvas> {
|
||||
private readonly snap = new SnapToggle(() => this.canvas);
|
||||
readonly snap = new SnapToggle(() => this.canvas);
|
||||
|
||||
constructor(state: EditorState) {
|
||||
super(state, { infoWidth: INFO_WIDTH });
|
||||
|
||||
@@ -171,7 +171,7 @@ export class VectorArtCanvas extends Widget {
|
||||
return this.tool;
|
||||
}
|
||||
|
||||
setSnap(on: boolean): void {
|
||||
setSnapping(on: boolean): void {
|
||||
this.snap = on;
|
||||
}
|
||||
|
||||
|
||||
@@ -2,6 +2,7 @@ import type { Asset, VectorArtAsset } from '../../core/model/Asset';
|
||||
import { vectorCanvasSize } from '../../core/model/vectorart';
|
||||
import { Button } from '../../ui';
|
||||
import { AssetTabEditor } from '../AssetTabEditor';
|
||||
import { SnapToggle } from '../graph/SnapToggle';
|
||||
import type { EditorState } from '../state';
|
||||
import { VectorArtCanvas, type VectorTool } from './VectorArtCanvas';
|
||||
|
||||
@@ -14,20 +15,16 @@ const INFO_WIDTH = 190;
|
||||
* a panel of its own is #191.
|
||||
*/
|
||||
export class VectorArtEditor extends AssetTabEditor<VectorArtAsset, VectorArtCanvas> {
|
||||
/** Public for the check scripts: the two tools and the grid switch. */
|
||||
/** Public for the check scripts: the two tools and the magnet. */
|
||||
readonly selectButton: Button;
|
||||
readonly penButton: Button;
|
||||
readonly snapButton: Button;
|
||||
readonly snap = new SnapToggle(() => this.canvas);
|
||||
|
||||
constructor(state: EditorState) {
|
||||
super(state, { infoWidth: INFO_WIDTH });
|
||||
this.selectButton = new Button('Select', { toggle: true, onClick: () => this.setTool('select') });
|
||||
this.penButton = new Button('Pen', { toggle: true, onClick: () => this.setTool('pen') });
|
||||
this.snapButton = new Button('Grid', {
|
||||
toggle: true,
|
||||
onClick: () => this.setSnap(!this.canvas.snapping),
|
||||
});
|
||||
this.mount(new VectorArtCanvas(state), [this.selectButton, this.penButton, this.snapButton]);
|
||||
this.mount(new VectorArtCanvas(state), [this.selectButton, this.penButton, ...this.snap.widgets]);
|
||||
this.setTool('select');
|
||||
}
|
||||
|
||||
@@ -37,11 +34,6 @@ export class VectorArtEditor extends AssetTabEditor<VectorArtAsset, VectorArtCan
|
||||
this.penButton.setToggled(tool === 'pen');
|
||||
}
|
||||
|
||||
setSnap(on: boolean): void {
|
||||
this.canvas.setSnap(on);
|
||||
this.snapButton.setToggled(on);
|
||||
}
|
||||
|
||||
/** Where a canvas pixel of the shown drawing sits on screen – for the check scripts. */
|
||||
canvasToScreen(x: number, y: number): { x: number; y: number } {
|
||||
return this.canvas.canvasToScreen(x, y);
|
||||
@@ -71,6 +63,6 @@ export class VectorArtEditor extends AssetTabEditor<VectorArtAsset, VectorArtCan
|
||||
this.fitButton.setEnabled(asset !== null);
|
||||
this.selectButton.setEnabled(asset !== null);
|
||||
this.penButton.setEnabled(asset !== null);
|
||||
this.snapButton.setEnabled(asset !== null);
|
||||
this.snap.setEnabled(asset !== null);
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user