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
@@ -3,6 +3,8 @@ export const BRUSH_MODE_PIXEL = "pixel";
export const CANVAS_VIEW_MIN_SCALE = 1;
export const CANVAS_VIEW_MAX_SCALE = 8;
export const CANVAS_UNDO_LIMIT = 50;
export const COORDINATE_GUIDE_STEPS = Object.freeze([1, 2, 4, 8, 16, 32]);
export const COORDINATE_GUIDE_TARGET_SPACING = 48;
const MODE_SPECS = Object.freeze({
[BRUSH_MODE_NORMAL]: Object.freeze({ min: 1, max: 16, presets: Object.freeze([1, 2, 4, 8]) }),
@@ -23,23 +25,55 @@ export function normalizeBrushSize(value, mode) {
return Math.max(spec.min, Math.min(spec.max, Number.isFinite(parsed) ? parsed : spec.min));
}
export function restoreCanvasToolState(savedV2, savedV1 = null) {
const current = record(savedV2);
const legacy = record(savedV1);
export function restoreCanvasToolState(savedV3, savedV2 = null) {
const current = record(savedV3);
const legacy = record(savedV2);
const source = current || legacy || {};
return {
paintColor: typeof source.paintColor === "string" ? source.paintColor : null,
backgroundColor: typeof source.backgroundColor === "string" ? source.backgroundColor : null,
tool: source.tool === "eraser" ? "eraser" : "brush",
pixelEditEnabled: Boolean(current?.pixelEditEnabled),
pixelEditEnabled: Boolean(source.pixelEditEnabled),
normalBrushSize: normalizeBrushSize(
current?.normalBrushSize ?? source.brushSize,
source.normalBrushSize ?? source.brushSize,
BRUSH_MODE_NORMAL,
),
pixelBrushSize: normalizeBrushSize(current?.pixelBrushSize, BRUSH_MODE_PIXEL),
pixelBrushSize: normalizeBrushSize(source.pixelBrushSize, BRUSH_MODE_PIXEL),
coordinateGuideEnabled: Boolean(current?.coordinateGuideEnabled),
};
}
export function chooseCoordinateGuideStep(
logicalPixelCssSize,
targetSpacing = COORDINATE_GUIDE_TARGET_SPACING,
) {
const pixelSize = Math.max(0, Number(logicalPixelCssSize) || 0);
const target = Math.max(1, Number(targetSpacing) || COORDINATE_GUIDE_TARGET_SPACING);
return COORDINATE_GUIDE_STEPS.reduce((best, candidate) => (
Math.abs(candidate * pixelSize - target) < Math.abs(best * pixelSize - target)
? candidate
: best
), COORDINATE_GUIDE_STEPS[0]);
}
export function coordinateGuideTicks({
boardStart,
boardSize,
visibleStart = Number.NEGATIVE_INFINITY,
visibleEnd = Number.POSITIVE_INFINITY,
step,
}) {
const start = Number(boardStart) || 0;
const size = Math.max(0, Number(boardSize) || 0);
const interval = COORDINATE_GUIDE_STEPS.includes(step) ? step : COORDINATE_GUIDE_STEPS[0];
const ticks = [];
for (let value = 0; value < 64; value += interval) {
const position = start + (value / 64) * size;
if (position >= visibleStart && position <= visibleEnd) ticks.push({ value, position });
}
return ticks;
}
export function linePoints(from, to) {
const points = [];
let x = from.x;