diff --git a/crates/gpui/src/app.rs b/crates/gpui/src/app.rs index 108ad28d24a161..17f92efb581f3a 100644 --- a/crates/gpui/src/app.rs +++ b/crates/gpui/src/app.rs @@ -196,7 +196,6 @@ pub struct AppContext { pending_updates: usize, pub(crate) actions: Rc, pub(crate) active_drag: Option, - pub(crate) active_tooltip: Option, pub(crate) next_frame_callbacks: FxHashMap>, pub(crate) frame_consumers: FxHashMap>, pub(crate) background_executor: BackgroundExecutor, @@ -258,7 +257,6 @@ impl AppContext { flushing_effects: false, pending_updates: 0, active_drag: None, - active_tooltip: None, next_frame_callbacks: FxHashMap::default(), frame_consumers: FxHashMap::default(), background_executor: executor, @@ -1268,8 +1266,10 @@ pub struct AnyDrag { pub cursor_offset: Point, } +/// Contains state associated with a tooltip. You'll only need this struct if you're implementing +/// tooltip behavior on a custom element. Otherwise, use [Div::tooltip]. #[derive(Clone)] -pub(crate) struct AnyTooltip { +pub struct AnyTooltip { pub view: AnyView, pub cursor_offset: Point, } diff --git a/crates/gpui/src/elements/div.rs b/crates/gpui/src/elements/div.rs index 082f88b8e67753..74000da0512ffa 100644 --- a/crates/gpui/src/elements/div.rs +++ b/crates/gpui/src/elements/div.rs @@ -1433,8 +1433,8 @@ impl Interactivity { .borrow() .as_ref() { - if active_tooltip.tooltip.is_some() { - cx.active_tooltip = active_tooltip.tooltip.clone() + if let Some(tooltip) = active_tooltip.tooltip.clone() { + cx.set_tooltip(tooltip); } } } diff --git a/crates/gpui/src/key_dispatch.rs b/crates/gpui/src/key_dispatch.rs index 06d502d7780c9c..85a67168e5e1d8 100644 --- a/crates/gpui/src/key_dispatch.rs +++ b/crates/gpui/src/key_dispatch.rs @@ -112,7 +112,7 @@ impl DispatchTree { target.action_listeners = mem::take(&mut source.action_listeners); } - pub fn graft(&mut self, view_id: EntityId, source: &mut Self) -> SmallVec<[EntityId; 8]> { + pub fn reuse_view(&mut self, view_id: EntityId, source: &mut Self) -> SmallVec<[EntityId; 8]> { let view_source_node_id = source .view_node_ids .get(&view_id) diff --git a/crates/gpui/src/window.rs b/crates/gpui/src/window.rs index 8e38992251f5da..2a88a4f3976646 100644 --- a/crates/gpui/src/window.rs +++ b/crates/gpui/src/window.rs @@ -1,10 +1,10 @@ #![deny(missing_docs)] use crate::{ - px, size, transparent_black, Action, AnyDrag, AnyView, AppContext, Arena, AsyncWindowContext, - AvailableSpace, Bounds, BoxShadow, Context, Corners, CursorStyle, DevicePixels, - DispatchActionListener, DispatchNodeId, DispatchTree, DisplayId, Edges, Effect, Entity, - EntityId, EventEmitter, FileDropEvent, Flatten, FontId, GlobalElementId, GlyphId, Hsla, + px, size, transparent_black, Action, AnyDrag, AnyTooltip, AnyView, AppContext, Arena, + AsyncWindowContext, AvailableSpace, Bounds, BoxShadow, Context, Corners, CursorStyle, + DevicePixels, DispatchActionListener, DispatchNodeId, DispatchTree, DisplayId, Edges, Effect, + Entity, EntityId, EventEmitter, FileDropEvent, Flatten, FontId, GlobalElementId, GlyphId, Hsla, ImageData, InputEvent, IsZero, KeyBinding, KeyContext, KeyDownEvent, KeystrokeEvent, LayoutId, Model, ModelContext, Modifiers, MonochromeSprite, MouseButton, MouseMoveEvent, MouseUpEvent, Path, Pixels, PlatformAtlas, PlatformDisplay, PlatformInputHandler, PlatformWindow, Point, @@ -300,6 +300,11 @@ struct RequestedInputHandler { handler: Option>, } +struct TooltipRequest { + view_id: EntityId, + tooltip: AnyTooltip, +} + pub(crate) struct Frame { focus: Option, window_active: bool, @@ -313,6 +318,7 @@ pub(crate) struct Frame { content_mask_stack: Vec>, element_offset_stack: Vec>, requested_input_handler: Option, + tooltip_request: Option, cursor_styles: FxHashMap, requested_cursor_style: Option, pub(crate) view_stack: Vec, @@ -328,12 +334,13 @@ impl Frame { mouse_listeners: FxHashMap::default(), dispatch_tree, scene: Scene::default(), + depth_map: Vec::new(), z_index_stack: StackingOrder::default(), next_stacking_order_id: 0, - depth_map: Vec::new(), content_mask_stack: Vec::new(), element_offset_stack: Vec::new(), requested_input_handler: None, + tooltip_request: None, cursor_styles: FxHashMap::default(), requested_cursor_style: None, view_stack: Vec::new(), @@ -350,6 +357,7 @@ impl Frame { self.reused_views.clear(); self.scene.clear(); self.requested_input_handler.take(); + self.tooltip_request.take(); self.cursor_styles.clear(); self.requested_cursor_style.take(); debug_assert_eq!(self.view_stack.len(), 0); @@ -1052,6 +1060,12 @@ impl<'a> WindowContext<'a> { self.window.next_frame.requested_cursor_style = Some(style); } + /// Set a tooltip to be rendered for the upcoming frame + pub fn set_tooltip(&mut self, tooltip: AnyTooltip) { + let view_id = self.parent_view_id(); + self.window.next_frame.tooltip_request = Some(TooltipRequest { view_id, tooltip }); + } + /// Called during painting to track which z-index is on top at each pixel position pub fn add_opaque_layer(&mut self, bounds: Bounds) { let stacking_order = self.window.next_frame.z_index_stack.clone(); @@ -1432,12 +1446,11 @@ impl<'a> WindowContext<'a> { .window .next_frame .dispatch_tree - .graft(view_id, &mut self.window.rendered_frame.dispatch_tree); + .reuse_view(view_id, &mut self.window.rendered_frame.dispatch_tree); for view_id in grafted_view_ids { assert!(self.window.next_frame.reused_views.insert(view_id)); - // Reuse the previous input handler if it was associated with one of - // the views grafted from the tree in the previous frame. + // Reuse the previous input handler requested during painting of the reused view. if self .window .rendered_frame @@ -1449,6 +1462,19 @@ impl<'a> WindowContext<'a> { self.window.rendered_frame.requested_input_handler.take(); } + // Reuse the tooltip previously requested during painting of the reused view. + if self + .window + .rendered_frame + .tooltip_request + .as_ref() + .map_or(false, |requested| requested.view_id == view_id) + { + self.window.next_frame.tooltip_request = + self.window.rendered_frame.tooltip_request.take(); + } + + // Reuse the cursor styles previously requested during painting of the reused view. if let Some(style) = self.window.rendered_frame.cursor_styles.remove(&view_id) { self.window.next_frame.cursor_styles.insert(view_id, style); self.window.next_frame.requested_cursor_style = Some(style); @@ -1498,13 +1524,16 @@ impl<'a> WindowContext<'a> { active_drag.view.draw(offset, available_space, cx); }); self.active_drag = Some(active_drag); - } else if let Some(active_tooltip) = self.app.active_tooltip.take() { + } else if let Some(tooltip_request) = self.window.next_frame.tooltip_request.take() { self.with_z_index(1, |cx| { let available_space = size(AvailableSpace::MinContent, AvailableSpace::MinContent); - active_tooltip - .view - .draw(active_tooltip.cursor_offset, available_space, cx); + tooltip_request.tooltip.view.draw( + tooltip_request.tooltip.cursor_offset, + available_space, + cx, + ); }); + self.window.next_frame.tooltip_request = Some(tooltip_request); } self.window.dirty_views.clear(); @@ -2145,7 +2174,8 @@ impl<'a> WindowContext<'a> { /// Set an input handler, such as [`ElementInputHandler`][element_input_handler], which interfaces with the /// platform to receive textual input with proper integration with concerns such - /// as IME interactions. + /// as IME interactions. This handler will be active for the upcoming frame until the following frame is + /// rendered. /// /// [element_input_handler]: crate::ElementInputHandler pub fn handle_input(