Fix crash when selections exceed the container's bounds

Antonio Scandurra created

Change summary

gpui/src/geometry.rs                        |  8 +++++++-
gpui/src/platform/mac/renderer.rs           |  2 ++
gpui/src/platform/mac/shaders/shaders.h     |  2 ++
gpui/src/platform/mac/shaders/shaders.metal | 16 ++++++++++++++--
gpui/src/scene.rs                           |  4 +++-
zed/src/editor/buffer_element.rs            | 19 ++++++++++++-------
6 files changed, 40 insertions(+), 11 deletions(-)

Detailed changes

gpui/src/geometry.rs 🔗

@@ -54,7 +54,13 @@ impl PathBuilder {
         self.current = point;
     }
 
-    pub fn build(self, color: ColorU) -> Path {
+    pub fn build(mut self, color: ColorU, clip_bounds: Option<RectF>) -> Path {
+        if let Some(clip_bounds) = clip_bounds {
+            self.bounds = self
+                .bounds
+                .intersection(clip_bounds)
+                .unwrap_or(RectF::default());
+        }
         Path {
             bounds: self.bounds,
             color,

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

@@ -181,6 +181,8 @@ impl Renderer {
                     vertices.push(shaders::GPUIPathVertex {
                         xy_position: (atlas_origin + xy_position).to_float2(),
                         st_position: vertex.st_position.to_float2(),
+                        clip_rect_origin: atlas_origin.to_float2(),
+                        clip_rect_size: size.to_float2(),
                     });
                 }
             }

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

@@ -63,4 +63,6 @@ typedef enum {
 typedef struct {
     vector_float2 xy_position;
     vector_float2 st_position;
+    vector_float2 clip_rect_origin;
+    vector_float2 clip_rect_size;
 } GPUIPathVertex;

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

@@ -218,21 +218,33 @@ fragment float4 sprite_fragment(
     return color;
 }
 
+struct PathAtlasVertexOutput {
+    float4 position [[position]];
+    float2 st_position;
+    float clip_rect_distance [[clip_distance]] [4];
+};
+
 struct PathAtlasFragmentInput {
     float4 position [[position]];
     float2 st_position;
 };
 
-vertex PathAtlasFragmentInput path_atlas_vertex(
+vertex PathAtlasVertexOutput path_atlas_vertex(
     uint vertex_id [[vertex_id]],
     constant GPUIPathVertex *vertices [[buffer(GPUIPathAtlasVertexInputIndexVertices)]],
     constant float2 *atlas_size [[buffer(GPUIPathAtlasVertexInputIndexAtlasSize)]]
 ) {
     GPUIPathVertex v = vertices[vertex_id];
     float4 device_position = to_device_position(v.xy_position, *atlas_size);
-    return PathAtlasFragmentInput {
+    return PathAtlasVertexOutput {
         device_position,
         v.st_position,
+        {
+            v.xy_position.x - v.clip_rect_origin.x,
+            v.clip_rect_origin.x + v.clip_rect_size.x - v.xy_position.x,
+            v.xy_position.y - v.clip_rect_origin.y,
+            v.clip_rect_origin.y + v.clip_rect_size.y - v.xy_position.y
+        }
     };
 }
 

gpui/src/scene.rs 🔗

@@ -156,7 +156,9 @@ impl Layer {
     }
 
     fn push_path(&mut self, path: Path) {
-        self.paths.push(path);
+        if !path.bounds.is_empty() {
+            self.paths.push(path);
+        }
     }
 
     pub fn paths(&self) -> &[Path] {

zed/src/editor/buffer_element.rs 🔗

@@ -259,7 +259,7 @@ impl BufferElement {
                         .collect(),
                 };
 
-                selection.paint(ctx.scene);
+                selection.paint(bounds, ctx.scene);
             }
 
             if view.cursors_visible() {
@@ -586,16 +586,21 @@ struct SelectionLine {
 }
 
 impl Selection {
-    fn paint(&self, scene: &mut Scene) {
+    fn paint(&self, bounds: RectF, scene: &mut Scene) {
         if self.lines.len() >= 2 && self.lines[0].start_x > self.lines[1].end_x {
-            self.paint_lines(self.start_y, &self.lines[0..1], scene);
-            self.paint_lines(self.start_y + self.line_height, &self.lines[1..], scene);
+            self.paint_lines(self.start_y, &self.lines[0..1], bounds, scene);
+            self.paint_lines(
+                self.start_y + self.line_height,
+                &self.lines[1..],
+                bounds,
+                scene,
+            );
         } else {
-            self.paint_lines(self.start_y, &self.lines, scene);
+            self.paint_lines(self.start_y, &self.lines, bounds, scene);
         }
     }
 
-    fn paint_lines(&self, start_y: f32, lines: &[SelectionLine], scene: &mut Scene) {
+    fn paint_lines(&self, start_y: f32, lines: &[SelectionLine], bounds: RectF, scene: &mut Scene) {
         if lines.is_empty() {
             return;
         }
@@ -675,7 +680,7 @@ impl Selection {
         path.curve_to(first_top_left + top_curve_width, first_top_left);
         path.line_to(first_top_right - top_curve_width);
 
-        scene.push_path(path.build(ColorF::new(0.639, 0.839, 1.0, 1.0).to_u8()));
+        scene.push_path(path.build(ColorF::new(0.639, 0.839, 1.0, 1.0).to_u8(), Some(bounds)));
     }
 }