From d7b27f9ead8457d5989a2bc6507a5bc0c026c7ee Mon Sep 17 00:00:00 2001
From: Danilo Leal <67129314+danilo-leal@users.noreply.github.com>
Date: Tue, 26 May 2026 11:32:45 -0300
Subject: [PATCH] gpui: Add support for inset shadow (#57685)
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
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.
Run the example above with `cargo run --example shadow -p gpui`.
Release Notes:
- N/A
---------
Co-authored-by: Agus Zubiaga
---
.../src/conversation_view/thread_view.rs | 1 +
.../src/agent_api_keys_onboarding.rs | 1 +
crates/editor/src/edit_prediction.rs | 1 +
crates/gpui/examples/opacity.rs | 1 +
crates/gpui/examples/shadow.rs | 130 ++++++++++++++++++
crates/gpui/examples/window_shadow.rs | 2 +
crates/gpui/src/scene.rs | 5 +
crates/gpui/src/style.rs | 6 +-
crates/gpui/src/window.rs | 60 +++++++-
crates/gpui_macos/src/shaders.metal | 35 +++--
crates/gpui_macros/src/styles.rs | 11 ++
crates/gpui_wgpu/src/shaders.wgsl | 70 +++++++---
crates/gpui_windows/src/shaders.hlsl | 61 +++++---
.../ui/src/components/button/split_button.rs | 1 +
crates/ui/src/components/keybinding_hint.rs | 1 +
.../src/components/progress/progress_bar.rs | 1 +
crates/ui/src/styles/elevation.rs | 6 +
crates/workspace/src/workspace.rs | 1 +
18 files changed, 341 insertions(+), 53 deletions(-)
diff --git a/crates/agent_ui/src/conversation_view/thread_view.rs b/crates/agent_ui/src/conversation_view/thread_view.rs
index d7df0feba8..f310a29b6d 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 47197ec233..56b4e7ffaa 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 e7c942bd00..bdaef3430e 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 ba61d0f5da..1d74127f4c 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 d39a2eb62e..5cb54fe9a6 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 b8c052693d..3f06098c19 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 ef37caa7b9..bc7f5d79ea 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 05bc083d1d..54e09bc37d 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 1590fe1795..310734dcc6 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 4dc2d334e1..2b52bb9ecc 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 fdbc64f623..6a0095a6c7 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 b700697f47..933b88e84d 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);
+ 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);
diff --git a/crates/gpui_windows/src/shaders.hlsl b/crates/gpui_windows/src/shaders.hlsl
index d40c7241bd..89c12489b2 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);
+ 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);
diff --git a/crates/ui/src/components/button/split_button.rs b/crates/ui/src/components/button/split_button.rs
index e6821e9490..7871ea3e98 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 9da470c4ee..2d6f1f72f9 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 b46b052368..31e9e74289 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 e6df718b8f..2f0bc2618e 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 7d1e3384dc..dbb18703c5 100644
--- a/crates/workspace/src/workspace.rs
+++ b/crates/workspace/src/workspace.rs
@@ -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)),
}])
}),