Allow dragging and dropping project entries

This commit is contained in:
Kay Simmons 2022-11-10 20:43:55 -08:00
parent 738893c527
commit d61c0fb24c
2 changed files with 150 additions and 34 deletions

View File

@ -4,12 +4,16 @@ use collections::HashSet;
use gpui::{ use gpui::{
elements::{Empty, MouseEventHandler, Overlay}, elements::{Empty, MouseEventHandler, Overlay},
geometry::{rect::RectF, vector::Vector2F}, geometry::{rect::RectF, vector::Vector2F},
scene::MouseDrag, scene::{MouseDown, MouseDrag},
CursorStyle, Element, ElementBox, EventContext, MouseButton, MutableAppContext, RenderContext, CursorStyle, Element, ElementBox, EventContext, MouseButton, MutableAppContext, RenderContext,
View, WeakViewHandle, View, WeakViewHandle,
}; };
enum State<V: View> { enum State<V: View> {
Down {
region_offset: Vector2F,
region: RectF,
},
Dragging { Dragging {
window_id: usize, window_id: usize,
position: Vector2F, position: Vector2F,
@ -24,6 +28,13 @@ enum State<V: View> {
impl<V: View> Clone for State<V> { impl<V: View> Clone for State<V> {
fn clone(&self) -> Self { fn clone(&self) -> Self {
match self { match self {
&State::Down {
region_offset,
region,
} => State::Down {
region_offset,
region,
},
State::Dragging { State::Dragging {
window_id, window_id,
position, position,
@ -87,6 +98,15 @@ impl<V: View> DragAndDrop<V> {
}) })
} }
pub fn drag_started(event: MouseDown, cx: &mut EventContext) {
cx.update_global(|this: &mut Self, _| {
this.currently_dragged = Some(State::Down {
region_offset: event.region.origin() - event.position,
region: event.region,
});
})
}
pub fn dragging<T: Any>( pub fn dragging<T: Any>(
event: MouseDrag, event: MouseDrag,
payload: Rc<T>, payload: Rc<T>,
@ -94,37 +114,32 @@ impl<V: View> DragAndDrop<V> {
render: Rc<impl 'static + Fn(&T, &mut RenderContext<V>) -> ElementBox>, render: Rc<impl 'static + Fn(&T, &mut RenderContext<V>) -> ElementBox>,
) { ) {
let window_id = cx.window_id(); let window_id = cx.window_id();
cx.update_global::<Self, _, _>(|this, cx| { cx.update_global(|this: &mut Self, cx| {
this.notify_containers_for_window(window_id, cx); this.notify_containers_for_window(window_id, cx);
if matches!(this.currently_dragged, Some(State::Canceled)) { match this.currently_dragged.as_ref() {
return; Some(&State::Down {
region_offset,
region,
})
| Some(&State::Dragging {
region_offset,
region,
..
}) => {
this.currently_dragged = Some(State::Dragging {
window_id,
region_offset,
region,
position: event.position,
payload,
render: Rc::new(move |payload, cx| {
render(payload.downcast_ref::<T>().unwrap(), cx)
}),
});
}
_ => {}
} }
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,
position: event.position,
payload,
render: Rc::new(move |payload, cx| {
render(payload.downcast_ref::<T>().unwrap(), cx)
}),
});
}); });
} }
@ -135,6 +150,7 @@ impl<V: View> DragAndDrop<V> {
.clone() .clone()
.and_then(|state| { .and_then(|state| {
match state { match state {
State::Down { .. } => None,
State::Dragging { State::Dragging {
window_id, window_id,
region_offset, region_offset,
@ -263,7 +279,11 @@ impl<Tag> Draggable for MouseEventHandler<Tag> {
{ {
let payload = Rc::new(payload); let payload = Rc::new(payload);
let render = Rc::new(render); let render = Rc::new(render);
self.on_drag(MouseButton::Left, move |e, cx| { self.on_down(MouseButton::Left, move |e, cx| {
cx.propagate_event();
DragAndDrop::<V>::drag_started(e, cx);
})
.on_drag(MouseButton::Left, move |e, cx| {
let payload = payload.clone(); let payload = payload.clone();
let render = render.clone(); let render = render.clone();
DragAndDrop::<V>::dragging(e, payload, cx, render) DragAndDrop::<V>::dragging(e, payload, cx, render)

View File

@ -43,6 +43,7 @@ pub struct ProjectPanel {
filename_editor: ViewHandle<Editor>, filename_editor: ViewHandle<Editor>,
clipboard_entry: Option<ClipboardEntry>, clipboard_entry: Option<ClipboardEntry>,
context_menu: ViewHandle<ContextMenu>, context_menu: ViewHandle<ContextMenu>,
dragged_entry_destination: Option<Arc<Path>>,
} }
#[derive(Copy, Clone)] #[derive(Copy, Clone)]
@ -95,6 +96,13 @@ pub struct Open {
pub change_focus: bool, pub change_focus: bool,
} }
#[derive(Clone, PartialEq)]
pub struct MoveProjectEntry {
pub entry_to_move: ProjectEntryId,
pub destination: ProjectEntryId,
pub destination_is_file: bool,
}
#[derive(Clone, PartialEq)] #[derive(Clone, PartialEq)]
pub struct DeployContextMenu { pub struct DeployContextMenu {
pub position: Vector2F, pub position: Vector2F,
@ -117,7 +125,10 @@ actions!(
ToggleFocus ToggleFocus
] ]
); );
impl_internal_actions!(project_panel, [Open, ToggleExpanded, DeployContextMenu]); impl_internal_actions!(
project_panel,
[Open, ToggleExpanded, DeployContextMenu, MoveProjectEntry]
);
pub fn init(cx: &mut MutableAppContext) { pub fn init(cx: &mut MutableAppContext) {
cx.add_action(ProjectPanel::deploy_context_menu); cx.add_action(ProjectPanel::deploy_context_menu);
@ -141,6 +152,7 @@ pub fn init(cx: &mut MutableAppContext) {
this.paste(action, cx); this.paste(action, cx);
}, },
); );
cx.add_action(ProjectPanel::move_entry);
} }
pub enum Event { pub enum Event {
@ -219,6 +231,7 @@ impl ProjectPanel {
filename_editor, filename_editor,
clipboard_entry: None, clipboard_entry: None,
context_menu: cx.add_view(ContextMenu::new), context_menu: cx.add_view(ContextMenu::new),
dragged_entry_destination: None,
}; };
this.update_visible_entries(None, cx); this.update_visible_entries(None, cx);
this this
@ -774,6 +787,39 @@ impl ProjectPanel {
} }
} }
fn move_entry(
&mut self,
&MoveProjectEntry {
entry_to_move,
destination,
destination_is_file,
}: &MoveProjectEntry,
cx: &mut ViewContext<Self>,
) {
let destination_worktree = self.project.update(cx, |project, cx| {
let entry_path = project.path_for_entry(entry_to_move, cx)?;
let destination_entry_path = project.path_for_entry(destination, cx)?.path.clone();
let mut destination_path = destination_entry_path.as_ref();
if destination_is_file {
destination_path = destination_path.parent()?;
}
let mut new_path = destination_path.to_path_buf();
new_path.push(entry_path.path.file_name()?);
if new_path != entry_path.path.as_ref() {
let task = project.rename_entry(entry_to_move, new_path, cx)?;
cx.foreground().spawn(task).detach_and_log_err(cx);
}
Some(project.worktree_id_for_entry(destination, cx)?)
});
if let Some(destination_worktree) = destination_worktree {
self.expand_entry(destination_worktree, destination, cx);
}
}
fn index_for_selection(&self, selection: Selection) -> Option<(usize, usize, usize)> { fn index_for_selection(&self, selection: Selection) -> Option<(usize, usize, usize)> {
let mut entry_index = 0; let mut entry_index = 0;
let mut visible_entries_index = 0; let mut visible_entries_index = 0;
@ -1079,10 +1125,13 @@ impl ProjectPanel {
entry_id: ProjectEntryId, entry_id: ProjectEntryId,
details: EntryDetails, details: EntryDetails,
editor: &ViewHandle<Editor>, editor: &ViewHandle<Editor>,
dragged_entry_destination: &mut Option<Arc<Path>>,
theme: &theme::ProjectPanel, theme: &theme::ProjectPanel,
cx: &mut RenderContext<Self>, cx: &mut RenderContext<Self>,
) -> ElementBox { ) -> ElementBox {
let this = cx.handle();
let kind = details.kind; let kind = details.kind;
let path = details.path.clone();
let padding = theme.container.padding.left + details.depth as f32 * theme.indent_width; let padding = theme.container.padding.left + details.depth as f32 * theme.indent_width;
let entry_style = if details.is_cut { let entry_style = if details.is_cut {
@ -1096,7 +1145,20 @@ impl ProjectPanel {
let show_editor = details.is_editing && !details.is_processing; let show_editor = details.is_editing && !details.is_processing;
MouseEventHandler::<Self>::new(entry_id.to_usize(), cx, |state, cx| { MouseEventHandler::<Self>::new(entry_id.to_usize(), cx, |state, cx| {
let style = entry_style.style_for(state, details.is_selected).clone(); let mut style = entry_style.style_for(state, details.is_selected).clone();
if cx
.global::<DragAndDrop<Workspace>>()
.currently_dragged::<ProjectEntryId>(cx.window_id())
.is_some()
&& dragged_entry_destination
.as_ref()
.filter(|destination| details.path.starts_with(destination))
.is_some()
{
style = entry_style.active.clone().unwrap();
}
let row_container_style = if show_editor { let row_container_style = if show_editor {
theme.filename_editor.container theme.filename_editor.container
} else { } else {
@ -1128,6 +1190,35 @@ impl ProjectPanel {
position: e.position, position: e.position,
}) })
}) })
.on_up(MouseButton::Left, move |_, cx| {
if let Some((_, dragged_entry)) = cx
.global::<DragAndDrop<Workspace>>()
.currently_dragged::<ProjectEntryId>(cx.window_id())
{
cx.dispatch_action(MoveProjectEntry {
entry_to_move: *dragged_entry,
destination: entry_id,
destination_is_file: matches!(details.kind, EntryKind::File(_)),
});
}
})
.on_move(move |_, cx| {
if cx
.global::<DragAndDrop<Workspace>>()
.currently_dragged::<ProjectEntryId>(cx.window_id())
.is_some()
{
if let Some(this) = this.upgrade(cx.app) {
this.update(cx.app, |this, _| {
this.dragged_entry_destination = if matches!(kind, EntryKind::File(_)) {
path.parent().map(|parent| Arc::from(parent))
} else {
Some(path.clone())
};
})
}
}
})
.as_draggable(entry_id, { .as_draggable(entry_id, {
let row_container_style = theme.dragged_entry.container; let row_container_style = theme.dragged_entry.container;
@ -1154,14 +1245,15 @@ impl View for ProjectPanel {
} }
fn render(&mut self, cx: &mut gpui::RenderContext<'_, Self>) -> gpui::ElementBox { fn render(&mut self, cx: &mut gpui::RenderContext<'_, Self>) -> gpui::ElementBox {
enum Tag {} enum ProjectPanel {}
let theme = &cx.global::<Settings>().theme.project_panel; let theme = &cx.global::<Settings>().theme.project_panel;
let mut container_style = theme.container; let mut container_style = theme.container;
let padding = std::mem::take(&mut container_style.padding); let padding = std::mem::take(&mut container_style.padding);
let last_worktree_root_id = self.last_worktree_root_id; let last_worktree_root_id = self.last_worktree_root_id;
Stack::new() Stack::new()
.with_child( .with_child(
MouseEventHandler::<Tag>::new(0, cx, |_, cx| { MouseEventHandler::<ProjectPanel>::new(0, cx, |_, cx| {
UniformList::new( UniformList::new(
self.list.clone(), self.list.clone(),
self.visible_entries self.visible_entries
@ -1171,15 +1263,19 @@ impl View for ProjectPanel {
cx, cx,
move |this, range, items, cx| { move |this, range, items, cx| {
let theme = cx.global::<Settings>().theme.clone(); let theme = cx.global::<Settings>().theme.clone();
let mut dragged_entry_destination =
this.dragged_entry_destination.clone();
this.for_each_visible_entry(range, cx, |id, details, cx| { this.for_each_visible_entry(range, cx, |id, details, cx| {
items.push(Self::render_entry( items.push(Self::render_entry(
id, id,
details, details,
&this.filename_editor, &this.filename_editor,
&mut dragged_entry_destination,
&theme.project_panel, &theme.project_panel,
cx, cx,
)); ));
}); });
this.dragged_entry_destination = dragged_entry_destination;
}, },
) )
.with_padding_top(padding.top) .with_padding_top(padding.top)