Handle project entry drop render & start fixing drag cancel issues
Co-Authored-By: Kay Simmons <kay@zed.dev>
This commit is contained in:
parent
847376a4f5
commit
9abfa037fd
4 changed files with 222 additions and 119 deletions
|
@ -1,32 +1,47 @@
|
||||||
|
pub mod shared_payloads;
|
||||||
|
|
||||||
use std::{any::Any, rc::Rc};
|
use std::{any::Any, rc::Rc};
|
||||||
|
|
||||||
use collections::HashSet;
|
use collections::HashSet;
|
||||||
use gpui::{
|
use gpui::{
|
||||||
elements::{MouseEventHandler, Overlay},
|
elements::{Empty, MouseEventHandler, Overlay},
|
||||||
geometry::{rect::RectF, vector::Vector2F},
|
geometry::{rect::RectF, vector::Vector2F},
|
||||||
scene::MouseDrag,
|
scene::MouseDrag,
|
||||||
CursorStyle, Element, ElementBox, EventContext, MouseButton, MutableAppContext, RenderContext,
|
CursorStyle, Element, ElementBox, EventContext, MouseButton, MutableAppContext, RenderContext,
|
||||||
View, WeakViewHandle,
|
View, WeakViewHandle,
|
||||||
};
|
};
|
||||||
|
|
||||||
struct State<V: View> {
|
enum State<V: View> {
|
||||||
|
Dragging {
|
||||||
window_id: usize,
|
window_id: usize,
|
||||||
position: Vector2F,
|
position: Vector2F,
|
||||||
region_offset: Vector2F,
|
region_offset: Vector2F,
|
||||||
region: RectF,
|
region: RectF,
|
||||||
payload: Rc<dyn Any + 'static>,
|
payload: Rc<dyn Any + 'static>,
|
||||||
render: Rc<dyn Fn(Rc<dyn Any>, &mut RenderContext<V>) -> ElementBox>,
|
render: Rc<dyn Fn(Rc<dyn Any>, &mut RenderContext<V>) -> ElementBox>,
|
||||||
|
},
|
||||||
|
Canceled,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<V: View> Clone for State<V> {
|
impl<V: View> Clone for State<V> {
|
||||||
fn clone(&self) -> Self {
|
fn clone(&self) -> Self {
|
||||||
Self {
|
match self {
|
||||||
window_id: self.window_id.clone(),
|
State::Dragging {
|
||||||
position: self.position.clone(),
|
window_id,
|
||||||
region_offset: self.region_offset.clone(),
|
position,
|
||||||
region: self.region.clone(),
|
region_offset,
|
||||||
payload: self.payload.clone(),
|
region,
|
||||||
render: self.render.clone(),
|
payload,
|
||||||
|
render,
|
||||||
|
} => Self::Dragging {
|
||||||
|
window_id: window_id.clone(),
|
||||||
|
position: position.clone(),
|
||||||
|
region_offset: region_offset.clone(),
|
||||||
|
region: region.clone(),
|
||||||
|
payload: payload.clone(),
|
||||||
|
render: render.clone(),
|
||||||
|
},
|
||||||
|
State::Canceled => State::Canceled,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -51,24 +66,27 @@ impl<V: View> DragAndDrop<V> {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn currently_dragged<T: Any>(&self, window_id: usize) -> Option<(Vector2F, Rc<T>)> {
|
pub fn currently_dragged<T: Any>(&self, window_id: usize) -> Option<(Vector2F, Rc<T>)> {
|
||||||
self.currently_dragged.as_ref().and_then(
|
self.currently_dragged.as_ref().and_then(|state| {
|
||||||
|State {
|
if let State::Dragging {
|
||||||
position,
|
position,
|
||||||
payload,
|
payload,
|
||||||
window_id: window_dragged_from,
|
window_id: window_dragged_from,
|
||||||
..
|
..
|
||||||
}| {
|
} = state
|
||||||
|
{
|
||||||
if &window_id != window_dragged_from {
|
if &window_id != window_dragged_from {
|
||||||
return None;
|
return None;
|
||||||
}
|
}
|
||||||
|
|
||||||
payload
|
payload
|
||||||
.clone()
|
.is::<T>()
|
||||||
.downcast::<T>()
|
.then(|| payload.clone().downcast::<T>().ok())
|
||||||
.ok()
|
.flatten()
|
||||||
.map(|payload| (position.clone(), payload))
|
.map(|payload| (position.clone(), payload))
|
||||||
},
|
} else {
|
||||||
)
|
None
|
||||||
|
}
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn dragging<T: Any>(
|
pub fn dragging<T: Any>(
|
||||||
|
@ -79,9 +97,19 @@ impl<V: View> DragAndDrop<V> {
|
||||||
) {
|
) {
|
||||||
let window_id = cx.window_id();
|
let window_id = cx.window_id();
|
||||||
cx.update_global::<Self, _, _>(|this, cx| {
|
cx.update_global::<Self, _, _>(|this, cx| {
|
||||||
let (region_offset, region) =
|
this.notify_containers_for_window(window_id, cx);
|
||||||
if let Some(previous_state) = this.currently_dragged.as_ref() {
|
|
||||||
(previous_state.region_offset, previous_state.region)
|
if matches!(this.currently_dragged, Some(State::Canceled)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let (region_offset, region) = if let Some(State::Dragging {
|
||||||
|
region_offset,
|
||||||
|
region,
|
||||||
|
..
|
||||||
|
}) = this.currently_dragged.as_ref()
|
||||||
|
{
|
||||||
|
(*region_offset, *region)
|
||||||
} else {
|
} else {
|
||||||
(
|
(
|
||||||
event.region.origin() - event.prev_mouse_position,
|
event.region.origin() - event.prev_mouse_position,
|
||||||
|
@ -89,7 +117,7 @@ impl<V: View> DragAndDrop<V> {
|
||||||
)
|
)
|
||||||
};
|
};
|
||||||
|
|
||||||
this.currently_dragged = Some(State {
|
this.currently_dragged = Some(State::Dragging {
|
||||||
window_id,
|
window_id,
|
||||||
region_offset,
|
region_offset,
|
||||||
region,
|
region,
|
||||||
|
@ -99,30 +127,30 @@ impl<V: View> DragAndDrop<V> {
|
||||||
render(payload.downcast_ref::<T>().unwrap(), cx)
|
render(payload.downcast_ref::<T>().unwrap(), cx)
|
||||||
}),
|
}),
|
||||||
});
|
});
|
||||||
|
|
||||||
this.notify_containers_for_window(window_id, cx);
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn render(cx: &mut RenderContext<V>) -> Option<ElementBox> {
|
pub fn render(cx: &mut RenderContext<V>) -> Option<ElementBox> {
|
||||||
let currently_dragged = cx.global::<Self>().currently_dragged.clone();
|
enum DraggedElementHandler {}
|
||||||
|
cx.global::<Self>()
|
||||||
currently_dragged.and_then(
|
.currently_dragged
|
||||||
|State {
|
.clone()
|
||||||
|
.and_then(|state| {
|
||||||
|
match state {
|
||||||
|
State::Dragging {
|
||||||
window_id,
|
window_id,
|
||||||
region_offset,
|
region_offset,
|
||||||
position,
|
position,
|
||||||
region,
|
region,
|
||||||
payload,
|
payload,
|
||||||
render,
|
render,
|
||||||
}| {
|
} => {
|
||||||
if cx.window_id() != window_id {
|
if cx.window_id() != window_id {
|
||||||
return None;
|
return None;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
dbg!("Rendered dragging state");
|
||||||
let position = position + region_offset;
|
let position = position + region_offset;
|
||||||
|
|
||||||
enum DraggedElementHandler {}
|
|
||||||
Some(
|
Some(
|
||||||
Overlay::new(
|
Overlay::new(
|
||||||
MouseEventHandler::<DraggedElementHandler>::new(0, cx, |_, cx| {
|
MouseEventHandler::<DraggedElementHandler>::new(0, cx, |_, cx| {
|
||||||
|
@ -131,13 +159,19 @@ impl<V: View> DragAndDrop<V> {
|
||||||
.with_cursor_style(CursorStyle::Arrow)
|
.with_cursor_style(CursorStyle::Arrow)
|
||||||
.on_up(MouseButton::Left, |_, cx| {
|
.on_up(MouseButton::Left, |_, cx| {
|
||||||
cx.defer(|cx| {
|
cx.defer(|cx| {
|
||||||
cx.update_global::<Self, _, _>(|this, cx| this.stop_dragging(cx));
|
cx.update_global::<Self, _, _>(|this, cx| {
|
||||||
|
dbg!("Up with dragging state");
|
||||||
|
this.finish_dragging(cx)
|
||||||
|
});
|
||||||
});
|
});
|
||||||
cx.propagate_event();
|
cx.propagate_event();
|
||||||
})
|
})
|
||||||
.on_up_out(MouseButton::Left, |_, cx| {
|
.on_up_out(MouseButton::Left, |_, cx| {
|
||||||
cx.defer(|cx| {
|
cx.defer(|cx| {
|
||||||
cx.update_global::<Self, _, _>(|this, cx| this.stop_dragging(cx));
|
cx.update_global::<Self, _, _>(|this, cx| {
|
||||||
|
dbg!("Up out with dragging state");
|
||||||
|
this.finish_dragging(cx)
|
||||||
|
});
|
||||||
});
|
});
|
||||||
})
|
})
|
||||||
// Don't block hover events or invalidations
|
// Don't block hover events or invalidations
|
||||||
|
@ -150,12 +184,57 @@ impl<V: View> DragAndDrop<V> {
|
||||||
.with_anchor_position(position)
|
.with_anchor_position(position)
|
||||||
.boxed(),
|
.boxed(),
|
||||||
)
|
)
|
||||||
},
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn stop_dragging(&mut self, cx: &mut MutableAppContext) {
|
State::Canceled => {
|
||||||
if let Some(State { window_id, .. }) = self.currently_dragged.take() {
|
dbg!("Rendered canceled state");
|
||||||
|
Some(
|
||||||
|
MouseEventHandler::<DraggedElementHandler>::new(0, cx, |_, _| {
|
||||||
|
Empty::new()
|
||||||
|
.constrained()
|
||||||
|
.with_width(0.)
|
||||||
|
.with_height(0.)
|
||||||
|
.boxed()
|
||||||
|
})
|
||||||
|
.on_up(MouseButton::Left, |_, cx| {
|
||||||
|
cx.defer(|cx| {
|
||||||
|
cx.update_global::<Self, _, _>(|this, _| {
|
||||||
|
dbg!("Up with canceled state");
|
||||||
|
this.currently_dragged = None;
|
||||||
|
});
|
||||||
|
});
|
||||||
|
})
|
||||||
|
.on_up_out(MouseButton::Left, |_, cx| {
|
||||||
|
cx.defer(|cx| {
|
||||||
|
cx.update_global::<Self, _, _>(|this, _| {
|
||||||
|
dbg!("Up out with canceled state");
|
||||||
|
this.currently_dragged = None;
|
||||||
|
});
|
||||||
|
});
|
||||||
|
})
|
||||||
|
.boxed(),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn cancel_dragging<P: Any>(&mut self, cx: &mut MutableAppContext) {
|
||||||
|
if let Some(State::Dragging {
|
||||||
|
payload, window_id, ..
|
||||||
|
}) = &self.currently_dragged
|
||||||
|
{
|
||||||
|
if payload.is::<P>() {
|
||||||
|
let window_id = *window_id;
|
||||||
|
self.currently_dragged = Some(State::Canceled);
|
||||||
|
dbg!("Canceled");
|
||||||
|
self.notify_containers_for_window(window_id, cx);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn finish_dragging(&mut self, cx: &mut MutableAppContext) {
|
||||||
|
if let Some(State::Dragging { window_id, .. }) = self.currently_dragged.take() {
|
||||||
self.notify_containers_for_window(window_id, cx);
|
self.notify_containers_for_window(window_id, cx);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
6
crates/drag_and_drop/src/shared_payloads.rs
Normal file
6
crates/drag_and_drop/src/shared_payloads.rs
Normal file
|
@ -0,0 +1,6 @@
|
||||||
|
use std::{path::Path, sync::Arc};
|
||||||
|
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub struct DraggedProjectEntry {
|
||||||
|
pub path: Arc<Path>,
|
||||||
|
}
|
|
@ -1,5 +1,5 @@
|
||||||
use context_menu::{ContextMenu, ContextMenuItem};
|
use context_menu::{ContextMenu, ContextMenuItem};
|
||||||
use drag_and_drop::Draggable;
|
use drag_and_drop::{shared_payloads::DraggedProjectEntry, DragAndDrop, Draggable};
|
||||||
use editor::{Cancel, Editor};
|
use editor::{Cancel, Editor};
|
||||||
use futures::stream::StreamExt;
|
use futures::stream::StreamExt;
|
||||||
use gpui::{
|
use gpui::{
|
||||||
|
@ -72,8 +72,8 @@ pub enum ClipboardEntry {
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, PartialEq, Eq, Clone)]
|
#[derive(Debug, PartialEq, Eq)]
|
||||||
struct EntryDetails {
|
pub struct EntryDetails {
|
||||||
filename: String,
|
filename: String,
|
||||||
path: Arc<Path>,
|
path: Arc<Path>,
|
||||||
depth: usize,
|
depth: usize,
|
||||||
|
@ -605,6 +605,10 @@ impl ProjectPanel {
|
||||||
cx.notify();
|
cx.notify();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
cx.update_global(|drag_and_drop: &mut DragAndDrop<Workspace>, cx| {
|
||||||
|
drag_and_drop.cancel_dragging::<DraggedProjectEntry>(cx);
|
||||||
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1014,8 +1018,8 @@ impl ProjectPanel {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn render_entry_visual_element<V: View>(
|
fn render_entry_visual_element<V: View>(
|
||||||
details: EntryDetails,
|
details: &EntryDetails,
|
||||||
editor: &ViewHandle<Editor>,
|
editor: Option<&ViewHandle<Editor>>,
|
||||||
padding: f32,
|
padding: f32,
|
||||||
row_container_style: ContainerStyle,
|
row_container_style: ContainerStyle,
|
||||||
style: &ProjectPanelEntry,
|
style: &ProjectPanelEntry,
|
||||||
|
@ -1046,8 +1050,8 @@ impl ProjectPanel {
|
||||||
.with_width(style.icon_size)
|
.with_width(style.icon_size)
|
||||||
.boxed(),
|
.boxed(),
|
||||||
)
|
)
|
||||||
.with_child(if show_editor {
|
.with_child(if show_editor && editor.is_some() {
|
||||||
ChildView::new(editor.clone(), cx)
|
ChildView::new(editor.unwrap().clone(), cx)
|
||||||
.contained()
|
.contained()
|
||||||
.with_margin_left(style.icon_spacing)
|
.with_margin_left(style.icon_spacing)
|
||||||
.aligned()
|
.aligned()
|
||||||
|
@ -1099,8 +1103,8 @@ impl ProjectPanel {
|
||||||
};
|
};
|
||||||
|
|
||||||
Self::render_entry_visual_element(
|
Self::render_entry_visual_element(
|
||||||
details.clone(),
|
&details,
|
||||||
editor,
|
Some(editor),
|
||||||
padding,
|
padding,
|
||||||
row_container_style,
|
row_container_style,
|
||||||
&style,
|
&style,
|
||||||
|
@ -1123,22 +1127,26 @@ impl ProjectPanel {
|
||||||
position: e.position,
|
position: e.position,
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
.as_draggable(details.clone(), {
|
.as_draggable(
|
||||||
let editor = editor.clone();
|
DraggedProjectEntry {
|
||||||
|
path: details.path.clone(),
|
||||||
|
},
|
||||||
|
{
|
||||||
let row_container_style = theme.dragged_entry.container;
|
let row_container_style = theme.dragged_entry.container;
|
||||||
|
|
||||||
move |payload, cx: &mut RenderContext<Workspace>| {
|
move |_, cx: &mut RenderContext<Workspace>| {
|
||||||
let theme = cx.global::<Settings>().theme.clone();
|
let theme = cx.global::<Settings>().theme.clone();
|
||||||
Self::render_entry_visual_element(
|
Self::render_entry_visual_element(
|
||||||
payload.clone(),
|
&details,
|
||||||
&editor,
|
None,
|
||||||
padding,
|
padding,
|
||||||
row_container_style,
|
row_container_style,
|
||||||
&theme.project_panel.dragged_entry,
|
&theme.project_panel.dragged_entry,
|
||||||
cx,
|
cx,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
})
|
},
|
||||||
|
)
|
||||||
.with_cursor_style(CursorStyle::PointingHand)
|
.with_cursor_style(CursorStyle::PointingHand)
|
||||||
.boxed()
|
.boxed()
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
use drag_and_drop::DragAndDrop;
|
use drag_and_drop::{shared_payloads::DraggedProjectEntry, DragAndDrop};
|
||||||
use gpui::{
|
use gpui::{
|
||||||
color::Color,
|
color::Color,
|
||||||
elements::{Canvas, MouseEventHandler, ParentElement, Stack},
|
elements::{Canvas, MouseEventHandler, ParentElement, Stack},
|
||||||
|
@ -28,12 +28,18 @@ where
|
||||||
MouseEventHandler::<Tag>::above(region_id, cx, |state, cx| {
|
MouseEventHandler::<Tag>::above(region_id, cx, |state, cx| {
|
||||||
// Observing hovered will cause a render when the mouse enters regardless
|
// Observing hovered will cause a render when the mouse enters regardless
|
||||||
// of if mouse position was accessed before
|
// of if mouse position was accessed before
|
||||||
let hovered = state.hovered();
|
let drag_position = if state.hovered() {
|
||||||
let drag_position = cx
|
cx.global::<DragAndDrop<Workspace>>()
|
||||||
.global::<DragAndDrop<Workspace>>()
|
|
||||||
.currently_dragged::<DraggedItem>(cx.window_id())
|
.currently_dragged::<DraggedItem>(cx.window_id())
|
||||||
.filter(|_| hovered)
|
.map(|(drag_position, _)| drag_position)
|
||||||
.map(|(drag_position, _)| drag_position);
|
.or_else(|| {
|
||||||
|
cx.global::<DragAndDrop<Workspace>>()
|
||||||
|
.currently_dragged::<DraggedProjectEntry>(cx.window_id())
|
||||||
|
.map(|(drag_position, _)| drag_position)
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
};
|
||||||
|
|
||||||
Stack::new()
|
Stack::new()
|
||||||
.with_child(render_child(state, cx))
|
.with_child(render_child(state, cx))
|
||||||
|
@ -70,10 +76,14 @@ where
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.on_move(|_, cx| {
|
.on_move(|_, cx| {
|
||||||
if cx
|
let drag_and_drop = cx.global::<DragAndDrop<Workspace>>();
|
||||||
.global::<DragAndDrop<Workspace>>()
|
|
||||||
|
if drag_and_drop
|
||||||
.currently_dragged::<DraggedItem>(cx.window_id())
|
.currently_dragged::<DraggedItem>(cx.window_id())
|
||||||
.is_some()
|
.is_some()
|
||||||
|
|| drag_and_drop
|
||||||
|
.currently_dragged::<DraggedProjectEntry>(cx.window_id())
|
||||||
|
.is_some()
|
||||||
{
|
{
|
||||||
cx.notify();
|
cx.notify();
|
||||||
} else {
|
} else {
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue