From a891475c6216ccae5ddc4c4f8a310bfaee672efc Mon Sep 17 00:00:00 2001 From: Skyler Lehmkuhl Date: Tue, 3 Dec 2024 00:38:01 -0500 Subject: [PATCH] load frames in opened file --- src/main.js | 57 +++++++++++++++++++++++++++++++++++++------------- src/newfile.js | 18 ++++++++++++++-- src/utils.js | 52 ++++++++++++++++++++++++++++++++++++++++++++- 3 files changed, 109 insertions(+), 18 deletions(-) diff --git a/src/main.js b/src/main.js index cba6570..84c7578 100644 --- a/src/main.js +++ b/src/main.js @@ -3,7 +3,7 @@ import * as fitCurve from '/fit-curve.js'; import { Bezier } from "/bezier.js"; import { Quadtree } from './quadtree.js'; import { createNewFileDialog, showNewFileDialog, closeDialog } from './newfile.js'; -import { titleCase, getMousePositionFraction, getKeyframesSurrounding } from './utils.js'; +import { titleCase, getMousePositionFraction, getKeyframesSurrounding, invertPixels } from './utils.js'; const { writeTextFile: writeTextFile, readTextFile: readTextFile }= window.__TAURI__.fs; const { open: openFileDialog, @@ -43,7 +43,9 @@ let maxFileVersion = "2.0" let filePath = undefined let fileWidth = 1500 let fileHeight = 1000 +let fileFps = 12 +let playing = false let tools = { select: { @@ -311,6 +313,8 @@ let actions = { }, execute: (action) => { let frame = pointerList[action.frame] + console.log(pointerList) + console.log(action.frame) frame.keys = structuredClone(action.newState) }, rollback: (action) => { @@ -352,10 +356,12 @@ let actions = { let frameNum = context.activeObject.currentFrameNum let layer = context.activeObject.activeLayer let formerType; - let addedFrames = 0; + let addedFrames = {}; if (frameNum >= layer.frames.length) { formerType = "none" - addedFrames = frameNum - layer.frames.length + for (let i=layer.frames.length; i<=frameNum; i++) { + addedFrames[i] = uuidv4() + } } else if (layer.frames[frameNum].frameType != "keyframe") { formerType = layer.frames[frameNum].frameType } else { @@ -368,17 +374,17 @@ let actions = { object: context.activeObject.idx, layer: layer.idx, formerType: formerType, - addedFrames: addedFrames + addedFrames: addedFrames, + uuid: uuidv4() } undoStack.push({name: 'addKeyframe', action: action}) actions.addKeyframe.execute(action) }, execute: (action) => { - // your code here let object = pointerList[action.object] let layer = pointerList[action.layer] let latestFrame = object.getFrame(Math.max(action.frameNum-1, 0)) - let newKeyframe = new Frame("keyframe") + let newKeyframe = new Frame("keyframe", action.uuid) for (let key in latestFrame.keys) { newKeyframe.keys[key] = structuredClone(latestFrame.keys[key]) } @@ -386,19 +392,20 @@ let actions = { newKeyframe.shapes.push(shape.copy()) } if (action.frameNum >= layer.frames.length) { - for (let i=layer.frames.length; i { let layer = pointerList[action.layer] if (action.formerType == "none") { - for (let i=0; i { // console.log(e) if (e.key == config.shortcuts.playAnimation) { console.log("Spacebar pressed") + playPause() } else if (e.key == config.shortcuts.undo && e.ctrlKey == true) { undo() } else if (e.key == config.shortcuts.redo && e.ctrlKey == true) { @@ -1354,6 +1371,11 @@ window.addEventListener("keydown", (e) => { } }) +function playPause() { + playing = !playing + updateUI() +} + function advanceFrame() { context.activeObject.currentFrameNum += 1 updateLayers() @@ -1370,11 +1392,12 @@ function decrementFrame() { } } -function _newFile(width, height) { +function _newFile(width, height, fps) { root = new GraphicsObject("root"); context.activeObject = root fileWidth = width fileHeight = height + fileFps = fps for (let stage of document.querySelectorAll(".stage")) { stage.width = width stage.height = height @@ -1397,6 +1420,7 @@ async function _save(path) { version: "1.1", width: fileWidth, height: fileHeight, + fps: fileFps, actions: undoStack } const contents = JSON.stringify(fileData ); @@ -1451,7 +1475,7 @@ async function open() { } if (file.version >= minFileVersion) { if (file.version < maxFileVersion) { - _newFile(file.width, file.height) + _newFile(file.width, file.height, file.fps) if (file.actions == undefined) { await messageDialog("File has no content!", {title: "Parse error", kind: 'error'}) return @@ -2163,6 +2187,9 @@ function updateUI() { } } + if (playing) { + setTimeout(advanceFrame, 1000/fileFps) + } } function updateLayers() { diff --git a/src/newfile.js b/src/newfile.js index a6acf15..0ced18f 100644 --- a/src/newfile.js +++ b/src/newfile.js @@ -44,6 +44,20 @@ function createNewFileDialog(callback) { heightInput.value = '1000'; // Default value newFileDialog.appendChild(heightInput); + // Create FPS input + const fpsLabel = document.createElement('label'); + fpsLabel.setAttribute('for', 'fps'); + fpsLabel.classList.add('dialog-label'); + fpsLabel.textContent = 'Frames per Second:'; + newFileDialog.appendChild(fpsLabel); + + const fpsInput = document.createElement('input'); + fpsInput.type = 'number'; + fpsInput.id = 'fps'; + fpsInput.classList.add('dialog-input'); + fpsInput.value = '12'; // Default value + newFileDialog.appendChild(fpsInput); + // Create Create button const createButton = document.createElement('button'); createButton.textContent = 'Create'; @@ -56,9 +70,9 @@ function createNewFileDialog(callback) { function createNewFile() { const width = document.getElementById('width').value; const height = document.getElementById('height').value; + const fps = document.getElementById('fps').value; console.log(`New file created with width: ${width} and height: ${height}`); - console.log(callback) - callback(width, height) + callback(width, height, fps) // Add any further logic to handle the new file creation here diff --git a/src/utils.js b/src/utils.js index 48c21bd..974df6d 100644 --- a/src/utils.js +++ b/src/utils.js @@ -41,4 +41,54 @@ function getKeyframesSurrounding(frames, index) { return { lastKeyframeBefore, firstKeyframeAfter }; } -export { titleCase, getMousePositionFraction, getKeyframesSurrounding }; \ No newline at end of file +function invertPixels(ctx, width, height) { + // Create an off-screen canvas for the pattern + const patternCanvas = document.createElement('canvas'); + const patternContext = patternCanvas.getContext('2d'); + + // Define the size of the repeating pattern (2x2 pixels) + const patternSize = 2; + patternCanvas.width = patternSize; + patternCanvas.height = patternSize; + + // Create the alternating pattern (regular and inverted pixels) + function createInvertedPattern() { + const patternData = patternContext.createImageData(patternSize, patternSize); + const data = patternData.data; + + // Fill the pattern with alternating colors (inverted every other pixel) + for (let i = 0; i < patternSize; i++) { + for (let j = 0; j < patternSize; j++) { + const index = (i * patternSize + j) * 4; + // Determine if we should invert the color + if ((i + j) % 2 === 0) { + data[index] = 255; // Red + data[index + 1] = 0; // Green + data[index + 2] = 0; // Blue + data[index + 3] = 255; // Alpha + } else { + data[index] = 0; // Red (inverted) + data[index + 1] = 255; // Green (inverted) + data[index + 2] = 255; // Blue (inverted) + data[index + 3] = 255; // Alpha + } + } + } + + // Set the pattern on the off-screen canvas + patternContext.putImageData(patternData, 0, 0); + return patternCanvas; + } + + // Create the pattern using the function + const pattern = ctx.createPattern(createInvertedPattern(), 'repeat'); + + // Draw a rectangle with the pattern + ctx.globalCompositeOperation = "difference" + ctx.fillStyle = pattern; + ctx.fillRect(0, 0, width, height); + + ctx.globalCompositeOperation = "source-over" +} + +export { titleCase, getMousePositionFraction, getKeyframesSurrounding, invertPixels }; \ No newline at end of file