load frames in opened file

This commit is contained in:
Skyler Lehmkuhl 2024-12-03 00:38:01 -05:00
parent 95834bb0e9
commit a891475c62
3 changed files with 109 additions and 18 deletions

View File

@ -3,7 +3,7 @@ import * as fitCurve from '/fit-curve.js';
import { Bezier } from "/bezier.js"; import { Bezier } from "/bezier.js";
import { Quadtree } from './quadtree.js'; import { Quadtree } from './quadtree.js';
import { createNewFileDialog, showNewFileDialog, closeDialog } from './newfile.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 { writeTextFile: writeTextFile, readTextFile: readTextFile }= window.__TAURI__.fs;
const { const {
open: openFileDialog, open: openFileDialog,
@ -43,7 +43,9 @@ let maxFileVersion = "2.0"
let filePath = undefined let filePath = undefined
let fileWidth = 1500 let fileWidth = 1500
let fileHeight = 1000 let fileHeight = 1000
let fileFps = 12
let playing = false
let tools = { let tools = {
select: { select: {
@ -311,6 +313,8 @@ let actions = {
}, },
execute: (action) => { execute: (action) => {
let frame = pointerList[action.frame] let frame = pointerList[action.frame]
console.log(pointerList)
console.log(action.frame)
frame.keys = structuredClone(action.newState) frame.keys = structuredClone(action.newState)
}, },
rollback: (action) => { rollback: (action) => {
@ -352,10 +356,12 @@ let actions = {
let frameNum = context.activeObject.currentFrameNum let frameNum = context.activeObject.currentFrameNum
let layer = context.activeObject.activeLayer let layer = context.activeObject.activeLayer
let formerType; let formerType;
let addedFrames = 0; let addedFrames = {};
if (frameNum >= layer.frames.length) { if (frameNum >= layer.frames.length) {
formerType = "none" 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") { } else if (layer.frames[frameNum].frameType != "keyframe") {
formerType = layer.frames[frameNum].frameType formerType = layer.frames[frameNum].frameType
} else { } else {
@ -368,17 +374,17 @@ let actions = {
object: context.activeObject.idx, object: context.activeObject.idx,
layer: layer.idx, layer: layer.idx,
formerType: formerType, formerType: formerType,
addedFrames: addedFrames addedFrames: addedFrames,
uuid: uuidv4()
} }
undoStack.push({name: 'addKeyframe', action: action}) undoStack.push({name: 'addKeyframe', action: action})
actions.addKeyframe.execute(action) actions.addKeyframe.execute(action)
}, },
execute: (action) => { execute: (action) => {
// your code here
let object = pointerList[action.object] let object = pointerList[action.object]
let layer = pointerList[action.layer] let layer = pointerList[action.layer]
let latestFrame = object.getFrame(Math.max(action.frameNum-1, 0)) 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) { for (let key in latestFrame.keys) {
newKeyframe.keys[key] = structuredClone(latestFrame.keys[key]) newKeyframe.keys[key] = structuredClone(latestFrame.keys[key])
} }
@ -386,19 +392,20 @@ let actions = {
newKeyframe.shapes.push(shape.copy()) newKeyframe.shapes.push(shape.copy())
} }
if (action.frameNum >= layer.frames.length) { if (action.frameNum >= layer.frames.length) {
for (let i=layer.frames.length; i<action.frameNum; i++) { for (const [index, idx] of Object.entries(action.addedFrames)) {
layer.frames.push(new Frame()) layer.frames[index] = new Frame("normal", idx)
} }
layer.frames.push(newKeyframe)
} else if (layer.frames[action.frameNum].frameType != "keyframe") {
layer.frames[action.frameNum] = newKeyframe
} }
// layer.frames.push(newKeyframe)
// } else if (layer.frames[action.frameNum].frameType != "keyframe") {
layer.frames[action.frameNum] = newKeyframe
// }
updateLayers() updateLayers()
}, },
rollback: (action) => { rollback: (action) => {
let layer = pointerList[action.layer] let layer = pointerList[action.layer]
if (action.formerType == "none") { if (action.formerType == "none") {
for (let i=0; i<action.addedFrames+1; i++) { for (let i in action.addedFrames) {
layer.frames.pop() layer.frames.pop()
} }
} else { } else {
@ -1085,7 +1092,7 @@ class GraphicsObject {
this.currentFrameNum = 0; this.currentFrameNum = 0;
this.currentLayer = 0; this.currentLayer = 0;
this.layers = [new Layer()] this.layers = [new Layer(uuid+"-L1")]
// this.children = [] // this.children = []
this.shapes = [] this.shapes = []
@ -1169,7 +1176,13 @@ class GraphicsObject {
// this.currentFrameNum = 0; // this.currentFrameNum = 0;
// } // }
for (let shape of this.currentFrame.shapes) { for (let shape of this.currentFrame.shapes) {
if (false) {
invertPixels(ctx, fileWidth, fileHeight)
}
shape.draw(context) shape.draw(context)
if (false) {
invertPixels(ctx, fileWidth, fileHeight)
}
} }
for (let child of this.children) { for (let child of this.children) {
let idx = child.idx let idx = child.idx
@ -1180,6 +1193,9 @@ class GraphicsObject {
child.scale = this.currentFrame.keys[idx].scale; child.scale = this.currentFrame.keys[idx].scale;
ctx.save() ctx.save()
child.draw(context) child.draw(context)
if (true) {
}
ctx.restore() ctx.restore()
} }
} }
@ -1331,6 +1347,7 @@ window.addEventListener("keydown", (e) => {
// console.log(e) // console.log(e)
if (e.key == config.shortcuts.playAnimation) { if (e.key == config.shortcuts.playAnimation) {
console.log("Spacebar pressed") console.log("Spacebar pressed")
playPause()
} else if (e.key == config.shortcuts.undo && e.ctrlKey == true) { } else if (e.key == config.shortcuts.undo && e.ctrlKey == true) {
undo() undo()
} else if (e.key == config.shortcuts.redo && e.ctrlKey == true) { } 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() { function advanceFrame() {
context.activeObject.currentFrameNum += 1 context.activeObject.currentFrameNum += 1
updateLayers() updateLayers()
@ -1370,11 +1392,12 @@ function decrementFrame() {
} }
} }
function _newFile(width, height) { function _newFile(width, height, fps) {
root = new GraphicsObject("root"); root = new GraphicsObject("root");
context.activeObject = root context.activeObject = root
fileWidth = width fileWidth = width
fileHeight = height fileHeight = height
fileFps = fps
for (let stage of document.querySelectorAll(".stage")) { for (let stage of document.querySelectorAll(".stage")) {
stage.width = width stage.width = width
stage.height = height stage.height = height
@ -1397,6 +1420,7 @@ async function _save(path) {
version: "1.1", version: "1.1",
width: fileWidth, width: fileWidth,
height: fileHeight, height: fileHeight,
fps: fileFps,
actions: undoStack actions: undoStack
} }
const contents = JSON.stringify(fileData ); const contents = JSON.stringify(fileData );
@ -1451,7 +1475,7 @@ async function open() {
} }
if (file.version >= minFileVersion) { if (file.version >= minFileVersion) {
if (file.version < maxFileVersion) { if (file.version < maxFileVersion) {
_newFile(file.width, file.height) _newFile(file.width, file.height, file.fps)
if (file.actions == undefined) { if (file.actions == undefined) {
await messageDialog("File has no content!", {title: "Parse error", kind: 'error'}) await messageDialog("File has no content!", {title: "Parse error", kind: 'error'})
return return
@ -2163,6 +2187,9 @@ function updateUI() {
} }
} }
if (playing) {
setTimeout(advanceFrame, 1000/fileFps)
}
} }
function updateLayers() { function updateLayers() {

View File

@ -44,6 +44,20 @@ function createNewFileDialog(callback) {
heightInput.value = '1000'; // Default value heightInput.value = '1000'; // Default value
newFileDialog.appendChild(heightInput); 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 // Create Create button
const createButton = document.createElement('button'); const createButton = document.createElement('button');
createButton.textContent = 'Create'; createButton.textContent = 'Create';
@ -56,9 +70,9 @@ function createNewFileDialog(callback) {
function createNewFile() { function createNewFile() {
const width = document.getElementById('width').value; const width = document.getElementById('width').value;
const height = document.getElementById('height').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(`New file created with width: ${width} and height: ${height}`);
console.log(callback) callback(width, height, fps)
callback(width, height)
// Add any further logic to handle the new file creation here // Add any further logic to handle the new file creation here

View File

@ -41,4 +41,54 @@ function getKeyframesSurrounding(frames, index) {
return { lastKeyframeBefore, firstKeyframeAfter }; return { lastKeyframeBefore, firstKeyframeAfter };
} }
export { titleCase, getMousePositionFraction, getKeyframesSurrounding }; 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 };