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 | 57 +++-- 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 | 61 +++-- crates/gpui/src/view.rs | 170 ++++++------- crates/gpui/src/window.rs | 243 ++++++++++--------- crates/terminal_view/src/terminal_element.rs | 44 ++-- crates/ui/src/components/popover_menu.rs | 174 ++++++------- 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, 830 insertions(+), 502 deletions(-) diff --git a/crates/editor/src/element.rs b/crates/editor/src/element.rs index 1b9b88e2e57a7a060ff0721a616d34dca3cf83b9..56c6ba395b318129fb61d5eb1a97405b2b13069c 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 987c9647259074fa68cd3dd481ae6f4d6b2e3b33..dd7485a30a9142501546c5f410b11e59eb838497 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 c4079214dccf5e13676de91f8ab986c923e7832c..427e6d61ef0b70de7260520a3edd7b3c96d20a66 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 15421f4ab332aa75584ca4e93aeb1dd0b3db163a..27b86849f449c795d0b07984372f53f818822a24 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 f18ff3fcb8acbcc97198333acd6216c8640fa774..34806d962379965b6dee1e6b96445d9671473e37 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 989ea76da501e9a5d82ed9b8fca46573fa64d9de..f98a9620948352946fd171f54fc596ee46198172 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 9bf365ae0dd50dab0339ad9e7c824cba0ac5cd64..b878897750d3936b5cb24fe8d1a29ff45e1abec7 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 981b86aadc9255731870ea04d64e0c64e78fdc61..989bab7c672701f7772bfadb7f34a4b2e7962ce7 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 51eeccb3f82ca0d47359f238e3146cf8a79af560..c5e2f1701e5ac555696fde4dc2a808462217456a 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() + } + + 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, []) - }); + 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 befee0bbd9bfcec05ebb3426c2d318e019159f58..586331afbd8a4fe0c4a9717e51142605a0447d31 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 83f9ba68df9dbdb68fa1164265940c0e57a93d3e..34925bd0753568ae266a9d3c648a8adffaf744a8 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 bdafd809a88f109b5da4000c1b2627d3e4e0e2b8..0e795712b40956bc3f49de2a8303fff88f59db3e 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 910f82bbeecb4eff973a6684db1cbfaa62b966f8..d922e421b159247efa34ba5cc3cf76975844397d 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 height = match available_space.height { - AvailableSpace::Definite(height) => desired_height.min(height), - AvailableSpace::MinContent | AvailableSpace::MaxContent => desired_height, - }; - size(width, height) - }) - }); + 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) + }) + }); ( 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 6b68e5235ef3552af7db0438a42000c056599e59..d67f335fbb867654e6839d6b74233a73861e4509 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(); - - 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)); - } + cx.with_element_state::(global_id.unwrap(), |element_state, cx| { + 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, 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(); - - ( - 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) + 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), + 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 4d20b4a4414f043b5ebab32d8833c5a8bf99bfc8..d5aaed8586dc2a1150df0c577de9e2b6aa4c1188 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,14 +1875,27 @@ 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, - f: impl FnOnce(Option>, &mut Self) -> (R, Option), + global_id: &GlobalElementId, + f: impl FnOnce(Option, &mut Self) -> (R, S), ) -> R where S: 'static, @@ -1922,90 +1907,110 @@ 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 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)] { - let ElementStateBox { - inner, - #[cfg(debug_assertions)] + anyhow::anyhow!( + "invalid element state type for id, requested {:?}, actual: {:?}", + std::any::type_name::(), 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 + ) } - } + + #[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 + 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" + ); + + 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 b0bee7fa5e708ca8ef0df5fa5d36db1a3635b0f7..ba457bd7a0b11e7e8bb9e14b164e5a753ee7362d 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 0d842d2a035aade531fa42dd8a0905b0d7d7b735..05bceda2d0724a6846223918544960e52a0a48d1 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; - - 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(); - - 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 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), - ); - - ( - layout_id, - PopoverMenuFrameState { - child_element, - child_layout_id, - menu_element, - }, - ) - }) + 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(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 + }); + + 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 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, + }, + ), + 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 e656835c1d84cf5d167632f2b8bff2042ae02b07..367841286ff9730bcd31430fad3d9da1eccd35e0 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 9ad7acf734c21faa8f654fcba49e2b0b8edfe212..062bbbe02eece9bd4e591eb0ada4876d67f06205 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 d2b042668ed1d6721a5e4347b6c3ce321d93b657..1377c5519bcf07d28a0b60bc532fb1a5090cd5dd 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 25a20ec0ce266d1b99d39697cb3380778c37606f..dbeb7eb4275f3affa12ba0f258f4693ceb4bb57a 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,