Checkpoint

Antonio Scandurra created

Change summary

crates/gpui3/src/element.rs     | 433 ----------------------------------
crates/gpui3/src/focusable.rs   |  98 +++++++
crates/gpui3/src/interactive.rs | 395 +++++++++++++++++++++++++++++--
3 files changed, 463 insertions(+), 463 deletions(-)

Detailed changes

crates/gpui3/src/element.rs 🔗

@@ -1,16 +1,7 @@
-use crate::{
-    AppContext, BorrowWindow, Bounds, DispatchPhase, ElementId, FocusHandle, FocusListeners,
-    KeyDownEvent, KeyListener, KeyMatch, LayoutId, MouseClickEvent, MouseClickListener,
-    MouseDownEvent, MouseDownListener, MouseMoveEvent, MouseMoveListener, MouseUpEvent,
-    MouseUpListener, Pixels, Point, ScrollWheelEvent, ScrollWheelListener, SharedString, Style,
-    StyleRefinement, ViewContext, WindowContext,
-};
-use collections::HashMap;
+use crate::{BorrowWindow, Bounds, ElementId, LayoutId, Pixels, Point, ViewContext};
 use derive_more::{Deref, DerefMut};
-use parking_lot::Mutex;
-use refineable::Refineable;
 pub(crate) use smallvec::SmallVec;
-use std::{any::TypeId, mem, sync::Arc};
+use std::mem;
 
 pub trait Element: 'static + Send + Sync + IntoAnyElement<Self::ViewState> {
     type ViewState: 'static + Send + Sync;
@@ -44,426 +35,6 @@ pub trait Element: 'static + Send + Sync + IntoAnyElement<Self::ViewState> {
 #[derive(Deref, DerefMut, Default, Clone, Debug, Eq, PartialEq, Hash)]
 pub struct GlobalElementId(SmallVec<[ElementId; 32]>);
 
-pub trait ElementInteraction<V: 'static + Send + Sync>: 'static + Send + Sync {
-    fn as_stateless(&self) -> &StatelessInteraction<V>;
-    fn as_stateless_mut(&mut self) -> &mut StatelessInteraction<V>;
-    fn as_stateful(&self) -> Option<&StatefulInteractivity<V>>;
-    fn as_stateful_mut(&mut self) -> Option<&mut StatefulInteractivity<V>>;
-
-    fn initialize<R>(
-        &mut self,
-        cx: &mut ViewContext<V>,
-        f: impl FnOnce(&mut ViewContext<V>) -> R,
-    ) -> R {
-        if let Some(stateful) = self.as_stateful_mut() {
-            cx.with_element_id(stateful.id.clone(), |global_id, cx| {
-                stateful.key_listeners.push((
-                    TypeId::of::<KeyDownEvent>(),
-                    Arc::new(move |_, key_down, context, phase, cx| {
-                        if phase == DispatchPhase::Bubble {
-                            let key_down = key_down.downcast_ref::<KeyDownEvent>().unwrap();
-                            if let KeyMatch::Some(action) =
-                                cx.match_keystroke(&global_id, &key_down.keystroke, context)
-                            {
-                                return Some(action);
-                            }
-                        }
-
-                        None
-                    }),
-                ));
-                let result = stateful.stateless.initialize(cx, f);
-                stateful.key_listeners.pop();
-                result
-            })
-        } else {
-            cx.with_key_listeners(&self.as_stateless().key_listeners, f)
-        }
-    }
-
-    fn refine_style(
-        &self,
-        style: &mut Style,
-        bounds: Bounds<Pixels>,
-        element_state: &InteractiveElementState,
-        cx: &mut ViewContext<V>,
-    ) {
-        let mouse_position = cx.mouse_position();
-        let stateless = self.as_stateless();
-        if let Some(group_hover) = stateless.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(&stateless.hover_style);
-        }
-
-        if let Some(stateful) = self.as_stateful() {
-            let active_state = element_state.active_state.lock();
-            if active_state.group {
-                if let Some(group_style) = stateful.group_active_style.as_ref() {
-                    style.refine(&group_style.style);
-                }
-            }
-            if active_state.element {
-                style.refine(&stateful.active_style);
-            }
-        }
-    }
-
-    fn paint(
-        &mut self,
-        bounds: Bounds<Pixels>,
-        element_state: &InteractiveElementState,
-        cx: &mut ViewContext<V>,
-    ) {
-        let stateless = self.as_stateless();
-        for listener in stateless.mouse_down_listeners.iter().cloned() {
-            cx.on_mouse_event(move |state, event: &MouseDownEvent, phase, cx| {
-                listener(state, event, &bounds, phase, cx);
-            })
-        }
-
-        for listener in stateless.mouse_up_listeners.iter().cloned() {
-            cx.on_mouse_event(move |state, event: &MouseUpEvent, phase, cx| {
-                listener(state, event, &bounds, phase, cx);
-            })
-        }
-
-        for listener in stateless.mouse_move_listeners.iter().cloned() {
-            cx.on_mouse_event(move |state, event: &MouseMoveEvent, phase, cx| {
-                listener(state, event, &bounds, phase, cx);
-            })
-        }
-
-        for listener in stateless.scroll_wheel_listeners.iter().cloned() {
-            cx.on_mouse_event(move |state, event: &ScrollWheelEvent, phase, cx| {
-                listener(state, event, &bounds, phase, cx);
-            })
-        }
-
-        let hover_group_bounds = stateless
-            .group_hover_style
-            .as_ref()
-            .and_then(|group_hover| GroupBounds::get(&group_hover.group, cx));
-
-        if let Some(group_bounds) = hover_group_bounds {
-            paint_hover_listener(group_bounds, cx);
-        }
-
-        if stateless.hover_style.is_some() {
-            paint_hover_listener(bounds, cx);
-        }
-
-        if let Some(stateful) = self.as_stateful() {
-            let click_listeners = stateful.mouse_click_listeners.clone();
-
-            let pending_click = element_state.pending_click.clone();
-            let mouse_down = pending_click.lock().clone();
-            if let Some(mouse_down) = mouse_down {
-                cx.on_mouse_event(move |state, event: &MouseUpEvent, phase, cx| {
-                    if phase == DispatchPhase::Bubble && bounds.contains_point(&event.position) {
-                        let mouse_click = MouseClickEvent {
-                            down: mouse_down.clone(),
-                            up: event.clone(),
-                        };
-                        for listener in &click_listeners {
-                            listener(state, &mouse_click, cx);
-                        }
-                    }
-
-                    *pending_click.lock() = None;
-                });
-            } else {
-                cx.on_mouse_event(move |_state, event: &MouseDownEvent, phase, _cx| {
-                    if phase == DispatchPhase::Bubble && bounds.contains_point(&event.position) {
-                        *pending_click.lock() = Some(event.clone());
-                    }
-                });
-            }
-
-            let active_state = element_state.active_state.clone();
-            if active_state.lock().is_none() {
-                let active_group_bounds = stateful
-                    .group_active_style
-                    .as_ref()
-                    .and_then(|group_active| GroupBounds::get(&group_active.group, cx));
-                cx.on_mouse_event(move |_view, down: &MouseDownEvent, phase, cx| {
-                    if phase == DispatchPhase::Bubble {
-                        let group = active_group_bounds
-                            .map_or(false, |bounds| bounds.contains_point(&down.position));
-                        let element = bounds.contains_point(&down.position);
-                        if group || element {
-                            *active_state.lock() = ActiveState { group, element };
-                            cx.notify();
-                        }
-                    }
-                });
-            } else {
-                cx.on_mouse_event(move |_, _: &MouseUpEvent, phase, cx| {
-                    if phase == DispatchPhase::Capture {
-                        *active_state.lock() = ActiveState::default();
-                        cx.notify();
-                    }
-                });
-            }
-        }
-    }
-}
-
-fn paint_hover_listener<V>(bounds: Bounds<Pixels>, cx: &mut ViewContext<V>)
-where
-    V: 'static + Send + Sync,
-{
-    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();
-            }
-        }
-    });
-}
-
-#[derive(Deref, DerefMut)]
-pub struct StatefulInteractivity<V: 'static + Send + Sync> {
-    pub id: ElementId,
-    #[deref]
-    #[deref_mut]
-    stateless: StatelessInteraction<V>,
-    pub mouse_click_listeners: SmallVec<[MouseClickListener<V>; 2]>,
-    pub active_style: StyleRefinement,
-    pub group_active_style: Option<GroupStyle>,
-}
-
-impl<V> ElementInteraction<V> for StatefulInteractivity<V>
-where
-    V: 'static + Send + Sync,
-{
-    fn as_stateful(&self) -> Option<&StatefulInteractivity<V>> {
-        Some(self)
-    }
-
-    fn as_stateful_mut(&mut self) -> Option<&mut StatefulInteractivity<V>> {
-        Some(self)
-    }
-
-    fn as_stateless(&self) -> &StatelessInteraction<V> {
-        &self.stateless
-    }
-
-    fn as_stateless_mut(&mut self) -> &mut StatelessInteraction<V> {
-        &mut self.stateless
-    }
-}
-
-impl<V> From<ElementId> for StatefulInteractivity<V>
-where
-    V: 'static + Send + Sync,
-{
-    fn from(id: ElementId) -> Self {
-        Self {
-            id,
-            stateless: StatelessInteraction::default(),
-            mouse_click_listeners: SmallVec::new(),
-            active_style: StyleRefinement::default(),
-            group_active_style: None,
-        }
-    }
-}
-
-pub struct StatelessInteraction<V> {
-    pub mouse_down_listeners: SmallVec<[MouseDownListener<V>; 2]>,
-    pub mouse_up_listeners: SmallVec<[MouseUpListener<V>; 2]>,
-    pub mouse_move_listeners: SmallVec<[MouseMoveListener<V>; 2]>,
-    pub scroll_wheel_listeners: SmallVec<[ScrollWheelListener<V>; 2]>,
-    pub key_listeners: SmallVec<[(TypeId, KeyListener<V>); 32]>,
-    pub hover_style: StyleRefinement,
-    pub group_hover_style: Option<GroupStyle>,
-}
-
-pub struct GroupStyle {
-    pub group: SharedString,
-    pub style: StyleRefinement,
-}
-
-#[derive(Default)]
-pub struct GroupBounds(HashMap<SharedString, SmallVec<[Bounds<Pixels>; 1]>>);
-
-impl GroupBounds {
-    pub fn get(name: &SharedString, cx: &mut AppContext) -> Option<Bounds<Pixels>> {
-        cx.default_global::<Self>()
-            .0
-            .get(name)
-            .and_then(|bounds_stack| bounds_stack.last())
-            .cloned()
-    }
-
-    pub fn push(name: SharedString, bounds: Bounds<Pixels>, cx: &mut AppContext) {
-        cx.default_global::<Self>()
-            .0
-            .entry(name)
-            .or_default()
-            .push(bounds);
-    }
-
-    pub fn pop(name: &SharedString, cx: &mut AppContext) {
-        cx.default_global::<GroupBounds>()
-            .0
-            .get_mut(name)
-            .unwrap()
-            .pop();
-    }
-}
-
-#[derive(Copy, Clone, Default, Eq, PartialEq)]
-struct ActiveState {
-    pub group: bool,
-    pub element: bool,
-}
-
-impl ActiveState {
-    pub fn is_none(&self) -> bool {
-        !self.group && !self.element
-    }
-}
-
-#[derive(Default)]
-pub struct InteractiveElementState {
-    active_state: Arc<Mutex<ActiveState>>,
-    pending_click: Arc<Mutex<Option<MouseDownEvent>>>,
-}
-
-impl<V> Default for StatelessInteraction<V> {
-    fn default() -> Self {
-        Self {
-            mouse_down_listeners: SmallVec::new(),
-            mouse_up_listeners: SmallVec::new(),
-            mouse_move_listeners: SmallVec::new(),
-            scroll_wheel_listeners: SmallVec::new(),
-            key_listeners: SmallVec::new(),
-            hover_style: StyleRefinement::default(),
-            group_hover_style: None,
-        }
-    }
-}
-
-impl<V> ElementInteraction<V> for StatelessInteraction<V>
-where
-    V: 'static + Send + Sync,
-{
-    fn as_stateful(&self) -> Option<&StatefulInteractivity<V>> {
-        None
-    }
-
-    fn as_stateful_mut(&mut self) -> Option<&mut StatefulInteractivity<V>> {
-        None
-    }
-
-    fn as_stateless(&self) -> &StatelessInteraction<V> {
-        self
-    }
-
-    fn as_stateless_mut(&mut self) -> &mut StatelessInteraction<V> {
-        self
-    }
-}
-
-pub trait ElementFocus<V: 'static + Send + Sync>: 'static + Send + Sync {
-    fn as_focusable(&self) -> Option<&FocusEnabled<V>>;
-
-    fn initialize<R>(
-        &self,
-        cx: &mut ViewContext<V>,
-        f: impl FnOnce(&mut ViewContext<V>) -> R,
-    ) -> R {
-        if let Some(focusable) = self.as_focusable() {
-            for listener in focusable.focus_listeners.iter().cloned() {
-                cx.on_focus_changed(move |view, event, cx| listener(view, event, cx));
-            }
-            cx.with_focus(focusable.focus_handle.clone(), |cx| f(cx))
-        } else {
-            f(cx)
-        }
-    }
-
-    fn refine_style(&self, style: &mut Style, cx: &WindowContext) {
-        if let Some(focusable) = self.as_focusable() {
-            if focusable.focus_handle.contains_focused(cx) {
-                style.refine(&focusable.focus_in_style);
-            }
-
-            if focusable.focus_handle.within_focused(cx) {
-                style.refine(&focusable.in_focus_style);
-            }
-
-            if focusable.focus_handle.is_focused(cx) {
-                style.refine(&focusable.focus_style);
-            }
-        }
-    }
-
-    fn paint(&self, bounds: Bounds<Pixels>, cx: &mut WindowContext) {
-        if let Some(focusable) = self.as_focusable() {
-            let focus_handle = focusable.focus_handle.clone();
-            cx.on_mouse_event(move |event: &MouseDownEvent, phase, cx| {
-                if phase == DispatchPhase::Bubble && bounds.contains_point(&event.position) {
-                    if !cx.default_prevented() {
-                        cx.focus(&focus_handle);
-                        cx.prevent_default();
-                    }
-                }
-            })
-        }
-    }
-}
-
-pub struct FocusEnabled<V: 'static + Send + Sync> {
-    pub focus_handle: FocusHandle,
-    pub focus_listeners: FocusListeners<V>,
-    pub focus_style: StyleRefinement,
-    pub focus_in_style: StyleRefinement,
-    pub in_focus_style: StyleRefinement,
-}
-
-impl<V> ElementFocus<V> for FocusEnabled<V>
-where
-    V: 'static + Send + Sync,
-{
-    fn as_focusable(&self) -> Option<&FocusEnabled<V>> {
-        Some(self)
-    }
-}
-
-impl<V> From<FocusHandle> for FocusEnabled<V>
-where
-    V: 'static + Send + Sync,
-{
-    fn from(value: FocusHandle) -> Self {
-        Self {
-            focus_handle: value,
-            focus_listeners: FocusListeners::default(),
-            focus_style: StyleRefinement::default(),
-            focus_in_style: StyleRefinement::default(),
-            in_focus_style: StyleRefinement::default(),
-        }
-    }
-}
-
-pub struct FocusDisabled;
-
-impl<V> ElementFocus<V> for FocusDisabled
-where
-    V: 'static + Send + Sync,
-{
-    fn as_focusable(&self) -> Option<&FocusEnabled<V>> {
-        None
-    }
-}
-
 pub trait ParentElement: Element {
     fn children_mut(&mut self) -> &mut SmallVec<[AnyElement<Self::ViewState>; 2]>;
 

crates/gpui3/src/focusable.rs 🔗

@@ -1,4 +1,8 @@
-use crate::{Element, FocusEvent, FocusHandle, StyleRefinement, ViewContext};
+use crate::{
+    Bounds, DispatchPhase, Element, FocusEvent, FocusHandle, MouseDownEvent, Pixels, Style,
+    StyleRefinement, ViewContext, WindowContext,
+};
+use refineable::Refineable;
 use smallvec::SmallVec;
 use std::sync::Arc;
 
@@ -135,3 +139,95 @@ pub trait Focusable: Element {
         self
     }
 }
+
+pub trait ElementFocus<V: 'static + Send + Sync>: 'static + Send + Sync {
+    fn as_focusable(&self) -> Option<&FocusEnabled<V>>;
+
+    fn initialize<R>(
+        &self,
+        cx: &mut ViewContext<V>,
+        f: impl FnOnce(&mut ViewContext<V>) -> R,
+    ) -> R {
+        if let Some(focusable) = self.as_focusable() {
+            for listener in focusable.focus_listeners.iter().cloned() {
+                cx.on_focus_changed(move |view, event, cx| listener(view, event, cx));
+            }
+            cx.with_focus(focusable.focus_handle.clone(), |cx| f(cx))
+        } else {
+            f(cx)
+        }
+    }
+
+    fn refine_style(&self, style: &mut Style, cx: &WindowContext) {
+        if let Some(focusable) = self.as_focusable() {
+            if focusable.focus_handle.contains_focused(cx) {
+                style.refine(&focusable.focus_in_style);
+            }
+
+            if focusable.focus_handle.within_focused(cx) {
+                style.refine(&focusable.in_focus_style);
+            }
+
+            if focusable.focus_handle.is_focused(cx) {
+                style.refine(&focusable.focus_style);
+            }
+        }
+    }
+
+    fn paint(&self, bounds: Bounds<Pixels>, cx: &mut WindowContext) {
+        if let Some(focusable) = self.as_focusable() {
+            let focus_handle = focusable.focus_handle.clone();
+            cx.on_mouse_event(move |event: &MouseDownEvent, phase, cx| {
+                if phase == DispatchPhase::Bubble && bounds.contains_point(&event.position) {
+                    if !cx.default_prevented() {
+                        cx.focus(&focus_handle);
+                        cx.prevent_default();
+                    }
+                }
+            })
+        }
+    }
+}
+
+pub struct FocusEnabled<V: 'static + Send + Sync> {
+    pub focus_handle: FocusHandle,
+    pub focus_listeners: FocusListeners<V>,
+    pub focus_style: StyleRefinement,
+    pub focus_in_style: StyleRefinement,
+    pub in_focus_style: StyleRefinement,
+}
+
+impl<V> ElementFocus<V> for FocusEnabled<V>
+where
+    V: 'static + Send + Sync,
+{
+    fn as_focusable(&self) -> Option<&FocusEnabled<V>> {
+        Some(self)
+    }
+}
+
+impl<V> From<FocusHandle> for FocusEnabled<V>
+where
+    V: 'static + Send + Sync,
+{
+    fn from(value: FocusHandle) -> Self {
+        Self {
+            focus_handle: value,
+            focus_listeners: FocusListeners::default(),
+            focus_style: StyleRefinement::default(),
+            focus_in_style: StyleRefinement::default(),
+            in_focus_style: StyleRefinement::default(),
+        }
+    }
+}
+
+pub struct FocusDisabled;
+
+impl<V> ElementFocus<V> for FocusDisabled
+where
+    V: 'static + Send + Sync,
+{
+    fn as_focusable(&self) -> Option<&FocusEnabled<V>> {
+        None
+    }
+}

crates/gpui3/src/interactive.rs 🔗

@@ -1,8 +1,13 @@
 use crate::{
-    point, Action, Bounds, DispatchContext, DispatchPhase, Element, FocusHandle, GroupStyle,
-    Keystroke, Modifiers, Pixels, Point, SharedString, StatefulInteractivity, StatelessInteraction,
+    point, Action, AppContext, BorrowWindow, Bounds, DispatchContext, DispatchPhase, Element,
+    ElementId, FocusHandle, KeyMatch, Keystroke, Modifiers, Pixels, Point, SharedString, Style,
     StyleRefinement, ViewContext,
 };
+use collections::HashMap;
+use derive_more::{Deref, DerefMut};
+use parking_lot::Mutex;
+use refineable::Refineable;
+use smallvec::SmallVec;
 use std::{
     any::{Any, TypeId},
     ops::Deref,
@@ -12,6 +17,29 @@ use std::{
 pub trait StatelessInteractive: Element {
     fn stateless_interactivity(&mut self) -> &mut StatelessInteraction<Self::ViewState>;
 
+    fn hover(mut self, f: impl FnOnce(StyleRefinement) -> StyleRefinement) -> Self
+    where
+        Self: Sized,
+    {
+        self.stateless_interactivity().hover_style = f(StyleRefinement::default());
+        self
+    }
+
+    fn group_hover(
+        mut self,
+        group_name: impl Into<SharedString>,
+        f: impl FnOnce(StyleRefinement) -> StyleRefinement,
+    ) -> Self
+    where
+        Self: Sized,
+    {
+        self.stateless_interactivity().group_hover_style = Some(GroupStyle {
+            group: group_name.into(),
+            style: f(StyleRefinement::default()),
+        });
+        self
+    }
+
     fn on_mouse_down(
         mut self,
         button: MouseButton,
@@ -148,29 +176,6 @@ pub trait StatelessInteractive: Element {
         self
     }
 
-    fn hover(mut self, f: impl FnOnce(StyleRefinement) -> StyleRefinement) -> Self
-    where
-        Self: Sized,
-    {
-        self.stateless_interactivity().hover_style = f(StyleRefinement::default());
-        self
-    }
-
-    fn group_hover(
-        mut self,
-        group_name: impl Into<SharedString>,
-        f: impl FnOnce(StyleRefinement) -> StyleRefinement,
-    ) -> Self
-    where
-        Self: Sized,
-    {
-        self.stateless_interactivity().group_hover_style = Some(GroupStyle {
-            group: group_name.into(),
-            style: f(StyleRefinement::default()),
-        });
-        self
-    }
-
     fn on_key_down(
         mut self,
         listener: impl Fn(
@@ -196,9 +201,9 @@ pub trait StatelessInteractive: Element {
         self
     }
 
-    fn on_key_up(
+    fn on_action<A: 'static>(
         mut self,
-        listener: impl Fn(&mut Self::ViewState, &KeyUpEvent, DispatchPhase, &mut ViewContext<Self::ViewState>)
+        listener: impl Fn(&mut Self::ViewState, &A, DispatchPhase, &mut ViewContext<Self::ViewState>)
             + Send
             + Sync
             + 'static,
@@ -207,7 +212,7 @@ pub trait StatelessInteractive: Element {
         Self: Sized,
     {
         self.stateless_interactivity().key_listeners.push((
-            TypeId::of::<KeyUpEvent>(),
+            TypeId::of::<A>(),
             Arc::new(move |view, event, _, phase, cx| {
                 let event = event.downcast_ref().unwrap();
                 listener(view, event, phase, cx);
@@ -217,9 +222,9 @@ pub trait StatelessInteractive: Element {
         self
     }
 
-    fn on_action<A: 'static>(
+    fn on_key_up(
         mut self,
-        listener: impl Fn(&mut Self::ViewState, &A, DispatchPhase, &mut ViewContext<Self::ViewState>)
+        listener: impl Fn(&mut Self::ViewState, &KeyUpEvent, DispatchPhase, &mut ViewContext<Self::ViewState>)
             + Send
             + Sync
             + 'static,
@@ -228,7 +233,7 @@ pub trait StatelessInteractive: Element {
         Self: Sized,
     {
         self.stateless_interactivity().key_listeners.push((
-            TypeId::of::<A>(),
+            TypeId::of::<KeyUpEvent>(),
             Arc::new(move |view, event, _, phase, cx| {
                 let event = event.downcast_ref().unwrap();
                 listener(view, event, phase, cx);
@@ -282,6 +287,334 @@ pub trait StatefulInteractive: StatelessInteractive {
     }
 }
 
+pub trait ElementInteraction<V: 'static + Send + Sync>: 'static + Send + Sync {
+    fn as_stateless(&self) -> &StatelessInteraction<V>;
+    fn as_stateless_mut(&mut self) -> &mut StatelessInteraction<V>;
+    fn as_stateful(&self) -> Option<&StatefulInteractivity<V>>;
+    fn as_stateful_mut(&mut self) -> Option<&mut StatefulInteractivity<V>>;
+
+    fn initialize<R>(
+        &mut self,
+        cx: &mut ViewContext<V>,
+        f: impl FnOnce(&mut ViewContext<V>) -> R,
+    ) -> R {
+        if let Some(stateful) = self.as_stateful_mut() {
+            cx.with_element_id(stateful.id.clone(), |global_id, cx| {
+                stateful.key_listeners.push((
+                    TypeId::of::<KeyDownEvent>(),
+                    Arc::new(move |_, key_down, context, phase, cx| {
+                        if phase == DispatchPhase::Bubble {
+                            let key_down = key_down.downcast_ref::<KeyDownEvent>().unwrap();
+                            if let KeyMatch::Some(action) =
+                                cx.match_keystroke(&global_id, &key_down.keystroke, context)
+                            {
+                                return Some(action);
+                            }
+                        }
+
+                        None
+                    }),
+                ));
+                let result = stateful.stateless.initialize(cx, f);
+                stateful.key_listeners.pop();
+                result
+            })
+        } else {
+            cx.with_key_listeners(&self.as_stateless().key_listeners, f)
+        }
+    }
+
+    fn refine_style(
+        &self,
+        style: &mut Style,
+        bounds: Bounds<Pixels>,
+        element_state: &InteractiveElementState,
+        cx: &mut ViewContext<V>,
+    ) {
+        let mouse_position = cx.mouse_position();
+        let stateless = self.as_stateless();
+        if let Some(group_hover) = stateless.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(&stateless.hover_style);
+        }
+
+        if let Some(stateful) = self.as_stateful() {
+            let active_state = element_state.active_state.lock();
+            if active_state.group {
+                if let Some(group_style) = stateful.group_active_style.as_ref() {
+                    style.refine(&group_style.style);
+                }
+            }
+            if active_state.element {
+                style.refine(&stateful.active_style);
+            }
+        }
+    }
+
+    fn paint(
+        &mut self,
+        bounds: Bounds<Pixels>,
+        element_state: &InteractiveElementState,
+        cx: &mut ViewContext<V>,
+    ) {
+        let stateless = self.as_stateless();
+        for listener in stateless.mouse_down_listeners.iter().cloned() {
+            cx.on_mouse_event(move |state, event: &MouseDownEvent, phase, cx| {
+                listener(state, event, &bounds, phase, cx);
+            })
+        }
+
+        for listener in stateless.mouse_up_listeners.iter().cloned() {
+            cx.on_mouse_event(move |state, event: &MouseUpEvent, phase, cx| {
+                listener(state, event, &bounds, phase, cx);
+            })
+        }
+
+        for listener in stateless.mouse_move_listeners.iter().cloned() {
+            cx.on_mouse_event(move |state, event: &MouseMoveEvent, phase, cx| {
+                listener(state, event, &bounds, phase, cx);
+            })
+        }
+
+        for listener in stateless.scroll_wheel_listeners.iter().cloned() {
+            cx.on_mouse_event(move |state, event: &ScrollWheelEvent, phase, cx| {
+                listener(state, event, &bounds, phase, cx);
+            })
+        }
+
+        let hover_group_bounds = stateless
+            .group_hover_style
+            .as_ref()
+            .and_then(|group_hover| GroupBounds::get(&group_hover.group, cx));
+
+        if let Some(group_bounds) = hover_group_bounds {
+            paint_hover_listener(group_bounds, cx);
+        }
+
+        if stateless.hover_style.is_some() {
+            paint_hover_listener(bounds, cx);
+        }
+
+        if let Some(stateful) = self.as_stateful() {
+            let click_listeners = stateful.mouse_click_listeners.clone();
+
+            let pending_click = element_state.pending_click.clone();
+            let mouse_down = pending_click.lock().clone();
+            if let Some(mouse_down) = mouse_down {
+                cx.on_mouse_event(move |state, event: &MouseUpEvent, phase, cx| {
+                    if phase == DispatchPhase::Bubble && bounds.contains_point(&event.position) {
+                        let mouse_click = MouseClickEvent {
+                            down: mouse_down.clone(),
+                            up: event.clone(),
+                        };
+                        for listener in &click_listeners {
+                            listener(state, &mouse_click, cx);
+                        }
+                    }
+
+                    *pending_click.lock() = None;
+                });
+            } else {
+                cx.on_mouse_event(move |_state, event: &MouseDownEvent, phase, _cx| {
+                    if phase == DispatchPhase::Bubble && bounds.contains_point(&event.position) {
+                        *pending_click.lock() = Some(event.clone());
+                    }
+                });
+            }
+
+            let active_state = element_state.active_state.clone();
+            if active_state.lock().is_none() {
+                let active_group_bounds = stateful
+                    .group_active_style
+                    .as_ref()
+                    .and_then(|group_active| GroupBounds::get(&group_active.group, cx));
+                cx.on_mouse_event(move |_view, down: &MouseDownEvent, phase, cx| {
+                    if phase == DispatchPhase::Bubble {
+                        let group = active_group_bounds
+                            .map_or(false, |bounds| bounds.contains_point(&down.position));
+                        let element = bounds.contains_point(&down.position);
+                        if group || element {
+                            *active_state.lock() = ActiveState { group, element };
+                            cx.notify();
+                        }
+                    }
+                });
+            } else {
+                cx.on_mouse_event(move |_, _: &MouseUpEvent, phase, cx| {
+                    if phase == DispatchPhase::Capture {
+                        *active_state.lock() = ActiveState::default();
+                        cx.notify();
+                    }
+                });
+            }
+        }
+    }
+}
+
+fn paint_hover_listener<V>(bounds: Bounds<Pixels>, cx: &mut ViewContext<V>)
+where
+    V: 'static + Send + Sync,
+{
+    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();
+            }
+        }
+    });
+}
+
+#[derive(Deref, DerefMut)]
+pub struct StatefulInteractivity<V: 'static + Send + Sync> {
+    pub id: ElementId,
+    #[deref]
+    #[deref_mut]
+    stateless: StatelessInteraction<V>,
+    pub mouse_click_listeners: SmallVec<[MouseClickListener<V>; 2]>,
+    pub active_style: StyleRefinement,
+    pub group_active_style: Option<GroupStyle>,
+}
+
+impl<V> ElementInteraction<V> for StatefulInteractivity<V>
+where
+    V: 'static + Send + Sync,
+{
+    fn as_stateful(&self) -> Option<&StatefulInteractivity<V>> {
+        Some(self)
+    }
+
+    fn as_stateful_mut(&mut self) -> Option<&mut StatefulInteractivity<V>> {
+        Some(self)
+    }
+
+    fn as_stateless(&self) -> &StatelessInteraction<V> {
+        &self.stateless
+    }
+
+    fn as_stateless_mut(&mut self) -> &mut StatelessInteraction<V> {
+        &mut self.stateless
+    }
+}
+
+impl<V> From<ElementId> for StatefulInteractivity<V>
+where
+    V: 'static + Send + Sync,
+{
+    fn from(id: ElementId) -> Self {
+        Self {
+            id,
+            stateless: StatelessInteraction::default(),
+            mouse_click_listeners: SmallVec::new(),
+            active_style: StyleRefinement::default(),
+            group_active_style: None,
+        }
+    }
+}
+
+pub struct StatelessInteraction<V> {
+    pub mouse_down_listeners: SmallVec<[MouseDownListener<V>; 2]>,
+    pub mouse_up_listeners: SmallVec<[MouseUpListener<V>; 2]>,
+    pub mouse_move_listeners: SmallVec<[MouseMoveListener<V>; 2]>,
+    pub scroll_wheel_listeners: SmallVec<[ScrollWheelListener<V>; 2]>,
+    pub key_listeners: SmallVec<[(TypeId, KeyListener<V>); 32]>,
+    pub hover_style: StyleRefinement,
+    pub group_hover_style: Option<GroupStyle>,
+}
+
+pub struct GroupStyle {
+    pub group: SharedString,
+    pub style: StyleRefinement,
+}
+
+#[derive(Default)]
+pub struct GroupBounds(HashMap<SharedString, SmallVec<[Bounds<Pixels>; 1]>>);
+
+impl GroupBounds {
+    pub fn get(name: &SharedString, cx: &mut AppContext) -> Option<Bounds<Pixels>> {
+        cx.default_global::<Self>()
+            .0
+            .get(name)
+            .and_then(|bounds_stack| bounds_stack.last())
+            .cloned()
+    }
+
+    pub fn push(name: SharedString, bounds: Bounds<Pixels>, cx: &mut AppContext) {
+        cx.default_global::<Self>()
+            .0
+            .entry(name)
+            .or_default()
+            .push(bounds);
+    }
+
+    pub fn pop(name: &SharedString, cx: &mut AppContext) {
+        cx.default_global::<GroupBounds>()
+            .0
+            .get_mut(name)
+            .unwrap()
+            .pop();
+    }
+}
+
+#[derive(Copy, Clone, Default, Eq, PartialEq)]
+struct ActiveState {
+    pub group: bool,
+    pub element: bool,
+}
+
+impl ActiveState {
+    pub fn is_none(&self) -> bool {
+        !self.group && !self.element
+    }
+}
+
+#[derive(Default)]
+pub struct InteractiveElementState {
+    active_state: Arc<Mutex<ActiveState>>,
+    pending_click: Arc<Mutex<Option<MouseDownEvent>>>,
+}
+
+impl<V> Default for StatelessInteraction<V> {
+    fn default() -> Self {
+        Self {
+            mouse_down_listeners: SmallVec::new(),
+            mouse_up_listeners: SmallVec::new(),
+            mouse_move_listeners: SmallVec::new(),
+            scroll_wheel_listeners: SmallVec::new(),
+            key_listeners: SmallVec::new(),
+            hover_style: StyleRefinement::default(),
+            group_hover_style: None,
+        }
+    }
+}
+
+impl<V> ElementInteraction<V> for StatelessInteraction<V>
+where
+    V: 'static + Send + Sync,
+{
+    fn as_stateful(&self) -> Option<&StatefulInteractivity<V>> {
+        None
+    }
+
+    fn as_stateful_mut(&mut self) -> Option<&mut StatefulInteractivity<V>> {
+        None
+    }
+
+    fn as_stateless(&self) -> &StatelessInteraction<V> {
+        self
+    }
+
+    fn as_stateless_mut(&mut self) -> &mut StatelessInteraction<V> {
+        self
+    }
+}
+
 #[derive(Clone, Debug, Eq, PartialEq)]
 pub struct KeyDownEvent {
     pub keystroke: Keystroke,