Handle project entry drop render & start fixing drag cancel issues

Co-Authored-By: Kay Simmons <kay@zed.dev>
This commit is contained in:
Julia 2022-11-07 18:17:36 -05:00
parent 847376a4f5
commit 9abfa037fd
4 changed files with 222 additions and 119 deletions

View file

@ -1,32 +1,47 @@
pub mod shared_payloads;
use std::{any::Any, rc::Rc};
use collections::HashSet;
use gpui::{
elements::{MouseEventHandler, Overlay},
elements::{Empty, MouseEventHandler, Overlay},
geometry::{rect::RectF, vector::Vector2F},
scene::MouseDrag,
CursorStyle, Element, ElementBox, EventContext, MouseButton, MutableAppContext, RenderContext,
View, WeakViewHandle,
};
struct State<V: View> {
window_id: usize,
position: Vector2F,
region_offset: Vector2F,
region: RectF,
payload: Rc<dyn Any + 'static>,
render: Rc<dyn Fn(Rc<dyn Any>, &mut RenderContext<V>) -> ElementBox>,
enum State<V: View> {
Dragging {
window_id: usize,
position: Vector2F,
region_offset: Vector2F,
region: RectF,
payload: Rc<dyn Any + 'static>,
render: Rc<dyn Fn(Rc<dyn Any>, &mut RenderContext<V>) -> ElementBox>,
},
Canceled,
}
impl<V: View> Clone for State<V> {
fn clone(&self) -> Self {
Self {
window_id: self.window_id.clone(),
position: self.position.clone(),
region_offset: self.region_offset.clone(),
region: self.region.clone(),
payload: self.payload.clone(),
render: self.render.clone(),
match self {
State::Dragging {
window_id,
position,
region_offset,
region,
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>)> {
self.currently_dragged.as_ref().and_then(
|State {
position,
payload,
window_id: window_dragged_from,
..
}| {
self.currently_dragged.as_ref().and_then(|state| {
if let State::Dragging {
position,
payload,
window_id: window_dragged_from,
..
} = state
{
if &window_id != window_dragged_from {
return None;
}
payload
.clone()
.downcast::<T>()
.ok()
.is::<T>()
.then(|| payload.clone().downcast::<T>().ok())
.flatten()
.map(|payload| (position.clone(), payload))
},
)
} else {
None
}
})
}
pub fn dragging<T: Any>(
@ -79,17 +97,27 @@ impl<V: View> DragAndDrop<V> {
) {
let window_id = cx.window_id();
cx.update_global::<Self, _, _>(|this, cx| {
let (region_offset, region) =
if let Some(previous_state) = this.currently_dragged.as_ref() {
(previous_state.region_offset, previous_state.region)
} else {
(
event.region.origin() - event.prev_mouse_position,
event.region,
)
};
this.notify_containers_for_window(window_id, cx);
this.currently_dragged = Some(State {
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 {
(
event.region.origin() - event.prev_mouse_position,
event.region,
)
};
this.currently_dragged = Some(State::Dragging {
window_id,
region_offset,
region,
@ -99,63 +127,114 @@ impl<V: View> DragAndDrop<V> {
render(payload.downcast_ref::<T>().unwrap(), cx)
}),
});
this.notify_containers_for_window(window_id, cx);
});
}
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
.clone()
.and_then(|state| {
match state {
State::Dragging {
window_id,
region_offset,
position,
region,
payload,
render,
} => {
if cx.window_id() != window_id {
return None;
}
currently_dragged.and_then(
|State {
window_id,
region_offset,
position,
region,
payload,
render,
}| {
if cx.window_id() != window_id {
return None;
dbg!("Rendered dragging state");
let position = position + region_offset;
Some(
Overlay::new(
MouseEventHandler::<DraggedElementHandler>::new(0, cx, |_, cx| {
render(payload, cx)
})
.with_cursor_style(CursorStyle::Arrow)
.on_up(MouseButton::Left, |_, cx| {
cx.defer(|cx| {
cx.update_global::<Self, _, _>(|this, cx| {
dbg!("Up with dragging state");
this.finish_dragging(cx)
});
});
cx.propagate_event();
})
.on_up_out(MouseButton::Left, |_, cx| {
cx.defer(|cx| {
cx.update_global::<Self, _, _>(|this, cx| {
dbg!("Up out with dragging state");
this.finish_dragging(cx)
});
});
})
// Don't block hover events or invalidations
.with_hoverable(false)
.constrained()
.with_width(region.width())
.with_height(region.height())
.boxed(),
)
.with_anchor_position(position)
.boxed(),
)
}
State::Canceled => {
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(),
)
}
}
let position = position + region_offset;
enum DraggedElementHandler {}
Some(
Overlay::new(
MouseEventHandler::<DraggedElementHandler>::new(0, cx, |_, cx| {
render(payload, cx)
})
.with_cursor_style(CursorStyle::Arrow)
.on_up(MouseButton::Left, |_, cx| {
cx.defer(|cx| {
cx.update_global::<Self, _, _>(|this, cx| this.stop_dragging(cx));
});
cx.propagate_event();
})
.on_up_out(MouseButton::Left, |_, cx| {
cx.defer(|cx| {
cx.update_global::<Self, _, _>(|this, cx| this.stop_dragging(cx));
});
})
// Don't block hover events or invalidations
.with_hoverable(false)
.constrained()
.with_width(region.width())
.with_height(region.height())
.boxed(),
)
.with_anchor_position(position)
.boxed(),
)
},
)
})
}
fn stop_dragging(&mut self, cx: &mut MutableAppContext) {
if let Some(State { window_id, .. }) = self.currently_dragged.take() {
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);
}
}

View file

@ -0,0 +1,6 @@
use std::{path::Path, sync::Arc};
#[derive(Debug, Clone)]
pub struct DraggedProjectEntry {
pub path: Arc<Path>,
}

View file

@ -1,5 +1,5 @@
use context_menu::{ContextMenu, ContextMenuItem};
use drag_and_drop::Draggable;
use drag_and_drop::{shared_payloads::DraggedProjectEntry, DragAndDrop, Draggable};
use editor::{Cancel, Editor};
use futures::stream::StreamExt;
use gpui::{
@ -72,8 +72,8 @@ pub enum ClipboardEntry {
},
}
#[derive(Debug, PartialEq, Eq, Clone)]
struct EntryDetails {
#[derive(Debug, PartialEq, Eq)]
pub struct EntryDetails {
filename: String,
path: Arc<Path>,
depth: usize,
@ -605,6 +605,10 @@ impl ProjectPanel {
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>(
details: EntryDetails,
editor: &ViewHandle<Editor>,
details: &EntryDetails,
editor: Option<&ViewHandle<Editor>>,
padding: f32,
row_container_style: ContainerStyle,
style: &ProjectPanelEntry,
@ -1046,8 +1050,8 @@ impl ProjectPanel {
.with_width(style.icon_size)
.boxed(),
)
.with_child(if show_editor {
ChildView::new(editor.clone(), cx)
.with_child(if show_editor && editor.is_some() {
ChildView::new(editor.unwrap().clone(), cx)
.contained()
.with_margin_left(style.icon_spacing)
.aligned()
@ -1099,8 +1103,8 @@ impl ProjectPanel {
};
Self::render_entry_visual_element(
details.clone(),
editor,
&details,
Some(editor),
padding,
row_container_style,
&style,
@ -1123,22 +1127,26 @@ impl ProjectPanel {
position: e.position,
})
})
.as_draggable(details.clone(), {
let editor = editor.clone();
let row_container_style = theme.dragged_entry.container;
.as_draggable(
DraggedProjectEntry {
path: details.path.clone(),
},
{
let row_container_style = theme.dragged_entry.container;
move |payload, cx: &mut RenderContext<Workspace>| {
let theme = cx.global::<Settings>().theme.clone();
Self::render_entry_visual_element(
payload.clone(),
&editor,
padding,
row_container_style,
&theme.project_panel.dragged_entry,
cx,
)
}
})
move |_, cx: &mut RenderContext<Workspace>| {
let theme = cx.global::<Settings>().theme.clone();
Self::render_entry_visual_element(
&details,
None,
padding,
row_container_style,
&theme.project_panel.dragged_entry,
cx,
)
}
},
)
.with_cursor_style(CursorStyle::PointingHand)
.boxed()
}

View file

@ -1,4 +1,4 @@
use drag_and_drop::DragAndDrop;
use drag_and_drop::{shared_payloads::DraggedProjectEntry, DragAndDrop};
use gpui::{
color::Color,
elements::{Canvas, MouseEventHandler, ParentElement, Stack},
@ -28,12 +28,18 @@ where
MouseEventHandler::<Tag>::above(region_id, cx, |state, cx| {
// Observing hovered will cause a render when the mouse enters regardless
// of if mouse position was accessed before
let hovered = state.hovered();
let drag_position = cx
.global::<DragAndDrop<Workspace>>()
.currently_dragged::<DraggedItem>(cx.window_id())
.filter(|_| hovered)
.map(|(drag_position, _)| drag_position);
let drag_position = if state.hovered() {
cx.global::<DragAndDrop<Workspace>>()
.currently_dragged::<DraggedItem>(cx.window_id())
.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()
.with_child(render_child(state, cx))
@ -70,10 +76,14 @@ where
}
})
.on_move(|_, cx| {
if cx
.global::<DragAndDrop<Workspace>>()
let drag_and_drop = cx.global::<DragAndDrop<Workspace>>();
if drag_and_drop
.currently_dragged::<DraggedItem>(cx.window_id())
.is_some()
|| drag_and_drop
.currently_dragged::<DraggedProjectEntry>(cx.window_id())
.is_some()
{
cx.notify();
} else {