Add adaptive coordinate guides to canvas editor

This commit is contained in:
2026-09-10 17:48:04 +08:00
parent 8d368de3b5
commit a493fe1757
9 changed files with 292 additions and 13 deletions
@@ -8,8 +8,10 @@ import {
PixelUndoHistory,
brushModeSpec,
brushStampPoints,
chooseCoordinateGuideStep,
clampCanvasView,
clampFloatingButton,
coordinateGuideTicks,
linePoints,
normalizeBrushSize,
restoreCanvasToolState,
@@ -60,7 +62,7 @@ test("line interpolation includes endpoints without coordinate gaps", () => {
}
});
test("v1 tools migrate into normal mode while v2 restores both sizes", () => {
test("older tools migrate while v3 restores the coordinate guide choice", () => {
assert.deepEqual(restoreCanvasToolState(null, {
paintColor: "#123456",
backgroundColor: "#654321",
@@ -73,12 +75,14 @@ test("v1 tools migrate into normal mode while v2 restores both sizes", () => {
pixelEditEnabled: false,
normalBrushSize: 8,
pixelBrushSize: 1,
coordinateGuideEnabled: false,
});
assert.deepEqual(restoreCanvasToolState({
tool: "brush",
pixelEditEnabled: true,
normalBrushSize: 12,
pixelBrushSize: 4,
coordinateGuideEnabled: true,
}), {
paintColor: null,
backgroundColor: null,
@@ -86,8 +90,58 @@ test("v1 tools migrate into normal mode while v2 restores both sizes", () => {
pixelEditEnabled: true,
normalBrushSize: 12,
pixelBrushSize: 4,
coordinateGuideEnabled: true,
});
assert.equal(restoreCanvasToolState("damaged", { brushSize: "bad" }).normalBrushSize, 1);
assert.equal(restoreCanvasToolState("damaged", { coordinateGuideEnabled: true }).coordinateGuideEnabled, false);
assert.deepEqual(restoreCanvasToolState(null, {
pixelEditEnabled: true,
normalBrushSize: 9,
pixelBrushSize: 3,
}), {
paintColor: null,
backgroundColor: null,
tool: "brush",
pixelEditEnabled: true,
normalBrushSize: 9,
pixelBrushSize: 3,
coordinateGuideEnabled: false,
});
});
test("coordinate guide spacing adapts to the transformed logical pixel size", () => {
assert.equal(chooseCoordinateGuideStep(4.625), 8);
assert.equal(chooseCoordinateGuideStep(9.25), 4);
assert.equal(chooseCoordinateGuideStep(18.5), 2);
assert.equal(chooseCoordinateGuideStep(37), 1);
assert.equal(chooseCoordinateGuideStep("damaged"), 1);
});
test("coordinate guide ticks preserve logical values through zoom and pan", () => {
assert.deepEqual(coordinateGuideTicks({
boardStart: 20,
boardSize: 320,
visibleStart: 0,
visibleEnd: 400,
step: 16,
}), [
{ value: 0, position: 20 },
{ value: 16, position: 100 },
{ value: 32, position: 180 },
{ value: 48, position: 260 },
]);
assert.deepEqual(coordinateGuideTicks({
boardStart: -300,
boardSize: 640,
visibleStart: 0,
visibleEnd: 320,
step: 8,
}), [
{ value: 32, position: 20 },
{ value: 40, position: 100 },
{ value: 48, position: 180 },
{ value: 56, position: 260 },
]);
});
test("pixel mode markup and grid stay accessible and non-interactive", async () => {
@@ -168,13 +222,19 @@ test("canvas editor markup, guarded input, and checkpoint events stay wired", as
]);
assert.match(index, /id="start-canvas-edit"[^>]*>开始编辑<\/button>/);
assert.match(index, /id="canvas-assistive-toggle"[^>]+aria-expanded="false"/);
assert.match(index, /id="canvas-coordinate-guide"[^>]+aria-hidden="true"[^>]+hidden/);
assert.match(index, /id="toggle-canvas-coordinates"[^>]+aria-pressed="false"/);
assert.match(index, /id="undo-canvas-edit"[^>]+aria-disabled="true"/);
assert.match(index, /id="move-canvas-view"/);
assert.match(index, /id="continue-canvas-edit"/);
assert.match(style, /body\.canvas-edit-active #board-shell\s*\{[^}]*position:\s*fixed/s);
assert.match(style, /\.canvas-coordinate-guide\s*\{[^}]*pointer-events:\s*none/s);
assert.match(style, /body\.canvas-edit-active \.canvas-coordinate-guide:not\(\[hidden\]\)\s*\{[^}]*z-index:\s*1005/s);
assert.match(style, /#scene-overlay\[data-interaction="pixels"\]:not\(\[data-canvas-editing="true"\]\)\s*\{[^}]*pointer-events:\s*none/s);
assert.match(canvasView, /if \(!editorActive \|\| getInteractionMode\(\) !== "pixels"\) return/);
assert.match(canvasView, /new PixelUndoHistory\(\)/);
assert.match(canvasView, /createLocalState\("canvas-tools", 3\)/);
assert.match(canvasView, /coordinateGuide\.hidden = true/);
assert.match(sceneBoard, /matrix:scene-replaced/);
assert.match(templates, /matrix:canvas-history-checkpoint/);
assert.match(animations, /matrix:canvas-history-checkpoint/);