diff --git a/src/components/video-editor/SettingsPanel.tsx b/src/components/video-editor/SettingsPanel.tsx index 89974d144..eb6bb09da 100644 --- a/src/components/video-editor/SettingsPanel.tsx +++ b/src/components/video-editor/SettingsPanel.tsx @@ -477,6 +477,8 @@ interface SettingsPanelProps { onShowCursorChange?: (enabled: boolean) => void; loopCursor?: boolean; onLoopCursorChange?: (enabled: boolean) => void; + alwaysUsePointerCursor?: boolean; + onAlwaysUsePointerCursorChange?: (enabled: boolean) => void; cursorStyle?: CursorStyle; onCursorStyleChange?: (style: CursorStyle) => void; cursorSize?: number; @@ -923,6 +925,8 @@ export function SettingsPanel({ onShowCursorChange, loopCursor = false, onLoopCursorChange, + alwaysUsePointerCursor = false, + onAlwaysUsePointerCursorChange, cursorStyle = DEFAULT_CURSOR_STYLE, onCursorStyleChange, cursorSize = 5, @@ -1435,6 +1439,7 @@ export function SettingsPanel({ const resetCursorSection = () => { onShowCursorChange?.(initialEditorPreferences.showCursor); onLoopCursorChange?.(initialEditorPreferences.loopCursor); + onAlwaysUsePointerCursorChange?.(initialEditorPreferences.alwaysUsePointerCursor); onCursorStyleChange?.(initialEditorPreferences.cursorStyle); onCursorSizeChange?.(initialEditorPreferences.cursorSize); onCursorSmoothingChange?.(initialEditorPreferences.cursorSmoothing); @@ -2837,6 +2842,14 @@ export function SettingsPanel({ onCheckedChange={onLoopCursorChange} /> + )} diff --git a/src/components/video-editor/VideoPlayback.tsx b/src/components/video-editor/VideoPlayback.tsx index 47a841013..024ac954b 100644 --- a/src/components/video-editor/VideoPlayback.tsx +++ b/src/components/video-editor/VideoPlayback.tsx @@ -261,6 +261,7 @@ interface VideoPlaybackProps { onAnnotationSizeChange?: (id: string, size: { width: number; height: number }) => void; cursorTelemetry?: CursorTelemetryPoint[]; showCursor?: boolean; + alwaysUsePointerCursor?: boolean; cursorStyle?: CursorStyle; cursorSize?: number; cursorSmoothing?: number; @@ -347,6 +348,7 @@ const VideoPlayback = forwardRef( onAnnotationSizeChange, cursorTelemetry = [], showCursor = false, + alwaysUsePointerCursor = false, cursorStyle = DEFAULT_CURSOR_STYLE, cursorSize = DEFAULT_CURSOR_SIZE, cursorSmoothing = DEFAULT_CURSOR_SMOOTHING, @@ -492,6 +494,7 @@ const VideoPlayback = forwardRef( const cursorOverlayRef = useRef(null); const cursorTelemetryRef = useRef([]); const showCursorRef = useRef(showCursor); + const alwaysUsePointerCursorRef = useRef(alwaysUsePointerCursor); const cursorSizeRef = useRef(cursorSize); const cursorStyleRef = useRef(cursorStyle); const cursorSmoothingRef = useRef(cursorSmoothing); @@ -1427,6 +1430,12 @@ const VideoPlayback = forwardRef( requestPausedFrameRefresh(); }, [cursorStyle, requestPausedFrameRefresh]); + useEffect(() => { + alwaysUsePointerCursorRef.current = alwaysUsePointerCursor; + cursorOverlayRef.current?.setAlwaysUsePointerCursor(alwaysUsePointerCursor); + requestPausedFrameRefresh(); + }, [alwaysUsePointerCursor, requestPausedFrameRefresh]); + useEffect(() => { cursorSizeRef.current = cursorSize; requestPausedFrameRefresh(); @@ -1794,6 +1803,7 @@ const VideoPlayback = forwardRef( dotRadius: DEFAULT_CURSOR_CONFIG.dotRadius * cursorSizeRef.current, minViewportScale: 0, style: cursorStyleRef.current, + alwaysUsePointerCursor: alwaysUsePointerCursorRef.current, smoothingFactor: cursorSmoothingRef.current, springTuning: { stiffnessMultiplier: cursorSpringStiffnessMultiplierRef.current, diff --git a/src/components/video-editor/editorPreferences.ts b/src/components/video-editor/editorPreferences.ts index 61ade506f..61109e31d 100644 --- a/src/components/video-editor/editorPreferences.ts +++ b/src/components/video-editor/editorPreferences.ts @@ -26,6 +26,7 @@ type PersistedEditorControls = Pick< | "connectedZoomEasing" | "showCursor" | "loopCursor" + | "alwaysUsePointerCursor" | "cursorStyle" | "cursorSize" | "cursorSmoothing" @@ -103,6 +104,7 @@ export const DEFAULT_EDITOR_PREFERENCES: EditorPreferences = { connectedZoomEasing: DEFAULT_EDITOR_CONTROLS.connectedZoomEasing, showCursor: DEFAULT_EDITOR_CONTROLS.showCursor, loopCursor: DEFAULT_EDITOR_CONTROLS.loopCursor, + alwaysUsePointerCursor: DEFAULT_EDITOR_CONTROLS.alwaysUsePointerCursor, cursorStyle: DEFAULT_EDITOR_CONTROLS.cursorStyle, cursorSize: DEFAULT_EDITOR_CONTROLS.cursorSize, cursorSmoothing: DEFAULT_EDITOR_CONTROLS.cursorSmoothing, @@ -287,6 +289,8 @@ function normalizeEditorControls( connectedZoomEasing: sanitizedRaw.connectedZoomEasing ?? fallback.connectedZoomEasing, showCursor: sanitizedRaw.showCursor ?? fallback.showCursor, loopCursor: sanitizedRaw.loopCursor ?? fallback.loopCursor, + alwaysUsePointerCursor: + sanitizedRaw.alwaysUsePointerCursor ?? fallback.alwaysUsePointerCursor, cursorStyle: sanitizedRaw.cursorStyle ?? fallback.cursorStyle, cursorSize: sanitizedRaw.cursorSize ?? fallback.cursorSize, cursorSmoothing: sanitizedRaw.cursorSmoothing ?? fallback.cursorSmoothing, @@ -343,6 +347,7 @@ function normalizeEditorControls( connectedZoomEasing: normalized.connectedZoomEasing, showCursor: normalized.showCursor, loopCursor: normalized.loopCursor, + alwaysUsePointerCursor: normalized.alwaysUsePointerCursor, cursorStyle: normalized.cursorStyle, cursorSize: normalized.cursorSize, cursorSmoothing: normalized.cursorSmoothing, diff --git a/src/components/video-editor/export/buildExportRenderOptions.ts b/src/components/video-editor/export/buildExportRenderOptions.ts index ae836c5a7..a0cef7eea 100644 --- a/src/components/video-editor/export/buildExportRenderOptions.ts +++ b/src/components/video-editor/export/buildExportRenderOptions.ts @@ -64,6 +64,7 @@ export function buildExportRenderOptions({ zoomRegions: effectiveZoomRegions, cursorTelemetry: effectiveCursorTelemetry, showCursor: effectiveShowCursor, + alwaysUsePointerCursor: appearance.alwaysUsePointerCursor, cursorStyle: appearance.cursorStyle, cursorSize: appearance.cursorSize, cursorSmoothing: appearance.cursorSmoothing, diff --git a/src/components/video-editor/layout/EditorVideoPreview.tsx b/src/components/video-editor/layout/EditorVideoPreview.tsx index 06ff3e1ed..f833c4700 100644 --- a/src/components/video-editor/layout/EditorVideoPreview.tsx +++ b/src/components/video-editor/layout/EditorVideoPreview.tsx @@ -104,6 +104,7 @@ export function EditorVideoPreview({ selectedAnnotationId={timeline.selectedAnnotationId} cursorTelemetry={effectiveCursorTelemetry} showCursor={effectiveShowCursor} + alwaysUsePointerCursor={appearance.alwaysUsePointerCursor} cursorStyle={appearance.cursorStyle} cursorSize={appearance.cursorSize} cursorSmoothing={appearance.cursorSmoothing} diff --git a/src/components/video-editor/layout/useEditorSettingsPanelProps.ts b/src/components/video-editor/layout/useEditorSettingsPanelProps.ts index 4c4c8c696..5730a0927 100644 --- a/src/components/video-editor/layout/useEditorSettingsPanelProps.ts +++ b/src/components/video-editor/layout/useEditorSettingsPanelProps.ts @@ -133,6 +133,8 @@ export function useEditorSettingsPanelProps(input: Input): ComponentProps): Pro ), showCursor: typeof editor.showCursor === "boolean" ? editor.showCursor : true, loopCursor: typeof editor.loopCursor === "boolean" ? editor.loopCursor : false, + alwaysUsePointerCursor: + typeof editor.alwaysUsePointerCursor === "boolean" + ? editor.alwaysUsePointerCursor + : false, cursorStyle: normalizedCursorStyle, cursorClickEffect: normalizeCursorClickEffectStyle( (editor as Partial).cursorClickEffect, diff --git a/src/components/video-editor/state/useAppearanceState.ts b/src/components/video-editor/state/useAppearanceState.ts index 6dbfd4b16..8a5f07dbe 100644 --- a/src/components/video-editor/state/useAppearanceState.ts +++ b/src/components/video-editor/state/useAppearanceState.ts @@ -61,6 +61,9 @@ export function useAppearanceState(preferences: EditorPreferences) { ); const [showCursor, setShowCursor] = useState(preferences.showCursor); const [loopCursor, setLoopCursor] = useState(preferences.loopCursor); + const [alwaysUsePointerCursor, setAlwaysUsePointerCursor] = useState( + preferences.alwaysUsePointerCursor, + ); const [cursorStyle, setCursorStyle] = useState( preferences.cursorStyle ?? DEFAULT_CURSOR_STYLE, ); @@ -150,6 +153,8 @@ export function useAppearanceState(preferences: EditorPreferences) { setShowCursor, loopCursor, setLoopCursor, + alwaysUsePointerCursor, + setAlwaysUsePointerCursor, cursorStyle, setCursorStyle, cursorSize, diff --git a/src/components/video-editor/videoPlayback/cursorRenderer.ts b/src/components/video-editor/videoPlayback/cursorRenderer.ts index bde9bfd66..d1586683f 100644 --- a/src/components/video-editor/videoPlayback/cursorRenderer.ts +++ b/src/components/video-editor/videoPlayback/cursorRenderer.ts @@ -112,6 +112,8 @@ export interface CursorRenderConfig { sway: number; /** Cursor visual style. */ style: CursorStyle; + /** Render every sample as the default arrow cursor. */ + alwaysUsePointerCursor: boolean; } const MIN_CURSOR_VIEWPORT_SCALE = 0; @@ -154,6 +156,7 @@ export const DEFAULT_CURSOR_CONFIG: CursorRenderConfig = { clickEffectDurationMs: DEFAULT_CURSOR_CLICK_EFFECT_DURATION_MS, sway: 0, style: DEFAULT_CURSOR_STYLE, + alwaysUsePointerCursor: false, }; let cursorAssetsPromise: Promise | null = null; let cursorPackAssetsPromise: Promise | null = null; @@ -1236,6 +1239,10 @@ export class PixiCursorOverlay { this.config.sway = clamp(sway, 0, 2); } + setAlwaysUsePointerCursor(enabled: boolean) { + this.config.alwaysUsePointerCursor = enabled; + } + setStyle(style: CursorStyle) { this.config.style = style; if (isStatefulCursorStyle(style)) { @@ -1321,13 +1328,20 @@ export class PixiCursorOverlay { const h = this.config.dotRadius * getCursorViewportScale(viewport.width, this.config.minViewportScale); - const { cursorType, clickSample, clickBounceProgress, clickProgress } = - getCursorVisualState( - samples, - timeMs, - this.config.clickBounceDuration, - this.config.clickEffectDurationMs, - ); + const { + cursorType: rawCursorType, + clickSample, + clickBounceProgress, + clickProgress, + } = getCursorVisualState( + samples, + timeMs, + this.config.clickBounceDuration, + this.config.clickEffectDurationMs, + ); + const cursorType: CursorAssetKey = this.config.alwaysUsePointerCursor + ? "arrow" + : rawCursorType; const projectedClickSample = clickSample ? projectCursorPositionToViewport(clickSample, viewport.sourceCrop) : null; @@ -1630,12 +1644,18 @@ export function drawCursorOnCanvas( const px = viewport.x + smoothedState.x * viewport.width; const py = viewport.y + smoothedState.y * viewport.height; const h = config.dotRadius * getCursorViewportScale(viewport.width, config.minViewportScale); - const { cursorType, clickSample, clickBounceProgress, clickProgress } = getCursorVisualState( + const { + cursorType: rawCursorType, + clickSample, + clickBounceProgress, + clickProgress, + } = getCursorVisualState( samples, timeMs, config.clickBounceDuration, config.clickEffectDurationMs, ); + const cursorType: CursorAssetKey = config.alwaysUsePointerCursor ? "arrow" : rawCursorType; const projectedClickSample = clickSample ? projectCursorPositionToViewport(clickSample, viewport.sourceCrop) : null; diff --git a/src/i18n/locales/de/settings.json b/src/i18n/locales/de/settings.json index 3dae35b70..a531ec15b 100644 --- a/src/i18n/locales/de/settings.json +++ b/src/i18n/locales/de/settings.json @@ -31,6 +31,7 @@ "show": "Anzeigen", "showCursor": "Cursor anzeigen", "loopCursor": "Cursor wiederholen", + "alwaysUsePointerCursor": "Immer Standardcursor verwenden", "cursorStyle": "Cursorstil", "cursorStyleOptions": { "macos": "macOS", diff --git a/src/i18n/locales/en/settings.json b/src/i18n/locales/en/settings.json index 9db0e74eb..54161c143 100644 --- a/src/i18n/locales/en/settings.json +++ b/src/i18n/locales/en/settings.json @@ -37,6 +37,7 @@ "show": "Show", "showCursor": "Show Cursor", "loopCursor": "Loop cursor", + "alwaysUsePointerCursor": "Always use default cursor", "cursorStyle": "Cursor Style", "cursorStyleOptions": { "macos": "macOS", diff --git a/src/i18n/locales/es/settings.json b/src/i18n/locales/es/settings.json index 5eafdfdde..c99d69245 100644 --- a/src/i18n/locales/es/settings.json +++ b/src/i18n/locales/es/settings.json @@ -31,6 +31,7 @@ "show": "Mostrar", "showCursor": "Mostrar cursor", "loopCursor": "Cursor en bucle", + "alwaysUsePointerCursor": "Usar siempre el cursor predeterminado", "cursorStyle": "Estilo del cursor", "cursorStyleOptions": { "macos": "macOS", diff --git a/src/i18n/locales/fr/settings.json b/src/i18n/locales/fr/settings.json index 26283e789..ce2493dd6 100644 --- a/src/i18n/locales/fr/settings.json +++ b/src/i18n/locales/fr/settings.json @@ -31,6 +31,7 @@ "show": "Afficher", "showCursor": "Afficher le curseur", "loopCursor": "Boucler le curseur", + "alwaysUsePointerCursor": "Toujours utiliser le curseur par défaut", "cursorStyle": "Style du curseur", "cursorStyleOptions": { "macos": "macOS", diff --git a/src/i18n/locales/it/settings.json b/src/i18n/locales/it/settings.json index b43fdba9a..988f562a2 100644 --- a/src/i18n/locales/it/settings.json +++ b/src/i18n/locales/it/settings.json @@ -31,6 +31,7 @@ "show": "Mostra", "showCursor": "Mostra cursore", "loopCursor": "Cursore in loop", + "alwaysUsePointerCursor": "Usa sempre il cursore predefinito", "cursorStyle": "Stile cursore", "cursorStyleOptions": { "macos": "macOS", diff --git a/src/i18n/locales/ko/settings.json b/src/i18n/locales/ko/settings.json index 33488fc0b..b2e8ce59e 100644 --- a/src/i18n/locales/ko/settings.json +++ b/src/i18n/locales/ko/settings.json @@ -31,6 +31,7 @@ "show": "표시", "showCursor": "커서 표시", "loopCursor": "커서 반복", + "alwaysUsePointerCursor": "항상 기본 커서 사용", "cursorStyle": "커서 스타일", "cursorStyleOptions": { "macos": "macOS", diff --git a/src/i18n/locales/nl/settings.json b/src/i18n/locales/nl/settings.json index 9d863cb53..b7ea48977 100644 --- a/src/i18n/locales/nl/settings.json +++ b/src/i18n/locales/nl/settings.json @@ -31,6 +31,7 @@ "show": "Tonen", "showCursor": "Cursor tonen", "loopCursor": "Cursor herhalen", + "alwaysUsePointerCursor": "Altijd standaardcursor gebruiken", "cursorStyle": "Cursorstijl", "cursorStyleOptions": { "macos": "macOS", diff --git a/src/i18n/locales/pt-BR/settings.json b/src/i18n/locales/pt-BR/settings.json index 7541913a7..dd972559b 100644 --- a/src/i18n/locales/pt-BR/settings.json +++ b/src/i18n/locales/pt-BR/settings.json @@ -31,6 +31,7 @@ "show": "Mostrar", "showCursor": "Mostrar cursor", "loopCursor": "Loop do cursor", + "alwaysUsePointerCursor": "Usar sempre o cursor padrão", "cursorStyle": "Estilo do cursor", "cursorStyleOptions": { "macos": "macOS", diff --git a/src/i18n/locales/ru/settings.json b/src/i18n/locales/ru/settings.json index aadb70e02..26dc12df7 100644 --- a/src/i18n/locales/ru/settings.json +++ b/src/i18n/locales/ru/settings.json @@ -31,6 +31,7 @@ "show": "Показать", "showCursor": "Показать курсор", "loopCursor": "Зациклить курсор", + "alwaysUsePointerCursor": "Всегда использовать курсор по умолчанию", "cursorStyle": "Стиль курсора", "cursorStyleOptions": { "macos": "macOS", diff --git a/src/i18n/locales/zh-CN/settings.json b/src/i18n/locales/zh-CN/settings.json index ba4a68c6a..55db861fe 100644 --- a/src/i18n/locales/zh-CN/settings.json +++ b/src/i18n/locales/zh-CN/settings.json @@ -31,6 +31,7 @@ "show": "显示", "showCursor": "显示光标", "loopCursor": "循环光标", + "alwaysUsePointerCursor": "始终使用默认光标", "cursorStyle": "光标样式", "cursorStyleOptions": { "macos": "macOS", diff --git a/src/i18n/locales/zh-TW/settings.json b/src/i18n/locales/zh-TW/settings.json index b7b944030..646e480ae 100644 --- a/src/i18n/locales/zh-TW/settings.json +++ b/src/i18n/locales/zh-TW/settings.json @@ -31,6 +31,7 @@ "show": "顯示", "showCursor": "顯示游標", "loopCursor": "游標循環", + "alwaysUsePointerCursor": "始終使用預設游標", "cursorStyle": "游標樣式", "cursorStyleOptions": { "macos": "macOS", diff --git a/src/lib/exporter/frameRenderer.ts b/src/lib/exporter/frameRenderer.ts index d42f6a69b..ce8ceb40c 100644 --- a/src/lib/exporter/frameRenderer.ts +++ b/src/lib/exporter/frameRenderer.ts @@ -113,6 +113,7 @@ interface FrameRenderConfig { previewHeight?: number; cursorTelemetry?: CursorTelemetryPoint[]; showCursor?: boolean; + alwaysUsePointerCursor?: boolean; cursorStyle?: CursorStyle; cursorSize?: number; cursorSmoothing?: number; @@ -399,6 +400,7 @@ export class FrameRenderer { this.cursorOverlay = new PixiCursorOverlay({ dotRadius: DEFAULT_CURSOR_CONFIG.dotRadius * (this.config.cursorSize ?? 1.4), style: this.config.cursorStyle ?? "tahoe", + alwaysUsePointerCursor: this.config.alwaysUsePointerCursor ?? false, smoothingFactor: this.config.cursorSmoothing ?? DEFAULT_CURSOR_CONFIG.smoothingFactor, springTuning: { diff --git a/src/lib/exporter/gifExporter.ts b/src/lib/exporter/gifExporter.ts index 2df730000..eadb73110 100644 --- a/src/lib/exporter/gifExporter.ts +++ b/src/lib/exporter/gifExporter.ts @@ -66,6 +66,7 @@ interface GifExporterConfig { autoCaptionSettings?: AutoCaptionSettings; cursorTelemetry?: CursorTelemetryPoint[]; showCursor?: boolean; + alwaysUsePointerCursor?: boolean; cursorStyle?: CursorStyle; cursorSize?: number; cursorSmoothing?: number; @@ -170,6 +171,7 @@ export function buildGifFrameRendererConfig( previewHeight: config.previewHeight, cursorTelemetry: config.cursorTelemetry, showCursor: config.showCursor, + alwaysUsePointerCursor: config.alwaysUsePointerCursor, cursorStyle: config.cursorStyle, cursorSize: config.cursorSize, cursorSmoothing: config.cursorSmoothing, diff --git a/src/lib/exporter/modernFrameRenderer.ts b/src/lib/exporter/modernFrameRenderer.ts index 805af27c0..9f9a01068 100644 --- a/src/lib/exporter/modernFrameRenderer.ts +++ b/src/lib/exporter/modernFrameRenderer.ts @@ -132,6 +132,7 @@ interface FrameRenderConfig { previewHeight?: number; cursorTelemetry?: CursorTelemetryPoint[]; showCursor?: boolean; + alwaysUsePointerCursor?: boolean; cursorStyle?: CursorStyle; cursorSize?: number; cursorSmoothing?: number; @@ -556,6 +557,7 @@ export class FrameRenderer { this.cursorOverlay = new PixiCursorOverlay({ dotRadius: DEFAULT_CURSOR_CONFIG.dotRadius * (this.config.cursorSize ?? 1.4), style: this.config.cursorStyle ?? "tahoe", + alwaysUsePointerCursor: this.config.alwaysUsePointerCursor ?? false, smoothingFactor: this.config.cursorSmoothing ?? DEFAULT_CURSOR_CONFIG.smoothingFactor, springTuning: { diff --git a/src/lib/exporter/modernVideoExporter.ts b/src/lib/exporter/modernVideoExporter.ts index 10fdf2755..78e8ad15c 100644 --- a/src/lib/exporter/modernVideoExporter.ts +++ b/src/lib/exporter/modernVideoExporter.ts @@ -125,6 +125,7 @@ interface VideoExporterConfig extends ExportConfig { autoCaptionSettings?: AutoCaptionSettings; cursorTelemetry?: CursorTelemetryPoint[]; showCursor?: boolean; + alwaysUsePointerCursor?: boolean; cursorStyle?: CursorStyle; cursorSize?: number; cursorSmoothing?: number; @@ -638,6 +639,7 @@ export class ModernVideoExporter { previewHeight: this.config.previewHeight, cursorTelemetry: this.config.cursorTelemetry, showCursor: this.config.showCursor, + alwaysUsePointerCursor: this.config.alwaysUsePointerCursor, cursorStyle: this.config.cursorStyle, cursorSize: this.config.cursorSize, cursorSmoothing: this.config.cursorSmoothing, @@ -2283,6 +2285,7 @@ export class ModernVideoExporter { clickBounce: this.config.cursorClickBounce, clickBounceDurationMs: this.config.cursorClickBounceDuration, sourceCrop: this.config.cropRegion, + alwaysUsePointerCursor: this.config.alwaysUsePointerCursor, }); } diff --git a/src/lib/exporter/nativeStaticLayoutTelemetry.test.ts b/src/lib/exporter/nativeStaticLayoutTelemetry.test.ts index 5157b5205..999203ab5 100644 --- a/src/lib/exporter/nativeStaticLayoutTelemetry.test.ts +++ b/src/lib/exporter/nativeStaticLayoutTelemetry.test.ts @@ -110,6 +110,24 @@ describe("buildNativeStaticLayoutCursorTelemetry", () => { expect(resampled?.some((sample) => (sample.bounceScale ?? 1) < 1)).toBe(true); }); + it("keeps recorded cursor type boundaries when forcing the pointer cursor", () => { + const samples = [ + { timeMs: 0, cx: 0.4, cy: 0.6, cursorType: "arrow" as const }, + { timeMs: 500, cx: 0.4, cy: 0.6, cursorType: "text" as const }, + ]; + const options = { frameRate: 10, durationSec: 1 }; + const plain = buildNativeStaticLayoutCursorTelemetry(samples, options); + const forced = buildNativeStaticLayoutCursorTelemetry(samples, { + ...options, + alwaysUsePointerCursor: true, + }); + + expect(forced?.map((sample) => sample.timeMs)).toEqual(plain?.map((sample) => sample.timeMs)); + expect(forced).toHaveLength(2); + expect(forced?.every((sample) => sample.cursorType === "arrow")).toBe(true); + expect(forced?.every((sample) => sample.cursorTypeIndex === 0)).toBe(true); + }); + it("projects cursor samples into the cropped viewport and marks out-of-crop samples hidden", () => { const resampled = buildNativeStaticLayoutCursorTelemetry( [ diff --git a/src/lib/exporter/nativeStaticLayoutTelemetry.ts b/src/lib/exporter/nativeStaticLayoutTelemetry.ts index 7a8cda118..01954fe96 100644 --- a/src/lib/exporter/nativeStaticLayoutTelemetry.ts +++ b/src/lib/exporter/nativeStaticLayoutTelemetry.ts @@ -18,6 +18,7 @@ export type NativeStaticLayoutCursorTelemetryOptions = { clickBounce?: number; clickBounceDurationMs?: number; sourceCrop?: CropRegion; + alwaysUsePointerCursor?: boolean; }; const CURSOR_POSITION_EPSILON = 0.00001; @@ -204,5 +205,14 @@ export function buildNativeStaticLayoutCursorTelemetry( pushCursorSample(resampled, sampleCursorAt(durationMs)); } + if (options.alwaysUsePointerCursor) { + const arrowIndex = getCursorTypeIndex("arrow"); + return resampled.map((sample) => ({ + ...sample, + cursorType: "arrow" as const, + cursorTypeIndex: arrowIndex, + })); + } + return resampled; } diff --git a/src/lib/exporter/videoExporter.ts b/src/lib/exporter/videoExporter.ts index 27e48bb44..9d06d5a2e 100644 --- a/src/lib/exporter/videoExporter.ts +++ b/src/lib/exporter/videoExporter.ts @@ -75,6 +75,7 @@ interface VideoExporterConfig extends ExportConfig { autoCaptionSettings?: AutoCaptionSettings; cursorTelemetry?: CursorTelemetryPoint[]; showCursor?: boolean; + alwaysUsePointerCursor?: boolean; cursorStyle?: CursorStyle; cursorSize?: number; cursorSmoothing?: number; @@ -249,6 +250,7 @@ export class VideoExporter { previewHeight: this.config.previewHeight, cursorTelemetry: this.config.cursorTelemetry, showCursor: this.config.showCursor, + alwaysUsePointerCursor: this.config.alwaysUsePointerCursor, cursorStyle: this.config.cursorStyle, cursorSize: this.config.cursorSize, cursorSmoothing: this.config.cursorSmoothing,