mirror of
https://github.com/zed-industries/zed.git
synced 2024-11-10 05:37:29 +03:00
Allow dragging and dropping project entries
This commit is contained in:
parent
738893c527
commit
d61c0fb24c
@ -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)
|
||||||
|
@ -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)
|
||||||
|
Loading…
Reference in New Issue
Block a user