あ
This commit is contained in:
parent
8f9f5b5100
commit
f1c3af2b89
49 changed files with 989 additions and 310 deletions
|
|
@ -18,18 +18,33 @@
|
|||
undo: "\u21B6 \u4E00\u3064\u623B\u3059",
|
||||
clear: "\uD83D\uDDD1 \u5168\u6D88\u3057",
|
||||
});
|
||||
const DEFAULT_GRID_SIZE = 20;
|
||||
function normalizedGridSize(value = DEFAULT_GRID_SIZE) {
|
||||
return Math.max(10, Math.min(80, Math.round(Number(value || DEFAULT_GRID_SIZE) || DEFAULT_GRID_SIZE)));
|
||||
}
|
||||
function snapValue(value, gridSize = DEFAULT_GRID_SIZE) {
|
||||
const grid = normalizedGridSize(gridSize);
|
||||
return Math.round((Number(value) || 0) / grid) * grid;
|
||||
}
|
||||
function snapPoint(point, gridSize = DEFAULT_GRID_SIZE) {
|
||||
return { x: snapValue(point?.x, gridSize), y: snapValue(point?.y, gridSize) };
|
||||
}
|
||||
function templateSegments(name) {
|
||||
if (name === "bar") return [[-130, 0, 130, 0]];
|
||||
if (name === "cross") return [[-118, 0, 118, 0], [0, -90, 0, 90]];
|
||||
if (name === "bar") return [[-120, 0, 120, 0]];
|
||||
if (name === "cross") return [[-120, 0, 120, 0], [0, -80, 0, 80]];
|
||||
if (name === "circle") {
|
||||
const segments = [];
|
||||
const n = 18, r = 108;
|
||||
for (let i = 0; i < n; i += 1) {
|
||||
const a = i / n * Math.PI * 2;
|
||||
const b = (i + 1) / n * Math.PI * 2;
|
||||
segments.push([Math.cos(a) * r, Math.sin(a) * r, Math.cos(b) * r, Math.sin(b) * r]);
|
||||
}
|
||||
return segments;
|
||||
// 24-sided lattice circle. More vertices preserve a round silhouette while
|
||||
// every endpoint remains on the shared 20px editor grid.
|
||||
const points = [
|
||||
[120, 0], [120, 40], [100, 60], [80, 80], [60, 100], [40, 120],
|
||||
[0, 120], [-40, 120], [-60, 100], [-80, 80], [-100, 60], [-120, 40],
|
||||
[-120, 0], [-120, -40], [-100, -60], [-80, -80], [-60, -100], [-40, -120],
|
||||
[0, -120], [40, -120], [60, -100], [80, -80], [100, -60], [120, -40],
|
||||
];
|
||||
return points.map((point, index) => {
|
||||
const next = points[(index + 1) % points.length];
|
||||
return [point[0], point[1], next[0], next[1]];
|
||||
});
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
|
@ -65,23 +80,32 @@
|
|||
function cleanSegments(segments = [], opts = {}) {
|
||||
const limit = Math.max(1, Number(opts.limit || 96) || 96);
|
||||
const bound = Math.max(1, Number(opts.bound || 420) || 420);
|
||||
const minLength = Math.max(0, Number(opts.minLength || 4) || 4);
|
||||
const fallback = Array.isArray(opts.fallback) ? opts.fallback : [-78, 0, 78, 0];
|
||||
const gridSize = normalizedGridSize(opts.gridSize);
|
||||
const snap = opts.snap !== false;
|
||||
const minLength = Math.max(gridSize, Number(opts.minLength || gridSize) || gridSize);
|
||||
const fallback = Array.isArray(opts.fallback) ? opts.fallback : [-80, 0, 80, 0];
|
||||
const coord = value => {
|
||||
const raw = snap ? snapValue(value, gridSize) : (Number(value) || 0);
|
||||
return Math.max(-bound, Math.min(bound, raw));
|
||||
};
|
||||
const out = [];
|
||||
for (const seg of segments || []) {
|
||||
if (!Array.isArray(seg) || seg.length < 4) continue;
|
||||
const x1 = Math.max(-bound, Math.min(bound, Number(seg[0]) || 0));
|
||||
const y1 = Math.max(-bound, Math.min(bound, Number(seg[1]) || 0));
|
||||
const x2 = Math.max(-bound, Math.min(bound, Number(seg[2]) || 0));
|
||||
const y2 = Math.max(-bound, Math.min(bound, Number(seg[3]) || 0));
|
||||
const x1 = coord(seg[0]);
|
||||
const y1 = coord(seg[1]);
|
||||
const x2 = coord(seg[2]);
|
||||
const y2 = coord(seg[3]);
|
||||
if (Math.hypot(x2 - x1, y2 - y1) >= minLength) out.push([x1, y1, x2, y2]);
|
||||
if (out.length >= limit) break;
|
||||
}
|
||||
if (!out.length) out.push(fallback.slice(0, 4));
|
||||
if (!out.length) {
|
||||
const snappedFallback = fallback.slice(0, 4).map(value => coord(value));
|
||||
out.push(snappedFallback);
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
function drawEditorBase(ctx, canvas, cx = canvas?.width / 2 || 0, cy = canvas?.height / 2 || 0) {
|
||||
function drawEditorBase(ctx, canvas, cx = canvas?.width / 2 || 0, cy = canvas?.height / 2 || 0, gridSize = DEFAULT_GRID_SIZE) {
|
||||
if (!ctx || !canvas) return false;
|
||||
ctx.clearRect(0, 0, canvas.width, canvas.height);
|
||||
ctx.save();
|
||||
|
|
@ -90,8 +114,9 @@
|
|||
ctx.translate(cx, cy);
|
||||
ctx.strokeStyle = "rgba(100,80,60,0.16)";
|
||||
ctx.lineWidth = 1;
|
||||
for (let x = -cx; x <= cx; x += 40) { ctx.beginPath(); ctx.moveTo(x, -cy); ctx.lineTo(x, cy); ctx.stroke(); }
|
||||
for (let y = -cy; y <= cy; y += 40) { ctx.beginPath(); ctx.moveTo(-cx, y); ctx.lineTo(cx, y); ctx.stroke(); }
|
||||
const grid = normalizedGridSize(gridSize);
|
||||
for (let x = -cx; x <= cx; x += grid) { ctx.beginPath(); ctx.moveTo(x, -cy); ctx.lineTo(x, cy); ctx.stroke(); }
|
||||
for (let y = -cy; y <= cy; y += grid) { ctx.beginPath(); ctx.moveTo(-cx, y); ctx.lineTo(cx, y); ctx.stroke(); }
|
||||
ctx.strokeStyle = "rgba(70,60,50,0.38)";
|
||||
ctx.lineWidth = 1.5;
|
||||
ctx.beginPath(); ctx.moveTo(-cx, 0); ctx.lineTo(cx, 0); ctx.moveTo(0, -cy); ctx.lineTo(0, cy); ctx.stroke();
|
||||
|
|
@ -173,19 +198,20 @@
|
|||
const prefix = opts.prefix || "rotator";
|
||||
const cx = opts.cx ?? canvas.width / 2;
|
||||
const cy = opts.cy ?? canvas.height / 2;
|
||||
const fallback = Array.isArray(opts.fallback) ? opts.fallback : [-78, 0, 78, 0];
|
||||
const gridSize = normalizedGridSize(opts.gridSize);
|
||||
const fallback = Array.isArray(opts.fallback) ? opts.fallback : [-80, 0, 80, 0];
|
||||
let mode = "line";
|
||||
let segments = (opts.segments || []).map(seg => Array.isArray(seg) ? seg.slice(0, 4) : seg).filter(seg => Array.isArray(seg) && seg.length >= 4);
|
||||
let segments = cleanSegments(opts.segments || [], { fallback, limit: opts.limit, bound: opts.bound, minLength: opts.minLength, gridSize, snap: true });
|
||||
let draft = null;
|
||||
let drawing = false;
|
||||
|
||||
const lineWidth = () => Math.max(1, Number(typeof opts.lineWidth === "function" ? opts.lineWidth() : opts.lineWidth || 12) || 12);
|
||||
const strokeStyle = () => typeof opts.strokeStyle === "function" ? opts.strokeStyle() : opts.strokeStyle;
|
||||
const clean = () => { segments = cleanSegments(segments, { fallback, limit: opts.limit, bound: opts.bound, minLength: opts.minLength }); };
|
||||
const toLocal = (e) => canvasLocalPoint(canvas, e, cx, cy);
|
||||
const clean = () => { segments = cleanSegments(segments, { fallback, limit: opts.limit, bound: opts.bound, minLength: opts.minLength, gridSize, snap: true }); };
|
||||
const toLocal = (e) => snapPoint(canvasLocalPoint(canvas, e, cx, cy), gridSize);
|
||||
|
||||
const draw = () => {
|
||||
drawEditorBase(ctx, canvas, cx, cy);
|
||||
drawEditorBase(ctx, canvas, cx, cy, gridSize);
|
||||
if (typeof opts.drawBeforeSegments === "function") opts.drawBeforeSegments(ctx, { cx, cy, lineWidth: lineWidth(), segments: segments.slice() });
|
||||
const lw = lineWidth();
|
||||
drawSegments(ctx, segments, { lineWidth: lw, strokeStyle: strokeStyle() || "rgba(80,150,205,0.92)" });
|
||||
|
|
@ -240,7 +266,7 @@
|
|||
const last = segments[segments.length - 1];
|
||||
if (!last) return;
|
||||
const lx = last[2], ly = last[3];
|
||||
if (Math.hypot(p.x - lx, p.y - ly) >= 5) {
|
||||
if (Math.hypot(p.x - lx, p.y - ly) >= gridSize) {
|
||||
last[2] = p.x; last[3] = p.y;
|
||||
segments.push([p.x, p.y, p.x, p.y]);
|
||||
}
|
||||
|
|
@ -257,7 +283,7 @@
|
|||
if (!draft) return;
|
||||
if (mode === "line") {
|
||||
const seg = [draft[0], draft[1], p.x, p.y];
|
||||
if (Math.hypot(seg[2] - seg[0], seg[3] - seg[1]) >= 4) segments.push(seg);
|
||||
if (Math.hypot(seg[2] - seg[0], seg[3] - seg[1]) >= gridSize) segments.push(seg);
|
||||
}
|
||||
draft = null;
|
||||
clean();
|
||||
|
|
@ -281,7 +307,7 @@
|
|||
draw,
|
||||
getSegments: () => segments.map(seg => seg.slice(0, 4)),
|
||||
setMode,
|
||||
setSegments(next = []) { segments = next.map(seg => Array.isArray(seg) ? seg.slice(0, 4) : seg).filter(seg => Array.isArray(seg) && seg.length >= 4); draft = null; draw(); },
|
||||
setSegments(next = []) { segments = cleanSegments(next, { fallback, limit: opts.limit, bound: opts.bound, minLength: opts.minLength, gridSize, snap: true }); draft = null; draw(); },
|
||||
};
|
||||
}
|
||||
|
||||
|
|
@ -289,5 +315,9 @@
|
|||
decorateToolbar,
|
||||
createSegmentEditorController,
|
||||
syncInputPair,
|
||||
snapPoint,
|
||||
cleanSegments,
|
||||
templateSegments,
|
||||
gridSize: DEFAULT_GRID_SIZE,
|
||||
});
|
||||
})(typeof window !== "undefined" ? window : globalThis);
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue