Mapping editor: snap to grid and to other zones' corners
'Aggancia' toggle + grid step (5%, 10%, 2.5%, 1/8, 1/16), persisted. While dragging a corner or a whole zone the delta is shifted so the closest moving point lands exactly on a grid line or on another zone's corner coordinate (per axis, 9 css px capture radius) — adjacent LED panels butt together to the pixel. Hold Alt to drag freely; keyboard nudge stays unsnapped. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01XVMQ2JhsJ77pGKqcvvFHqc
This commit is contained in:
1 parent
e754656585
commit
9af9d26b2b
2 files changed
+66
-10
No files matched your search
@@ -214,6 +214,14 @@
|
||||
<div class="row">
|
||||
<label class="chk"><input type="checkbox" id="map-on" /> Attiva</label>
|
||||
<label class="chk"><input type="checkbox" id="map-edit" /> Mostra maniglie anche sull'output</label>
|
||||
<label class="chk" title="Aggancia angoli e zone alla griglia e agli angoli delle altre zone (tieni premuto Alt per disattivarlo mentre trascini)"><input type="checkbox" id="map-snap" /> 🧲 Aggancia</label>
|
||||
<select id="map-grid" title="Passo della griglia">
|
||||
<option value="20">Griglia 5%</option>
|
||||
<option value="10">Griglia 10%</option>
|
||||
<option value="40">Griglia 2,5%</option>
|
||||
<option value="8">Griglia 1/8</option>
|
||||
<option value="16">Griglia 1/16</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="map-ed-wrap">
|
||||
<canvas id="map-editor" tabindex="0"></canvas>
|
||||
@@ -247,6 +255,9 @@
|
||||
vede subito sul ledwall/proiettore. Per la rifinitura usa la <b>tastiera</b>
|
||||
(clicca prima sull'editor): <b>frecce</b> = 1 pixel, <b>Shift+frecce</b> = 10 pixel,
|
||||
<b>Tab</b> = passa all'angolo successivo, <b>Esc</b> = torna a muovere la zona intera.
|
||||
Con <b>🧲 Aggancia</b> angoli e zone si agganciano alla griglia (passo
|
||||
a scelta) e agli angoli delle altre zone, così i pannelli adiacenti combaciano
|
||||
al pixel; tieni premuto <b>Alt</b> mentre trascini per muoverti libero.
|
||||
La riga sotto l'editor mostra la posizione in pixel. Se vuoi vedere i contorni anche
|
||||
sullo schermo, spunta <b>Mostra maniglie anche sull'output</b>.</p>
|
||||
<p><b>Opzioni per zona</b>:</p>
|
||||
|
||||
+55
-10
@@ -1672,7 +1672,11 @@ $('#map-img-input').addEventListener('change', (e) => {
|
||||
// normalized 0..1 (y-down) corners the output uses.
|
||||
let mapSel = -1; // selected zone index
|
||||
let mapSelC = -1; // selected corner (0 TL,1 TR,2 BR,3 BL) or -1 = whole zone
|
||||
let mapEdDrag = null; // { ci, lx, ly }
|
||||
let mapEdDrag = null; // { ci, x0, y0, start: corners snapshot }
|
||||
// Snap: grid divisions per side + on/off, persisted.
|
||||
let mapSnap = { on: true, div: 10 };
|
||||
try { mapSnap = Object.assign(mapSnap, JSON.parse(localStorage.getItem('mapsnap') || '{}')); } catch (e) {}
|
||||
const MAP_SNAP_PX = 9; // capture radius in editor css pixels
|
||||
let outBounds = null; // main output display size (set by refreshDisplays)
|
||||
const MAP_CORNER = ['alto-sx', 'alto-dx', 'basso-dx', 'basso-sx'];
|
||||
|
||||
@@ -1722,10 +1726,15 @@ function mapEdDraw() {
|
||||
// output frame + 10% grid
|
||||
ctx.fillStyle = '#000'; ctx.fillRect(ox, 0, W, H);
|
||||
ctx.strokeStyle = 'rgba(255,255,255,0.06)'; ctx.lineWidth = 1;
|
||||
for (let k = 1; k < 10; k++) {
|
||||
ctx.beginPath(); ctx.moveTo(ox + W * k / 10, 0); ctx.lineTo(ox + W * k / 10, H); ctx.stroke();
|
||||
ctx.beginPath(); ctx.moveTo(ox, H * k / 10); ctx.lineTo(ox + W, H * k / 10); ctx.stroke();
|
||||
const dv = mapSnap.div;
|
||||
ctx.strokeStyle = mapSnap.on ? 'rgba(127,168,255,0.13)' : 'rgba(255,255,255,0.06)';
|
||||
for (let k = 1; k < dv; k++) {
|
||||
// the centre lines are slightly stronger: handy for symmetric setups
|
||||
ctx.globalAlpha = (k * 2 === dv) ? 1.8 : 1;
|
||||
ctx.beginPath(); ctx.moveTo(ox + W * k / dv, 0); ctx.lineTo(ox + W * k / dv, H); ctx.stroke();
|
||||
ctx.beginPath(); ctx.moveTo(ox, H * k / dv); ctx.lineTo(ox + W, H * k / dv); ctx.stroke();
|
||||
}
|
||||
ctx.globalAlpha = 1;
|
||||
ctx.strokeStyle = 'rgba(255,255,255,0.25)'; ctx.strokeRect(ox + 0.5, 0.5, W - 1, H - 1);
|
||||
const P = (c) => [ox + c[0] * W, c[1] * H];
|
||||
mapCfg.zones.forEach((z, zi) => {
|
||||
@@ -1765,7 +1774,21 @@ function mapEdDraw() {
|
||||
: '● ' + z.name + ' · zona intera · angolo alto-sx ' + fmt(z.corners[0]);
|
||||
}
|
||||
|
||||
// Snap targets on one axis: grid lines (screen edges included) and the
|
||||
// corners of every OTHER zone, so adjacent panels butt together exactly.
|
||||
function mapSnapTargets(axis, skipZone) {
|
||||
const out = [];
|
||||
for (let k = 0; k <= mapSnap.div; k++) out.push(k / mapSnap.div);
|
||||
mapCfg.zones.forEach((z, zi) => { if (zi !== skipZone) z.corners.forEach(c => out.push(c[axis])); });
|
||||
return out;
|
||||
}
|
||||
|
||||
(function mapEdInit() {
|
||||
$('#map-snap').checked = mapSnap.on;
|
||||
$('#map-grid').value = String(mapSnap.div);
|
||||
const snapSave = () => { localStorage.setItem('mapsnap', JSON.stringify(mapSnap)); mapEdDraw(); };
|
||||
$('#map-snap').addEventListener('change', (e) => { mapSnap.on = e.target.checked; snapSave(); });
|
||||
$('#map-grid').addEventListener('change', (e) => { mapSnap.div = parseInt(e.target.value, 10) || 10; snapSave(); });
|
||||
const cv = $('#map-editor');
|
||||
const toN = (e) => {
|
||||
const r = cv.getBoundingClientRect(), g = cv._geo || { ox: 0, W: r.width, H: r.height };
|
||||
@@ -1795,13 +1818,13 @@ function mapEdDraw() {
|
||||
}
|
||||
if (best) {
|
||||
mapSelect(best.zi, best.ci);
|
||||
mapEdDrag = { ci: best.ci, lx: x, ly: y };
|
||||
mapEdDrag = { ci: best.ci, x0: x, y0: y, start: mapCfg.zones[best.zi].corners.map(c => c.slice()) };
|
||||
return;
|
||||
}
|
||||
for (let zi = mapCfg.zones.length - 1; zi >= 0; zi--) {
|
||||
if (inside(mapCfg.zones[zi].corners, x, y)) {
|
||||
mapSelect(zi, -1);
|
||||
mapEdDrag = { ci: -1, lx: x, ly: y };
|
||||
mapEdDrag = { ci: -1, x0: x, y0: y, start: mapCfg.zones[zi].corners.map(c => c.slice()) };
|
||||
return;
|
||||
}
|
||||
}
|
||||
@@ -1812,12 +1835,34 @@ function mapEdDraw() {
|
||||
const z = mapCfg.zones[mapSel];
|
||||
if (!z) { mapEdDrag = null; return; }
|
||||
const [x, y] = toN(e);
|
||||
const g = cv._geo || { W: cv.clientWidth, H: cv.clientHeight };
|
||||
let dx = x - mapEdDrag.x0, dy = y - mapEdDrag.y0;
|
||||
const st = mapEdDrag.start;
|
||||
// Moving points (one corner, or all four for a whole-zone drag) snap by
|
||||
// shifting the WHOLE delta so the closest candidate lands exactly on a
|
||||
// target — per axis, so an edge can align horizontally and vertically.
|
||||
const moving = mapEdDrag.ci >= 0 ? [st[mapEdDrag.ci]] : st;
|
||||
if (mapSnap.on && !e.altKey) {
|
||||
const tx = mapSnapTargets(0, mapSel), ty = mapSnapTargets(1, mapSel);
|
||||
const fix = (d, axis, targets, tol) => {
|
||||
let best = null;
|
||||
for (const p of moving) {
|
||||
const v = p[axis] + d;
|
||||
for (const t of targets) {
|
||||
const off = t - v;
|
||||
if (Math.abs(off) <= tol && (best === null || Math.abs(off) < Math.abs(best))) best = off;
|
||||
}
|
||||
}
|
||||
return best === null ? d : d + best;
|
||||
};
|
||||
dx = fix(dx, 0, tx, MAP_SNAP_PX / g.W);
|
||||
dy = fix(dy, 1, ty, MAP_SNAP_PX / g.H);
|
||||
}
|
||||
const z0 = mapCfg.zones[mapSel];
|
||||
if (mapEdDrag.ci >= 0) {
|
||||
z.corners[mapEdDrag.ci] = [clampC(x), clampC(y)];
|
||||
z0.corners[mapEdDrag.ci] = [clampC(st[mapEdDrag.ci][0] + dx), clampC(st[mapEdDrag.ci][1] + dy)];
|
||||
} else {
|
||||
const dx = x - mapEdDrag.lx, dy = y - mapEdDrag.ly;
|
||||
z.corners.forEach(c => { c[0] = clampC(c[0] + dx); c[1] = clampC(c[1] + dy); });
|
||||
mapEdDrag.lx = x; mapEdDrag.ly = y;
|
||||
z0.corners = st.map(c => [clampC(c[0] + dx), clampC(c[1] + dy)]);
|
||||
}
|
||||
mapEdDraw(); mapEdSync();
|
||||
});
|
||||
|
||||
Reference in new issue
Block a user