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 { tickIn: ComputedRef; tickOut: ComputedRef; interval: ComputedRef; /** An inclusive range from tickIn to tickOut, with `interval` step. */ ticks: ComputedRef; width: ComputedRef; widthPx: ComputedRef; left: (tickIn: T) => ComputedRef; label: (tickIn: T) => ComputedRef; } export function useTimelineTicks( viewportIn: MaybeRefOrGetter, viewportOut: MaybeRefOrGetter, interval: ComputedRef, intervalToPixels: (interval: T) => Px, positionToPixels: (position: T) => Px, positionToLabel: (position: T) => string, ): Ticks { 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 { const timeline = useTimelineStore(); const { contentWidthIncludingEmptySpace, durationIncludingEmptySpace, viewportInSeconds, viewportOutSeconds, } = storeToRefs(timeline); return useTimelineTicks( viewportInSeconds, viewportOutSeconds, useOptimalTickInterval( contentWidthIncludingEmptySpace, durationIncludingEmptySpace, ), (interval) => timeline.secondsToPixels(interval), (position) => timeline.secondsToPixels(position), (position) => formatTime(position, 2), ); } export function useTimelineTicksBeats(): Ticks { const timeline = useTimelineStore(); const { contentWidthIncludingEmptySpace, durationBeatsIncludingEmptySpace, viewportInLoopOffsetBeats, viewportOutLoopOffsetBeats, } = storeToRefs(timeline); return useTimelineTicks( viewportInLoopOffsetBeats, viewportOutLoopOffsetBeats, useOptimalBeatTickInterval( contentWidthIncludingEmptySpace, durationBeatsIncludingEmptySpace, ), (interval) => timeline.beatsToPixels(interval), (position) => timeline.loopOffsetBeatsToPixels(position), (position) => position.toFixed(), ); }