feat: exponential zoom
This commit is contained in:
parent
22ea6b8925
commit
c3f0d640ef
|
|
@ -16,6 +16,7 @@ import {
|
|||
import { Slider } from "@/components/ui/slider";
|
||||
import { formatTimeCode } from "@/lib/time";
|
||||
import { TIMELINE_CONSTANTS } from "@/constants/timeline-constants";
|
||||
import { sliderToZoom, zoomToSlider } from "@/lib/timeline/zoom-utils";
|
||||
import { EditableTimecode } from "@/components/editable-timecode";
|
||||
import { ScenesView } from "../scenes-view";
|
||||
import { type TAction, invokeAction } from "@/lib/actions";
|
||||
|
|
@ -50,13 +51,11 @@ export function TimelineToolbar({
|
|||
setZoomLevel: ({ zoom }: { zoom: number }) => void;
|
||||
}) {
|
||||
const handleZoom = ({ direction }: { direction: "in" | "out" }) => {
|
||||
const zoomFactor = 1.25;
|
||||
const newZoomLevel =
|
||||
direction === "in"
|
||||
? Math.min(
|
||||
TIMELINE_CONSTANTS.ZOOM_MAX,
|
||||
zoomLevel + TIMELINE_CONSTANTS.ZOOM_STEP,
|
||||
)
|
||||
: Math.max(minZoom, zoomLevel - TIMELINE_CONSTANTS.ZOOM_STEP);
|
||||
? Math.min(TIMELINE_CONSTANTS.ZOOM_MAX, zoomLevel * zoomFactor)
|
||||
: Math.max(minZoom, zoomLevel / zoomFactor);
|
||||
setZoomLevel({ zoom: newZoomLevel });
|
||||
};
|
||||
|
||||
|
|
@ -127,17 +126,13 @@ function ToolbarLeftSection() {
|
|||
<ToolbarButton
|
||||
icon={<HugeiconsIcon icon={ScissorIcon} />}
|
||||
tooltip="Split element"
|
||||
onClick={({ event }) =>
|
||||
handleAction({ action: "split", event })
|
||||
}
|
||||
onClick={({ event }) => handleAction({ action: "split", event })}
|
||||
/>
|
||||
|
||||
<ToolbarButton
|
||||
icon={<HugeiconsIcon icon={AlignLeftIcon} />}
|
||||
tooltip="Split left"
|
||||
onClick={({ event }) =>
|
||||
handleAction({ action: "split-left", event })
|
||||
}
|
||||
onClick={({ event }) => handleAction({ action: "split-left", event })}
|
||||
/>
|
||||
|
||||
<ToolbarButton
|
||||
|
|
@ -305,12 +300,14 @@ function ToolbarRightSection({
|
|||
<HugeiconsIcon icon={SearchAddIcon} />
|
||||
</Button>
|
||||
<Slider
|
||||
className="w-24"
|
||||
value={[zoomLevel]}
|
||||
onValueChange={(values) => onZoomChange(values[0])}
|
||||
min={minZoom}
|
||||
max={TIMELINE_CONSTANTS.ZOOM_MAX}
|
||||
step={TIMELINE_CONSTANTS.ZOOM_STEP}
|
||||
className="w-28"
|
||||
value={[zoomToSlider({ zoomLevel, minZoom })]}
|
||||
onValueChange={(values) =>
|
||||
onZoomChange(sliderToZoom({ sliderPosition: values[0], minZoom }))
|
||||
}
|
||||
min={0}
|
||||
max={1}
|
||||
step={0.005}
|
||||
/>
|
||||
<Button
|
||||
variant="text"
|
||||
|
|
|
|||
|
|
@ -51,3 +51,36 @@ export function getZoomPercent({
|
|||
}): number {
|
||||
return (zoomLevel - minZoom) / (TIMELINE_CONSTANTS.ZOOM_MAX - minZoom);
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert linear slider position (0-1) to exponential zoom level.
|
||||
* At low slider values, zoom changes are small. At high values, changes are large.
|
||||
*/
|
||||
export function sliderToZoom({
|
||||
sliderPosition,
|
||||
minZoom,
|
||||
maxZoom = TIMELINE_CONSTANTS.ZOOM_MAX,
|
||||
}: {
|
||||
sliderPosition: number;
|
||||
minZoom: number;
|
||||
maxZoom?: number;
|
||||
}): number {
|
||||
const clampedPosition = Math.max(0, Math.min(1, sliderPosition));
|
||||
return minZoom * (maxZoom / minZoom) ** clampedPosition;
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert exponential zoom level to linear slider position (0-1).
|
||||
*/
|
||||
export function zoomToSlider({
|
||||
zoomLevel,
|
||||
minZoom,
|
||||
maxZoom = TIMELINE_CONSTANTS.ZOOM_MAX,
|
||||
}: {
|
||||
zoomLevel: number;
|
||||
minZoom: number;
|
||||
maxZoom?: number;
|
||||
}): number {
|
||||
const clampedZoom = Math.max(minZoom, Math.min(maxZoom, zoomLevel));
|
||||
return Math.log(clampedZoom / minZoom) / Math.log(maxZoom / minZoom);
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue