Start work on dragging entries in the project panel

This commit is contained in:
Max Brunsfeld 2023-12-11 15:52:58 -08:00
parent a208229a2c
commit cbce49ff68
3 changed files with 137 additions and 80 deletions

View file

@ -2557,7 +2557,7 @@ impl CollabPanel {
let channel = channel.clone(); let channel = channel.clone();
move |cx| { move |cx| {
let channel = channel.clone(); let channel = channel.clone();
cx.build_view({ |cx| DraggedChannelView { channel, width } }) cx.build_view(|cx| DraggedChannelView { channel, width })
} }
}) })
.drag_over::<DraggedChannelView>(|style| { .drag_over::<DraggedChannelView>(|style| {

View file

@ -29,6 +29,7 @@ use std::{
path::Path, path::Path,
sync::Arc, sync::Arc,
}; };
use theme::ThemeSettings;
use ui::{prelude::*, v_stack, ContextMenu, IconElement, Label, ListItem}; use ui::{prelude::*, v_stack, ContextMenu, IconElement, Label, ListItem};
use unicase::UniCase; use unicase::UniCase;
use util::{maybe, ResultExt, TryFutureExt}; use util::{maybe, ResultExt, TryFutureExt};
@ -55,7 +56,7 @@ pub struct ProjectPanel {
clipboard_entry: Option<ClipboardEntry>, clipboard_entry: Option<ClipboardEntry>,
_dragged_entry_destination: Option<Arc<Path>>, _dragged_entry_destination: Option<Arc<Path>>,
_workspace: WeakView<Workspace>, _workspace: WeakView<Workspace>,
width: Option<f32>, width: Option<Pixels>,
pending_serialization: Task<Option<()>>, pending_serialization: Task<Option<()>>,
} }
@ -86,7 +87,7 @@ pub enum ClipboardEntry {
}, },
} }
#[derive(Debug, PartialEq, Eq)] #[derive(Debug, PartialEq, Eq, Clone)]
pub struct EntryDetails { pub struct EntryDetails {
filename: String, filename: String,
icon: Option<Arc<str>>, icon: Option<Arc<str>>,
@ -162,6 +163,12 @@ struct SerializedProjectPanel {
width: Option<f32>, width: Option<f32>,
} }
struct DraggedProjectEntryView {
entry_id: ProjectEntryId,
details: EntryDetails,
width: Pixels,
}
impl ProjectPanel { impl ProjectPanel {
fn new(workspace: &mut Workspace, cx: &mut ViewContext<Workspace>) -> View<Self> { fn new(workspace: &mut Workspace, cx: &mut ViewContext<Workspace>) -> View<Self> {
let project = workspace.project().clone(); let project = workspace.project().clone();
@ -236,7 +243,6 @@ impl ProjectPanel {
context_menu: None, context_menu: None,
filename_editor, filename_editor,
clipboard_entry: None, clipboard_entry: None,
// context_menu: cx.add_view(|cx| ContextMenu::new(view_id, cx)),
_dragged_entry_destination: None, _dragged_entry_destination: None,
_workspace: workspace.weak_handle(), _workspace: workspace.weak_handle(),
width: None, width: None,
@ -331,7 +337,7 @@ impl ProjectPanel {
let panel = ProjectPanel::new(workspace, cx); let panel = ProjectPanel::new(workspace, cx);
if let Some(serialized_panel) = serialized_panel { if let Some(serialized_panel) = serialized_panel {
panel.update(cx, |panel, cx| { panel.update(cx, |panel, cx| {
panel.width = serialized_panel.width; panel.width = serialized_panel.width.map(px);
cx.notify(); cx.notify();
}); });
} }
@ -346,7 +352,9 @@ impl ProjectPanel {
KEY_VALUE_STORE KEY_VALUE_STORE
.write_kvp( .write_kvp(
PROJECT_PANEL_KEY.into(), PROJECT_PANEL_KEY.into(),
serde_json::to_string(&SerializedProjectPanel { width })?, serde_json::to_string(&SerializedProjectPanel {
width: width.map(|p| p.0),
})?,
) )
.await?; .await?;
anyhow::Ok(()) anyhow::Ok(())
@ -1003,37 +1011,36 @@ impl ProjectPanel {
} }
} }
// todo!() fn move_entry(
// fn move_entry( &mut self,
// &mut self, entry_to_move: ProjectEntryId,
// entry_to_move: ProjectEntryId, destination: ProjectEntryId,
// destination: ProjectEntryId, destination_is_file: bool,
// destination_is_file: bool, cx: &mut ViewContext<Self>,
// cx: &mut ViewContext<Self>, ) {
// ) { let destination_worktree = self.project.update(cx, |project, cx| {
// let destination_worktree = self.project.update(cx, |project, cx| { let entry_path = project.path_for_entry(entry_to_move, 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 destination_entry_path = project.path_for_entry(destination, cx)?.path.clone();
// let mut destination_path = destination_entry_path.as_ref(); let mut destination_path = destination_entry_path.as_ref();
// if destination_is_file { if destination_is_file {
// destination_path = destination_path.parent()?; destination_path = destination_path.parent()?;
// } }
// let mut new_path = destination_path.to_path_buf(); let mut new_path = destination_path.to_path_buf();
// new_path.push(entry_path.path.file_name()?); new_path.push(entry_path.path.file_name()?);
// if new_path != entry_path.path.as_ref() { if new_path != entry_path.path.as_ref() {
// let task = project.rename_entry(entry_to_move, new_path, cx); let task = project.rename_entry(entry_to_move, new_path, cx);
// cx.foreground_executor().spawn(task).detach_and_log_err(cx); cx.foreground_executor().spawn(task).detach_and_log_err(cx);
// } }
// Some(project.worktree_id_for_entry(destination, cx)?) Some(project.worktree_id_for_entry(destination, cx)?)
// }); });
// if let Some(destination_worktree) = destination_worktree { if let Some(destination_worktree) = destination_worktree {
// self.expand_entry(destination_worktree, destination, cx); 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;
@ -1349,15 +1356,15 @@ impl ProjectPanel {
&self, &self,
entry_id: ProjectEntryId, entry_id: ProjectEntryId,
details: EntryDetails, details: EntryDetails,
// dragged_entry_destination: &mut Option<Arc<Path>>,
cx: &mut ViewContext<Self>, cx: &mut ViewContext<Self>,
) -> ListItem { ) -> Stateful<Div> {
let kind = details.kind; let kind = details.kind;
let settings = ProjectPanelSettings::get_global(cx); let settings = ProjectPanelSettings::get_global(cx);
let show_editor = details.is_editing && !details.is_processing; let show_editor = details.is_editing && !details.is_processing;
let is_selected = self let is_selected = self
.selection .selection
.map_or(false, |selection| selection.entry_id == entry_id); .map_or(false, |selection| selection.entry_id == entry_id);
let width = self.width.unwrap_or(px(0.));
let theme = cx.theme(); let theme = cx.theme();
let filename_text_color = details let filename_text_color = details
@ -1370,52 +1377,69 @@ impl ProjectPanel {
}) })
.unwrap_or(theme.status().info); .unwrap_or(theme.status().info);
ListItem::new(entry_id.to_proto() as usize) div()
.indent_level(details.depth) .id(entry_id.to_proto() as usize)
.indent_step_size(px(settings.indent_size)) .on_drag({
.selected(is_selected) let details = details.clone();
.child(if let Some(icon) = &details.icon { move |cx| {
div().child(IconElement::from_path(icon.to_string())) let details = details.clone();
} else { cx.build_view(|_| DraggedProjectEntryView {
div() details,
width,
entry_id,
})
}
}) })
.drag_over::<DraggedProjectEntryView>(|style| {
style.bg(cx.theme().colors().ghost_element_hover)
})
.on_drop(cx.listener(
move |this, dragged_view: &View<DraggedProjectEntryView>, cx| {
this.move_entry(dragged_view.read(cx).entry_id, entry_id, kind.is_file(), cx);
},
))
.child( .child(
if let (Some(editor), true) = (Some(&self.filename_editor), show_editor) { ListItem::new(entry_id.to_proto() as usize)
div().h_full().w_full().child(editor.clone()) .indent_level(details.depth)
} else { .indent_step_size(px(settings.indent_size))
div() .selected(is_selected)
.text_color(filename_text_color) .child(if let Some(icon) = &details.icon {
.child(Label::new(details.filename.clone())) div().child(IconElement::from_path(icon.to_string()))
}
.ml_1(),
)
.on_click(cx.listener(move |this, event: &gpui::ClickEvent, cx| {
if event.down.button == MouseButton::Right {
return;
}
if !show_editor {
if kind.is_dir() {
this.toggle_expanded(entry_id, cx);
} else { } else {
if event.down.modifiers.command { div()
this.split_entry(entry_id, cx); })
.child(
if let (Some(editor), true) = (Some(&self.filename_editor), show_editor) {
div().h_full().w_full().child(editor.clone())
} else { } else {
this.open_entry(entry_id, event.up.click_count > 1, cx); div()
.text_color(filename_text_color)
.child(Label::new(details.filename.clone()))
} }
} .ml_1(),
} )
})) .on_click(cx.listener(move |this, event: &gpui::ClickEvent, cx| {
.on_secondary_mouse_down(cx.listener(move |this, event: &MouseDownEvent, cx| { if event.down.button == MouseButton::Right {
this.deploy_context_menu(event.position, entry_id, cx); return;
})) }
// .on_drop::<ProjectEntryId>(|this, event, cx| { if !show_editor {
// this.move_entry( if kind.is_dir() {
// *dragged_entry, this.toggle_expanded(entry_id, cx);
// entry_id, } else {
// matches!(details.kind, EntryKind::File(_)), if event.down.modifiers.command {
// cx, this.split_entry(entry_id, cx);
// ); } else {
// }) this.open_entry(entry_id, event.up.click_count > 1, cx);
}
}
}
}))
.on_secondary_mouse_down(cx.listener(
move |this, event: &MouseDownEvent, cx| {
this.deploy_context_menu(event.position, entry_id, cx);
},
)),
)
} }
fn dispatch_context(&self, cx: &ViewContext<Self>) -> KeyContext { fn dispatch_context(&self, cx: &ViewContext<Self>) -> KeyContext {
@ -1430,7 +1454,6 @@ impl ProjectPanel {
}; };
dispatch_context.add(identifier); dispatch_context.add(identifier);
dispatch_context dispatch_context
} }
} }
@ -1503,6 +1526,30 @@ impl Render for ProjectPanel {
} }
} }
impl Render for DraggedProjectEntryView {
type Element = Div;
fn render(&mut self, cx: &mut ViewContext<Self>) -> Self::Element {
let settings = ProjectPanelSettings::get_global(cx);
let ui_font = ThemeSettings::get_global(cx).ui_font.family.clone();
h_stack()
.font(ui_font)
.bg(cx.theme().colors().background)
.w(self.width)
.child(
ListItem::new(self.entry_id.to_proto() as usize)
.indent_level(self.details.depth)
.indent_step_size(px(settings.indent_size))
.child(if let Some(icon) = &self.details.icon {
div().child(IconElement::from_path(icon.to_string()))
} else {
div()
})
.child(Label::new(self.details.filename.clone())),
)
}
}
impl EventEmitter<Event> for ProjectPanel {} impl EventEmitter<Event> for ProjectPanel {}
impl EventEmitter<PanelEvent> for ProjectPanel {} impl EventEmitter<PanelEvent> for ProjectPanel {}
@ -1534,12 +1581,14 @@ impl Panel for ProjectPanel {
} }
fn size(&self, cx: &WindowContext) -> f32 { fn size(&self, cx: &WindowContext) -> f32 {
self.width self.width.map_or_else(
.unwrap_or_else(|| ProjectPanelSettings::get_global(cx).default_width) || ProjectPanelSettings::get_global(cx).default_width,
|width| width.0,
)
} }
fn set_size(&mut self, size: Option<f32>, cx: &mut ViewContext<Self>) { fn set_size(&mut self, size: Option<f32>, cx: &mut ViewContext<Self>) {
self.width = size; self.width = size.map(px);
self.serialize(cx); self.serialize(cx);
cx.notify(); cx.notify();
} }

View file

@ -54,6 +54,14 @@ impl ListItem {
self self
} }
pub fn on_drag(
mut self,
handler: impl Fn(&MouseDownEvent, &mut WindowContext) + 'static,
) -> Self {
self.on_secondary_mouse_down = Some(Box::new(handler));
self
}
pub fn tooltip(mut self, tooltip: impl Fn(&mut WindowContext) -> AnyView + 'static) -> Self { pub fn tooltip(mut self, tooltip: impl Fn(&mut WindowContext) -> AnyView + 'static) -> Self {
self.tooltip = Some(Box::new(tooltip)); self.tooltip = Some(Box::new(tooltip));
self self