Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions crates/agent_ui/src/conversation_view/thread_view.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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(
Expand Down
1 change: 1 addition & 0 deletions crates/ai_onboarding/src/agent_api_keys_onboarding.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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()
Expand Down
1 change: 1 addition & 0 deletions crates/editor/src/edit_prediction.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
1 change: 1 addition & 0 deletions crates/gpui/examples/opacity.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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)")
Expand Down
130 changes: 130 additions & 0 deletions crates/gpui/examples/shadow.rs

Large diffs are not rendered by default.

2 changes: 2 additions & 0 deletions crates/gpui/examples/window_shadow.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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)),
}])
}),
Expand Down Expand Up @@ -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 {
Expand Down
5 changes: 5 additions & 0 deletions crates/gpui/src/scene.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down
6 changes: 5 additions & 1 deletion crates/gpui/src/style.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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()) {
Expand Down Expand Up @@ -694,6 +696,8 @@ impl Style {
));
}

window.paint_inset_shadows(bounds, corner_radii, &self.box_shadow);

continuation(window, cx);

if self.is_border_visible() {
Expand Down
60 changes: 58 additions & 2 deletions crates/gpui/src/window.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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>,
Expand All @@ -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,
Expand All @@ -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,
});
}
}
Expand Down
35 changes: 24 additions & 11 deletions crates/gpui_macos/src/shaders.metal
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down Expand Up @@ -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);
}

Expand Down Expand Up @@ -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;
}
}

Expand Down
11 changes: 11 additions & 0 deletions crates/gpui_macros/src/styles.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
Expand All @@ -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
}
Expand All @@ -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
Expand All @@ -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
Expand All @@ -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
Expand All @@ -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
Expand All @@ -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
}
Expand Down
Loading
Loading