diff --git a/src/main.js b/src/main.js index 0b4cf2f..cba6570 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 } from './utils.js'; +import { titleCase, getMousePositionFraction, getKeyframesSurrounding } from './utils.js'; const { writeTextFile: writeTextFile, readTextFile: readTextFile }= window.__TAURI__.fs; const { open: openFileDialog, @@ -240,43 +240,49 @@ let actions = { } }, addImageObject: { - create: (x, y, img, parent) => { + create: (x, y, imgsrc, ix, parent) => { redoStack.length = 0; // Clear redo stack let action = { shapeUuid: uuidv4(), objectUuid: uuidv4(), x: x, y: y, - width: img.width, - height: img.height, - ix: img.ix, - img: img.idx, + src: imgsrc, + ix: ix, parent: parent.idx + } undoStack.push({name: "addImageObject", action: action}) actions.addImageObject.execute(action) }, execute: (action) => { let imageObject = new GraphicsObject(action.objectUuid) - let img = pointerList[action.img] - let ct = { - ...context, - fillImage: img, - strokeShape: false, + // let img = pointerList[action.img] + let img = new Image(); + img.onload = function() { + let ct = { + ...context, + fillImage: img, + strokeShape: false, + } + let imageShape = new Shape(0, 0, ct, action.shapeUuid) + imageShape.addLine(img.width, 0) + imageShape.addLine(img.width, img.height) + imageShape.addLine(0, img.height) + imageShape.addLine(0, 0) + imageShape.update() + imageShape.regions[0].fillImage = img + imageShape.regions[0].filled = true + imageObject.addShape(imageShape) + let parent = pointerList[action.parent] + parent.addObject( + imageObject, + action.x-img.width/2 + (20*action.ix), + action.y-img.height/2 + (20*action.ix) + ) + updateUI(); } - let imageShape = new Shape(0, 0, ct, action.shapeUuid) - imageShape.addLine(action.width, 0) - imageShape.addLine(action.width, action.height) - imageShape.addLine(0, action.height) - imageShape.addLine(0, 0) - imageShape.update() - imageObject.addShape(imageShape) - let parent = pointerList[action.parent] - parent.addObject( - imageObject, - action.x-action.width/2 + (20*action.ix), - action.y-action.height/2 + (20*action.ix) - ) + img.src = action.src }, rollback: (action) => { let shape = pointerList[action.shapeUuid] @@ -312,6 +318,111 @@ let actions = { frame.keys = structuredClone(action.oldState) } }, + addFrame: { + create: () => { + redoStack.length = 0 + let frames = [] + for (let i=context.activeObject.activeLayer.frames.length; i<=context.activeObject.currentFrameNum; i++) { + frames.push(uuidv4()) + } + let action = { + frames: frames, + layer: context.activeObject.activeLayer.idx + } + undoStack.push({name: 'addFrame', action: action}) + actions.addFrame.execute(action) + }, + execute: (action) => { + let layer = pointerList[action.layer] + for (let frame of action.frames) { + layer.frames.push(new Frame("normal", frame)) + } + updateLayers() + }, + rollback: (action) => { + let layer = pointerList[action.layer] + for (let _frame of action.frames) { + layer.frames.pop() + } + updateLayers() + } + }, + addKeyframe: { + create: () => { + let frameNum = context.activeObject.currentFrameNum + let layer = context.activeObject.activeLayer + let formerType; + let addedFrames = 0; + if (frameNum >= layer.frames.length) { + formerType = "none" + addedFrames = frameNum - layer.frames.length + } else if (layer.frames[frameNum].frameType != "keyframe") { + formerType = layer.frames[frameNum].frameType + } else { + console.log("foolish") + return // Already a keyframe, nothing to do + } + redoStack.length = 0 + let action = { + frameNum: frameNum, + object: context.activeObject.idx, + layer: layer.idx, + formerType: formerType, + addedFrames: addedFrames + } + 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") + for (let key in latestFrame.keys) { + newKeyframe.keys[key] = structuredClone(latestFrame.keys[key]) + } + for (let shape of latestFrame.shapes) { + 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 { + redoStack.length = 0 + let action = { + } + undoStack.push({name: 'addMotionTween', action: action}) + actions.addMotionTween.execute(action) + }, + execute: (action) => { + // your code here + }, + rollback: (action) => { + // your code here + } + }, } function uuidv4() { @@ -919,7 +1030,7 @@ class Shape { ctx.lineCap = "round" for (let region of this.regions) { // if (region.filled) continue; - if (region.fillStyle && region.filled) { + if ((region.fillStyle || region.fillImage) && region.filled) { // ctx.fillStyle = region.fill if (region.fillImage) { let pat = ctx.createPattern(region.fillImage, "no-repeat") @@ -937,19 +1048,21 @@ class Shape { ctx.fill() } } - for (let curve of this.curves) { - ctx.strokeStyle = curve.color - ctx.beginPath() - ctx.moveTo(curve.points[0].x, curve.points[0].y) - ctx.bezierCurveTo(curve.points[1].x, curve.points[1].y, - curve.points[2].x, curve.points[2].y, - curve.points[3].x, curve.points[3].y) - ctx.stroke() + if (this.stroked) { + for (let curve of this.curves) { + ctx.strokeStyle = curve.color + ctx.beginPath() + ctx.moveTo(curve.points[0].x, curve.points[0].y) + ctx.bezierCurveTo(curve.points[1].x, curve.points[1].y, + curve.points[2].x, curve.points[2].y, + curve.points[3].x, curve.points[3].y) + ctx.stroke() - // Debug, show curve endpoints - // ctx.beginPath() - // ctx.arc(curve.points[3].x,curve.points[3].y, 3, 0, 2*Math.PI) - // ctx.fill() + // Debug, show curve endpoints + // ctx.beginPath() + // ctx.arc(curve.points[3].x,curve.points[3].y, 3, 0, 2*Math.PI) + // ctx.fill() + } } // Debug, show quadtree // this.quadtree.draw(ctx) @@ -981,26 +1094,46 @@ class GraphicsObject { return this.layers[this.currentLayer] } get children() { - return this.layers[this.currentLayer].children + return this.activeLayer.children } get currentFrame() { return this.getFrame(this.currentFrameNum) } getFrame(num) { - if (this.layers[this.currentLayer].frames[num]) { - if (this.layers[this.currentLayer].frames[num].frameType == "keyframe") { - return this.layers[this.currentLayer].frames[num] - } else if (this.layers[this.currentLayer].frames[num].frameType == "motion") { - - } else if (this.layers[this.currentLayer].frames[num].frameType == "shape") { + if (this.activeLayer.frames[num]) { + if (this.activeLayer.frames[num].frameType == "keyframe") { + return this.activeLayer.frames[num] + } else if (this.activeLayer.frames[num].frameType == "motion") { + let frameKeys = {} + const t = (num - this.activeLayer.frames[num].prevIndex) / (this.activeLayer.frames[num].nextIndex - this.activeLayer.frames[num].prevIndex); + console.log(this.activeLayer.frames[num].prev) + for (let key in this.activeLayer.frames[num].prev.keys) { + frameKeys[key] = {} + let prevKeyDict = this.activeLayer.frames[num].prev.keys[key] + let nextKeyDict = this.activeLayer.frames[num].next.keys[key] + for (let prop in prevKeyDict) { + frameKeys[key][prop] = (1 - t) * prevKeyDict[prop] + t * nextKeyDict[prop]; + } + + } + let frame = new Frame("motion", "temp") + frame.keys = frameKeys + return frame + } else if (this.activeLayer.frames[num].frameType == "shape") { } else { - for (let i=num; i>=0; i--) { - if (this.layers[this.currentLayer].frames[i].frameType == "keyframe") { - return this.layers[this.currentLayer].frames[i] + for (let i=Math.min(num, this.activeLayer.frames.length-1); i>=0; i--) { + if (this.activeLayer.frames[i].frameType == "keyframe") { + return this.activeLayer.frames[i] } } } + } else { + for (let i=Math.min(num, this.activeLayer.frames.length-1); i>=0; i--) { + if (this.activeLayer.frames[i].frameType == "keyframe") { + return this.activeLayer.frames[i] + } + } } } get maxFrame() { @@ -1032,9 +1165,9 @@ class GraphicsObject { let ctx = context.ctx; ctx.translate(this.x, this.y) ctx.rotate(this.rotation) - if (this.currentFrameNum>=this.maxFrame) { - this.currentFrameNum = 0; - } + // if (this.currentFrameNum>=this.maxFrame) { + // this.currentFrameNum = 0; + // } for (let shape of this.currentFrame.shapes) { shape.draw(context) } @@ -1361,31 +1494,30 @@ async function quit() { function addFrame() { if (context.activeObject.currentFrameNum >= context.activeObject.activeLayer.frames.length) { - for (let i=context.activeObject.activeLayer.frames.length; i<=context.activeObject.currentFrameNum; i++) { - context.activeObject.activeLayer.frames.push(new Frame()) - } - updateLayers() + actions.addFrame.create() } } function addKeyframe() { - let newKeyframe = new Frame("keyframe") - let latestFrame = context.activeObject.getFrame(Math.max(context.activeObject.currentFrameNum-1, 0)) - for (let key in latestFrame.keys) { - newKeyframe.keys[key] = latestFrame.keys[key] - } - for (let shape of latestFrame.shapes) { - newKeyframe.shapes.push(shape.copy()) - } - if (context.activeObject.currentFrameNum >= context.activeObject.activeLayer.frames.length) { - for (let i=context.activeObject.activeLayer.frames.length; i { @@ -2055,10 +2199,8 @@ function updateLayers() { frameEl.classList.add("active") highlightedFrame = true } - console.log(frame.frameType) - if (frame.frameType == "keyframe") { - frameEl.classList.add("keyframe") - } + + frameEl.classList.add(frame.frameType) layerTrack.appendChild(frameEl) }) if (!highlightedFrame) { @@ -2172,6 +2314,11 @@ async function updateMenu() { newFrameMenuItem, newKeyframeMenuItem, deleteFrameMenuItem, + { + text: "Add Motion Tween", + enabled: activeFrame && (!activeKeyframe), + action: addMotionTween + }, { text: "Return to start", enabled: false, diff --git a/src/styles.css b/src/styles.css index 8b5fb14..51781e1 100644 --- a/src/styles.css +++ b/src/styles.css @@ -305,6 +305,21 @@ button { background-color: #222; /* Set the color of the circle (black in this case) */ margin-bottom: 5px; } +.frame.motion { + background-color: #7a00b3; + border: none; +} +.frame.motion:hover, .frame.motion.active { + background-color: #530379; + border: none; +} +/* :nth-child(1 of .frame.motion) { + background-color: blue; +} +:nth-last-child(1 of .frame.motion) { + background-color: red; +} */ + .frame-highlight { background-color: red; width: 25px; diff --git a/src/utils.js b/src/utils.js index aad03a0..48c21bd 100644 --- a/src/utils.js +++ b/src/utils.js @@ -19,4 +19,26 @@ function getMousePositionFraction(event, element) { return 0; // If neither class is present, return 0 (or handle as needed) } -export { titleCase, getMousePositionFraction }; \ No newline at end of file +function getKeyframesSurrounding(frames, index) { + let lastKeyframeBefore = undefined; + let firstKeyframeAfter = undefined; + + // Find the last keyframe before the given index + for (let i = index - 1; i >= 0; i--) { + if (frames[i].frameType === "keyframe") { + lastKeyframeBefore = i; + break; + } + } + + // Find the first keyframe after the given index + for (let i = index + 1; i < frames.length; i++) { + if (frames[i].frameType === "keyframe") { + firstKeyframeAfter = i; + break; + } + } + return { lastKeyframeBefore, firstKeyframeAfter }; +} + +export { titleCase, getMousePositionFraction, getKeyframesSurrounding }; \ No newline at end of file