diff --git a/crates/agent_ui/src/conversation_view/thread_view.rs b/crates/agent_ui/src/conversation_view/thread_view.rs index d7df0feba87c41..f310a29b6d9dd3 100644 --- a/crates/agent_ui/src/conversation_view/thread_view.rs +++ b/crates/agent_ui/src/conversation_view/thread_view.rs @@ -2608,6 +2608,7 @@ impl ThreadView { offset: point(px(1.), px(-1.)), blur_radius: px(2.), spread_radius: px(0.), + inset: false, }]) .when_some(awaiting_permission, |this, element| this.child(element)) .when( diff --git a/crates/ai_onboarding/src/agent_api_keys_onboarding.rs b/crates/ai_onboarding/src/agent_api_keys_onboarding.rs index 47197ec2331b97..56b4e7ffaa686b 100644 --- a/crates/ai_onboarding/src/agent_api_keys_onboarding.rs +++ b/crates/ai_onboarding/src/agent_api_keys_onboarding.rs @@ -73,6 +73,7 @@ impl Render for ApiKeysWithProviders { offset: point(px(1.), px(-1.)), blur_radius: px(3.), spread_radius: px(0.), + inset: false, }]) .child( h_flex() diff --git a/crates/editor/src/edit_prediction.rs b/crates/editor/src/edit_prediction.rs index e7c942bd000c9e..bdaef3430eda18 100644 --- a/crates/editor/src/edit_prediction.rs +++ b/crates/editor/src/edit_prediction.rs @@ -1980,6 +1980,7 @@ impl Editor { offset: point(px(1.), px(1.)), blur_radius: px(2.), spread_radius: px(0.), + inset: false, }]) .bg(Editor::edit_prediction_line_popover_bg_color(cx)) .border(BORDER_WIDTH) diff --git a/crates/gpui/examples/opacity.rs b/crates/gpui/examples/opacity.rs index ba61d0f5daca2e..1d74127f4cd355 100644 --- a/crates/gpui/examples/opacity.rs +++ b/crates/gpui/examples/opacity.rs @@ -119,6 +119,7 @@ impl Render for HelloWorld { blur_radius: px(1.0), spread_radius: px(5.0), offset: point(px(10.0), px(10.0)), + inset: false, }]) .child(img("image/app-icon.png").size_8()) .child("Opacity Panel (Click to test)") diff --git a/crates/gpui/examples/shadow.rs b/crates/gpui/examples/shadow.rs index d39a2eb62ed74b..5cb54fe9a66485 100644 --- a/crates/gpui/examples/shadow.rs +++ b/crates/gpui/examples/shadow.rs @@ -109,6 +109,7 @@ impl Render for Shadow { offset: point(px(0.), px(8.)), blur_radius: px(8.), spread_radius: px(0.), + inset: false, }]), ), example( @@ -119,6 +120,7 @@ impl Render for Shadow { offset: point(px(0.), px(8.)), blur_radius: px(8.), spread_radius: px(0.), + inset: false, }]), ), example( @@ -129,6 +131,7 @@ impl Render for Shadow { offset: point(px(0.), px(8.)), blur_radius: px(8.), spread_radius: px(0.), + inset: false, }]), ), example( @@ -139,6 +142,7 @@ impl Render for Shadow { offset: point(px(0.), px(8.)), blur_radius: px(8.), spread_radius: px(0.), + inset: false, }]), ), example( @@ -149,6 +153,7 @@ impl Render for Shadow { offset: point(px(0.), px(8.)), blur_radius: px(8.), spread_radius: px(0.), + inset: false, }]), ), ]), @@ -185,6 +190,7 @@ impl Render for Shadow { offset: point(px(0.), px(8.)), blur_radius: px(0.), spread_radius: px(0.), + inset: false, }]), ), example( @@ -194,6 +200,7 @@ impl Render for Shadow { offset: point(px(0.), px(8.)), blur_radius: px(2.), spread_radius: px(0.), + inset: false, }]), ), example( @@ -203,6 +210,7 @@ impl Render for Shadow { offset: point(px(0.), px(8.)), blur_radius: px(4.), spread_radius: px(0.), + inset: false, }]), ), example( @@ -212,6 +220,7 @@ impl Render for Shadow { offset: point(px(0.), px(8.)), blur_radius: px(8.), spread_radius: px(0.), + inset: false, }]), ), example( @@ -221,6 +230,7 @@ impl Render for Shadow { offset: point(px(0.), px(8.)), blur_radius: px(16.), spread_radius: px(0.), + inset: false, }]), ), ]), @@ -237,6 +247,7 @@ impl Render for Shadow { offset: point(px(0.), px(8.)), blur_radius: px(8.), spread_radius: px(0.), + inset: false, }]), ), example( @@ -246,6 +257,7 @@ impl Render for Shadow { offset: point(px(0.), px(8.)), blur_radius: px(8.), spread_radius: px(2.), + inset: false, }]), ), example( @@ -255,6 +267,7 @@ impl Render for Shadow { offset: point(px(0.), px(8.)), blur_radius: px(8.), spread_radius: px(4.), + inset: false, }]), ), example( @@ -264,6 +277,7 @@ impl Render for Shadow { offset: point(px(0.), px(8.)), blur_radius: px(8.), spread_radius: px(8.), + inset: false, }]), ), example( @@ -273,6 +287,7 @@ impl Render for Shadow { offset: point(px(0.), px(8.)), blur_radius: px(8.), spread_radius: px(16.), + inset: false, }]), ), ]), @@ -289,6 +304,7 @@ impl Render for Shadow { offset: point(px(0.), px(8.)), blur_radius: px(8.), spread_radius: px(0.), + inset: false, }]), ), example( @@ -298,6 +314,7 @@ impl Render for Shadow { offset: point(px(0.), px(8.)), blur_radius: px(8.), spread_radius: px(8.), + inset: false, }]), ), example( @@ -307,6 +324,7 @@ impl Render for Shadow { offset: point(px(0.), px(8.)), blur_radius: px(8.), spread_radius: px(16.), + inset: false, }]), ), ]), @@ -323,6 +341,7 @@ impl Render for Shadow { offset: point(px(0.), px(8.)), blur_radius: px(8.), spread_radius: px(0.), + inset: false, }]), ), example( @@ -332,6 +351,7 @@ impl Render for Shadow { offset: point(px(0.), px(8.)), blur_radius: px(8.), spread_radius: px(8.), + inset: false, }]), ), example( @@ -341,6 +361,7 @@ impl Render for Shadow { offset: point(px(0.), px(8.)), blur_radius: px(8.), spread_radius: px(16.), + inset: false, }]), ), ]), @@ -357,6 +378,7 @@ impl Render for Shadow { offset: point(px(-8.), px(0.)), blur_radius: px(8.), spread_radius: px(0.), + inset: false, }]), ), example( @@ -366,6 +388,7 @@ impl Render for Shadow { offset: point(px(8.), px(0.)), blur_radius: px(8.), spread_radius: px(0.), + inset: false, }]), ), example( @@ -375,6 +398,7 @@ impl Render for Shadow { offset: point(px(0.), px(-8.)), blur_radius: px(8.), spread_radius: px(0.), + inset: false, }]), ), example( @@ -384,6 +408,7 @@ impl Render for Shadow { offset: point(px(0.), px(8.)), blur_radius: px(8.), spread_radius: px(0.), + inset: false, }]), ), ]), @@ -400,6 +425,7 @@ impl Render for Shadow { offset: point(px(-8.), px(0.)), blur_radius: px(8.), spread_radius: px(0.), + inset: false, }]), ), example( @@ -409,6 +435,7 @@ impl Render for Shadow { offset: point(px(8.), px(0.)), blur_radius: px(8.), spread_radius: px(0.), + inset: false, }]), ), example( @@ -418,6 +445,7 @@ impl Render for Shadow { offset: point(px(0.), px(-8.)), blur_radius: px(8.), spread_radius: px(0.), + inset: false, }]), ), example( @@ -427,6 +455,7 @@ impl Render for Shadow { offset: point(px(0.), px(8.)), blur_radius: px(8.), spread_radius: px(0.), + inset: false, }]), ), ]), @@ -443,6 +472,7 @@ impl Render for Shadow { offset: point(px(-8.), px(0.)), blur_radius: px(8.), spread_radius: px(0.), + inset: false, }]), ), example( @@ -452,6 +482,7 @@ impl Render for Shadow { offset: point(px(8.), px(0.)), blur_radius: px(8.), spread_radius: px(0.), + inset: false, }]), ), example( @@ -461,6 +492,7 @@ impl Render for Shadow { offset: point(px(0.), px(-8.)), blur_radius: px(8.), spread_radius: px(0.), + inset: false, }]), ), example( @@ -470,6 +502,7 @@ impl Render for Shadow { offset: point(px(0.), px(8.)), blur_radius: px(8.), spread_radius: px(0.), + inset: false, }]), ), ]), @@ -487,24 +520,28 @@ impl Render for Shadow { offset: point(px(0.), px(-12.)), blur_radius: px(8.), spread_radius: px(2.), + inset: false, }, BoxShadow { color: hsla(60.0 / 360., 1.0, 0.5, 0.3), // Yellow offset: point(px(12.), px(0.)), blur_radius: px(8.), spread_radius: px(2.), + inset: false, }, BoxShadow { color: hsla(120.0 / 360., 1.0, 0.5, 0.3), // Green offset: point(px(0.), px(12.)), blur_radius: px(8.), spread_radius: px(2.), + inset: false, }, BoxShadow { color: hsla(240.0 / 360., 1.0, 0.5, 0.3), // Blue offset: point(px(-12.), px(0.)), blur_radius: px(8.), spread_radius: px(2.), + inset: false, }, ]), ), @@ -516,24 +553,28 @@ impl Render for Shadow { offset: point(px(0.), px(-12.)), blur_radius: px(8.), spread_radius: px(2.), + inset: false, }, BoxShadow { color: hsla(60.0 / 360., 1.0, 0.5, 0.3), // Yellow offset: point(px(12.), px(0.)), blur_radius: px(8.), spread_radius: px(2.), + inset: false, }, BoxShadow { color: hsla(120.0 / 360., 1.0, 0.5, 0.3), // Green offset: point(px(0.), px(12.)), blur_radius: px(8.), spread_radius: px(2.), + inset: false, }, BoxShadow { color: hsla(240.0 / 360., 1.0, 0.5, 0.3), // Blue offset: point(px(-12.), px(0.)), blur_radius: px(8.), spread_radius: px(2.), + inset: false, }, ]), ), @@ -545,24 +586,113 @@ impl Render for Shadow { offset: point(px(0.), px(-12.)), blur_radius: px(8.), spread_radius: px(2.), + inset: false, }, BoxShadow { color: hsla(60.0 / 360., 1.0, 0.5, 0.3), // Yellow offset: point(px(12.), px(0.)), blur_radius: px(8.), spread_radius: px(2.), + inset: false, }, BoxShadow { color: hsla(120.0 / 360., 1.0, 0.5, 0.3), // Green offset: point(px(0.), px(12.)), blur_radius: px(8.), spread_radius: px(2.), + inset: false, }, BoxShadow { color: hsla(240.0 / 360., 1.0, 0.5, 0.3), // Blue offset: point(px(-12.), px(0.)), blur_radius: px(8.), spread_radius: px(2.), + inset: false, + }, + ]), + ), + ]), + // Inset shadows (CSS `box-shadow: inset ...`). + div() + .border_b_1() + .border_color(hsla(0.0, 0.0, 0.0, 1.0)) + .flex() + .w_full() + .children(vec![ + example( + "Inset basic", + Shadow::base().shadow(vec![BoxShadow { + color: hsla(0.0, 0.0, 0.0, 0.5), + offset: point(px(0.), px(0.)), + blur_radius: px(12.), + spread_radius: px(0.), + inset: true, + }]), + ), + example( + "Inset offset", + Shadow::base().shadow(vec![BoxShadow { + color: hsla(0.0, 0.0, 0.0, 0.5), + offset: point(px(6.), px(6.)), + blur_radius: px(8.), + spread_radius: px(0.), + inset: true, + }]), + ), + example( + "Inset spread", + Shadow::base().shadow(vec![BoxShadow { + color: hsla(0.0, 0.0, 0.0, 0.5), + offset: point(px(0.), px(0.)), + blur_radius: px(4.), + spread_radius: px(8.), + inset: true, + }]), + ), + example( + "Inset rounded", + Shadow::rounded_large().shadow(vec![BoxShadow { + color: hsla(0.0, 0.0, 0.0, 0.5), + offset: point(px(0.), px(4.)), + blur_radius: px(10.), + spread_radius: px(2.), + inset: true, + }]), + ), + example( + "Inset sharp", + Shadow::square().shadow(vec![BoxShadow { + color: hsla(0.0, 0.0, 0.0, 0.6), + offset: point(px(0.), px(0.)), + blur_radius: px(0.), + spread_radius: px(6.), + inset: true, + }]), + ), + ]), + // Combined: drop + inset shadows on the same element. + div() + .border_b_1() + .border_color(hsla(0.0, 0.0, 0.0, 1.0)) + .flex() + .w_full() + .children(vec![ + example( + "Drop + Inset", + Shadow::rounded_medium().shadow(vec![ + BoxShadow { + color: hsla(0.0, 0.0, 0.0, 0.25), + offset: point(px(0.), px(8.)), + blur_radius: px(12.), + spread_radius: px(0.), + inset: false, + }, + BoxShadow { + color: hsla(0.0, 0.0, 0.0, 0.4), + offset: point(px(0.), px(2.)), + blur_radius: px(4.), + spread_radius: px(0.), + inset: true, }, ]), ), diff --git a/crates/gpui/examples/window_shadow.rs b/crates/gpui/examples/window_shadow.rs index b8c052693da9f3..3f06098c1981c6 100644 --- a/crates/gpui/examples/window_shadow.rs +++ b/crates/gpui/examples/window_shadow.rs @@ -116,6 +116,7 @@ impl Render for WindowShadow { }, blur_radius: shadow_size / 2., spread_radius: px(0.), + inset: false, offset: point(px(0.0), px(0.0)), }]) }), @@ -156,6 +157,7 @@ impl Render for WindowShadow { }, blur_radius: px(20.0), spread_radius: px(0.0), + inset: false, offset: point(px(0.0), px(0.0)), }]) .map(|div| match decorations { diff --git a/crates/gpui/src/scene.rs b/crates/gpui/src/scene.rs index ef37caa7b95cfb..bc7f5d79eace55 100644 --- a/crates/gpui/src/scene.rs +++ b/crates/gpui/src/scene.rs @@ -530,6 +530,11 @@ pub struct Shadow { pub corner_radii: Corners, pub content_mask: ContentMask, pub color: Hsla, + pub element_bounds: Bounds, + pub element_corner_radii: Corners, + /// 0 = drop shadow (rendered outside the element), 1 = inset shadow (rendered inside). + pub inset: u32, + pub pad: u32, // align to 8 bytes } impl From for Primitive { diff --git a/crates/gpui/src/style.rs b/crates/gpui/src/style.rs index 05bc083d1d7ad7..54e09bc37dd994 100644 --- a/crates/gpui/src/style.rs +++ b/crates/gpui/src/style.rs @@ -351,6 +351,8 @@ pub struct BoxShadow { pub blur_radius: Pixels, /// How much should the shadow spread? pub spread_radius: Pixels, + /// Whether this is an inset shadow (drawn inside the element's bounds). + pub inset: bool, } /// How to handle whitespace in text @@ -665,7 +667,7 @@ impl Style { .to_pixels(rem_size) .clamp_radii_for_quad_size(bounds.size); - window.paint_shadows(bounds, corner_radii, &self.box_shadow); + window.paint_drop_shadows(bounds, corner_radii, &self.box_shadow); let background_color = self.background.as_ref().and_then(Fill::color); if background_color.is_some_and(|color| !color.is_transparent()) { @@ -694,6 +696,8 @@ impl Style { )); } + window.paint_inset_shadows(bounds, corner_radii, &self.box_shadow); + continuation(window, cx); if self.is_border_visible() { diff --git a/crates/gpui/src/window.rs b/crates/gpui/src/window.rs index 1590fe1795fe29..310734dcc6b64d 100644 --- a/crates/gpui/src/window.rs +++ b/crates/gpui/src/window.rs @@ -3449,10 +3449,12 @@ impl Window { result } - /// Paint one or more drop shadows into the scene for the next frame at the current z-index. + /// Paint the drop (non-inset) shadows from `shadows` into the scene at the current + /// z-index. Inset shadows are skipped; paint those with [`Self::paint_inset_shadows`] + /// after the element's background so they layer on top of the fill. /// /// This method should only be called as part of the paint phase of element drawing. - pub fn paint_shadows( + pub(crate) fn paint_drop_shadows( &mut self, bounds: Bounds, corner_radii: Corners, @@ -3463,7 +3465,12 @@ impl Window { let scale_factor = self.scale_factor(); let content_mask = self.snapped_content_mask(); let opacity = self.element_opacity(); + let element_bounds = self.cover_bounds(bounds); + let element_corner_radii = corner_radii.scale(scale_factor); for shadow in shadows { + if shadow.inset { + continue; + } let shadow_bounds = (bounds + shadow.offset).dilate(shadow.spread_radius); self.next_frame.scene.insert_primitive(Shadow { order: 0, @@ -3472,6 +3479,55 @@ impl Window { content_mask, corner_radii: corner_radii.scale(scale_factor), color: shadow.color.opacity(opacity), + element_bounds, + element_corner_radii, + inset: 0, + pad: 0, + }); + } + } + + /// Paint the inset shadows from `shadows` into the scene at the current z-index. Should + /// be called after the element's background so the shadow layers on top of the fill. + /// Drop shadows are skipped; paint those with [`Self::paint_drop_shadows`] before the background. + pub(crate) fn paint_inset_shadows( + &mut self, + bounds: Bounds, + corner_radii: Corners, + shadows: &[BoxShadow], + ) { + self.invalidator.debug_assert_paint(); + + let scale_factor = self.scale_factor(); + let content_mask = self.snapped_content_mask(); + let opacity = self.element_opacity(); + let element_bounds = self.cover_bounds(bounds); + let element_corner_radii = corner_radii.scale(scale_factor); + for shadow in shadows { + if !shadow.inset { + continue; + } + let hole = (bounds + shadow.offset).dilate(-shadow.spread_radius); + // Clamp at zero so a large spread can't produce negative radii, which would + // break the SDF in the shader. + let zero = Pixels::ZERO; + let hole_corner_radii = Corners { + top_left: (corner_radii.top_left - shadow.spread_radius).max(zero), + top_right: (corner_radii.top_right - shadow.spread_radius).max(zero), + bottom_right: (corner_radii.bottom_right - shadow.spread_radius).max(zero), + bottom_left: (corner_radii.bottom_left - shadow.spread_radius).max(zero), + }; + self.next_frame.scene.insert_primitive(Shadow { + order: 0, + blur_radius: shadow.blur_radius.scale(scale_factor), + bounds: self.cover_bounds(hole), + content_mask, + corner_radii: hole_corner_radii.scale(scale_factor), + color: shadow.color.opacity(opacity), + element_bounds, + element_corner_radii, + inset: 1, + pad: 0, }); } } diff --git a/crates/gpui_macos/src/shaders.metal b/crates/gpui_macos/src/shaders.metal index 4dc2d334e1e092..2b52bb9ecc0476 100644 --- a/crates/gpui_macos/src/shaders.metal +++ b/crates/gpui_macos/src/shaders.metal @@ -468,14 +468,18 @@ vertex ShadowVertexOutput shadow_vertex( float2 unit_vertex = unit_vertices[unit_vertex_id]; Shadow shadow = shadows[shadow_id]; - float margin = 3. * shadow.blur_radius; - // Set the bounds of the shadow and adjust its size based on the shadow's - // spread radius to achieve the spreading effect - Bounds_ScaledPixels bounds = shadow.bounds; - bounds.origin.x -= margin; - bounds.origin.y -= margin; - bounds.size.width += 2. * margin; - bounds.size.height += 2. * margin; + Bounds_ScaledPixels bounds; + if (shadow.inset != 0u) { + bounds = shadow.element_bounds; + } else { + // Leave room for the gaussian tail outside the shadow rect. + float margin = 3. * shadow.blur_radius; + bounds = shadow.bounds; + bounds.origin.x -= margin; + bounds.origin.y -= margin; + bounds.size.width += 2. * margin; + bounds.size.height += 2. * margin; + } float4 device_position = to_device_position(unit_vertex, bounds, viewport_size); @@ -538,6 +542,15 @@ fragment float4 shadow_fragment(ShadowFragmentInput input [[stage_in]], } } + if (shadow.inset != 0u) { + // The inset shadow is the complement of the (blurred) hole rect, clipped to the element. + // `saturate(0.5 - d)` gives a 1-pixel antialiased edge: d <= -0.5 -> 1, d >= 0.5 -> 0. + alpha = 1. - alpha; + float element_distance = quad_sdf(input.position.xy, shadow.element_bounds, + shadow.element_corner_radii); + alpha *= saturate(0.5 - element_distance); + } + return input.color * float4(1., 1., 1., alpha); } @@ -1251,14 +1264,14 @@ float4 fill_color(Background background, // checkerboard float size = background.gradient_angle_or_pattern_height; float2 relative_position = position - float2(bounds.origin.x, bounds.origin.y); - + float x_index = floor(relative_position.x / size); float y_index = floor(relative_position.y / size); float should_be_colored = fmod(x_index + y_index, 2.0); - + color = solid_color; color.a *= saturate(should_be_colored); - break; + break; } } diff --git a/crates/gpui_macros/src/styles.rs b/crates/gpui_macros/src/styles.rs index fdbc64f623a474..6a0095a6c798be 100644 --- a/crates/gpui_macros/src/styles.rs +++ b/crates/gpui_macros/src/styles.rs @@ -410,6 +410,7 @@ pub fn box_shadow_style_methods(input: TokenStream) -> TokenStream { offset: point(px(0.), px(1.)), blur_radius: px(0.), spread_radius: px(0.), + inset: false, }]); self } @@ -425,6 +426,7 @@ pub fn box_shadow_style_methods(input: TokenStream) -> TokenStream { offset: point(px(0.), px(1.)), blur_radius: px(2.), spread_radius: px(0.), + inset: false, }]); self } @@ -441,12 +443,14 @@ pub fn box_shadow_style_methods(input: TokenStream) -> TokenStream { offset: point(px(0.), px(1.)), blur_radius: px(3.), spread_radius: px(0.), + inset: false, }, BoxShadow { color: hsla(0., 0., 0., 0.1), offset: point(px(0.), px(1.)), blur_radius: px(2.), spread_radius: px(-1.), + inset: false, } ]); self @@ -464,12 +468,14 @@ pub fn box_shadow_style_methods(input: TokenStream) -> TokenStream { offset: point(px(0.), px(4.)), blur_radius: px(6.), spread_radius: px(-1.), + inset: false, }, BoxShadow { color: hsla(0., 0., 0., 0.1), offset: point(px(0.), px(2.)), blur_radius: px(4.), spread_radius: px(-2.), + inset: false, } ]); self @@ -487,12 +493,14 @@ pub fn box_shadow_style_methods(input: TokenStream) -> TokenStream { offset: point(px(0.), px(10.)), blur_radius: px(15.), spread_radius: px(-3.), + inset: false, }, BoxShadow { color: hsla(0., 0., 0., 0.1), offset: point(px(0.), px(4.)), blur_radius: px(6.), spread_radius: px(-4.), + inset: false, } ]); self @@ -510,12 +518,14 @@ pub fn box_shadow_style_methods(input: TokenStream) -> TokenStream { offset: point(px(0.), px(20.)), blur_radius: px(25.), spread_radius: px(-5.), + inset: false, }, BoxShadow { color: hsla(0., 0., 0., 0.1), offset: point(px(0.), px(8.)), blur_radius: px(10.), spread_radius: px(-6.), + inset: false, } ]); self @@ -532,6 +542,7 @@ pub fn box_shadow_style_methods(input: TokenStream) -> TokenStream { offset: point(px(0.), px(25.)), blur_radius: px(50.), spread_radius: px(-12.), + inset: false, }]); self } diff --git a/crates/gpui_wgpu/src/shaders.wgsl b/crates/gpui_wgpu/src/shaders.wgsl index b700697f47b932..933b88e84d79a9 100644 --- a/crates/gpui_wgpu/src/shaders.wgsl +++ b/crates/gpui_wgpu/src/shaders.wgsl @@ -951,10 +951,18 @@ fn fmod(a: f32, b: f32) -> f32 { struct Shadow { order: u32, blur_radius: f32, + // The shadow rect for drop shadows; the "hole" rect for inset shadows. bounds: Bounds, corner_radii: Corners, content_mask: Bounds, color: Hsla, + // Only consulted when `inset == 1u`: the element's own bounds, used as a rounded-rect + // clip so the shadow never escapes the element. + element_bounds: Bounds, + element_corner_radii: Corners, + // 0 = drop shadow, 1 = inset shadow. + inset: u32, + pad: u32, // align to 8 bytes } @group(1) @binding(0) var b_shadows: array; @@ -971,17 +979,22 @@ fn vs_shadow(@builtin(vertex_index) vertex_id: u32, @builtin(instance_index) ins let unit_vertex = vec2(f32(vertex_id & 1u), 0.5 * f32(vertex_id & 2u)); var shadow = b_shadows[instance_id]; - let margin = 3.0 * shadow.blur_radius; - // Set the bounds of the shadow and adjust its size based on the shadow's - // spread radius to achieve the spreading effect - shadow.bounds.origin -= vec2(margin); - shadow.bounds.size += 2.0 * vec2(margin); + var geometry: Bounds; + if (shadow.inset != 0u) { + geometry = shadow.element_bounds; + } else { + // Leave room for the gaussian tail outside the shadow rect. + let margin = 3.0 * shadow.blur_radius; + geometry = shadow.bounds; + geometry.origin -= vec2(margin); + geometry.size += 2.0 * vec2(margin); + } var out = ShadowVarying(); - out.position = to_device_position(unit_vertex, shadow.bounds); + out.position = to_device_position(unit_vertex, geometry); out.color = hsla_to_rgba(shadow.color); out.shadow_id = instance_id; - out.clip_distances = distance_from_clip_rect(unit_vertex, shadow.bounds, shadow.content_mask); + out.clip_distances = distance_from_clip_rect(unit_vertex, geometry, shadow.content_mask); return out; } @@ -999,21 +1012,36 @@ fn fs_shadow(input: ShadowVarying) -> @location(0) vec4 { let corner_radius = pick_corner_radius(center_to_point, shadow.corner_radii); - // The signal is only non-zero in a limited range, so don't waste samples - let low = center_to_point.y - half_size.y; - let high = center_to_point.y + half_size.y; - let start = clamp(-3.0 * shadow.blur_radius, low, high); - let end = clamp(3.0 * shadow.blur_radius, low, high); - - // Accumulate samples (we can get away with surprisingly few samples) - let step = (end - start) / 4.0; - var y = start + step * 0.5; - var alpha = 0.0; - for (var i = 0; i < 4; i += 1) { - let blur = blur_along_x(center_to_point.x, center_to_point.y - y, - shadow.blur_radius, corner_radius, half_size); - alpha += blur * gaussian(y, shadow.blur_radius) * step; - y += step; + var alpha: f32; + if (shadow.blur_radius == 0.0) { + let distance = quad_sdf(input.position.xy, shadow.bounds, shadow.corner_radii); + alpha = saturate(0.5 - distance); + } else { + // The signal is only non-zero in a limited range, so don't waste samples + let low = center_to_point.y - half_size.y; + let high = center_to_point.y + half_size.y; + let start = clamp(-3.0 * shadow.blur_radius, low, high); + let end = clamp(3.0 * shadow.blur_radius, low, high); + + // Accumulate samples (we can get away with surprisingly few samples) + let step = (end - start) / 4.0; + var y = start + step * 0.5; + alpha = 0.0; + for (var i = 0; i < 4; i += 1) { + let blur = blur_along_x(center_to_point.x, center_to_point.y - y, + shadow.blur_radius, corner_radius, half_size); + alpha += blur * gaussian(y, shadow.blur_radius) * step; + y += step; + } + } + + if (shadow.inset != 0u) { + // The inset shadow is the complement of the (blurred) hole rect, clipped to the element. + // `saturate(0.5 - d)` gives a 1-pixel antialiased edge: d <= -0.5 -> 1, d >= 0.5 -> 0. + alpha = 1.0 - alpha; + let element_distance = quad_sdf(input.position.xy, shadow.element_bounds, + shadow.element_corner_radii); + alpha *= saturate(0.5 - element_distance); } return blend_color(input.color, alpha); diff --git a/crates/gpui_windows/src/shaders.hlsl b/crates/gpui_windows/src/shaders.hlsl index d40c7241bd0f23..89c12489b24cf6 100644 --- a/crates/gpui_windows/src/shaders.hlsl +++ b/crates/gpui_windows/src/shaders.hlsl @@ -854,6 +854,10 @@ struct Shadow { Corners corner_radii; Bounds content_mask; Hsla color; + Bounds element_bounds; + Corners element_corner_radii; + uint inset; + uint pad; // align to 8 bytes }; struct ShadowVertexOutput { @@ -875,10 +879,16 @@ ShadowVertexOutput shadow_vertex(uint vertex_id: SV_VertexID, uint shadow_id: SV float2 unit_vertex = float2(float(vertex_id & 1u), 0.5 * float(vertex_id & 2u)); Shadow shadow = shadows[shadow_id]; - float margin = 3.0 * shadow.blur_radius; - Bounds bounds = shadow.bounds; - bounds.origin -= margin; - bounds.size += 2.0 * margin; + Bounds bounds; + if (shadow.inset != 0u) { + bounds = shadow.element_bounds; + } else { + // Leave room for the gaussian tail outside the shadow rect. + float margin = 3.0 * shadow.blur_radius; + bounds = shadow.bounds; + bounds.origin -= margin; + bounds.size += 2.0 * margin; + } float4 device_position = to_device_position(unit_vertex, bounds); float4 clip_distance = distance_from_clip_rect(unit_vertex, bounds, shadow.content_mask); @@ -901,21 +911,36 @@ float4 shadow_fragment(ShadowFragmentInput input): SV_TARGET { float2 point0 = input.position.xy - center; float corner_radius = pick_corner_radius(point0, shadow.corner_radii); - // The signal is only non-zero in a limited range, so don't waste samples - float low = point0.y - half_size.y; - float high = point0.y + half_size.y; - float start = clamp(-3. * shadow.blur_radius, low, high); - float end = clamp(3. * shadow.blur_radius, low, high); - - // Accumulate samples (we can get away with surprisingly few samples) - float step = (end - start) / 4.; - float y = start + step * 0.5; - float alpha = 0.; - for (int i = 0; i < 4; i++) { - alpha += blur_along_x(point0.x, point0.y - y, shadow.blur_radius, - corner_radius, half_size) * - gaussian(y, shadow.blur_radius) * step; - y += step; + float alpha; + if (shadow.blur_radius == 0.) { + float distance = quad_sdf(input.position.xy, shadow.bounds, shadow.corner_radii); + alpha = saturate(0.5 - distance); + } else { + // The signal is only non-zero in a limited range, so don't waste samples + float low = point0.y - half_size.y; + float high = point0.y + half_size.y; + float start = clamp(-3. * shadow.blur_radius, low, high); + float end = clamp(3. * shadow.blur_radius, low, high); + + // Accumulate samples (we can get away with surprisingly few samples) + float step = (end - start) / 4.; + float y = start + step * 0.5; + alpha = 0.; + for (int i = 0; i < 4; i++) { + alpha += blur_along_x(point0.x, point0.y - y, shadow.blur_radius, + corner_radius, half_size) * + gaussian(y, shadow.blur_radius) * step; + y += step; + } + } + + if (shadow.inset != 0u) { + // The inset shadow is the complement of the (blurred) hole rect, clipped to the element. + // `saturate(0.5 - d)` gives a 1-pixel antialiased edge: d <= -0.5 -> 1, d >= 0.5 -> 0. + alpha = 1.0 - alpha; + float element_distance = quad_sdf(input.position.xy, shadow.element_bounds, + shadow.element_corner_radii); + alpha *= saturate(0.5 - element_distance); } return input.color * float4(1., 1., 1., alpha); diff --git a/crates/ui/src/components/button/split_button.rs b/crates/ui/src/components/button/split_button.rs index e6821e949009fe..7871ea3e98ce36 100644 --- a/crates/ui/src/components/button/split_button.rs +++ b/crates/ui/src/components/button/split_button.rs @@ -94,6 +94,7 @@ impl RenderOnce for SplitButton { offset: point(px(0.), px(1.)), blur_radius: px(0.), spread_radius: px(0.), + inset: false, }]) }) } diff --git a/crates/ui/src/components/keybinding_hint.rs b/crates/ui/src/components/keybinding_hint.rs index 9da470c4ee4173..2d6f1f72f9cf13 100644 --- a/crates/ui/src/components/keybinding_hint.rs +++ b/crates/ui/src/components/keybinding_hint.rs @@ -247,6 +247,7 @@ impl RenderOnce for KeybindingHint { offset: point(px(0.), px(1.)), blur_radius: px(0.), spread_radius: px(0.), + inset: false, }]) .child(self.keybinding.size(rems_from_px(kb_size))), ) diff --git a/crates/ui/src/components/progress/progress_bar.rs b/crates/ui/src/components/progress/progress_bar.rs index b46b0523681e84..31e9e74289466a 100644 --- a/crates/ui/src/components/progress/progress_bar.rs +++ b/crates/ui/src/components/progress/progress_bar.rs @@ -76,6 +76,7 @@ impl RenderOnce for ProgressBar { offset: point(px(0.), px(1.)), blur_radius: px(0.), spread_radius: px(0.), + inset: false, }]) .child( div() diff --git a/crates/ui/src/styles/elevation.rs b/crates/ui/src/styles/elevation.rs index e6df718b8f7642..2f0bc2618e42a3 100644 --- a/crates/ui/src/styles/elevation.rs +++ b/crates/ui/src/styles/elevation.rs @@ -52,12 +52,14 @@ impl ElevationIndex { offset: point(px(0.), px(2.)), blur_radius: px(3.), spread_radius: px(0.), + inset: false, }, BoxShadow { color: hsla(0., 0., 0., if is_light { 0.03 } else { 0.06 }), offset: point(px(0.), px(1.)), blur_radius: px(0.), spread_radius: px(0.), + inset: false, }, ], @@ -67,24 +69,28 @@ impl ElevationIndex { offset: point(px(0.), px(2.)), blur_radius: px(3.), spread_radius: px(0.), + inset: false, }, BoxShadow { color: hsla(0., 0., 0., if is_light { 0.06 } else { 0.08 }), offset: point(px(0.), px(3.)), blur_radius: px(6.), spread_radius: px(0.), + inset: false, }, BoxShadow { color: hsla(0., 0., 0., 0.04), offset: point(px(0.), px(6.)), blur_radius: px(12.), spread_radius: px(0.), + inset: false, }, BoxShadow { color: hsla(0., 0., 0., if is_light { 0.04 } else { 0.12 }), offset: point(px(0.), px(1.)), blur_radius: px(0.), spread_radius: px(0.), + inset: false, }, ], diff --git a/crates/workspace/src/workspace.rs b/crates/workspace/src/workspace.rs index 3cd02c1599bc9b..dc446a4e6d113c 100644 --- a/crates/workspace/src/workspace.rs +++ b/crates/workspace/src/workspace.rs @@ -10637,6 +10637,7 @@ pub fn client_side_decorations( }, blur_radius: theme::CLIENT_SIDE_DECORATION_SHADOW / 2., spread_radius: px(0.), + inset: false, offset: point(px(0.0), px(0.0)), }]) }),