fix: resize not matching with frames

This commit is contained in:
Maze Winther
2025-08-01 09:51:54 +02:00
parent b00d6d5fb6
commit 09cb8c8d98
2 changed files with 78 additions and 53 deletions
@@ -68,45 +68,51 @@ export function TimelineTrackContent({
elementDuration: number, elementDuration: number,
excludeElementId?: string excludeElementId?: string
) => { ) => {
if (!snappingEnabled) { // Always apply frame snapping first
// Use frame snapping if project has FPS, otherwise use decimal snapping const projectStore = useProjectStore.getState();
const projectStore = useProjectStore.getState(); const projectFps = projectStore.activeProject?.fps || 30;
const projectFps = projectStore.activeProject?.fps || 30; let finalTime = snapTimeToFrame(dropTime, projectFps);
return snapTimeToFrame(dropTime, projectFps);
// Additionally apply element snapping if enabled
if (snappingEnabled) {
// Try snapping both start and end edges for drops
const startSnapResult = snapElementEdge(
dropTime,
elementDuration,
tracks,
currentTime,
zoomLevel,
excludeElementId,
true // snap to start edge
);
const endSnapResult = snapElementEdge(
dropTime,
elementDuration,
tracks,
currentTime,
zoomLevel,
excludeElementId,
false // snap to end edge
);
// Choose the snap result with the smaller distance (closer snap)
let bestSnapResult = startSnapResult;
if (
endSnapResult.snapPoint &&
(!startSnapResult.snapPoint ||
endSnapResult.snapDistance < startSnapResult.snapDistance)
) {
bestSnapResult = endSnapResult;
}
// Only use element snapping if it found a snap point, otherwise keep frame-snapped time
if (bestSnapResult.snapPoint) {
finalTime = bestSnapResult.snappedTime;
}
} }
// Try snapping both start and end edges for drops return finalTime;
const startSnapResult = snapElementEdge(
dropTime,
elementDuration,
tracks,
currentTime,
zoomLevel,
excludeElementId,
true // snap to start edge
);
const endSnapResult = snapElementEdge(
dropTime,
elementDuration,
tracks,
currentTime,
zoomLevel,
excludeElementId,
false // snap to end edge
);
// Choose the snap result with the smaller distance (closer snap)
let bestSnapResult = startSnapResult;
if (
endSnapResult.snapPoint &&
(!startSnapResult.snapPoint ||
endSnapResult.snapDistance < startSnapResult.snapDistance)
) {
bestSnapResult = endSnapResult;
}
return bestSnapResult.snappedTime;
}; };
const timelineRef = useRef<HTMLDivElement>(null); const timelineRef = useRef<HTMLDivElement>(null);
@@ -148,9 +154,13 @@ export function TimelineTrackContent({
); );
const adjustedTime = Math.max(0, mouseTime - dragState.clickOffsetTime); const adjustedTime = Math.max(0, mouseTime - dragState.clickOffsetTime);
// Apply snapping if enabled // Always apply frame snapping first
let finalTime = adjustedTime; const projectStore = useProjectStore.getState();
const projectFps = projectStore.activeProject?.fps || 30;
let finalTime = snapTimeToFrame(adjustedTime, projectFps);
let snapPoint = null; let snapPoint = null;
// Additionally apply element snapping if enabled
if (snappingEnabled) { if (snappingEnabled) {
// Find the element being dragged to get its duration // Find the element being dragged to get its duration
let elementDuration = 5; // fallback duration let elementDuration = 5; // fallback duration
@@ -196,18 +206,16 @@ export function TimelineTrackContent({
bestSnapResult = endSnapResult; bestSnapResult = endSnapResult;
} }
finalTime = bestSnapResult.snappedTime; // Only use element snapping if it found a snap point, otherwise keep frame-snapped time
snapPoint = bestSnapResult.snapPoint; if (bestSnapResult.snapPoint) {
finalTime = bestSnapResult.snappedTime;
snapPoint = bestSnapResult.snapPoint;
}
// Notify parent component about snap point change // Notify parent component about snap point change
onSnapPointChange?.(snapPoint); onSnapPointChange?.(snapPoint);
} else { } else {
// Use frame snapping if project has FPS, otherwise use decimal snapping // Clear snap point when element snapping is disabled
const projectStore = useProjectStore.getState();
const projectFps = projectStore.activeProject?.fps || 30;
finalTime = snapTimeToFrame(adjustedTime, projectFps);
// Clear snap point when not snapping
onSnapPointChange?.(null); onSnapPointChange?.(null);
} }
@@ -221,17 +221,34 @@ export function useTimelineElementResize({
} }
} else { } else {
// Normal trimming within original duration // Normal trimming within original duration
const maxTrimEnd = element.duration - resizing.initialTrimStart - 0.1; // Leave at least 0.1s visible // Calculate the desired end time based on mouse movement
const newTrimEnd = snapTimeToFrame( const currentEndTime =
Math.max(0, Math.min(maxTrimEnd, calculated)), element.startTime +
projectFps element.duration -
element.trimStart -
element.trimEnd;
const desiredEndTime = currentEndTime + deltaTime;
// Snap the desired end time to frame
const snappedEndTime = snapTimeToFrame(desiredEndTime, projectFps);
// Calculate what trimEnd should be to achieve this snapped end time
const newTrimEnd = Math.max(
0,
element.duration -
element.trimStart -
(snappedEndTime - element.startTime)
); );
// Ensure we don't trim more than available content (leave at least 0.1s visible)
const maxTrimEnd = element.duration - element.trimStart - 0.1;
const finalTrimEnd = Math.min(maxTrimEnd, newTrimEnd);
updateElementTrim( updateElementTrim(
track.id, track.id,
element.id, element.id,
resizing.initialTrimStart, element.trimStart,
newTrimEnd, finalTrimEnd,
false false
); );
} }