Show red box when dragging

Nathan Sobo created

Change summary

crates/gpui2/src/color.rs               |  9 ++
crates/gpui2/src/elements/div.rs        | 12 +-
crates/gpui2/src/geometry.rs            |  4 
crates/gpui2/src/interactive.rs         | 15 +++
crates/gpui2/src/platform/mac/events.rs |  4 
crates/gpui2/src/taffy.rs               |  2 
crates/gpui2/src/window.rs              | 83 +++++++++++++++++---------
crates/ui2/src/components/tab.rs        | 16 ++--
8 files changed, 95 insertions(+), 50 deletions(-)

Detailed changes

crates/gpui2/src/color.rs 🔗

@@ -155,6 +155,15 @@ pub fn white() -> Hsla {
     }
 }
 
+pub fn red() -> Hsla {
+    Hsla {
+        h: 0.,
+        s: 1.,
+        l: 0.5,
+        a: 1.,
+    }
+}
+
 impl Hsla {
     /// Returns true if the HSLA color is fully transparent, false otherwise.
     pub fn is_transparent(&self) -> bool {

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

@@ -1,9 +1,9 @@
 use crate::{
-    point, AnyElement, BorrowWindow, Bounds, Element, ElementFocus, ElementId,
-    ElementInteraction, FocusDisabled, FocusEnabled, FocusHandle, FocusListeners, Focusable,
-    GlobalElementId, GroupBounds, InteractiveElementState, IntoAnyElement, LayoutId, Overflow,
-    ParentElement, Pixels, Point, SharedString, StatefulInteraction, StatefulInteractive,
-    StatelessInteraction, StatelessInteractive, Style, StyleRefinement, Styled, ViewContext,
+    point, AnyElement, BorrowWindow, Bounds, Element, ElementFocus, ElementId, ElementInteraction,
+    FocusDisabled, FocusEnabled, FocusHandle, FocusListeners, Focusable, GlobalElementId,
+    GroupBounds, InteractiveElementState, IntoAnyElement, LayoutId, Overflow, ParentElement,
+    Pixels, Point, SharedString, StatefulInteraction, StatefulInteractive, StatelessInteraction,
+    StatelessInteractive, Style, StyleRefinement, Styled, ViewContext,
 };
 use refineable::Refineable;
 use smallvec::SmallVec;
@@ -298,7 +298,7 @@ where
                     style.apply_text_style(cx, |cx| {
                         style.apply_overflow(bounds, cx, |cx| {
                             let scroll_offset = element_state.interactive.scroll_offset();
-                            cx.with_scroll_offset(scroll_offset, |cx| {
+                            cx.with_element_offset(scroll_offset, |cx| {
                                 for child in &mut this.children {
                                     child.paint(view_state, cx);
                                 }

crates/gpui2/src/geometry.rs 🔗

@@ -7,7 +7,9 @@ use std::{
     ops::{Add, Div, Mul, MulAssign, Sub},
 };
 
-#[derive(Refineable, Default, Add, AddAssign, Sub, SubAssign, Copy, Debug, PartialEq, Eq, Hash)]
+#[derive(
+    Refineable, Default, Add, AddAssign, Sub, SubAssign, Copy, Debug, PartialEq, Eq, Hash, Neg,
+)]
 #[refineable(debug)]
 #[repr(C)]
 pub struct Point<T: Default + Clone + Debug> {

crates/gpui2/src/interactive.rs 🔗

@@ -464,10 +464,17 @@ pub trait ElementInteraction<V: 'static + Send + Sync>: 'static + Send + Sync {
                 let mouse_down = pending_mouse_down.lock().clone();
                 if let Some(mouse_down) = mouse_down {
                     if let Some(drag_listener) = drag_listener {
-                        cx.on_mouse_event(move |view_state, _: &MouseMoveEvent, phase, cx| {
-                            if phase == DispatchPhase::Bubble {
+                        cx.on_mouse_event(move |view_state, event: &MouseMoveEvent, phase, cx| {
+                            if cx.active_drag.is_some() {
+                                if phase == DispatchPhase::Capture {
+                                    cx.notify();
+                                }
+                            } else if phase == DispatchPhase::Bubble
+                                && bounds.contains_point(&event.position)
+                            {
                                 let any_drag = drag_listener(view_state, cx);
                                 cx.start_drag(any_drag);
+                                cx.stop_propagation();
                             }
                         });
                     }
@@ -484,7 +491,9 @@ pub trait ElementInteraction<V: 'static + Send + Sync>: 'static + Send + Sync {
                             }
                         }
 
-                        cx.end_drag();
+                        if cx.active_drag.is_some() {
+                            cx.end_drag();
+                        }
                         *pending_mouse_down.lock() = None;
                     });
                 } else {

crates/gpui2/src/platform/mac/events.rs 🔗

@@ -201,7 +201,7 @@ impl InputEvent {
                     _ => return None,
                 };
 
-                dbg!(window_height.map(|window_height| {
+                window_height.map(|window_height| {
                     Self::MouseMoved(MouseMoveEvent {
                         pressed_button: Some(pressed_button),
                         position: point(
@@ -210,7 +210,7 @@ impl InputEvent {
                         ),
                         modifiers: read_modifiers(native_event),
                     })
-                }))
+                })
             }
             NSEventType::NSMouseMoved => window_height.map(|window_height| {
                 Self::MouseMoved(MouseMoveEvent {

crates/gpui2/src/taffy.rs 🔗

@@ -129,7 +129,7 @@ impl TaffyLayoutEngine {
         self.taffy
             .compute_layout(id.into(), available_space.into())
             .expect(EXPECT_MESSAGE);
-        println!("compute_layout took {:?}", started_at.elapsed());
+        // println!("compute_layout took {:?}", started_at.elapsed());
     }
 
     pub fn layout_bounds(&mut self, id: LayoutId) -> Bounds<Pixels> {

crates/gpui2/src/window.rs 🔗

@@ -159,7 +159,7 @@ pub struct Window {
     key_matchers: HashMap<GlobalElementId, KeyMatcher>,
     z_index_stack: StackingOrder,
     content_mask_stack: Vec<ContentMask<Pixels>>,
-    scroll_offset_stack: Vec<Point<Pixels>>,
+    element_offset_stack: Vec<Point<Pixels>>,
     mouse_listeners: HashMap<TypeId, Vec<(StackingOrder, AnyListener)>>,
     key_dispatch_stack: Vec<KeyDispatchStackFrame>,
     freeze_key_dispatch_stack: bool,
@@ -177,7 +177,7 @@ pub struct Window {
 }
 
 impl Window {
-    pub fn new(
+    pub(crate) fn new(
         handle: AnyWindowHandle,
         options: WindowOptions,
         cx: &mut MainThread<AppContext>,
@@ -234,7 +234,7 @@ impl Window {
             key_matchers: HashMap::default(),
             z_index_stack: StackingOrder(SmallVec::new()),
             content_mask_stack: Vec::new(),
-            scroll_offset_stack: Vec::new(),
+            element_offset_stack: Vec::new(),
             mouse_listeners: HashMap::default(),
             key_dispatch_stack: Vec::new(),
             freeze_key_dispatch_stack: false,
@@ -469,7 +469,7 @@ impl<'a, 'w> WindowContext<'a, 'w> {
             .layout_engine
             .layout_bounds(layout_id)
             .map(Into::into);
-        bounds.origin -= self.scroll_offset();
+        bounds.origin -= self.element_offset();
         bounds
     }
 
@@ -805,14 +805,22 @@ impl<'a, 'w> WindowContext<'a, 'w> {
 
             let mut root_view = cx.window.root_view.take().unwrap();
 
-            if let Some(element_id) = root_view.id() {
-                cx.with_element_state(element_id, |element_state, cx| {
-                    let element_state = draw_with_element_state(&mut root_view, element_state, cx);
-                    ((), element_state)
+            cx.stack(0, |cx| {
+                let available_space = cx.window.content_size.map(Into::into);
+                draw_any_view(&mut root_view, available_space, cx);
+            });
+
+            if let Some(mut active_drag) = cx.active_drag.take() {
+                cx.stack(1, |cx| {
+                    let mouse_position = -cx.mouse_position();
+                    cx.with_element_offset(Some(mouse_position), |cx| {
+                        let available_space =
+                            size(AvailableSpace::MinContent, AvailableSpace::MinContent);
+                        draw_any_view(&mut active_drag.drag_handle_view, available_space, cx);
+                        cx.active_drag = Some(active_drag);
+                    });
                 });
-            } else {
-                draw_with_element_state(&mut root_view, None, cx);
-            };
+            }
 
             cx.window.root_view = Some(root_view);
             let scene = cx.window.scene_builder.build();
@@ -827,20 +835,21 @@ impl<'a, 'w> WindowContext<'a, 'w> {
             .detach();
         });
 
-        fn draw_with_element_state(
-            root_view: &mut AnyView,
-            element_state: Option<AnyBox>,
+        fn draw_any_view(
+            view: &mut AnyView,
+            available_space: Size<AvailableSpace>,
             cx: &mut ViewContext<()>,
-        ) -> AnyBox {
-            let mut element_state = root_view.initialize(&mut (), element_state, cx);
-            let layout_id = root_view.layout(&mut (), &mut element_state, cx);
-            let available_space = cx.window.content_size.map(Into::into);
-            cx.window
-                .layout_engine
-                .compute_layout(layout_id, available_space);
-            let bounds = cx.window.layout_engine.layout_bounds(layout_id);
-            root_view.paint(bounds, &mut (), &mut element_state, cx);
-            element_state
+        ) {
+            cx.with_optional_element_state(view.id(), |element_state, cx| {
+                let mut element_state = view.initialize(&mut (), element_state, cx);
+                let layout_id = view.layout(&mut (), &mut element_state, cx);
+                cx.window
+                    .layout_engine
+                    .compute_layout(layout_id, available_space);
+                let bounds = cx.window.layout_engine.layout_bounds(layout_id);
+                view.paint(bounds, &mut (), &mut element_state, cx);
+                ((), element_state)
+            });
         }
     }
 
@@ -1209,7 +1218,7 @@ pub trait BorrowWindow: BorrowAppContext {
         result
     }
 
-    fn with_scroll_offset<R>(
+    fn with_element_offset<R>(
         &mut self,
         offset: Option<Point<Pixels>>,
         f: impl FnOnce(&mut Self) -> R,
@@ -1218,16 +1227,16 @@ pub trait BorrowWindow: BorrowAppContext {
             return f(self);
         };
 
-        let offset = self.scroll_offset() + offset;
-        self.window_mut().scroll_offset_stack.push(offset);
+        let offset = self.element_offset() + offset;
+        self.window_mut().element_offset_stack.push(offset);
         let result = f(self);
-        self.window_mut().scroll_offset_stack.pop();
+        self.window_mut().element_offset_stack.pop();
         result
     }
 
-    fn scroll_offset(&self) -> Point<Pixels> {
+    fn element_offset(&self) -> Point<Pixels> {
         self.window()
-            .scroll_offset_stack
+            .element_offset_stack
             .last()
             .copied()
             .unwrap_or_default()
@@ -1266,6 +1275,18 @@ pub trait BorrowWindow: BorrowAppContext {
         })
     }
 
+    fn with_optional_element_state<S: 'static + Send + Sync, R>(
+        &mut self,
+        element_id: Option<ElementId>,
+        f: impl FnOnce(Option<S>, &mut Self) -> (R, S),
+    ) -> R {
+        if let Some(element_id) = element_id {
+            self.with_element_state(element_id, f)
+        } else {
+            f(None, self).0
+        }
+    }
+
     fn content_mask(&self) -> ContentMask<Pixels> {
         self.window()
             .content_mask_stack
@@ -1608,10 +1629,12 @@ impl<'a, 'w, V: Send + Sync + 'static> ViewContext<'a, 'w, V> {
 
     pub(crate) fn start_drag(&mut self, drag: AnyDrag) {
         self.app.active_drag = Some(drag);
+        self.notify();
     }
 
     pub(crate) fn end_drag(&mut self) {
         self.app.active_drag = None;
+        self.notify();
     }
 }
 

crates/ui2/src/components/tab.rs 🔗

@@ -17,6 +17,7 @@ pub struct Tab<S: 'static + Send + Sync + Clone> {
     close_side: IconSide,
 }
 
+#[derive(Clone)]
 struct TabDragState {
     title: String,
 }
@@ -115,14 +116,15 @@ impl<S: 'static + Send + Sync + Clone> Tab<S> {
             ),
         };
 
+        let drag_state = TabDragState {
+            title: self.title.clone(),
+        };
+
         div()
             .id(self.id.clone())
-            // .on_drag(|_view, _cx| Drag {
-            //     element: div().w_8().h_4().bg(black()),
-            //     state: TabDragState {
-            //         title: self.title.clone(),
-            //     },
-            // })
+            .on_drag(move |_view, _cx| {
+                Drag::new(drag_state.clone(), |view, cx| div().w_8().h_4().bg(red()))
+            })
             .px_2()
             .py_0p5()
             .flex()
@@ -158,7 +160,7 @@ impl<S: 'static + Send + Sync + Clone> Tab<S> {
     }
 }
 
-use gpui2::{black, ElementId};
+use gpui2::{red, Drag, ElementId};
 #[cfg(feature = "stories")]
 pub use stories::*;