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
45 changes: 45 additions & 0 deletions celstomp/celstomp-app.js
Original file line number Diff line number Diff line change
Expand Up @@ -404,6 +404,9 @@
function clearFx() {
fxctx.setTransform(1, 0, 0, 1, 0, 0);
fxctx.clearRect(0, 0, fxCanvas.width, fxCanvas.height);
setTransform(fxctx);
drawGrid(fxctx);
drawGuides(fxctx);
}

function wireBrushButtonRightClick() {
Expand Down Expand Up @@ -924,6 +927,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 @@ -401,3 +401,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;
}
119 changes: 119 additions & 0 deletions celstomp/js/input/pointer-events.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,16 @@ 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 trailPoints = [];

function pressure(e) {
Expand Down Expand Up @@ -47,6 +57,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 @@ -1487,3 +1505,104 @@ function fillFromLineart(F) {
updateTimelineHasContent(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;
}
8 changes: 8 additions & 0 deletions celstomp/js/ui/interaction-shortcuts.js
Original file line number Diff line number Diff line change
Expand Up @@ -491,6 +491,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
13 changes: 12 additions & 1 deletion celstomp/parts/timeline.js
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,17 @@ document.getElementById('part-timeline').innerHTML = `
<span class="timelineZoom">Zoom</span>
<button id="zoomTimelineIn" class="miniBtn" title="Zoom In">+</button>

<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">—</button>
</div>
</div>
Expand All @@ -60,6 +71,6 @@ document.getElementById('part-timeline').innerHTML = `
</div>
</section>


<button id="showTimelineEdge" class="edge-btn">Show Timeline</button>
<div id="guideModeHint" class="guideModeHint" hidden></div>
`;