Detailed changes
@@ -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<TextSystem>,
flushing_effects: bool,
pending_updates: usize,
+ pub(crate) active_drag: Option<AnyDrag>,
pub(crate) next_frame_callbacks: HashMap<DisplayId, Vec<FrameCallback>>,
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;
@@ -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>,
V: 'static + Send + Sync,
{
- fn stateful_interactivity(&mut self) -> &mut StatefulInteraction<Self::ViewState> {
+ fn stateful_interaction(&mut self) -> &mut StatefulInteraction<Self::ViewState> {
&mut self.interaction
}
}
@@ -166,8 +166,8 @@ where
V: 'static + Send + Sync,
F: ElementFocus<V>,
{
- fn stateful_interactivity(&mut self) -> &mut StatefulInteraction<Self::ViewState> {
- self.base.stateful_interactivity()
+ fn stateful_interaction(&mut self) -> &mut StatefulInteraction<Self::ViewState> {
+ self.base.stateful_interaction()
}
}
@@ -140,8 +140,8 @@ where
V: 'static + Send + Sync,
F: ElementFocus<V>,
{
- fn stateful_interactivity(&mut self) -> &mut StatefulInteraction<Self::ViewState> {
- self.base.stateful_interactivity()
+ fn stateful_interaction(&mut self) -> &mut StatefulInteraction<Self::ViewState> {
+ self.base.stateful_interaction()
}
}
@@ -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<Self::ViewState>;
+ fn stateful_interaction(&mut self) -> &mut StatefulInteraction<Self::ViewState>;
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<Self::ViewState>)
+ listener: impl Fn(&mut Self::ViewState, &ClickEvent, &mut ViewContext<Self::ViewState>)
+ 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<S, R, E>(
+ mut self,
+ listener: impl Fn(
+ &mut Self::ViewState,
+ &mut ViewContext<Self::ViewState>,
+ ) -> Drag<S, R, Self::ViewState, E>
+ + Send
+ + Sync
+ + 'static,
+ ) -> Self
+ where
+ Self: Sized,
+ S: 'static + Send + Sync,
+ R: 'static + Fn(&mut Self::ViewState, &mut ViewContext<Self::ViewState>) -> E + Send + Sync,
+ E: Element<ViewState = Self::ViewState>,
+ {
+ 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::<S>(),
+ }
+ }));
self
}
}
@@ -419,30 +456,45 @@ pub trait ElementInteraction<V: 'static + Send + Sync>: '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<V: 'static + Send + Sync> {
#[deref]
#[deref_mut]
stateless: StatelessInteraction<V>,
- pub mouse_click_listeners: SmallVec<[MouseClickListener<V>; 2]>,
+ pub click_listeners: SmallVec<[ClickListener<V>; 2]>,
+ pub(crate) drag_listener: Option<DragListener<V>>,
pub active_style: StyleRefinement,
pub group_active_style: Option<GroupStyle>,
}
@@ -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<Mutex<ActiveState>>,
- pending_click: Arc<Mutex<Option<MouseDownEvent>>>,
+ pending_mouse_down: Arc<Mutex<Option<MouseDownEvent>>>,
scroll_offset: Option<Arc<Mutex<Point<Pixels>>>>,
}
@@ -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<S, R, V, E>
+where
+ S: 'static + Send + Sync,
+ R: Fn(&mut V, &mut ViewContext<V>) -> E,
+ V: 'static + Send + Sync,
+ E: Element<ViewState = V>,
+{
+ pub state: S,
+ pub render_drag_handle: R,
+ view_type: PhantomData<V>,
+}
+
+impl<S, R, V, E> Drag<S, R, V, E>
+where
+ S: 'static + Send + Sync,
+ R: Fn(&mut V, &mut ViewContext<V>) -> E + Send + Sync,
+ V: 'static + Send + Sync,
+ E: Element<ViewState = V>,
+{
+ 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<V> = Arc<
+ Sync
+ 'static,
>;
-pub type MouseClickListener<V> =
- Arc<dyn Fn(&mut V, &MouseClickEvent, &mut ViewContext<V>) + Send + Sync + 'static>;
pub type MouseMoveListener<V> = Arc<
dyn Fn(&mut V, &MouseMoveEvent, &Bounds<Pixels>, DispatchPhase, &mut ViewContext<V>)
@@ -936,6 +1017,12 @@ pub type ScrollWheelListener<V> = Arc<
+ 'static,
>;
+pub type ClickListener<V> =
+ Arc<dyn Fn(&mut V, &ClickEvent, &mut ViewContext<V>) + Send + Sync + 'static>;
+
+pub(crate) type DragListener<V> =
+ Arc<dyn Fn(&mut V, &mut ViewContext<V>) -> AnyDrag + Send + Sync + 'static>;
+
pub type KeyListener<V> = Arc<
dyn Fn(
&mut V,
@@ -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 {
@@ -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> {
@@ -17,6 +17,10 @@ pub struct Tab<S: 'static + Send + Sync + Clone> {
close_side: IconSide,
}
+struct TabDragState {
+ title: String,
+}
+
impl<S: 'static + Send + Sync + Clone> Tab<S> {
pub fn new(id: impl Into<ElementId>) -> Self {
Self {
@@ -113,6 +117,12 @@ impl<S: 'static + Send + Sync + Clone> Tab<S> {
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<S: 'static + Send + Sync + Clone> Tab<S> {
}
}
-use gpui2::ElementId;
+use gpui2::{black, ElementId};
#[cfg(feature = "stories")]
pub use stories::*;