diff --git a/crates/editor2/src/element.rs b/crates/editor2/src/element.rs index f8386ee271..38ec0f3d11 100644 --- a/crates/editor2/src/element.rs +++ b/crates/editor2/src/element.rs @@ -2640,6 +2640,15 @@ impl Element for EditorElement { cx.request_layout(&style, None) } + fn prepaint( + &mut self, + bounds: Bounds, + view_state: &mut Editor, + element_state: &mut Self::ElementState, + cx: &mut ViewContext, + ) { + } + fn paint( &mut self, bounds: Bounds, diff --git a/crates/gpui2/src/element.rs b/crates/gpui2/src/element.rs index 9ee9eaa7c3..1dc9c155eb 100644 --- a/crates/gpui2/src/element.rs +++ b/crates/gpui2/src/element.rs @@ -26,6 +26,14 @@ pub trait Element { cx: &mut ViewContext, ) -> LayoutId; + fn prepaint( + &mut self, + bounds: Bounds, + view_state: &mut V, + element_state: &mut Self::ElementState, + cx: &mut ViewContext, + ); + fn paint( &mut self, bounds: Bounds, @@ -62,6 +70,7 @@ pub trait ParentElement { trait ElementObject { fn initialize(&mut self, view_state: &mut V, cx: &mut ViewContext); fn layout(&mut self, view_state: &mut V, cx: &mut ViewContext) -> LayoutId; + fn prepaint(&mut self, view_state: &mut V, cx: &mut ViewContext); fn paint(&mut self, view_state: &mut V, cx: &mut ViewContext); fn measure( &mut self, @@ -199,6 +208,36 @@ where }; } + fn prepaint(&mut self, view_state: &mut V, cx: &mut ViewContext) { + self.phase = match mem::take(&mut self.phase) { + ElementRenderPhase::LayoutRequested { + layout_id, + mut frame_state, + } + | ElementRenderPhase::LayoutComputed { + layout_id, + mut frame_state, + .. + } => { + let bounds = cx.layout_bounds(layout_id); + if let Some(id) = self.element.id() { + cx.with_element_state(id, |element_state, cx| { + let mut element_state = element_state.unwrap(); + self.element + .prepaint(bounds, view_state, &mut element_state, cx); + ((), element_state) + }); + } else { + self.element + .prepaint(bounds, view_state, frame_state.as_mut().unwrap(), cx); + } + ElementRenderPhase::Painted + } + + _ => panic!("must call layout before paint"), + }; + } + fn measure( &mut self, available_space: Size, @@ -283,6 +322,10 @@ impl AnyElement { self.0.paint(view_state, cx) } + pub fn prepaint(&mut self, view_state: &mut V, cx: &mut ViewContext) { + self.0.prepaint(view_state, cx) + } + /// Initializes this element and performs layout within the given available space to determine its size. pub fn measure( &mut self, @@ -376,6 +419,16 @@ where rendered_element.layout(view_state, cx) } + fn prepaint( + &mut self, + _bounds: Bounds, + view_state: &mut V, + rendered_element: &mut Self::ElementState, + cx: &mut ViewContext, + ) { + rendered_element.prepaint(view_state, cx) + } + fn paint( &mut self, _bounds: Bounds, diff --git a/crates/gpui2/src/elements/div.rs b/crates/gpui2/src/elements/div.rs index 080cdc2c4d..1211a6bc12 100644 --- a/crates/gpui2/src/elements/div.rs +++ b/crates/gpui2/src/elements/div.rs @@ -265,6 +265,16 @@ where }) } + fn prepaint( + &mut self, + bounds: Bounds, + view_state: &mut V, + element_state: &mut Self::ElementState, + cx: &mut ViewContext, + ) { + todo!() + } + fn paint( &mut self, bounds: Bounds, @@ -309,7 +319,7 @@ where cx.with_z_index(0, |cx| { style.paint(bounds, cx); this.key_dispatch.paint(bounds, cx); - this.interactivity.paint( + this.interactivity.handle_events( bounds, content_size, style.overflow, diff --git a/crates/gpui2/src/elements/img.rs b/crates/gpui2/src/elements/img.rs index 1ff088c1af..137dde7dfe 100644 --- a/crates/gpui2/src/elements/img.rs +++ b/crates/gpui2/src/elements/img.rs @@ -94,6 +94,15 @@ where self.base.layout(view_state, element_state, cx) } + fn prepaint( + &mut self, + bounds: Bounds, + view_state: &mut V, + element_state: &mut Self::ElementState, + cx: &mut ViewContext, + ) { + } + fn paint( &mut self, bounds: Bounds, diff --git a/crates/gpui2/src/elements/node.rs b/crates/gpui2/src/elements/node.rs index ec73af8721..bcf3772aed 100644 --- a/crates/gpui2/src/elements/node.rs +++ b/crates/gpui2/src/elements/node.rs @@ -10,6 +10,7 @@ use smallvec::SmallVec; use std::{ any::{Any, TypeId}, marker::PhantomData, + mem, sync::Arc, }; @@ -530,24 +531,6 @@ pub struct Node { children: Vec>, } -pub struct Interactivity { - group: Option, - pub dispatch_context: KeyContext, - pub mouse_down_listeners: SmallVec<[MouseDownListener; 2]>, - pub mouse_up_listeners: SmallVec<[MouseUpListener; 2]>, - pub mouse_move_listeners: SmallVec<[MouseMoveListener; 2]>, - pub scroll_wheel_listeners: SmallVec<[ScrollWheelListener; 2]>, - pub key_down_listeners: SmallVec<[KeyDownListener; 2]>, - pub key_up_listeners: SmallVec<[KeyUpListener; 2]>, - pub action_listeners: SmallVec<[(TypeId, ActionListener); 8]>, - pub hover_style: StyleRefinement, - pub group_hover_style: Option, - drag_over_styles: SmallVec<[(TypeId, StyleRefinement); 2]>, - group_drag_over_styles: SmallVec<[(TypeId, GroupStyle); 2]>, - drop_listeners: SmallVec<[(TypeId, Box>); 2]>, - scroll_offset: Point, -} - impl Node { fn compute_style(&self) -> Style { let mut style = Style::default(); @@ -610,6 +593,20 @@ impl Element for Node { }) } + fn prepaint( + &mut self, + bounds: Bounds, + view_state: &mut V, + _: &mut Self::ElementState, + cx: &mut ViewContext, + ) { + for child in &mut self.children { + child.prepaint(view_state, cx); + } + self.interactivity + .refine_style(&mut self.style, bounds, view_state, cx); + } + fn paint( &mut self, bounds: Bounds, @@ -652,6 +649,7 @@ impl Element for Node { cx.with_z_index(z_index, |cx| { cx.with_z_index(0, |cx| { style.paint(bounds, cx); + self.interactivity.paint(bounds, cx); }); cx.with_z_index(1, |cx| { style.with_text_style(cx, |cx| { @@ -673,6 +671,144 @@ impl Element for Node { } } +pub struct Interactivity { + pub hover_style: StyleRefinement, + pub group_hover_style: Option, + pub drag_over_styles: SmallVec<[(TypeId, StyleRefinement); 2]>, + pub group_drag_over_styles: SmallVec<[(TypeId, GroupStyle); 2]>, + group: Option, + pub dispatch_context: KeyContext, + pub mouse_down_listeners: SmallVec<[MouseDownListener; 2]>, + pub mouse_up_listeners: SmallVec<[MouseUpListener; 2]>, + pub mouse_move_listeners: SmallVec<[MouseMoveListener; 2]>, + pub scroll_wheel_listeners: SmallVec<[ScrollWheelListener; 2]>, + pub key_down_listeners: SmallVec<[KeyDownListener; 2]>, + pub key_up_listeners: SmallVec<[KeyUpListener; 2]>, + pub action_listeners: SmallVec<[(TypeId, ActionListener); 8]>, + drop_listeners: SmallVec<[(TypeId, Box>); 2]>, + scroll_offset: Point, +} + +impl Interactivity { + fn refine_style( + &self, + style: &mut StyleRefinement, + bounds: Bounds, + cx: &mut ViewContext, + ) { + let mouse_position = cx.mouse_position(); + if let Some(group_hover) = self.group_hover_style.as_ref() { + if let Some(group_bounds) = GroupBounds::get(&group_hover.group, cx) { + if group_bounds.contains_point(&mouse_position) { + style.refine(&group_hover.style); + } + } + } + if bounds.contains_point(&mouse_position) { + style.refine(&self.hover_style); + } + + if let Some(drag) = cx.active_drag.take() { + for (state_type, group_drag_style) in &self.group_drag_over_styles { + if let Some(group_bounds) = GroupBounds::get(&group_drag_style.group, cx) { + if *state_type == drag.view.entity_type() + && group_bounds.contains_point(&mouse_position) + { + style.refine(&group_drag_style.style); + } + } + } + + for (state_type, drag_over_style) in &self.drag_over_styles { + if *state_type == drag.view.entity_type() && bounds.contains_point(&mouse_position) + { + style.refine(drag_over_style); + } + } + + cx.active_drag = Some(drag); + } + } + + fn paint(&mut self, bounds: Bounds, cx: &mut ViewContext) { + for listener in self.mouse_down_listeners.drain(..) { + cx.on_mouse_event(move |state, event: &MouseDownEvent, phase, cx| { + listener(state, event, &bounds, phase, cx); + }) + } + + for listener in self.mouse_up_listeners.drain(..) { + cx.on_mouse_event(move |state, event: &MouseUpEvent, phase, cx| { + listener(state, event, &bounds, phase, cx); + }) + } + + for listener in self.mouse_move_listeners.drain(..) { + cx.on_mouse_event(move |state, event: &MouseMoveEvent, phase, cx| { + listener(state, event, &bounds, phase, cx); + }) + } + + for listener in self.scroll_wheel_listeners.drain(..) { + cx.on_mouse_event(move |state, event: &ScrollWheelEvent, phase, cx| { + listener(state, event, &bounds, phase, cx); + }) + } + + let hover_group_bounds = self + .group_hover_style + .as_ref() + .and_then(|group_hover| GroupBounds::get(&group_hover.group, cx)); + + if let Some(group_bounds) = hover_group_bounds { + let hovered = group_bounds.contains_point(&cx.mouse_position()); + cx.on_mouse_event(move |_, event: &MouseMoveEvent, phase, cx| { + if phase == DispatchPhase::Capture { + if group_bounds.contains_point(&event.position) != hovered { + cx.notify(); + } + } + }); + } + + if self.hover_style.is_some() + || (cx.active_drag.is_some() && !self.drag_over_styles.is_empty()) + { + let hovered = bounds.contains_point(&cx.mouse_position()); + cx.on_mouse_event(move |_, event: &MouseMoveEvent, phase, cx| { + if phase == DispatchPhase::Capture { + if bounds.contains_point(&event.position) != hovered { + cx.notify(); + } + } + }); + } + + if cx.active_drag.is_some() { + let drop_listeners = mem::take(&mut self.drop_listeners); + cx.on_mouse_event(move |view, event: &MouseUpEvent, phase, cx| { + if phase == DispatchPhase::Bubble && bounds.contains_point(&event.position) { + if let Some(drag_state_type) = + cx.active_drag.as_ref().map(|drag| drag.view.entity_type()) + { + for (drop_state_type, listener) in &drop_listeners { + if *drop_state_type == drag_state_type { + let drag = cx + .active_drag + .take() + .expect("checked for type drag state type above"); + listener(view, drag.view.clone(), cx); + cx.notify(); + cx.stop_propagation(); + } + } + } + } + }); + } + } +} + #[derive(Default)] pub struct GroupBounds(HashMap; 1]>>); diff --git a/crates/gpui2/src/elements/svg.rs b/crates/gpui2/src/elements/svg.rs index bafedb7f2d..124c578b28 100644 --- a/crates/gpui2/src/elements/svg.rs +++ b/crates/gpui2/src/elements/svg.rs @@ -84,6 +84,15 @@ where self.base.layout(view_state, element_state, cx) } + fn prepaint( + &mut self, + bounds: Bounds, + view_state: &mut V, + element_state: &mut Self::ElementState, + cx: &mut ViewContext, + ) { + } + fn paint( &mut self, bounds: Bounds, diff --git a/crates/gpui2/src/elements/text.rs b/crates/gpui2/src/elements/text.rs index 5c5709d32e..0794ef431a 100644 --- a/crates/gpui2/src/elements/text.rs +++ b/crates/gpui2/src/elements/text.rs @@ -121,6 +121,15 @@ impl Element for Text { layout_id } + fn prepaint( + &mut self, + bounds: Bounds, + view_state: &mut V, + element_state: &mut Self::ElementState, + cx: &mut ViewContext, + ) { + } + fn paint( &mut self, bounds: Bounds, diff --git a/crates/gpui2/src/elements/uniform_list.rs b/crates/gpui2/src/elements/uniform_list.rs index 6687559d1c..16abbcf883 100644 --- a/crates/gpui2/src/elements/uniform_list.rs +++ b/crates/gpui2/src/elements/uniform_list.rs @@ -152,6 +152,16 @@ impl Element for UniformList { ) } + fn prepaint( + &mut self, + bounds: Bounds, + view_state: &mut V, + element_state: &mut Self::ElementState, + cx: &mut ViewContext, + ) { + todo!() + } + fn paint( &mut self, bounds: crate::Bounds, @@ -229,7 +239,7 @@ impl Element for UniformList { let overflow = point(style.overflow.x, Overflow::Scroll); cx.with_z_index(0, |cx| { - self.interactivity.paint( + self.interactivity.handle_events( bounds, content_size, overflow, diff --git a/crates/gpui2/src/interactive.rs b/crates/gpui2/src/interactive.rs index aacaeac01f..f896bfc439 100644 --- a/crates/gpui2/src/interactive.rs +++ b/crates/gpui2/src/interactive.rs @@ -450,7 +450,7 @@ pub trait ElementInteractivity: 'static { } } - fn paint( + fn handle_events( &mut self, bounds: Bounds, content_size: Size, diff --git a/crates/gpui2/src/view.rs b/crates/gpui2/src/view.rs index d12d84f43b..9b2a06191f 100644 --- a/crates/gpui2/src/view.rs +++ b/crates/gpui2/src/view.rs @@ -147,6 +147,7 @@ pub struct AnyView { model: AnyModel, initialize: fn(&AnyView, &mut WindowContext) -> AnyBox, layout: fn(&AnyView, &mut AnyBox, &mut WindowContext) -> LayoutId, + prepaint: fn(&AnyView, &mut AnyBox, &mut WindowContext), paint: fn(&AnyView, &mut AnyBox, &mut WindowContext), } @@ -156,6 +157,7 @@ impl AnyView { model: self.model.downgrade(), initialize: self.initialize, layout: self.layout, + prepaint: self.prepaint, paint: self.paint, } } @@ -167,6 +169,7 @@ impl AnyView { model, initialize: self.initialize, layout: self.layout, + prepaint: self.prepaint, paint: self.paint, }), } @@ -198,6 +201,7 @@ impl From> for AnyView { model: value.model.into_any(), initialize: any_view::initialize::, layout: any_view::layout::, + prepaint: any_view::prepaint::, paint: any_view::paint::, } } @@ -228,6 +232,16 @@ impl Element for AnyView { (self.layout)(self, rendered_element, cx) } + fn prepaint( + &mut self, + bounds: Bounds, + view_state: &mut ParentViewState, + rendered_element: &mut Self::ElementState, + cx: &mut ViewContext, + ) { + (self.prepaint)(self, rendered_element, cx) + } + fn paint( &mut self, _bounds: Bounds, @@ -243,6 +257,7 @@ pub struct AnyWeakView { model: AnyWeakModel, initialize: fn(&AnyView, &mut WindowContext) -> AnyBox, layout: fn(&AnyView, &mut AnyBox, &mut WindowContext) -> LayoutId, + prepaint: fn(&AnyView, &mut AnyBox, &mut WindowContext), paint: fn(&AnyView, &mut AnyBox, &mut WindowContext), } @@ -253,6 +268,7 @@ impl AnyWeakView { model, initialize: self.initialize, layout: self.layout, + prepaint: self.prepaint, paint: self.paint, }) } @@ -264,6 +280,7 @@ impl From> for AnyWeakView { model: view.model.into(), initialize: any_view::initialize::, layout: any_view::layout::, + prepaint: any_view::prepaint::, paint: any_view::paint::, } } @@ -309,6 +326,18 @@ mod any_view { }) } + pub(crate) fn prepaint( + view: &AnyView, + element: &mut Box, + cx: &mut WindowContext, + ) { + cx.with_element_id(view.model.entity_id, |_, cx| { + let view = view.clone().downcast::().unwrap(); + let element = element.downcast_mut::>().unwrap(); + view.update(cx, |view, cx| element.prepaint(view, cx)) + }) + } + pub(crate) fn paint( view: &AnyView, element: &mut Box, diff --git a/crates/ui2/src/components/modal.rs b/crates/ui2/src/components/modal.rs index 75528b5c34..805bbe95b2 100644 --- a/crates/ui2/src/components/modal.rs +++ b/crates/ui2/src/components/modal.rs @@ -1,4 +1,4 @@ -use gpui::AnyElement; +use gpui::{AnyElement, Pixels}; use smallvec::SmallVec; use crate::{h_stack, prelude::*, v_stack, Button, Icon, IconButton, Label};