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,