feat: add windows cursor preview diagnostics
This commit is contained in:
@@ -26,10 +26,10 @@ import {
|
||||
type WebcamSizePreset,
|
||||
} from "@/lib/compositeLayout";
|
||||
import {
|
||||
getNativeCursorDisplayMetrics,
|
||||
hasNativeCursorRecordingData,
|
||||
projectNativeCursorToStage,
|
||||
resolveInterpolatedNativeCursorFrame,
|
||||
resolveNativeCursorRenderAsset,
|
||||
} from "@/lib/cursor/nativeCursor";
|
||||
import { classifyWallpaper, DEFAULT_WALLPAPER, resolveImageWallpaperUrl } from "@/lib/wallpaper";
|
||||
import { getCssClipPath } from "@/lib/webcamMaskShapes";
|
||||
@@ -1324,19 +1324,20 @@ const VideoPlayback = forwardRef<VideoPlaybackRef, VideoPlaybackProps>(
|
||||
sample: frame.sample,
|
||||
});
|
||||
if (projectedPoint) {
|
||||
const metrics = getNativeCursorDisplayMetrics(
|
||||
const renderAsset = resolveNativeCursorRenderAsset(
|
||||
frame.asset,
|
||||
window.devicePixelRatio || 1,
|
||||
frame.sample,
|
||||
);
|
||||
const scale = Math.max(0, cursorSizeRef.current);
|
||||
if (nativeCursorImg.dataset.cursorId !== frame.asset.id) {
|
||||
nativeCursorImg.src = frame.asset.imageDataUrl;
|
||||
nativeCursorImg.dataset.cursorId = frame.asset.id;
|
||||
if (nativeCursorImg.dataset.cursorId !== renderAsset.id) {
|
||||
nativeCursorImg.src = renderAsset.imageDataUrl;
|
||||
nativeCursorImg.dataset.cursorId = renderAsset.id;
|
||||
}
|
||||
nativeCursorImg.style.left = `${projectedPoint.x - metrics.hotspotX * scale}px`;
|
||||
nativeCursorImg.style.top = `${projectedPoint.y - metrics.hotspotY * scale}px`;
|
||||
nativeCursorImg.style.width = `${metrics.width * scale}px`;
|
||||
nativeCursorImg.style.height = `${metrics.height * scale}px`;
|
||||
nativeCursorImg.style.left = `${projectedPoint.x - renderAsset.hotspotX * scale}px`;
|
||||
nativeCursorImg.style.top = `${projectedPoint.y - renderAsset.hotspotY * scale}px`;
|
||||
nativeCursorImg.style.width = `${renderAsset.width * scale}px`;
|
||||
nativeCursorImg.style.height = `${renderAsset.height * scale}px`;
|
||||
nativeCursorImg.style.display = "block";
|
||||
} else {
|
||||
nativeCursorImg.style.display = "none";
|
||||
|
||||
@@ -1,9 +1,20 @@
|
||||
import { type Container, Point } from "pixi.js";
|
||||
import crosshairUrl from "@/assets/cursors/Cursor=Cross.svg";
|
||||
import arrowUrl from "@/assets/cursors/Cursor=Default.svg";
|
||||
import pointerUrl from "@/assets/cursors/Cursor=Hand-(Pointing).svg";
|
||||
import notAllowedUrl from "@/assets/cursors/Cursor=Menu.svg";
|
||||
import moveUrl from "@/assets/cursors/Cursor=Move.svg";
|
||||
import resizeNeswUrl from "@/assets/cursors/Cursor=Resize-North-East-South-West.svg";
|
||||
import resizeNsUrl from "@/assets/cursors/Cursor=Resize-North-South.svg";
|
||||
import resizeNwseUrl from "@/assets/cursors/Cursor=Resize-North-West-South-East.svg";
|
||||
import resizeEwUrl from "@/assets/cursors/Cursor=Resize-West-East.svg";
|
||||
import textUrl from "@/assets/cursors/Cursor=Text-Cursor.svg";
|
||||
import type { CropRegion } from "@/components/video-editor/types";
|
||||
import type {
|
||||
CursorRecordingData,
|
||||
CursorRecordingSample,
|
||||
NativeCursorAsset,
|
||||
NativeCursorType,
|
||||
} from "@/native/contracts";
|
||||
|
||||
export interface ActiveNativeCursorFrame {
|
||||
@@ -23,6 +34,87 @@ function clamp(value: number, min: number, max: number) {
|
||||
return Math.min(max, Math.max(min, value));
|
||||
}
|
||||
|
||||
interface PrettyNativeCursorAsset {
|
||||
imageDataUrl: string;
|
||||
width: number;
|
||||
height: number;
|
||||
hotspotX: number;
|
||||
hotspotY: number;
|
||||
}
|
||||
|
||||
const PRETTY_NATIVE_CURSOR_ASSETS: Partial<Record<NativeCursorType, PrettyNativeCursorAsset>> = {
|
||||
arrow: {
|
||||
imageDataUrl: arrowUrl,
|
||||
width: 32,
|
||||
height: 32,
|
||||
hotspotX: 5.8,
|
||||
hotspotY: 3.2,
|
||||
},
|
||||
text: {
|
||||
imageDataUrl: textUrl,
|
||||
width: 32,
|
||||
height: 32,
|
||||
hotspotX: 16,
|
||||
hotspotY: 16,
|
||||
},
|
||||
pointer: {
|
||||
imageDataUrl: pointerUrl,
|
||||
width: 32,
|
||||
height: 32,
|
||||
hotspotX: 11.8,
|
||||
hotspotY: 2.6,
|
||||
},
|
||||
crosshair: {
|
||||
imageDataUrl: crosshairUrl,
|
||||
width: 32,
|
||||
height: 32,
|
||||
hotspotX: 16,
|
||||
hotspotY: 16,
|
||||
},
|
||||
"resize-ew": {
|
||||
imageDataUrl: resizeEwUrl,
|
||||
width: 32,
|
||||
height: 32,
|
||||
hotspotX: 16,
|
||||
hotspotY: 16,
|
||||
},
|
||||
"resize-ns": {
|
||||
imageDataUrl: resizeNsUrl,
|
||||
width: 32,
|
||||
height: 32,
|
||||
hotspotX: 16,
|
||||
hotspotY: 16,
|
||||
},
|
||||
"resize-nesw": {
|
||||
imageDataUrl: resizeNeswUrl,
|
||||
width: 32,
|
||||
height: 32,
|
||||
hotspotX: 16,
|
||||
hotspotY: 16,
|
||||
},
|
||||
"resize-nwse": {
|
||||
imageDataUrl: resizeNwseUrl,
|
||||
width: 32,
|
||||
height: 32,
|
||||
hotspotX: 16,
|
||||
hotspotY: 16,
|
||||
},
|
||||
move: {
|
||||
imageDataUrl: moveUrl,
|
||||
width: 32,
|
||||
height: 32,
|
||||
hotspotX: 16,
|
||||
hotspotY: 16,
|
||||
},
|
||||
"not-allowed": {
|
||||
imageDataUrl: notAllowedUrl,
|
||||
width: 32,
|
||||
height: 32,
|
||||
hotspotX: 16,
|
||||
hotspotY: 16,
|
||||
},
|
||||
};
|
||||
|
||||
export function hasNativeCursorRecordingData(
|
||||
recordingData: CursorRecordingData | null | undefined,
|
||||
): recordingData is CursorRecordingData {
|
||||
@@ -169,3 +261,39 @@ export function getNativeCursorDisplayMetrics(asset: NativeCursorAsset, deviceSc
|
||||
hotspotY: asset.hotspotY / scaleFactor,
|
||||
};
|
||||
}
|
||||
|
||||
export function resolvePrettyNativeCursorAsset(
|
||||
asset: NativeCursorAsset,
|
||||
sample?: CursorRecordingSample,
|
||||
) {
|
||||
const cursorType = sample?.cursorType ?? asset.cursorType ?? null;
|
||||
return cursorType ? (PRETTY_NATIVE_CURSOR_ASSETS[cursorType] ?? null) : null;
|
||||
}
|
||||
|
||||
export function resolveNativeCursorRenderAsset(
|
||||
asset: NativeCursorAsset,
|
||||
deviceScaleFactor: number,
|
||||
sample?: CursorRecordingSample,
|
||||
) {
|
||||
const prettyAsset = resolvePrettyNativeCursorAsset(asset, sample);
|
||||
if (prettyAsset) {
|
||||
return {
|
||||
id: `pretty:${sample?.cursorType ?? asset.cursorType}`,
|
||||
imageDataUrl: prettyAsset.imageDataUrl,
|
||||
width: prettyAsset.width,
|
||||
height: prettyAsset.height,
|
||||
hotspotX: prettyAsset.hotspotX,
|
||||
hotspotY: prettyAsset.hotspotY,
|
||||
};
|
||||
}
|
||||
|
||||
const metrics = getNativeCursorDisplayMetrics(asset, deviceScaleFactor);
|
||||
return {
|
||||
id: asset.id,
|
||||
imageDataUrl: asset.imageDataUrl,
|
||||
width: metrics.width,
|
||||
height: metrics.height,
|
||||
hotspotX: metrics.hotspotX,
|
||||
hotspotY: metrics.hotspotY,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -57,13 +57,13 @@ import {
|
||||
type StyledRenderRect,
|
||||
} from "@/lib/compositeLayout";
|
||||
import {
|
||||
getNativeCursorDisplayMetrics,
|
||||
projectNativeCursorToStage,
|
||||
resolveInterpolatedNativeCursorFrame,
|
||||
resolveNativeCursorRenderAsset,
|
||||
} from "@/lib/cursor/nativeCursor";
|
||||
import { BackgroundLoadError, classifyWallpaper, resolveImageWallpaperUrl } from "@/lib/wallpaper";
|
||||
import { drawCanvasClipPath } from "@/lib/webcamMaskShapes";
|
||||
import type { CursorRecordingData, NativeCursorAsset } from "@/native/contracts";
|
||||
import type { CursorRecordingData } from "@/native/contracts";
|
||||
import { renderAnnotations } from "./annotationRenderer";
|
||||
import {
|
||||
getLinearGradientPoints,
|
||||
@@ -585,19 +585,23 @@ export class FrameRenderer {
|
||||
return;
|
||||
}
|
||||
|
||||
const image = await this.getCursorImage(activeNativeCursor.asset);
|
||||
const metrics = getNativeCursorDisplayMetrics(activeNativeCursor.asset, 1);
|
||||
const renderAsset = resolveNativeCursorRenderAsset(
|
||||
activeNativeCursor.asset,
|
||||
1,
|
||||
activeNativeCursor.sample,
|
||||
);
|
||||
const image = await this.getCursorImage(renderAsset);
|
||||
const scale = Math.max(0, this.config.cursorScale ?? 1);
|
||||
this.compositeCtx.drawImage(
|
||||
image,
|
||||
projectedPoint.x - metrics.hotspotX * scale,
|
||||
projectedPoint.y - metrics.hotspotY * scale,
|
||||
metrics.width * scale,
|
||||
metrics.height * scale,
|
||||
projectedPoint.x - renderAsset.hotspotX * scale,
|
||||
projectedPoint.y - renderAsset.hotspotY * scale,
|
||||
renderAsset.width * scale,
|
||||
renderAsset.height * scale,
|
||||
);
|
||||
}
|
||||
|
||||
private async getCursorImage(asset: NativeCursorAsset) {
|
||||
private async getCursorImage(asset: { id: string; imageDataUrl: string }) {
|
||||
const cachedImage = this.cursorImageCache.get(asset.id);
|
||||
if (cachedImage) {
|
||||
return cachedImage;
|
||||
|
||||
@@ -3,6 +3,21 @@ export const NATIVE_BRIDGE_VERSION = 1;
|
||||
|
||||
export type NativePlatform = "darwin" | "win32" | "linux";
|
||||
export type CursorProviderKind = "native" | "none";
|
||||
export type NativeCursorType =
|
||||
| "arrow"
|
||||
| "text"
|
||||
| "pointer"
|
||||
| "crosshair"
|
||||
| "resize-ew"
|
||||
| "resize-ns"
|
||||
| "resize-nesw"
|
||||
| "resize-nwse"
|
||||
| "move"
|
||||
| "not-allowed"
|
||||
| "wait"
|
||||
| "app-starting"
|
||||
| "help"
|
||||
| "up-arrow";
|
||||
|
||||
export interface CursorTelemetryPoint {
|
||||
timeMs: number;
|
||||
@@ -13,6 +28,7 @@ export interface CursorTelemetryPoint {
|
||||
export interface CursorRecordingSample extends CursorTelemetryPoint {
|
||||
assetId?: string | null;
|
||||
visible?: boolean;
|
||||
cursorType?: NativeCursorType | null;
|
||||
}
|
||||
|
||||
export interface NativeCursorAsset {
|
||||
@@ -24,6 +40,7 @@ export interface NativeCursorAsset {
|
||||
hotspotX: number;
|
||||
hotspotY: number;
|
||||
scaleFactor?: number;
|
||||
cursorType?: NativeCursorType | null;
|
||||
}
|
||||
|
||||
export interface CursorRecordingData {
|
||||
|
||||
Reference in New Issue
Block a user