Wire up per corner radii for quad

Nathan Sobo created

Still need to expose this in the styling layer and allow images
to have per corner radii.

Change summary

crates/collab_ui/src/contact_list.rs               |   8 
crates/editor/src/element.rs                       |  26 +-
crates/editor/src/hover_popover.rs                 |   2 
crates/gpui/examples/quad.rs                       | 120 ++++++++++++++++
crates/gpui/src/elements/container.rs              |   8 
crates/gpui/src/platform/mac/renderer.rs           |   5 
crates/gpui/src/platform/mac/shaders/shaders.h     |   5 
crates/gpui/src/platform/mac/shaders/shaders.metal |  36 ++++
crates/gpui/src/scene.rs                           |  21 ++
crates/terminal_view/src/terminal_element.rs       |   4 
crates/workspace/src/pane.rs                       |   2 
crates/workspace/src/pane/dragged_item_receiver.rs |   2 
crates/workspace/src/workspace.rs                  |   2 
13 files changed, 205 insertions(+), 36 deletions(-)

Detailed changes

crates/collab_ui/src/contact_list.rs 🔗

@@ -837,7 +837,7 @@ impl ContactList {
                                 ),
                                 background: Some(tree_branch.color),
                                 border: gpui::Border::default(),
-                                corner_radius: 0.,
+                                corner_radii: Default::default(),
                             });
                             scene.push_quad(gpui::Quad {
                                 bounds: RectF::from_points(
@@ -846,7 +846,7 @@ impl ContactList {
                                 ),
                                 background: Some(tree_branch.color),
                                 border: gpui::Border::default(),
-                                corner_radius: 0.,
+                                corner_radii: Default::default(),
                             });
                         }))
                         .constrained()
@@ -934,7 +934,7 @@ impl ContactList {
                                     ),
                                     background: Some(tree_branch.color),
                                     border: gpui::Border::default(),
-                                    corner_radius: 0.,
+                                    corner_radii: Default::default(),
                                 });
                                 scene.push_quad(gpui::Quad {
                                     bounds: RectF::from_points(
@@ -943,7 +943,7 @@ impl ContactList {
                                     ),
                                     background: Some(tree_branch.color),
                                     border: gpui::Border::default(),
-                                    corner_radius: 0.,
+                                    corner_radii: Default::default(),
                                 });
                             }))
                             .constrained()

crates/editor/src/element.rs 🔗

@@ -488,13 +488,13 @@ impl EditorElement {
             bounds: gutter_bounds,
             background: Some(self.style.gutter_background),
             border: Border::new(0., Color::transparent_black()),
-            corner_radius: 0.,
+            corner_radii: Default::default(),
         });
         scene.push_quad(Quad {
             bounds: text_bounds,
             background: Some(self.style.background),
             border: Border::new(0., Color::transparent_black()),
-            corner_radius: 0.,
+            corner_radii: Default::default(),
         });
 
         if let EditorMode::Full = layout.mode {
@@ -522,7 +522,7 @@ impl EditorElement {
                         bounds: RectF::new(origin, size),
                         background: Some(self.style.active_line_background),
                         border: Border::default(),
-                        corner_radius: 0.,
+                        corner_radii: Default::default(),
                     });
                 }
             }
@@ -542,7 +542,7 @@ impl EditorElement {
                     bounds: RectF::new(origin, size),
                     background: Some(self.style.highlighted_line_background),
                     border: Border::default(),
-                    corner_radius: 0.,
+                    corner_radii: Default::default(),
                 });
             }
 
@@ -572,7 +572,7 @@ impl EditorElement {
                     ),
                     background: Some(color),
                     border: Border::new(0., Color::transparent_black()),
-                    corner_radius: 0.,
+                    corner_radii: Default::default(),
                 });
             }
         }
@@ -673,7 +673,7 @@ impl EditorElement {
                         bounds: highlight_bounds,
                         background: Some(diff_style.modified),
                         border: Border::new(0., Color::transparent_black()),
-                        corner_radius: 1. * line_height,
+                        corner_radii: (1. * line_height).into(),
                     });
 
                     continue;
@@ -706,7 +706,7 @@ impl EditorElement {
                         bounds: highlight_bounds,
                         background: Some(diff_style.deleted),
                         border: Border::new(0., Color::transparent_black()),
-                        corner_radius: 1. * line_height,
+                        corner_radii: (1. * line_height).into(),
                     });
 
                     continue;
@@ -728,7 +728,7 @@ impl EditorElement {
                 bounds: highlight_bounds,
                 background: Some(color),
                 border: Border::new(0., Color::transparent_black()),
-                corner_radius: diff_style.corner_radius * line_height,
+                corner_radii: (diff_style.corner_radius * line_height).into(),
             });
         }
     }
@@ -1129,7 +1129,7 @@ impl EditorElement {
                         bounds,
                         background: Some(color),
                         border,
-                        corner_radius: style.thumb.corner_radius,
+                        corner_radii: style.thumb.corner_radius.into(),
                     })
                 };
                 let background_ranges = editor
@@ -1189,7 +1189,7 @@ impl EditorElement {
                         bounds,
                         background: Some(color),
                         border,
-                        corner_radius: style.thumb.corner_radius,
+                        corner_radii: style.thumb.corner_radius.into(),
                     })
                 }
             }
@@ -1198,7 +1198,7 @@ impl EditorElement {
                 bounds: thumb_bounds,
                 border: style.thumb.border,
                 background: style.thumb.background_color,
-                corner_radius: style.thumb.corner_radius,
+                corner_radii: style.thumb.corner_radius.into(),
             });
         }
 
@@ -2725,14 +2725,14 @@ impl Cursor {
                 bounds,
                 background: None,
                 border: Border::all(1., self.color),
-                corner_radius: 0.,
+                corner_radii: Default::default(),
             });
         } else {
             scene.push_quad(Quad {
                 bounds,
                 background: Some(self.color),
                 border: Default::default(),
-                corner_radius: 0.,
+                corner_radii: Default::default(),
             });
         }
 

crates/editor/src/hover_popover.rs 🔗

@@ -599,7 +599,7 @@ impl InfoPopover {
                                         bounds,
                                         background: Some(code_span_background_color),
                                         border: Default::default(),
-                                        corner_radius: 2.0,
+                                        corner_radii: (2.0).into(),
                                     });
                                 }
                             },

crates/gpui/examples/quad.rs 🔗

@@ -0,0 +1,120 @@
+use gpui::{color::Color, geometry::rect::RectF, AnyElement, App, Element, Entity, Quad, View};
+use log::LevelFilter;
+use pathfinder_geometry::vector::vec2f;
+use simplelog::SimpleLogger;
+
+fn main() {
+    SimpleLogger::init(LevelFilter::Info, Default::default()).expect("could not initialize logger");
+
+    App::new(()).unwrap().run(|cx| {
+        cx.platform().activate(true);
+        cx.add_window(Default::default(), |_| QuadView);
+    });
+}
+
+struct QuadView;
+
+impl Entity for QuadView {
+    type Event = ();
+}
+
+impl View for QuadView {
+    fn ui_name() -> &'static str {
+        "QuadView"
+    }
+
+    fn render(&mut self, _: &mut gpui::ViewContext<Self>) -> AnyElement<QuadView> {
+        QuadElement.into_any()
+    }
+}
+
+struct QuadElement;
+
+impl<V: View> gpui::Element<V> for QuadElement {
+    type LayoutState = ();
+
+    type PaintState = ();
+
+    fn layout(
+        &mut self,
+        constraint: gpui::SizeConstraint,
+        _: &mut V,
+        _: &mut gpui::LayoutContext<V>,
+    ) -> (gpui::geometry::vector::Vector2F, Self::LayoutState) {
+        (constraint.max, ())
+    }
+
+    fn paint(
+        &mut self,
+        scene: &mut gpui::SceneBuilder,
+        _: pathfinder_geometry::rect::RectF,
+        _: pathfinder_geometry::rect::RectF,
+        _: &mut Self::LayoutState,
+        _: &mut V,
+        _: &mut gpui::PaintContext<V>,
+    ) -> Self::PaintState {
+        scene.push_quad(Quad {
+            bounds: RectF::new(vec2f(100., 100.), vec2f(100., 100.)),
+            background: Some(Color::red()),
+            border: Default::default(),
+            corner_radii: gpui::scene::CornerRadii {
+                top_left: 20.,
+                ..Default::default()
+            },
+        });
+
+        scene.push_quad(Quad {
+            bounds: RectF::new(vec2f(200., 100.), vec2f(100., 100.)),
+            background: Some(Color::green()),
+            border: Default::default(),
+            corner_radii: gpui::scene::CornerRadii {
+                top_right: 20.,
+                ..Default::default()
+            },
+        });
+
+        scene.push_quad(Quad {
+            bounds: RectF::new(vec2f(100., 200.), vec2f(100., 100.)),
+            background: Some(Color::blue()),
+            border: Default::default(),
+            corner_radii: gpui::scene::CornerRadii {
+                bottom_left: 20.,
+                ..Default::default()
+            },
+        });
+
+        scene.push_quad(Quad {
+            bounds: RectF::new(vec2f(200., 200.), vec2f(100., 100.)),
+            background: Some(Color::yellow()),
+            border: Default::default(),
+            corner_radii: gpui::scene::CornerRadii {
+                bottom_right: 20.,
+                ..Default::default()
+            },
+        });
+    }
+
+    fn rect_for_text_range(
+        &self,
+        _: std::ops::Range<usize>,
+        _: pathfinder_geometry::rect::RectF,
+        _: pathfinder_geometry::rect::RectF,
+        _: &Self::LayoutState,
+        _: &Self::PaintState,
+        _: &V,
+        _: &gpui::ViewContext<V>,
+    ) -> Option<pathfinder_geometry::rect::RectF> {
+        unimplemented!()
+    }
+
+    fn debug(
+        &self,
+        _: pathfinder_geometry::rect::RectF,
+        _: &Self::LayoutState,
+        _: &Self::PaintState,
+        _: &V,
+        _: &gpui::ViewContext<V>,
+    ) -> serde_json::Value {
+        unimplemented!()
+    }
+}

crates/gpui/src/elements/container.rs 🔗

@@ -248,7 +248,7 @@ impl<V: View> Element<V> for Container<V> {
                 bounds: quad_bounds,
                 background: self.style.background_color,
                 border: Default::default(),
-                corner_radius: self.style.corner_radius,
+                corner_radii: self.style.corner_radius.into(),
             });
 
             self.child
@@ -259,7 +259,7 @@ impl<V: View> Element<V> for Container<V> {
                 bounds: quad_bounds,
                 background: self.style.overlay_color,
                 border: self.style.border,
-                corner_radius: self.style.corner_radius,
+                corner_radii: self.style.corner_radius.into(),
             });
             scene.pop_layer();
         } else {
@@ -267,7 +267,7 @@ impl<V: View> Element<V> for Container<V> {
                 bounds: quad_bounds,
                 background: self.style.background_color,
                 border: self.style.border,
-                corner_radius: self.style.corner_radius,
+                corner_radii: self.style.corner_radius.into(),
             });
 
             let child_origin = child_origin
@@ -284,7 +284,7 @@ impl<V: View> Element<V> for Container<V> {
                     bounds: quad_bounds,
                     background: self.style.overlay_color,
                     border: Default::default(),
-                    corner_radius: 0.,
+                    corner_radii: self.style.corner_radius.into(),
                 });
                 scene.pop_layer();
             }

crates/gpui/src/platform/mac/renderer.rs 🔗

@@ -586,7 +586,10 @@ impl Renderer {
                 border_bottom: border_width * (quad.border.bottom as usize as f32),
                 border_left: border_width * (quad.border.left as usize as f32),
                 border_color: quad.border.color.to_uchar4(),
-                corner_radius: quad.corner_radius * scale_factor,
+                corner_radius_top_left: quad.corner_radii.top_left * scale_factor,
+                corner_radius_top_right: quad.corner_radii.top_right * scale_factor,
+                corner_radius_bottom_right: quad.corner_radii.bottom_right * scale_factor,
+                corner_radius_bottom_left: quad.corner_radii.bottom_left * scale_factor,
             };
             unsafe {
                 *(buffer_contents.add(ix)) = shader_quad;

crates/gpui/src/platform/mac/shaders/shaders.h 🔗

@@ -19,7 +19,10 @@ typedef struct {
   float border_bottom;
   float border_left;
   vector_uchar4 border_color;
-  float corner_radius;
+  float corner_radius_top_left;
+  float corner_radius_top_right;
+  float corner_radius_bottom_right;
+  float corner_radius_bottom_left;
 } GPUIQuad;
 
 typedef enum {

crates/gpui/src/platform/mac/shaders/shaders.metal 🔗

@@ -43,7 +43,10 @@ struct QuadFragmentInput {
     float border_bottom;
     float border_left;
     float4 border_color;
-    float corner_radius;
+    float corner_radius_top_left;
+    float corner_radius_top_right;
+    float corner_radius_bottom_right;
+    float corner_radius_bottom_left;
     uchar grayscale; // only used in image shader
 };
 
@@ -51,12 +54,27 @@ float4 quad_sdf(QuadFragmentInput input) {
     float2 half_size = input.size / 2.;
     float2 center = input.origin + half_size;
     float2 center_to_point = input.position.xy - center;
-    float2 rounded_edge_to_point = abs(center_to_point) - half_size + input.corner_radius;
-    float distance = length(max(0., rounded_edge_to_point)) + min(0., max(rounded_edge_to_point.x, rounded_edge_to_point.y)) - input.corner_radius;
+    float corner_radius;
+    if (center_to_point.x < 0.) {
+        if (center_to_point.y < 0.) {
+            corner_radius = input.corner_radius_top_left;
+        } else {
+            corner_radius = input.corner_radius_bottom_left;
+        }
+    } else {
+        if (center_to_point.y < 0.) {
+            corner_radius = input.corner_radius_top_right;
+        } else {
+            corner_radius = input.corner_radius_bottom_right;
+        }
+    }
+
+    float2 rounded_edge_to_point = abs(center_to_point) - half_size + corner_radius;
+    float distance = length(max(0., rounded_edge_to_point)) + min(0., max(rounded_edge_to_point.x, rounded_edge_to_point.y)) - corner_radius;
 
     float vertical_border = center_to_point.x <= 0. ? input.border_left : input.border_right;
     float horizontal_border = center_to_point.y <= 0. ? input.border_top : input.border_bottom;
-    float2 inset_size = half_size - input.corner_radius - float2(vertical_border, horizontal_border);
+    float2 inset_size = half_size - corner_radius - float2(vertical_border, horizontal_border);
     float2 point_to_inset_corner = abs(center_to_point) - inset_size;
     float border_width;
     if (point_to_inset_corner.x < 0. && point_to_inset_corner.y < 0.) {
@@ -110,7 +128,10 @@ vertex QuadFragmentInput quad_vertex(
         quad.border_bottom,
         quad.border_left,
         coloru_to_colorf(quad.border_color),
-        quad.corner_radius,
+        quad.corner_radius_top_left,
+        quad.corner_radius_top_right,
+        quad.corner_radius_bottom_right,
+        quad.corner_radius_bottom_left,
         0,
     };
 }
@@ -253,6 +274,9 @@ vertex QuadFragmentInput image_vertex(
         image.border_left,
         coloru_to_colorf(image.border_color),
         image.corner_radius,
+        image.corner_radius,
+        image.corner_radius,
+        image.corner_radius,
         image.grayscale,
     };
 }
@@ -266,7 +290,7 @@ fragment float4 image_fragment(
     if (input.grayscale) {
         float grayscale =
             0.2126 * input.background_color.r +
-            0.7152 * input.background_color.g + 
+            0.7152 * input.background_color.g +
             0.0722 * input.background_color.b;
         input.background_color = float4(grayscale, grayscale, grayscale, input.background_color.a);
     }

crates/gpui/src/scene.rs 🔗

@@ -65,7 +65,26 @@ pub struct Quad {
     pub bounds: RectF,
     pub background: Option<Color>,
     pub border: Border,
-    pub corner_radius: f32,
+    pub corner_radii: CornerRadii,
+}
+
+#[derive(Default, Debug)]
+pub struct CornerRadii {
+    pub top_left: f32,
+    pub top_right: f32,
+    pub bottom_right: f32,
+    pub bottom_left: f32,
+}
+
+impl From<f32> for CornerRadii {
+    fn from(radius: f32) -> Self {
+        Self {
+            top_left: radius,
+            top_right: radius,
+            bottom_right: radius,
+            bottom_left: radius,
+        }
+    }
 }
 
 #[derive(Debug)]

crates/terminal_view/src/terminal_element.rs 🔗

@@ -153,7 +153,7 @@ impl LayoutRect {
             bounds: RectF::new(position, size),
             background: Some(self.color),
             border: Default::default(),
-            corner_radius: 0.,
+            corner_radii: Default::default(),
         })
     }
 }
@@ -763,7 +763,7 @@ impl Element<TerminalView> for TerminalElement {
                     bounds: RectF::new(bounds.origin(), bounds.size()),
                     background: Some(layout.background_color),
                     border: Default::default(),
-                    corner_radius: 0.,
+                    corner_radii: Default::default(),
                 });
 
                 for rect in &layout.rects {

crates/workspace/src/pane.rs 🔗

@@ -1397,7 +1397,7 @@ impl Pane {
                         bounds: square,
                         background: Some(color),
                         border: Default::default(),
-                        corner_radius: diameter / 2.,
+                        corner_radii: (diameter / 2.).into(),
                     });
                 }
             })

crates/workspace/src/workspace.rs 🔗

@@ -3614,7 +3614,7 @@ fn notify_of_new_dock(workspace: &WeakViewHandle<Workspace>, cx: &mut AsyncAppCo
                                 bounds,
                                 background: Some(code_span_background_color),
                                 border: Default::default(),
-                                corner_radius: 2.0,
+                                corner_radii: (2.0).into(),
                             })
                         })
                         .into_any()