mirror of
https://github.com/webadderallorg/Recordly.git
synced 2026-09-24 14:55:37 +00:00
Merge pull request #465 from webadderallorg/fix/issue395-zoom-independent-cursor
fix(editor): keep cursor size stable during zoom
This commit is contained in:
@@ -2182,6 +2182,7 @@ const VideoPlayback = forwardRef<VideoPlaybackRef, VideoPlaybackProps>(
|
||||
baseMaskRef.current,
|
||||
showCursorRef.current,
|
||||
!isPlayingRef.current || isSeekingRef.current,
|
||||
animationStateRef.current.appliedScale || 1,
|
||||
);
|
||||
|
||||
smoothedCursorForHooks = mapSmoothedCursorToCanvasNormalized(
|
||||
|
||||
@@ -0,0 +1,27 @@
|
||||
import { describe, expect, it } from "vitest";
|
||||
|
||||
import { resolveCursorDrawHeight } from "./cursorRenderer";
|
||||
|
||||
describe("resolveCursorDrawHeight", () => {
|
||||
const viewport = {
|
||||
x: 100,
|
||||
y: 50,
|
||||
width: 960,
|
||||
height: 540,
|
||||
};
|
||||
|
||||
it("keeps cursor output size stable when the zoom parent scales up", () => {
|
||||
const baseHeight = resolveCursorDrawHeight(viewport, 70, 1);
|
||||
const zoomedLocalHeight = resolveCursorDrawHeight(viewport, 70, 2);
|
||||
|
||||
expect(zoomedLocalHeight).toBeCloseTo(baseHeight / 2, 6);
|
||||
expect(zoomedLocalHeight * 2).toBeCloseTo(baseHeight, 6);
|
||||
});
|
||||
|
||||
it("falls back to unscaled sizing for invalid parent scale values", () => {
|
||||
const baseHeight = resolveCursorDrawHeight(viewport, 70, 1);
|
||||
|
||||
expect(resolveCursorDrawHeight(viewport, 70, 0)).toBe(baseHeight);
|
||||
expect(resolveCursorDrawHeight(viewport, 70, Number.NaN)).toBe(baseHeight);
|
||||
});
|
||||
});
|
||||
@@ -1,8 +1,8 @@
|
||||
import { Assets, BlurFilter, Container, Graphics, Sprite, Texture } from "pixi.js";
|
||||
import { MotionBlurFilter } from "pixi-filters/motion-blur";
|
||||
import minimalCursorUrl from "@/assets/cursors/custom/minimal-cursor.svg";
|
||||
import { getRenderableAssetUrl } from "@/lib/assetPath";
|
||||
import { extensionHost } from "@/lib/extensions";
|
||||
import minimalCursorUrl from "@/assets/cursors/custom/minimal-cursor.svg";
|
||||
import {
|
||||
type CursorStyle,
|
||||
type CursorTelemetryPoint,
|
||||
@@ -12,11 +12,11 @@ import {
|
||||
import { computeCursorSwayRotation } from "./cursorSway";
|
||||
import { type CursorViewportRect, projectCursorPositionToViewport } from "./cursorViewport";
|
||||
import {
|
||||
type CursorSpringTuning,
|
||||
createSpringState,
|
||||
getCursorSpringConfig,
|
||||
resetSpringState,
|
||||
stepSpringValue,
|
||||
type CursorSpringTuning,
|
||||
} from "./motionSmoothing";
|
||||
import { cursorSetAssets, getCursorStyleSizeMultiplier } from "./uploadedCursorAssets";
|
||||
|
||||
@@ -745,6 +745,15 @@ function getCursorViewportScale(viewport: CursorViewportRect) {
|
||||
return Math.max(MIN_CURSOR_VIEWPORT_SCALE, viewport.width / REFERENCE_WIDTH);
|
||||
}
|
||||
|
||||
export function resolveCursorDrawHeight(
|
||||
viewport: CursorViewportRect,
|
||||
dotRadius: number,
|
||||
parentScale = 1,
|
||||
) {
|
||||
const safeParentScale = Number.isFinite(parentScale) && parentScale > 0 ? parentScale : 1;
|
||||
return (dotRadius * getCursorViewportScale(viewport)) / safeParentScale;
|
||||
}
|
||||
|
||||
function getCursorSwaySpringConfig(
|
||||
smoothingFactor: number,
|
||||
springTuning: CursorSpringTuning,
|
||||
@@ -1061,6 +1070,7 @@ export class PixiCursorOverlay {
|
||||
viewport: CursorViewportRect,
|
||||
visible: boolean,
|
||||
freeze = false,
|
||||
parentScale = 1,
|
||||
): void {
|
||||
if (!visible || samples.length === 0 || viewport.width <= 0 || viewport.height <= 0) {
|
||||
this.container.visible = false;
|
||||
@@ -1107,7 +1117,7 @@ export class PixiCursorOverlay {
|
||||
|
||||
const px = viewport.x + this.state.x * viewport.width;
|
||||
const py = viewport.y + this.state.y * viewport.height;
|
||||
const h = this.config.dotRadius * getCursorViewportScale(viewport);
|
||||
const h = resolveCursorDrawHeight(viewport, this.config.dotRadius, parentScale);
|
||||
const { cursorType, clickBounceProgress } = getCursorVisualState(
|
||||
samples,
|
||||
timeMs,
|
||||
@@ -1303,6 +1313,7 @@ export function drawCursorOnCanvas(
|
||||
viewport: CursorViewportRect,
|
||||
smoothedState: SmoothedCursorState,
|
||||
config: CursorRenderConfig = DEFAULT_CURSOR_CONFIG,
|
||||
parentScale = 1,
|
||||
): void {
|
||||
if (samples.length === 0 || viewport.width <= 0 || viewport.height <= 0) return;
|
||||
|
||||
@@ -1316,7 +1327,7 @@ 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);
|
||||
const h = resolveCursorDrawHeight(viewport, config.dotRadius, parentScale);
|
||||
const { cursorType, clickBounceProgress } = getCursorVisualState(
|
||||
samples,
|
||||
timeMs,
|
||||
|
||||
@@ -1626,6 +1626,12 @@ export class FrameRenderer {
|
||||
const timeMs = this.currentVideoTime * 1000;
|
||||
const cursorTimeMs = cursorTimestamp / 1000;
|
||||
|
||||
const TICKS_PER_FRAME = 1;
|
||||
|
||||
for (let i = 0; i < TICKS_PER_FRAME; i++) {
|
||||
this.updateAnimationState(timeMs);
|
||||
}
|
||||
|
||||
if (this.cursorOverlay) {
|
||||
this.cursorOverlay.update(
|
||||
this.config.cursorTelemetry ?? [],
|
||||
@@ -1633,6 +1639,7 @@ export class FrameRenderer {
|
||||
layoutCache.maskRect,
|
||||
this.config.showCursor ?? true,
|
||||
false,
|
||||
this.animationState.appliedScale || 1,
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1655,12 +1662,6 @@ export class FrameRenderer {
|
||||
: null,
|
||||
);
|
||||
|
||||
const TICKS_PER_FRAME = 1;
|
||||
|
||||
for (let i = 0; i < TICKS_PER_FRAME; i++) {
|
||||
this.updateAnimationState(timeMs);
|
||||
}
|
||||
|
||||
applyZoomTransform({
|
||||
cameraContainer: this.cameraContainer,
|
||||
zoomBlurFilter: this.zoomBlurFilter,
|
||||
@@ -2110,6 +2111,8 @@ export class FrameRenderer {
|
||||
const timeMs = this.currentVideoTime * 1000;
|
||||
const cursorTimeMs = cursorTimestamp / 1000;
|
||||
|
||||
this.updateAnimationState(timeMs);
|
||||
|
||||
if (this.cursorOverlay) {
|
||||
this.cursorOverlay.update(
|
||||
this.config.cursorTelemetry ?? [],
|
||||
@@ -2117,6 +2120,7 @@ export class FrameRenderer {
|
||||
layoutCache.maskRect,
|
||||
this.config.showCursor ?? true,
|
||||
false,
|
||||
this.animationState.appliedScale || 1,
|
||||
);
|
||||
}
|
||||
|
||||
@@ -2129,8 +2133,6 @@ export class FrameRenderer {
|
||||
},
|
||||
);
|
||||
|
||||
this.updateAnimationState(timeMs);
|
||||
|
||||
applyZoomTransform({
|
||||
cameraContainer: this.cameraContainer,
|
||||
zoomBlurFilter: this.zoomBlurFilter,
|
||||
|
||||
@@ -2885,6 +2885,8 @@ export class FrameRenderer {
|
||||
const timeMs = this.currentVideoTime * 1000;
|
||||
const cursorTimeMs = cursorTimestamp / 1000;
|
||||
|
||||
this.updateAnimationState(timeMs);
|
||||
|
||||
if (this.cursorOverlay) {
|
||||
this.cursorOverlay.update(
|
||||
this.config.cursorTelemetry ?? [],
|
||||
@@ -2892,11 +2894,10 @@ export class FrameRenderer {
|
||||
layoutCache.maskRect,
|
||||
this.config.showCursor ?? true,
|
||||
false,
|
||||
this.animationState.appliedScale || 1,
|
||||
);
|
||||
}
|
||||
|
||||
this.updateAnimationState(timeMs);
|
||||
|
||||
applyZoomTransform({
|
||||
cameraContainer: this.cameraContainer,
|
||||
zoomBlurFilter: this.zoomBlurFilter,
|
||||
@@ -3132,6 +3133,8 @@ export class FrameRenderer {
|
||||
const timeMs = this.currentVideoTime * 1000;
|
||||
const cursorTimeMs = cursorTimestamp / 1000;
|
||||
|
||||
this.updateAnimationState(timeMs);
|
||||
|
||||
if (this.cursorOverlay) {
|
||||
this.cursorOverlay.update(
|
||||
this.config.cursorTelemetry ?? [],
|
||||
@@ -3139,11 +3142,10 @@ export class FrameRenderer {
|
||||
layoutCache.maskRect,
|
||||
this.config.showCursor ?? true,
|
||||
false,
|
||||
this.animationState.appliedScale || 1,
|
||||
);
|
||||
}
|
||||
|
||||
this.updateAnimationState(timeMs);
|
||||
|
||||
applyZoomTransform({
|
||||
cameraContainer: this.cameraContainer,
|
||||
zoomBlurFilter: this.zoomBlurFilter,
|
||||
|
||||
Reference in New Issue
Block a user