Work on timeline
This commit is contained in:
parent
9414bdcd74
commit
e45659ddfd
289
src/main.js
289
src/main.js
|
|
@ -524,11 +524,15 @@ let actions = {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Add keyframes to AnimationData for this shape
|
// Add keyframes to AnimationData for this shape
|
||||||
|
// Use shapeId (not idx) so that multiple versions share curves
|
||||||
let existsKeyframe = new Keyframe(action.time, 1, "hold");
|
let existsKeyframe = new Keyframe(action.time, 1, "hold");
|
||||||
layer.animationData.addKeyframe(`shape.${newShape.idx}.exists`, existsKeyframe);
|
layer.animationData.addKeyframe(`shape.${newShape.shapeId}.exists`, existsKeyframe);
|
||||||
|
|
||||||
let zOrderKeyframe = new Keyframe(action.time, zOrder, "hold");
|
let zOrderKeyframe = new Keyframe(action.time, zOrder, "hold");
|
||||||
layer.animationData.addKeyframe(`shape.${newShape.idx}.zOrder`, zOrderKeyframe);
|
layer.animationData.addKeyframe(`shape.${newShape.shapeId}.zOrder`, zOrderKeyframe);
|
||||||
|
|
||||||
|
let shapeIndexKeyframe = new Keyframe(action.time, 0, "linear");
|
||||||
|
layer.animationData.addKeyframe(`shape.${newShape.shapeId}.shapeIndex`, shapeIndexKeyframe);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
rollback: (action) => {
|
rollback: (action) => {
|
||||||
|
|
@ -541,9 +545,10 @@ let actions = {
|
||||||
layer.shapes.splice(shapeIndex, 1);
|
layer.shapes.splice(shapeIndex, 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Remove keyframes from AnimationData
|
// Remove keyframes from AnimationData (use shapeId not idx)
|
||||||
delete layer.animationData.curves[`shape.${shape.idx}.exists`];
|
delete layer.animationData.curves[`shape.${shape.shapeId}.exists`];
|
||||||
delete layer.animationData.curves[`shape.${shape.idx}.zOrder`];
|
delete layer.animationData.curves[`shape.${shape.shapeId}.zOrder`];
|
||||||
|
delete layer.animationData.curves[`shape.${shape.shapeId}.shapeIndex`];
|
||||||
|
|
||||||
delete pointerList[action.uuid];
|
delete pointerList[action.uuid];
|
||||||
},
|
},
|
||||||
|
|
@ -1577,8 +1582,9 @@ let actions = {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Remove animation curves for this shape from parent layer
|
// Remove animation curves for this shape from parent layer
|
||||||
layer.animationData.removeCurve(`shape.${shape.idx}.exists`);
|
layer.animationData.removeCurve(`shape.${shape.shapeId}.exists`);
|
||||||
layer.animationData.removeCurve(`shape.${shape.idx}.zOrder`);
|
layer.animationData.removeCurve(`shape.${shape.shapeId}.zOrder`);
|
||||||
|
layer.animationData.removeCurve(`shape.${shape.shapeId}.shapeIndex`);
|
||||||
|
|
||||||
// Add shape to group's first layer
|
// Add shape to group's first layer
|
||||||
let groupLayer = group.activeLayer;
|
let groupLayer = group.activeLayer;
|
||||||
|
|
@ -1586,13 +1592,17 @@ let actions = {
|
||||||
groupLayer.shapes.push(shape);
|
groupLayer.shapes.push(shape);
|
||||||
|
|
||||||
// Add animation curves for this shape in group's layer
|
// Add animation curves for this shape in group's layer
|
||||||
let existsCurve = new AnimationCurve(`shape.${shape.idx}.exists`);
|
let existsCurve = new AnimationCurve(`shape.${shape.shapeId}.exists`);
|
||||||
existsCurve.addKeyframe(new Keyframe(0, 1, 'linear'));
|
existsCurve.addKeyframe(new Keyframe(0, 1, 'linear'));
|
||||||
groupLayer.animationData.setCurve(`shape.${shape.idx}.exists`, existsCurve);
|
groupLayer.animationData.setCurve(`shape.${shape.shapeId}.exists`, existsCurve);
|
||||||
|
|
||||||
let zOrderCurve = new AnimationCurve(`shape.${shape.idx}.zOrder`);
|
let zOrderCurve = new AnimationCurve(`shape.${shape.shapeId}.zOrder`);
|
||||||
zOrderCurve.addKeyframe(new Keyframe(0, groupLayer.shapes.length - 1, 'linear'));
|
zOrderCurve.addKeyframe(new Keyframe(0, groupLayer.shapes.length - 1, 'linear'));
|
||||||
groupLayer.animationData.setCurve(`shape.${shape.idx}.zOrder`, zOrderCurve);
|
groupLayer.animationData.setCurve(`shape.${shape.shapeId}.zOrder`, zOrderCurve);
|
||||||
|
|
||||||
|
let shapeIndexCurve = new AnimationCurve(`shape.${shape.shapeId}.shapeIndex`);
|
||||||
|
shapeIndexCurve.addKeyframe(new Keyframe(0, 0, 'linear'));
|
||||||
|
groupLayer.animationData.setCurve(`shape.${shape.shapeId}.shapeIndex`, shapeIndexCurve);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Move objects (children) to the group
|
// Move objects (children) to the group
|
||||||
|
|
@ -3210,7 +3220,7 @@ class Layer extends Widget {
|
||||||
if (shape instanceof TempShape) continue;
|
if (shape instanceof TempShape) continue;
|
||||||
|
|
||||||
// Check if shape exists at current time
|
// Check if shape exists at current time
|
||||||
let existsValue = this.animationData.interpolate(`shape.${shape.idx}.exists`, time);
|
let existsValue = this.animationData.interpolate(`shape.${shape.shapeId}.exists`, time);
|
||||||
if (existsValue && existsValue > 0) {
|
if (existsValue && existsValue > 0) {
|
||||||
visibleShapes.push(shape);
|
visibleShapes.push(shape);
|
||||||
}
|
}
|
||||||
|
|
@ -3219,6 +3229,7 @@ class Layer extends Widget {
|
||||||
}
|
}
|
||||||
|
|
||||||
draw(ctx) {
|
draw(ctx) {
|
||||||
|
console.log(`[Layer.draw] CALLED - shapes:`, this.shapes ? this.shapes.length : 0);
|
||||||
// super.draw(ctx)
|
// super.draw(ctx)
|
||||||
if (!this.visible) return;
|
if (!this.visible) return;
|
||||||
let frameInfo = this.getFrameValue(this.frameNum);
|
let frameInfo = this.getFrameValue(this.frameNum);
|
||||||
|
|
@ -3233,16 +3244,93 @@ class Layer extends Widget {
|
||||||
t = (this.frameNum - frameInfo.prevIndex) / (frameInfo.nextIndex - frameInfo.prevIndex);
|
t = (this.frameNum - frameInfo.prevIndex) / (frameInfo.nextIndex - frameInfo.prevIndex);
|
||||||
}
|
}
|
||||||
|
|
||||||
// NEW: Draw shapes using AnimationData curves for exists and zOrder
|
// NEW: Draw shapes using AnimationData curves for exists, zOrder, and shape tweening
|
||||||
let currentTime = context.activeObject?.currentTime || 0;
|
let currentTime = context.activeObject?.currentTime || 0;
|
||||||
let visibleShapes = [];
|
|
||||||
|
|
||||||
|
// Group shapes by shapeId for tweening support
|
||||||
|
const shapesByShapeId = new Map();
|
||||||
for (let shape of this.shapes) {
|
for (let shape of this.shapes) {
|
||||||
// Check if shape exists at current time (>0 allows for future fade-in/out animations)
|
if (shape instanceof TempShape) continue;
|
||||||
let existsValue = this.animationData.interpolate(`shape.${shape.idx}.exists`, currentTime);
|
if (!shapesByShapeId.has(shape.shapeId)) {
|
||||||
if (existsValue !== null && existsValue > 0) {
|
shapesByShapeId.set(shape.shapeId, []);
|
||||||
let zOrder = this.animationData.interpolate(`shape.${shape.idx}.zOrder`, currentTime);
|
}
|
||||||
visibleShapes.push({ shape, zOrder: zOrder || 0 });
|
shapesByShapeId.get(shape.shapeId).push(shape);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Process each logical shape (shapeId)
|
||||||
|
let visibleShapes = [];
|
||||||
|
for (let [shapeId, shapes] of shapesByShapeId) {
|
||||||
|
console.log(`[Layer.draw] Processing shapeId ${shapeId}, have ${shapes.length} versions:`, shapes.map(s => ({idx: s.idx, shapeIndex: s.shapeIndex})));
|
||||||
|
|
||||||
|
// Check if this logical shape exists at current time
|
||||||
|
let existsValue = this.animationData.interpolate(`shape.${shapeId}.exists`, currentTime);
|
||||||
|
console.log(`[Layer.draw] existsValue for ${shapeId} at time ${currentTime}:`, existsValue);
|
||||||
|
if (existsValue === null || existsValue <= 0) continue;
|
||||||
|
|
||||||
|
// Get z-order
|
||||||
|
let zOrder = this.animationData.interpolate(`shape.${shapeId}.zOrder`, currentTime);
|
||||||
|
|
||||||
|
// Get shapeIndex curve and surrounding keyframes
|
||||||
|
const shapeIndexCurve = this.animationData.getCurve(`shape.${shapeId}.shapeIndex`);
|
||||||
|
if (!shapeIndexCurve || !shapeIndexCurve.keyframes || shapeIndexCurve.keyframes.length === 0) {
|
||||||
|
// No shapeIndex curve, just show shape with index 0
|
||||||
|
const shape = shapes.find(s => s.shapeIndex === 0);
|
||||||
|
if (shape) {
|
||||||
|
visibleShapes.push({ shape, zOrder: zOrder || 0, selected: context.shapeselection.includes(shape) });
|
||||||
|
}
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Find surrounding keyframes
|
||||||
|
const { prev: prevKf, next: nextKf } = getKeyframesSurrounding(shapeIndexCurve.keyframes, currentTime);
|
||||||
|
console.log(`[Layer.draw] Keyframes for ${shapeId}: prev=`, prevKf, 'next=', nextKf);
|
||||||
|
|
||||||
|
// Get interpolated value
|
||||||
|
let shapeIndexValue = shapeIndexCurve.interpolate(currentTime);
|
||||||
|
if (shapeIndexValue === null) shapeIndexValue = 0;
|
||||||
|
console.log(`[Layer.draw] shapeIndexValue at time ${currentTime}:`, shapeIndexValue);
|
||||||
|
|
||||||
|
// Sort shape versions by shapeIndex
|
||||||
|
shapes.sort((a, b) => a.shapeIndex - b.shapeIndex);
|
||||||
|
|
||||||
|
// Determine whether to morph based on whether interpolated value equals a keyframe value
|
||||||
|
// Check if we're at either the previous or next keyframe value (no morphing needed)
|
||||||
|
const atPrevKeyframe = prevKf && Math.abs(shapeIndexValue - prevKf.value) < 0.001;
|
||||||
|
const atNextKeyframe = nextKf && Math.abs(shapeIndexValue - nextKf.value) < 0.001;
|
||||||
|
console.log(`[Layer.draw] atPrevKeyframe=${atPrevKeyframe}, atNextKeyframe=${atNextKeyframe}`);
|
||||||
|
|
||||||
|
if (atPrevKeyframe || atNextKeyframe) {
|
||||||
|
// No morphing - display the shape at the keyframe value
|
||||||
|
const targetValue = atNextKeyframe ? nextKf.value : prevKf.value;
|
||||||
|
console.log(`[Layer.draw] Showing single shape with shapeIndex=${targetValue}`);
|
||||||
|
const shape = shapes.find(s => s.shapeIndex === targetValue);
|
||||||
|
if (shape) {
|
||||||
|
console.log(`[Layer.draw] Found shape with idx=${shape.idx}, shapeIndex=${shape.shapeIndex}`);
|
||||||
|
visibleShapes.push({ shape, zOrder: zOrder || 0, selected: context.shapeselection.includes(shape) });
|
||||||
|
} else {
|
||||||
|
console.warn(`[Layer.draw] Could not find shape with shapeIndex=${targetValue}`);
|
||||||
|
}
|
||||||
|
} else if (prevKf && nextKf && prevKf.value !== nextKf.value) {
|
||||||
|
// Morph between shapes specified by surrounding keyframes
|
||||||
|
const shape1 = shapes.find(s => s.shapeIndex === prevKf.value);
|
||||||
|
const shape2 = shapes.find(s => s.shapeIndex === nextKf.value);
|
||||||
|
|
||||||
|
if (shape1 && shape2) {
|
||||||
|
// Calculate t based on time position between keyframes
|
||||||
|
const t = (currentTime - prevKf.time) / (nextKf.time - prevKf.time);
|
||||||
|
const morphedShape = shape1.lerpShape(shape2, t);
|
||||||
|
visibleShapes.push({ shape: morphedShape, zOrder: zOrder || 0, selected: context.shapeselection.includes(shape1) || context.shapeselection.includes(shape2) });
|
||||||
|
} else if (shape1) {
|
||||||
|
visibleShapes.push({ shape: shape1, zOrder: zOrder || 0, selected: context.shapeselection.includes(shape1) });
|
||||||
|
} else if (shape2) {
|
||||||
|
visibleShapes.push({ shape: shape2, zOrder: zOrder || 0, selected: context.shapeselection.includes(shape2) });
|
||||||
|
}
|
||||||
|
} else if (nextKf) {
|
||||||
|
// Only next keyframe exists, show that shape
|
||||||
|
const shape = shapes.find(s => s.shapeIndex === nextKf.value);
|
||||||
|
if (shape) {
|
||||||
|
visibleShapes.push({ shape, zOrder: zOrder || 0, selected: context.shapeselection.includes(shape) });
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -3250,8 +3338,8 @@ class Layer extends Widget {
|
||||||
visibleShapes.sort((a, b) => a.zOrder - b.zOrder);
|
visibleShapes.sort((a, b) => a.zOrder - b.zOrder);
|
||||||
|
|
||||||
// Draw sorted shapes
|
// Draw sorted shapes
|
||||||
for (let { shape } of visibleShapes) {
|
for (let { shape, selected } of visibleShapes) {
|
||||||
cxt.selected = context.shapeselection.includes(shape);
|
cxt.selected = selected;
|
||||||
shape.draw(cxt);
|
shape.draw(cxt);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -3912,6 +4000,7 @@ class Shape extends BaseShape {
|
||||||
} else {
|
} else {
|
||||||
this.shapeId = shapeId;
|
this.shapeId = shapeId;
|
||||||
}
|
}
|
||||||
|
this.shapeIndex = 0; // Default shape version index for tweening
|
||||||
pointerList[this.idx] = this;
|
pointerList[this.idx] = this;
|
||||||
this.regionIdx = 0;
|
this.regionIdx = 0;
|
||||||
this.inProgress = true;
|
this.inProgress = true;
|
||||||
|
|
@ -3960,6 +4049,10 @@ class Shape extends BaseShape {
|
||||||
filled: region.filled,
|
filled: region.filled,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
// Load shapeIndex if present (for shape tweening)
|
||||||
|
if (json.shapeIndex !== undefined) {
|
||||||
|
shape.shapeIndex = json.shapeIndex;
|
||||||
|
}
|
||||||
return shape;
|
return shape;
|
||||||
}
|
}
|
||||||
toJSON(randomizeUuid = false) {
|
toJSON(randomizeUuid = false) {
|
||||||
|
|
@ -3983,6 +4076,7 @@ class Shape extends BaseShape {
|
||||||
json.idx = this.idx;
|
json.idx = this.idx;
|
||||||
}
|
}
|
||||||
json.shapeId = this.shapeId;
|
json.shapeId = this.shapeId;
|
||||||
|
json.shapeIndex = this.shapeIndex; // For shape tweening
|
||||||
json.curves = [];
|
json.curves = [];
|
||||||
for (let curve of this.curves) {
|
for (let curve of this.curves) {
|
||||||
json.curves.push(curve.toJSON(randomizeUuid));
|
json.curves.push(curve.toJSON(randomizeUuid));
|
||||||
|
|
@ -4536,7 +4630,7 @@ class GraphicsObject extends Widget {
|
||||||
for (let layer of this.layers) {
|
for (let layer of this.layers) {
|
||||||
for (let shape of layer.shapes) {
|
for (let shape of layer.shapes) {
|
||||||
// Check if shape exists at current time
|
// Check if shape exists at current time
|
||||||
let existsValue = layer.animationData.interpolate(`shape.${shape.idx}.exists`, currentTime);
|
let existsValue = layer.animationData.interpolate(`shape.${shape.shapeId}.exists`, currentTime);
|
||||||
if (existsValue !== null && existsValue > 0) {
|
if (existsValue !== null && existsValue > 0) {
|
||||||
if (!bbox) {
|
if (!bbox) {
|
||||||
bbox = structuredClone(shape.boundingBox);
|
bbox = structuredClone(shape.boundingBox);
|
||||||
|
|
@ -4604,9 +4698,9 @@ class GraphicsObject extends Widget {
|
||||||
|
|
||||||
for (let shape of layer.shapes) {
|
for (let shape of layer.shapes) {
|
||||||
if (shape instanceof TempShape) continue;
|
if (shape instanceof TempShape) continue;
|
||||||
let existsValue = layer.animationData.interpolate(`shape.${shape.idx}.exists`, currentTime);
|
let existsValue = layer.animationData.interpolate(`shape.${shape.shapeId}.exists`, currentTime);
|
||||||
if (existsValue !== null && existsValue > 0) {
|
if (existsValue !== null && existsValue > 0) {
|
||||||
let zOrder = layer.animationData.interpolate(`shape.${shape.idx}.zOrder`, currentTime);
|
let zOrder = layer.animationData.interpolate(`shape.${shape.shapeId}.zOrder`, currentTime);
|
||||||
visibleShapes.push({ shape, zOrder: zOrder || 0 });
|
visibleShapes.push({ shape, zOrder: zOrder || 0 });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -5044,7 +5138,7 @@ class GraphicsObject extends Widget {
|
||||||
|
|
||||||
// Get keyframes from all shape curves
|
// Get keyframes from all shape curves
|
||||||
for (let shape of layer.shapes) {
|
for (let shape of layer.shapes) {
|
||||||
const existsKey = `shape.${shape.idx}.exists`;
|
const existsKey = `shape.${shape.shapeId}.exists`;
|
||||||
const existsCurve = layer.animationData.curves[existsKey];
|
const existsCurve = layer.animationData.curves[existsKey];
|
||||||
if (existsCurve && existsCurve.keyframes) {
|
if (existsCurve && existsCurve.keyframes) {
|
||||||
for (let kf of existsCurve.keyframes) {
|
for (let kf of existsCurve.keyframes) {
|
||||||
|
|
@ -5855,6 +5949,13 @@ async function quit() {
|
||||||
}
|
}
|
||||||
|
|
||||||
function copy() {
|
function copy() {
|
||||||
|
// Phase 6: Check if timeline has selected keyframes first
|
||||||
|
if (context.timelineWidget && context.timelineWidget.copySelectedKeyframes()) {
|
||||||
|
// Keyframes were copied, don't copy objects/shapes
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Otherwise, copy objects and shapes as usual
|
||||||
clipboard = [];
|
clipboard = [];
|
||||||
for (let object of context.selection) {
|
for (let object of context.selection) {
|
||||||
clipboard.push(object.toJSON(true));
|
clipboard.push(object.toJSON(true));
|
||||||
|
|
@ -5865,6 +5966,13 @@ function copy() {
|
||||||
}
|
}
|
||||||
|
|
||||||
function paste() {
|
function paste() {
|
||||||
|
// Phase 6: Check if timeline has keyframes in clipboard first
|
||||||
|
if (context.timelineWidget && context.timelineWidget.pasteKeyframes()) {
|
||||||
|
// Keyframes were pasted
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Otherwise, paste objects and shapes as usual
|
||||||
// for (let item of clipboard) {
|
// for (let item of clipboard) {
|
||||||
// if (item instanceof GraphicsObject) {
|
// if (item instanceof GraphicsObject) {
|
||||||
// console.log(item);
|
// console.log(item);
|
||||||
|
|
@ -5902,6 +6010,8 @@ function addKeyframe() {
|
||||||
* For new timeline system (Phase 5)
|
* For new timeline system (Phase 5)
|
||||||
*/
|
*/
|
||||||
function addKeyframeAtPlayhead() {
|
function addKeyframeAtPlayhead() {
|
||||||
|
console.log('addKeyframeAtPlayhead called');
|
||||||
|
|
||||||
// Get the timeline widget and current time
|
// Get the timeline widget and current time
|
||||||
if (!context.timelineWidget) {
|
if (!context.timelineWidget) {
|
||||||
console.warn('Timeline widget not available');
|
console.warn('Timeline widget not available');
|
||||||
|
|
@ -5909,21 +6019,26 @@ function addKeyframeAtPlayhead() {
|
||||||
}
|
}
|
||||||
|
|
||||||
const currentTime = context.timelineWidget.timelineState.currentTime;
|
const currentTime = context.timelineWidget.timelineState.currentTime;
|
||||||
|
console.log(`Current time: ${currentTime}`);
|
||||||
|
|
||||||
// Determine which object to add keyframes to based on selection
|
// Determine which object to add keyframes to based on selection
|
||||||
let targetObjects = [];
|
let targetObjects = [];
|
||||||
|
|
||||||
// If shapes are selected, add keyframes to those shapes
|
// If shapes are selected, add keyframes to those shapes
|
||||||
if (context.shapeselection && context.shapeselection.length > 0) {
|
if (context.shapeselection && context.shapeselection.length > 0) {
|
||||||
|
console.log(`Found ${context.shapeselection.length} selected shapes`);
|
||||||
targetObjects = context.shapeselection;
|
targetObjects = context.shapeselection;
|
||||||
}
|
}
|
||||||
// If objects are selected, add keyframes to those objects
|
// If objects are selected, add keyframes to those objects
|
||||||
else if (context.selection && context.selection.length > 0) {
|
else if (context.selection && context.selection.length > 0) {
|
||||||
|
console.log(`Found ${context.selection.length} selected objects`);
|
||||||
targetObjects = context.selection;
|
targetObjects = context.selection;
|
||||||
}
|
}
|
||||||
// Otherwise, if no selection, don't do anything
|
// Otherwise, if no selection, don't do anything
|
||||||
else {
|
else {
|
||||||
console.log('No shapes or objects selected to add keyframes to');
|
console.log('No shapes or objects selected to add keyframes to');
|
||||||
|
console.log('context.shapeselection:', context.shapeselection);
|
||||||
|
console.log('context.selection:', context.selection);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -5964,38 +6079,105 @@ function addKeyframeAtPlayhead() {
|
||||||
|
|
||||||
if (!animationData) continue;
|
if (!animationData) continue;
|
||||||
|
|
||||||
// Get all curves for this object/shape by iterating through animationData.curves
|
// Special handling for shapes: duplicate shape with incremented shapeIndex
|
||||||
const curves = [];
|
if (isShape) {
|
||||||
const prefix = isShape ? `shape.${obj.idx}.` : `child.${obj.idx}.`;
|
// Find the layer that contains this shape
|
||||||
|
let parentLayer = null;
|
||||||
|
const findShapeLayerObj = (searchObj) => {
|
||||||
|
for (let layer of searchObj.children) {
|
||||||
|
if (layer.shapes && layer.shapes.includes(obj)) {
|
||||||
|
parentLayer = layer;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (layer.children) {
|
||||||
|
for (let child of layer.children) {
|
||||||
|
if (findShapeLayerObj(child)) return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
};
|
||||||
|
findShapeLayerObj(context.activeObject);
|
||||||
|
|
||||||
for (let curveName in animationData.curves) {
|
if (parentLayer) {
|
||||||
if (curveName.startsWith(prefix)) {
|
// Find the highest shapeIndex for this shapeId
|
||||||
curves.push(animationData.curves[curveName]);
|
const shapesWithSameId = parentLayer.shapes.filter(s => s.shapeId === obj.shapeId);
|
||||||
|
let maxShapeIndex = 0;
|
||||||
|
for (let shape of shapesWithSameId) {
|
||||||
|
maxShapeIndex = Math.max(maxShapeIndex, shape.shapeIndex || 0);
|
||||||
|
}
|
||||||
|
const newShapeIndex = maxShapeIndex + 1;
|
||||||
|
|
||||||
|
// Duplicate the shape with new shapeIndex
|
||||||
|
const shapeJSON = obj.toJSON(false); // Don't randomize UUIDs
|
||||||
|
shapeJSON.idx = uuidv4(); // But do create a new idx for the duplicate
|
||||||
|
shapeJSON.shapeIndex = newShapeIndex;
|
||||||
|
const newShape = Shape.fromJSON(shapeJSON, parentLayer);
|
||||||
|
parentLayer.shapes.push(newShape);
|
||||||
|
|
||||||
|
// Add keyframes to all shape curves (exists, zOrder, shapeIndex)
|
||||||
|
// This allows controlling timing, z-order, and morphing
|
||||||
|
const existsCurve = animationData.getOrCreateCurve(`shape.${obj.shapeId}.exists`);
|
||||||
|
const existsValue = existsCurve.interpolate(currentTime);
|
||||||
|
if (existsValue === null) {
|
||||||
|
// No previous keyframe, default to visible
|
||||||
|
existsCurve.addKeyframe(new Keyframe(currentTime, 1, 'hold'));
|
||||||
|
} else {
|
||||||
|
// Add keyframe with current interpolated value
|
||||||
|
existsCurve.addKeyframe(new Keyframe(currentTime, existsValue, 'hold'));
|
||||||
|
}
|
||||||
|
|
||||||
|
const zOrderCurve = animationData.getOrCreateCurve(`shape.${obj.shapeId}.zOrder`);
|
||||||
|
const zOrderValue = zOrderCurve.interpolate(currentTime);
|
||||||
|
if (zOrderValue === null) {
|
||||||
|
// No previous keyframe, find current z-order from layer
|
||||||
|
const currentZOrder = parentLayer.shapes.indexOf(obj);
|
||||||
|
zOrderCurve.addKeyframe(new Keyframe(currentTime, currentZOrder, 'hold'));
|
||||||
|
} else {
|
||||||
|
// Add keyframe with current interpolated value
|
||||||
|
zOrderCurve.addKeyframe(new Keyframe(currentTime, zOrderValue, 'hold'));
|
||||||
|
}
|
||||||
|
|
||||||
|
const shapeIndexCurve = animationData.getOrCreateCurve(`shape.${obj.shapeId}.shapeIndex`);
|
||||||
|
const shapeIndexKeyframe = new Keyframe(currentTime, newShapeIndex, 'linear');
|
||||||
|
shapeIndexCurve.addKeyframe(shapeIndexKeyframe);
|
||||||
|
|
||||||
|
console.log(`Created new shape version with shapeIndex ${newShapeIndex} at time ${currentTime}`);
|
||||||
}
|
}
|
||||||
}
|
} else {
|
||||||
|
// For objects (not shapes), add keyframes to all curves
|
||||||
|
const curves = [];
|
||||||
|
const prefix = `child.${obj.idx}.`;
|
||||||
|
|
||||||
// For each curve, add a keyframe at the current time with the interpolated value
|
for (let curveName in animationData.curves) {
|
||||||
for (let curve of curves) {
|
if (curveName.startsWith(prefix)) {
|
||||||
// Get the current interpolated value at this time
|
curves.push(animationData.curves[curveName]);
|
||||||
const currentValue = curve.interpolate(currentTime);
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Check if there's already a keyframe at this exact time
|
// For each curve, add a keyframe at the current time with the interpolated value
|
||||||
const existingKeyframe = curve.keyframes.find(kf => Math.abs(kf.time - currentTime) < 0.001);
|
for (let curve of curves) {
|
||||||
|
// Get the current interpolated value at this time
|
||||||
|
const currentValue = curve.interpolate(currentTime);
|
||||||
|
|
||||||
if (existingKeyframe) {
|
// Check if there's already a keyframe at this exact time
|
||||||
// Update the existing keyframe's value
|
const existingKeyframe = curve.keyframes.find(kf => Math.abs(kf.time - currentTime) < 0.001);
|
||||||
existingKeyframe.value = currentValue;
|
|
||||||
console.log(`Updated keyframe at time ${currentTime} on ${curve.parameter}`);
|
|
||||||
} else {
|
|
||||||
// Create a new keyframe
|
|
||||||
const newKeyframe = new Keyframe(
|
|
||||||
currentTime,
|
|
||||||
currentValue,
|
|
||||||
'linear' // Default to linear interpolation
|
|
||||||
);
|
|
||||||
|
|
||||||
curve.addKeyframe(newKeyframe);
|
if (existingKeyframe) {
|
||||||
console.log(`Added keyframe at time ${currentTime} on ${curve.parameter} with value ${currentValue}`);
|
// Update the existing keyframe's value
|
||||||
|
existingKeyframe.value = currentValue;
|
||||||
|
console.log(`Updated keyframe at time ${currentTime} on ${curve.parameter}`);
|
||||||
|
} else {
|
||||||
|
// Create a new keyframe
|
||||||
|
const newKeyframe = new Keyframe(
|
||||||
|
currentTime,
|
||||||
|
currentValue,
|
||||||
|
'linear' // Default to linear interpolation
|
||||||
|
);
|
||||||
|
|
||||||
|
curve.addKeyframe(newKeyframe);
|
||||||
|
console.log(`Added keyframe at time ${currentTime} on ${curve.parameter} with value ${currentValue}`);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -9463,9 +9645,10 @@ async function renderMenu() {
|
||||||
};
|
};
|
||||||
newKeyframeMenuItem = {
|
newKeyframeMenuItem = {
|
||||||
text: "New Keyframe",
|
text: "New Keyframe",
|
||||||
enabled: !activeKeyframe,
|
enabled: (context.selection && context.selection.length > 0) ||
|
||||||
|
(context.shapeselection && context.shapeselection.length > 0),
|
||||||
accelerator: getShortcut("addKeyframe"),
|
accelerator: getShortcut("addKeyframe"),
|
||||||
action: addKeyframe,
|
action: addKeyframeAtPlayhead,
|
||||||
};
|
};
|
||||||
newBlankKeyframeMenuItem = {
|
newBlankKeyframeMenuItem = {
|
||||||
text: "New Blank Keyframe",
|
text: "New Blank Keyframe",
|
||||||
|
|
|
||||||
|
|
@ -418,10 +418,21 @@ class TrackHierarchy {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Add shapes
|
// Add shapes (grouped by shapeId for shape tweening)
|
||||||
if (layer.shapes) {
|
if (layer.shapes) {
|
||||||
|
// Group shapes by shapeId
|
||||||
|
const shapesByShapeId = new Map();
|
||||||
for (let shape of layer.shapes) {
|
for (let shape of layer.shapes) {
|
||||||
this.addShapeTrack(shape, 1)
|
if (!shapesByShapeId.has(shape.shapeId)) {
|
||||||
|
shapesByShapeId.set(shape.shapeId, []);
|
||||||
|
}
|
||||||
|
shapesByShapeId.get(shape.shapeId).push(shape);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add one track per unique shapeId
|
||||||
|
for (let [shapeId, shapes] of shapesByShapeId) {
|
||||||
|
// Use the first shape as the representative for the track
|
||||||
|
this.addShapeTrack(shapes[0], 1, shapeId, shapes)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -463,8 +474,18 @@ class TrackHierarchy {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (layer.shapes) {
|
if (layer.shapes) {
|
||||||
|
// Group shapes by shapeId
|
||||||
|
const shapesByShapeId = new Map();
|
||||||
for (let shape of layer.shapes) {
|
for (let shape of layer.shapes) {
|
||||||
this.addShapeTrack(shape, indent + 2)
|
if (!shapesByShapeId.has(shape.shapeId)) {
|
||||||
|
shapesByShapeId.set(shape.shapeId, []);
|
||||||
|
}
|
||||||
|
shapesByShapeId.get(shape.shapeId).push(shape);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add one track per unique shapeId
|
||||||
|
for (let [shapeId, shapes] of shapesByShapeId) {
|
||||||
|
this.addShapeTrack(shapes[0], indent + 2, shapeId, shapes)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -473,12 +494,14 @@ class TrackHierarchy {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Add shape track
|
* Add shape track (grouped by shapeId for shape tweening)
|
||||||
*/
|
*/
|
||||||
addShapeTrack(shape, indent) {
|
addShapeTrack(shape, indent, shapeId, shapes) {
|
||||||
const track = {
|
const track = {
|
||||||
type: 'shape',
|
type: 'shape',
|
||||||
object: shape,
|
object: shape, // Representative shape for display
|
||||||
|
shapeId: shapeId, // The shared shapeId
|
||||||
|
shapes: shapes, // All shape versions with this shapeId
|
||||||
name: shape.constructor.name || 'Shape',
|
name: shape.constructor.name || 'Shape',
|
||||||
indent: indent
|
indent: indent
|
||||||
}
|
}
|
||||||
|
|
@ -500,12 +523,9 @@ class TrackHierarchy {
|
||||||
|
|
||||||
// Calculate additional height needed for curves
|
// Calculate additional height needed for curves
|
||||||
if (obj.curvesMode === 'minimized') {
|
if (obj.curvesMode === 'minimized') {
|
||||||
// Count curves for this object/shape
|
// Phase 6: Minimized mode should be compact - no extra height
|
||||||
// For minimized mode: 15px per curve
|
// Keyframes are overlaid on the segment bar
|
||||||
// This is a simplified calculation - actual curve count would require AnimationData lookup
|
return baseHeight
|
||||||
// For now, assume 3-5 curves per object (x, y, rotation, etc)
|
|
||||||
const estimatedCurves = 5
|
|
||||||
return baseHeight + (estimatedCurves * 15) + 10 // +10 for padding
|
|
||||||
} else if (obj.curvesMode === 'expanded') {
|
} else if (obj.curvesMode === 'expanded') {
|
||||||
// Use the object's curvesHeight property
|
// Use the object's curvesHeight property
|
||||||
return baseHeight + (obj.curvesHeight || 150) + 10 // +10 for padding
|
return baseHeight + (obj.curvesHeight || 150) + 10 // +10 for padding
|
||||||
|
|
|
||||||
1198
src/widgets.js
1198
src/widgets.js
File diff suppressed because it is too large
Load Diff
Loading…
Reference in New Issue