From c0008cf0f8cffafe00ca186c6697e91c1563ab8b Mon Sep 17 00:00:00 2001 From: Cole Miller Date: Mon, 2 Mar 2026 08:38:50 -0500 Subject: [PATCH] editor: Fix edit prediction popovers painting outside the editor's bounds (#50361) Deferred draws previously didn't use a content mask, so you could horizontally scroll an EP popover all the way out of the containing pane. This also affects other UI elements that use the deferred draw system; I think in practice it doesn't make much difference because most of those seem to require something in the editor to be hovered, so if you scroll horizontally the element goes away. Release Notes: - Fixed being able to scroll the edit prediction popover out of the containing pane. --- crates/editor/src/editor.rs | 9 +++++++- crates/editor/src/element.rs | 16 +++++++------- crates/gpui/src/elements/deferred.rs | 2 +- crates/gpui/src/window.rs | 31 ++++++++++++++++++++-------- 4 files changed, 39 insertions(+), 19 deletions(-) diff --git a/crates/editor/src/editor.rs b/crates/editor/src/editor.rs index daeb355b048d649d638a8830bdf3d367ea9cd40b..a6d9e593cc4b2d8d593f48a7887e6308ff0e63cb 100644 --- a/crates/editor/src/editor.rs +++ b/crates/editor/src/editor.rs @@ -9893,7 +9893,14 @@ impl Editor { origin.x -= BORDER_WIDTH; - window.defer_draw(element, origin, 1); + window.with_content_mask( + Some(gpui::ContentMask { + bounds: *text_bounds, + }), + |window| { + window.defer_draw(element, origin, 1, Some(window.content_mask())); + }, + ); // Do not return an element, since it will already be drawn due to defer_draw. None diff --git a/crates/editor/src/element.rs b/crates/editor/src/element.rs index 4779784ad75fbbe3740bf63572c2bd8cec06f1da..6fc2627533dde920c021b14d5d172cbef40d7a95 100644 --- a/crates/editor/src/element.rs +++ b/crates/editor/src/element.rs @@ -2863,7 +2863,7 @@ impl EditorElement { } }); - window.defer_draw(element, origin, 2); + window.defer_draw(element, origin, 2, None); } } @@ -5108,7 +5108,7 @@ impl EditorElement { current_position.y -= size.height; } let position = current_position; - window.defer_draw(element, current_position, 1); + window.defer_draw(element, current_position, 1, None); if !y_flipped { current_position.y += size.height + MENU_GAP; } else { @@ -5211,7 +5211,7 @@ impl EditorElement { // Skip drawing if it doesn't fit anywhere. if let Some((aside, position, size)) = positioned_aside { let aside_bounds = Bounds::new(position, size); - window.defer_draw(aside, position, 2); + window.defer_draw(aside, position, 2, None); return Some(aside_bounds); } @@ -5420,7 +5420,7 @@ impl EditorElement { .on_mouse_move(|_, _, cx| cx.stop_propagation()) .into_any_element(); occlusion.layout_as_root(size(width, HOVER_POPOVER_GAP).into(), window, cx); - window.defer_draw(occlusion, origin, 2); + window.defer_draw(occlusion, origin, 2, None); } fn place_popovers_above( @@ -5437,7 +5437,7 @@ impl EditorElement { current_y - size.height, ); - window.defer_draw(popover.element, popover_origin, 2); + window.defer_draw(popover.element, popover_origin, 2, None); if position != itertools::Position::Last { let origin = point(popover_origin.x, popover_origin.y - HOVER_POPOVER_GAP); draw_occluder(size.width, origin, window, cx); @@ -5459,7 +5459,7 @@ impl EditorElement { let size = popover.size; let popover_origin = point(hovered_point.x + popover.horizontal_offset, current_y); - window.defer_draw(popover.element, popover_origin, 2); + window.defer_draw(popover.element, popover_origin, 2, None); if position != itertools::Position::Last { let origin = point(popover_origin.x, popover_origin.y + size.height); draw_occluder(size.width, origin, window, cx); @@ -5561,7 +5561,7 @@ impl EditorElement { let size = popover.size; let popover_origin = point(origin.x, current_y); - window.defer_draw(popover.element, popover_origin, 2); + window.defer_draw(popover.element, popover_origin, 2, None); if position != itertools::Position::Last { let origin = point(popover_origin.x, popover_origin.y + size.height); draw_occluder(size.width, origin, window, cx); @@ -5893,7 +5893,7 @@ impl EditorElement { }) }; - window.defer_draw(element, final_origin, 2); + window.defer_draw(element, final_origin, 2, None); } fn paint_background(&self, layout: &EditorLayout, window: &mut Window, cx: &mut App) { diff --git a/crates/gpui/src/elements/deferred.rs b/crates/gpui/src/elements/deferred.rs index 9498734198dbe58798867ebe7f20138e5667777b..25245fa4b6ea70284658bf0b91b53ca395b750dd 100644 --- a/crates/gpui/src/elements/deferred.rs +++ b/crates/gpui/src/elements/deferred.rs @@ -62,7 +62,7 @@ impl Element for Deferred { ) { let child = self.child.take().unwrap(); let element_offset = window.element_offset(); - window.defer_draw(child, element_offset, self.priority) + window.defer_draw(child, element_offset, self.priority, None) } fn paint( diff --git a/crates/gpui/src/window.rs b/crates/gpui/src/window.rs index df5948cb99e75a1f15d5b9a63cb1c3a5a29fac03..3fcb911d2c58f8968bc6b0c66f26ed2de365dd53 100644 --- a/crates/gpui/src/window.rs +++ b/crates/gpui/src/window.rs @@ -726,6 +726,7 @@ pub(crate) struct DeferredDraw { parent_node: DispatchNodeId, element_id_stack: SmallVec<[ElementId; 32]>, text_style_stack: Vec, + content_mask: Option>, rem_size: Pixels, element: Option, absolute_offset: Point, @@ -2429,15 +2430,18 @@ impl Window { .set_active_node(deferred_draw.parent_node); let prepaint_start = self.prepaint_index(); + let content_mask = deferred_draw.content_mask.clone(); if let Some(element) = deferred_draw.element.as_mut() { self.with_rendered_view(deferred_draw.current_view, |window| { - window.with_rem_size(Some(deferred_draw.rem_size), |window| { - window.with_absolute_element_offset( - deferred_draw.absolute_offset, - |window| { - element.prepaint(window, cx); - }, - ); + window.with_content_mask(content_mask, |window| { + window.with_rem_size(Some(deferred_draw.rem_size), |window| { + window.with_absolute_element_offset( + deferred_draw.absolute_offset, + |window| { + element.prepaint(window, cx); + }, + ); + }); }); }) } else { @@ -2469,10 +2473,13 @@ impl Window { .set_active_node(deferred_draw.parent_node); let paint_start = self.paint_index(); + let content_mask = deferred_draw.content_mask.clone(); if let Some(element) = deferred_draw.element.as_mut() { self.with_rendered_view(deferred_draw.current_view, |window| { - window.with_rem_size(Some(deferred_draw.rem_size), |window| { - element.paint(window, cx); + window.with_content_mask(content_mask, |window| { + window.with_rem_size(Some(deferred_draw.rem_size), |window| { + element.paint(window, cx); + }); }) }) } else { @@ -2536,6 +2543,7 @@ impl Window { parent_node: reused_subtree.refresh_node_id(deferred_draw.parent_node), element_id_stack: deferred_draw.element_id_stack.clone(), text_style_stack: deferred_draw.text_style_stack.clone(), + content_mask: deferred_draw.content_mask.clone(), rem_size: deferred_draw.rem_size, priority: deferred_draw.priority, element: None, @@ -3019,12 +3027,16 @@ impl Window { /// at a later time. The `priority` parameter determines the drawing order relative to other deferred elements, /// with higher values being drawn on top. /// + /// When `content_mask` is provided, the deferred element will be clipped to that region during + /// both prepaint and paint. When `None`, no additional clipping is applied. + /// /// This method should only be called as part of the prepaint phase of element drawing. pub fn defer_draw( &mut self, element: AnyElement, absolute_offset: Point, priority: usize, + content_mask: Option>, ) { self.invalidator.debug_assert_prepaint(); let parent_node = self.next_frame.dispatch_tree.active_node_id().unwrap(); @@ -3033,6 +3045,7 @@ impl Window { parent_node, element_id_stack: self.element_id_stack.clone(), text_style_stack: self.text_style_stack.clone(), + content_mask, rem_size: self.rem_size(), priority, element: Some(element),