diff --git a/crates/editor2/src/element.rs b/crates/editor2/src/element.rs index 3af6281121..bae148092d 100644 --- a/crates/editor2/src/element.rs +++ b/crates/editor2/src/element.rs @@ -889,7 +889,7 @@ impl EditorElement { } let fold_corner_radius = 0.15 * layout.position_map.line_height; - cx.with_element_id(Some("folds"), |cx| { + cx.with_element_id(Some("folds"), |_, cx| { let snapshot = &layout.position_map.snapshot; for fold in snapshot.folds_in_range(layout.visible_anchor_range.clone()) { let fold_range = fold.range.clone(); @@ -938,7 +938,7 @@ impl EditorElement { fold_bounds.size, cx, |fold_element_state, cx| { - if fold_element_state.is_active() { + if fold_element_state.is_active { cx.theme().colors().ghost_element_active } else if fold_bounds.contains(&cx.mouse_position()) { cx.theme().colors().ghost_element_hover @@ -1996,7 +1996,7 @@ impl EditorElement { .width; let scroll_width = longest_line_width.max(max_visible_line_width) + overscroll.width; - let (scroll_width, blocks) = cx.with_element_id(Some("editor_blocks"), |cx| { + let (scroll_width, blocks) = cx.with_element_id(Some("editor_blocks"), |_, cx| { self.layout_blocks( start_row..end_row, &snapshot, @@ -2081,7 +2081,7 @@ impl EditorElement { cx, ); - let mut fold_indicators = cx.with_element_id(Some("gutter_fold_indicators"), |cx| { + let mut fold_indicators = cx.with_element_id(Some("gutter_fold_indicators"), |_, cx| { editor.render_fold_indicators( fold_statuses, &style, @@ -2734,13 +2734,9 @@ enum Invisible { } impl Element for EditorElement { - type State = (); + type FrameState = (); - fn layout( - &mut self, - element_state: Option, - cx: &mut gpui::WindowContext, - ) -> (gpui::LayoutId, Self::State) { + fn layout(&mut self, cx: &mut gpui::WindowContext) -> (gpui::LayoutId, Self::FrameState) { self.editor.update(cx, |editor, cx| { editor.set_style(self.style.clone(), cx); @@ -2788,7 +2784,7 @@ impl Element for EditorElement { fn paint( &mut self, bounds: Bounds, - element_state: &mut Self::State, + _element_state: &mut Self::FrameState, cx: &mut gpui::WindowContext, ) { let editor = self.editor.clone(); @@ -2823,7 +2819,7 @@ impl Element for EditorElement { self.paint_mouse_listeners(bounds, gutter_bounds, text_bounds, &layout, cx); if !layout.blocks.is_empty() { - cx.with_element_id(Some("editor_blocks"), |cx| { + cx.with_element_id(Some("editor_blocks"), |_, cx| { self.paint_blocks(bounds, &mut layout, cx); }); } diff --git a/crates/gpui2/src/element.rs b/crates/gpui2/src/element.rs index 6f739d8e33..8ea93ae003 100644 --- a/crates/gpui2/src/element.rs +++ b/crates/gpui2/src/element.rs @@ -4,7 +4,7 @@ use crate::{ }; use derive_more::{Deref, DerefMut}; pub(crate) use smallvec::SmallVec; -use std::{any::Any, fmt::Debug}; +use std::{any::Any, fmt::Debug, mem}; pub trait Render: 'static + Sized { type Element: Element + 'static; @@ -28,30 +28,20 @@ pub trait IntoElement: Sized { origin: Point, available_space: Size, cx: &mut WindowContext, - f: impl FnOnce(&mut ::State, &mut WindowContext) -> R, + f: impl FnOnce(&mut ::FrameState, &mut WindowContext) -> R, ) -> R where T: Clone + Default + Debug + Into, { let element = self.into_element(); - let element_id = element.element_id(); let element = DrawableElement { element: Some(element), phase: ElementDrawPhase::Start, }; - let frame_state = + let mut frame_state = DrawableElement::draw(element, origin, available_space.map(Into::into), cx); - - if let Some(mut frame_state) = frame_state { - f(&mut frame_state, cx) - } else { - cx.with_element_state(element_id.unwrap(), |element_state, cx| { - let mut element_state = element_state.unwrap(); - let result = f(&mut element_state, cx); - (result, element_state) - }) - } + f(&mut frame_state, cx) } fn map(self, f: impl FnOnce(Self) -> U) -> U @@ -84,15 +74,16 @@ pub trait IntoElement: Sized { } pub trait Element: 'static + IntoElement { - type State: 'static; + type FrameState: 'static; - fn layout( + fn layout(&mut self, cx: &mut WindowContext) -> (LayoutId, Self::FrameState); + + fn paint( &mut self, - state: Option, + bounds: Bounds, + state: &mut Self::FrameState, cx: &mut WindowContext, - ) -> (LayoutId, Self::State); - - fn paint(&mut self, bounds: Bounds, state: &mut Self::State, cx: &mut WindowContext); + ); fn into_any(self) -> AnyElement { AnyElement::new(self) @@ -111,7 +102,7 @@ pub struct Component { pub struct CompositeElementState { rendered_element: Option<::Element>, - rendered_element_state: Option<<::Element as Element>::State>, + rendered_element_state: Option<<::Element as Element>::FrameState>, } impl Component { @@ -123,48 +114,30 @@ impl Component { } impl Element for Component { - type State = CompositeElementState; + type FrameState = CompositeElementState; - fn layout( - &mut self, - state: Option, - cx: &mut WindowContext, - ) -> (LayoutId, Self::State) { + fn layout(&mut self, cx: &mut WindowContext) -> (LayoutId, Self::FrameState) { let mut element = self.component.take().unwrap().render(cx).into_element(); - if let Some(element_id) = element.element_id() { - let layout_id = - cx.with_element_state(element_id, |state, cx| element.layout(state, cx)); - let state = CompositeElementState { - rendered_element: Some(element), - rendered_element_state: None, - }; - (layout_id, state) - } else { - let (layout_id, state) = - element.layout(state.and_then(|s| s.rendered_element_state), cx); - let state = CompositeElementState { - rendered_element: Some(element), - rendered_element_state: Some(state), - }; - (layout_id, state) - } + let (layout_id, state) = element.layout(cx); + let state = CompositeElementState { + rendered_element: Some(element), + rendered_element_state: Some(state), + }; + (layout_id, state) } - fn paint(&mut self, bounds: Bounds, state: &mut Self::State, cx: &mut WindowContext) { + fn paint( + &mut self, + bounds: Bounds, + state: &mut Self::FrameState, + cx: &mut WindowContext, + ) { let mut element = state.rendered_element.take().unwrap(); - if let Some(element_id) = element.element_id() { - cx.with_element_state(element_id, |element_state, cx| { - let mut element_state = element_state.unwrap(); - element.paint(bounds, &mut element_state, cx); - ((), element_state) - }); - } else { - element.paint( - bounds, - &mut state.rendered_element_state.as_mut().unwrap(), - cx, - ); - } + element.paint( + bounds, + &mut state.rendered_element_state.as_mut().unwrap(), + cx, + ); } } @@ -227,7 +200,7 @@ trait ElementObject { pub struct DrawableElement { element: Option, - phase: ElementDrawPhase, + phase: ElementDrawPhase, } #[derive(Default)] @@ -236,12 +209,12 @@ enum ElementDrawPhase { Start, LayoutRequested { layout_id: LayoutId, - frame_state: Option, + frame_state: S, }, LayoutComputed { layout_id: LayoutId, available_space: Size, - frame_state: Option, + frame_state: S, }, } @@ -259,17 +232,7 @@ impl DrawableElement { } fn layout(&mut self, cx: &mut WindowContext) -> LayoutId { - let (layout_id, frame_state) = if let Some(id) = self.element.as_ref().unwrap().element_id() - { - let layout_id = cx.with_element_state(id, |element_state, cx| { - self.element.as_mut().unwrap().layout(element_state, cx) - }); - (layout_id, None) - } else { - let (layout_id, frame_state) = self.element.as_mut().unwrap().layout(None, cx); - (layout_id, Some(frame_state)) - }; - + let (layout_id, frame_state) = self.element.as_mut().unwrap().layout(cx); self.phase = ElementDrawPhase::LayoutRequested { layout_id, frame_state, @@ -277,42 +240,23 @@ impl DrawableElement { layout_id } - fn paint(mut self, cx: &mut WindowContext) -> Option { + fn paint(mut self, cx: &mut WindowContext) -> E::FrameState { match self.phase { ElementDrawPhase::LayoutRequested { layout_id, - frame_state, + mut frame_state, } | ElementDrawPhase::LayoutComputed { layout_id, - frame_state, + mut frame_state, .. } => { let bounds = cx.layout_bounds(layout_id); - - if let Some(mut frame_state) = frame_state { - self.element - .take() - .unwrap() - .paint(bounds, &mut frame_state, cx); - Some(frame_state) - } else { - let element_id = self - .element - .as_ref() - .unwrap() - .element_id() - .expect("if we don't have frame state, we should have element state"); - cx.with_element_state(element_id, |element_state, cx| { - let mut element_state = element_state.unwrap(); - self.element - .take() - .unwrap() - .paint(bounds, &mut element_state, cx); - ((), element_state) - }); - None - } + self.element + .take() + .unwrap() + .paint(bounds, &mut frame_state, cx); + frame_state } _ => panic!("must call layout before paint"), @@ -328,30 +272,34 @@ impl DrawableElement { self.layout(cx); } - let layout_id = match &mut self.phase { + let layout_id = match mem::take(&mut self.phase) { ElementDrawPhase::LayoutRequested { layout_id, frame_state, } => { - cx.compute_layout(*layout_id, available_space); - let layout_id = *layout_id; + cx.compute_layout(layout_id, available_space); self.phase = ElementDrawPhase::LayoutComputed { layout_id, available_space, - frame_state: frame_state.take(), + frame_state, }; layout_id } ElementDrawPhase::LayoutComputed { layout_id, available_space: prev_available_space, - .. + frame_state, } => { - if available_space != *prev_available_space { - cx.compute_layout(*layout_id, available_space); - *prev_available_space = available_space; + if available_space != prev_available_space { + cx.compute_layout(layout_id, available_space); } - *layout_id + self.phase = ElementDrawPhase::LayoutComputed { + layout_id, + available_space, + frame_state, + }; + + layout_id } _ => panic!("cannot measure after painting"), }; @@ -364,7 +312,7 @@ impl DrawableElement { origin: Point, available_space: Size, cx: &mut WindowContext, - ) -> Option { + ) -> E::FrameState { self.measure(available_space, cx); cx.with_absolute_element_offset(origin, |cx| self.paint(cx)) } @@ -373,7 +321,7 @@ impl DrawableElement { impl ElementObject for Option> where E: Element, - E::State: 'static, + E::FrameState: 'static, { fn element_id(&self) -> Option { self.as_ref().unwrap().element_id() @@ -411,7 +359,7 @@ impl AnyElement { pub fn new(element: E) -> Self where E: 'static + Element, - E::State: Any, + E::FrameState: Any, { let element = frame_alloc(|| Some(DrawableElement::new(element))) .map(|element| element as &mut dyn ElementObject); @@ -451,18 +399,14 @@ impl AnyElement { } impl Element for AnyElement { - type State = (); + type FrameState = (); - fn layout( - &mut self, - _: Option, - cx: &mut WindowContext, - ) -> (LayoutId, Self::State) { + fn layout(&mut self, cx: &mut WindowContext) -> (LayoutId, Self::FrameState) { let layout_id = self.layout(cx); (layout_id, ()) } - fn paint(&mut self, _: Bounds, _: &mut Self::State, cx: &mut WindowContext) { + fn paint(&mut self, _: Bounds, _: &mut Self::FrameState, cx: &mut WindowContext) { self.paint(cx) } } @@ -499,20 +443,16 @@ impl IntoElement for () { } impl Element for () { - type State = (); + type FrameState = (); - fn layout( - &mut self, - _state: Option, - cx: &mut WindowContext, - ) -> (LayoutId, Self::State) { + fn layout(&mut self, cx: &mut WindowContext) -> (LayoutId, Self::FrameState) { (cx.request_layout(&crate::Style::default(), None), ()) } fn paint( &mut self, _bounds: Bounds, - _state: &mut Self::State, + _state: &mut Self::FrameState, _cx: &mut WindowContext, ) { } diff --git a/crates/gpui2/src/elements/canvas.rs b/crates/gpui2/src/elements/canvas.rs index d04c65811f..06bcb54cd1 100644 --- a/crates/gpui2/src/elements/canvas.rs +++ b/crates/gpui2/src/elements/canvas.rs @@ -27,13 +27,9 @@ impl IntoElement for Canvas { } impl Element for Canvas { - type State = Style; + type FrameState = Style; - fn layout( - &mut self, - _: Option, - cx: &mut WindowContext, - ) -> (crate::LayoutId, Self::State) { + fn layout(&mut self, cx: &mut WindowContext) -> (crate::LayoutId, Self::FrameState) { let mut style = Style::default(); style.refine(&self.style); let layout_id = cx.request_layout(&style, []); diff --git a/crates/gpui2/src/elements/div.rs b/crates/gpui2/src/elements/div.rs index 1f56f44900..cd5257e868 100644 --- a/crates/gpui2/src/elements/div.rs +++ b/crates/gpui2/src/elements/div.rs @@ -1,9 +1,10 @@ use crate::{ point, px, Action, AnyDrag, AnyElement, AnyTooltip, AnyView, AppContext, BorrowAppContext, - BorrowWindow, Bounds, ClickEvent, DispatchPhase, Element, ElementId, FocusHandle, IntoElement, - KeyContext, KeyDownEvent, KeyUpEvent, LayoutId, MouseButton, MouseDownEvent, MouseMoveEvent, - MouseUpEvent, ParentElement, Pixels, Point, Render, ScrollWheelEvent, SharedString, Size, - StackingOrder, Style, StyleRefinement, Styled, Task, View, Visibility, WindowContext, + BorrowWindow, Bounds, ClickEvent, DispatchPhase, Element, ElementId, FocusHandle, + GlobalElementId, IntoElement, KeyContext, KeyDownEvent, KeyUpEvent, LayoutId, MouseButton, + MouseDownEvent, MouseMoveEvent, MouseUpEvent, ParentElement, Pixels, Point, Render, + ScrollWheelEvent, SharedString, Size, StackingOrder, Style, StyleRefinement, Styled, Task, + View, Visibility, WindowContext, }; use collections::HashMap; @@ -704,33 +705,29 @@ impl ParentElement for Div { } impl Element for Div { - type State = DivState; + type FrameState = DivState; - fn layout( - &mut self, - element_state: Option, - cx: &mut WindowContext, - ) -> (LayoutId, Self::State) { + fn layout(&mut self, cx: &mut WindowContext) -> (LayoutId, Self::FrameState) { let mut child_layout_ids = SmallVec::new(); - let (layout_id, interactive_state) = self.interactivity.layout( - element_state.map(|s| s.interactive_state), - cx, - |style, cx| { - cx.with_text_style(style.text_style().cloned(), |cx| { - child_layout_ids = self - .children - .iter_mut() - .map(|child| child.layout(cx)) - .collect::>(); - cx.request_layout(&style, child_layout_ids.iter().copied()) - }) - }, - ); + let mut is_active = false; + let layout_id = self.interactivity.layout(cx, |style, element_state, cx| { + cx.with_text_style(style.text_style().cloned(), |cx| { + child_layout_ids = self + .children + .iter_mut() + .map(|child| child.layout(cx)) + .collect::>(); + is_active = element_state.as_ref().map_or(false, |(_, element_state)| { + element_state.pending_mouse_down.is_some() + }); + cx.request_layout(&style, child_layout_ids.iter().copied()) + }) + }); ( layout_id, DivState { - interactive_state, child_layout_ids, + is_active, }, ) } @@ -738,7 +735,7 @@ impl Element for Div { fn paint( &mut self, bounds: Bounds, - element_state: &mut Self::State, + element_state: &mut Self::FrameState, cx: &mut WindowContext, ) { let mut child_min = point(Pixels::MAX, Pixels::MAX); @@ -774,12 +771,8 @@ impl Element for Div { (child_max - child_min).into() }; - self.interactivity.paint( - bounds, - content_size, - &mut element_state.interactive_state, - cx, - |style, scroll_offset, cx| { + self.interactivity + .paint(bounds, content_size, cx, |style, _, scroll_offset, cx| { let z_index = style.z_index.unwrap_or(0); cx.with_z_index(z_index, |cx| { @@ -795,8 +788,7 @@ impl Element for Div { }) }); }) - }, - ); + }); } } @@ -814,16 +806,7 @@ impl IntoElement for Div { pub struct DivState { child_layout_ids: SmallVec<[LayoutId; 2]>, - interactive_state: InteractiveElementState, -} - -impl DivState { - pub fn is_active(&self) -> bool { - self.interactive_state - .pending_mouse_down - .as_ref() - .map_or(false, |pending| pending.borrow().is_some()) - } + pub is_active: bool, } pub struct Interactivity { @@ -879,43 +862,70 @@ impl InteractiveBounds { impl Interactivity { pub fn layout( &mut self, - element_state: Option, cx: &mut WindowContext, - f: impl FnOnce(Style, &mut WindowContext) -> LayoutId, - ) -> (LayoutId, InteractiveElementState) { - let mut element_state = element_state.unwrap_or_default(); + f: impl FnOnce( + Style, + Option<(&GlobalElementId, &mut InteractiveElementState)>, + &mut WindowContext, + ) -> LayoutId, + ) -> LayoutId { + let style = self.compute_style(None, cx); + self.with_element_state(cx, |this, mut element_state, cx| { + if let Some((_, element_state)) = element_state.as_mut() { + // Ensure a focus handle exists if we're focusable. + // If there's an explicit focus handle we're tracking, use that. Otherwise + // create a new handle and store it in the element state, which lives for as + // as frames contain an element with this id. + if this.focusable { + this.tracked_focus_handle.get_or_insert_with(|| { + element_state + .focus_handle + .get_or_insert_with(|| cx.focus_handle()) + .clone() + }); + } - // Ensure we store a focus handle in our element state if we're focusable. - // If there's an explicit focus handle we're tracking, use that. Otherwise - // create a new handle and store it in the element state, which lives for as - // as frames contain an element with this id. - if self.focusable { - element_state.focus_handle.get_or_insert_with(|| { - self.tracked_focus_handle - .clone() - .unwrap_or_else(|| cx.focus_handle()) - }); - } + if let Some(scroll_handle) = this.scroll_handle.as_ref() { + element_state.scroll_offset = Some(scroll_handle.0.borrow().offset.clone()); + } + } - if let Some(scroll_handle) = self.scroll_handle.as_ref() { - element_state.scroll_offset = Some(scroll_handle.0.borrow().offset.clone()); - } - - let style = self.compute_style(None, &mut element_state, cx); - let layout_id = f(style, cx); - (layout_id, element_state) + f(style, element_state, cx) + }) } pub fn paint( &mut self, bounds: Bounds, content_size: Size, - element_state: &mut InteractiveElementState, cx: &mut WindowContext, - f: impl FnOnce(Style, Point, &mut WindowContext), + f: impl FnOnce( + Style, + Option<(&GlobalElementId, &mut InteractiveElementState)>, + Point, + &mut WindowContext, + ), ) { - let style = self.compute_style(Some(bounds), element_state, cx); + let style = self.compute_style(Some(bounds), cx); + self.with_element_state(cx, |this, element_state, cx| { + this.paint_internal(style, bounds, content_size, element_state, cx, f) + }) + } + fn paint_internal( + &mut self, + style: Style, + bounds: Bounds, + content_size: Size, + mut element_state: Option<(&GlobalElementId, &mut InteractiveElementState)>, + cx: &mut WindowContext, + f: impl FnOnce( + Style, + Option<(&GlobalElementId, &mut InteractiveElementState)>, + Point, + &mut WindowContext, + ), + ) { if style.visibility == Visibility::Hidden { return; } @@ -1052,10 +1062,10 @@ impl Interactivity { } } - // If this element can be focused, register a mouse down listener + // If self element can be focused, register a mouse down listener // that will automatically transfer focus when hitting the element. // This behavior can be suppressed by using `cx.prevent_default()`. - if let Some(focus_handle) = element_state.focus_handle.clone() { + if let Some(focus_handle) = self.tracked_focus_handle.clone() { cx.on_mouse_event({ let interactive_bounds = interactive_bounds.clone(); move |event: &MouseDownEvent, phase, cx| { @@ -1164,263 +1174,301 @@ impl Interactivity { } } - let click_listeners = mem::take(&mut self.click_listeners); - let mut drag_listener = mem::take(&mut self.drag_listener); + if let Some((global_element_id, element_state)) = element_state.as_mut() { + let click_listeners = mem::take(&mut self.click_listeners); + let mut drag_listener = mem::take(&mut self.drag_listener); - if !click_listeners.is_empty() || drag_listener.is_some() { - let pending_mouse_down = element_state - .pending_mouse_down - .get_or_insert_with(Default::default) - .clone(); - let mouse_down = pending_mouse_down.borrow().clone(); - if let Some(mouse_down) = mouse_down { - if drag_listener.is_some() { - let active_state = element_state - .clicked_state - .get_or_insert_with(Default::default) - .clone(); - let interactive_bounds = interactive_bounds.clone(); + if !click_listeners.is_empty() || drag_listener.is_some() { + let global_element_id = global_element_id.clone(); + if let Some(mouse_down) = element_state.pending_mouse_down.clone() { + if drag_listener.is_some() { + let interactive_bounds = interactive_bounds.clone(); + let global_element_id = global_element_id.clone(); + cx.on_mouse_event(move |event: &MouseMoveEvent, phase, cx| { + if cx.active_drag.is_some() { + if phase == DispatchPhase::Capture { + cx.notify(); + } + } else if phase == DispatchPhase::Bubble + && interactive_bounds.visibly_contains(&event.position, cx) + && (event.position - mouse_down.position).magnitude() + > DRAG_THRESHOLD + { + let (drag_value, drag_listener) = drag_listener + .take() + .expect("The notify below should invalidate self callback"); - cx.on_mouse_event(move |event: &MouseMoveEvent, phase, cx| { - if cx.active_drag.is_some() { - if phase == DispatchPhase::Capture { + cx.with_element_state::( + &global_element_id, + |element_state, _cx| { + if let Some(element_state) = element_state { + element_state.clicked_state = + ElementClickedState::default(); + } + }, + ); + + let cursor_offset = event.position - bounds.origin; + let drag = (drag_listener)(drag_value.as_ref(), cx); + cx.active_drag = Some(AnyDrag { + view: drag, + value: drag_value, + cursor_offset, + }); cx.notify(); + cx.stop_propagation(); } - } else if phase == DispatchPhase::Bubble - && interactive_bounds.visibly_contains(&event.position, cx) - && (event.position - mouse_down.position).magnitude() > DRAG_THRESHOLD - { - let (drag_value, drag_listener) = drag_listener - .take() - .expect("The notify below should invalidate this callback"); + }); + } - *active_state.borrow_mut() = ElementClickedState::default(); - let cursor_offset = event.position - bounds.origin; - let drag = (drag_listener)(drag_value.as_ref(), cx); - cx.active_drag = Some(AnyDrag { - view: drag, - value: drag_value, - cursor_offset, - }); + let interactive_bounds = interactive_bounds.clone(); + cx.on_mouse_event(move |event: &MouseUpEvent, phase, cx| { + if phase == DispatchPhase::Bubble + && interactive_bounds.visibly_contains(&event.position, cx) + { + let mouse_click = ClickEvent { + down: mouse_down.clone(), + up: event.clone(), + }; + for listener in &click_listeners { + listener(&mouse_click, cx); + } + } + + cx.with_element_state::( + &global_element_id, + |element_state, _cx| { + if let Some(element_state) = element_state { + element_state.pending_mouse_down = None; + } + }, + ); + + cx.notify(); + }); + } else { + let interactive_bounds = interactive_bounds.clone(); + cx.on_mouse_event(move |event: &MouseDownEvent, phase, cx| { + if phase == DispatchPhase::Bubble + && event.button == MouseButton::Left + && interactive_bounds.visibly_contains(&event.position, cx) + { + cx.with_element_state::( + &global_element_id, + |element_state, _cx| { + if let Some(element_state) = element_state { + element_state.pending_mouse_down = Some(event.clone()); + } + }, + ); + cx.notify(); + } + }); + } + } + + if let Some(hover_listener) = self.hover_listener.take() { + let interactive_bounds = interactive_bounds.clone(); + let global_element_id = global_element_id.clone(); + cx.on_mouse_event(move |event: &MouseMoveEvent, phase, cx| { + if phase != DispatchPhase::Bubble { + return; + } + + cx.with_element_state::( + &global_element_id, + |element_state, cx| { + if let Some(element_state) = element_state { + let is_hovered = interactive_bounds + .visibly_contains(&event.position, cx) + && element_state.pending_mouse_down.is_none(); + if is_hovered != element_state.hover_state { + element_state.hover_state = is_hovered; + hover_listener(&is_hovered, cx); + } + } + }, + ); + }); + } + + if let Some(tooltip_builder) = self.tooltip_builder.take() { + let interactive_bounds = interactive_bounds.clone(); + + cx.on_mouse_event({ + let global_element_id = global_element_id.clone(); + move |event: &MouseMoveEvent, phase, cx| { + cx.with_element_state::( + &global_element_id, + |element_state, cx| { + if let Some(element_state) = element_state { + let is_hovered = interactive_bounds + .visibly_contains(&event.position, cx) + && element_state.pending_mouse_down.is_none(); + if !is_hovered { + element_state.active_tooltip = None; + return; + } + + if phase != DispatchPhase::Bubble { + return; + } + + if element_state.active_tooltip.is_none() { + let task = cx.spawn({ + let global_element_id = global_element_id.clone(); + let tooltip_builder = tooltip_builder.clone(); + move |mut cx| async move { + cx.background_executor().timer(TOOLTIP_DELAY).await; + cx.update(|_, cx| { + cx.with_element_state::( + &global_element_id, + |element_state, cx| { + if let Some(element_state) = element_state { + element_state.active_tooltip = Some( + ActiveTooltip { + tooltip: Some(AnyTooltip { + view: tooltip_builder(cx), + cursor_offset: cx.mouse_position(), + }), + _task: None, + }, + ); + cx.notify(); + } + }); + }) + .ok(); + } + }); + element_state.active_tooltip = Some(ActiveTooltip { + tooltip: None, + _task: Some(task), + }); + } + } + }, + ); + } + }); + + let global_element_id = global_element_id.clone(); + cx.on_mouse_event(move |_: &MouseDownEvent, _, cx| { + cx.with_element_state::( + &global_element_id, + |element_state, _cx| { + if let Some(element_state) = element_state { + element_state.active_tooltip = None; + } + }, + ); + }); + + if let Some(active_tooltip) = element_state.active_tooltip.as_ref() { + if active_tooltip.tooltip.is_some() { + cx.active_tooltip = active_tooltip.tooltip.clone() + } + } + } + + if element_state.clicked_state.is_clicked() { + let global_element_id = global_element_id.clone(); + cx.on_mouse_event(move |_: &MouseUpEvent, phase, cx| { + if phase == DispatchPhase::Capture { + cx.with_element_state::( + &global_element_id, + |element_state, cx| { + if let Some(element_state) = element_state { + element_state.clicked_state = ElementClickedState::default(); + cx.notify(); + } + }, + ); + } + }); + } else { + let active_group_bounds = self + .group_active_style + .as_ref() + .and_then(|group_active| GroupBounds::get(&group_active.group, cx)); + let interactive_bounds = interactive_bounds.clone(); + let global_element_id = global_element_id.clone(); + cx.on_mouse_event(move |down: &MouseDownEvent, phase, cx| { + if phase == DispatchPhase::Bubble && !cx.default_prevented() { + let group = active_group_bounds + .map_or(false, |bounds| bounds.contains(&down.position)); + let element = interactive_bounds.visibly_contains(&down.position, cx); + if group || element { + cx.with_element_state::( + &global_element_id, + |element_state, cx| { + if let Some(element_state) = element_state { + element_state.clicked_state = + ElementClickedState { group, element }; + cx.notify(); + } + }, + ); + } + } + }); + } + + let overflow = style.overflow; + if overflow.x == Overflow::Scroll || overflow.y == Overflow::Scroll { + if let Some(scroll_handle) = &self.scroll_handle { + scroll_handle.0.borrow_mut().overflow = overflow; + } + + let scroll_offset = element_state + .scroll_offset + .get_or_insert_with(Rc::default) + .clone(); + let line_height = cx.line_height(); + let scroll_max = (content_size - bounds.size).max(&Size::default()); + let interactive_bounds = interactive_bounds.clone(); + + cx.on_mouse_event(move |event: &ScrollWheelEvent, phase, cx| { + if phase == DispatchPhase::Bubble + && interactive_bounds.visibly_contains(&event.position, cx) + { + let mut scroll_offset = scroll_offset.borrow_mut(); + let old_scroll_offset = *scroll_offset; + let delta = event.delta.pixel_delta(line_height); + + if overflow.x == Overflow::Scroll { + scroll_offset.x = + (scroll_offset.x + delta.x).clamp(-scroll_max.width, px(0.)); + } + + if overflow.y == Overflow::Scroll { + scroll_offset.y = + (scroll_offset.y + delta.y).clamp(-scroll_max.height, px(0.)); + } + + if *scroll_offset != old_scroll_offset { cx.notify(); cx.stop_propagation(); } - }); - } - - let interactive_bounds = interactive_bounds.clone(); - cx.on_mouse_event(move |event: &MouseUpEvent, phase, cx| { - if phase == DispatchPhase::Bubble - && interactive_bounds.visibly_contains(&event.position, cx) - { - let mouse_click = ClickEvent { - down: mouse_down.clone(), - up: event.clone(), - }; - for listener in &click_listeners { - listener(&mouse_click, cx); - } - } - *pending_mouse_down.borrow_mut() = None; - cx.notify(); - }); - } else { - let interactive_bounds = interactive_bounds.clone(); - cx.on_mouse_event(move |event: &MouseDownEvent, phase, cx| { - if phase == DispatchPhase::Bubble - && event.button == MouseButton::Left - && interactive_bounds.visibly_contains(&event.position, cx) - { - *pending_mouse_down.borrow_mut() = Some(event.clone()); - cx.notify(); } }); } - } - if let Some(hover_listener) = self.hover_listener.take() { - let was_hovered = element_state - .hover_state - .get_or_insert_with(Default::default) - .clone(); - let has_mouse_down = element_state - .pending_mouse_down - .get_or_insert_with(Default::default) - .clone(); - let interactive_bounds = interactive_bounds.clone(); - - cx.on_mouse_event(move |event: &MouseMoveEvent, phase, cx| { - if phase != DispatchPhase::Bubble { - return; - } - let is_hovered = interactive_bounds.visibly_contains(&event.position, cx) - && has_mouse_down.borrow().is_none(); - let mut was_hovered = was_hovered.borrow_mut(); - - if is_hovered != was_hovered.clone() { - *was_hovered = is_hovered; - drop(was_hovered); - - hover_listener(&is_hovered, cx); - } - }); - } - - if let Some(tooltip_builder) = self.tooltip_builder.take() { - let active_tooltip = element_state - .active_tooltip - .get_or_insert_with(Default::default) - .clone(); - let pending_mouse_down = element_state - .pending_mouse_down - .get_or_insert_with(Default::default) - .clone(); - let interactive_bounds = interactive_bounds.clone(); - - cx.on_mouse_event(move |event: &MouseMoveEvent, phase, cx| { - let is_hovered = interactive_bounds.visibly_contains(&event.position, cx) - && pending_mouse_down.borrow().is_none(); - if !is_hovered { - active_tooltip.borrow_mut().take(); - return; - } - - if phase != DispatchPhase::Bubble { - return; - } - - if active_tooltip.borrow().is_none() { - let task = cx.spawn({ - let active_tooltip = active_tooltip.clone(); - let tooltip_builder = tooltip_builder.clone(); - - move |mut cx| async move { - cx.background_executor().timer(TOOLTIP_DELAY).await; - cx.update(|_, cx| { - active_tooltip.borrow_mut().replace(ActiveTooltip { - tooltip: Some(AnyTooltip { - view: tooltip_builder(cx), - cursor_offset: cx.mouse_position(), - }), - _task: None, - }); - cx.notify(); - }) - .ok(); - } - }); - active_tooltip.borrow_mut().replace(ActiveTooltip { - tooltip: None, - _task: Some(task), - }); - } - }); - - let active_tooltip = element_state - .active_tooltip - .get_or_insert_with(Default::default) - .clone(); - cx.on_mouse_event(move |_: &MouseDownEvent, _, _| { - active_tooltip.borrow_mut().take(); - }); - - if let Some(active_tooltip) = element_state - .active_tooltip - .get_or_insert_with(Default::default) - .borrow() - .as_ref() - { - if active_tooltip.tooltip.is_some() { - cx.active_tooltip = active_tooltip.tooltip.clone() - } + if let Some(group) = self.group.clone() { + GroupBounds::push(group, bounds, cx); } } - let active_state = element_state - .clicked_state - .get_or_insert_with(Default::default) - .clone(); - if active_state.borrow().is_clicked() { - cx.on_mouse_event(move |_: &MouseUpEvent, phase, cx| { - if phase == DispatchPhase::Capture { - *active_state.borrow_mut() = ElementClickedState::default(); - cx.notify(); - } - }); - } else { - let active_group_bounds = self - .group_active_style - .as_ref() - .and_then(|group_active| GroupBounds::get(&group_active.group, cx)); - let interactive_bounds = interactive_bounds.clone(); - cx.on_mouse_event(move |down: &MouseDownEvent, phase, cx| { - if phase == DispatchPhase::Bubble && !cx.default_prevented() { - let group = - active_group_bounds.map_or(false, |bounds| bounds.contains(&down.position)); - let element = interactive_bounds.visibly_contains(&down.position, cx); - if group || element { - *active_state.borrow_mut() = ElementClickedState { group, element }; - cx.notify(); - } - } - }); - } - - let overflow = style.overflow; - if overflow.x == Overflow::Scroll || overflow.y == Overflow::Scroll { - if let Some(scroll_handle) = &self.scroll_handle { - scroll_handle.0.borrow_mut().overflow = overflow; - } - - let scroll_offset = element_state - .scroll_offset - .get_or_insert_with(Rc::default) - .clone(); - let line_height = cx.line_height(); - let scroll_max = (content_size - bounds.size).max(&Size::default()); - let interactive_bounds = interactive_bounds.clone(); - - cx.on_mouse_event(move |event: &ScrollWheelEvent, phase, cx| { - if phase == DispatchPhase::Bubble - && interactive_bounds.visibly_contains(&event.position, cx) - { - let mut scroll_offset = scroll_offset.borrow_mut(); - let old_scroll_offset = *scroll_offset; - let delta = event.delta.pixel_delta(line_height); - - if overflow.x == Overflow::Scroll { - scroll_offset.x = - (scroll_offset.x + delta.x).clamp(-scroll_max.width, px(0.)); - } - - if overflow.y == Overflow::Scroll { - scroll_offset.y = - (scroll_offset.y + delta.y).clamp(-scroll_max.height, px(0.)); - } - - if *scroll_offset != old_scroll_offset { - cx.notify(); - cx.stop_propagation(); - } - } - }); - } - - if let Some(group) = self.group.clone() { - GroupBounds::push(group, bounds, cx); - } - let scroll_offset = element_state - .scroll_offset .as_ref() - .map(|scroll_offset| *scroll_offset.borrow()); + .and_then(|(_, element_state)| Some(*element_state.scroll_offset.as_ref()?.borrow())); let key_down_listeners = mem::take(&mut self.key_down_listeners); let key_up_listeners = mem::take(&mut self.key_up_listeners); let action_listeners = mem::take(&mut self.action_listeners); cx.with_key_dispatch( self.key_context.clone(), - element_state.focus_handle.clone(), + self.tracked_focus_handle.clone(), |_, cx| { for listener in key_down_listeners { cx.on_key_event(move |event: &KeyDownEvent, phase, cx| { @@ -1438,7 +1486,7 @@ impl Interactivity { cx.on_action(action_type, listener) } - f(style, scroll_offset.unwrap_or_default(), cx) + f(style, element_state, scroll_offset.unwrap_or_default(), cx) }, ); @@ -1447,10 +1495,31 @@ impl Interactivity { } } + fn with_element_state( + &mut self, + cx: &mut WindowContext, + f: impl FnOnce( + &mut Self, + Option<(&GlobalElementId, &mut InteractiveElementState)>, + &mut WindowContext, + ) -> R, + ) -> R { + cx.with_element_id(self.element_id.clone(), |global_element_id, cx| { + if let Some(global_element_id) = global_element_id { + cx.with_element_state(&global_element_id, |element_state, cx| { + let mut element_state = + element_state.get_or_insert_with(Box::::default); + f(self, Some((&global_element_id, &mut element_state)), cx) + }) + } else { + f(self, None, cx) + } + }) + } + pub fn compute_style( - &self, + &mut self, bounds: Option>, - element_state: &mut InteractiveElementState, cx: &mut WindowContext, ) -> Style { let mut style = Style::default(); @@ -1521,21 +1590,21 @@ impl Interactivity { } } - let clicked_state = element_state - .clicked_state - .get_or_insert_with(Default::default) - .borrow(); - if clicked_state.group { - if let Some(group) = self.group_active_style.as_ref() { - style.refine(&group.style) - } - } + self.with_element_state(cx, |this, element_state, _cx| { + if let Some((_, element_state)) = element_state { + if element_state.clicked_state.group { + if let Some(group) = this.group_active_style.as_ref() { + style.refine(&group.style) + } + } - if let Some(active_style) = self.active_style.as_ref() { - if clicked_state.element { - style.refine(active_style) + if let Some(active_style) = this.active_style.as_ref() { + if element_state.clicked_state.element { + style.refine(active_style) + } + } } - } + }) }); style @@ -1583,11 +1652,11 @@ impl Default for Interactivity { #[derive(Default)] pub struct InteractiveElementState { pub focus_handle: Option, - pub clicked_state: Option>>, - pub hover_state: Option>>, - pub pending_mouse_down: Option>>>, + pub clicked_state: ElementClickedState, + pub hover_state: bool, + pub pending_mouse_down: Option, pub scroll_offset: Option>>>, - pub active_tooltip: Option>>>, + pub active_tooltip: Option, } pub struct ActiveTooltip { @@ -1663,17 +1732,18 @@ impl Element for Focusable where E: Element, { - type State = E::State; + type FrameState = E::FrameState; - fn layout( - &mut self, - state: Option, - cx: &mut WindowContext, - ) -> (LayoutId, Self::State) { - self.element.layout(state, cx) + fn layout(&mut self, cx: &mut WindowContext) -> (LayoutId, Self::FrameState) { + self.element.layout(cx) } - fn paint(&mut self, bounds: Bounds, state: &mut Self::State, cx: &mut WindowContext) { + fn paint( + &mut self, + bounds: Bounds, + state: &mut Self::FrameState, + cx: &mut WindowContext, + ) { self.element.paint(bounds, state, cx) } } @@ -1737,17 +1807,18 @@ impl Element for Stateful where E: Element, { - type State = E::State; + type FrameState = E::FrameState; - fn layout( - &mut self, - state: Option, - cx: &mut WindowContext, - ) -> (LayoutId, Self::State) { - self.element.layout(state, cx) + fn layout(&mut self, cx: &mut WindowContext) -> (LayoutId, Self::FrameState) { + self.element.layout(cx) } - fn paint(&mut self, bounds: Bounds, state: &mut Self::State, cx: &mut WindowContext) { + fn paint( + &mut self, + bounds: Bounds, + state: &mut Self::FrameState, + cx: &mut WindowContext, + ) { self.element.paint(bounds, state, cx) } } diff --git a/crates/gpui2/src/elements/img.rs b/crates/gpui2/src/elements/img.rs index 4f81f604c8..4dd498d98f 100644 --- a/crates/gpui2/src/elements/img.rs +++ b/crates/gpui2/src/elements/img.rs @@ -1,9 +1,8 @@ use std::sync::Arc; use crate::{ - point, size, Bounds, DevicePixels, Element, ImageData, InteractiveElement, - InteractiveElementState, Interactivity, IntoElement, LayoutId, Pixels, SharedString, Size, - StyleRefinement, Styled, WindowContext, + point, size, Bounds, DevicePixels, Element, ImageData, InteractiveElement, Interactivity, + IntoElement, LayoutId, Pixels, SharedString, Size, StyleRefinement, Styled, WindowContext, }; use futures::FutureExt; use media::core_video::CVImageBuffer; @@ -69,30 +68,19 @@ impl Img { } impl Element for Img { - type State = InteractiveElementState; + type FrameState = (); - fn layout( - &mut self, - element_state: Option, - cx: &mut WindowContext, - ) -> (LayoutId, Self::State) { - self.interactivity - .layout(element_state, cx, |style, cx| cx.request_layout(&style, [])) + fn layout(&mut self, cx: &mut WindowContext) -> (LayoutId, Self::FrameState) { + let layout_id = self + .interactivity + .layout(cx, |style, _, cx| cx.request_layout(&style, [])); + (layout_id, ()) } - fn paint( - &mut self, - bounds: Bounds, - element_state: &mut Self::State, - cx: &mut WindowContext, - ) { + fn paint(&mut self, bounds: Bounds, _: &mut Self::FrameState, cx: &mut WindowContext) { let source = self.source.clone(); - self.interactivity.paint( - bounds, - bounds.size, - element_state, - cx, - |style, _scroll_offset, cx| { + self.interactivity + .paint(bounds, bounds.size, cx, |style, _, _, cx| { let corner_radii = style.corner_radii.to_pixels(bounds.size, cx.rem_size()); cx.with_z_index(1, |cx| { match source { @@ -130,8 +118,7 @@ impl Element for Img { } }; }); - }, - ) + }) } } diff --git a/crates/gpui2/src/elements/list.rs b/crates/gpui2/src/elements/list.rs index 73bd319afc..d9d3e1f023 100644 --- a/crates/gpui2/src/elements/list.rs +++ b/crates/gpui2/src/elements/list.rs @@ -300,13 +300,9 @@ pub struct ListOffset { } impl Element for List { - type State = (); + type FrameState = (); - fn layout( - &mut self, - _state: Option, - cx: &mut crate::WindowContext, - ) -> (crate::LayoutId, Self::State) { + fn layout(&mut self, cx: &mut crate::WindowContext) -> (crate::LayoutId, Self::FrameState) { let mut style = Style::default(); style.refine(&self.style); let layout_id = cx.with_text_style(style.text_style().cloned(), |cx| { @@ -318,7 +314,7 @@ impl Element for List { fn paint( &mut self, bounds: crate::Bounds, - _state: &mut Self::State, + _state: &mut Self::FrameState, cx: &mut crate::WindowContext, ) { let state = &mut *self.state.0.borrow_mut(); diff --git a/crates/gpui2/src/elements/overlay.rs b/crates/gpui2/src/elements/overlay.rs index 5b72019f17..9f6fe010f3 100644 --- a/crates/gpui2/src/elements/overlay.rs +++ b/crates/gpui2/src/elements/overlay.rs @@ -58,13 +58,9 @@ impl ParentElement for Overlay { } impl Element for Overlay { - type State = OverlayState; + type FrameState = OverlayState; - fn layout( - &mut self, - _: Option, - cx: &mut WindowContext, - ) -> (crate::LayoutId, Self::State) { + fn layout(&mut self, cx: &mut WindowContext) -> (crate::LayoutId, Self::FrameState) { let child_layout_ids = self .children .iter_mut() @@ -83,7 +79,7 @@ impl Element for Overlay { fn paint( &mut self, bounds: crate::Bounds, - element_state: &mut Self::State, + element_state: &mut Self::FrameState, cx: &mut WindowContext, ) { if element_state.child_layout_ids.is_empty() { diff --git a/crates/gpui2/src/elements/svg.rs b/crates/gpui2/src/elements/svg.rs index 9ca9baf470..474d9eb863 100644 --- a/crates/gpui2/src/elements/svg.rs +++ b/crates/gpui2/src/elements/svg.rs @@ -24,28 +24,25 @@ impl Svg { } impl Element for Svg { - type State = InteractiveElementState; + type FrameState = (); - fn layout( - &mut self, - element_state: Option, - cx: &mut WindowContext, - ) -> (LayoutId, Self::State) { - self.interactivity.layout(element_state, cx, |style, cx| { - cx.request_layout(&style, None) - }) + fn layout(&mut self, cx: &mut WindowContext) -> (LayoutId, Self::FrameState) { + let layout_id = self + .interactivity + .layout(cx, |style, _, cx| cx.request_layout(&style, None)); + (layout_id, ()) } fn paint( &mut self, bounds: Bounds, - element_state: &mut Self::State, + element_state: &mut Self::FrameState, cx: &mut WindowContext, ) where Self: Sized, { self.interactivity - .paint(bounds, bounds.size, element_state, cx, |style, _, cx| { + .paint(bounds, bounds.size, cx, |style, _, _, cx| { if let Some((path, color)) = self.path.as_ref().zip(style.text.color) { cx.paint_svg(bounds, path.clone(), color).log_err(); } diff --git a/crates/gpui2/src/elements/text.rs b/crates/gpui2/src/elements/text.rs index 175a79c19a..c1f87d2207 100644 --- a/crates/gpui2/src/elements/text.rs +++ b/crates/gpui2/src/elements/text.rs @@ -1,22 +1,18 @@ use crate::{ - Bounds, DispatchPhase, Element, ElementId, HighlightStyle, IntoElement, LayoutId, + BorrowWindow, Bounds, DispatchPhase, Element, ElementId, HighlightStyle, IntoElement, LayoutId, MouseDownEvent, MouseUpEvent, Pixels, Point, SharedString, Size, TextRun, TextStyle, WhiteSpace, WindowContext, WrappedLine, }; use anyhow::anyhow; use parking_lot::{Mutex, MutexGuard}; use smallvec::SmallVec; -use std::{cell::Cell, mem, ops::Range, rc::Rc, sync::Arc}; +use std::{mem, ops::Range, sync::Arc}; use util::ResultExt; impl Element for &'static str { - type State = TextState; + type FrameState = TextState; - fn layout( - &mut self, - _: Option, - cx: &mut WindowContext, - ) -> (LayoutId, Self::State) { + fn layout(&mut self, cx: &mut WindowContext) -> (LayoutId, Self::FrameState) { let mut state = TextState::default(); let layout_id = state.layout(SharedString::from(*self), None, cx); (layout_id, state) @@ -40,13 +36,9 @@ impl IntoElement for &'static str { } impl Element for SharedString { - type State = TextState; + type FrameState = TextState; - fn layout( - &mut self, - _: Option, - cx: &mut WindowContext, - ) -> (LayoutId, Self::State) { + fn layout(&mut self, cx: &mut WindowContext) -> (LayoutId, Self::FrameState) { let mut state = TextState::default(); let layout_id = state.layout(self.clone(), None, cx); (layout_id, state) @@ -116,19 +108,20 @@ impl StyledText { } impl Element for StyledText { - type State = TextState; + type FrameState = TextState; - fn layout( - &mut self, - _: Option, - cx: &mut WindowContext, - ) -> (LayoutId, Self::State) { + fn layout(&mut self, cx: &mut WindowContext) -> (LayoutId, Self::FrameState) { let mut state = TextState::default(); let layout_id = state.layout(self.text.clone(), self.runs.take(), cx); (layout_id, state) } - fn paint(&mut self, bounds: Bounds, state: &mut Self::State, cx: &mut WindowContext) { + fn paint( + &mut self, + bounds: Bounds, + state: &mut Self::FrameState, + cx: &mut WindowContext, + ) { state.paint(bounds, &self.text, cx) } } @@ -295,9 +288,9 @@ struct InteractiveTextClickEvent { mouse_up_index: usize, } +#[derive(Default)] pub struct InteractiveTextState { - text_state: TextState, - mouse_down_index: Rc>>, + mouse_down_index: Option, } impl InteractiveText { @@ -329,86 +322,93 @@ impl InteractiveText { } impl Element for InteractiveText { - type State = InteractiveTextState; + type FrameState = TextState; - fn layout( - &mut self, - state: Option, - cx: &mut WindowContext, - ) -> (LayoutId, Self::State) { - if let Some(InteractiveTextState { - mouse_down_index, .. - }) = state - { - let (layout_id, text_state) = self.text.layout(None, cx); - let element_state = InteractiveTextState { - text_state, - mouse_down_index, - }; - (layout_id, element_state) - } else { - let (layout_id, text_state) = self.text.layout(None, cx); - let element_state = InteractiveTextState { - text_state, - mouse_down_index: Rc::default(), - }; - (layout_id, element_state) - } + fn layout(&mut self, cx: &mut WindowContext) -> (LayoutId, Self::FrameState) { + self.text.layout(cx) } - fn paint(&mut self, bounds: Bounds, state: &mut Self::State, cx: &mut WindowContext) { - if let Some(click_listener) = self.click_listener.take() { - if let Some(ix) = state - .text_state - .index_for_position(bounds, cx.mouse_position()) - { - if self - .clickable_ranges - .iter() - .any(|range| range.contains(&ix)) - { - cx.set_cursor_style(crate::CursorStyle::PointingHand) - } - } - - let text_state = state.text_state.clone(); - let mouse_down = state.mouse_down_index.clone(); - if let Some(mouse_down_index) = mouse_down.get() { - let clickable_ranges = mem::take(&mut self.clickable_ranges); - cx.on_mouse_event(move |event: &MouseUpEvent, phase, cx| { - if phase == DispatchPhase::Bubble { - if let Some(mouse_up_index) = - text_state.index_for_position(bounds, event.position) + fn paint( + &mut self, + bounds: Bounds, + text_state: &mut Self::FrameState, + cx: &mut WindowContext, + ) { + cx.with_element_id(Some(self.element_id.clone()), |global_element_id, cx| { + let global_element_id = global_element_id.unwrap(); + cx.with_element_state::( + &global_element_id, + |element_state, cx| { + let element_state = element_state.get_or_insert_with(Default::default); + if let Some(click_listener) = self.click_listener.take() { + if let Some(ix) = text_state.index_for_position(bounds, cx.mouse_position()) { - click_listener( - &clickable_ranges, - InteractiveTextClickEvent { - mouse_down_index, - mouse_up_index, - }, - cx, - ) + if self + .clickable_ranges + .iter() + .any(|range| range.contains(&ix)) + { + cx.set_cursor_style(crate::CursorStyle::PointingHand) + } } - mouse_down.take(); - cx.notify(); - } - }); - } else { - cx.on_mouse_event(move |event: &MouseDownEvent, phase, cx| { - if phase == DispatchPhase::Bubble { - if let Some(mouse_down_index) = - text_state.index_for_position(bounds, event.position) - { - mouse_down.set(Some(mouse_down_index)); - cx.notify(); + let text_state = text_state.clone(); + if let Some(mouse_down_index) = element_state.mouse_down_index { + let global_element_id = global_element_id.clone(); + let clickable_ranges = mem::take(&mut self.clickable_ranges); + cx.on_mouse_event(move |event: &MouseUpEvent, phase, cx| { + if phase == DispatchPhase::Bubble { + if let Some(mouse_up_index) = + text_state.index_for_position(bounds, event.position) + { + click_listener( + &clickable_ranges, + InteractiveTextClickEvent { + mouse_down_index, + mouse_up_index, + }, + cx, + ) + } + + cx.with_element_state::( + &global_element_id, + |element_state, cx| { + if let Some(element_state) = element_state { + element_state.mouse_down_index = None; + cx.notify(); + } + }, + ); + } + }); + } else { + let global_element_id = global_element_id.clone(); + cx.on_mouse_event(move |event: &MouseDownEvent, phase, cx| { + if phase == DispatchPhase::Bubble { + if let Some(mouse_down_index) = + text_state.index_for_position(bounds, event.position) + { + cx.with_element_state::( + &global_element_id, + |element_state, cx| { + if let Some(element_state) = element_state { + element_state.mouse_down_index = + Some(mouse_down_index); + cx.notify(); + } + }, + ); + } + } + }); } } - }); - } - } + }, + ) + }); - self.text.paint(bounds, &mut state.text_state, cx) + self.text.paint(bounds, text_state, cx) } } diff --git a/crates/gpui2/src/elements/uniform_list.rs b/crates/gpui2/src/elements/uniform_list.rs index 26b1df4cda..3600810bf6 100644 --- a/crates/gpui2/src/elements/uniform_list.rs +++ b/crates/gpui2/src/elements/uniform_list.rs @@ -1,7 +1,7 @@ use crate::{ point, px, size, AnyElement, AvailableSpace, BorrowWindow, Bounds, ContentMask, Element, - ElementId, InteractiveElement, InteractiveElementState, Interactivity, IntoElement, LayoutId, - Pixels, Point, Render, Size, StyleRefinement, Styled, View, ViewContext, WindowContext, + ElementId, InteractiveElement, Interactivity, IntoElement, LayoutId, Pixels, Point, Render, + Size, StyleRefinement, Styled, View, ViewContext, WindowContext, }; use smallvec::SmallVec; use std::{cell::RefCell, cmp, ops::Range, rc::Rc}; @@ -99,70 +99,40 @@ impl Styled for UniformList { } } -#[derive(Default)] -pub struct UniformListState { - interactive: InteractiveElementState, - item_size: Size, -} - impl Element for UniformList { - type State = UniformListState; + type FrameState = Size; - fn layout( - &mut self, - state: Option, - cx: &mut WindowContext, - ) -> (LayoutId, Self::State) { + fn layout(&mut self, cx: &mut WindowContext) -> (LayoutId, Self::FrameState) { let max_items = self.item_count; - let item_size = state - .as_ref() - .map(|s| s.item_size) - .unwrap_or_else(|| self.measure_item(None, cx)); + let item_size = self.measure_item(None, cx); + let layout_id = self.interactivity.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, interactive) = - self.interactivity - .layout(state.map(|s| s.interactive), 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 element_state = UniformListState { - interactive, - item_size, - }; - - (layout_id, element_state) + (layout_id, item_size) } fn paint( &mut self, bounds: Bounds, - element_state: &mut Self::State, + item_size: &mut Self::FrameState, cx: &mut WindowContext, ) { - let style = - self.interactivity - .compute_style(Some(bounds), &mut element_state.interactive, cx); + let item_size = *item_size; + let style = self.interactivity.compute_style(Some(bounds), cx); let border = style.border_widths.to_pixels(cx.rem_size()); let padding = style.padding.to_pixels(bounds.size.into(), cx.rem_size()); @@ -172,26 +142,24 @@ impl Element for UniformList { - point(border.right + padding.right, border.bottom + padding.bottom), ); - let item_size = element_state.item_size; let content_size = Size { width: padded_bounds.size.width, height: item_size.height * self.item_count + padding.top + padding.bottom, }; - let shared_scroll_offset = element_state - .interactive - .scroll_offset - .get_or_insert_with(Rc::default) - .clone(); - let item_height = self.measure_item(Some(padded_bounds.size.width), cx).height; self.interactivity.paint( bounds, content_size, - &mut element_state.interactive, cx, - |style, mut scroll_offset, cx| { + |style, element_state, mut scroll_offset, cx| { + let (_, element_state) = element_state.unwrap(); + let shared_scroll_offset = element_state + .scroll_offset + .get_or_insert_with(Rc::default) + .clone(); + let border = style.border_widths.to_pixels(cx.rem_size()); let padding = style.padding.to_pixels(bounds.size.into(), cx.rem_size()); diff --git a/crates/gpui2/src/view.rs b/crates/gpui2/src/view.rs index 1b4c2b6346..8152d5b21f 100644 --- a/crates/gpui2/src/view.rs +++ b/crates/gpui2/src/view.rs @@ -79,19 +79,15 @@ impl View { } impl Element for View { - type State = Option; + type FrameState = Option; - fn layout( - &mut self, - _state: Option, - cx: &mut WindowContext, - ) -> (LayoutId, Self::State) { + fn layout(&mut self, cx: &mut WindowContext) -> (LayoutId, Self::FrameState) { let mut element = self.update(cx, |view, cx| view.render(cx).into_any()); let layout_id = element.layout(cx); (layout_id, Some(element)) } - fn paint(&mut self, _: Bounds, element: &mut Self::State, cx: &mut WindowContext) { + fn paint(&mut self, _: Bounds, element: &mut Self::FrameState, cx: &mut WindowContext) { element.take().unwrap().paint(cx); } } @@ -227,18 +223,14 @@ impl From> for AnyView { } impl Element for AnyView { - type State = Option; + type FrameState = Option; - fn layout( - &mut self, - _state: Option, - cx: &mut WindowContext, - ) -> (LayoutId, Self::State) { + fn layout(&mut self, cx: &mut WindowContext) -> (LayoutId, Self::FrameState) { let (layout_id, state) = (self.layout)(self, cx); (layout_id, Some(state)) } - fn paint(&mut self, _: Bounds, state: &mut Self::State, cx: &mut WindowContext) { + fn paint(&mut self, _: Bounds, state: &mut Self::FrameState, cx: &mut WindowContext) { debug_assert!( state.is_some(), "state is None. Did you include an AnyView twice in the tree?" diff --git a/crates/gpui2/src/window.rs b/crates/gpui2/src/window.rs index 337f61272f..7cb19ad68d 100644 --- a/crates/gpui2/src/window.rs +++ b/crates/gpui2/src/window.rs @@ -1977,17 +1977,18 @@ pub trait BorrowWindow: BorrowMut + BorrowMut { fn with_element_id( &mut self, id: Option>, - f: impl FnOnce(&mut Self) -> R, + f: impl FnOnce(Option, &mut Self) -> R, ) -> R { if let Some(id) = id.map(Into::into) { let window = self.window_mut(); window.element_id_stack.push(id.into()); - let result = f(self); + let global_element_id = window.element_id_stack.clone(); + let result = f(Some(global_element_id), self); let window: &mut Window = self.borrow_mut(); window.element_id_stack.pop(); result } else { - f(self) + f(None, self) } } @@ -2071,90 +2072,67 @@ pub trait BorrowWindow: BorrowMut + BorrowMut { /// when drawing the next frame. fn with_element_state( &mut self, - id: ElementId, - f: impl FnOnce(Option, &mut Self) -> (R, S), + global_element_id: &GlobalElementId, + f: impl FnOnce(&mut Option>, &mut Self) -> R, ) -> R where S: 'static, { - self.with_element_id(Some(id), |cx| { - let global_id = cx.window().element_id_stack.clone(); - - if let Some(any) = cx + let mut state = self .window_mut() .next_frame .element_states - .remove(&global_id) + .remove(global_element_id) .or_else(|| { - cx.window_mut() + self.window_mut() .rendered_frame .element_states - .remove(&global_id) - }) - { - 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!( - "invalid element state type for id, requested_type {:?}, actual type: {:?}", - std::any::type_name::(), - type_name - ) - } - - #[cfg(not(debug_assertions))] - { - anyhow!( - "invalid element state type for id, requested_type {:?}", - std::any::type_name::(), - ) - } - }) - .unwrap(); - - // Actual: Option <- View - // Requested: () <- AnyElemet - let state = state_box - .take() - .expect("element state is already on the stack"); - let (result, state) = f(Some(state), cx); - state_box.replace(state); - cx.window_mut() - .next_frame - .element_states - .insert(global_id, ElementStateBox { - inner: state_box, + .remove(global_element_id) + }).map(|any| { + let ElementStateBox { + inner, #[cfg(debug_assertions)] type_name - }); - result - } else { - let (result, state) = f(None, cx); - cx.window_mut() - .next_frame - .element_states - .insert(global_id, - ElementStateBox { - inner: Box::new(Some(state)), - + } = any; + inner + .downcast::() + .map_err(|_| { #[cfg(debug_assertions)] - type_name: std::any::type_name::() - } + { + anyhow!( + "invalid element state type for id, requested_type {:?}, actual type: {:?}", + std::any::type_name::(), + type_name + ) + } - ); - result - } - }) + #[cfg(not(debug_assertions))] + { + anyhow!( + "invalid element state type for id, requested_type {:?}", + std::any::type_name::(), + ) + } + }) + .unwrap() + }); + + let result = f(&mut state, self); + + if let Some(state) = state { + self.window_mut().next_frame.element_states.insert( + global_element_id.clone(), + ElementStateBox { + inner: state, + + #[cfg(debug_assertions)] + type_name: std::any::type_name::(), + }, + ); + } + + result } /// Obtain the current content mask. diff --git a/crates/terminal_view2/src/terminal_element.rs b/crates/terminal_view2/src/terminal_element.rs index e9335a4220..69fc1496b2 100644 --- a/crates/terminal_view2/src/terminal_element.rs +++ b/crates/terminal_view2/src/terminal_element.rs @@ -2,10 +2,10 @@ use editor::{Cursor, HighlightedRange, HighlightedRangeLine}; use gpui::{ black, div, fill, point, px, red, relative, AnyElement, AsyncWindowContext, AvailableSpace, Bounds, DispatchPhase, Element, ElementId, ExternalPaths, FocusHandle, Font, FontStyle, - FontWeight, HighlightStyle, Hsla, InteractiveElement, InteractiveElementState, Interactivity, - IntoElement, LayoutId, Model, ModelContext, ModifiersChangedEvent, MouseButton, Pixels, - PlatformInputHandler, Point, Rgba, ShapedLine, Size, StatefulInteractiveElement, Styled, - TextRun, TextStyle, TextSystem, UnderlineStyle, WhiteSpace, WindowContext, + FontWeight, HighlightStyle, Hsla, InteractiveElement, Interactivity, IntoElement, LayoutId, + Model, ModelContext, ModifiersChangedEvent, MouseButton, Pixels, PlatformInputHandler, Point, + Rgba, ShapedLine, Size, StatefulInteractiveElement, Styled, TextRun, TextStyle, TextSystem, + UnderlineStyle, WhiteSpace, WindowContext, }; use itertools::Itertools; use language::CursorShape; @@ -759,30 +759,22 @@ impl TerminalElement { } impl Element for TerminalElement { - type State = InteractiveElementState; + type FrameState = (); - fn layout( - &mut self, - element_state: Option, - cx: &mut WindowContext<'_>, - ) -> (LayoutId, Self::State) { - let (layout_id, interactive_state) = - self.interactivity - .layout(element_state, 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 layout(&mut self, cx: &mut WindowContext<'_>) -> (LayoutId, Self::FrameState) { + let layout_id = self.interactivity.layout(cx, |mut style, _, cx| { + style.size.width = relative(1.).into(); + style.size.height = relative(1.).into(); + cx.request_layout(&style, None) + }); - layout_id - }); - - (layout_id, interactive_state) + (layout_id, ()) } fn paint( &mut self, bounds: Bounds, - state: &mut Self::State, + state: &mut Self::FrameState, cx: &mut WindowContext<'_>, ) { let mut layout = self.compute_layout(bounds, cx); @@ -812,7 +804,7 @@ impl Element for TerminalElement { // }) let mut interactivity = mem::take(&mut self.interactivity); - interactivity.paint(bounds, bounds.size, state, cx, |_, _, cx| { + interactivity.paint(bounds, bounds.size, cx, |_, _, _, cx| { cx.handle_input(&self.focus, terminal_input_handler); self.register_key_listeners(cx); diff --git a/crates/ui2/src/components/popover_menu.rs b/crates/ui2/src/components/popover_menu.rs index 0f2fa6d23f..52469fe160 100644 --- a/crates/ui2/src/components/popover_menu.rs +++ b/crates/ui2/src/components/popover_menu.rs @@ -126,20 +126,19 @@ pub struct PopoverMenuState { } impl Element for PopoverMenu { - type State = PopoverMenuState; + type FrameState = PopoverMenuState; - fn layout( - &mut self, - element_state: Option, - cx: &mut WindowContext, - ) -> (gpui::LayoutId, Self::State) { + fn layout(&mut self, cx: &mut WindowContext) -> (gpui::LayoutId, Self::FrameState) { let mut menu_layout_id = None; - let (menu, child_bounds) = if let Some(element_state) = element_state { - (element_state.menu, element_state.child_bounds) - } else { - (Rc::default(), None) - }; + // todo!() + // let (menu, child_bounds) = if let Some(element_state) = element_state { + // (element_state.menu, element_state.child_bounds) + // } else { + // (Rc::default(), None) + // }; + let menu: Rc>>> = Rc::default(); + let child_bounds = None; let menu_element = menu.borrow_mut().as_mut().map(|menu| { let mut overlay = overlay().snap_to_window().anchor(self.anchor); @@ -184,7 +183,7 @@ impl Element for PopoverMenu { fn paint( &mut self, _: Bounds, - element_state: &mut Self::State, + element_state: &mut Self::FrameState, cx: &mut WindowContext, ) { if let Some(mut child) = element_state.child_element.take() { diff --git a/crates/ui2/src/components/right_click_menu.rs b/crates/ui2/src/components/right_click_menu.rs index a3a454d652..f0f1c9bd06 100644 --- a/crates/ui2/src/components/right_click_menu.rs +++ b/crates/ui2/src/components/right_click_menu.rs @@ -58,18 +58,17 @@ pub struct MenuHandleState { } impl Element for RightClickMenu { - type State = MenuHandleState; + type FrameState = MenuHandleState; - fn layout( - &mut self, - element_state: Option, - cx: &mut WindowContext, - ) -> (gpui::LayoutId, Self::State) { - let (menu, position) = if let Some(element_state) = element_state { - (element_state.menu, element_state.position) - } else { - (Rc::default(), Rc::default()) - }; + fn layout(&mut self, cx: &mut WindowContext) -> (gpui::LayoutId, Self::FrameState) { + // todo!() + // let (menu, position) = if let Some(element_state) = element_state { + // (element_state.menu, element_state.position) + // } else { + // (Rc::default(), Rc::default()) + // }; + let menu: Rc>>> = Rc::default(); + let position: Rc>> = Rc::default(); let mut menu_layout_id = None; @@ -114,7 +113,7 @@ impl Element for RightClickMenu { fn paint( &mut self, bounds: Bounds, - element_state: &mut Self::State, + element_state: &mut Self::FrameState, cx: &mut WindowContext, ) { if let Some(mut child) = element_state.child_element.take() { diff --git a/crates/workspace2/src/pane_group.rs b/crates/workspace2/src/pane_group.rs index b52cec2b18..27b1a294b8 100644 --- a/crates/workspace2/src/pane_group.rs +++ b/crates/workspace2/src/pane_group.rs @@ -756,25 +756,25 @@ mod element { } impl Element for PaneAxisElement { - type State = Rc>>; + type FrameState = Rc>>; fn layout( &mut self, - state: Option, cx: &mut ui::prelude::WindowContext, - ) -> (gpui::LayoutId, Self::State) { + ) -> (gpui::LayoutId, Self::FrameState) { let mut style = Style::default(); style.size.width = relative(1.).into(); style.size.height = relative(1.).into(); let layout_id = cx.request_layout(&style, None); - let dragged_pane = state.unwrap_or_else(|| Rc::new(RefCell::new(None))); + // todo!() + let dragged_pane = Rc::new(RefCell::new(None)); (layout_id, dragged_pane) } fn paint( &mut self, bounds: gpui::Bounds, - state: &mut Self::State, + state: &mut Self::FrameState, cx: &mut ui::prelude::WindowContext, ) { let flexes = self.flexes.lock().clone();