From 39fb1d567d559eb8acfa1c060c885f8debe8a8fa Mon Sep 17 00:00:00 2001 From: Nathan Sobo Date: Sun, 28 Apr 2024 12:59:21 -0700 Subject: [PATCH] Incorporate ElementId as part of the Element::id trait method and expose GlobalId (#11101) We're planning to associate "selection sources" with global element ids to allow arbitrary UI text to be selected in GPUI. Previously, global ids were not exposed outside the framework and we entangled management of the element id stack with element state access. This was more acceptable when element state was the only place we used global element ids, but now that we're planning to use them more places, it makes sense to deal with element identity as a first-class part of the element system. We now ensure that the stack of element ids which forms the current global element id is correctly managed in every phase of element layout and paint and make the global id available to each element method. In a subsequent PR, we'll use the global element id as part of implementing arbitrary selection for UI text. Release Notes: - N/A --------- Co-authored-by: Antonio Scandurra --- crates/editor/src/element.rs | 62 +++-- crates/gpui/src/assets.rs | 2 +- crates/gpui/src/element.rs | 136 ++++++++-- crates/gpui/src/elements/anchored.rs | 11 +- crates/gpui/src/elements/animation.rs | 16 +- crates/gpui/src/elements/canvas.rs | 12 +- crates/gpui/src/elements/deferred.rs | 16 +- crates/gpui/src/elements/div.rs | 123 +++++++--- crates/gpui/src/elements/img.rs | 63 +++-- crates/gpui/src/elements/list.rs | 11 +- crates/gpui/src/elements/svg.rs | 24 +- crates/gpui/src/elements/text.rs | 75 ++++-- crates/gpui/src/elements/uniform_list.rs | 59 +++-- crates/gpui/src/view.rs | 166 ++++++------- crates/gpui/src/window.rs | 245 ++++++++++--------- crates/terminal_view/src/terminal_element.rs | 44 ++-- crates/ui/src/components/popover_menu.rs | 162 ++++++------ crates/ui/src/components/right_click_menu.rs | 49 ++-- crates/workspace/src/pane_group.rs | 21 +- crates/workspace/src/toolbar.rs | 6 +- crates/workspace/src/workspace.rs | 19 +- 21 files changed, 825 insertions(+), 497 deletions(-) diff --git a/crates/editor/src/element.rs b/crates/editor/src/element.rs index 1b9b88e2e5..56c6ba395b 100644 --- a/crates/editor/src/element.rs +++ b/crates/editor/src/element.rs @@ -25,10 +25,11 @@ use gpui::{ anchored, deferred, div, fill, outline, point, px, quad, relative, size, svg, transparent_black, Action, AnchorCorner, AnyElement, AvailableSpace, Bounds, ClipboardItem, ContentMask, Corners, CursorStyle, DispatchPhase, Edges, Element, ElementInputHandler, Entity, - Hitbox, Hsla, InteractiveElement, IntoElement, ModifiersChangedEvent, MouseButton, - MouseDownEvent, MouseMoveEvent, MouseUpEvent, PaintQuad, ParentElement, Pixels, ScrollDelta, - ScrollWheelEvent, ShapedLine, SharedString, Size, Stateful, StatefulInteractiveElement, Style, - Styled, TextRun, TextStyle, TextStyleRefinement, View, ViewContext, WeakView, WindowContext, + GlobalElementId, Hitbox, Hsla, InteractiveElement, IntoElement, ModifiersChangedEvent, + MouseButton, MouseDownEvent, MouseMoveEvent, MouseUpEvent, PaintQuad, ParentElement, Pixels, + ScrollDelta, ScrollWheelEvent, ShapedLine, SharedString, Size, Stateful, + StatefulInteractiveElement, Style, Styled, TextRun, TextStyle, TextStyleRefinement, View, + ViewContext, WeakView, WindowContext, }; use itertools::Itertools; use language::language_settings::ShowWhitespaceSetting; @@ -2270,7 +2271,7 @@ impl EditorElement { } cx.paint_layer(layout.gutter_hitbox.bounds, |cx| { - cx.with_element_id(Some("gutter_fold_indicators"), |cx| { + cx.with_element_namespace("gutter_fold_indicators", |cx| { for fold_indicator in layout.fold_indicators.iter_mut().flatten() { fold_indicator.paint(cx); } @@ -2419,7 +2420,7 @@ impl EditorElement { }; cx.set_cursor_style(cursor_style, &layout.text_hitbox); - cx.with_element_id(Some("folds"), |cx| self.paint_folds(layout, cx)); + cx.with_element_namespace("folds", |cx| self.paint_folds(layout, cx)); let invisible_display_ranges = self.paint_highlights(layout, cx); self.paint_lines(&invisible_display_ranges, layout, cx); self.paint_redactions(layout, cx); @@ -3446,7 +3447,15 @@ impl Element for EditorElement { type RequestLayoutState = (); type PrepaintState = EditorLayout; - fn request_layout(&mut self, cx: &mut WindowContext) -> (gpui::LayoutId, ()) { + fn id(&self) -> Option { + None + } + + fn request_layout( + &mut self, + _: Option<&GlobalElementId>, + cx: &mut WindowContext, + ) -> (gpui::LayoutId, ()) { self.editor.update(cx, |editor, cx| { editor.set_style(self.style.clone(), cx); @@ -3490,6 +3499,7 @@ impl Element for EditorElement { fn prepaint( &mut self, + _: Option<&GlobalElementId>, bounds: Bounds, _: &mut Self::RequestLayoutState, cx: &mut WindowContext, @@ -3666,19 +3676,22 @@ impl Element for EditorElement { .width; let mut scroll_width = longest_line_width.max(max_visible_line_width) + overscroll.width; - let mut blocks = self.build_blocks( - start_row..end_row, - &snapshot, - &hitbox, - &text_hitbox, - &mut scroll_width, - &gutter_dimensions, - em_width, - gutter_dimensions.width + gutter_dimensions.margin, - line_height, - &line_layouts, - cx, - ); + + let mut blocks = cx.with_element_namespace("blocks", |cx| { + self.build_blocks( + start_row..end_row, + &snapshot, + &hitbox, + &text_hitbox, + &mut scroll_width, + &gutter_dimensions, + em_width, + gutter_dimensions.width + gutter_dimensions.margin, + line_height, + &line_layouts, + cx, + ) + }); let scroll_pixel_position = point( scroll_position.x * em_width, @@ -3740,7 +3753,7 @@ impl Element for EditorElement { } }); - cx.with_element_id(Some("blocks"), |cx| { + cx.with_element_namespace("blocks", |cx| { self.layout_blocks( &mut blocks, &hitbox, @@ -3776,7 +3789,7 @@ impl Element for EditorElement { cx, ); - let folds = cx.with_element_id(Some("folds"), |cx| { + let folds = cx.with_element_namespace("folds", |cx| { self.layout_folds( &snapshot, content_origin, @@ -3837,7 +3850,7 @@ impl Element for EditorElement { let mouse_context_menu = self.layout_mouse_context_menu(cx); let fold_indicators = if gutter_settings.folds { - cx.with_element_id(Some("gutter_fold_indicators"), |cx| { + cx.with_element_namespace("gutter_fold_indicators", |cx| { self.layout_gutter_fold_indicators( fold_statuses, line_height, @@ -3930,6 +3943,7 @@ impl Element for EditorElement { fn paint( &mut self, + _: Option<&GlobalElementId>, bounds: Bounds, _: &mut Self::RequestLayoutState, layout: &mut Self::PrepaintState, @@ -3962,7 +3976,7 @@ impl Element for EditorElement { self.paint_text(layout, cx); if !layout.blocks.is_empty() { - cx.with_element_id(Some("blocks"), |cx| { + cx.with_element_namespace("blocks", |cx| { self.paint_blocks(layout, cx); }); } diff --git a/crates/gpui/src/assets.rs b/crates/gpui/src/assets.rs index 987c964725..dd7485a30a 100644 --- a/crates/gpui/src/assets.rs +++ b/crates/gpui/src/assets.rs @@ -20,7 +20,7 @@ pub trait AssetSource: 'static + Send + Sync { impl AssetSource for () { fn load(&self, path: &str) -> Result> { Err(anyhow!( - "get called on empty asset provider with \"{}\"", + "load called on empty asset provider with \"{}\"", path )) } diff --git a/crates/gpui/src/element.rs b/crates/gpui/src/element.rs index c4079214dc..427e6d61ef 100644 --- a/crates/gpui/src/element.rs +++ b/crates/gpui/src/element.rs @@ -52,14 +52,26 @@ pub trait Element: 'static + IntoElement { /// provided to [`Element::paint`]. type PrepaintState: 'static; + /// If this element has a unique identifier, return it here. This is used to track elements across frames, and + /// will cause a GlobalElementId to be passed to the request_layout, prepaint, and paint methods. + /// + /// The global id can in turn be used to access state that's connected to an element with the same id across + /// frames. This id must be unique among children of the first containing element with an id. + fn id(&self) -> Option; + /// Before an element can be painted, we need to know where it's going to be and how big it is. /// Use this method to request a layout from Taffy and initialize the element's state. - fn request_layout(&mut self, cx: &mut WindowContext) -> (LayoutId, Self::RequestLayoutState); + fn request_layout( + &mut self, + id: Option<&GlobalElementId>, + cx: &mut WindowContext, + ) -> (LayoutId, Self::RequestLayoutState); /// After laying out an element, we need to commit its bounds to the current frame for hitbox /// purposes. The state argument is the same state that was returned from [`Element::request_layout()`]. fn prepaint( &mut self, + id: Option<&GlobalElementId>, bounds: Bounds, request_layout: &mut Self::RequestLayoutState, cx: &mut WindowContext, @@ -69,6 +81,7 @@ pub trait Element: 'static + IntoElement { /// The state argument is the same state that was returned from [`Element::request_layout()`]. fn paint( &mut self, + id: Option<&GlobalElementId>, bounds: Bounds, request_layout: &mut Self::RequestLayoutState, prepaint: &mut Self::PrepaintState, @@ -164,18 +177,33 @@ impl Element for Component { type RequestLayoutState = AnyElement; type PrepaintState = (); - fn request_layout(&mut self, cx: &mut WindowContext) -> (LayoutId, Self::RequestLayoutState) { + fn id(&self) -> Option { + None + } + + fn request_layout( + &mut self, + _id: Option<&GlobalElementId>, + cx: &mut WindowContext, + ) -> (LayoutId, Self::RequestLayoutState) { let mut element = self.0.take().unwrap().render(cx).into_any_element(); let layout_id = element.request_layout(cx); (layout_id, element) } - fn prepaint(&mut self, _: Bounds, element: &mut AnyElement, cx: &mut WindowContext) { + fn prepaint( + &mut self, + _id: Option<&GlobalElementId>, + _: Bounds, + element: &mut AnyElement, + cx: &mut WindowContext, + ) { element.prepaint(cx); } fn paint( &mut self, + _id: Option<&GlobalElementId>, _: Bounds, element: &mut Self::RequestLayoutState, _: &mut Self::PrepaintState, @@ -194,8 +222,8 @@ impl IntoElement for Component { } /// A globally unique identifier for an element, used to track state across frames. -#[derive(Deref, DerefMut, Default, Clone, Debug, Eq, PartialEq, Hash)] -pub(crate) struct GlobalElementId(SmallVec<[ElementId; 32]>); +#[derive(Deref, DerefMut, Default, Debug, Eq, PartialEq, Hash)] +pub struct GlobalElementId(pub(crate) SmallVec<[ElementId; 32]>); trait ElementObject { fn inner_element(&mut self) -> &mut dyn Any; @@ -224,17 +252,20 @@ pub struct Drawable { enum ElementDrawPhase { #[default] Start, - RequestLayoutState { + RequestLayout { layout_id: LayoutId, + global_id: Option, request_layout: RequestLayoutState, }, LayoutComputed { layout_id: LayoutId, + global_id: Option, available_space: Size, request_layout: RequestLayoutState, }, - PrepaintState { + Prepaint { node_id: DispatchNodeId, + global_id: Option, bounds: Bounds, request_layout: RequestLayoutState, prepaint: PrepaintState, @@ -254,9 +285,21 @@ impl Drawable { fn request_layout(&mut self, cx: &mut WindowContext) -> LayoutId { match mem::take(&mut self.phase) { ElementDrawPhase::Start => { - let (layout_id, request_layout) = self.element.request_layout(cx); - self.phase = ElementDrawPhase::RequestLayoutState { + let global_id = self.element.id().map(|element_id| { + cx.window.element_id_stack.push(element_id); + GlobalElementId(cx.window.element_id_stack.clone()) + }); + + let (layout_id, request_layout) = + self.element.request_layout(global_id.as_ref(), cx); + + if global_id.is_some() { + cx.window.element_id_stack.pop(); + } + + self.phase = ElementDrawPhase::RequestLayout { layout_id, + global_id, request_layout, }; layout_id @@ -267,25 +310,40 @@ impl Drawable { pub(crate) fn prepaint(&mut self, cx: &mut WindowContext) { match mem::take(&mut self.phase) { - ElementDrawPhase::RequestLayoutState { + ElementDrawPhase::RequestLayout { layout_id, + global_id, mut request_layout, } | ElementDrawPhase::LayoutComputed { layout_id, + global_id, mut request_layout, .. } => { + if let Some(element_id) = self.element.id() { + cx.window.element_id_stack.push(element_id); + debug_assert_eq!(global_id.as_ref().unwrap().0, cx.window.element_id_stack); + } + let bounds = cx.layout_bounds(layout_id); let node_id = cx.window.next_frame.dispatch_tree.push_node(); - let prepaint = self.element.prepaint(bounds, &mut request_layout, cx); - self.phase = ElementDrawPhase::PrepaintState { + let prepaint = + self.element + .prepaint(global_id.as_ref(), bounds, &mut request_layout, cx); + cx.window.next_frame.dispatch_tree.pop_node(); + + if global_id.is_some() { + cx.window.element_id_stack.pop(); + } + + self.phase = ElementDrawPhase::Prepaint { node_id, + global_id, bounds, request_layout, prepaint, }; - cx.window.next_frame.dispatch_tree.pop_node(); } _ => panic!("must call request_layout before prepaint"), } @@ -296,16 +354,32 @@ impl Drawable { cx: &mut WindowContext, ) -> (E::RequestLayoutState, E::PrepaintState) { match mem::take(&mut self.phase) { - ElementDrawPhase::PrepaintState { + ElementDrawPhase::Prepaint { node_id, + global_id, bounds, mut request_layout, mut prepaint, .. } => { + if let Some(element_id) = self.element.id() { + cx.window.element_id_stack.push(element_id); + debug_assert_eq!(global_id.as_ref().unwrap().0, cx.window.element_id_stack); + } + cx.window.next_frame.dispatch_tree.set_active_node(node_id); - self.element - .paint(bounds, &mut request_layout, &mut prepaint, cx); + self.element.paint( + global_id.as_ref(), + bounds, + &mut request_layout, + &mut prepaint, + cx, + ); + + if global_id.is_some() { + cx.window.element_id_stack.pop(); + } + self.phase = ElementDrawPhase::Painted; (request_layout, prepaint) } @@ -323,13 +397,15 @@ impl Drawable { } let layout_id = match mem::take(&mut self.phase) { - ElementDrawPhase::RequestLayoutState { + ElementDrawPhase::RequestLayout { layout_id, + global_id, request_layout, } => { cx.compute_layout(layout_id, available_space); self.phase = ElementDrawPhase::LayoutComputed { layout_id, + global_id, available_space, request_layout, }; @@ -337,6 +413,7 @@ impl Drawable { } ElementDrawPhase::LayoutComputed { layout_id, + global_id, available_space: prev_available_space, request_layout, } => { @@ -345,6 +422,7 @@ impl Drawable { } self.phase = ElementDrawPhase::LayoutComputed { layout_id, + global_id, available_space, request_layout, }; @@ -454,13 +532,22 @@ impl Element for AnyElement { type RequestLayoutState = (); type PrepaintState = (); - fn request_layout(&mut self, cx: &mut WindowContext) -> (LayoutId, Self::RequestLayoutState) { + fn id(&self) -> Option { + None + } + + fn request_layout( + &mut self, + _: Option<&GlobalElementId>, + cx: &mut WindowContext, + ) -> (LayoutId, Self::RequestLayoutState) { let layout_id = self.request_layout(cx); (layout_id, ()) } fn prepaint( &mut self, + _: Option<&GlobalElementId>, _: Bounds, _: &mut Self::RequestLayoutState, cx: &mut WindowContext, @@ -470,6 +557,7 @@ impl Element for AnyElement { fn paint( &mut self, + _: Option<&GlobalElementId>, _: Bounds, _: &mut Self::RequestLayoutState, _: &mut Self::PrepaintState, @@ -506,12 +594,21 @@ impl Element for Empty { type RequestLayoutState = (); type PrepaintState = (); - fn request_layout(&mut self, cx: &mut WindowContext) -> (LayoutId, Self::RequestLayoutState) { + fn id(&self) -> Option { + None + } + + fn request_layout( + &mut self, + _id: Option<&GlobalElementId>, + cx: &mut WindowContext, + ) -> (LayoutId, Self::RequestLayoutState) { (cx.request_layout(&Style::default(), None), ()) } fn prepaint( &mut self, + _id: Option<&GlobalElementId>, _bounds: Bounds, _state: &mut Self::RequestLayoutState, _cx: &mut WindowContext, @@ -520,6 +617,7 @@ impl Element for Empty { fn paint( &mut self, + _id: Option<&GlobalElementId>, _bounds: Bounds, _request_layout: &mut Self::RequestLayoutState, _prepaint: &mut Self::PrepaintState, diff --git a/crates/gpui/src/elements/anchored.rs b/crates/gpui/src/elements/anchored.rs index 15421f4ab3..27b86849f4 100644 --- a/crates/gpui/src/elements/anchored.rs +++ b/crates/gpui/src/elements/anchored.rs @@ -2,8 +2,8 @@ use smallvec::SmallVec; use taffy::style::{Display, Position}; use crate::{ - point, AnyElement, Bounds, Element, IntoElement, LayoutId, ParentElement, Pixels, Point, Size, - Style, WindowContext, + point, AnyElement, Bounds, Element, GlobalElementId, IntoElement, LayoutId, ParentElement, + Pixels, Point, Size, Style, WindowContext, }; /// The state that the anchored element element uses to track its children. @@ -72,8 +72,13 @@ impl Element for Anchored { type RequestLayoutState = AnchoredState; type PrepaintState = (); + fn id(&self) -> Option { + None + } + fn request_layout( &mut self, + _id: Option<&GlobalElementId>, cx: &mut WindowContext, ) -> (crate::LayoutId, Self::RequestLayoutState) { let child_layout_ids = self @@ -95,6 +100,7 @@ impl Element for Anchored { fn prepaint( &mut self, + _id: Option<&GlobalElementId>, bounds: Bounds, request_layout: &mut Self::RequestLayoutState, cx: &mut WindowContext, @@ -177,6 +183,7 @@ impl Element for Anchored { fn paint( &mut self, + _id: Option<&GlobalElementId>, _bounds: crate::Bounds, _request_layout: &mut Self::RequestLayoutState, _prepaint: &mut Self::PrepaintState, diff --git a/crates/gpui/src/elements/animation.rs b/crates/gpui/src/elements/animation.rs index f18ff3fcb8..34806d9623 100644 --- a/crates/gpui/src/elements/animation.rs +++ b/crates/gpui/src/elements/animation.rs @@ -1,6 +1,6 @@ use std::time::{Duration, Instant}; -use crate::{AnyElement, Element, ElementId, IntoElement}; +use crate::{AnyElement, Element, ElementId, GlobalElementId, IntoElement}; pub use easing::*; @@ -86,15 +86,19 @@ struct AnimationState { impl Element for AnimationElement { type RequestLayoutState = AnyElement; - type PrepaintState = (); + fn id(&self) -> Option { + Some(self.id.clone()) + } + fn request_layout( &mut self, + global_id: Option<&GlobalElementId>, cx: &mut crate::WindowContext, ) -> (crate::LayoutId, Self::RequestLayoutState) { - cx.with_element_state(Some(self.id.clone()), |state, cx| { - let state = state.unwrap().unwrap_or_else(|| AnimationState { + cx.with_element_state(global_id.unwrap(), |state, cx| { + let state = state.unwrap_or_else(|| AnimationState { start: Instant::now(), }); let mut delta = @@ -130,12 +134,13 @@ impl Element for AnimationElement { }) } - ((element.request_layout(cx), element), Some(state)) + ((element.request_layout(cx), element), state) }) } fn prepaint( &mut self, + _id: Option<&GlobalElementId>, _bounds: crate::Bounds, element: &mut Self::RequestLayoutState, cx: &mut crate::WindowContext, @@ -145,6 +150,7 @@ impl Element for AnimationElement { fn paint( &mut self, + _id: Option<&GlobalElementId>, _bounds: crate::Bounds, element: &mut Self::RequestLayoutState, _: &mut Self::PrepaintState, diff --git a/crates/gpui/src/elements/canvas.rs b/crates/gpui/src/elements/canvas.rs index 989ea76da5..f98a962094 100644 --- a/crates/gpui/src/elements/canvas.rs +++ b/crates/gpui/src/elements/canvas.rs @@ -1,6 +1,9 @@ use refineable::Refineable as _; -use crate::{Bounds, Element, IntoElement, Pixels, Style, StyleRefinement, Styled, WindowContext}; +use crate::{ + Bounds, Element, ElementId, GlobalElementId, IntoElement, Pixels, Style, StyleRefinement, + Styled, WindowContext, +}; /// Construct a canvas element with the given paint callback. /// Useful for adding short term custom drawing to a view. @@ -35,8 +38,13 @@ impl Element for Canvas { type RequestLayoutState = Style; type PrepaintState = Option; + fn id(&self) -> Option { + None + } + fn request_layout( &mut self, + _id: Option<&GlobalElementId>, cx: &mut WindowContext, ) -> (crate::LayoutId, Self::RequestLayoutState) { let mut style = Style::default(); @@ -47,6 +55,7 @@ impl Element for Canvas { fn prepaint( &mut self, + _id: Option<&GlobalElementId>, bounds: Bounds, _request_layout: &mut Style, cx: &mut WindowContext, @@ -56,6 +65,7 @@ impl Element for Canvas { fn paint( &mut self, + _id: Option<&GlobalElementId>, bounds: Bounds, style: &mut Style, prepaint: &mut Self::PrepaintState, diff --git a/crates/gpui/src/elements/deferred.rs b/crates/gpui/src/elements/deferred.rs index 9bf365ae0d..b878897750 100644 --- a/crates/gpui/src/elements/deferred.rs +++ b/crates/gpui/src/elements/deferred.rs @@ -1,4 +1,6 @@ -use crate::{AnyElement, Bounds, Element, IntoElement, LayoutId, Pixels, WindowContext}; +use crate::{ + AnyElement, Bounds, Element, GlobalElementId, IntoElement, LayoutId, Pixels, WindowContext, +}; /// Builds a `Deferred` element, which delays the layout and paint of its child. pub fn deferred(child: impl IntoElement) -> Deferred { @@ -29,13 +31,22 @@ impl Element for Deferred { type RequestLayoutState = (); type PrepaintState = (); - fn request_layout(&mut self, cx: &mut WindowContext) -> (LayoutId, ()) { + fn id(&self) -> Option { + None + } + + fn request_layout( + &mut self, + _id: Option<&GlobalElementId>, + cx: &mut WindowContext, + ) -> (LayoutId, ()) { let layout_id = self.child.as_mut().unwrap().request_layout(cx); (layout_id, ()) } fn prepaint( &mut self, + _id: Option<&GlobalElementId>, _bounds: Bounds, _request_layout: &mut Self::RequestLayoutState, cx: &mut WindowContext, @@ -47,6 +58,7 @@ impl Element for Deferred { fn paint( &mut self, + _id: Option<&GlobalElementId>, _bounds: Bounds, _request_layout: &mut Self::RequestLayoutState, _prepaint: &mut Self::PrepaintState, diff --git a/crates/gpui/src/elements/div.rs b/crates/gpui/src/elements/div.rs index 981b86aadc..989bab7c67 100644 --- a/crates/gpui/src/elements/div.rs +++ b/crates/gpui/src/elements/div.rs @@ -17,11 +17,11 @@ use crate::{ point, px, size, Action, AnyDrag, AnyElement, AnyTooltip, AnyView, AppContext, Bounds, - ClickEvent, DispatchPhase, Element, ElementId, FocusHandle, Global, Hitbox, HitboxId, - IntoElement, IsZero, KeyContext, KeyDownEvent, KeyUpEvent, LayoutId, ModifiersChangedEvent, - MouseButton, MouseDownEvent, MouseMoveEvent, MouseUpEvent, ParentElement, Pixels, Point, - Render, ScrollWheelEvent, SharedString, Size, Style, StyleRefinement, Styled, Task, TooltipId, - View, Visibility, WindowContext, + ClickEvent, DispatchPhase, Element, ElementId, FocusHandle, Global, GlobalElementId, Hitbox, + HitboxId, IntoElement, IsZero, KeyContext, KeyDownEvent, KeyUpEvent, LayoutId, + ModifiersChangedEvent, MouseButton, MouseDownEvent, MouseMoveEvent, MouseUpEvent, + ParentElement, Pixels, Point, Render, ScrollWheelEvent, SharedString, Size, Style, + StyleRefinement, Styled, Task, TooltipId, View, Visibility, WindowContext, }; use collections::HashMap; use refineable::Refineable; @@ -1123,23 +1123,34 @@ impl Element for Div { type RequestLayoutState = DivFrameState; type PrepaintState = Option; - fn request_layout(&mut self, cx: &mut WindowContext) -> (LayoutId, Self::RequestLayoutState) { + fn id(&self) -> Option { + self.interactivity.element_id.clone() + } + + fn request_layout( + &mut self, + global_id: Option<&GlobalElementId>, + cx: &mut WindowContext, + ) -> (LayoutId, Self::RequestLayoutState) { let mut child_layout_ids = SmallVec::new(); - let layout_id = self.interactivity.request_layout(cx, |style, cx| { - cx.with_text_style(style.text_style().cloned(), |cx| { - child_layout_ids = self - .children - .iter_mut() - .map(|child| child.request_layout(cx)) - .collect::>(); - cx.request_layout(&style, child_layout_ids.iter().copied()) - }) - }); + let layout_id = self + .interactivity + .request_layout(global_id, cx, |style, cx| { + cx.with_text_style(style.text_style().cloned(), |cx| { + child_layout_ids = self + .children + .iter_mut() + .map(|child| child.request_layout(cx)) + .collect::>(); + cx.request_layout(&style, child_layout_ids.iter().copied()) + }) + }); (layout_id, DivFrameState { child_layout_ids }) } fn prepaint( &mut self, + global_id: Option<&GlobalElementId>, bounds: Bounds, request_layout: &mut Self::RequestLayoutState, cx: &mut WindowContext, @@ -1178,6 +1189,7 @@ impl Element for Div { }; self.interactivity.prepaint( + global_id, bounds, content_size, cx, @@ -1194,13 +1206,14 @@ impl Element for Div { fn paint( &mut self, + global_id: Option<&GlobalElementId>, bounds: Bounds, _request_layout: &mut Self::RequestLayoutState, hitbox: &mut Option, cx: &mut WindowContext, ) { self.interactivity - .paint(bounds, hitbox.as_ref(), cx, |_style, cx| { + .paint(global_id, bounds, hitbox.as_ref(), cx, |_style, cx| { for child in &mut self.children { child.paint(cx); } @@ -1220,7 +1233,7 @@ impl IntoElement for Div { /// interactivity in the `Div` element. #[derive(Default)] pub struct Interactivity { - /// The element ID of the element + /// The element ID of the element. In id is required to support a stateful subset of the interactivity such as on_click. pub element_id: Option, /// Whether the element was clicked. This will only be present after layout. pub active: Option, @@ -1276,11 +1289,12 @@ impl Interactivity { /// Layout this element according to this interactivity state's configured styles pub fn request_layout( &mut self, + global_id: Option<&GlobalElementId>, cx: &mut WindowContext, f: impl FnOnce(Style, &mut WindowContext) -> LayoutId, ) -> LayoutId { - cx.with_element_state::( - self.element_id.clone(), + cx.with_optional_element_state::( + global_id, |element_state, cx| { let mut element_state = element_state.map(|element_state| element_state.unwrap_or_default()); @@ -1339,14 +1353,15 @@ impl Interactivity { /// Commit the bounds of this element according to this interactivity state's configured styles. pub fn prepaint( &mut self, + global_id: Option<&GlobalElementId>, bounds: Bounds, content_size: Size, cx: &mut WindowContext, f: impl FnOnce(&Style, Point, Option, &mut WindowContext) -> R, ) -> R { self.content_size = content_size; - cx.with_element_state::( - self.element_id.clone(), + cx.with_optional_element_state::( + global_id, |element_state, cx| { let mut element_state = element_state.map(|element_state| element_state.unwrap_or_default()); @@ -1454,14 +1469,15 @@ impl Interactivity { /// with the current scroll offset pub fn paint( &mut self, + global_id: Option<&GlobalElementId>, bounds: Bounds, hitbox: Option<&Hitbox>, cx: &mut WindowContext, f: impl FnOnce(&Style, &mut WindowContext), ) { self.hovered = hitbox.map(|hitbox| hitbox.is_hovered(cx)); - cx.with_element_state::( - self.element_id.clone(), + cx.with_optional_element_state::( + global_id, |element_state, cx| { let mut element_state = element_state.map(|element_state| element_state.unwrap_or_default()); @@ -1487,7 +1503,7 @@ impl Interactivity { cx.with_content_mask(style.overflow_mask(bounds, cx.rem_size()), |cx| { if let Some(hitbox) = hitbox { #[cfg(debug_assertions)] - self.paint_debug_info(hitbox, &style, cx); + self.paint_debug_info(global_id, hitbox, &style, cx); if !cx.has_active_drag() { if let Some(mouse_cursor) = style.mouse_cursor { @@ -1521,13 +1537,19 @@ impl Interactivity { } #[cfg(debug_assertions)] - fn paint_debug_info(&mut self, hitbox: &Hitbox, style: &Style, cx: &mut WindowContext) { - if self.element_id.is_some() + fn paint_debug_info( + &mut self, + global_id: Option<&GlobalElementId>, + hitbox: &Hitbox, + style: &Style, + cx: &mut WindowContext, + ) { + if global_id.is_some() && (style.debug || style.debug_below || cx.has_global::()) && hitbox.is_hovered(cx) { const FONT_SIZE: crate::Pixels = crate::Pixels(10.); - let element_id = format!("{:?}", self.element_id.as_ref().unwrap()); + let element_id = format!("{:?}", global_id.unwrap()); let str_len = element_id.len(); let render_debug_text = |cx: &mut WindowContext| { @@ -2064,8 +2086,13 @@ impl Interactivity { } /// Compute the visual style for this element, based on the current bounds and the element's state. - pub fn compute_style(&self, hitbox: Option<&Hitbox>, cx: &mut WindowContext) -> Style { - cx.with_element_state(self.element_id.clone(), |element_state, cx| { + pub fn compute_style( + &self, + global_id: Option<&GlobalElementId>, + hitbox: Option<&Hitbox>, + cx: &mut WindowContext, + ) -> Style { + cx.with_optional_element_state(global_id, |element_state, cx| { let mut element_state = element_state.map(|element_state| element_state.unwrap_or_default()); let style = self.compute_style_internal(hitbox, element_state.as_mut(), cx); @@ -2264,27 +2291,37 @@ where type RequestLayoutState = E::RequestLayoutState; type PrepaintState = E::PrepaintState; - fn request_layout(&mut self, cx: &mut WindowContext) -> (LayoutId, Self::RequestLayoutState) { - self.element.request_layout(cx) + fn id(&self) -> Option { + self.element.id() + } + + fn request_layout( + &mut self, + id: Option<&GlobalElementId>, + cx: &mut WindowContext, + ) -> (LayoutId, Self::RequestLayoutState) { + self.element.request_layout(id, cx) } fn prepaint( &mut self, + id: Option<&GlobalElementId>, bounds: Bounds, state: &mut Self::RequestLayoutState, cx: &mut WindowContext, ) -> E::PrepaintState { - self.element.prepaint(bounds, state, cx) + self.element.prepaint(id, bounds, state, cx) } fn paint( &mut self, + id: Option<&GlobalElementId>, bounds: Bounds, request_layout: &mut Self::RequestLayoutState, prepaint: &mut Self::PrepaintState, cx: &mut WindowContext, ) { - self.element.paint(bounds, request_layout, prepaint, cx) + self.element.paint(id, bounds, request_layout, prepaint, cx) } } @@ -2347,27 +2384,37 @@ where type RequestLayoutState = E::RequestLayoutState; type PrepaintState = E::PrepaintState; - fn request_layout(&mut self, cx: &mut WindowContext) -> (LayoutId, Self::RequestLayoutState) { - self.element.request_layout(cx) + fn id(&self) -> Option { + self.element.id() + } + + fn request_layout( + &mut self, + id: Option<&GlobalElementId>, + cx: &mut WindowContext, + ) -> (LayoutId, Self::RequestLayoutState) { + self.element.request_layout(id, cx) } fn prepaint( &mut self, + id: Option<&GlobalElementId>, bounds: Bounds, state: &mut Self::RequestLayoutState, cx: &mut WindowContext, ) -> E::PrepaintState { - self.element.prepaint(bounds, state, cx) + self.element.prepaint(id, bounds, state, cx) } fn paint( &mut self, + id: Option<&GlobalElementId>, bounds: Bounds, request_layout: &mut Self::RequestLayoutState, prepaint: &mut Self::PrepaintState, cx: &mut WindowContext, ) { - self.element.paint(bounds, request_layout, prepaint, cx); + self.element.paint(id, bounds, request_layout, prepaint, cx); } } diff --git a/crates/gpui/src/elements/img.rs b/crates/gpui/src/elements/img.rs index 51eeccb3f8..c5e2f1701e 100644 --- a/crates/gpui/src/elements/img.rs +++ b/crates/gpui/src/elements/img.rs @@ -3,9 +3,10 @@ use std::path::PathBuf; use std::sync::Arc; use crate::{ - point, px, size, AbsoluteLength, Asset, Bounds, DefiniteLength, DevicePixels, Element, Hitbox, - ImageData, InteractiveElement, Interactivity, IntoElement, LayoutId, Length, Pixels, SharedUri, - Size, StyleRefinement, Styled, SvgSize, UriOrPath, WindowContext, + point, px, size, AbsoluteLength, Asset, Bounds, DefiniteLength, DevicePixels, Element, + ElementId, GlobalElementId, Hitbox, ImageData, InteractiveElement, Interactivity, IntoElement, + LayoutId, Length, Pixels, SharedUri, Size, StyleRefinement, Styled, SvgSize, UriOrPath, + WindowContext, }; use futures::{AsyncReadExt, Future}; use image::{ImageBuffer, ImageError}; @@ -232,42 +233,54 @@ impl Element for Img { type RequestLayoutState = (); type PrepaintState = Option; - fn request_layout(&mut self, cx: &mut WindowContext) -> (LayoutId, Self::RequestLayoutState) { - let layout_id = self.interactivity.request_layout(cx, |mut style, cx| { - if let Some(data) = self.source.data(cx) { - let image_size = data.size(); - match (style.size.width, style.size.height) { - (Length::Auto, Length::Auto) => { - style.size = Size { - width: Length::Definite(DefiniteLength::Absolute( - AbsoluteLength::Pixels(px(image_size.width.0 as f32)), - )), - height: Length::Definite(DefiniteLength::Absolute( - AbsoluteLength::Pixels(px(image_size.height.0 as f32)), - )), - } - } - _ => {} - } - } + fn id(&self) -> Option { + self.interactivity.element_id.clone() + } - cx.request_layout(&style, []) - }); + fn request_layout( + &mut self, + global_id: Option<&GlobalElementId>, + cx: &mut WindowContext, + ) -> (LayoutId, Self::RequestLayoutState) { + let layout_id = self + .interactivity + .request_layout(global_id, cx, |mut style, cx| { + if let Some(data) = self.source.data(cx) { + let image_size = data.size(); + match (style.size.width, style.size.height) { + (Length::Auto, Length::Auto) => { + style.size = Size { + width: Length::Definite(DefiniteLength::Absolute( + AbsoluteLength::Pixels(px(image_size.width.0 as f32)), + )), + height: Length::Definite(DefiniteLength::Absolute( + AbsoluteLength::Pixels(px(image_size.height.0 as f32)), + )), + } + } + _ => {} + } + } + + cx.request_layout(&style, []) + }); (layout_id, ()) } fn prepaint( &mut self, + global_id: Option<&GlobalElementId>, bounds: Bounds, _request_layout: &mut Self::RequestLayoutState, cx: &mut WindowContext, ) -> Option { self.interactivity - .prepaint(bounds, bounds.size, cx, |_, _, hitbox, _| hitbox) + .prepaint(global_id, bounds, bounds.size, cx, |_, _, hitbox, _| hitbox) } fn paint( &mut self, + global_id: Option<&GlobalElementId>, bounds: Bounds, _: &mut Self::RequestLayoutState, hitbox: &mut Self::PrepaintState, @@ -275,7 +288,7 @@ impl Element for Img { ) { let source = self.source.clone(); self.interactivity - .paint(bounds, hitbox.as_ref(), cx, |style, cx| { + .paint(global_id, bounds, hitbox.as_ref(), cx, |style, cx| { let corner_radii = style.corner_radii.to_pixels(bounds.size, cx.rem_size()); if let Some(data) = source.data(cx) { diff --git a/crates/gpui/src/elements/list.rs b/crates/gpui/src/elements/list.rs index befee0bbd9..586331afbd 100644 --- a/crates/gpui/src/elements/list.rs +++ b/crates/gpui/src/elements/list.rs @@ -8,8 +8,8 @@ use crate::{ point, px, size, AnyElement, AvailableSpace, Bounds, ContentMask, DispatchPhase, Edges, - Element, FocusHandle, Hitbox, IntoElement, Pixels, Point, ScrollWheelEvent, Size, Style, - StyleRefinement, Styled, WindowContext, + Element, FocusHandle, GlobalElementId, Hitbox, IntoElement, Pixels, Point, ScrollWheelEvent, + Size, Style, StyleRefinement, Styled, WindowContext, }; use collections::VecDeque; use refineable::Refineable as _; @@ -704,8 +704,13 @@ impl Element for List { type RequestLayoutState = (); type PrepaintState = ListPrepaintState; + fn id(&self) -> Option { + None + } + fn request_layout( &mut self, + _id: Option<&GlobalElementId>, cx: &mut crate::WindowContext, ) -> (crate::LayoutId, Self::RequestLayoutState) { let layout_id = match self.sizing_behavior { @@ -770,6 +775,7 @@ impl Element for List { fn prepaint( &mut self, + _id: Option<&GlobalElementId>, bounds: Bounds, _: &mut Self::RequestLayoutState, cx: &mut WindowContext, @@ -812,6 +818,7 @@ impl Element for List { fn paint( &mut self, + _id: Option<&GlobalElementId>, bounds: Bounds, _: &mut Self::RequestLayoutState, prepaint: &mut Self::PrepaintState, diff --git a/crates/gpui/src/elements/svg.rs b/crates/gpui/src/elements/svg.rs index 83f9ba68df..34925bd075 100644 --- a/crates/gpui/src/elements/svg.rs +++ b/crates/gpui/src/elements/svg.rs @@ -1,7 +1,7 @@ use crate::{ - geometry::Negate as _, point, px, radians, size, Bounds, Element, Hitbox, InteractiveElement, - Interactivity, IntoElement, LayoutId, Pixels, Point, Radians, SharedString, Size, - StyleRefinement, Styled, TransformationMatrix, WindowContext, + geometry::Negate as _, point, px, radians, size, Bounds, Element, GlobalElementId, Hitbox, + InteractiveElement, Interactivity, IntoElement, LayoutId, Pixels, Point, Radians, SharedString, + Size, StyleRefinement, Styled, TransformationMatrix, WindowContext, }; use util::ResultExt; @@ -40,25 +40,35 @@ impl Element for Svg { type RequestLayoutState = (); type PrepaintState = Option; - fn request_layout(&mut self, cx: &mut WindowContext) -> (LayoutId, Self::RequestLayoutState) { + fn id(&self) -> Option { + self.interactivity.element_id.clone() + } + + fn request_layout( + &mut self, + global_id: Option<&GlobalElementId>, + cx: &mut WindowContext, + ) -> (LayoutId, Self::RequestLayoutState) { let layout_id = self .interactivity - .request_layout(cx, |style, cx| cx.request_layout(&style, None)); + .request_layout(global_id, cx, |style, cx| cx.request_layout(&style, None)); (layout_id, ()) } fn prepaint( &mut self, + global_id: Option<&GlobalElementId>, bounds: Bounds, _request_layout: &mut Self::RequestLayoutState, cx: &mut WindowContext, ) -> Option { self.interactivity - .prepaint(bounds, bounds.size, cx, |_, _, hitbox, _| hitbox) + .prepaint(global_id, bounds, bounds.size, cx, |_, _, hitbox, _| hitbox) } fn paint( &mut self, + global_id: Option<&GlobalElementId>, bounds: Bounds, _request_layout: &mut Self::RequestLayoutState, hitbox: &mut Option, @@ -67,7 +77,7 @@ impl Element for Svg { Self: Sized, { self.interactivity - .paint(bounds, hitbox.as_ref(), cx, |style, cx| { + .paint(global_id, bounds, hitbox.as_ref(), cx, |style, cx| { if let Some((path, color)) = self.path.as_ref().zip(style.text.color) { let transformation = self .transformation diff --git a/crates/gpui/src/elements/text.rs b/crates/gpui/src/elements/text.rs index bdafd809a8..0e795712b4 100644 --- a/crates/gpui/src/elements/text.rs +++ b/crates/gpui/src/elements/text.rs @@ -1,7 +1,8 @@ use crate::{ - ActiveTooltip, AnyTooltip, AnyView, Bounds, DispatchPhase, Element, ElementId, HighlightStyle, - Hitbox, IntoElement, LayoutId, MouseDownEvent, MouseMoveEvent, MouseUpEvent, Pixels, Point, - SharedString, Size, TextRun, TextStyle, WhiteSpace, WindowContext, WrappedLine, TOOLTIP_DELAY, + ActiveTooltip, AnyTooltip, AnyView, Bounds, DispatchPhase, Element, ElementId, GlobalElementId, + HighlightStyle, Hitbox, IntoElement, LayoutId, MouseDownEvent, MouseMoveEvent, MouseUpEvent, + Pixels, Point, SharedString, Size, TextRun, TextStyle, WhiteSpace, WindowContext, WrappedLine, + TOOLTIP_DELAY, }; use anyhow::anyhow; use parking_lot::{Mutex, MutexGuard}; @@ -19,7 +20,15 @@ impl Element for &'static str { type RequestLayoutState = TextState; type PrepaintState = (); - fn request_layout(&mut self, cx: &mut WindowContext) -> (LayoutId, Self::RequestLayoutState) { + fn id(&self) -> Option { + None + } + + fn request_layout( + &mut self, + _id: Option<&GlobalElementId>, + cx: &mut WindowContext, + ) -> (LayoutId, Self::RequestLayoutState) { let mut state = TextState::default(); let layout_id = state.layout(SharedString::from(*self), None, cx); (layout_id, state) @@ -27,6 +36,7 @@ impl Element for &'static str { fn prepaint( &mut self, + _id: Option<&GlobalElementId>, _bounds: Bounds, _text_state: &mut Self::RequestLayoutState, _cx: &mut WindowContext, @@ -35,6 +45,7 @@ impl Element for &'static str { fn paint( &mut self, + _id: Option<&GlobalElementId>, bounds: Bounds, text_state: &mut TextState, _: &mut (), @@ -64,7 +75,17 @@ impl Element for SharedString { type RequestLayoutState = TextState; type PrepaintState = (); - fn request_layout(&mut self, cx: &mut WindowContext) -> (LayoutId, Self::RequestLayoutState) { + fn id(&self) -> Option { + None + } + + fn request_layout( + &mut self, + + _id: Option<&GlobalElementId>, + + cx: &mut WindowContext, + ) -> (LayoutId, Self::RequestLayoutState) { let mut state = TextState::default(); let layout_id = state.layout(self.clone(), None, cx); (layout_id, state) @@ -72,6 +93,7 @@ impl Element for SharedString { fn prepaint( &mut self, + _id: Option<&GlobalElementId>, _bounds: Bounds, _text_state: &mut Self::RequestLayoutState, _cx: &mut WindowContext, @@ -80,6 +102,7 @@ impl Element for SharedString { fn paint( &mut self, + _id: Option<&GlobalElementId>, bounds: Bounds, text_state: &mut Self::RequestLayoutState, _: &mut Self::PrepaintState, @@ -150,7 +173,17 @@ impl Element for StyledText { type RequestLayoutState = TextState; type PrepaintState = (); - fn request_layout(&mut self, cx: &mut WindowContext) -> (LayoutId, Self::RequestLayoutState) { + fn id(&self) -> Option { + None + } + + fn request_layout( + &mut self, + + _id: Option<&GlobalElementId>, + + cx: &mut WindowContext, + ) -> (LayoutId, Self::RequestLayoutState) { let mut state = TextState::default(); let layout_id = state.layout(self.text.clone(), self.runs.take(), cx); (layout_id, state) @@ -158,6 +191,7 @@ impl Element for StyledText { fn prepaint( &mut self, + _id: Option<&GlobalElementId>, _bounds: Bounds, _state: &mut Self::RequestLayoutState, _cx: &mut WindowContext, @@ -166,6 +200,7 @@ impl Element for StyledText { fn paint( &mut self, + _id: Option<&GlobalElementId>, bounds: Bounds, text_state: &mut Self::RequestLayoutState, _: &mut Self::PrepaintState, @@ -404,18 +439,27 @@ impl Element for InteractiveText { type RequestLayoutState = TextState; type PrepaintState = Hitbox; - fn request_layout(&mut self, cx: &mut WindowContext) -> (LayoutId, Self::RequestLayoutState) { - self.text.request_layout(cx) + fn id(&self) -> Option { + Some(self.element_id.clone()) + } + + fn request_layout( + &mut self, + _id: Option<&GlobalElementId>, + cx: &mut WindowContext, + ) -> (LayoutId, Self::RequestLayoutState) { + self.text.request_layout(None, cx) } fn prepaint( &mut self, + global_id: Option<&GlobalElementId>, bounds: Bounds, state: &mut Self::RequestLayoutState, cx: &mut WindowContext, ) -> Hitbox { - cx.with_element_state::( - Some(self.element_id.clone()), + cx.with_optional_element_state::( + global_id, |interactive_state, cx| { let interactive_state = interactive_state .map(|interactive_state| interactive_state.unwrap_or_default()); @@ -429,7 +473,7 @@ impl Element for InteractiveText { } } - self.text.prepaint(bounds, state, cx); + self.text.prepaint(None, bounds, state, cx); let hitbox = cx.insert_hitbox(bounds, false); (hitbox, interactive_state) }, @@ -438,15 +482,16 @@ impl Element for InteractiveText { fn paint( &mut self, + global_id: Option<&GlobalElementId>, bounds: Bounds, text_state: &mut Self::RequestLayoutState, hitbox: &mut Hitbox, cx: &mut WindowContext, ) { cx.with_element_state::( - Some(self.element_id.clone()), + global_id.unwrap(), |interactive_state, cx| { - let mut interactive_state = interactive_state.unwrap().unwrap_or_default(); + let mut interactive_state = interactive_state.unwrap_or_default(); if let Some(click_listener) = self.click_listener.take() { let mouse_position = cx.mouse_position(); if let Some(ix) = text_state.index_for_position(bounds, mouse_position) { @@ -576,9 +621,9 @@ impl Element for InteractiveText { }); } - self.text.paint(bounds, text_state, &mut (), cx); + self.text.paint(None, bounds, text_state, &mut (), cx); - ((), Some(interactive_state)) + ((), interactive_state) }, ); } diff --git a/crates/gpui/src/elements/uniform_list.rs b/crates/gpui/src/elements/uniform_list.rs index 910f82bbee..d922e421b1 100644 --- a/crates/gpui/src/elements/uniform_list.rs +++ b/crates/gpui/src/elements/uniform_list.rs @@ -5,9 +5,9 @@ //! elements with uniform height. use crate::{ - point, px, size, AnyElement, AvailableSpace, Bounds, ContentMask, Element, ElementId, Hitbox, - InteractiveElement, Interactivity, IntoElement, LayoutId, Pixels, Render, ScrollHandle, Size, - StyleRefinement, Styled, View, ViewContext, WindowContext, + point, px, size, AnyElement, AvailableSpace, Bounds, ContentMask, Element, ElementId, + GlobalElementId, Hitbox, InteractiveElement, Interactivity, IntoElement, LayoutId, Pixels, + Render, ScrollHandle, Size, StyleRefinement, Styled, View, ViewContext, WindowContext, }; use smallvec::SmallVec; use std::{cell::RefCell, cmp, ops::Range, rc::Rc}; @@ -107,26 +107,38 @@ impl Element for UniformList { type RequestLayoutState = UniformListFrameState; type PrepaintState = Option; - fn request_layout(&mut self, cx: &mut WindowContext) -> (LayoutId, Self::RequestLayoutState) { + fn id(&self) -> Option { + self.interactivity.element_id.clone() + } + + fn request_layout( + &mut self, + global_id: Option<&GlobalElementId>, + cx: &mut WindowContext, + ) -> (LayoutId, Self::RequestLayoutState) { let max_items = self.item_count; let item_size = self.measure_item(None, cx); - let layout_id = self.interactivity.request_layout(cx, |style, cx| { - cx.request_measured_layout(style, move |known_dimensions, available_space, _cx| { - let desired_height = item_size.height * max_items; - let width = known_dimensions - .width - .unwrap_or(match available_space.width { - AvailableSpace::Definite(x) => x, - AvailableSpace::MinContent | AvailableSpace::MaxContent => item_size.width, - }); + let layout_id = self + .interactivity + .request_layout(global_id, cx, |style, cx| { + cx.request_measured_layout(style, move |known_dimensions, available_space, _cx| { + let desired_height = item_size.height * max_items; + let width = known_dimensions + .width + .unwrap_or(match available_space.width { + AvailableSpace::Definite(x) => x, + AvailableSpace::MinContent | AvailableSpace::MaxContent => { + item_size.width + } + }); - let height = match available_space.height { - AvailableSpace::Definite(height) => desired_height.min(height), - AvailableSpace::MinContent | AvailableSpace::MaxContent => desired_height, - }; - size(width, height) - }) - }); + let height = match available_space.height { + AvailableSpace::Definite(height) => desired_height.min(height), + AvailableSpace::MinContent | AvailableSpace::MaxContent => desired_height, + }; + size(width, height) + }) + }); ( layout_id, @@ -139,11 +151,12 @@ impl Element for UniformList { fn prepaint( &mut self, + global_id: Option<&GlobalElementId>, bounds: Bounds, frame_state: &mut Self::RequestLayoutState, cx: &mut WindowContext, ) -> Option { - let style = self.interactivity.compute_style(None, cx); + let style = self.interactivity.compute_style(global_id, None, cx); let border = style.border_widths.to_pixels(cx.rem_size()); let padding = style.padding.to_pixels(bounds.size.into(), cx.rem_size()); @@ -167,6 +180,7 @@ impl Element for UniformList { .and_then(|handle| handle.deferred_scroll_to_item.take()); self.interactivity.prepaint( + global_id, bounds, content_size, cx, @@ -236,13 +250,14 @@ impl Element for UniformList { fn paint( &mut self, + global_id: Option<&GlobalElementId>, bounds: Bounds, request_layout: &mut Self::RequestLayoutState, hitbox: &mut Option, cx: &mut WindowContext, ) { self.interactivity - .paint(bounds, hitbox.as_ref(), cx, |_, cx| { + .paint(global_id, bounds, hitbox.as_ref(), cx, |_, cx| { for item in &mut request_layout.items { item.paint(cx); } diff --git a/crates/gpui/src/view.rs b/crates/gpui/src/view.rs index 6b68e5235e..d67f335fbb 100644 --- a/crates/gpui/src/view.rs +++ b/crates/gpui/src/view.rs @@ -1,8 +1,8 @@ use crate::{ seal::Sealed, AnyElement, AnyModel, AnyWeakModel, AppContext, Bounds, ContentMask, Element, - ElementId, Entity, EntityId, Flatten, FocusHandle, FocusableView, IntoElement, LayoutId, Model, - PaintIndex, Pixels, PrepaintStateIndex, Render, Style, StyleRefinement, TextStyle, ViewContext, - VisualContext, WeakModel, WindowContext, + ElementId, Entity, EntityId, Flatten, FocusHandle, FocusableView, GlobalElementId, IntoElement, + LayoutId, Model, PaintIndex, Pixels, PrepaintStateIndex, Render, Style, StyleRefinement, + TextStyle, ViewContext, VisualContext, WeakModel, WindowContext, }; use anyhow::{Context, Result}; use refineable::Refineable; @@ -93,36 +93,40 @@ impl Element for View { type RequestLayoutState = AnyElement; type PrepaintState = (); - fn request_layout(&mut self, cx: &mut WindowContext) -> (LayoutId, Self::RequestLayoutState) { - cx.with_element_id(Some(ElementId::View(self.entity_id())), |cx| { - let mut element = self.update(cx, |view, cx| view.render(cx).into_any_element()); - let layout_id = element.request_layout(cx); - (layout_id, element) - }) + fn id(&self) -> Option { + Some(ElementId::View(self.entity_id())) + } + + fn request_layout( + &mut self, + _id: Option<&GlobalElementId>, + cx: &mut WindowContext, + ) -> (LayoutId, Self::RequestLayoutState) { + let mut element = self.update(cx, |view, cx| view.render(cx).into_any_element()); + let layout_id = element.request_layout(cx); + (layout_id, element) } fn prepaint( &mut self, + _id: Option<&GlobalElementId>, _: Bounds, element: &mut Self::RequestLayoutState, cx: &mut WindowContext, ) { cx.set_view_id(self.entity_id()); - cx.with_element_id(Some(ElementId::View(self.entity_id())), |cx| { - element.prepaint(cx) - }) + element.prepaint(cx); } fn paint( &mut self, + _id: Option<&GlobalElementId>, _: Bounds, element: &mut Self::RequestLayoutState, _: &mut Self::PrepaintState, cx: &mut WindowContext, ) { - cx.with_element_id(Some(ElementId::View(self.entity_id())), |cx| { - element.paint(cx) - }) + element.paint(cx); } } @@ -279,112 +283,108 @@ impl Element for AnyView { type RequestLayoutState = Option; type PrepaintState = Option; - fn request_layout(&mut self, cx: &mut WindowContext) -> (LayoutId, Self::RequestLayoutState) { + fn id(&self) -> Option { + Some(ElementId::View(self.entity_id())) + } + + fn request_layout( + &mut self, + _id: Option<&GlobalElementId>, + cx: &mut WindowContext, + ) -> (LayoutId, Self::RequestLayoutState) { if let Some(style) = self.cached_style.as_ref() { let mut root_style = Style::default(); root_style.refine(style); let layout_id = cx.request_layout(&root_style, None); (layout_id, None) } else { - cx.with_element_id(Some(ElementId::View(self.entity_id())), |cx| { - let mut element = (self.render)(self, cx); - let layout_id = element.request_layout(cx); - (layout_id, Some(element)) - }) + let mut element = (self.render)(self, cx); + let layout_id = element.request_layout(cx); + (layout_id, Some(element)) } } fn prepaint( &mut self, + global_id: Option<&GlobalElementId>, bounds: Bounds, element: &mut Self::RequestLayoutState, cx: &mut WindowContext, ) -> Option { cx.set_view_id(self.entity_id()); if self.cached_style.is_some() { - cx.with_element_state::( - Some(ElementId::View(self.entity_id())), - |element_state, cx| { - let mut element_state = element_state.unwrap(); + cx.with_element_state::(global_id.unwrap(), |element_state, cx| { + let content_mask = cx.content_mask(); + let text_style = cx.text_style(); - let content_mask = cx.content_mask(); - let text_style = cx.text_style(); - - if let Some(mut element_state) = element_state { - if element_state.cache_key.bounds == bounds - && element_state.cache_key.content_mask == content_mask - && element_state.cache_key.text_style == text_style - && !cx.window.dirty_views.contains(&self.entity_id()) - && !cx.window.refreshing - { - let prepaint_start = cx.prepaint_index(); - cx.reuse_prepaint(element_state.prepaint_range.clone()); - let prepaint_end = cx.prepaint_index(); - element_state.prepaint_range = prepaint_start..prepaint_end; - return (None, Some(element_state)); - } + if let Some(mut element_state) = element_state { + if element_state.cache_key.bounds == bounds + && element_state.cache_key.content_mask == content_mask + && element_state.cache_key.text_style == text_style + && !cx.window.dirty_views.contains(&self.entity_id()) + && !cx.window.refreshing + { + let prepaint_start = cx.prepaint_index(); + cx.reuse_prepaint(element_state.prepaint_range.clone()); + let prepaint_end = cx.prepaint_index(); + element_state.prepaint_range = prepaint_start..prepaint_end; + return (None, element_state); } + } - let prepaint_start = cx.prepaint_index(); - let mut element = (self.render)(self, cx); - element.layout_as_root(bounds.size.into(), cx); - element.prepaint_at(bounds.origin, cx); - let prepaint_end = cx.prepaint_index(); + let prepaint_start = cx.prepaint_index(); + let mut element = (self.render)(self, cx); + element.layout_as_root(bounds.size.into(), cx); + element.prepaint_at(bounds.origin, cx); + let prepaint_end = cx.prepaint_index(); - ( - Some(element), - Some(AnyViewState { - prepaint_range: prepaint_start..prepaint_end, - paint_range: PaintIndex::default()..PaintIndex::default(), - cache_key: ViewCacheKey { - bounds, - content_mask, - text_style, - }, - }), - ) - }, - ) - } else { - cx.with_element_id(Some(ElementId::View(self.entity_id())), |cx| { - let mut element = element.take().unwrap(); - element.prepaint(cx); - Some(element) + ( + Some(element), + AnyViewState { + prepaint_range: prepaint_start..prepaint_end, + paint_range: PaintIndex::default()..PaintIndex::default(), + cache_key: ViewCacheKey { + bounds, + content_mask, + text_style, + }, + }, + ) }) + } else { + let mut element = element.take().unwrap(); + element.prepaint(cx); + Some(element) } } fn paint( &mut self, + global_id: Option<&GlobalElementId>, _bounds: Bounds, _: &mut Self::RequestLayoutState, element: &mut Self::PrepaintState, cx: &mut WindowContext, ) { if self.cached_style.is_some() { - cx.with_element_state::( - Some(ElementId::View(self.entity_id())), - |element_state, cx| { - let mut element_state = element_state.unwrap().unwrap(); + cx.with_element_state::(global_id.unwrap(), |element_state, cx| { + let mut element_state = element_state.unwrap(); - let paint_start = cx.paint_index(); + let paint_start = cx.paint_index(); - if let Some(element) = element { - element.paint(cx); - } else { - cx.reuse_paint(element_state.paint_range.clone()); - } + if let Some(element) = element { + element.paint(cx); + } else { + cx.reuse_paint(element_state.paint_range.clone()); + } - let paint_end = cx.paint_index(); - element_state.paint_range = paint_start..paint_end; + let paint_end = cx.paint_index(); + element_state.paint_range = paint_start..paint_end; - ((), Some(element_state)) - }, - ) - } else { - cx.with_element_id(Some(ElementId::View(self.entity_id())), |cx| { - element.as_mut().unwrap().paint(cx); + ((), element_state) }) + } else { + element.as_mut().unwrap().paint(cx); } } } diff --git a/crates/gpui/src/window.rs b/crates/gpui/src/window.rs index 4d20b4a441..d5aaed8586 100644 --- a/crates/gpui/src/window.rs +++ b/crates/gpui/src/window.rs @@ -353,7 +353,7 @@ pub(crate) struct TooltipRequest { pub(crate) struct DeferredDraw { priority: usize, parent_node: DispatchNodeId, - element_id_stack: GlobalElementId, + element_id_stack: SmallVec<[ElementId; 32]>, text_style_stack: Vec, element: Option, absolute_offset: Point, @@ -454,9 +454,10 @@ impl Frame { pub(crate) fn finish(&mut self, prev_frame: &mut Self) { for element_state_key in &self.accessed_element_states { - if let Some(element_state) = prev_frame.element_states.remove(element_state_key) { - self.element_states - .insert(element_state_key.clone(), element_state); + if let Some((element_state_key, element_state)) = + prev_frame.element_states.remove_entry(element_state_key) + { + self.element_states.insert(element_state_key, element_state); } } @@ -477,7 +478,7 @@ pub struct Window { pub(crate) viewport_size: Size, layout_engine: Option, pub(crate) root_view: Option, - pub(crate) element_id_stack: GlobalElementId, + pub(crate) element_id_stack: SmallVec<[ElementId; 32]>, pub(crate) text_style_stack: Vec, pub(crate) element_offset_stack: Vec>, pub(crate) content_mask_stack: Vec>, @@ -745,7 +746,7 @@ impl Window { viewport_size: content_size, layout_engine: Some(TaffyLayoutEngine::new()), root_view: None, - element_id_stack: GlobalElementId::default(), + element_id_stack: SmallVec::default(), text_style_stack: Vec::new(), element_offset_stack: Vec::new(), content_mask_stack: Vec::new(), @@ -1499,7 +1500,7 @@ impl<'a> WindowContext<'a> { window.rendered_frame.accessed_element_states[range.start.accessed_element_states_index ..range.end.accessed_element_states_index] .iter() - .cloned(), + .map(|(id, type_id)| (GlobalElementId(id.0.clone()), *type_id)), ); window .text_system @@ -1562,7 +1563,7 @@ impl<'a> WindowContext<'a> { window.rendered_frame.accessed_element_states[range.start.accessed_element_states_index ..range.end.accessed_element_states_index] .iter() - .cloned(), + .map(|(id, type_id)| (GlobalElementId(id.0.clone()), *type_id)), ); window .text_system @@ -1630,35 +1631,6 @@ impl<'a> WindowContext<'a> { id } - /// Pushes the given element id onto the global stack and invokes the given closure - /// with a `GlobalElementId`, which disambiguates the given id in the context of its ancestor - /// ids. Because elements are discarded and recreated on each frame, the `GlobalElementId` is - /// used to associate state with identified elements across separate frames. This method should - /// only be called as part of element drawing. - pub fn with_element_id( - &mut self, - id: Option>, - f: impl FnOnce(&mut Self) -> R, - ) -> R { - debug_assert!( - matches!( - self.window.draw_phase, - DrawPhase::Prepaint | DrawPhase::Paint - ), - "this method can only be called during request_layout, prepaint, or paint" - ); - if let Some(id) = id.map(Into::into) { - let window = self.window_mut(); - window.element_id_stack.push(id); - let result = f(self); - let window: &mut Window = self.borrow_mut(); - window.element_id_stack.pop(); - result - } else { - f(self) - } - } - /// Invoke the given function with the given content mask after intersecting it /// with the current mask. This method should only be called during element drawing. pub fn with_content_mask( @@ -1903,13 +1875,114 @@ impl<'a> WindowContext<'a> { }) } + /// Provide elements in the called function with a new namespace in which their identiers must be unique. + /// This can be used within a custom element to distinguish multiple sets of child elements. + pub fn with_element_namespace( + &mut self, + element_id: impl Into, + f: impl FnOnce(&mut Self) -> R, + ) -> R { + self.window.element_id_stack.push(element_id.into()); + let result = f(self); + self.window.element_id_stack.pop(); + result + } + /// Updates or initializes state for an element with the given id that lives across multiple /// frames. If an element with this ID existed in the rendered frame, its state will be passed /// to the given closure. The state returned by the closure will be stored so it can be referenced /// when drawing the next frame. This method should only be called as part of element drawing. pub fn with_element_state( &mut self, - element_id: Option, + global_id: &GlobalElementId, + f: impl FnOnce(Option, &mut Self) -> (R, S), + ) -> R + where + S: 'static, + { + debug_assert!( + matches!( + self.window.draw_phase, + DrawPhase::Prepaint | DrawPhase::Paint + ), + "this method can only be called during request_layout, prepaint, or paint" + ); + + let key = (GlobalElementId(global_id.0.clone()), TypeId::of::()); + self.window + .next_frame + .accessed_element_states + .push((GlobalElementId(key.0.clone()), TypeId::of::())); + + if let Some(any) = self + .window + .next_frame + .element_states + .remove(&key) + .or_else(|| self.window.rendered_frame.element_states.remove(&key)) + { + let ElementStateBox { + inner, + #[cfg(debug_assertions)] + type_name, + } = any; + // Using the extra inner option to avoid needing to reallocate a new box. + let mut state_box = inner + .downcast::>() + .map_err(|_| { + #[cfg(debug_assertions)] + { + anyhow::anyhow!( + "invalid element state type for id, requested {:?}, actual: {:?}", + std::any::type_name::(), + type_name + ) + } + + #[cfg(not(debug_assertions))] + { + anyhow::anyhow!( + "invalid element state type for id, requested {:?}", + std::any::type_name::(), + ) + } + }) + .unwrap(); + + let state = state_box.take().expect( + "reentrant call to with_element_state for the same state type and element id", + ); + let (result, state) = f(Some(state), self); + state_box.replace(state); + self.window.next_frame.element_states.insert( + key, + ElementStateBox { + inner: state_box, + #[cfg(debug_assertions)] + type_name, + }, + ); + result + } else { + let (result, state) = f(None, self); + self.window.next_frame.element_states.insert( + key, + ElementStateBox { + inner: Box::new(Some(state)), + #[cfg(debug_assertions)] + type_name: std::any::type_name::(), + }, + ); + result + } + } + + /// A variant of `with_element_state` that allows the element's id to be optional. This is a convenience + /// method for elements where the element id may or may not be assigned. Prefer using `with_element_state` + /// when the element is guaranteed to have an id. + pub fn with_optional_element_state( + &mut self, + global_id: Option<&GlobalElementId>, f: impl FnOnce(Option>, &mut Self) -> (R, Option), ) -> R where @@ -1922,90 +1995,22 @@ impl<'a> WindowContext<'a> { ), "this method can only be called during request_layout, prepaint, or paint" ); - let id_is_none = element_id.is_none(); - self.with_element_id(element_id, |cx| { - if id_is_none { - let (result, state) = f(None, cx); - debug_assert!(state.is_none(), "you must not return an element state when passing None for the element id"); - result - } else { - let global_id = cx.window().element_id_stack.clone(); - let key = (global_id, TypeId::of::()); - cx.window.next_frame.accessed_element_states.push(key.clone()); - if let Some(any) = cx - .window_mut() - .next_frame - .element_states - .remove(&key) - .or_else(|| { - cx.window_mut() - .rendered_frame - .element_states - .remove(&key) - }) - { - let ElementStateBox { - inner, - #[cfg(debug_assertions)] - type_name - } = any; - // Using the extra inner option to avoid needing to reallocate a new box. - let mut state_box = inner - .downcast::>() - .map_err(|_| { - #[cfg(debug_assertions)] - { - anyhow::anyhow!( - "invalid element state type for id, requested_type {:?}, actual type: {:?}", - std::any::type_name::(), - type_name - ) - } - - #[cfg(not(debug_assertions))] - { - anyhow::anyhow!( - "invalid element state type for id, requested_type {:?}", - std::any::type_name::(), - ) - } - }) - .unwrap(); - - // Actual: Option <- View - // Requested: () <- AnyElement - let state = state_box - .take() - .expect("reentrant call to with_element_state for the same state type and element id"); - let (result, state) = f(Some(Some(state)), cx); - state_box.replace(state.expect("you must return ")); - cx.window_mut() - .next_frame - .element_states - .insert(key, ElementStateBox { - inner: state_box, - #[cfg(debug_assertions)] - type_name - }); - result - } else { - let (result, state) = f(Some(None), cx); - cx.window_mut() - .next_frame - .element_states - .insert(key, - ElementStateBox { - inner: Box::new(Some(state.expect("you must return Some when you pass some element id"))), - #[cfg(debug_assertions)] - type_name: std::any::type_name::() - } - - ); - result - } - } + if let Some(global_id) = global_id { + self.with_element_state(global_id, |state, cx| { + let (result, state) = f(Some(state), cx); + let state = + state.expect("you must return some state when you pass some element id"); + (result, state) }) + } else { + let (result, state) = f(None, self); + debug_assert!( + state.is_none(), + "you must not return an element state when passing None for the global id" + ); + result + } } /// Defers the drawing of the given element, scheduling it to be painted on top of the currently-drawn tree diff --git a/crates/terminal_view/src/terminal_element.rs b/crates/terminal_view/src/terminal_element.rs index b0bee7fa5e..ba457bd7a0 100644 --- a/crates/terminal_view/src/terminal_element.rs +++ b/crates/terminal_view/src/terminal_element.rs @@ -1,11 +1,11 @@ use editor::{CursorLayout, HighlightedRange, HighlightedRangeLine}; use gpui::{ - div, fill, point, px, relative, AnyElement, Bounds, DispatchPhase, Element, FocusHandle, Font, - FontStyle, FontWeight, HighlightStyle, Hitbox, Hsla, InputHandler, InteractiveElement, - Interactivity, IntoElement, LayoutId, Model, ModelContext, ModifiersChangedEvent, MouseButton, - MouseMoveEvent, Pixels, Point, ShapedLine, StatefulInteractiveElement, StrikethroughStyle, - Styled, TextRun, TextStyle, UnderlineStyle, WeakView, WhiteSpace, WindowContext, - WindowTextSystem, + div, fill, point, px, relative, AnyElement, Bounds, DispatchPhase, Element, ElementId, + FocusHandle, Font, FontStyle, FontWeight, GlobalElementId, HighlightStyle, Hitbox, Hsla, + InputHandler, InteractiveElement, Interactivity, IntoElement, LayoutId, Model, ModelContext, + ModifiersChangedEvent, MouseButton, MouseMoveEvent, Pixels, Point, ShapedLine, + StatefulInteractiveElement, StrikethroughStyle, Styled, TextRun, TextStyle, UnderlineStyle, + WeakView, WhiteSpace, WindowContext, WindowTextSystem, }; use itertools::Itertools; use language::CursorShape; @@ -544,26 +544,37 @@ impl Element for TerminalElement { type RequestLayoutState = (); type PrepaintState = LayoutState; - fn request_layout(&mut self, cx: &mut WindowContext) -> (LayoutId, Self::RequestLayoutState) { - self.interactivity.occlude_mouse(); - let layout_id = self.interactivity.request_layout(cx, |mut style, cx| { - style.size.width = relative(1.).into(); - style.size.height = relative(1.).into(); - let layout_id = cx.request_layout(&style, None); + fn id(&self) -> Option { + self.interactivity.element_id.clone() + } - layout_id - }); + fn request_layout( + &mut self, + global_id: Option<&GlobalElementId>, + cx: &mut WindowContext, + ) -> (LayoutId, Self::RequestLayoutState) { + self.interactivity.occlude_mouse(); + let layout_id = self + .interactivity + .request_layout(global_id, cx, |mut style, cx| { + style.size.width = relative(1.).into(); + style.size.height = relative(1.).into(); + let layout_id = cx.request_layout(&style, None); + + layout_id + }); (layout_id, ()) } fn prepaint( &mut self, + global_id: Option<&GlobalElementId>, bounds: Bounds, _: &mut Self::RequestLayoutState, cx: &mut WindowContext, ) -> Self::PrepaintState { self.interactivity - .prepaint(bounds, bounds.size, cx, |_, _, hitbox, cx| { + .prepaint(global_id, bounds, bounds.size, cx, |_, _, hitbox, cx| { let hitbox = hitbox.unwrap(); let settings = ThemeSettings::get_global(cx).clone(); @@ -775,6 +786,7 @@ impl Element for TerminalElement { fn paint( &mut self, + global_id: Option<&GlobalElementId>, bounds: Bounds, _: &mut Self::RequestLayoutState, layout: &mut Self::PrepaintState, @@ -802,7 +814,7 @@ impl Element for TerminalElement { let cursor = layout.cursor.take(); let hyperlink_tooltip = layout.hyperlink_tooltip.take(); self.interactivity - .paint(bounds, Some(&layout.hitbox), cx, |_, cx| { + .paint(global_id, bounds, Some(&layout.hitbox), cx, |_, cx| { cx.handle_input(&self.focus, terminal_input_handler); cx.on_key_event({ diff --git a/crates/ui/src/components/popover_menu.rs b/crates/ui/src/components/popover_menu.rs index 0d842d2a03..05bceda2d0 100644 --- a/crates/ui/src/components/popover_menu.rs +++ b/crates/ui/src/components/popover_menu.rs @@ -2,9 +2,9 @@ use std::{cell::RefCell, rc::Rc}; use gpui::{ anchored, deferred, div, point, prelude::FluentBuilder, px, AnchorCorner, AnyElement, Bounds, - DismissEvent, DispatchPhase, Element, ElementId, HitboxId, InteractiveElement, IntoElement, - LayoutId, ManagedView, MouseDownEvent, ParentElement, Pixels, Point, View, VisualContext, - WindowContext, + DismissEvent, DispatchPhase, Element, ElementId, GlobalElementId, HitboxId, InteractiveElement, + IntoElement, LayoutId, ManagedView, MouseDownEvent, ParentElement, Pixels, Point, View, + VisualContext, WindowContext, }; use crate::prelude::*; @@ -109,21 +109,6 @@ impl PopoverMenu { } }) } - - fn with_element_state( - &mut self, - cx: &mut WindowContext, - f: impl FnOnce(&mut Self, &mut PopoverMenuElementState, &mut WindowContext) -> R, - ) -> R { - cx.with_element_state::, _>( - Some(self.id.clone()), - |element_state, cx| { - let mut element_state = element_state.unwrap().unwrap_or_default(); - let result = f(self, &mut element_state, cx); - (result, Some(element_state)) - }, - ) - } } /// Creates a [`PopoverMenu`] @@ -171,101 +156,118 @@ impl Element for PopoverMenu { type RequestLayoutState = PopoverMenuFrameState; type PrepaintState = Option; + fn id(&self) -> Option { + Some(self.id.clone()) + } + fn request_layout( &mut self, + global_id: Option<&GlobalElementId>, cx: &mut WindowContext, ) -> (gpui::LayoutId, Self::RequestLayoutState) { - self.with_element_state(cx, |this, element_state, cx| { - let mut menu_layout_id = None; + cx.with_element_state( + global_id.unwrap(), + |element_state: Option>, cx| { + let element_state = element_state.unwrap_or_default(); + let mut menu_layout_id = None; - let menu_element = element_state.menu.borrow_mut().as_mut().map(|menu| { - let mut anchored = anchored().snap_to_window().anchor(this.anchor); - if let Some(child_bounds) = element_state.child_bounds { - anchored = anchored.position( - this.resolved_attach().corner(child_bounds) + this.resolved_offset(cx), - ); - } - let mut element = deferred(anchored.child(div().occlude().child(menu.clone()))) - .with_priority(1) - .into_any(); + let menu_element = element_state.menu.borrow_mut().as_mut().map(|menu| { + let mut anchored = anchored().snap_to_window().anchor(self.anchor); + if let Some(child_bounds) = element_state.child_bounds { + anchored = anchored.position( + self.resolved_attach().corner(child_bounds) + self.resolved_offset(cx), + ); + } + let mut element = deferred(anchored.child(div().occlude().child(menu.clone()))) + .with_priority(1) + .into_any(); - menu_layout_id = Some(element.request_layout(cx)); - element - }); + menu_layout_id = Some(element.request_layout(cx)); + element + }); - let mut child_element = this.child_builder.take().map(|child_builder| { - (child_builder)(element_state.menu.clone(), this.menu_builder.clone()) - }); + let mut child_element = self.child_builder.take().map(|child_builder| { + (child_builder)(element_state.menu.clone(), self.menu_builder.clone()) + }); - let child_layout_id = child_element - .as_mut() - .map(|child_element| child_element.request_layout(cx)); + let child_layout_id = child_element + .as_mut() + .map(|child_element| child_element.request_layout(cx)); - let layout_id = cx.request_layout( - &gpui::Style::default(), - menu_layout_id.into_iter().chain(child_layout_id), - ); + let layout_id = cx.request_layout( + &gpui::Style::default(), + menu_layout_id.into_iter().chain(child_layout_id), + ); - ( - layout_id, - PopoverMenuFrameState { - child_element, - child_layout_id, - menu_element, - }, - ) - }) + ( + ( + layout_id, + PopoverMenuFrameState { + child_element, + child_layout_id, + menu_element, + }, + ), + element_state, + ) + }, + ) } fn prepaint( &mut self, + global_id: Option<&GlobalElementId>, _bounds: Bounds, request_layout: &mut Self::RequestLayoutState, cx: &mut WindowContext, ) -> Option { - self.with_element_state(cx, |_this, element_state, cx| { - if let Some(child) = request_layout.child_element.as_mut() { - child.prepaint(cx); - } + if let Some(child) = request_layout.child_element.as_mut() { + child.prepaint(cx); + } - if let Some(menu) = request_layout.menu_element.as_mut() { - menu.prepaint(cx); - } + if let Some(menu) = request_layout.menu_element.as_mut() { + menu.prepaint(cx); + } - request_layout.child_layout_id.map(|layout_id| { - let bounds = cx.layout_bounds(layout_id); + let hitbox_id = request_layout.child_layout_id.map(|layout_id| { + let bounds = cx.layout_bounds(layout_id); + cx.with_element_state(global_id.unwrap(), |element_state, _cx| { + let mut element_state: PopoverMenuElementState = element_state.unwrap(); element_state.child_bounds = Some(bounds); - cx.insert_hitbox(bounds, false).id - }) - }) + ((), element_state) + }); + + cx.insert_hitbox(bounds, false).id + }); + + hitbox_id } fn paint( &mut self, + _id: Option<&GlobalElementId>, _: Bounds, request_layout: &mut Self::RequestLayoutState, child_hitbox: &mut Option, cx: &mut WindowContext, ) { - self.with_element_state(cx, |_this, _element_state, cx| { - if let Some(mut child) = request_layout.child_element.take() { - child.paint(cx); - } + if let Some(mut child) = request_layout.child_element.take() { + child.paint(cx); + } - if let Some(mut menu) = request_layout.menu_element.take() { - menu.paint(cx); + if let Some(mut menu) = request_layout.menu_element.take() { + menu.paint(cx); - if let Some(child_hitbox) = *child_hitbox { - // Mouse-downing outside the menu dismisses it, so we don't - // want a click on the toggle to re-open it. - cx.on_mouse_event(move |_: &MouseDownEvent, phase, cx| { - if phase == DispatchPhase::Bubble && child_hitbox.is_hovered(cx) { - cx.stop_propagation() - } - }) - } + if let Some(child_hitbox) = *child_hitbox { + // Mouse-downing outside the menu dismisses it, so we don't + // want a click on the toggle to re-open it. + cx.on_mouse_event(move |_: &MouseDownEvent, phase, cx| { + if phase == DispatchPhase::Bubble && child_hitbox.is_hovered(cx) { + cx.stop_propagation() + } + }) } - }) + } } } diff --git a/crates/ui/src/components/right_click_menu.rs b/crates/ui/src/components/right_click_menu.rs index e656835c1d..367841286f 100644 --- a/crates/ui/src/components/right_click_menu.rs +++ b/crates/ui/src/components/right_click_menu.rs @@ -2,8 +2,9 @@ use std::{cell::RefCell, rc::Rc}; use gpui::{ anchored, deferred, div, AnchorCorner, AnyElement, Bounds, DismissEvent, DispatchPhase, - Element, ElementId, Hitbox, InteractiveElement, IntoElement, LayoutId, ManagedView, - MouseButton, MouseDownEvent, ParentElement, Pixels, Point, View, VisualContext, WindowContext, + Element, ElementId, GlobalElementId, Hitbox, InteractiveElement, IntoElement, LayoutId, + ManagedView, MouseButton, MouseDownEvent, ParentElement, Pixels, Point, View, VisualContext, + WindowContext, }; pub struct RightClickMenu { @@ -40,11 +41,12 @@ impl RightClickMenu { fn with_element_state( &mut self, + global_id: &GlobalElementId, cx: &mut WindowContext, f: impl FnOnce(&mut Self, &mut MenuHandleElementState, &mut WindowContext) -> R, ) -> R { - cx.with_element_state::, _>( - Some(self.id.clone()), + cx.with_optional_element_state::, _>( + Some(global_id), |element_state, cx| { let mut element_state = element_state.unwrap().unwrap_or_default(); let result = f(self, &mut element_state, cx); @@ -103,11 +105,16 @@ impl Element for RightClickMenu { type RequestLayoutState = RequestLayoutState; type PrepaintState = PrepaintState; + fn id(&self) -> Option { + Some(self.id.clone()) + } + fn request_layout( &mut self, + id: Option<&GlobalElementId>, cx: &mut WindowContext, ) -> (gpui::LayoutId, Self::RequestLayoutState) { - self.with_element_state(cx, |this, element_state, cx| { + self.with_element_state(id.unwrap(), cx, |this, element_state, cx| { let mut menu_layout_id = None; let menu_element = element_state.menu.borrow_mut().as_mut().map(|menu| { @@ -152,38 +159,38 @@ impl Element for RightClickMenu { fn prepaint( &mut self, + _id: Option<&GlobalElementId>, bounds: Bounds, request_layout: &mut Self::RequestLayoutState, cx: &mut WindowContext, ) -> PrepaintState { - cx.with_element_id(Some(self.id.clone()), |cx| { - let hitbox = cx.insert_hitbox(bounds, false); + let hitbox = cx.insert_hitbox(bounds, false); - if let Some(child) = request_layout.child_element.as_mut() { - child.prepaint(cx); - } + if let Some(child) = request_layout.child_element.as_mut() { + child.prepaint(cx); + } - if let Some(menu) = request_layout.menu_element.as_mut() { - menu.prepaint(cx); - } + if let Some(menu) = request_layout.menu_element.as_mut() { + menu.prepaint(cx); + } - PrepaintState { - hitbox, - child_bounds: request_layout - .child_layout_id - .map(|layout_id| cx.layout_bounds(layout_id)), - } - }) + PrepaintState { + hitbox, + child_bounds: request_layout + .child_layout_id + .map(|layout_id| cx.layout_bounds(layout_id)), + } } fn paint( &mut self, + id: Option<&GlobalElementId>, _bounds: Bounds, request_layout: &mut Self::RequestLayoutState, prepaint_state: &mut Self::PrepaintState, cx: &mut WindowContext, ) { - self.with_element_state(cx, |this, element_state, cx| { + self.with_element_state(id.unwrap(), cx, |this, element_state, cx| { if let Some(mut child) = request_layout.child_element.take() { child.paint(cx); } diff --git a/crates/workspace/src/pane_group.rs b/crates/workspace/src/pane_group.rs index 9ad7acf734..062bbbe02e 100644 --- a/crates/workspace/src/pane_group.rs +++ b/crates/workspace/src/pane_group.rs @@ -597,9 +597,9 @@ mod element { use std::{cell::RefCell, iter, rc::Rc, sync::Arc}; use gpui::{ - px, relative, Along, AnyElement, Axis, Bounds, Element, IntoElement, MouseDownEvent, - MouseMoveEvent, MouseUpEvent, ParentElement, Pixels, Point, Size, Style, WeakView, - WindowContext, + px, relative, Along, AnyElement, Axis, Bounds, Element, GlobalElementId, IntoElement, + MouseDownEvent, MouseMoveEvent, MouseUpEvent, ParentElement, Pixels, Point, Size, Style, + WeakView, WindowContext, }; use gpui::{CursorStyle, Hitbox}; use parking_lot::Mutex; @@ -795,8 +795,13 @@ mod element { type RequestLayoutState = (); type PrepaintState = PaneAxisLayout; + fn id(&self) -> Option { + Some(self.basis.into()) + } + fn request_layout( &mut self, + _global_id: Option<&GlobalElementId>, cx: &mut ui::prelude::WindowContext, ) -> (gpui::LayoutId, Self::RequestLayoutState) { let mut style = Style::default(); @@ -810,17 +815,16 @@ mod element { fn prepaint( &mut self, + global_id: Option<&GlobalElementId>, bounds: Bounds, _state: &mut Self::RequestLayoutState, cx: &mut WindowContext, ) -> PaneAxisLayout { let dragged_handle = cx.with_element_state::>>, _>( - Some(self.basis.into()), + global_id.unwrap(), |state, _cx| { - let state = state - .unwrap() - .unwrap_or_else(|| Rc::new(RefCell::new(None))); - (state.clone(), Some(state)) + let state = state.unwrap_or_else(|| Rc::new(RefCell::new(None))); + (state.clone(), state) }, ); let flexes = self.flexes.lock().clone(); @@ -897,6 +901,7 @@ mod element { fn paint( &mut self, + _id: Option<&GlobalElementId>, bounds: gpui::Bounds, _: &mut Self::RequestLayoutState, layout: &mut Self::PrepaintState, diff --git a/crates/workspace/src/toolbar.rs b/crates/workspace/src/toolbar.rs index d2b042668e..1377c5519b 100644 --- a/crates/workspace/src/toolbar.rs +++ b/crates/workspace/src/toolbar.rs @@ -158,8 +158,10 @@ impl Toolbar { { let location = item.set_active_pane_item(self.active_item.as_deref(), cx); cx.subscribe(&item, |this, item, event, cx| { - if let Some((_, current_location)) = - this.items.iter_mut().find(|(i, _)| i.id() == item.id()) + if let Some((_, current_location)) = this + .items + .iter_mut() + .find(|(i, _)| i.id() == item.entity_id()) { match event { ToolbarItemEvent::ChangeLocation(new_location) => { diff --git a/crates/workspace/src/workspace.rs b/crates/workspace/src/workspace.rs index 25a20ec0ce..dbeb7eb427 100644 --- a/crates/workspace/src/workspace.rs +++ b/crates/workspace/src/workspace.rs @@ -28,9 +28,10 @@ use futures::{ use gpui::{ actions, canvas, impl_actions, point, relative, size, Action, AnyElement, AnyView, AnyWeakView, AppContext, AsyncAppContext, AsyncWindowContext, Bounds, DevicePixels, DragMoveEvent, - Entity as _, EntityId, EventEmitter, FocusHandle, FocusableView, Global, KeyContext, Keystroke, - LayoutId, ManagedView, Model, ModelContext, PathPromptOptions, Point, PromptLevel, Render, - Size, Subscription, Task, View, WeakView, WindowHandle, WindowOptions, + ElementId, Entity as _, EntityId, EventEmitter, FocusHandle, FocusableView, Global, + GlobalElementId, KeyContext, Keystroke, LayoutId, ManagedView, Model, ModelContext, + PathPromptOptions, Point, PromptLevel, Render, Size, Subscription, Task, View, WeakView, + WindowHandle, WindowOptions, }; use item::{ FollowableItem, FollowableItemHandle, Item, ItemHandle, ItemSettings, PreviewTabsSettings, @@ -5060,7 +5061,15 @@ impl Element for DisconnectedOverlay { type RequestLayoutState = AnyElement; type PrepaintState = (); - fn request_layout(&mut self, cx: &mut WindowContext) -> (LayoutId, Self::RequestLayoutState) { + fn id(&self) -> Option { + None + } + + fn request_layout( + &mut self, + _id: Option<&GlobalElementId>, + cx: &mut WindowContext, + ) -> (LayoutId, Self::RequestLayoutState) { let mut background = cx.theme().colors().elevated_surface_background; background.fade_out(0.2); let mut overlay = div() @@ -5083,6 +5092,7 @@ impl Element for DisconnectedOverlay { fn prepaint( &mut self, + _id: Option<&GlobalElementId>, bounds: Bounds, overlay: &mut Self::RequestLayoutState, cx: &mut WindowContext, @@ -5093,6 +5103,7 @@ impl Element for DisconnectedOverlay { fn paint( &mut self, + _id: Option<&GlobalElementId>, _: Bounds, overlay: &mut Self::RequestLayoutState, _: &mut Self::PrepaintState,