From 904fe6368ae4e021ba4202e08284b2ee51e20c4e 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 daeb355b04..a6d9e593cc 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 4779784ad7..6fc2627533 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 9498734198..25245fa4b6 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 df5948cb99..3fcb911d2c 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),