1
0
Fork 0
muzika-gromche/Frontend/src/lib/useTimelineTicks.ts

106 lines
3.0 KiB
TypeScript

import { formatTime } from "@/lib/AudioTrack";
import { rangeInclusive } from "@/lib/iter";
import {
useOptimalBeatTickInterval,
useOptimalTickInterval,
useTicksBounds,
} from "@/lib/TinelineTicks";
import { usePx } from "@/lib/vue";
import { useTimelineStore } from "@/store/TimelineStore";
import { storeToRefs } from "pinia";
import {
computed,
type ComputedRef,
type MaybeRefOrGetter,
toValue,
} from "vue";
import type { AnyTime, Beats, Px, Seconds } from "./units";
export interface Ticks<T extends AnyTime> {
tickIn: ComputedRef<T>;
tickOut: ComputedRef<T>;
interval: ComputedRef<T>;
/** An inclusive range from tickIn to tickOut, with `interval` step. */
ticks: ComputedRef<T[]>;
width: ComputedRef<Px>;
widthPx: ComputedRef<string>;
left: (tickIn: T) => ComputedRef<Px>;
label: (tickIn: T) => ComputedRef<string>;
}
export function useTimelineTicks<T extends AnyTime>(
viewportIn: MaybeRefOrGetter<T>,
viewportOut: MaybeRefOrGetter<T>,
interval: ComputedRef<T>,
intervalToPixels: (interval: T) => Px,
positionToPixels: (position: T) => Px,
positionToLabel: (position: T) => string,
): Ticks<T> {
const ticksBounds = useTicksBounds(interval, viewportIn, viewportOut);
const tickIn = computed(() => ticksBounds.value.tickIn);
const tickOut = computed(() => ticksBounds.value.tickOut);
const ticks = computed(() =>
rangeInclusive(tickIn.value, tickOut.value, toValue(interval)) as T[]
);
const width = computed(() => intervalToPixels(toValue(interval)));
const widthPx = usePx(width);
return {
tickIn,
tickOut,
interval,
ticks,
width,
widthPx,
left: (tickIn) => computed(() => positionToPixels(tickIn)),
label: (tickIn) => computed(() => positionToLabel(tickIn)),
};
}
// TODO: cache / singletone / store?
export function useTimelineTicksSeconds(): Ticks<Seconds> {
const timeline = useTimelineStore();
const {
contentWidthIncludingEmptySpace,
durationIncludingEmptySpace,
viewportInSeconds,
viewportOutSeconds,
} = storeToRefs(timeline);
return useTimelineTicks<Seconds>(
viewportInSeconds,
viewportOutSeconds,
useOptimalTickInterval(
contentWidthIncludingEmptySpace,
durationIncludingEmptySpace,
),
(interval) => timeline.secondsToPixels(interval),
(position) => timeline.secondsToPixels(position),
(position) => formatTime(position, 2),
);
}
export function useTimelineTicksBeats(): Ticks<Beats> {
const timeline = useTimelineStore();
const {
contentWidthIncludingEmptySpace,
durationBeatsIncludingEmptySpace,
viewportInLoopOffsetBeats,
viewportOutLoopOffsetBeats,
} = storeToRefs(timeline);
return useTimelineTicks<Beats>(
viewportInLoopOffsetBeats,
viewportOutLoopOffsetBeats,
useOptimalBeatTickInterval(
contentWidthIncludingEmptySpace,
durationBeatsIncludingEmptySpace,
),
(interval) => timeline.beatsToPixels(interval),
(position) => timeline.loopOffsetBeatsToPixels(position),
(position) => position.toFixed(),
);
}