Checkpoint

Nathan Sobo created

Change summary

crates/gpui3/src/element.rs             |  25 ++++
crates/gpui3/src/elements.rs            |   2 
crates/gpui3/src/elements/clickable.rs  | 138 +++++++++++++++++++++++++++
crates/gpui3/src/elements/div.rs        |  14 +-
crates/gpui3/src/elements/hoverable.rs  |   8 
crates/gpui3/src/elements/identified.rs |   6 
crates/gpui3/src/elements/pressable.rs  |  17 +-
crates/gpui3/src/interactive.rs         |  32 ------
crates/storybook2/src/collab_panel.rs   |   9 
crates/storybook2/src/theme.rs          |   4 
crates/storybook2/src/workspace.rs      |   3 
11 files changed, 197 insertions(+), 61 deletions(-)

Detailed changes

crates/gpui3/src/element.rs 🔗

@@ -1,4 +1,9 @@
-use crate::{BorrowWindow, Bounds, ElementId, LayoutId, Pixels, Point, ViewContext};
+use std::sync::Arc;
+
+use crate::{
+    BorrowWindow, Bounds, Clickable, ElementId, LayoutId, MouseDownEvent, MouseUpEvent, Pixels,
+    Point, ViewContext,
+};
 use derive_more::{Deref, DerefMut};
 pub(crate) use smallvec::SmallVec;
 
@@ -24,10 +29,26 @@ pub trait Element: 'static + Send + Sync {
     );
 }
 
-pub trait StatefulElement: Element {
+pub trait IdentifiedElement: Element {
     fn element_id(&self) -> ElementId {
         Element::element_id(self).unwrap()
     }
+
+    fn on_click(
+        self,
+        listener: impl Fn(
+                &mut Self::ViewState,
+                (&MouseDownEvent, &MouseUpEvent),
+                &mut ViewContext<Self::ViewState>,
+            ) + Send
+            + Sync
+            + 'static,
+    ) -> Clickable<Self>
+    where
+        Self: Sized,
+    {
+        Clickable::new(self, Arc::from(listener))
+    }
 }
 
 #[derive(Deref, DerefMut, Default, Clone, Debug, Eq, PartialEq, Hash)]

crates/gpui3/src/elements.rs 🔗

@@ -1,3 +1,4 @@
+mod clickable;
 mod div;
 mod hoverable;
 mod identified;
@@ -6,6 +7,7 @@ mod pressable;
 mod svg;
 mod text;
 
+pub use clickable::*;
 pub use div::*;
 pub use hoverable::*;
 pub use identified::*;

crates/gpui3/src/elements/clickable.rs 🔗

@@ -0,0 +1,138 @@
+use crate::{
+    AnyElement, Bounds, DispatchPhase, Element, IdentifiedElement, Interactive, MouseDownEvent,
+    MouseEventListeners, MouseUpEvent, ParentElement, Pixels, Styled, ViewContext,
+};
+use parking_lot::Mutex;
+use refineable::RefinementCascade;
+use smallvec::SmallVec;
+use std::sync::Arc;
+
+pub type ClickListener<S> =
+    dyn Fn(&mut S, (&MouseDownEvent, &MouseUpEvent), &mut ViewContext<S>) + Send + Sync + 'static;
+
+pub struct Clickable<E: Element> {
+    child: E,
+    listener: Arc<ClickListener<E::ViewState>>,
+}
+
+pub struct ClickableState<S> {
+    last_mouse_down: Arc<Mutex<Option<MouseDownEvent>>>,
+    child_state: S,
+}
+
+impl<E: Element> Clickable<E> {
+    pub fn new(child: E, listener: Arc<ClickListener<E::ViewState>>) -> Self {
+        Self { child, listener }
+    }
+}
+
+impl<E> Styled for Clickable<E>
+where
+    E: Styled + IdentifiedElement,
+{
+    type Style = E::Style;
+
+    fn style_cascade(&mut self) -> &mut RefinementCascade<E::Style> {
+        self.child.style_cascade()
+    }
+
+    fn declared_style(&mut self) -> &mut <Self::Style as refineable::Refineable>::Refinement {
+        self.child.declared_style()
+    }
+}
+
+impl<S, E> Interactive<S> for Clickable<E>
+where
+    S: 'static + Send + Sync,
+    E: IdentifiedElement + Interactive<S>,
+{
+    fn listeners(&mut self) -> &mut MouseEventListeners<S> {
+        self.child.listeners()
+    }
+}
+
+impl<E> Element for Clickable<E>
+where
+    E: IdentifiedElement,
+{
+    type ViewState = E::ViewState;
+    type ElementState = ClickableState<E::ElementState>;
+
+    fn element_id(&self) -> Option<crate::ElementId> {
+        Some(IdentifiedElement::element_id(&self.child))
+    }
+
+    fn layout(
+        &mut self,
+        state: &mut Self::ViewState,
+        element_state: Option<Self::ElementState>,
+        cx: &mut ViewContext<Self::ViewState>,
+    ) -> (crate::LayoutId, Self::ElementState) {
+        if let Some(element_state) = element_state {
+            let (layout_id, child_state) =
+                self.child
+                    .layout(state, Some(element_state.child_state), cx);
+
+            let element_state = ClickableState {
+                last_mouse_down: element_state.last_mouse_down,
+                child_state,
+            };
+            (layout_id, element_state)
+        } else {
+            let (layout_id, child_state) = self.child.layout(state, None, cx);
+            let element_state = ClickableState {
+                last_mouse_down: Default::default(),
+                child_state,
+            };
+            (layout_id, element_state)
+        }
+    }
+
+    fn paint(
+        &mut self,
+        bounds: Bounds<Pixels>,
+        state: &mut Self::ViewState,
+        element_state: &mut Self::ElementState,
+        cx: &mut ViewContext<Self::ViewState>,
+    ) {
+        let last_mouse_down = element_state.last_mouse_down.clone();
+        let is_some = last_mouse_down.lock().is_some();
+
+        if is_some {
+            let listener = self.listener.clone();
+            cx.on_mouse_event(move |view, up_event: &MouseUpEvent, phase, cx| {
+                if phase == DispatchPhase::Capture && !bounds.contains_point(up_event.position) {
+                    *last_mouse_down.lock() = None;
+                } else if phase == DispatchPhase::Bubble && bounds.contains_point(up_event.position)
+                {
+                    if let Some(down_event) = last_mouse_down.lock().take() {
+                        listener(view, (&down_event, up_event), cx);
+                    } else {
+                        log::error!("No mouse down event found for click event");
+                    }
+                }
+            })
+        } else {
+            cx.on_mouse_event(move |_, event: &MouseDownEvent, phase, _| {
+                if phase == DispatchPhase::Bubble {
+                    if bounds.contains_point(event.position) {
+                        *last_mouse_down.lock() = Some(event.clone());
+                    }
+                }
+            })
+        }
+
+        self.child
+            .paint(bounds, state, &mut element_state.child_state, cx);
+    }
+}
+
+impl<E: IdentifiedElement + ParentElement> ParentElement for Clickable<E> {
+    type State = E::State;
+
+    fn children_mut(&mut self) -> &mut SmallVec<[AnyElement<Self::State>; 2]> {
+        self.child.children_mut()
+    }
+}
+
+impl<E> IdentifiedElement for Clickable<E> where E: IdentifiedElement + Styled {}

crates/gpui3/src/elements/div.rs 🔗

@@ -1,7 +1,7 @@
 use crate::{
-    AnyElement, Bounds, Element, ElementId, Interactive, LayoutId, MouseEventListeners, Overflow,
-    ParentElement, Pixels, Point, Refineable, RefinementCascade, StatefulElement, Style, Styled,
-    ViewContext,
+    AnyElement, Bounds, Element, ElementId, IdentifiedElement, Interactive, LayoutId,
+    MouseEventListeners, Overflow, ParentElement, Pixels, Point, Refineable, RefinementCascade,
+    Style, Styled, ViewContext,
 };
 use parking_lot::Mutex;
 use smallvec::SmallVec;
@@ -171,7 +171,7 @@ impl<V: 'static + Send + Sync, Marker: 'static + Send + Sync> Styled for Div<V,
     }
 }
 
-impl<V: Send + Sync + 'static> StatefulElement for Div<V, HasId> {}
+impl<V: Send + Sync + 'static> IdentifiedElement for Div<V, HasId> {}
 
 impl<V: Send + Sync + 'static> Interactive<V> for Div<V, HasId> {
     fn listeners(&mut self) -> &mut MouseEventListeners<V> {
@@ -179,10 +179,10 @@ impl<V: Send + Sync + 'static> Interactive<V> for Div<V, HasId> {
     }
 }
 
-impl<S: 'static> ParentElement for Div<S> {
-    type State = S;
+impl<V: 'static, Marker: 'static + Send + Sync> ParentElement for Div<V, Marker> {
+    type State = V;
 
-    fn children_mut(&mut self) -> &mut SmallVec<[AnyElement<S>; 2]> {
+    fn children_mut(&mut self) -> &mut SmallVec<[AnyElement<V>; 2]> {
         &mut self.children
     }
 }

crates/gpui3/src/elements/hoverable.rs 🔗

@@ -1,6 +1,6 @@
 use crate::{
-    AnyElement, Bounds, DispatchPhase, Element, ElementId, Interactive, MouseEventListeners,
-    MouseMoveEvent, ParentElement, Pixels, StatefulElement, Styled, ViewContext,
+    AnyElement, Bounds, DispatchPhase, Element, ElementId, IdentifiedElement, Interactive,
+    MouseEventListeners, MouseMoveEvent, ParentElement, Pixels, Styled, ViewContext,
 };
 use refineable::{CascadeSlot, Refineable, RefinementCascade};
 use smallvec::SmallVec;
@@ -104,9 +104,9 @@ impl<E: ParentElement + Styled> ParentElement for Hoverable<E> {
     }
 }
 
-impl<E> StatefulElement for Hoverable<E>
+impl<E> IdentifiedElement for Hoverable<E>
 where
-    E: StatefulElement + Styled,
+    E: IdentifiedElement + Styled,
     <E as Styled>::Style: 'static + Refineable + Send + Sync + Default,
     <<E as Styled>::Style as Refineable>::Refinement: 'static + Refineable + Send + Sync + Default,
 {

crates/gpui3/src/elements/identified.rs 🔗

@@ -2,8 +2,8 @@ use refineable::{Refineable, RefinementCascade};
 use smallvec::SmallVec;
 
 use crate::{
-    AnyElement, BorrowWindow, Bounds, Element, ElementId, LayoutId, ParentElement, StatefulElement,
-    Styled, ViewContext,
+    AnyElement, BorrowWindow, Bounds, Element, ElementId, IdentifiedElement, LayoutId,
+    ParentElement, Styled, ViewContext,
 };
 
 pub struct Identified<E> {
@@ -41,7 +41,7 @@ impl<E: Element> Element for Identified<E> {
     }
 }
 
-impl<E: Element> StatefulElement for Identified<E> {}
+impl<E: Element> IdentifiedElement for Identified<E> {}
 
 impl<E: Styled> Styled for Identified<E> {
     type Style = E::Style;

crates/gpui3/src/elements/pressable.rs 🔗

@@ -1,6 +1,6 @@
 use crate::{
-    AnyElement, Bounds, DispatchPhase, Element, Interactive, MouseDownEvent, MouseEventListeners,
-    MouseUpEvent, ParentElement, Pixels, StatefulElement, Styled, ViewContext,
+    AnyElement, Bounds, DispatchPhase, Element, IdentifiedElement, Interactive, MouseDownEvent,
+    MouseEventListeners, MouseUpEvent, ParentElement, Pixels, Styled, ViewContext,
 };
 use refineable::{CascadeSlot, Refineable, RefinementCascade};
 use smallvec::SmallVec;
@@ -53,7 +53,7 @@ impl<S: 'static + Send + Sync, E: Interactive<S> + Styled> Interactive<S> for Pr
 
 impl<E> Element for Pressable<E>
 where
-    E: Styled + StatefulElement,
+    E: Styled + IdentifiedElement,
     <E as Styled>::Style: 'static + Refineable + Send + Sync + Default,
     <<E as Styled>::Style as Refineable>::Refinement: 'static + Refineable + Send + Sync + Default,
 {
@@ -61,7 +61,7 @@ where
     type ElementState = PressableState<E::ElementState>;
 
     fn element_id(&self) -> Option<crate::ElementId> {
-        Some(StatefulElement::element_id(&self.child))
+        Some(IdentifiedElement::element_id(&self.child))
     }
 
     fn layout(
@@ -127,7 +127,10 @@ where
     }
 }
 
-impl<E: ParentElement + Styled> ParentElement for Pressable<E> {
+impl<E> ParentElement for Pressable<E>
+where
+    E: ParentElement + IdentifiedElement + Styled,
+{
     type State = E::State;
 
     fn children_mut(&mut self) -> &mut SmallVec<[AnyElement<Self::State>; 2]> {
@@ -135,9 +138,9 @@ impl<E: ParentElement + Styled> ParentElement for Pressable<E> {
     }
 }
 
-impl<E> StatefulElement for Pressable<E>
+impl<E> IdentifiedElement for Pressable<E>
 where
-    E: StatefulElement + Styled,
+    E: IdentifiedElement + Styled,
     <E as Styled>::Style: 'static + Refineable + Send + Sync + Default,
     <<E as Styled>::Style as Refineable>::Refinement: 'static + Refineable + Send + Sync + Default,
 {

crates/gpui3/src/interactive.rs 🔗

@@ -2,7 +2,6 @@ use crate::{
     Bounds, DispatchPhase, MouseButton, MouseDownEvent, MouseMoveEvent, MouseUpEvent, Pixels,
     ScrollWheelEvent, ViewContext,
 };
-use parking_lot::Mutex;
 use smallvec::SmallVec;
 use std::sync::Arc;
 
@@ -93,37 +92,6 @@ pub trait Interactive<S: 'static + Send + Sync> {
         self
     }
 
-    fn on_click(
-        self,
-        button: MouseButton,
-        handler: impl Fn(&mut S, (&MouseDownEvent, &MouseUpEvent), &mut ViewContext<S>)
-            + Send
-            + Sync
-            + 'static,
-    ) -> Self
-    where
-        Self: Sized,
-    {
-        let down_event = Arc::new(Mutex::new(None));
-        self.on_mouse_down(button, {
-            let down_event = down_event.clone();
-            move |_, event, _| {
-                down_event.lock().replace(event.clone());
-            }
-        })
-        .on_mouse_up_out(button, {
-            let down_event = down_event.clone();
-            move |_, _, _| {
-                down_event.lock().take();
-            }
-        })
-        .on_mouse_up(button, move |view, event, cx| {
-            if let Some(down_event) = down_event.lock().take() {
-                handler(view, (&down_event, event), cx);
-            }
-        })
-    }
-
     fn on_mouse_move(
         mut self,
         handler: impl Fn(&mut S, &MouseMoveEvent, &mut ViewContext<S>) + Send + Sync + 'static,

crates/storybook2/src/collab_panel.rs 🔗

@@ -1,8 +1,8 @@
 use crate::theme::{theme, Theme};
 use gpui3::{
-    div, img, svg, view, AppContext, Context, Element, ElementId, IntoAnyElement, MouseButton,
-    ParentElement, ScrollState, SharedString, StyleHelpers, Styled, View, ViewContext,
-    WindowContext,
+    div, img, svg, view, AppContext, Context, Element, ElementId, IdentifiedElement,
+    IntoAnyElement, ParentElement, ScrollState, SharedString, StyleHelpers, Styled, View,
+    ViewContext, WindowContext,
 };
 
 pub struct CollabPanel {
@@ -45,7 +45,8 @@ impl CollabPanel {
                     // List Container
                     .child(
                         div()
-                            .on_click(MouseButton::Left, |_, _, _| {
+                            .id(0)
+                            .on_click(|_, _, _| {
                                 dbg!("click!");
                             })
                             .fill(theme.lowest.base.default.background)

crates/storybook2/src/theme.rs 🔗

@@ -147,6 +147,10 @@ impl<E: Element> Element for Themed<E> {
     type ViewState = E::ViewState;
     type ElementState = E::ElementState;
 
+    fn element_id(&self) -> Option<gpui3::ElementId> {
+        None
+    }
+
     fn layout(
         &mut self,
         state: &mut E::ViewState,

crates/storybook2/src/workspace.rs 🔗

@@ -47,8 +47,7 @@ impl Workspace {
                         .flex_row()
                         .overflow_hidden()
                         .child(self.left_panel.clone())
-                        .child(div().h_full().flex_1())
-                        .child(self.right_panel.clone()),
+                        .child(div().h_full().flex_1()), // .child(self.right_panel.clone()),
                 )
                 .child(statusbar::statusbar(cx))
         })