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,