gpui: Add support for inset shadow (#57685)

This PR adds support for inset shadows in the box shadow through the
`inset: true` field. It includes support for both the macOS as well WGSL
shaders. For now, there is no immediate application of it in Zed, so
nothing should change in the app.

<img width="600" alt="Screenshot 2026-05-25 at 8  46@2x"
src="https://github.com/user-attachments/assets/db564a6b-8af5-491a-a573-17c060a3647c"
/>

Run the example above with `cargo run --example shadow -p gpui`.

Release Notes:

- N/A

---------

Co-authored-by: Agus Zubiaga <agus@zed.dev>
This commit is contained in:
Danilo Leal 2026-05-26 11:32:45 -03:00 committed by GitHub
parent b5b52ada0c
commit eb944cfd7a
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
18 changed files with 341 additions and 53 deletions

View file

@ -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(

View file

@ -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()

View file

@ -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)

View file

@ -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)")

View file

@ -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,
},
]),
),

View file

@ -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 {

View file

@ -530,6 +530,11 @@ pub struct Shadow {
pub corner_radii: Corners<ScaledPixels>,
pub content_mask: ContentMask<ScaledPixels>,
pub color: Hsla,
pub element_bounds: Bounds<ScaledPixels>,
pub element_corner_radii: Corners<ScaledPixels>,
/// 0 = drop shadow (rendered outside the element), 1 = inset shadow (rendered inside).
pub inset: u32,
pub pad: u32, // align to 8 bytes
}
impl From<Shadow> for Primitive {

View file

@ -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() {

View file

@ -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<Pixels>,
corner_radii: Corners<Pixels>,
@ -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<Pixels>,
corner_radii: Corners<Pixels>,
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,
});
}
}

View file

@ -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;
}
}

View file

@ -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
}

View file

@ -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<storage, read> b_shadows: array<Shadow>;
@ -971,17 +979,22 @@ fn vs_shadow(@builtin(vertex_index) vertex_id: u32, @builtin(instance_index) ins
let unit_vertex = vec2<f32>(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<f32>(margin);
shadow.bounds.size += 2.0 * vec2<f32>(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<f32>(margin);
geometry.size += 2.0 * vec2<f32>(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<f32> {
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);
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;
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;
// 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);

View file

@ -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);
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;
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;
// 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);

View file

@ -94,6 +94,7 @@ impl RenderOnce for SplitButton {
offset: point(px(0.), px(1.)),
blur_radius: px(0.),
spread_radius: px(0.),
inset: false,
}])
})
}

View file

@ -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))),
)

View file

@ -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()

View file

@ -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,
},
],

View file

@ -10643,6 +10643,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)),
}])
}),