Better grid resizing behavior.
This commit is contained in:
parent
3f4aee73a3
commit
0b85715c1e
1 changed files with 202 additions and 148 deletions
|
|
@ -2,22 +2,19 @@
|
|||
{% set dot_size = [grid_size * 1.25, 32]|max|int %}
|
||||
|
||||
{% block style %}
|
||||
:root {
|
||||
--grid: {{ grid_size }}px;
|
||||
}
|
||||
|
||||
#grid {
|
||||
cursor: crosshair;
|
||||
height: 80vh;
|
||||
#gridWrap {
|
||||
width: 100%;
|
||||
touch-action: none;
|
||||
height: 80vh;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
@supports (height: calc(round(nearest, 80vh, {{ grid_size }}px))) {
|
||||
#grid {
|
||||
height: calc(round(nearest, 80vh, var(--grid)) + 1px);
|
||||
width: calc(round(nearest, 100%, var(--grid)) + 1px);
|
||||
}
|
||||
position: relative;
|
||||
cursor: crosshair;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
touch-action: none;
|
||||
}
|
||||
|
||||
#toolBar {
|
||||
|
|
@ -46,6 +43,7 @@
|
|||
|
||||
{% block main %}
|
||||
<div class="container">
|
||||
<div id="gridWrap">
|
||||
<div id="grid" class="position-relative overflow-hidden">
|
||||
<div id="toolBar"
|
||||
class="btn-toolbar bg-light position-absolute border border-secondary-subtle rounded start-50 p-1 align-items-center flex-nowrap overflow-auto">
|
||||
|
|
@ -185,6 +183,7 @@
|
|||
<canvas id="overlay" class="position-absolute w-100 h-100"></canvas>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
||||
{% block script %}
|
||||
|
|
@ -199,6 +198,7 @@ const gridEl = document.getElementById('grid');
|
|||
const importButtonEl = document.getElementById('importButton');
|
||||
const importEl = document.getElementById('import');
|
||||
const gridSizeEl = document.getElementById('gridSize');
|
||||
const gridWrapEl = document.getElementById('gridWrap');
|
||||
|
||||
let gridSize = Number(gridSizeEl.value) || {{ grid_size }};
|
||||
let dotSize = Math.floor(Math.max(gridSize * 1.25, 32));
|
||||
|
|
@ -210,8 +210,11 @@ let selectedColor;
|
|||
let currentShape = null;
|
||||
let shapes = loadShapes();
|
||||
|
||||
const ro = new ResizeObserver(() => resizeAndSetupCanvas());
|
||||
ro.observe(gridEl);
|
||||
let sizingRAF = 0;
|
||||
let lastApplied = { w: 0, h: 0 };
|
||||
|
||||
const ro = new ResizeObserver(scheduleSnappedGridSize);
|
||||
ro.observe(gridWrapEl);
|
||||
|
||||
const savedTool = localStorage.getItem('gridTool');
|
||||
if (savedTool) {
|
||||
|
|
@ -228,12 +231,43 @@ window.addEventListener('resize', resizeAndSetupCanvas);
|
|||
|
||||
setGrid();
|
||||
|
||||
function snapDown(n, step) {
|
||||
return Math.floor(n / step) * step;
|
||||
}
|
||||
|
||||
function applySnappedGridSize() {
|
||||
sizingRAF = 0;
|
||||
|
||||
const grid = gridSize;
|
||||
if (!Number.isFinite(grid) || grid < 1) return;
|
||||
|
||||
const w = gridWrapEl.clientWidth;
|
||||
const h = gridWrapEl.clientHeight;
|
||||
|
||||
const snappedW = snapDown(w, grid);
|
||||
const snappedH = snapDown(h, grid);
|
||||
|
||||
if (snappedW === lastApplied.w && snappedH === lastApplied.h) return;
|
||||
|
||||
lastApplied = { w: snappedW, h: snappedH };
|
||||
|
||||
gridEl.style.width = `${snappedW}px`;
|
||||
gridEl.style.height = `${snappedH}px`;
|
||||
|
||||
resizeAndSetupCanvas();
|
||||
}
|
||||
|
||||
function scheduleSnappedGridSize() {
|
||||
if (sizingRAF) return;
|
||||
sizingRAF = requestAnimationFrame(applySnappedGridSize);
|
||||
}
|
||||
|
||||
function applyGridSize(newSize) {
|
||||
const n = Number(newSize);
|
||||
if (!Number.isFinite(n) || n < 1) return;
|
||||
|
||||
gridSize = n;
|
||||
document.documentElement.style.setProperty('--grid', `${gridSize}px`);
|
||||
snapSizeToGrid();
|
||||
|
||||
dotSize = Math.floor(Math.max(gridSize * 1.25, 32));
|
||||
|
||||
|
|
@ -241,7 +275,7 @@ function applyGridSize(newSize) {
|
|||
dotSVGEl.setAttribute('height', dotSize);
|
||||
|
||||
setGrid();
|
||||
resizeAndSetupCanvas();
|
||||
scheduleSnappedGridSize();
|
||||
}
|
||||
|
||||
function pxToGrid(v) {
|
||||
|
|
@ -274,7 +308,29 @@ function setActiveType(typeId) {
|
|||
}
|
||||
}
|
||||
|
||||
function snapSizeToGrid() {
|
||||
const grid = gridSize;
|
||||
const rect = gridEl.getBoundingClientRect();
|
||||
|
||||
const targetW = rect.width;
|
||||
const targetH = rect.height;
|
||||
|
||||
const snappedW = Math.floor(targetW / grid) * grid + 1;
|
||||
const snappedH = Math.floor(targetH / grid) * grid + 1;
|
||||
|
||||
gridEl.style.width = `${snappedW}px`;
|
||||
gridEl.style.height = `${snappedH}px`;
|
||||
}
|
||||
|
||||
function snapToGrid(x, y) {
|
||||
/*
|
||||
For portability, we do not allow pixel coordinates in the data model
|
||||
and only use pixels for rendering. We display both spaces on the screen
|
||||
to ensure no matter the mode, the user is reasoning in the same two
|
||||
coordinate spaces as they need. Thus, snapping will happen even if
|
||||
the tool doesn't use it.
|
||||
*/
|
||||
|
||||
const rect = gridEl.getBoundingClientRect();
|
||||
const clampedX = Math.min(Math.max(x, rect.left), rect.right);
|
||||
const clampedY = Math.min(Math.max(y, rect.top), rect.bottom);
|
||||
|
|
@ -344,6 +400,7 @@ function normalizeLine(shape) {
|
|||
}
|
||||
|
||||
function resizeAndSetupCanvas() {
|
||||
snapSizeToGrid();
|
||||
dpr = window.devicePixelRatio || 1;
|
||||
const rect = canvasEl.getBoundingClientRect();
|
||||
|
||||
|
|
@ -581,9 +638,6 @@ document.addEventListener('keydown', (e) => {
|
|||
});
|
||||
|
||||
gridEl.addEventListener('pointermove', (e) => {
|
||||
// Note to certain minds that keep thinking we want to mix grid and pixel coordinates.
|
||||
// No. No we do not. Pixels are not portable. Stop it.
|
||||
|
||||
if (!ctx) return;
|
||||
|
||||
const { ix, iy, x: snapX, y: snapY } = snapToGrid(e.clientX, e.clientY);
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue