diff --git a/docs/HEROUI_MIGRATION.md b/docs/HEROUI_MIGRATION.md index f65106cf..f31be42b 100644 --- a/docs/HEROUI_MIGRATION.md +++ b/docs/HEROUI_MIGRATION.md @@ -47,8 +47,10 @@ The clip lane samples real source frames with a bounded cache and one background video decoder. Sampling follows clip source offsets, speed, and the visible timeline range; it never seeks the playback element. Zooms use a compact lane with labels that adapt to block width. The ruler chooses tick density from available width, -and the red playhead has a white centre line. Webcam roundness defaults to 69%; -existing saved values remain intact. Three compact timeline tracks fit before +and the red playhead has a white centre line. Webcam roundness defaults to 100%; +existing saved values remain intact. New recordings inherit the saved webcam +appearance, and 100% is the maximum squircle radius rather than a circular mask. +Three compact timeline tracks fit before vertical scrolling. Popovers support outside-click and Escape dismissal and share one padding layer. Project and preset names truncate inside their rows. The recorder keeps its compact desktop layout. diff --git a/src/components/video-editor/types.ts b/src/components/video-editor/types.ts index 904c9cfd..d5dc8714 100644 --- a/src/components/video-editor/types.ts +++ b/src/components/video-editor/types.ts @@ -191,7 +191,7 @@ export const DEFAULT_ZOOM_OUT_EASING: ZoomTransitionEasing = "recordly"; export const DEFAULT_CONNECTED_ZOOM_EASING: ZoomTransitionEasing = "glide"; export const DEFAULT_WEBCAM_SIZE = 40; export const DEFAULT_WEBCAM_REACT_TO_ZOOM = true; -export const DEFAULT_WEBCAM_ROUNDNESS = 69; +export const DEFAULT_WEBCAM_ROUNDNESS = 100; export const DEFAULT_WEBCAM_SHADOW = 0.3; export const DEFAULT_WEBCAM_MARGIN = 24; export const DEFAULT_WEBCAM_POSITION_PRESET: WebcamPositionPreset = "bottom-right"; diff --git a/tests/ui/editor-refinements.spec.ts b/tests/ui/editor-refinements.spec.ts index 02e78dfc..eeb73c76 100644 --- a/tests/ui/editor-refinements.spec.ts +++ b/tests/ui/editor-refinements.spec.ts @@ -61,7 +61,7 @@ test("advanced controls, webcam defaults and captions have consistent layouts", await page.getByRole("radio", { name: "Webcam", exact: true }).click(); await expect(page.getByRole("switch", { name: "Mirror webcam" })).toHaveCount(0); await expect(page.getByRole("switch", { name: "Webcam Reacts To Zoom" })).toHaveCount(0); - await expect(page.getByRole("slider", { name: "Webcam Roundness" })).toHaveValue("69"); + await expect(page.getByRole("slider", { name: "Webcam Roundness" })).toHaveValue("100"); await page.evaluate(() => { window.electronAPI.openVideoFilePicker = async () => ({ success: true, diff --git a/tests/ui/webcam-defaults.spec.ts b/tests/ui/webcam-defaults.spec.ts new file mode 100644 index 00000000..870e1648 --- /dev/null +++ b/tests/ui/webcam-defaults.spec.ts @@ -0,0 +1,47 @@ +import { expect, test } from "@playwright/test"; +import { installDesktopBridge } from "./bridge"; + +for (const savedRoundness of [undefined, 69]) { + test(`new recording webcam uses ${savedRoundness === undefined ? "100% by default" : "saved roundness"}`, async ({ + page, + }) => { + await installDesktopBridge(page); + await page.addInitScript((roundness) => { + if (roundness !== undefined) { + window.electronAPI.getAppSetting = (key) => + key === "recordly.editor.preferences" ? { webcam: { roundness } } : null; + } + window.electronAPI.getCurrentRecordingSession = async () => ({ + success: true, + session: { + videoPath: `${location.origin}/tests/ui/fixtures/preview.mp4`, + webcamPath: `${location.origin}/tests/ui/fixtures/preview.mp4`, + timeOffsetMs: 0, + }, + }); + }, savedRoundness); + await page.goto("/?windowType=editor"); + await page.getByRole("radio", { name: "Webcam", exact: true }).click(); + const expected = savedRoundness ?? 100; + await expect(page.getByRole("slider", { name: "Webcam Roundness" })).toHaveValue( + String(expected), + ); + const mask = page.locator('div[style*="clip-path"][style*="contain: paint"]:has(video)'); + await expect(mask).toHaveCount(1); + await expect + .poll(async () => + mask.evaluate((node) => { + const radius = Number( + getComputedStyle(node).clipPath.match(/M\s+([\d.]+)/)?.[1], + ); + const box = node.getBoundingClientRect(); + return Math.round(100 * (radius / (Math.min(box.width, box.height) / 2)) ** 2); + }), + ) + .toBe(expected); + await page.screenshot({ + path: `test-results/webcam-new-recording-${expected}.png`, + animations: "disabled", + }); + }); +}