From 96f2c4a9de2f9673e30ac651a4203ac40b54e153 Mon Sep 17 00:00:00 2001 From: Nathan Sobo Date: Mon, 23 Oct 2023 14:15:12 +0200 Subject: [PATCH 1/7] Checkpoint --- crates/gpui2/src/app.rs | 18 ++- crates/gpui2/src/elements/div.rs | 12 +- crates/gpui2/src/elements/img.rs | 4 +- crates/gpui2/src/elements/svg.rs | 4 +- crates/gpui2/src/interactive.rs | 163 ++++++++++++++++++------ crates/gpui2/src/platform/mac/events.rs | 4 +- crates/gpui2/src/window.rs | 10 +- crates/ui2/src/components/tab.rs | 12 +- 8 files changed, 170 insertions(+), 57 deletions(-) diff --git a/crates/gpui2/src/app.rs b/crates/gpui2/src/app.rs index 1e4852ff4d462e967dc2bbd0b5aa373bdd962225..89abf277d3ca559dad685a212fac9bdc60ea2dee 100644 --- a/crates/gpui2/src/app.rs +++ b/crates/gpui2/src/app.rs @@ -9,11 +9,11 @@ use refineable::Refineable; use smallvec::SmallVec; use crate::{ - current_platform, image_cache::ImageCache, Action, AppMetadata, AssetSource, Context, - DispatchPhase, DisplayId, Executor, FocusEvent, FocusHandle, FocusId, KeyBinding, Keymap, - LayoutId, MainThread, MainThreadOnly, Platform, SharedString, SubscriberSet, Subscription, - SvgRenderer, Task, TextStyle, TextStyleRefinement, TextSystem, View, Window, WindowContext, - WindowHandle, WindowId, + current_platform, image_cache::ImageCache, Action, AnyBox, AnyView, AppMetadata, AssetSource, + Context, DispatchPhase, DisplayId, Executor, FocusEvent, FocusHandle, FocusId, KeyBinding, + Keymap, LayoutId, MainThread, MainThreadOnly, Platform, SharedString, SubscriberSet, + Subscription, SvgRenderer, Task, TextStyle, TextStyleRefinement, TextSystem, View, Window, + WindowContext, WindowHandle, WindowId, }; use anyhow::{anyhow, Result}; use collections::{HashMap, HashSet, VecDeque}; @@ -91,6 +91,7 @@ impl App { global_observers: SubscriberSet::new(), layout_id_buffer: Default::default(), propagate_event: true, + active_drag: None, }) })) } @@ -168,6 +169,7 @@ pub struct AppContext { text_system: Arc, flushing_effects: bool, pending_updates: usize, + pub(crate) active_drag: Option, pub(crate) next_frame_callbacks: HashMap>, pub(crate) executor: Executor, pub(crate) svg_renderer: SvgRenderer, @@ -732,6 +734,12 @@ pub(crate) enum Effect { }, } +pub(crate) struct AnyDrag { + pub drag_handle_view: AnyView, + pub state: AnyBox, + pub state_type: TypeId, +} + #[cfg(test)] mod tests { use super::AppContext; diff --git a/crates/gpui2/src/elements/div.rs b/crates/gpui2/src/elements/div.rs index e955309112071a863ff1386bba9857b48b6442ab..08551eaf70b0783da3586a4c4c35dab9d139c4ca 100644 --- a/crates/gpui2/src/elements/div.rs +++ b/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; @@ -364,7 +364,7 @@ where F: ElementFocus, V: 'static + Send + Sync, { - fn stateful_interactivity(&mut self) -> &mut StatefulInteraction { + fn stateful_interaction(&mut self) -> &mut StatefulInteraction { &mut self.interaction } } diff --git a/crates/gpui2/src/elements/img.rs b/crates/gpui2/src/elements/img.rs index 50e9e29fc58f2fb79ba6ca43d1be8cf752d8b0ae..7c50a68def7bdb3b07167757ca9025811f309ac5 100644 --- a/crates/gpui2/src/elements/img.rs +++ b/crates/gpui2/src/elements/img.rs @@ -166,8 +166,8 @@ where V: 'static + Send + Sync, F: ElementFocus, { - fn stateful_interactivity(&mut self) -> &mut StatefulInteraction { - self.base.stateful_interactivity() + fn stateful_interaction(&mut self) -> &mut StatefulInteraction { + self.base.stateful_interaction() } } diff --git a/crates/gpui2/src/elements/svg.rs b/crates/gpui2/src/elements/svg.rs index fd55296a3a6aff6ac221614685154c16273b949d..35e6e143a461eef1617d0b5892ad14c4e7bbe952 100644 --- a/crates/gpui2/src/elements/svg.rs +++ b/crates/gpui2/src/elements/svg.rs @@ -140,8 +140,8 @@ where V: 'static + Send + Sync, F: ElementFocus, { - fn stateful_interactivity(&mut self) -> &mut StatefulInteraction { - self.base.stateful_interactivity() + fn stateful_interaction(&mut self) -> &mut StatefulInteraction { + self.base.stateful_interaction() } } diff --git a/crates/gpui2/src/interactive.rs b/crates/gpui2/src/interactive.rs index 475cbb75a470427b5c33f3356c6f6f0cbb59364f..2e7c7649ff0f2ddd2ee8e6386150bcfc555b6be1 100644 --- a/crates/gpui2/src/interactive.rs +++ b/crates/gpui2/src/interactive.rs @@ -1,7 +1,7 @@ use crate::{ - point, px, Action, AppContext, BorrowWindow, Bounds, DispatchContext, DispatchPhase, Element, - ElementId, FocusHandle, KeyMatch, Keystroke, Modifiers, Overflow, Pixels, Point, SharedString, - Size, Style, StyleRefinement, ViewContext, + point, px, view, Action, AnyDrag, AppContext, BorrowWindow, Bounds, DispatchContext, + DispatchPhase, Element, ElementId, FocusHandle, KeyMatch, Keystroke, Modifiers, Overflow, + Pixels, Point, SharedString, Size, Style, StyleRefinement, ViewContext, }; use collections::HashMap; use derive_more::{Deref, DerefMut}; @@ -11,6 +11,7 @@ use smallvec::SmallVec; use std::{ any::{Any, TypeId}, fmt::Debug, + marker::PhantomData, ops::Deref, sync::Arc, }; @@ -257,13 +258,13 @@ pub trait StatelessInteractive: Element { } pub trait StatefulInteractive: StatelessInteractive { - fn stateful_interactivity(&mut self) -> &mut StatefulInteraction; + fn stateful_interaction(&mut self) -> &mut StatefulInteraction; fn active(mut self, f: impl FnOnce(StyleRefinement) -> StyleRefinement) -> Self where Self: Sized, { - self.stateful_interactivity().active_style = f(StyleRefinement::default()); + self.stateful_interaction().active_style = f(StyleRefinement::default()); self } @@ -275,7 +276,7 @@ pub trait StatefulInteractive: StatelessInteractive { where Self: Sized, { - self.stateful_interactivity().group_active_style = Some(GroupStyle { + self.stateful_interaction().group_active_style = Some(GroupStyle { group: group_name.into(), style: f(StyleRefinement::default()), }); @@ -284,7 +285,7 @@ pub trait StatefulInteractive: StatelessInteractive { fn on_click( mut self, - handler: impl Fn(&mut Self::ViewState, &MouseClickEvent, &mut ViewContext) + listener: impl Fn(&mut Self::ViewState, &ClickEvent, &mut ViewContext) + Send + Sync + 'static, @@ -292,9 +293,45 @@ pub trait StatefulInteractive: StatelessInteractive { where Self: Sized, { - self.stateful_interactivity() - .mouse_click_listeners - .push(Arc::new(move |view, event, cx| handler(view, event, cx))); + self.stateful_interaction() + .click_listeners + .push(Arc::new(move |view, event, cx| listener(view, event, cx))); + self + } + + fn on_drag( + mut self, + listener: impl Fn( + &mut Self::ViewState, + &mut ViewContext, + ) -> Drag + + Send + + Sync + + 'static, + ) -> Self + where + Self: Sized, + S: 'static + Send + Sync, + R: 'static + Fn(&mut Self::ViewState, &mut ViewContext) -> E + Send + Sync, + E: Element, + { + debug_assert!( + self.stateful_interaction().drag_listener.is_none(), + "calling on_drag more than once on the same element is not supported" + ); + self.stateful_interaction().drag_listener = Some(Arc::new(move |view_state, cx| { + let drag = listener(view_state, cx); + let view_handle = cx.handle().upgrade().unwrap(); + let drag_handle_view = view(view_handle, move |view_state, cx| { + (drag.render_drag_handle)(view_state, cx) + }) + .into_any(); + AnyDrag { + drag_handle_view, + state: Box::new(drag.state), + state_type: TypeId::of::(), + } + })); self } } @@ -419,30 +456,45 @@ pub trait ElementInteraction: 'static + Send + Sync { } if let Some(stateful) = self.as_stateful() { - let click_listeners = stateful.mouse_click_listeners.clone(); + let click_listeners = stateful.click_listeners.clone(); + let drag_listener = stateful.drag_listener.clone(); + + if !click_listeners.is_empty() || drag_listener.is_some() { + let pending_mouse_down = element_state.pending_mouse_down.clone(); + 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 { + let any_drag = drag_listener(view_state, cx); + cx.start_drag(any_drag); + } + }); + } - 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); + cx.on_mouse_event(move |view_state, event: &MouseUpEvent, phase, cx| { + if phase == DispatchPhase::Bubble && bounds.contains_point(&event.position) + { + let mouse_click = ClickEvent { + down: mouse_down.clone(), + up: event.clone(), + }; + for listener in &click_listeners { + listener(view_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()); - } - }); + cx.end_drag(); + *pending_mouse_down.lock() = None; + }); + } else { + cx.on_mouse_event(move |_state, event: &MouseDownEvent, phase, _cx| { + if phase == DispatchPhase::Bubble && bounds.contains_point(&event.position) + { + *pending_mouse_down.lock() = Some(event.clone()); + } + }); + } } let active_state = element_state.active_state.clone(); @@ -526,7 +578,8 @@ pub struct StatefulInteraction { #[deref] #[deref_mut] stateless: StatelessInteraction, - pub mouse_click_listeners: SmallVec<[MouseClickListener; 2]>, + pub click_listeners: SmallVec<[ClickListener; 2]>, + pub(crate) drag_listener: Option>, pub active_style: StyleRefinement, pub group_active_style: Option, } @@ -560,7 +613,8 @@ where Self { id, stateless: StatelessInteraction::default(), - mouse_click_listeners: SmallVec::new(), + click_listeners: SmallVec::new(), + drag_listener: None, active_style: StyleRefinement::default(), group_active_style: None, } @@ -586,7 +640,8 @@ where StatefulInteraction { id: id.into(), stateless: self, - mouse_click_listeners: SmallVec::new(), + click_listeners: SmallVec::new(), + drag_listener: None, active_style: StyleRefinement::default(), group_active_style: None, } @@ -642,7 +697,7 @@ impl ActiveState { #[derive(Default)] pub struct InteractiveElementState { active_state: Arc>, - pending_click: Arc>>, + pending_mouse_down: Arc>>, scroll_offset: Option>>>, } @@ -740,11 +795,39 @@ pub struct MouseUpEvent { } #[derive(Clone, Debug, Default)] -pub struct MouseClickEvent { +pub struct ClickEvent { pub down: MouseDownEvent, pub up: MouseUpEvent, } +pub struct Drag +where + S: 'static + Send + Sync, + R: Fn(&mut V, &mut ViewContext) -> E, + V: 'static + Send + Sync, + E: Element, +{ + pub state: S, + pub render_drag_handle: R, + view_type: PhantomData, +} + +impl Drag +where + S: 'static + Send + Sync, + R: Fn(&mut V, &mut ViewContext) -> E + Send + Sync, + V: 'static + Send + Sync, + E: Element, +{ + pub fn new(state: S, render_drag_handle: R) -> Self { + Drag { + state, + render_drag_handle, + view_type: PhantomData, + } + } +} + #[derive(Hash, PartialEq, Eq, Copy, Clone, Debug)] pub enum MouseButton { Left, @@ -919,8 +1002,6 @@ pub type MouseUpListener = Arc< + Sync + 'static, >; -pub type MouseClickListener = - Arc) + Send + Sync + 'static>; pub type MouseMoveListener = Arc< dyn Fn(&mut V, &MouseMoveEvent, &Bounds, DispatchPhase, &mut ViewContext) @@ -936,6 +1017,12 @@ pub type ScrollWheelListener = Arc< + 'static, >; +pub type ClickListener = + Arc) + Send + Sync + 'static>; + +pub(crate) type DragListener = + Arc) -> AnyDrag + Send + Sync + 'static>; + pub type KeyListener = Arc< dyn Fn( &mut V, diff --git a/crates/gpui2/src/platform/mac/events.rs b/crates/gpui2/src/platform/mac/events.rs index 916b9a18bebc3b1c4e07a98a9c045efbb2917c9b..c40c665481da950d0ee2f06a876ed4c04688f01d 100644 --- a/crates/gpui2/src/platform/mac/events.rs +++ b/crates/gpui2/src/platform/mac/events.rs @@ -201,7 +201,7 @@ impl InputEvent { _ => return None, }; - window_height.map(|window_height| { + dbg!(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 { diff --git a/crates/gpui2/src/window.rs b/crates/gpui2/src/window.rs index cfd800fbe1736e1edcab1ec408814d8cb11f11cf..966d25b7e2fd12ed6bb9804dec164c83bc0c72c0 100644 --- a/crates/gpui2/src/window.rs +++ b/crates/gpui2/src/window.rs @@ -1,5 +1,5 @@ use crate::{ - px, size, Action, AnyBox, AnyView, AppContext, AsyncWindowContext, AvailableSpace, + px, size, Action, AnyBox, AnyDrag, AnyView, AppContext, AsyncWindowContext, AvailableSpace, BorrowAppContext, Bounds, BoxShadow, Context, Corners, DevicePixels, DispatchContext, DisplayId, Edges, Effect, Element, EntityId, EventEmitter, FocusEvent, FontId, GlobalElementId, GlyphId, Handle, Hsla, ImageData, InputEvent, IsZero, KeyListener, KeyMatch, KeyMatcher, @@ -1605,6 +1605,14 @@ 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); + } + + pub(crate) fn end_drag(&mut self) { + self.app.active_drag = None; + } } impl<'a, 'w, V: EventEmitter + Send + Sync + 'static> ViewContext<'a, 'w, V> { diff --git a/crates/ui2/src/components/tab.rs b/crates/ui2/src/components/tab.rs index 2d787e6f025347f1498f629edce5dc4b4652e211..c404b7e9acce2872b6ec6ac472453521622bdd5b 100644 --- a/crates/ui2/src/components/tab.rs +++ b/crates/ui2/src/components/tab.rs @@ -17,6 +17,10 @@ pub struct Tab { close_side: IconSide, } +struct TabDragState { + title: String, +} + impl Tab { pub fn new(id: impl Into) -> Self { Self { @@ -113,6 +117,12 @@ impl Tab { div() .id(self.id.clone()) + // .on_drag(|_view, _cx| Drag { + // element: div().w_8().h_4().bg(black()), + // state: TabDragState { + // title: self.title.clone(), + // }, + // }) .px_2() .py_0p5() .flex() @@ -148,7 +158,7 @@ impl Tab { } } -use gpui2::ElementId; +use gpui2::{black, ElementId}; #[cfg(feature = "stories")] pub use stories::*; From d1adce589038060c83cab33d9aa29c90f9d57192 Mon Sep 17 00:00:00 2001 From: Nathan Sobo Date: Mon, 23 Oct 2023 15:09:22 +0200 Subject: [PATCH 2/7] Show red box when dragging --- 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(-) diff --git a/crates/gpui2/src/color.rs b/crates/gpui2/src/color.rs index d05caba1a6e9bf923c02680d2e634f64ec0f9b92..f6ea2549ec94db1379d19d8066d1f3ade8a3250f 100644 --- a/crates/gpui2/src/color.rs +++ b/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 { diff --git a/crates/gpui2/src/elements/div.rs b/crates/gpui2/src/elements/div.rs index 08551eaf70b0783da3586a4c4c35dab9d139c4ca..1eebbbe5dbb767096975b4dcb0e93fb3e1c4d1bc 100644 --- a/crates/gpui2/src/elements/div.rs +++ b/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); } diff --git a/crates/gpui2/src/geometry.rs b/crates/gpui2/src/geometry.rs index 08602352eea82ce31b6c154e87dbbc6b11585d46..923bb0bf9f68cf598da1982d1de0494b1e9afb73 100644 --- a/crates/gpui2/src/geometry.rs +++ b/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 { diff --git a/crates/gpui2/src/interactive.rs b/crates/gpui2/src/interactive.rs index 2e7c7649ff0f2ddd2ee8e6386150bcfc555b6be1..aeaa5b959af087610a0b4c55bf72323b6195c408 100644 --- a/crates/gpui2/src/interactive.rs +++ b/crates/gpui2/src/interactive.rs @@ -464,10 +464,17 @@ pub trait ElementInteraction: '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: 'static + Send + Sync { } } - cx.end_drag(); + if cx.active_drag.is_some() { + cx.end_drag(); + } *pending_mouse_down.lock() = None; }); } else { diff --git a/crates/gpui2/src/platform/mac/events.rs b/crates/gpui2/src/platform/mac/events.rs index c40c665481da950d0ee2f06a876ed4c04688f01d..916b9a18bebc3b1c4e07a98a9c045efbb2917c9b 100644 --- a/crates/gpui2/src/platform/mac/events.rs +++ b/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 { diff --git a/crates/gpui2/src/taffy.rs b/crates/gpui2/src/taffy.rs index 71844bb8a631bb0820ebbcadaf8e4131aef0eeb3..cee0f7592f0427490045efd8b93736282ca86fcf 100644 --- a/crates/gpui2/src/taffy.rs +++ b/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 { diff --git a/crates/gpui2/src/window.rs b/crates/gpui2/src/window.rs index 966d25b7e2fd12ed6bb9804dec164c83bc0c72c0..0211c8d9a17bb4f10b6b01a17bfafdb451d17270 100644 --- a/crates/gpui2/src/window.rs +++ b/crates/gpui2/src/window.rs @@ -159,7 +159,7 @@ pub struct Window { key_matchers: HashMap, z_index_stack: StackingOrder, content_mask_stack: Vec>, - scroll_offset_stack: Vec>, + element_offset_stack: Vec>, mouse_listeners: HashMap>, key_dispatch_stack: Vec, 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, @@ -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, + fn draw_any_view( + view: &mut AnyView, + available_space: Size, 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( + fn with_element_offset( &mut self, offset: Option>, 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 { + fn element_offset(&self) -> Point { 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( + &mut self, + element_id: Option, + f: impl FnOnce(Option, &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 { 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(); } } diff --git a/crates/ui2/src/components/tab.rs b/crates/ui2/src/components/tab.rs index c404b7e9acce2872b6ec6ac472453521622bdd5b..17157f506c911f2367b59dbec24d49251010b09d 100644 --- a/crates/ui2/src/components/tab.rs +++ b/crates/ui2/src/components/tab.rs @@ -17,6 +17,7 @@ pub struct Tab { close_side: IconSide, } +#[derive(Clone)] struct TabDragState { title: String, } @@ -115,14 +116,15 @@ impl Tab { ), }; + 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 Tab { } } -use gpui2::{black, ElementId}; +use gpui2::{red, Drag, ElementId}; #[cfg(feature = "stories")] pub use stories::*; From fc927f740664e8eec2232ca386d2a61613856384 Mon Sep 17 00:00:00 2001 From: Nathan Sobo Date: Mon, 23 Oct 2023 15:14:10 +0200 Subject: [PATCH 3/7] Checkpoint --- crates/gpui2/src/geometry.rs | 2 +- crates/gpui2/src/interactive.rs | 4 ++-- crates/gpui2/src/window.rs | 4 ++-- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/crates/gpui2/src/geometry.rs b/crates/gpui2/src/geometry.rs index 923bb0bf9f68cf598da1982d1de0494b1e9afb73..b472d77f574db56f13a4ebd0e655de9bd1f8b0fd 100644 --- a/crates/gpui2/src/geometry.rs +++ b/crates/gpui2/src/geometry.rs @@ -8,7 +8,7 @@ use std::{ }; #[derive( - Refineable, Default, Add, AddAssign, Sub, SubAssign, Copy, Debug, PartialEq, Eq, Hash, Neg, + Refineable, Default, Add, AddAssign, Sub, SubAssign, Copy, Debug, PartialEq, Eq, Hash, )] #[refineable(debug)] #[repr(C)] diff --git a/crates/gpui2/src/interactive.rs b/crates/gpui2/src/interactive.rs index aeaa5b959af087610a0b4c55bf72323b6195c408..194e6824ef97f39a96802736a0a68d23281344fc 100644 --- a/crates/gpui2/src/interactive.rs +++ b/crates/gpui2/src/interactive.rs @@ -548,12 +548,12 @@ pub trait ElementInteraction: 'static + Send + Sync { if overflow.x == Overflow::Scroll { scroll_offset.x = - (scroll_offset.x - delta.x).clamp(px(0.), scroll_max.width); + (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(px(0.), scroll_max.height); + (scroll_offset.y + delta.y).clamp(-scroll_max.height, px(0.)); } if *scroll_offset != old_scroll_offset { diff --git a/crates/gpui2/src/window.rs b/crates/gpui2/src/window.rs index 0211c8d9a17bb4f10b6b01a17bfafdb451d17270..33b6daeb224e3bf21393e1e64244e92be53bb418 100644 --- a/crates/gpui2/src/window.rs +++ b/crates/gpui2/src/window.rs @@ -469,7 +469,7 @@ impl<'a, 'w> WindowContext<'a, 'w> { .layout_engine .layout_bounds(layout_id) .map(Into::into); - bounds.origin -= self.element_offset(); + bounds.origin += self.element_offset(); bounds } @@ -812,7 +812,7 @@ impl<'a, 'w> WindowContext<'a, 'w> { if let Some(mut active_drag) = cx.active_drag.take() { cx.stack(1, |cx| { - let mouse_position = -cx.mouse_position(); + let mouse_position = cx.mouse_position(); cx.with_element_offset(Some(mouse_position), |cx| { let available_space = size(AvailableSpace::MinContent, AvailableSpace::MinContent); From 258fcaea94fbd7232ea6fdf99a368fa157b38871 Mon Sep 17 00:00:00 2001 From: Nathan Sobo Date: Mon, 23 Oct 2023 15:20:33 +0200 Subject: [PATCH 4/7] Position drag handle relative to cursor --- crates/gpui2/src/app.rs | 7 ++++--- crates/gpui2/src/interactive.rs | 33 ++++++++++++++++++--------------- crates/gpui2/src/window.rs | 4 ++-- 3 files changed, 24 insertions(+), 20 deletions(-) diff --git a/crates/gpui2/src/app.rs b/crates/gpui2/src/app.rs index 89abf277d3ca559dad685a212fac9bdc60ea2dee..0228dca57b05ba64c5ab98178c1f1503eedb7d13 100644 --- a/crates/gpui2/src/app.rs +++ b/crates/gpui2/src/app.rs @@ -11,9 +11,9 @@ use smallvec::SmallVec; use crate::{ current_platform, image_cache::ImageCache, Action, AnyBox, AnyView, AppMetadata, AssetSource, Context, DispatchPhase, DisplayId, Executor, FocusEvent, FocusHandle, FocusId, KeyBinding, - Keymap, LayoutId, MainThread, MainThreadOnly, Platform, SharedString, SubscriberSet, - Subscription, SvgRenderer, Task, TextStyle, TextStyleRefinement, TextSystem, View, Window, - WindowContext, WindowHandle, WindowId, + Keymap, LayoutId, MainThread, MainThreadOnly, Pixels, Platform, Point, SharedString, + SubscriberSet, Subscription, SvgRenderer, Task, TextStyle, TextStyleRefinement, TextSystem, + View, Window, WindowContext, WindowHandle, WindowId, }; use anyhow::{anyhow, Result}; use collections::{HashMap, HashSet, VecDeque}; @@ -736,6 +736,7 @@ pub(crate) enum Effect { pub(crate) struct AnyDrag { pub drag_handle_view: AnyView, + pub cursor_offset: Point, pub state: AnyBox, pub state_type: TypeId, } diff --git a/crates/gpui2/src/interactive.rs b/crates/gpui2/src/interactive.rs index 194e6824ef97f39a96802736a0a68d23281344fc..acc97810f14b9d2800cbac8206ed98e1cc8d2d0d 100644 --- a/crates/gpui2/src/interactive.rs +++ b/crates/gpui2/src/interactive.rs @@ -319,19 +319,21 @@ pub trait StatefulInteractive: StatelessInteractive { self.stateful_interaction().drag_listener.is_none(), "calling on_drag more than once on the same element is not supported" ); - self.stateful_interaction().drag_listener = Some(Arc::new(move |view_state, cx| { - let drag = listener(view_state, cx); - let view_handle = cx.handle().upgrade().unwrap(); - let drag_handle_view = view(view_handle, move |view_state, cx| { - (drag.render_drag_handle)(view_state, cx) - }) - .into_any(); - AnyDrag { - drag_handle_view, - state: Box::new(drag.state), - state_type: TypeId::of::(), - } - })); + self.stateful_interaction().drag_listener = + Some(Arc::new(move |view_state, cursor_offset, cx| { + let drag = listener(view_state, cx); + let view_handle = cx.handle().upgrade().unwrap(); + let drag_handle_view = view(view_handle, move |view_state, cx| { + (drag.render_drag_handle)(view_state, cx) + }) + .into_any(); + AnyDrag { + drag_handle_view, + cursor_offset, + state: Box::new(drag.state), + state_type: TypeId::of::(), + } + })); self } } @@ -472,7 +474,8 @@ pub trait ElementInteraction: 'static + Send + Sync { } else if phase == DispatchPhase::Bubble && bounds.contains_point(&event.position) { - let any_drag = drag_listener(view_state, cx); + let cursor_offset = event.position - bounds.origin; + let any_drag = drag_listener(view_state, cursor_offset, cx); cx.start_drag(any_drag); cx.stop_propagation(); } @@ -1030,7 +1033,7 @@ pub type ClickListener = Arc) + Send + Sync + 'static>; pub(crate) type DragListener = - Arc) -> AnyDrag + Send + Sync + 'static>; + Arc, &mut ViewContext) -> AnyDrag + Send + Sync + 'static>; pub type KeyListener = Arc< dyn Fn( diff --git a/crates/gpui2/src/window.rs b/crates/gpui2/src/window.rs index 33b6daeb224e3bf21393e1e64244e92be53bb418..c54103b2a0aad9b87a50686e73cc4df15ebe6efe 100644 --- a/crates/gpui2/src/window.rs +++ b/crates/gpui2/src/window.rs @@ -812,8 +812,8 @@ impl<'a, 'w> WindowContext<'a, 'w> { 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 offset = cx.mouse_position() - active_drag.cursor_offset; + cx.with_element_offset(Some(offset), |cx| { let available_space = size(AvailableSpace::MinContent, AvailableSpace::MinContent); draw_any_view(&mut active_drag.drag_handle_view, available_space, cx); From 239b0c2f71a6a7b5defa0ec94ed2a855c430beb6 Mon Sep 17 00:00:00 2001 From: Nathan Sobo Date: Mon, 23 Oct 2023 16:10:04 +0200 Subject: [PATCH 5/7] Clear active state when drag starts --- crates/gpui2/src/interactive.rs | 98 +++++++++++++++++++++++++------- crates/ui2/src/components/tab.rs | 3 +- 2 files changed, 79 insertions(+), 22 deletions(-) diff --git a/crates/gpui2/src/interactive.rs b/crates/gpui2/src/interactive.rs index acc97810f14b9d2800cbac8206ed98e1cc8d2d0d..0479d49f74865ffc925f847271b1cbefd3b7b025 100644 --- a/crates/gpui2/src/interactive.rs +++ b/crates/gpui2/src/interactive.rs @@ -336,6 +336,34 @@ pub trait StatefulInteractive: StatelessInteractive { })); self } + + fn drag_over(mut self, f: impl FnOnce(StyleRefinement) -> StyleRefinement) -> Self + where + Self: Sized, + { + self.stateful_interaction() + .drag_over_styles + .push((TypeId::of::(), f(StyleRefinement::default()))); + self + } + + fn group_drag_over( + mut self, + group_name: impl Into, + f: impl FnOnce(StyleRefinement) -> StyleRefinement, + ) -> Self + where + Self: Sized, + { + self.stateful_interaction().group_drag_over_styles.push(( + TypeId::of::(), + GroupStyle { + group: group_name.into(), + style: f(StyleRefinement::default()), + }, + )); + self + } } pub trait ElementInteraction: 'static + Send + Sync { @@ -398,6 +426,26 @@ pub trait ElementInteraction: 'static + Send + Sync { style.refine(&stateless.hover_style); } + if let Some(drag) = cx.active_drag.take() { + for (state_type, group_drag_style) in &self.as_stateless().group_drag_over_styles { + if let Some(group_bounds) = GroupBounds::get(&group_drag_style.group, cx) { + if *state_type == drag.state_type + && group_bounds.contains_point(&mouse_position) + { + style.refine(&group_drag_style.style); + } + } + } + + for (state_type, drag_over_style) in &self.as_stateless().drag_over_styles { + if *state_type == drag.state_type && bounds.contains_point(&mouse_position) { + style.refine(drag_over_style); + } + } + + cx.active_drag = Some(drag); + } + if let Some(stateful) = self.as_stateful() { let active_state = element_state.active_state.lock(); if active_state.group { @@ -450,11 +498,27 @@ pub trait ElementInteraction: 'static + Send + Sync { .and_then(|group_hover| GroupBounds::get(&group_hover.group, cx)); if let Some(group_bounds) = hover_group_bounds { - paint_hover_listener(group_bounds, cx); + 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 stateless.hover_style.is_some() { - paint_hover_listener(bounds, cx); + if stateless.hover_style.is_some() + || (cx.active_drag.is_some() && !stateless.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 let Some(stateful) = self.as_stateful() { @@ -466,6 +530,7 @@ pub trait ElementInteraction: '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 { + let active_state = element_state.active_state.clone(); cx.on_mouse_event(move |view_state, event: &MouseMoveEvent, phase, cx| { if cx.active_drag.is_some() { if phase == DispatchPhase::Capture { @@ -476,6 +541,7 @@ pub trait ElementInteraction: 'static + Send + Sync { { let cursor_offset = event.position - bounds.origin; let any_drag = drag_listener(view_state, cursor_offset, cx); + *active_state.lock() = ActiveState::default(); cx.start_drag(any_drag); cx.stop_propagation(); } @@ -570,30 +636,16 @@ pub trait ElementInteraction: 'static + Send + Sync { } } -fn paint_hover_listener(bounds: Bounds, cx: &mut ViewContext) -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 StatefulInteraction { pub id: ElementId, #[deref] #[deref_mut] stateless: StatelessInteraction, - pub click_listeners: SmallVec<[ClickListener; 2]>, - pub(crate) drag_listener: Option>, - pub active_style: StyleRefinement, - pub group_active_style: Option, + click_listeners: SmallVec<[ClickListener; 2]>, + active_style: StyleRefinement, + group_active_style: Option, + drag_listener: Option>, } impl ElementInteraction for StatefulInteraction @@ -642,6 +694,8 @@ pub struct StatelessInteraction { pub key_listeners: SmallVec<[(TypeId, KeyListener); 32]>, pub hover_style: StyleRefinement, pub group_hover_style: Option, + drag_over_styles: SmallVec<[(TypeId, StyleRefinement); 2]>, + group_drag_over_styles: SmallVec<[(TypeId, GroupStyle); 2]>, } impl StatelessInteraction @@ -732,6 +786,8 @@ impl Default for StatelessInteraction { key_listeners: SmallVec::new(), hover_style: StyleRefinement::default(), group_hover_style: None, + drag_over_styles: SmallVec::new(), + group_drag_over_styles: SmallVec::new(), } } } diff --git a/crates/ui2/src/components/tab.rs b/crates/ui2/src/components/tab.rs index 17157f506c911f2367b59dbec24d49251010b09d..1f37286f94e513edc239ba57e37fa580f503d4b4 100644 --- a/crates/ui2/src/components/tab.rs +++ b/crates/ui2/src/components/tab.rs @@ -125,6 +125,7 @@ impl Tab { .on_drag(move |_view, _cx| { Drag::new(drag_state.clone(), |view, cx| div().w_8().h_4().bg(red())) }) + .drag_over::(|d| d.bg(black())) .px_2() .py_0p5() .flex() @@ -160,7 +161,7 @@ impl Tab { } } -use gpui2::{red, Drag, ElementId}; +use gpui2::{black, red, Drag, ElementId}; #[cfg(feature = "stories")] pub use stories::*; From 38a7b39070b14afb59f88a7c46aa8fe43a942e29 Mon Sep 17 00:00:00 2001 From: Nathan Sobo Date: Mon, 23 Oct 2023 16:20:01 +0200 Subject: [PATCH 6/7] Don't start dragging until you move to 2px --- crates/gpui2/src/geometry.rs | 8 +++++--- crates/gpui2/src/interactive.rs | 5 +++++ 2 files changed, 10 insertions(+), 3 deletions(-) diff --git a/crates/gpui2/src/geometry.rs b/crates/gpui2/src/geometry.rs index b472d77f574db56f13a4ebd0e655de9bd1f8b0fd..95fa6c23535544c1a4433a266319690515e50823 100644 --- a/crates/gpui2/src/geometry.rs +++ b/crates/gpui2/src/geometry.rs @@ -7,9 +7,7 @@ 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)] #[refineable(debug)] #[repr(C)] pub struct Point { @@ -41,6 +39,10 @@ impl Point { y: self.y.scale(factor), } } + + pub fn magnitude(&self) -> f64 { + ((self.x.0.powi(2) + self.y.0.powi(2)) as f64).sqrt() + } } impl Mul for Point diff --git a/crates/gpui2/src/interactive.rs b/crates/gpui2/src/interactive.rs index 0479d49f74865ffc925f847271b1cbefd3b7b025..a078efff308994b8bf813c9a7f5b3d7a51169fb9 100644 --- a/crates/gpui2/src/interactive.rs +++ b/crates/gpui2/src/interactive.rs @@ -16,6 +16,8 @@ use std::{ sync::Arc, }; +const DRAG_THRESHOLD: f64 = 2.; + pub trait StatelessInteractive: Element { fn stateless_interactivity(&mut self) -> &mut StatelessInteraction; @@ -531,6 +533,7 @@ pub trait ElementInteraction: 'static + Send + Sync { if let Some(mouse_down) = mouse_down { if let Some(drag_listener) = drag_listener { let active_state = element_state.active_state.clone(); + cx.on_mouse_event(move |view_state, event: &MouseMoveEvent, phase, cx| { if cx.active_drag.is_some() { if phase == DispatchPhase::Capture { @@ -538,6 +541,8 @@ pub trait ElementInteraction: 'static + Send + Sync { } } else if phase == DispatchPhase::Bubble && bounds.contains_point(&event.position) + && (event.position - mouse_down.position).magnitude() + > DRAG_THRESHOLD { let cursor_offset = event.position - bounds.origin; let any_drag = drag_listener(view_state, cursor_offset, cx); From ec0b2e543044dc9429ed0ba8e415ec5321d64284 Mon Sep 17 00:00:00 2001 From: Nathan Sobo Date: Mon, 23 Oct 2023 16:59:16 +0200 Subject: [PATCH 7/7] Add on_drop listeners --- crates/gpui2/src/elements/div.rs | 2 +- crates/gpui2/src/elements/img.rs | 4 +- crates/gpui2/src/elements/svg.rs | 4 +- crates/gpui2/src/interactive.rs | 141 ++++++++++++++++++++----------- crates/gpui2/src/window.rs | 26 +++--- crates/ui2/src/components/tab.rs | 5 +- 6 files changed, 112 insertions(+), 70 deletions(-) diff --git a/crates/gpui2/src/elements/div.rs b/crates/gpui2/src/elements/div.rs index 1eebbbe5dbb767096975b4dcb0e93fb3e1c4d1bc..2753d5f8fb290453ea4b8741b74249d6f2e0c5de 100644 --- a/crates/gpui2/src/elements/div.rs +++ b/crates/gpui2/src/elements/div.rs @@ -354,7 +354,7 @@ where F: ElementFocus, V: 'static + Send + Sync, { - fn stateless_interactivity(&mut self) -> &mut StatelessInteraction { + fn stateless_interaction(&mut self) -> &mut StatelessInteraction { self.interaction.as_stateless_mut() } } diff --git a/crates/gpui2/src/elements/img.rs b/crates/gpui2/src/elements/img.rs index 7c50a68def7bdb3b07167757ca9025811f309ac5..713bc8b98dbf667eb75957a382a07d1e0961a634 100644 --- a/crates/gpui2/src/elements/img.rs +++ b/crates/gpui2/src/elements/img.rs @@ -156,8 +156,8 @@ where I: ElementInteraction, F: ElementFocus, { - fn stateless_interactivity(&mut self) -> &mut StatelessInteraction { - self.base.stateless_interactivity() + fn stateless_interaction(&mut self) -> &mut StatelessInteraction { + self.base.stateless_interaction() } } diff --git a/crates/gpui2/src/elements/svg.rs b/crates/gpui2/src/elements/svg.rs index 35e6e143a461eef1617d0b5892ad14c4e7bbe952..3785ce480e16a2979addd3604ecc1ec66403b8d5 100644 --- a/crates/gpui2/src/elements/svg.rs +++ b/crates/gpui2/src/elements/svg.rs @@ -130,8 +130,8 @@ where I: ElementInteraction, F: ElementFocus, { - fn stateless_interactivity(&mut self) -> &mut StatelessInteraction { - self.base.stateless_interactivity() + fn stateless_interaction(&mut self) -> &mut StatelessInteraction { + self.base.stateless_interaction() } } diff --git a/crates/gpui2/src/interactive.rs b/crates/gpui2/src/interactive.rs index a078efff308994b8bf813c9a7f5b3d7a51169fb9..aa8cbf4826e9f9218e3fe6ba814e477a70e8d65e 100644 --- a/crates/gpui2/src/interactive.rs +++ b/crates/gpui2/src/interactive.rs @@ -1,5 +1,5 @@ use crate::{ - point, px, view, Action, AnyDrag, AppContext, BorrowWindow, Bounds, DispatchContext, + point, px, view, Action, AnyBox, AnyDrag, AppContext, BorrowWindow, Bounds, DispatchContext, DispatchPhase, Element, ElementId, FocusHandle, KeyMatch, Keystroke, Modifiers, Overflow, Pixels, Point, SharedString, Size, Style, StyleRefinement, ViewContext, }; @@ -19,13 +19,13 @@ use std::{ const DRAG_THRESHOLD: f64 = 2.; pub trait StatelessInteractive: Element { - fn stateless_interactivity(&mut self) -> &mut StatelessInteraction; + fn stateless_interaction(&mut self) -> &mut StatelessInteraction; fn hover(mut self, f: impl FnOnce(StyleRefinement) -> StyleRefinement) -> Self where Self: Sized, { - self.stateless_interactivity().hover_style = f(StyleRefinement::default()); + self.stateless_interaction().hover_style = f(StyleRefinement::default()); self } @@ -37,7 +37,7 @@ pub trait StatelessInteractive: Element { where Self: Sized, { - self.stateless_interactivity().group_hover_style = Some(GroupStyle { + self.stateless_interaction().group_hover_style = Some(GroupStyle { group: group_name.into(), style: f(StyleRefinement::default()), }); @@ -55,7 +55,7 @@ pub trait StatelessInteractive: Element { where Self: Sized, { - self.stateless_interactivity() + self.stateless_interaction() .mouse_down_listeners .push(Arc::new(move |view, event, bounds, phase, cx| { if phase == DispatchPhase::Bubble @@ -79,7 +79,7 @@ pub trait StatelessInteractive: Element { where Self: Sized, { - self.stateless_interactivity() + self.stateless_interaction() .mouse_up_listeners .push(Arc::new(move |view, event, bounds, phase, cx| { if phase == DispatchPhase::Bubble @@ -103,7 +103,7 @@ pub trait StatelessInteractive: Element { where Self: Sized, { - self.stateless_interactivity() + self.stateless_interaction() .mouse_down_listeners .push(Arc::new(move |view, event, bounds, phase, cx| { if phase == DispatchPhase::Capture @@ -127,7 +127,7 @@ pub trait StatelessInteractive: Element { where Self: Sized, { - self.stateless_interactivity() + self.stateless_interaction() .mouse_up_listeners .push(Arc::new(move |view, event, bounds, phase, cx| { if phase == DispatchPhase::Capture @@ -150,7 +150,7 @@ pub trait StatelessInteractive: Element { where Self: Sized, { - self.stateless_interactivity() + self.stateless_interaction() .mouse_move_listeners .push(Arc::new(move |view, event, bounds, phase, cx| { if phase == DispatchPhase::Bubble && bounds.contains_point(&event.position) { @@ -170,7 +170,7 @@ pub trait StatelessInteractive: Element { where Self: Sized, { - self.stateless_interactivity() + self.stateless_interaction() .scroll_wheel_listeners .push(Arc::new(move |view, event, bounds, phase, cx| { if phase == DispatchPhase::Bubble && bounds.contains_point(&event.position) { @@ -186,7 +186,7 @@ pub trait StatelessInteractive: Element { C: TryInto, C::Error: Debug, { - self.stateless_interactivity().dispatch_context = + self.stateless_interaction().dispatch_context = context.try_into().expect("invalid dispatch context"); self } @@ -201,7 +201,7 @@ pub trait StatelessInteractive: Element { where Self: Sized, { - self.stateless_interactivity().key_listeners.push(( + self.stateless_interaction().key_listeners.push(( TypeId::of::(), Arc::new(move |view, event, _, phase, cx| { let event = event.downcast_ref().unwrap(); @@ -226,7 +226,7 @@ pub trait StatelessInteractive: Element { where Self: Sized, { - self.stateless_interactivity().key_listeners.push(( + self.stateless_interaction().key_listeners.push(( TypeId::of::(), Arc::new(move |view, event, _, phase, cx| { let event = event.downcast_ref().unwrap(); @@ -247,7 +247,7 @@ pub trait StatelessInteractive: Element { where Self: Sized, { - self.stateless_interactivity().key_listeners.push(( + self.stateless_interaction().key_listeners.push(( TypeId::of::(), Arc::new(move |view, event, _, phase, cx| { let event = event.downcast_ref().unwrap(); @@ -257,6 +257,53 @@ pub trait StatelessInteractive: Element { )); self } + + fn drag_over(mut self, f: impl FnOnce(StyleRefinement) -> StyleRefinement) -> Self + where + Self: Sized, + { + self.stateless_interaction() + .drag_over_styles + .push((TypeId::of::(), f(StyleRefinement::default()))); + self + } + + fn group_drag_over( + mut self, + group_name: impl Into, + f: impl FnOnce(StyleRefinement) -> StyleRefinement, + ) -> Self + where + Self: Sized, + { + self.stateless_interaction().group_drag_over_styles.push(( + TypeId::of::(), + GroupStyle { + group: group_name.into(), + style: f(StyleRefinement::default()), + }, + )); + self + } + + fn on_drop( + mut self, + listener: impl Fn(&mut Self::ViewState, S, &mut ViewContext) + + Send + + Sync + + 'static, + ) -> Self + where + Self: Sized, + { + self.stateless_interaction().drop_listeners.push(( + TypeId::of::(), + Arc::new(move |view, drag_state, cx| { + listener(view, *drag_state.downcast().unwrap(), cx); + }), + )); + self + } } pub trait StatefulInteractive: StatelessInteractive { @@ -338,34 +385,6 @@ pub trait StatefulInteractive: StatelessInteractive { })); self } - - fn drag_over(mut self, f: impl FnOnce(StyleRefinement) -> StyleRefinement) -> Self - where - Self: Sized, - { - self.stateful_interaction() - .drag_over_styles - .push((TypeId::of::(), f(StyleRefinement::default()))); - self - } - - fn group_drag_over( - mut self, - group_name: impl Into, - f: impl FnOnce(StyleRefinement) -> StyleRefinement, - ) -> Self - where - Self: Sized, - { - self.stateful_interaction().group_drag_over_styles.push(( - TypeId::of::(), - GroupStyle { - group: group_name.into(), - style: f(StyleRefinement::default()), - }, - )); - self - } } pub trait ElementInteraction: 'static + Send + Sync { @@ -523,6 +542,29 @@ pub trait ElementInteraction: 'static + Send + Sync { }); } + if cx.active_drag.is_some() { + let drop_listeners = stateless.drop_listeners.clone(); + 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.state_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.state, cx); + cx.notify(); + cx.stop_propagation(); + } + } + } + } + }); + } + if let Some(stateful) = self.as_stateful() { let click_listeners = stateful.click_listeners.clone(); let drag_listener = stateful.drag_listener.clone(); @@ -544,10 +586,11 @@ pub trait ElementInteraction: 'static + Send + Sync { && (event.position - mouse_down.position).magnitude() > DRAG_THRESHOLD { - let cursor_offset = event.position - bounds.origin; - let any_drag = drag_listener(view_state, cursor_offset, cx); *active_state.lock() = ActiveState::default(); - cx.start_drag(any_drag); + let cursor_offset = event.position - bounds.origin; + let drag = drag_listener(view_state, cursor_offset, cx); + cx.active_drag = Some(drag); + cx.notify(); cx.stop_propagation(); } }); @@ -564,10 +607,6 @@ pub trait ElementInteraction: 'static + Send + Sync { listener(view_state, &mouse_click, cx); } } - - if cx.active_drag.is_some() { - cx.end_drag(); - } *pending_mouse_down.lock() = None; }); } else { @@ -690,6 +729,8 @@ where } } +type DropListener = dyn Fn(&mut V, AnyBox, &mut ViewContext) + Send + Sync; + pub struct StatelessInteraction { pub dispatch_context: DispatchContext, pub mouse_down_listeners: SmallVec<[MouseDownListener; 2]>, @@ -701,6 +742,7 @@ pub struct StatelessInteraction { pub group_hover_style: Option, drag_over_styles: SmallVec<[(TypeId, StyleRefinement); 2]>, group_drag_over_styles: SmallVec<[(TypeId, GroupStyle); 2]>, + drop_listeners: SmallVec<[(TypeId, Arc>); 2]>, } impl StatelessInteraction @@ -793,6 +835,7 @@ impl Default for StatelessInteraction { group_hover_style: None, drag_over_styles: SmallVec::new(), group_drag_over_styles: SmallVec::new(), + drop_listeners: SmallVec::new(), } } } diff --git a/crates/gpui2/src/window.rs b/crates/gpui2/src/window.rs index c54103b2a0aad9b87a50686e73cc4df15ebe6efe..a92530c6e72afc1e4d1e5cd32dfe5ca72746710d 100644 --- a/crates/gpui2/src/window.rs +++ b/crates/gpui2/src/window.rs @@ -3,11 +3,11 @@ use crate::{ BorrowAppContext, Bounds, BoxShadow, Context, Corners, DevicePixels, DispatchContext, DisplayId, Edges, Effect, Element, EntityId, EventEmitter, FocusEvent, FontId, GlobalElementId, GlyphId, Handle, Hsla, ImageData, InputEvent, IsZero, KeyListener, KeyMatch, KeyMatcher, - Keystroke, LayoutId, MainThread, MainThreadOnly, MonochromeSprite, MouseMoveEvent, Path, - Pixels, Platform, PlatformAtlas, PlatformWindow, Point, PolychromeSprite, Quad, Reference, - RenderGlyphParams, RenderImageParams, RenderSvgParams, ScaledPixels, SceneBuilder, Shadow, - SharedString, Size, Style, Subscription, TaffyLayoutEngine, Task, Underline, UnderlineStyle, - WeakHandle, WindowOptions, SUBPIXEL_VARIANTS, + Keystroke, LayoutId, MainThread, MainThreadOnly, MonochromeSprite, MouseMoveEvent, + MouseUpEvent, Path, Pixels, Platform, PlatformAtlas, PlatformWindow, Point, PolychromeSprite, + Quad, Reference, RenderGlyphParams, RenderImageParams, RenderSvgParams, ScaledPixels, + SceneBuilder, Shadow, SharedString, Size, Style, Subscription, TaffyLayoutEngine, Task, + Underline, UnderlineStyle, WeakHandle, WindowOptions, SUBPIXEL_VARIANTS, }; use anyhow::Result; use collections::HashMap; @@ -925,6 +925,12 @@ impl<'a, 'w> WindowContext<'a, 'w> { } } + if self.app.propagate_event + && any_mouse_event.downcast_ref::().is_some() + { + self.active_drag = None; + } + // Just in case any handlers added new handlers, which is weird, but possible. handlers.extend( self.window @@ -1626,16 +1632,6 @@ 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(); - } } impl<'a, 'w, V: EventEmitter + Send + Sync + 'static> ViewContext<'a, 'w, V> { diff --git a/crates/ui2/src/components/tab.rs b/crates/ui2/src/components/tab.rs index 1f37286f94e513edc239ba57e37fa580f503d4b4..c13d4e5637cd4254caf26f57f38dc3de2a371e5b 100644 --- a/crates/ui2/src/components/tab.rs +++ b/crates/ui2/src/components/tab.rs @@ -17,7 +17,7 @@ pub struct Tab { close_side: IconSide, } -#[derive(Clone)] +#[derive(Clone, Debug)] struct TabDragState { title: String, } @@ -126,6 +126,9 @@ impl Tab { Drag::new(drag_state.clone(), |view, cx| div().w_8().h_4().bg(red())) }) .drag_over::(|d| d.bg(black())) + .on_drop(|_view, state: TabDragState, cx| { + dbg!(state); + }) .px_2() .py_0p5() .flex()