load frames in opened file
This commit is contained in:
parent
95834bb0e9
commit
a891475c62
57
src/main.js
57
src/main.js
|
|
@ -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.push(newKeyframe)
|
||||||
|
// } else if (layer.frames[action.frameNum].frameType != "keyframe") {
|
||||||
layer.frames[action.frameNum] = newKeyframe
|
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() {
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
||||||
|
|
|
||||||
52
src/utils.js
52
src/utils.js
|
|
@ -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 };
|
||||||
Loading…
Reference in New Issue