Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
44 changes: 44 additions & 0 deletions celstomp/celstomp-app.js
Original file line number Diff line number Diff line change
Expand Up @@ -421,6 +421,8 @@
fxctx.setTransform(1, 0, 0, 1, 0, 0);
fxctx.clearRect(0, 0, fxCanvas.width, fxCanvas.height);
setTransform(fxctx);
drawGrid(fxctx);
drawGuides(fxctx);
drawRectSelectionOverlay(fxctx);
drawLineToolPreview(fxctx);
drawRectToolPreview(fxctx);
Expand Down Expand Up @@ -1039,6 +1041,48 @@
playSnapped = !!e.target.checked;
safeSetChecked(playSnappedChk, playSnapped);
});

const gridBtn = $("toggleGridBtn");
const gridSizeInp = $("gridSizeInput");
const gridSnapBtn = $("toggleGridSnapBtn");
const guidesBtn = $("toggleGuidesBtn");
const guideSnapBtn = $("toggleGuideSnapBtn");
const addGuideHBtn = $("addGuideHBtn");
const addGuideVBtn = $("addGuideVBtn");
const clearGuidesBtn = $("clearGuidesBtn");
guideModeHint = $("guideModeHint");

gridBtn?.addEventListener("click", () => {
toggleGrid();
gridBtn.classList.toggle("active", gridEnabled);
});
gridSizeInp?.addEventListener("input", e => {
gridSize = Math.max(8, Math.min(128, parseInt(e.target.value, 10) || 32));
queueRenderAll();
});
gridSnapBtn?.addEventListener("click", () => {
toggleGridSnap();
gridSnapBtn.classList.toggle("active", gridSnap);
});
guidesBtn?.addEventListener("click", () => {
toggleGuides();
guidesBtn.classList.toggle("active", guidesEnabled);
});
guideSnapBtn?.addEventListener("click", () => {
toggleGuideSnap();
guideSnapBtn.classList.toggle("active", guideSnap);
});
addGuideHBtn?.addEventListener("click", () => {
setGuideMode(guideMode === 'h' ? null : 'h');
addGuideHBtn.classList.toggle("active", guideMode === 'h');
addGuideVBtn?.classList.remove("active");
});
addGuideVBtn?.addEventListener("click", () => {
setGuideMode(guideMode === 'v' ? null : 'v');
addGuideVBtn.classList.toggle("active", guideMode === 'v');
addGuideHBtn?.classList.remove("active");
});
clearGuidesBtn?.addEventListener("click", clearGuides);
}

function wirePanelToggles() {
Expand Down
26 changes: 26 additions & 0 deletions celstomp/css/components/timeline.css
Original file line number Diff line number Diff line change
Expand Up @@ -673,3 +673,29 @@ body.dragging-cel{ cursor: grabbing; user-select:none; }
padding: 6px 8px;
}
}

.gridGuideCtrls {
display: flex;
align-items: center;
gap: 4px;
}

.gridGuideCtrls button.active {
background: rgba(0, 229, 255, 0.25);
border-color: rgba(0, 229, 255, 0.6);
}

.guideModeHint {
position: fixed;
bottom: calc(var(--timeline-h) + 8px);
left: 50%;
transform: translateX(-50%);
background: rgba(0, 229, 255, 0.18);
border: 1px solid rgba(0, 229, 255, 0.5);
color: #9fd5ff;
padding: 6px 12px;
border-radius: 8px;
font-size: 12px;
z-index: 100;
pointer-events: none;
}
118 changes: 118 additions & 0 deletions celstomp/js/input/pointer-events.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,15 @@ let usePressureTilt = false;
let brushSize = 3;
let autofill = false;

let gridEnabled = false;
let gridSize = 32;
let gridSnap = false;
let guidesEnabled = false;
let guideSnap = false;
let guideHLines = [];
let guideVLines = [];
let guideMode = null;
let guideModeHint = null;
let textEntryActive = false;
let textEntryX = 0;
let textEntryY = 0;
Expand Down Expand Up @@ -58,6 +67,14 @@ function handlePointerDown(e) {
return;
}
}
if (guideMode && e.button === 0) {
const pos = getCanvasPointer(e);
const pt = screenToContent(pos.x, pos.y);
if (handleGuideClick(pt.x, pt.y)) {
e.preventDefault();
return;
}
}
try {
drawCanvas.setPointerCapture(e.pointerId);
} catch {}
Expand Down Expand Up @@ -1660,6 +1677,107 @@ function fillFromLineart(F) {
return true;
}

function drawGrid(ctx) {
if (!gridEnabled) return;
ctx.save();
ctx.strokeStyle = "rgba(255,255,255,0.12)";
ctx.lineWidth = 1 / Math.max(getZoom(), 1);
ctx.beginPath();
for (let x = 0; x <= contentW; x += gridSize) {
ctx.moveTo(x, 0);
ctx.lineTo(x, contentH);
}
for (let y = 0; y <= contentH; y += gridSize) {
ctx.moveTo(0, y);
ctx.lineTo(contentW, y);
}
ctx.stroke();
ctx.restore();
}

function drawGuides(ctx) {
if (!guidesEnabled) return;
ctx.save();
ctx.strokeStyle = "rgba(0,229,255,0.6)";
ctx.lineWidth = 1 / Math.max(getZoom(), 1);
ctx.setLineDash([4 / Math.max(getZoom(), 1), 2 / Math.max(getZoom(), 1)]);
ctx.beginPath();
for (const y of guideHLines) {
ctx.moveTo(0, y);
ctx.lineTo(contentW, y);
}
for (const x of guideVLines) {
ctx.moveTo(x, 0);
ctx.lineTo(x, contentH);
}
ctx.stroke();
ctx.restore();
}

function snapToGrid(x, y) {
if (!gridEnabled || !gridSnap) return { x, y };
return {
x: Math.round(x / gridSize) * gridSize,
y: Math.round(y / gridSize) * gridSize
};
}

function snapToGuides(x, y) {
if (!guidesEnabled || !guideSnap) return { x, y };
const threshold = 8;
let sx = x, sy = y;
for (const gy of guideHLines) {
if (Math.abs(y - gy) < threshold) sy = gy;
}
for (const gx of guideVLines) {
if (Math.abs(x - gx) < threshold) sx = gx;
}
return { x: sx, y: sy };
}

function toggleGrid() {
gridEnabled = !gridEnabled;
queueRenderAll();
}

function toggleGridSnap() {
gridSnap = !gridSnap;
}

function toggleGuides() {
guidesEnabled = !guidesEnabled;
queueRenderAll();
}

function toggleGuideSnap() {
guideSnap = !guideSnap;
}

function clearGuides() {
guideHLines = [];
guideVLines = [];
queueRenderAll();
}

function setGuideMode(mode) {
guideMode = mode;
if (guideModeHint) {
guideModeHint.textContent = mode ? `Click to place ${mode === 'h' ? 'horizontal' : 'vertical'} guide` : '';
guideModeHint.hidden = !mode;
}
}

function handleGuideClick(x, y) {
if (!guideMode) return false;
if (guideMode === 'h') {
guideHLines.push(Math.round(y));
} else if (guideMode === 'v') {
guideVLines.push(Math.round(x));
}
queueRenderAll();
return true;
}

function textEntryFont(entry) {
const bold = entry.bold ? "bold " : "";
const italic = entry.italic ? "italic " : "";
Expand Down
8 changes: 8 additions & 0 deletions celstomp/js/ui/interaction-shortcuts.js
Original file line number Diff line number Diff line change
Expand Up @@ -521,6 +521,14 @@ function _wireExtraKeyboardShortcuts() {
return;
}

if (k === "g") {
e.preventDefault();
toggleGrid();
const gridBtn = document.getElementById("toggleGridBtn");
if (gridBtn) gridBtn.classList.toggle("active", gridEnabled);
return;
}

if (k === "[") {
e.preventDefault();
brushSize = Math.max(1, brushSize - 5);
Expand Down
15 changes: 13 additions & 2 deletions celstomp/parts/timeline.js
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,17 @@ document.getElementById('part-timeline').innerHTML = `
</button>
</div>

<div class="gridGuideCtrls">
<button id="toggleGridBtn" class="miniBtn" title="Toggle Grid (G)">Grid</button>
<input id="gridSizeInput" type="number" min="8" max="128" value="32" title="Grid Size" style="width:50px;" />
<button id="toggleGridSnapBtn" class="miniBtn" title="Toggle Grid Snap">Snap</button>
<button id="toggleGuidesBtn" class="miniBtn" title="Toggle Guides">Guides</button>
<button id="toggleGuideSnapBtn" class="miniBtn" title="Toggle Guide Snap">GSnap</button>
<button id="addGuideHBtn" class="miniBtn" title="Add Horizontal Guide">H Guide</button>
<button id="addGuideVBtn" class="miniBtn" title="Add Vertical Guide">V Guide</button>
<button id="clearGuidesBtn" class="miniBtn danger" title="Clear Guides">Clear</button>
</div>

<button id="hideTimelineBtn" class="tl-icon-btn" title="Hide Timeline" onclick="document.body.classList.add('tl-collapsed');var t=document.getElementById('timeline');if(t){t.hidden=true;t.style.display='none';}var s=document.getElementById('showTimelineEdge');if(s){s.style.display='block';}window.dispatchEvent(new Event('resize'))">
<svg viewBox="0 0 24 24" width="16" height="16"><path d="M18 6L6 18M6 6l12 12" stroke="currentColor" stroke-width="2" stroke-linecap="round"/></svg>
</button>
Expand All @@ -90,6 +101,6 @@ document.getElementById('part-timeline').innerHTML = `
</div>
</section>


<button id="showTimelineEdge" class="edge-btn" onclick="document.body.classList.remove('tl-collapsed');var t=document.getElementById('timeline');if(t){t.hidden=false;t.style.display='';}this.style.display='none';window.dispatchEvent(new Event('resize'))">Show Timeline</button>
<button id="showTimelineEdge" class="edge-btn" onclick="document.body.classList.remove('tl-collapsed');var t=document.getElementById('timeline');if(t){t.hidden=false;t.style.display='';}this.style.display='none';window.dispatchEvent(new Event('resize'))">Show Timeline</button>
<div id="guideModeHint" class="guideModeHint" hidden></div>
`;