From ef829236526ca73f07f6f8081a4161d655349f9e Mon Sep 17 00:00:00 2001 From: Julian Bilcke Date: Thu, 5 Sep 2024 15:45:12 +0200 Subject: [PATCH] fix playhead width --- .../src/components/core/timeline/Slider.tsx | 47 +++++++++++++++++++ .../components/core/timeline/TimelineZoom.tsx | 37 +++++++++++++++ .../components/core/waveform/useWaveform.ts | 6 +-- .../src/components/monitor/VUMeter/index.tsx | 2 +- packages/app/src/components/monitor/index.tsx | 2 +- .../components/toolbars/bottom-bar/index.tsx | 2 + .../toolbars/bottom-bar/tasks/index.tsx | 3 +- packages/app/src/lib/core/constants.ts | 2 +- packages/app/src/lib/hooks/index.ts | 1 + packages/app/src/lib/hooks/useDebounceFn.ts | 34 ++++++++++++++ .../services/assistant/useVoiceAssistant.ts | 1 - .../src/services/renderer/useRenderLoop.ts | 1 - .../src/components/timeline/Cells.tsx | 34 ++++---------- .../src/components/timeline/Cursor.tsx | 22 ++++++--- .../src/components/timeline/CursorWeird.tsx | 21 --------- .../timeline/src/components/timeline/index.ts | 2 +- packages/timeline/src/constants/grid.ts | 8 ++++ packages/timeline/src/constants/themes.ts | 2 +- 18 files changed, 162 insertions(+), 65 deletions(-) create mode 100644 packages/app/src/components/core/timeline/Slider.tsx create mode 100644 packages/app/src/components/core/timeline/TimelineZoom.tsx create mode 100644 packages/app/src/lib/hooks/useDebounceFn.ts delete mode 100644 packages/timeline/src/components/timeline/CursorWeird.tsx diff --git a/packages/app/src/components/core/timeline/Slider.tsx b/packages/app/src/components/core/timeline/Slider.tsx new file mode 100644 index 00000000..566ec206 --- /dev/null +++ b/packages/app/src/components/core/timeline/Slider.tsx @@ -0,0 +1,47 @@ +import * as React from 'react' +import * as SliderPrimitive from '@radix-ui/react-slider' +import { cn } from '@/lib/utils' + +const Slider = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef & { + trackClass?: string + rangeClass?: string + thumbClass?: string + } +>(({ className, trackClass, rangeClass, thumbClass, ...props }, ref) => ( + + + + + + +)) +Slider.displayName = 'Slider' + +export { Slider } diff --git a/packages/app/src/components/core/timeline/TimelineZoom.tsx b/packages/app/src/components/core/timeline/TimelineZoom.tsx new file mode 100644 index 00000000..76286dbe --- /dev/null +++ b/packages/app/src/components/core/timeline/TimelineZoom.tsx @@ -0,0 +1,37 @@ +import * as React from 'react' +import { BiSolidZoomIn, BiSolidZoomOut } from 'react-icons/bi' +import { useTimeline } from '@aitube/timeline' + +import { cn } from '@/lib/utils' + +import { Slider } from './Slider' +import { useDebounceFn } from '@/lib/hooks' + +export function TimelineZoom() { + const setHorizontalZoomLevel = useTimeline((s) => s.setHorizontalZoomLevel) + const minHorizontalZoomLevel = useTimeline((s) => s.minHorizontalZoomLevel) + const maxHorizontalZoomLevel = useTimeline((s) => s.maxHorizontalZoomLevel) + + const onValueChange = useDebounceFn((values: number[]) => { + setHorizontalZoomLevel(values[0]) + }, 250) + + return ( +
+ + + +
+ ) +} diff --git a/packages/app/src/components/core/waveform/useWaveform.ts b/packages/app/src/components/core/waveform/useWaveform.ts index 8c4bef20..605680ff 100644 --- a/packages/app/src/components/core/waveform/useWaveform.ts +++ b/packages/app/src/components/core/waveform/useWaveform.ts @@ -73,14 +73,14 @@ export function useWaveform({ const cachedImage = cache[cacheKey] - console.log(`redraw() for cache key "${cacheKey}"`) + // console.log(`redraw() for cache key "${cacheKey}"`) if (canvasRenderingContext) { if (cachedImage) { - console.log('redraw() we have a cached image:', cachedImage) + // console.log('redraw() we have a cached image:', cachedImage) // If we have cached data canvasRenderingContext.putImageData(cachedImage, 0, 0) } else { - console.log('redraw() no cached image') + // console.log('redraw() no cached image') const middle = mode === WaveformRenderingMode.MONO ? height : height / 2 const channelData = audioBuffer.getChannelData(0) const step = Math.ceil(channelData.length / (width * zoom)) diff --git a/packages/app/src/components/monitor/VUMeter/index.tsx b/packages/app/src/components/monitor/VUMeter/index.tsx index a05c31c3..fa9528bd 100644 --- a/packages/app/src/components/monitor/VUMeter/index.tsx +++ b/packages/app/src/components/monitor/VUMeter/index.tsx @@ -130,4 +130,4 @@ export function VUMeter({ className = 'w-24 h-full' }) { ) -} \ No newline at end of file +} diff --git a/packages/app/src/components/monitor/index.tsx b/packages/app/src/components/monitor/index.tsx index 976517c9..18650962 100644 --- a/packages/app/src/components/monitor/index.tsx +++ b/packages/app/src/components/monitor/index.tsx @@ -57,7 +57,7 @@ export function Monitor() { `flex h-full flex-col items-center justify-between overflow-hidden` )} > - + ) diff --git a/packages/app/src/components/toolbars/bottom-bar/index.tsx b/packages/app/src/components/toolbars/bottom-bar/index.tsx index 46f6fbc6..97caeb90 100644 --- a/packages/app/src/components/toolbars/bottom-bar/index.tsx +++ b/packages/app/src/components/toolbars/bottom-bar/index.tsx @@ -5,6 +5,7 @@ import { Metrics } from './metrics' import { APP_REVISION } from '@/lib/core/constants' import { Tasks } from './tasks' import { useTimeline } from '@aitube/timeline' +import { TimelineZoom } from '@/components/core/timeline/TimelineZoom' export function BottomToolbar() { const theme = useTheme() @@ -57,6 +58,7 @@ export function BottomToolbar() {
+
diff --git a/packages/app/src/components/toolbars/bottom-bar/tasks/index.tsx b/packages/app/src/components/toolbars/bottom-bar/tasks/index.tsx index 3b0d917f..934c5123 100644 --- a/packages/app/src/components/toolbars/bottom-bar/tasks/index.tsx +++ b/packages/app/src/components/toolbars/bottom-bar/tasks/index.tsx @@ -36,12 +36,13 @@ export function Tasks() { - + {nbRunningBackgroundTasks || 'no'} pending tasks diff --git a/packages/app/src/lib/core/constants.ts b/packages/app/src/lib/core/constants.ts index 394adae7..39a3390c 100644 --- a/packages/app/src/lib/core/constants.ts +++ b/packages/app/src/lib/core/constants.ts @@ -3,7 +3,7 @@ export const HARD_LIMIT_NB_MAX_ASSETS_TO_GENERATE_IN_PARALLEL = 32 export const APP_NAME = 'Clapper.app' -export const APP_REVISION = '20240904+0113' +export const APP_REVISION = '20240905+1543' export const APP_DOMAIN = 'Clapper.app' export const APP_LINK = 'https://clapper.app' diff --git a/packages/app/src/lib/hooks/index.ts b/packages/app/src/lib/hooks/index.ts index b4ef642f..18a9887e 100644 --- a/packages/app/src/lib/hooks/index.ts +++ b/packages/app/src/lib/hooks/index.ts @@ -1,4 +1,5 @@ export { useDebounce } from './useDebounce' +export { useDebounceFn } from './useDebounceFn' export { useOpenFilePicker } from './useOpenFilePicker' export { useFullscreenStatus } from './useFullscreenStatus' export { useQueryStringParams } from './useQueryStringParams' diff --git a/packages/app/src/lib/hooks/useDebounceFn.ts b/packages/app/src/lib/hooks/useDebounceFn.ts new file mode 100644 index 00000000..0a73dfdd --- /dev/null +++ b/packages/app/src/lib/hooks/useDebounceFn.ts @@ -0,0 +1,34 @@ +import { useRef, useEffect } from 'react' + +type Timer = ReturnType +type SomeFunction = (...args: any[]) => void +/** + * + * @param func The original, non debounced function (You can pass any number of args to it) + * @param delay The delay (in ms) for the function to return + * @returns The debounced function, which will run only if the debounced function has not been called in the last (delay) ms + */ + +export function useDebounceFn( + func: Func, + delay = 1000 +) { + const timer = useRef() + + useEffect(() => { + return () => { + if (!timer.current) return + clearTimeout(timer.current) + } + }, []) + + const debouncedFunction = ((...args) => { + const newTimer = setTimeout(() => { + func(...args) + }, delay) + clearTimeout(timer.current) + timer.current = newTimer + }) as Func + + return debouncedFunction +} diff --git a/packages/app/src/services/assistant/useVoiceAssistant.ts b/packages/app/src/services/assistant/useVoiceAssistant.ts index e5a9a6ec..61a8f75d 100644 --- a/packages/app/src/services/assistant/useVoiceAssistant.ts +++ b/packages/app/src/services/assistant/useVoiceAssistant.ts @@ -2,7 +2,6 @@ import { useEffect } from 'react' import { useMic } from '../mic/useMic' import { useAssistant } from './useAssistant' -import { useDebounce } from '@/lib/hooks' export function useVoiceAssistant() { const processUserMessage = useAssistant((s) => s.processUserMessage) diff --git a/packages/app/src/services/renderer/useRenderLoop.ts b/packages/app/src/services/renderer/useRenderLoop.ts index 61b06137..6753ecdc 100644 --- a/packages/app/src/services/renderer/useRenderLoop.ts +++ b/packages/app/src/services/renderer/useRenderLoop.ts @@ -11,7 +11,6 @@ import { useAudio } from '@/services/audio/useAudio' import { useMonitor } from '../monitor/useMonitor' import { useEffect, useRef } from 'react' import { useSettings } from '../settings' -import { set } from 'date-fns' /** * Runs a rendering loop diff --git a/packages/timeline/src/components/timeline/Cells.tsx b/packages/timeline/src/components/timeline/Cells.tsx index a5e3db3c..1706851d 100644 --- a/packages/timeline/src/components/timeline/Cells.tsx +++ b/packages/timeline/src/components/timeline/Cells.tsx @@ -5,54 +5,36 @@ import { } from "@/hooks" import { Cell } from "@/components/cells" -import { Suspense } from "react"; -export function Cells() { +import { GRID_REFRESH_RATE_IN_MS } from "@/constants/grid"; - // refresh rate for the grid (high value == delay before we see the "hidden" cells) - // this should be a fact of the number of segments, - // as this puts a strain on the rendering FPS - // - // another solution can also consist in rendering more hidden cells, - // to avoid having to re-compute - const refreshRateInMs = 50 +export function Cells() { + // subscribe to changes in content width; height, as well as the container width const contentHeight = useTimeline(s => s.contentHeight) + const containerWidth = useTimeline(s => s.containerWidth) // note: this one is async, so it creates a delay // we could cheat by detecting the cell width change and apply it // faster on the current geometries - const { visibleSegments, loadedSegments } = useSegmentLoader({ - refreshRateInMs, + const { loadedSegments } = useSegmentLoader({ + refreshRateInMs: GRID_REFRESH_RATE_IN_MS, }); - /* - const [props, set] = useSpring(() => ({ - pos: [0, 0, 0], - scale: [1, 1, 1], - rotation: [0, 0, 0], - config: { mass: 10, tension: 1000, friction: 300, precision: 0.00001 } - })) - */ - - // console.log(`re-rendering (${visibleSegments.length} strictly visible, ${loadedSegments.length} loaded in total)`) + console.log(`re-rendering (${loadedSegments.length} loaded segments)`) return ( {loadedSegments.map((s) => - }> - )} ); diff --git a/packages/timeline/src/components/timeline/Cursor.tsx b/packages/timeline/src/components/timeline/Cursor.tsx index f63e7e7e..9f1e168d 100644 --- a/packages/timeline/src/components/timeline/Cursor.tsx +++ b/packages/timeline/src/components/timeline/Cursor.tsx @@ -5,8 +5,10 @@ import { useAnimationFrame, useTimeline } from "@/hooks" import { useCursorGeometry } from "@/hooks/useCursorGeometry" import { leftBarTrackScaleWidth } from "@/constants/themes" +const CURSOR_WIDTH_IN_PX = 2 const SPEED_RESOLUTION = 50 // px/sec +const MAX_SPEED = 200 const GRADIENT_EXPONENT = 3 export function Cursor() { @@ -36,20 +38,22 @@ export function Cursor() { const granularSpeed = Math.floor(speed / SPEED_RESOLUTION) * SPEED_RESOLUTION if (granularSpeed !== lastSpeedRef.current) { - const maxSpeed = 200 - const visibility = Math.min(granularSpeed / maxSpeed, 1) + const visibility = Math.min(granularSpeed / MAX_SPEED, 1) lineMatRefs.current.forEach((mat, idx) => { const t = idx / (lineMatRefs.current.length - 1) const opacity = visibility * Math.pow(t, GRADIENT_EXPONENT) - const isLast = idx >= (lineMatRefs.current.length - 2) - mat.opacity = isLast ? 1.0 : opacity + const isAlwaysVisible = idx >= (lineMatRefs.current.length - CURSOR_WIDTH_IN_PX) + mat.opacity = isAlwaysVisible ? 1.0 : opacity }) lastSpeedRef.current = granularSpeed } - timelineCursor.scale.x = direction + // note: we need to avoid having a scale of 0 + if (direction !== 0) { + timelineCursor.scale.x = direction + } } lastPositionRef.current = currentPosition @@ -64,7 +68,7 @@ export function Cursor() { {cursorGeometries.map((lineGeometry, idx) => { const t = idx / (cursorGeometries.length - 1) const opacity = Math.pow(t, GRADIENT_EXPONENT) - const isLast = idx >= (cursorGeometries.length - 2) + const isAlwaysVisible = idx >= (cursorGeometries.length - CURSOR_WIDTH_IN_PX) return ( { if (ref) lineMatRefs.current[idx] = ref }} attach="material" color={theme.playbackCursor.lineColor} + + // note: in WebGL lines larger than 1 are not guaranteed to be + // supported by the browser, so let's assume it is always 1px linewidth={1} - opacity={isLast ? 1.0 : opacity} + + opacity={isAlwaysVisible ? 1.0 : opacity} transparent /> diff --git a/packages/timeline/src/components/timeline/CursorWeird.tsx b/packages/timeline/src/components/timeline/CursorWeird.tsx deleted file mode 100644 index b2eb25bd..00000000 --- a/packages/timeline/src/components/timeline/CursorWeird.tsx +++ /dev/null @@ -1,21 +0,0 @@ -import React from "react" - -import { - useTimeline -} from "@/hooks" - -import { hslToHex } from "@/utils" -import { useCursorGeometry } from "@/hooks/useCursorGeometry" - -export function Cursor({ - width, - height -}: { - width: number - height: number -}) { - - return ( - <>{null} - ) -}; diff --git a/packages/timeline/src/components/timeline/index.ts b/packages/timeline/src/components/timeline/index.ts index ba74de4d..376d027c 100644 --- a/packages/timeline/src/components/timeline/index.ts +++ b/packages/timeline/src/components/timeline/index.ts @@ -1,4 +1,4 @@ -export { Cursor } from "./CursorWeird" +export { Cursor } from "./Cursor" export { Timeline } from "./Timeline" export { TopBarTimeScale } from "./TopBarTimeScale" export { LeftBarTrackScale } from "./LeftBarTrackScale" diff --git a/packages/timeline/src/constants/grid.ts b/packages/timeline/src/constants/grid.ts index e79d00fa..a209f2ea 100644 --- a/packages/timeline/src/constants/grid.ts +++ b/packages/timeline/src/constants/grid.ts @@ -11,3 +11,11 @@ export const PROMPT_STEP_HEIGHT_IN_PX = 48 export const PREVIEW_STEP_HEIGHT_IN_PX = 3 * PROMPT_STEP_HEIGHT_IN_PX export const NB_MAX_SHOTS = ((8 * 60 * 60) / 2) // 6 hours converted to seconds, and divided by 2 (a shot is about 2 sec) + +// refresh rate for the grid (high value == delay before we see the "hidden" cells) +// this should be a factor of the number of segments, +// as this puts a strain on the rendering FPS +// +// another solution can also consist in rendering more hidden cells, +// to avoid having to re-compute +export const GRID_REFRESH_RATE_IN_MS = 25 \ No newline at end of file diff --git a/packages/timeline/src/constants/themes.ts b/packages/timeline/src/constants/themes.ts index ea9ea07a..4c445d55 100644 --- a/packages/timeline/src/constants/themes.ts +++ b/packages/timeline/src/constants/themes.ts @@ -4,7 +4,7 @@ import { ClapTimelineTheme } from "@/types" import { ClapSegmentCategoryColors } from "@/types/theme" export const leftBarTrackScaleWidth = 120 -export const topBarTimeScaleHeight = 40 +export const topBarTimeScaleHeight = 34 export const baseClapSegmentCategoryColors: ClapSegmentCategoryColors = { [ClapSegmentCategory.SPLAT]: { hue: 347, saturation: 30, lightness: 78.6 },