diff --git a/crates/gpui2/src/app.rs b/crates/gpui2/src/app.rs index d04893b092f6e2b121dfcb7ab84a241d2ac89651..a2227cd984472aff4368416413d97febb0d29eda 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, 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}; @@ -93,6 +93,7 @@ impl App { quit_observers: SubscriberSet::new(), layout_id_buffer: Default::default(), propagate_event: true, + active_drag: None, }) })) } @@ -171,6 +172,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, @@ -758,6 +760,13 @@ pub(crate) enum Effect { }, } +pub(crate) struct AnyDrag { + pub drag_handle_view: AnyView, + pub cursor_offset: Point, + pub state: AnyBox, + pub state_type: TypeId, +} + #[cfg(test)] mod tests { use super::AppContext; 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 e955309112071a863ff1386bba9857b48b6442ab..2753d5f8fb290453ea4b8741b74249d6f2e0c5de 100644 --- a/crates/gpui2/src/elements/div.rs +++ b/crates/gpui2/src/elements/div.rs @@ -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); } @@ -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() } } @@ -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..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() } } @@ -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..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() } } @@ -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/geometry.rs b/crates/gpui2/src/geometry.rs index 08602352eea82ce31b6c154e87dbbc6b11585d46..95fa6c23535544c1a4433a266319690515e50823 100644 --- a/crates/gpui2/src/geometry.rs +++ b/crates/gpui2/src/geometry.rs @@ -39,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 475cbb75a470427b5c33f3356c6f6f0cbb59364f..aa8cbf4826e9f9218e3fe6ba814e477a70e8d65e 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, AnyBox, 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,18 +11,21 @@ use smallvec::SmallVec; use std::{ any::{Any, TypeId}, fmt::Debug, + marker::PhantomData, ops::Deref, sync::Arc, }; +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 } @@ -34,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()), }); @@ -52,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 @@ -76,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 @@ -100,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 @@ -124,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 @@ -147,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) { @@ -167,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) { @@ -183,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 } @@ -198,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(); @@ -223,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(); @@ -244,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(); @@ -254,16 +257,63 @@ 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 { - 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 +325,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,17 +334,55 @@ 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, + ) -> Self + where + Self: Sized, + { + 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, { - self.stateful_interactivity() - .mouse_click_listeners - .push(Arc::new(move |view, event, cx| handler(view, event, cx))); + 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, 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 } } @@ -359,6 +447,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 { @@ -411,38 +519,104 @@ 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() { - 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); + 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(); + } } } + } + }); + } - *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()); + if let Some(stateful) = self.as_stateful() { + 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 { + 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 { + cx.notify(); + } + } else if phase == DispatchPhase::Bubble + && bounds.contains_point(&event.position) + && (event.position - mouse_down.position).magnitude() + > DRAG_THRESHOLD + { + *active_state.lock() = ActiveState::default(); + 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(); + } + }); } - }); + + 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_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(); @@ -487,12 +661,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 { @@ -506,29 +680,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 mouse_click_listeners: SmallVec<[MouseClickListener; 2]>, - 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 @@ -560,13 +721,16 @@ 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, } } } +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]>, @@ -576,6 +740,9 @@ 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]>, + drop_listeners: SmallVec<[(TypeId, Arc>); 2]>, } impl StatelessInteraction @@ -586,7 +753,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 +810,7 @@ impl ActiveState { #[derive(Default)] pub struct InteractiveElementState { active_state: Arc>, - pending_click: Arc>>, + pending_mouse_down: Arc>>, scroll_offset: Option>>>, } @@ -665,6 +833,9 @@ 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(), + drop_listeners: SmallVec::new(), } } } @@ -740,11 +911,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 +1118,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 +1133,12 @@ pub type ScrollWheelListener = Arc< + 'static, >; +pub type ClickListener = + Arc) + Send + Sync + 'static>; + +pub(crate) type DragListener = + Arc, &mut ViewContext) -> AnyDrag + Send + Sync + 'static>; + pub type KeyListener = Arc< dyn Fn( &mut V, 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 6712e4cec6877c0896d234da3793faa6618f9035..cb07376fe34a1c1ca73c38140fdf8967268c5cd5 100644 --- a/crates/gpui2/src/window.rs +++ b/crates/gpui2/src/window.rs @@ -1,13 +1,13 @@ 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, - 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; @@ -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 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); + 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) + }); } } @@ -916,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 @@ -1206,7 +1221,7 @@ pub trait BorrowWindow: BorrowAppContext { result } - fn with_scroll_offset( + fn with_element_offset( &mut self, offset: Option>, f: impl FnOnce(&mut Self) -> R, @@ -1215,16 +1230,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() @@ -1263,6 +1278,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 diff --git a/crates/ui2/src/components/tab.rs b/crates/ui2/src/components/tab.rs index 2d787e6f025347f1498f629edce5dc4b4652e211..c13d4e5637cd4254caf26f57f38dc3de2a371e5b 100644 --- a/crates/ui2/src/components/tab.rs +++ b/crates/ui2/src/components/tab.rs @@ -17,6 +17,11 @@ pub struct Tab { close_side: IconSide, } +#[derive(Clone, Debug)] +struct TabDragState { + title: String, +} + impl Tab { pub fn new(id: impl Into) -> Self { Self { @@ -111,8 +116,19 @@ impl Tab { ), }; + let drag_state = TabDragState { + title: self.title.clone(), + }; + div() .id(self.id.clone()) + .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())) + .on_drop(|_view, state: TabDragState, cx| { + dbg!(state); + }) .px_2() .py_0p5() .flex() @@ -148,7 +164,7 @@ impl Tab { } } -use gpui2::ElementId; +use gpui2::{black, red, Drag, ElementId}; #[cfg(feature = "stories")] pub use stories::*;