diff --git a/crates/editor/src/element.rs b/crates/editor/src/element.rs index 4f3580da07..c6fae7a4c5 100644 --- a/crates/editor/src/element.rs +++ b/crates/editor/src/element.rs @@ -5871,6 +5871,7 @@ impl EditorElement { window.with_content_mask( Some(ContentMask { bounds: layout.position_map.text_hitbox.bounds, + ..Default::default() }), |window| { let editor = self.editor.read(cx); @@ -6808,9 +6809,15 @@ impl EditorElement { } else { let mut bounds = layout.hitbox.bounds; bounds.origin.x += layout.gutter_hitbox.bounds.size.width; - window.with_content_mask(Some(ContentMask { bounds }), |window| { - block.element.paint(window, cx); - }) + window.with_content_mask( + Some(ContentMask { + bounds, + ..Default::default() + }), + |window| { + block.element.paint(window, cx); + }, + ) } } } @@ -8022,9 +8029,13 @@ impl Element for EditorElement { } let rem_size = self.rem_size(cx); + let content_mask = ContentMask { + bounds, + ..Default::default() + }; window.with_rem_size(rem_size, |window| { window.with_text_style(Some(text_style), |window| { - window.with_content_mask(Some(ContentMask { bounds }), |window| { + window.with_content_mask(Some(content_mask), |window| { let (mut snapshot, is_read_only) = self.editor.update(cx, |editor, cx| { (editor.snapshot(window, cx), editor.read_only(cx)) }); @@ -9124,9 +9135,13 @@ impl Element for EditorElement { ..Default::default() }; let rem_size = self.rem_size(cx); + let content_mask = ContentMask { + bounds, + ..Default::default() + }; window.with_rem_size(rem_size, |window| { window.with_text_style(Some(text_style), |window| { - window.with_content_mask(Some(ContentMask { bounds }), |window| { + window.with_content_mask(Some(content_mask), |window| { self.paint_mouse_listeners(layout, window, cx); self.paint_background(layout, window, cx); self.paint_indent_guides(layout, window, cx); diff --git a/crates/gpui/examples/content_mask.rs b/crates/gpui/examples/content_mask.rs new file mode 100644 index 0000000000..8d40cc5bba --- /dev/null +++ b/crates/gpui/examples/content_mask.rs @@ -0,0 +1,228 @@ +use gpui::{ + App, Application, Bounds, Context, Window, WindowBounds, WindowOptions, div, prelude::*, px, + rgb, size, +}; + +struct Example {} + +impl Render for Example { + fn render(&mut self, _window: &mut Window, _cx: &mut Context) -> impl IntoElement { + div() + .font_family(".SystemUIFont") + .flex() + .flex_col() + .size_full() + .p_4() + .gap_4() + .bg(rgb(0x505050)) + .justify_center() + .items_center() + .text_center() + .shadow_lg() + .text_sm() + .text_color(rgb(0xffffff)) + .child( + div() + .overflow_hidden() + .rounded(px(32.)) + .border(px(8.)) + .border_color(gpui::white()) + .text_color(gpui::white()) + .child( + div() + .bg(gpui::black()) + .py_2() + .px_7() + .border_l_2() + .border_r_2() + .border_b_3() + .border_color(gpui::red()) + .child("Let build applications with GPUI"), + ) + .child( + div() + .bg(rgb(0x222222)) + .text_sm() + .py_1() + .px_7() + .border_l_3() + .border_r_3() + .border_color(gpui::green()) + .child("The fast, productive UI framework for Rust"), + ) + .child( + div() + .bg(rgb(0x222222)) + .w_full() + .flex() + .flex_row() + .text_sm() + .text_color(rgb(0xc0c0c0)) + .child( + div() + .flex_1() + .p_2() + .border_3() + .border_dashed() + .border_color(gpui::blue()) + .child("Rust"), + ) + .child( + div() + .flex_1() + .p_2() + .border_t_3() + .border_r_3() + .border_b_3() + .border_dashed() + .border_color(gpui::blue()) + .child("GPU Rendering"), + ), + ), + ) + .child( + div() + .flex() + .flex_col() + .w(px(320.)) + .gap_1() + .overflow_hidden() + .rounded(px(16.)) + .child( + div() + .w_full() + .p_2() + .bg(gpui::red()) + .child("Clip background"), + ), + ) + .child( + div() + .flex() + .flex_col() + .w(px(320.)) + .gap_1() + .rounded(px(16.)) + .child( + div() + .w_full() + .p_2() + .bg(gpui::yellow()) + .text_color(gpui::black()) + .child("No content mask"), + ), + ) + .child( + div() + .flex() + .flex_col() + .w(px(320.)) + .gap_1() + .overflow_hidden() + .rounded(px(16.)) + .child( + div() + .w_full() + .p_2() + .border_4() + .border_color(gpui::blue()) + .bg(gpui::blue().alpha(0.4)) + .child("Clip borders"), + ), + ) + .child( + div() + .flex() + .flex_col() + .w(px(320.)) + .gap_1() + .overflow_hidden() + .rounded(px(20.)) + .child( + div().w_full().border_2().border_color(gpui::black()).child( + div() + .size_full() + .bg(gpui::green().alpha(0.4)) + .p_2() + .border_8() + .border_color(gpui::green()) + .child("Clip nested elements"), + ), + ), + ) + .child( + div() + .flex() + .flex_col() + .w(px(320.)) + .gap_1() + .overflow_hidden() + .rounded(px(32.)) + .child( + div() + .w_full() + .p_2() + .bg(gpui::black()) + .border_2() + .border_dashed() + .rounded_lg() + .border_color(gpui::white()) + .child("dash border full and rounded"), + ) + .child( + div() + .w_full() + .flex() + .flex_row() + .gap_2() + .child( + div() + .w_full() + .p_2() + .bg(gpui::black()) + .border_x_2() + .border_dashed() + .rounded_lg() + .border_color(gpui::white()) + .child("border x"), + ) + .child( + div() + .w_full() + .p_2() + .bg(gpui::black()) + .border_y_2() + .border_dashed() + .rounded_lg() + .border_color(gpui::white()) + .child("border y"), + ), + ) + .child( + div() + .w_full() + .p_2() + .bg(gpui::black()) + .border_2() + .border_dashed() + .border_color(gpui::white()) + .child("border full and no rounded"), + ), + ) + } +} + +fn main() { + Application::new().run(|cx: &mut App| { + let bounds = Bounds::centered(None, size(px(800.), px(600.)), cx); + cx.open_window( + WindowOptions { + window_bounds: Some(WindowBounds::Windowed(bounds)), + ..Default::default() + }, + |_, cx| cx.new(|_| Example {}), + ) + .unwrap(); + cx.activate(true); + }); +} diff --git a/crates/gpui/src/elements/list.rs b/crates/gpui/src/elements/list.rs index 6758f4eee1..57bb501107 100644 --- a/crates/gpui/src/elements/list.rs +++ b/crates/gpui/src/elements/list.rs @@ -8,10 +8,10 @@ //! If all of your elements are the same height, see [`UniformList`] for a simpler API use crate::{ - AnyElement, App, AvailableSpace, Bounds, ContentMask, DispatchPhase, Edges, Element, EntityId, - FocusHandle, GlobalElementId, Hitbox, HitboxBehavior, InspectorElementId, IntoElement, - Overflow, Pixels, Point, ScrollDelta, ScrollWheelEvent, Size, Style, StyleRefinement, Styled, - Window, point, px, size, + AnyElement, App, AvailableSpace, Bounds, ContentMask, Corners, DispatchPhase, Edges, Element, + EntityId, FocusHandle, GlobalElementId, Hitbox, HitboxBehavior, InspectorElementId, + IntoElement, Overflow, Pixels, Point, ScrollDelta, ScrollWheelEvent, Size, Style, + StyleRefinement, Styled, Window, point, px, size, }; use collections::VecDeque; use refineable::Refineable as _; @@ -705,6 +705,7 @@ impl StateInner { &mut self, bounds: Bounds, padding: Edges, + corner_radii: Corners, autoscroll: bool, render_item: &mut RenderItemFn, window: &mut Window, @@ -728,9 +729,15 @@ impl StateInner { let mut item_origin = bounds.origin + Point::new(px(0.), padding.top); item_origin.y -= layout_response.scroll_top.offset_in_item; for item in &mut layout_response.item_layouts { - window.with_content_mask(Some(ContentMask { bounds }), |window| { - item.element.prepaint_at(item_origin, window, cx); - }); + window.with_content_mask( + Some(ContentMask { + bounds, + corner_radii, + }), + |window| { + item.element.prepaint_at(item_origin, window, cx); + }, + ); if let Some(autoscroll_bounds) = window.take_autoscroll() && autoscroll @@ -952,19 +959,34 @@ impl Element for List { state.items = new_items; } - let padding = style - .padding - .to_pixels(bounds.size.into(), window.rem_size()); - let layout = - match state.prepaint_items(bounds, padding, true, &mut self.render_item, window, cx) { - Ok(layout) => layout, - Err(autoscroll_request) => { - state.logical_scroll_top = Some(autoscroll_request); - state - .prepaint_items(bounds, padding, false, &mut self.render_item, window, cx) - .unwrap() - } - }; + let rem_size = window.rem_size(); + let padding = style.padding.to_pixels(bounds.size.into(), rem_size); + let corner_radii = style.corner_radii.to_pixels(rem_size); + let layout = match state.prepaint_items( + bounds, + padding, + corner_radii, + true, + &mut self.render_item, + window, + cx, + ) { + Ok(layout) => layout, + Err(autoscroll_request) => { + state.logical_scroll_top = Some(autoscroll_request); + state + .prepaint_items( + bounds, + padding, + corner_radii, + false, + &mut self.render_item, + window, + cx, + ) + .unwrap() + } + }; state.last_layout_bounds = Some(bounds); state.last_padding = Some(padding); @@ -982,11 +1004,17 @@ impl Element for List { cx: &mut App, ) { let current_view = window.current_view(); - window.with_content_mask(Some(ContentMask { bounds }), |window| { - for item in &mut prepaint.layout.item_layouts { - item.element.paint(window, cx); - } - }); + window.with_content_mask( + Some(ContentMask { + bounds, + ..Default::default() + }), + |window| { + for item in &mut prepaint.layout.item_layouts { + item.element.paint(window, cx); + } + }, + ); let list_state = self.state.clone(); let height = bounds.size.height; diff --git a/crates/gpui/src/elements/uniform_list.rs b/crates/gpui/src/elements/uniform_list.rs index cdf90d4eb8..db3b8c8839 100644 --- a/crates/gpui/src/elements/uniform_list.rs +++ b/crates/gpui/src/elements/uniform_list.rs @@ -411,7 +411,10 @@ impl Element for UniformList { (self.render_items)(visible_range.clone(), window, cx) }; - let content_mask = ContentMask { bounds }; + let content_mask = ContentMask { + bounds, + ..Default::default() + }; window.with_content_mask(Some(content_mask), |window| { for (mut item, ix) in items.into_iter().zip(visible_range.clone()) { let item_origin = padded_bounds.origin diff --git a/crates/gpui/src/platform/blade/shaders.wgsl b/crates/gpui/src/platform/blade/shaders.wgsl index 95980b54fe..dbab4237e3 100644 --- a/crates/gpui/src/platform/blade/shaders.wgsl +++ b/crates/gpui/src/platform/blade/shaders.wgsl @@ -53,6 +53,11 @@ struct Corners { bottom_left: f32, } +struct ContentMask { + bounds: Bounds, + corner_radii: Corners, +} + struct Edges { top: f32, right: f32, @@ -440,7 +445,7 @@ struct Quad { order: u32, border_style: u32, bounds: Bounds, - content_mask: Bounds, + content_mask: ContentMask, background: Background, border_color: Hsla, corner_radii: Corners, @@ -478,7 +483,7 @@ fn vs_quad(@builtin(vertex_index) vertex_id: u32, @builtin(instance_index) insta out.background_color1 = gradient.color1; out.border_color = hsla_to_rgba(quad.border_color); out.quad_id = instance_id; - out.clip_distances = distance_from_clip_rect(unit_vertex, quad.bounds, quad.content_mask); + out.clip_distances = distance_from_clip_rect(unit_vertex, quad.bounds, quad.content_mask.bounds); return out; } @@ -491,8 +496,19 @@ fn fs_quad(input: QuadVarying) -> @location(0) vec4 { let quad = b_quads[input.quad_id]; - let background_color = gradient_color(quad.background, input.position.xy, quad.bounds, + // Signed distance field threshold for inclusion of pixels. 0.5 is the + // minimum distance between the center of the pixel and the edge. + let antialias_threshold = 0.5; + + var background_color = gradient_color(quad.background, input.position.xy, quad.bounds, input.background_solid, input.background_color0, input.background_color1); + var border_color = input.border_color; + + // Apply content_mask corner radii clipping + let clip_sdf = quad_sdf(input.position.xy, quad.content_mask.bounds, quad.content_mask.corner_radii); + let clip_alpha = saturate(antialias_threshold - clip_sdf); + background_color.a *= clip_alpha; + border_color.a *= clip_alpha; let unrounded = quad.corner_radii.top_left == 0.0 && quad.corner_radii.bottom_left == 0.0 && @@ -513,10 +529,6 @@ fn fs_quad(input: QuadVarying) -> @location(0) vec4 { let point = input.position.xy - quad.bounds.origin; let center_to_point = point - half_size; - // Signed distance field threshold for inclusion of pixels. 0.5 is the - // minimum distance between the center of the pixel and the edge. - let antialias_threshold = 0.5; - // Radius of the nearest corner let corner_radius = pick_corner_radius(center_to_point, quad.corner_radii); @@ -607,8 +619,6 @@ fn fs_quad(input: QuadVarying) -> @location(0) vec4 { var color = background_color; if (border_sdf < antialias_threshold) { - var border_color = input.border_color; - // Dashed border logic when border_style == 1 if (quad.border_style == 1) { // Position along the perimeter in "dash space", where each dash @@ -644,7 +654,11 @@ fn fs_quad(input: QuadVarying) -> @location(0) vec4 { let is_horizontal = corner_center_to_point.x < corner_center_to_point.y; - let border_width = select(border.y, border.x, is_horizontal); + var border_width = select(border.y, border.x, is_horizontal); + // When border width of some side is 0, we need to use the other side width for dash velocity. + if (border_width == 0.0) { + border_width = select(border.x, border.y, is_horizontal); + } dash_velocity = dv_numerator / border_width; t = select(point.y, point.x, is_horizontal) * dash_velocity; max_t = select(size.y, size.x, is_horizontal) * dash_velocity; @@ -856,7 +870,7 @@ struct Shadow { blur_radius: f32, bounds: Bounds, corner_radii: Corners, - content_mask: Bounds, + content_mask: ContentMask, color: Hsla, } var b_shadows: array; @@ -884,7 +898,7 @@ fn vs_shadow(@builtin(vertex_index) vertex_id: u32, @builtin(instance_index) ins out.position = to_device_position(unit_vertex, shadow.bounds); 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, shadow.bounds, shadow.content_mask.bounds); return out; } @@ -899,7 +913,6 @@ fn fs_shadow(input: ShadowVarying) -> @location(0) vec4 { let half_size = shadow.bounds.size / 2.0; let center = shadow.bounds.origin + half_size; let center_to_point = input.position.xy - center; - 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 @@ -1027,7 +1040,7 @@ struct Underline { order: u32, pad: u32, bounds: Bounds, - content_mask: Bounds, + content_mask: ContentMask, color: Hsla, thickness: f32, wavy: u32, @@ -1051,7 +1064,7 @@ fn vs_underline(@builtin(vertex_index) vertex_id: u32, @builtin(instance_index) out.position = to_device_position(unit_vertex, underline.bounds); out.color = hsla_to_rgba(underline.color); out.underline_id = instance_id; - out.clip_distances = distance_from_clip_rect(unit_vertex, underline.bounds, underline.content_mask); + out.clip_distances = distance_from_clip_rect(unit_vertex, underline.bounds, underline.content_mask.bounds); return out; } @@ -1093,7 +1106,7 @@ struct MonochromeSprite { order: u32, pad: u32, bounds: Bounds, - content_mask: Bounds, + content_mask: ContentMask, color: Hsla, tile: AtlasTile, transformation: TransformationMatrix, @@ -1117,7 +1130,7 @@ fn vs_mono_sprite(@builtin(vertex_index) vertex_id: u32, @builtin(instance_index out.tile_position = to_tile_position(unit_vertex, sprite.tile); out.color = hsla_to_rgba(sprite.color); - out.clip_distances = distance_from_clip_rect(unit_vertex, sprite.bounds, sprite.content_mask); + out.clip_distances = distance_from_clip_rect(unit_vertex, sprite.bounds, sprite.content_mask.bounds); return out; } @@ -1139,7 +1152,7 @@ struct PolychromeSprite { grayscale: u32, opacity: f32, bounds: Bounds, - content_mask: Bounds, + content_mask: ContentMask, corner_radii: Corners, tile: AtlasTile, } @@ -1161,7 +1174,7 @@ fn vs_poly_sprite(@builtin(vertex_index) vertex_id: u32, @builtin(instance_index out.position = to_device_position(unit_vertex, sprite.bounds); out.tile_position = to_tile_position(unit_vertex, sprite.tile); out.sprite_id = instance_id; - out.clip_distances = distance_from_clip_rect(unit_vertex, sprite.bounds, sprite.content_mask); + out.clip_distances = distance_from_clip_rect(unit_vertex, sprite.bounds, sprite.content_mask.bounds); return out; } @@ -1234,3 +1247,12 @@ fn fs_surface(input: SurfaceVarying) -> @location(0) vec4 { return ycbcr_to_RGB * y_cb_cr; } + +fn max_corner_radii(a: Corners, b: Corners) -> Corners { + return Corners( + max(a.top_left, b.top_left), + max(a.top_right, b.top_right), + max(a.bottom_right, b.bottom_right), + max(a.bottom_left, b.bottom_left) + ); +} diff --git a/crates/gpui/src/platform/mac/shaders.metal b/crates/gpui/src/platform/mac/shaders.metal index 83c978b853..6aa1d18ee8 100644 --- a/crates/gpui/src/platform/mac/shaders.metal +++ b/crates/gpui/src/platform/mac/shaders.metal @@ -99,8 +99,21 @@ fragment float4 quad_fragment(QuadFragmentInput input [[stage_in]], constant Quad *quads [[buffer(QuadInputIndex_Quads)]]) { Quad quad = quads[input.quad_id]; + + // Signed distance field threshold for inclusion of pixels. 0.5 is the + // minimum distance between the center of the pixel and the edge. + const float antialias_threshold = 0.5; + float4 background_color = fill_color(quad.background, input.position.xy, quad.bounds, input.background_solid, input.background_color0, input.background_color1); + float4 border_color = input.border_color; + + // Apply content_mask corner radii clipping + float clip_sdf = quad_sdf(input.position.xy, quad.content_mask.bounds, + quad.content_mask.corner_radii); + float clip_alpha = saturate(antialias_threshold - clip_sdf); + background_color.a *= clip_alpha; + border_color *= clip_alpha; bool unrounded = quad.corner_radii.top_left == 0.0 && quad.corner_radii.bottom_left == 0.0 && @@ -121,10 +134,6 @@ fragment float4 quad_fragment(QuadFragmentInput input [[stage_in]], float2 point = input.position.xy - float2(quad.bounds.origin.x, quad.bounds.origin.y); float2 center_to_point = point - half_size; - // Signed distance field threshold for inclusion of pixels. 0.5 is the - // minimum distance between the center of the pixel and the edge. - const float antialias_threshold = 0.5; - // Radius of the nearest corner float corner_radius = pick_corner_radius(center_to_point, quad.corner_radii); @@ -164,7 +173,6 @@ fragment float4 quad_fragment(QuadFragmentInput input [[stage_in]], straight_border_inner_corner_to_point.x > 0.0 || straight_border_inner_corner_to_point.y > 0.0; - // Whether the point is far enough inside the quad, such that the pixels are // not affected by the straight border. bool is_within_inner_straight_border = @@ -208,8 +216,6 @@ fragment float4 quad_fragment(QuadFragmentInput input [[stage_in]], float4 color = background_color; if (border_sdf < antialias_threshold) { - float4 border_color = input.border_color; - // Dashed border logic when border_style == 1 if (quad.border_style == 1) { // Position along the perimeter in "dash space", where each dash @@ -244,6 +250,10 @@ fragment float4 quad_fragment(QuadFragmentInput input [[stage_in]], // perimeter. This way each line starts and ends with a dash. bool is_horizontal = corner_center_to_point.x < corner_center_to_point.y; float border_width = is_horizontal ? border.x : border.y; + // When border width of some side is 0, we need to use the other side width for dash velocity. + if (border_width == 0.0) { + border_width = is_horizontal ? border.y : border.x; + } dash_velocity = dv_numerator / border_width; t = is_horizontal ? point.x : point.y; t *= dash_velocity; diff --git a/crates/gpui/src/platform/windows/shaders.hlsl b/crates/gpui/src/platform/windows/shaders.hlsl index 6fabe859e3..c43e9c7708 100644 --- a/crates/gpui/src/platform/windows/shaders.hlsl +++ b/crates/gpui/src/platform/windows/shaders.hlsl @@ -449,11 +449,16 @@ float quarter_ellipse_sdf(float2 pt, float2 radii) { ** */ +struct ContentMask { + Bounds bounds; + Corners corner_radii; +}; + struct Quad { uint order; uint border_style; Bounds bounds; - Bounds content_mask; + ContentMask content_mask; Background background; Hsla border_color; Corners corner_radii; @@ -492,7 +497,7 @@ QuadVertexOutput quad_vertex(uint vertex_id: SV_VertexID, uint quad_id: SV_Insta quad.background.solid, quad.background.colors ); - float4 clip_distance = distance_from_clip_rect(unit_vertex, quad.bounds, quad.content_mask); + float4 clip_distance = distance_from_clip_rect(unit_vertex, quad.bounds, quad.content_mask.bounds); float4 border_color = hsla_to_rgba(quad.border_color); QuadVertexOutput output; @@ -508,8 +513,21 @@ QuadVertexOutput quad_vertex(uint vertex_id: SV_VertexID, uint quad_id: SV_Insta float4 quad_fragment(QuadFragmentInput input): SV_Target { Quad quad = quads[input.quad_id]; + + // Signed distance field threshold for inclusion of pixels. 0.5 is the + // minimum distance between the center of the pixel and the edge. + const float antialias_threshold = 0.5; + float4 background_color = gradient_color(quad.background, input.position.xy, quad.bounds, - input.background_solid, input.background_color0, input.background_color1); + input.background_solid, input.background_color0, input.background_color1); + float4 border_color = input.border_color; + + // Apply content_mask corner radii clipping + float clip_sdf = quad_sdf(input.position.xy, quad.content_mask.bounds, + quad.content_mask.corner_radii); + float clip_alpha = saturate(antialias_threshold - clip_sdf); + background_color.a *= clip_alpha; + border_color *= clip_alpha; bool unrounded = quad.corner_radii.top_left == 0.0 && quad.corner_radii.top_right == 0.0 && @@ -530,10 +548,6 @@ float4 quad_fragment(QuadFragmentInput input): SV_Target { float2 the_point = input.position.xy - quad.bounds.origin; float2 center_to_point = the_point - half_size; - // Signed distance field threshold for inclusion of pixels. 0.5 is the - // minimum distance between the center of the pixel and the edge. - const float antialias_threshold = 0.5; - // Radius of the nearest corner float corner_radius = pick_corner_radius(center_to_point, quad.corner_radii); @@ -616,7 +630,6 @@ float4 quad_fragment(QuadFragmentInput input): SV_Target { float4 color = background_color; if (border_sdf < antialias_threshold) { - float4 border_color = input.border_color; // Dashed border logic when border_style == 1 if (quad.border_style == 1) { // Position along the perimeter in "dash space", where each dash @@ -651,6 +664,10 @@ float4 quad_fragment(QuadFragmentInput input): SV_Target { // perimeter. This way each line starts and ends with a dash. bool is_horizontal = corner_center_to_point.x < corner_center_to_point.y; float border_width = is_horizontal ? border.x : border.y; + // When border width of some side is 0, we need to use the other side width for dash velocity. + if (border_width == 0.0) { + border_width = is_horizontal ? border.y : border.x; + } dash_velocity = dv_numerator / border_width; t = is_horizontal ? the_point.x : the_point.y; t *= dash_velocity; @@ -801,7 +818,7 @@ struct Shadow { float blur_radius; Bounds bounds; Corners corner_radii; - Bounds content_mask; + ContentMask content_mask; Hsla color; }; @@ -830,7 +847,7 @@ ShadowVertexOutput shadow_vertex(uint vertex_id: SV_VertexID, uint shadow_id: SV 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); + float4 clip_distance = distance_from_clip_rect(unit_vertex, bounds, shadow.content_mask.bounds); float4 color = hsla_to_rgba(shadow.color); ShadowVertexOutput output; @@ -983,7 +1000,7 @@ struct Underline { uint order; uint pad; Bounds bounds; - Bounds content_mask; + ContentMask content_mask; Hsla color; float thickness; uint wavy; @@ -1009,7 +1026,7 @@ UnderlineVertexOutput underline_vertex(uint vertex_id: SV_VertexID, uint underli Underline underline = underlines[underline_id]; float4 device_position = to_device_position(unit_vertex, underline.bounds); float4 clip_distance = distance_from_clip_rect(unit_vertex, underline.bounds, - underline.content_mask); + underline.content_mask.bounds); float4 color = hsla_to_rgba(underline.color); UnderlineVertexOutput output; @@ -1057,7 +1074,7 @@ struct MonochromeSprite { uint order; uint pad; Bounds bounds; - Bounds content_mask; + ContentMask content_mask; Hsla color; AtlasTile tile; TransformationMatrix transformation; @@ -1084,7 +1101,7 @@ MonochromeSpriteVertexOutput monochrome_sprite_vertex(uint vertex_id: SV_VertexI MonochromeSprite sprite = mono_sprites[sprite_id]; float4 device_position = to_device_position_transformed(unit_vertex, sprite.bounds, sprite.transformation); - float4 clip_distance = distance_from_clip_rect(unit_vertex, sprite.bounds, sprite.content_mask); + float4 clip_distance = distance_from_clip_rect(unit_vertex, sprite.bounds, sprite.content_mask.bounds); float2 tile_position = to_tile_position(unit_vertex, sprite.tile); float4 color = hsla_to_rgba(sprite.color); @@ -1113,7 +1130,7 @@ struct PolychromeSprite { uint grayscale; float opacity; Bounds bounds; - Bounds content_mask; + ContentMask content_mask; Corners corner_radii; AtlasTile tile; }; @@ -1138,7 +1155,7 @@ PolychromeSpriteVertexOutput polychrome_sprite_vertex(uint vertex_id: SV_VertexI PolychromeSprite sprite = poly_sprites[sprite_id]; float4 device_position = to_device_position(unit_vertex, sprite.bounds); float4 clip_distance = distance_from_clip_rect(unit_vertex, sprite.bounds, - sprite.content_mask); + sprite.content_mask.bounds); float2 tile_position = to_tile_position(unit_vertex, sprite.tile); PolychromeSpriteVertexOutput output; diff --git a/crates/gpui/src/style.rs b/crates/gpui/src/style.rs index 5b69ce7fa6..09f598f9b0 100644 --- a/crates/gpui/src/style.rs +++ b/crates/gpui/src/style.rs @@ -601,7 +601,19 @@ impl Style { (false, false) => Bounds::from_corners(min, max), }; - Some(ContentMask { bounds }) + let corner_radii = self.corner_radii.to_pixels(rem_size); + let border_widths = self.border_widths.to_pixels(rem_size); + Some(ContentMask { + bounds: Bounds { + origin: bounds.origin - point(border_widths.left, border_widths.top), + size: bounds.size + + size( + border_widths.left + border_widths.right, + border_widths.top + border_widths.bottom, + ), + }, + corner_radii, + }) } } } @@ -661,64 +673,16 @@ impl Style { if self.is_border_visible() { let border_widths = self.border_widths.to_pixels(rem_size); - let max_border_width = border_widths.max(); - let max_corner_radius = corner_radii.max(); - - let top_bounds = Bounds::from_corners( - bounds.origin, - bounds.top_right() + point(Pixels::ZERO, max_border_width.max(max_corner_radius)), - ); - let bottom_bounds = Bounds::from_corners( - bounds.bottom_left() - point(Pixels::ZERO, max_border_width.max(max_corner_radius)), - bounds.bottom_right(), - ); - let left_bounds = Bounds::from_corners( - top_bounds.bottom_left(), - bottom_bounds.origin + point(max_border_width, Pixels::ZERO), - ); - let right_bounds = Bounds::from_corners( - top_bounds.bottom_right() - point(max_border_width, Pixels::ZERO), - bottom_bounds.top_right(), - ); - let mut background = self.border_color.unwrap_or_default(); background.a = 0.; - let quad = quad( + window.paint_quad(quad( bounds, corner_radii, background, border_widths, self.border_color.unwrap_or_default(), self.border_style, - ); - - window.with_content_mask(Some(ContentMask { bounds: top_bounds }), |window| { - window.paint_quad(quad.clone()); - }); - window.with_content_mask( - Some(ContentMask { - bounds: right_bounds, - }), - |window| { - window.paint_quad(quad.clone()); - }, - ); - window.with_content_mask( - Some(ContentMask { - bounds: bottom_bounds, - }), - |window| { - window.paint_quad(quad.clone()); - }, - ); - window.with_content_mask( - Some(ContentMask { - bounds: left_bounds, - }), - |window| { - window.paint_quad(quad); - }, - ); + )); } #[cfg(debug_assertions)] diff --git a/crates/gpui/src/window.rs b/crates/gpui/src/window.rs index 0791dcc621..f1827874d9 100644 --- a/crates/gpui/src/window.rs +++ b/crates/gpui/src/window.rs @@ -1209,6 +1209,8 @@ pub(crate) struct DispatchEventResult { pub struct ContentMask { /// The bounds pub bounds: Bounds

, + /// The corner radii of the content mask. + pub corner_radii: Corners

, } impl ContentMask { @@ -1216,13 +1218,31 @@ impl ContentMask { pub fn scale(&self, factor: f32) -> ContentMask { ContentMask { bounds: self.bounds.scale(factor), + corner_radii: self.corner_radii.scale(factor), } } /// Intersect the content mask with the given content mask. pub fn intersect(&self, other: &Self) -> Self { let bounds = self.bounds.intersect(&other.bounds); - ContentMask { bounds } + ContentMask { + bounds, + corner_radii: Corners { + top_left: self.corner_radii.top_left.max(other.corner_radii.top_left), + top_right: self + .corner_radii + .top_right + .max(other.corner_radii.top_right), + bottom_right: self + .corner_radii + .bottom_right + .max(other.corner_radii.bottom_right), + bottom_left: self + .corner_radii + .bottom_left + .max(other.corner_radii.bottom_left), + }, + } } } @@ -2483,6 +2503,7 @@ impl Window { origin: Point::default(), size: self.viewport_size, }, + ..Default::default() }) } diff --git a/crates/terminal_view/src/terminal_element.rs b/crates/terminal_view/src/terminal_element.rs index fe3301fb89..997663e8ff 100644 --- a/crates/terminal_view/src/terminal_element.rs +++ b/crates/terminal_view/src/terminal_element.rs @@ -1184,7 +1184,7 @@ impl Element for TerminalElement { cx: &mut App, ) { let paint_start = Instant::now(); - window.with_content_mask(Some(ContentMask { bounds }), |window| { + window.with_content_mask(Some(ContentMask { bounds, ..Default::default() }), |window| { let scroll_top = self.terminal_view.read(cx).scroll_top; window.paint_quad(fill(bounds, layout.background_color)); diff --git a/crates/ui/src/components/scrollbar.rs b/crates/ui/src/components/scrollbar.rs index 605028202f..475575a483 100644 --- a/crates/ui/src/components/scrollbar.rs +++ b/crates/ui/src/components/scrollbar.rs @@ -303,9 +303,13 @@ impl Element for Scrollbar { window: &mut Window, _: &mut App, ) -> Self::PrepaintState { - window.with_content_mask(Some(ContentMask { bounds }), |window| { - window.insert_hitbox(bounds, HitboxBehavior::Normal) - }) + window.with_content_mask( + Some(ContentMask { + bounds, + ..Default::default() + }), + |window| window.insert_hitbox(bounds, HitboxBehavior::Normal), + ) } fn paint( @@ -319,7 +323,11 @@ impl Element for Scrollbar { cx: &mut App, ) { const EXTRA_PADDING: Pixels = px(5.0); - window.with_content_mask(Some(ContentMask { bounds }), |window| { + let content_mask = ContentMask { + bounds, + ..Default::default() + }; + window.with_content_mask(Some(content_mask), |window| { let axis = self.kind; let colors = cx.theme().colors(); let thumb_state = self.state.thumb_state.get();