From 0e6bbb325ac18c85701c461383a768b483d3d31a Mon Sep 17 00:00:00 2001 From: Ilya <47112191+ItsEeleeya@users.noreply.github.com> Date: Wed, 10 Dec 2025 10:16:10 +0330 Subject: [PATCH 1/5] Custom smoothness level --- .../src/routes/editor/ConfigSidebar.tsx | 153 ++++++------------ apps/desktop/src/routes/editor/context.ts | 36 +---- apps/desktop/src/utils/tauri.ts | 5 +- crates/project/src/configuration.rs | 12 +- crates/rendering/src/composite_frame.rs | 4 +- crates/rendering/src/lib.rs | 73 +++++++-- .../src/shaders/composite-video-frame.wgsl | 29 ++-- packages/ui-solid/src/auto-imports.d.ts | 1 + 8 files changed, 134 insertions(+), 179 deletions(-) diff --git a/apps/desktop/src/routes/editor/ConfigSidebar.tsx b/apps/desktop/src/routes/editor/ConfigSidebar.tsx index 7b56751f9a..23230c4c03 100644 --- a/apps/desktop/src/routes/editor/ConfigSidebar.tsx +++ b/apps/desktop/src/routes/editor/ConfigSidebar.tsx @@ -65,7 +65,7 @@ import IconLucideTimer from "~icons/lucide/timer"; import IconLucideType from "~icons/lucide/type"; import IconLucideWind from "~icons/lucide/wind"; import { CaptionsTab } from "./CaptionsTab"; -import { type CornerRoundingType, useEditorContext } from "./context"; +import { useEditorContext } from "./context"; import { evaluateMask, type MaskKind, type MaskSegment } from "./masks"; import { DEFAULT_GRADIENT_FROM, @@ -219,11 +219,6 @@ const CAMERA_SHAPES = [ }, ] satisfies Array<{ name: string; value: CameraShape }>; -const CORNER_STYLE_OPTIONS = [ - { name: "Squircle", value: "squircle" }, - { name: "Rounded", value: "rounded" }, -] satisfies Array<{ name: string; value: CornerRoundingType }>; - const BACKGROUND_THEMES = { macOS: "macOS", dark: "Dark", @@ -1718,9 +1713,9 @@ function BackgroundConfig(props: { scrollRef: HTMLDivElement }) { if (!file) return; /* - this is a Tauri bug in WebKit so we need to validate the file type manually - https://github.com/tauri-apps/tauri/issues/9158 - */ + this is a Tauri bug in WebKit so we need to validate the file type manually + https://github.com/tauri-apps/tauri/issues/9158 + */ const validExtensions = [ "jpg", "jpeg", @@ -1986,23 +1981,27 @@ function BackgroundConfig(props: { scrollRef: HTMLDivElement }) { /> }> -
- setProject("background", "rounding", v[0])} - minValue={0} - maxValue={100} - step={0.1} - formatTooltip="%" - /> - - setProject("background", "roundingType", value) - } - /> -
+ setProject("background", "rounding", v[0])} + minValue={0} + maxValue={100} + step={0.1} + formatTooltip="%" + /> +
+ } + > + setProject("background", "roundingSmoothness", v[0])} + minValue={0} + maxValue={1} + step={0.01} + formatTooltip={(value) => `${Math.round(value * 100)}%`} + /> }> }> -
- setProject("camera", "rounding", v[0])} - minValue={0} - maxValue={100} - step={0.1} - formatTooltip="%" - /> - setProject("camera", "roundingType", value)} - /> -
+ setProject("camera", "rounding", v[0])} + minValue={0} + maxValue={100} + step={0.1} + formatTooltip="%" + /> +
+ } + > + setProject("camera", "roundingSmoothness", v[0])} + minValue={0} + maxValue={1} + step={0.01} + formatTooltip={(value) => `${Math.round(value * 100)}%`} + /> }>
@@ -2425,72 +2430,6 @@ function CameraConfig(props: { scrollRef: HTMLDivElement }) { ); } -function CornerStyleSelect(props: { - label?: string; - value: CornerRoundingType; - onChange: (value: CornerRoundingType) => void; -}) { - return ( -
- - {(label) => ( - - {label()} - - )} - - - options={CORNER_STYLE_OPTIONS} - optionValue="value" - optionTextValue="name" - value={CORNER_STYLE_OPTIONS.find( - (option) => option.value === props.value, - )} - onChange={(option) => option && props.onChange(option.value)} - disallowEmptySelection - itemComponent={(itemProps) => ( - - as={KSelect.Item} - item={itemProps.item} - > - - {itemProps.item.rawValue.name} - - - )} - > - - class="flex-1 text-sm text-left truncate text-[--gray-500] font-normal"> - {(state) => {state.selectedOption().name}} - - - as={(iconProps) => ( - - )} - /> - - - - as={KSelect.Content} - class={cx(topSlideAnimateClasses, "z-50")} - > - - class="overflow-y-auto max-h-32" - as={KSelect.Listbox} - /> - - - -
- ); -} - const TEXT_FONT_OPTIONS = [ { value: "sans-serif", label: "Sans" }, { value: "serif", label: "Serif" }, diff --git a/apps/desktop/src/routes/editor/context.ts b/apps/desktop/src/routes/editor/context.ts index 502760f169..601fd67800 100644 --- a/apps/desktop/src/routes/editor/context.ts +++ b/apps/desktop/src/routes/editor/context.ts @@ -86,10 +86,6 @@ export type CustomDomainResponse = { domain_verified: boolean | null; }; -export type CornerRoundingType = "rounded" | "squircle"; - -type WithCornerStyle = T & { roundingType: CornerRoundingType }; - type EditorTimelineConfiguration = Omit< TimelineConfiguration, "sceneSegments" | "maskSegments" @@ -103,25 +99,12 @@ export type EditorProjectConfiguration = Omit< ProjectConfiguration, "background" | "camera" | "timeline" > & { - background: WithCornerStyle; - camera: WithCornerStyle; + background: ProjectConfiguration["background"]; + camera: ProjectConfiguration["camera"]; timeline?: EditorTimelineConfiguration | null; hiddenTextSegments?: number[]; }; -function withCornerDefaults< - T extends { - roundingType?: CornerRoundingType; - rounding_type?: CornerRoundingType; - }, ->(value: T): T & { roundingType: CornerRoundingType } { - const roundingType = value.roundingType ?? value.rounding_type ?? "squircle"; - return { - ...value, - roundingType, - }; -} - export function normalizeProject( config: ProjectConfiguration, ): EditorProjectConfiguration { @@ -147,8 +130,6 @@ export function normalizeProject( return { ...config, timeline, - background: withCornerDefaults(config.background), - camera: withCornerDefaults(config.camera), }; } @@ -156,9 +137,6 @@ export function serializeProjectConfiguration( project: EditorProjectConfiguration, ): ProjectConfiguration { const { background, camera, ...rest } = project; - const { roundingType: backgroundRoundingType, ...backgroundRest } = - background; - const { roundingType: cameraRoundingType, ...cameraRest } = camera; const timeline = project.timeline ? { @@ -171,14 +149,8 @@ export function serializeProjectConfiguration( return { ...rest, timeline: timeline as unknown as ProjectConfiguration["timeline"], - background: { - ...backgroundRest, - roundingType: backgroundRoundingType, - }, - camera: { - ...cameraRest, - roundingType: cameraRoundingType, - }, + background, + camera, }; } diff --git a/apps/desktop/src/utils/tauri.ts b/apps/desktop/src/utils/tauri.ts index 9a1f36bcc4..616fda3c34 100644 --- a/apps/desktop/src/utils/tauri.ts +++ b/apps/desktop/src/utils/tauri.ts @@ -357,10 +357,10 @@ export type AudioMeta = { path: string; start_time?: number | null } export type AuthSecret = { api_key: string } | { token: string; expires: number } export type AuthStore = { secret: AuthSecret; user_id: string | null; plan: Plan | null; intercom_hash: string | null; organizations?: Organization[] } -export type BackgroundConfiguration = { source: BackgroundSource; blur: number; padding: number; rounding: number; roundingType?: CornerStyle; inset: number; crop: Crop | null; shadow?: number; advancedShadow?: ShadowConfiguration | null; border?: BorderConfiguration | null } +export type BackgroundConfiguration = { source: BackgroundSource; blur: number; padding: number; rounding: number; roundingSmoothness?: number; inset: number; crop: Crop | null; shadow?: number; advancedShadow?: ShadowConfiguration | null; border?: BorderConfiguration | null } export type BackgroundSource = { type: "wallpaper"; path: string | null } | { type: "image"; path: string | null } | { type: "color"; value: [number, number, number]; alpha?: number } | { type: "gradient"; from: [number, number, number]; to: [number, number, number]; angle?: number } export type BorderConfiguration = { enabled: boolean; width: number; color: [number, number, number]; opacity: number } -export type Camera = { hide: boolean; mirror: boolean; position: CameraPosition; size: number; zoomSize: number | null; rounding?: number; shadow?: number; advancedShadow?: ShadowConfiguration | null; shape?: CameraShape; roundingType?: CornerStyle } +export type Camera = { hide: boolean; mirror: boolean; position: CameraPosition; size: number; zoomSize: number | null; rounding?: number; shadow?: number; advancedShadow?: ShadowConfiguration | null; shape?: CameraShape; roundingSmoothness?: number } export type CameraInfo = { device_id: string; model_id: ModelIDType | null; display_name: string } export type CameraPosition = { x: CameraXPosition; y: CameraYPosition } export type CameraPreviewShape = "round" | "square" | "full" @@ -380,7 +380,6 @@ export type CaptureWindowWithThumbnail = { id: WindowId; owner_name: string; nam export type ClipConfiguration = { index: number; offsets: ClipOffsets } export type ClipOffsets = { camera?: number; mic?: number; system_audio?: number } export type CommercialLicense = { licenseKey: string; expiryDate: number | null; refresh: number; activatedOn: number } -export type CornerStyle = "squircle" | "rounded" export type Crop = { position: XY; size: XY } export type CurrentRecording = { target: CurrentRecordingTarget; mode: RecordingMode; status: RecordingStatus } export type CurrentRecordingChanged = null diff --git a/crates/project/src/configuration.rs b/crates/project/src/configuration.rs index 85b34847ca..f43d20f963 100644 --- a/crates/project/src/configuration.rs +++ b/crates/project/src/configuration.rs @@ -227,7 +227,7 @@ pub struct BackgroundConfiguration { pub padding: f64, pub rounding: f64, #[serde(default)] - pub rounding_type: CornerStyle, + pub rounding_smoothness: f32, pub inset: u32, pub crop: Option, #[serde(default)] @@ -256,12 +256,12 @@ impl Default for BackgroundConfiguration { blur: 0.0, padding: 0.0, rounding: 0.0, - rounding_type: CornerStyle::default(), + rounding_smoothness: 0.6, inset: 0, crop: None, shadow: 73.6, advanced_shadow: Some(ShadowConfiguration::default()), - border: None, // Border is disabled by default for backwards compatibility + border: None, } } } @@ -307,8 +307,8 @@ pub struct Camera { pub advanced_shadow: Option, #[serde(default)] pub shape: CameraShape, - #[serde(alias = "rounding_type", default)] - pub rounding_type: CornerStyle, + #[serde(default)] + pub rounding_smoothness: f32, } #[derive(Debug, Clone, Copy, Serialize, Deserialize, Type, Default)] @@ -345,7 +345,7 @@ impl Default for Camera { blur: 10.5, }), shape: CameraShape::Square, - rounding_type: CornerStyle::default(), + rounding_smoothness: 0.0, } } } diff --git a/crates/rendering/src/composite_frame.rs b/crates/rendering/src/composite_frame.rs index 8d2eaae2fd..35a2e32e34 100644 --- a/crates/rendering/src/composite_frame.rs +++ b/crates/rendering/src/composite_frame.rs @@ -20,7 +20,7 @@ pub struct CompositeVideoFrameUniforms { pub motion_blur_params: [f32; 4], pub target_size: [f32; 2], pub rounding_px: f32, - pub rounding_type: f32, + pub corner_exponent: f32, pub mirror_x: f32, pub shadow: f32, pub shadow_size: f32, @@ -48,7 +48,7 @@ impl Default for CompositeVideoFrameUniforms { motion_blur_params: Default::default(), target_size: Default::default(), rounding_px: Default::default(), - rounding_type: 0.0, + corner_exponent: Default::default(), mirror_x: Default::default(), shadow: Default::default(), shadow_size: Default::default(), diff --git a/crates/rendering/src/lib.rs b/crates/rendering/src/lib.rs index fa31c5c4b5..81448b767a 100644 --- a/crates/rendering/src/lib.rs +++ b/crates/rendering/src/lib.rs @@ -1,7 +1,7 @@ use anyhow::Result; use cap_project::{ - AspectRatio, CameraShape, CameraXPosition, CameraYPosition, ClipOffsets, CornerStyle, Crop, - CursorEvents, MaskKind, ProjectConfiguration, RecordingMeta, StudioRecordingMeta, XY, + AspectRatio, CameraShape, CameraXPosition, CameraYPosition, ClipOffsets, Crop, CursorEvents, + MaskKind, ProjectConfiguration, RecordingMeta, StudioRecordingMeta, XY, }; use composite_frame::CompositeVideoFrameUniforms; use core::f64; @@ -46,13 +46,6 @@ use zoom::*; const STANDARD_CURSOR_HEIGHT: f32 = 75.0; -fn rounding_type_value(style: CornerStyle) -> f32 { - match style { - CornerStyle::Rounded => 0.0, - CornerStyle::Squircle => 1.0, - } -} - #[derive(Debug, Clone, Copy, Type)] pub struct RenderOptions { pub camera_size: Option>, @@ -1164,9 +1157,13 @@ impl ProjectUniforms { ], target_bounds: [start.x as f32, start.y as f32, end.x as f32, end.y as f32], target_size: [target_size.x as f32, target_size.y as f32], - rounding_px: (project.background.rounding / 100.0 * 0.5 * min_target_axis) - as f32, - rounding_type: rounding_type_value(project.background.rounding_type), + rounding_px: compute_adjusted_radius( + project.background.rounding as f32 / 100.0 * 0.5 * min_target_axis as f32, + project.background.rounding_smoothness as f32, + ), + corner_exponent: smoothness_to_exponent( + project.background.rounding_smoothness as f32, + ), mirror_x: 0.0, motion_blur_vector: descriptor.movement_vector_uv, motion_blur_zoom_center: descriptor.zoom_center_uv, @@ -1343,8 +1340,13 @@ impl ProjectUniforms { target_bounds[2] - target_bounds[0], target_bounds[3] - target_bounds[1], ], - rounding_px: project.camera.rounding / 100.0 * 0.5 * size[0].min(size[1]), - rounding_type: rounding_type_value(project.camera.rounding_type), + rounding_px: compute_adjusted_radius( + project.camera.rounding / 100.0 * 0.5 * size[0].min(size[1]) as f32, + project.background.rounding_smoothness as f32, + ), + corner_exponent: smoothness_to_exponent( + project.background.rounding_smoothness as f32, + ), mirror_x: if project.camera.mirror { 1.0 } else { 0.0 }, motion_blur_vector: camera_descriptor.movement_vector_uv, motion_blur_zoom_center: camera_descriptor.zoom_center_uv, @@ -1443,7 +1445,7 @@ impl ProjectUniforms { target_bounds[3] - target_bounds[1], ], rounding_px: 0.0, - rounding_type: rounding_type_value(project.camera.rounding_type), + corner_exponent: 0.6, mirror_x: if project.camera.mirror { 1.0 } else { 0.0 }, motion_blur_vector: camera_only_descriptor.movement_vector_uv, motion_blur_zoom_center: camera_only_descriptor.zoom_center_uv, @@ -2018,6 +2020,47 @@ fn srgb_to_linear(c: u16) -> f32 { } } +/// Converts a smoothness value (0.0 to 1.0) to a super ellipse exponent. +/// +/// When `smoothness` is 0.0, returns 2.0 (circular/rounded rectangle). +/// As smoothness increases toward 1.0, the exponent increases, creating +/// a more squared-off super ellipse shape. +/// +/// # Arguments +/// * `smoothness` - Smoothness factor (0.0 = rounded rect, 1.0 = super ellipse) +/// +/// # Returns +/// Super ellipse exponent value +#[inline] +fn smoothness_to_exponent(smoothness: f32) -> f32 { + 2.0 + smoothness * 6.0 +} + +/// Adjusts radius to maintain visual consistency across different super ellipse exponents. +/// +/// Combines two compensations: +/// 1. Capsule scaling - ensures 100% radius produces a capsule regardless of smoothness +/// 2. Visual normalization - compensates for super ellipses appearing smaller at higher exponents +/// +/// # Arguments +/// * `radius` - Base corner radius in pixels +/// * `smoothness` - Smoothness factor (0.0 to 1.0) +/// +/// # Returns +/// Adjusted radius that maintains visual consistency and capsule behavior +fn compute_adjusted_radius(radius: f32, smoothness: f32) -> f32 { + if smoothness == 0.0 { + return radius; + } + + let power = smoothness_to_exponent(smoothness); + let capsule_scale = 2.0f32.powf(0.5 - 1.0 / power); + + // Visual normalization: compensate for super ellipses appearing smaller + let visual_compensation = 1.0 + smoothness * 0.8; + radius * capsule_scale * visual_compensation +} + #[cfg(test)] mod project_uniforms_tests { use super::*; diff --git a/crates/rendering/src/shaders/composite-video-frame.wgsl b/crates/rendering/src/shaders/composite-video-frame.wgsl index 40d5311c10..863b5809d2 100644 --- a/crates/rendering/src/shaders/composite-video-frame.wgsl +++ b/crates/rendering/src/shaders/composite-video-frame.wgsl @@ -8,7 +8,7 @@ struct Uniforms { motion_blur_params: vec4, target_size: vec2, rounding_px: f32, - rounding_type: f32, + corner_exponent: f32, mirror_x: f32, shadow: f32, shadow_size: f32, @@ -51,19 +51,20 @@ fn superellipse_norm(p: vec2, power: f32) -> f32 { return pow(x + y, 1.0 / power); } -fn rounded_corner_norm(p: vec2, rounding_type: f32) -> f32 { - if rounding_type < 0.5 { +// Accept an exponent directly. If exponent <= 0 -> use circular corner (length). +fn rounded_corner_norm(p: vec2, exponent: f32) -> f32 { + if exponent <= 0.0 { return length(p); } - - let power = 4.0; + // protect against pathological exponent values + let power = max(exponent, 1e-3); return superellipse_norm(p, power); } -fn sdf_rounded_rect(p: vec2, b: vec2, r: f32, rounding_type: f32) -> f32 { +fn sdf_rounded_rect(p: vec2, b: vec2, r: f32, exponent: f32) -> f32 { let q = abs(p) - b + vec2(r); let outside = max(q, vec2(0.0)); - let outside_norm = rounded_corner_norm(outside, rounding_type); + let outside_norm = rounded_corner_norm(outside, exponent); return outside_norm + min(max(q.x, q.y), 0.0) - r; } @@ -72,8 +73,8 @@ fn fs_main(@builtin(position) frag_coord: vec4) -> @location(0) vec4 { let p = frag_coord.xy; let center = (uniforms.target_bounds.xy + uniforms.target_bounds.zw) * 0.5; let size = (uniforms.target_bounds.zw - uniforms.target_bounds.xy) * 0.5; - - let dist = sdf_rounded_rect(p - center, size, uniforms.rounding_px, uniforms.rounding_type); + + let dist = sdf_rounded_rect(p - center, size, uniforms.rounding_px, uniforms.corner_exponent); let min_frame_size = min(size.x, size.y); let shadow_enabled = uniforms.shadow > 0.0; @@ -100,7 +101,7 @@ fn fs_main(@builtin(position) frag_coord: vec4) -> @location(0) vec4 { shadow_enabled ); - let shadow_dist = sdf_rounded_rect(p - center, size, uniforms.rounding_px, uniforms.rounding_type); + let shadow_dist = sdf_rounded_rect(p - center, size, uniforms.rounding_px, uniforms.corner_exponent); // Apply blur and size to shadow let shadow_strength_final = smoothstep(shadow_size + shadow_blur, -shadow_blur, abs(shadow_dist)); @@ -117,10 +118,10 @@ fn fs_main(@builtin(position) frag_coord: vec4) -> @location(0) vec4 { p - center, size + vec2(uniforms.border_width), uniforms.rounding_px + uniforms.border_width, - uniforms.rounding_type + uniforms.corner_exponent ); let border_inner_dist = - sdf_rounded_rect(p - center, size, uniforms.rounding_px, uniforms.rounding_type); + sdf_rounded_rect(p - center, size, uniforms.rounding_px, uniforms.corner_exponent); if (border_outer_dist <= 0.0 && border_inner_dist > 0.0) { let inner_alpha = smoothstep(-0.5, 0.5, border_inner_dist); @@ -131,7 +132,7 @@ fn fs_main(@builtin(position) frag_coord: vec4) -> @location(0) vec4 { return vec4(uniforms.border_color.xyz, border_alpha); } } - + if target_uv.x < 0.0 || target_uv.x > 1.0 || target_uv.y < 0.0 || target_uv.y > 1.0 { return shadow_color; } @@ -269,7 +270,7 @@ fn sample_texture(uv: vec2, crop_bounds_uv: vec4) -> vec4 { fn apply_rounded_corners(current_color: vec4, target_uv: vec2) -> vec4 { let centered_uv = (target_uv - vec2(0.5)) * uniforms.target_size; let half_size = uniforms.target_size * 0.5; - let distance = sdf_rounded_rect(centered_uv, half_size, uniforms.rounding_px, uniforms.rounding_type); + let distance = sdf_rounded_rect(centered_uv, half_size, uniforms.rounding_px, uniforms.corner_exponent); let anti_alias_width = max(fwidth(distance), 0.5); let coverage = clamp(1.0 - smoothstep(0.0, anti_alias_width, distance), 0.0, 1.0); diff --git a/packages/ui-solid/src/auto-imports.d.ts b/packages/ui-solid/src/auto-imports.d.ts index 9708f6b361..a89318c90b 100644 --- a/packages/ui-solid/src/auto-imports.d.ts +++ b/packages/ui-solid/src/auto-imports.d.ts @@ -94,6 +94,7 @@ declare global { const IconLucideSave: typeof import('~icons/lucide/save.jsx')['default'] const IconLucideSearch: typeof import('~icons/lucide/search.jsx')['default'] const IconLucideSquarePlay: typeof import('~icons/lucide/square-play.jsx')['default'] + const IconLucideSquareRoundCorner: typeof import('~icons/lucide/square-round-corner.jsx')['default'] const IconLucideTimer: typeof import('~icons/lucide/timer.jsx')['default'] const IconLucideType: typeof import('~icons/lucide/type.jsx')['default'] const IconLucideUnplug: typeof import('~icons/lucide/unplug.jsx')['default'] From 69594bdbb7861c66333aa82dafb2d744e973eb1a Mon Sep 17 00:00:00 2001 From: Ilya <47112191+ItsEeleeya@users.noreply.github.com> Date: Wed, 10 Dec 2025 10:49:24 +0330 Subject: [PATCH 2/5] Add support to screenshot editor --- .../src/routes/editor/ConfigSidebar.tsx | 2 +- .../popovers/RoundingPopover.tsx | 129 +++++------------- crates/rendering/src/lib.rs | 25 +--- 3 files changed, 34 insertions(+), 122 deletions(-) diff --git a/apps/desktop/src/routes/editor/ConfigSidebar.tsx b/apps/desktop/src/routes/editor/ConfigSidebar.tsx index 23230c4c03..88320354e8 100644 --- a/apps/desktop/src/routes/editor/ConfigSidebar.tsx +++ b/apps/desktop/src/routes/editor/ConfigSidebar.tsx @@ -2340,7 +2340,7 @@ function CameraConfig(props: { scrollRef: HTMLDivElement }) { }> setProject("camera", "rounding", v[0])} minValue={0} maxValue={100} diff --git a/apps/desktop/src/routes/screenshot-editor/popovers/RoundingPopover.tsx b/apps/desktop/src/routes/screenshot-editor/popovers/RoundingPopover.tsx index 9c9612c009..87a85f82f1 100644 --- a/apps/desktop/src/routes/screenshot-editor/popovers/RoundingPopover.tsx +++ b/apps/desktop/src/routes/screenshot-editor/popovers/RoundingPopover.tsx @@ -1,24 +1,7 @@ import { Popover } from "@kobalte/core/popover"; -import { Select as KSelect } from "@kobalte/core/select"; -import { cx } from "cva"; -import { Show, type ValidComponent } from "solid-js"; -import IconCapChevronDown from "~icons/cap/chevron-down"; import IconCapCorners from "~icons/cap/corners"; import { useScreenshotEditorContext } from "../context"; -import { - EditorButton, - MenuItem, - MenuItemList, - PopperContent, - Slider, - topSlideAnimateClasses, -} from "../ui"; - -export type CornerRoundingType = "rounded" | "squircle"; -const CORNER_STYLE_OPTIONS = [ - { name: "Squircle", value: "squircle" }, - { name: "Rounded", value: "rounded" }, -] satisfies Array<{ name: string; value: CornerRoundingType }>; +import { EditorButton, Slider } from "../ui"; export function RoundingPopover() { const { project, setProject, activePopover, setActivePopover } = @@ -39,90 +22,40 @@ export function RoundingPopover() {
- Rounding - setProject("background", "rounding", v[0])} - minValue={0} - maxValue={100} - step={1} - formatTooltip="px" - /> +
+ + + Rounding + + setProject("background", "rounding", v[0])} + minValue={0} + maxValue={100} + step={0.1} + formatTooltip="%" + /> +
+
+ + + Rounding Smoothness + + + setProject("background", "roundingSmoothness", v[0]) + } + minValue={0} + maxValue={1} + step={0.01} + formatTooltip={(value) => `${Math.round(value * 100)}%`} + /> +
- setProject("background", "roundingType", v)} - />
); } - -function CornerStyleSelect(props: { - label?: string; - value: CornerRoundingType; - onChange: (value: CornerRoundingType) => void; -}) { - return ( -
- - {(label) => ( - - {label()} - - )} - - - options={CORNER_STYLE_OPTIONS} - optionValue="value" - optionTextValue="name" - value={CORNER_STYLE_OPTIONS.find( - (option) => option.value === props.value, - )} - onChange={(option) => option && props.onChange(option.value)} - disallowEmptySelection - itemComponent={(itemProps) => ( - - as={KSelect.Item} - item={itemProps.item} - > - - {itemProps.item.rawValue.name} - - - )} - > - - class="flex-1 text-sm text-left truncate text-[--gray-500] font-normal"> - {(state) => {state.selectedOption().name}} - - - as={(iconProps) => ( - - )} - /> - - - - as={KSelect.Content} - class={cx(topSlideAnimateClasses, "z-50")} - > - - class="overflow-y-auto max-h-32" - as={KSelect.Listbox} - /> - - - -
- ); -} diff --git a/crates/rendering/src/lib.rs b/crates/rendering/src/lib.rs index 81448b767a..c72120ba53 100644 --- a/crates/rendering/src/lib.rs +++ b/crates/rendering/src/lib.rs @@ -2020,34 +2020,13 @@ fn srgb_to_linear(c: u16) -> f32 { } } -/// Converts a smoothness value (0.0 to 1.0) to a super ellipse exponent. -/// -/// When `smoothness` is 0.0, returns 2.0 (circular/rounded rectangle). -/// As smoothness increases toward 1.0, the exponent increases, creating -/// a more squared-off super ellipse shape. -/// -/// # Arguments -/// * `smoothness` - Smoothness factor (0.0 = rounded rect, 1.0 = super ellipse) -/// -/// # Returns -/// Super ellipse exponent value +// Converts a smoothness value (0.0 to 1.0) to a super ellipse exponent. #[inline] fn smoothness_to_exponent(smoothness: f32) -> f32 { 2.0 + smoothness * 6.0 } -/// Adjusts radius to maintain visual consistency across different super ellipse exponents. -/// -/// Combines two compensations: -/// 1. Capsule scaling - ensures 100% radius produces a capsule regardless of smoothness -/// 2. Visual normalization - compensates for super ellipses appearing smaller at higher exponents -/// -/// # Arguments -/// * `radius` - Base corner radius in pixels -/// * `smoothness` - Smoothness factor (0.0 to 1.0) -/// -/// # Returns -/// Adjusted radius that maintains visual consistency and capsule behavior +// Adjusts radius to maintain visual consistency across different super ellipse exponents. fn compute_adjusted_radius(radius: f32, smoothness: f32) -> f32 { if smoothness == 0.0 { return radius; From ba6dbbea2a1b0aabf2352c8bb17bb8d44257f5e7 Mon Sep 17 00:00:00 2001 From: Ilya <47112191+ItsEeleeya@users.noreply.github.com> Date: Wed, 10 Dec 2025 10:56:47 +0330 Subject: [PATCH 3/5] Use default 0.6 for corner exponent, remove unused cast to f32 --- crates/rendering/src/composite_frame.rs | 2 +- crates/rendering/src/lib.rs | 12 ++++-------- 2 files changed, 5 insertions(+), 9 deletions(-) diff --git a/crates/rendering/src/composite_frame.rs b/crates/rendering/src/composite_frame.rs index 35a2e32e34..8c5f3183f0 100644 --- a/crates/rendering/src/composite_frame.rs +++ b/crates/rendering/src/composite_frame.rs @@ -48,7 +48,7 @@ impl Default for CompositeVideoFrameUniforms { motion_blur_params: Default::default(), target_size: Default::default(), rounding_px: Default::default(), - corner_exponent: Default::default(), + corner_exponent: 0.6, mirror_x: Default::default(), shadow: Default::default(), shadow_size: Default::default(), diff --git a/crates/rendering/src/lib.rs b/crates/rendering/src/lib.rs index c72120ba53..3380773d12 100644 --- a/crates/rendering/src/lib.rs +++ b/crates/rendering/src/lib.rs @@ -1159,11 +1159,9 @@ impl ProjectUniforms { target_size: [target_size.x as f32, target_size.y as f32], rounding_px: compute_adjusted_radius( project.background.rounding as f32 / 100.0 * 0.5 * min_target_axis as f32, - project.background.rounding_smoothness as f32, - ), - corner_exponent: smoothness_to_exponent( - project.background.rounding_smoothness as f32, + project.background.rounding_smoothness, ), + corner_exponent: smoothness_to_exponent(project.background.rounding_smoothness), mirror_x: 0.0, motion_blur_vector: descriptor.movement_vector_uv, motion_blur_zoom_center: descriptor.zoom_center_uv, @@ -1342,11 +1340,9 @@ impl ProjectUniforms { ], rounding_px: compute_adjusted_radius( project.camera.rounding / 100.0 * 0.5 * size[0].min(size[1]) as f32, - project.background.rounding_smoothness as f32, - ), - corner_exponent: smoothness_to_exponent( - project.background.rounding_smoothness as f32, + project.background.rounding_smoothness, ), + corner_exponent: smoothness_to_exponent(project.background.rounding_smoothness), mirror_x: if project.camera.mirror { 1.0 } else { 0.0 }, motion_blur_vector: camera_descriptor.movement_vector_uv, motion_blur_zoom_center: camera_descriptor.zoom_center_uv, From 5d61eb28c81d9e5138e1c3cf62d9e9d04cefaa66 Mon Sep 17 00:00:00 2001 From: Ilya <47112191+ItsEeleeya@users.noreply.github.com> Date: Wed, 10 Dec 2025 10:59:24 +0330 Subject: [PATCH 4/5] oops --- crates/rendering/src/lib.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/crates/rendering/src/lib.rs b/crates/rendering/src/lib.rs index 3380773d12..c3c2ff397b 100644 --- a/crates/rendering/src/lib.rs +++ b/crates/rendering/src/lib.rs @@ -1340,9 +1340,9 @@ impl ProjectUniforms { ], rounding_px: compute_adjusted_radius( project.camera.rounding / 100.0 * 0.5 * size[0].min(size[1]) as f32, - project.background.rounding_smoothness, + project.camera.rounding_smoothness, ), - corner_exponent: smoothness_to_exponent(project.background.rounding_smoothness), + corner_exponent: smoothness_to_exponent(project.camera.rounding_smoothness), mirror_x: if project.camera.mirror { 1.0 } else { 0.0 }, motion_blur_vector: camera_descriptor.movement_vector_uv, motion_blur_zoom_center: camera_descriptor.zoom_center_uv, @@ -1441,7 +1441,7 @@ impl ProjectUniforms { target_bounds[3] - target_bounds[1], ], rounding_px: 0.0, - corner_exponent: 0.6, + corner_exponent: 0.0, mirror_x: if project.camera.mirror { 1.0 } else { 0.0 }, motion_blur_vector: camera_only_descriptor.movement_vector_uv, motion_blur_zoom_center: camera_only_descriptor.zoom_center_uv, From fd0edbfd2e0b94acc866599647aad6ddfde7ac4e Mon Sep 17 00:00:00 2001 From: Ilya <47112191+ItsEeleeya@users.noreply.github.com> Date: Wed, 10 Dec 2025 11:15:00 +0330 Subject: [PATCH 5/5] smoothness_to_exponent --- crates/rendering/src/composite_frame.rs | 4 ++-- crates/rendering/src/lib.rs | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/crates/rendering/src/composite_frame.rs b/crates/rendering/src/composite_frame.rs index 8c5f3183f0..a878f4592d 100644 --- a/crates/rendering/src/composite_frame.rs +++ b/crates/rendering/src/composite_frame.rs @@ -1,7 +1,7 @@ use bytemuck::{Pod, Zeroable}; use wgpu::{include_wgsl, util::DeviceExt}; -use crate::create_shader_render_pipeline; +use crate::{create_shader_render_pipeline, smoothness_to_exponent}; pub struct CompositeVideoFramePipeline { pub bind_group_layout: wgpu::BindGroupLayout, @@ -48,7 +48,7 @@ impl Default for CompositeVideoFrameUniforms { motion_blur_params: Default::default(), target_size: Default::default(), rounding_px: Default::default(), - corner_exponent: 0.6, + corner_exponent: smoothness_to_exponent(0.6), mirror_x: Default::default(), shadow: Default::default(), shadow_size: Default::default(), diff --git a/crates/rendering/src/lib.rs b/crates/rendering/src/lib.rs index c3c2ff397b..efe256fc4c 100644 --- a/crates/rendering/src/lib.rs +++ b/crates/rendering/src/lib.rs @@ -2018,7 +2018,7 @@ fn srgb_to_linear(c: u16) -> f32 { // Converts a smoothness value (0.0 to 1.0) to a super ellipse exponent. #[inline] -fn smoothness_to_exponent(smoothness: f32) -> f32 { +pub(crate) fn smoothness_to_exponent(smoothness: f32) -> f32 { 2.0 + smoothness * 6.0 }