From cbce49ff68d96ef3a512db4301651837595dce79 Mon Sep 17 00:00:00 2001 From: Max Brunsfeld Date: Mon, 11 Dec 2023 15:52:58 -0800 Subject: [PATCH] Start work on dragging entries in the project panel --- crates/collab_ui2/src/collab_panel.rs | 2 +- crates/project_panel2/src/project_panel.rs | 207 ++++++++++++-------- crates/ui2/src/components/list/list_item.rs | 8 + 3 files changed, 137 insertions(+), 80 deletions(-) diff --git a/crates/collab_ui2/src/collab_panel.rs b/crates/collab_ui2/src/collab_panel.rs index 3f4a28b58b..dee285661a 100644 --- a/crates/collab_ui2/src/collab_panel.rs +++ b/crates/collab_ui2/src/collab_panel.rs @@ -2557,7 +2557,7 @@ impl CollabPanel { let channel = channel.clone(); move |cx| { let channel = channel.clone(); - cx.build_view({ |cx| DraggedChannelView { channel, width } }) + cx.build_view(|cx| DraggedChannelView { channel, width }) } }) .drag_over::(|style| { diff --git a/crates/project_panel2/src/project_panel.rs b/crates/project_panel2/src/project_panel.rs index 75c7d6ed85..b8961810e7 100644 --- a/crates/project_panel2/src/project_panel.rs +++ b/crates/project_panel2/src/project_panel.rs @@ -29,6 +29,7 @@ use std::{ path::Path, sync::Arc, }; +use theme::ThemeSettings; use ui::{prelude::*, v_stack, ContextMenu, IconElement, Label, ListItem}; use unicase::UniCase; use util::{maybe, ResultExt, TryFutureExt}; @@ -55,7 +56,7 @@ pub struct ProjectPanel { clipboard_entry: Option, _dragged_entry_destination: Option>, _workspace: WeakView, - width: Option, + width: Option, pending_serialization: Task>, } @@ -86,7 +87,7 @@ pub enum ClipboardEntry { }, } -#[derive(Debug, PartialEq, Eq)] +#[derive(Debug, PartialEq, Eq, Clone)] pub struct EntryDetails { filename: String, icon: Option>, @@ -162,6 +163,12 @@ struct SerializedProjectPanel { width: Option, } +struct DraggedProjectEntryView { + entry_id: ProjectEntryId, + details: EntryDetails, + width: Pixels, +} + impl ProjectPanel { fn new(workspace: &mut Workspace, cx: &mut ViewContext) -> View { let project = workspace.project().clone(); @@ -236,7 +243,6 @@ impl ProjectPanel { context_menu: None, filename_editor, clipboard_entry: None, - // context_menu: cx.add_view(|cx| ContextMenu::new(view_id, cx)), _dragged_entry_destination: None, _workspace: workspace.weak_handle(), width: None, @@ -331,7 +337,7 @@ impl ProjectPanel { let panel = ProjectPanel::new(workspace, cx); if let Some(serialized_panel) = serialized_panel { panel.update(cx, |panel, cx| { - panel.width = serialized_panel.width; + panel.width = serialized_panel.width.map(px); cx.notify(); }); } @@ -346,7 +352,9 @@ impl ProjectPanel { KEY_VALUE_STORE .write_kvp( PROJECT_PANEL_KEY.into(), - serde_json::to_string(&SerializedProjectPanel { width })?, + serde_json::to_string(&SerializedProjectPanel { + width: width.map(|p| p.0), + })?, ) .await?; anyhow::Ok(()) @@ -1003,37 +1011,36 @@ impl ProjectPanel { } } - // todo!() - // fn move_entry( - // &mut self, - // entry_to_move: ProjectEntryId, - // destination: ProjectEntryId, - // destination_is_file: bool, - // cx: &mut ViewContext, - // ) { - // 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(); + fn move_entry( + &mut self, + entry_to_move: ProjectEntryId, + destination: ProjectEntryId, + destination_is_file: bool, + cx: &mut ViewContext, + ) { + 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 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_executor().spawn(task).detach_and_log_err(cx); - // } + 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_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 { - // self.expand_entry(destination_worktree, 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)> { let mut entry_index = 0; @@ -1349,15 +1356,15 @@ impl ProjectPanel { &self, entry_id: ProjectEntryId, details: EntryDetails, - // dragged_entry_destination: &mut Option>, cx: &mut ViewContext, - ) -> ListItem { + ) -> Stateful
{ let kind = details.kind; let settings = ProjectPanelSettings::get_global(cx); let show_editor = details.is_editing && !details.is_processing; let is_selected = self .selection .map_or(false, |selection| selection.entry_id == entry_id); + let width = self.width.unwrap_or(px(0.)); let theme = cx.theme(); let filename_text_color = details @@ -1370,52 +1377,69 @@ impl ProjectPanel { }) .unwrap_or(theme.status().info); - ListItem::new(entry_id.to_proto() as usize) - .indent_level(details.depth) - .indent_step_size(px(settings.indent_size)) - .selected(is_selected) - .child(if let Some(icon) = &details.icon { - div().child(IconElement::from_path(icon.to_string())) - } else { - div() + div() + .id(entry_id.to_proto() as usize) + .on_drag({ + let details = details.clone(); + move |cx| { + let details = details.clone(); + cx.build_view(|_| DraggedProjectEntryView { + details, + width, + entry_id, + }) + } }) + .drag_over::(|style| { + style.bg(cx.theme().colors().ghost_element_hover) + }) + .on_drop(cx.listener( + move |this, dragged_view: &View, cx| { + this.move_entry(dragged_view.read(cx).entry_id, entry_id, kind.is_file(), cx); + }, + )) .child( - if let (Some(editor), true) = (Some(&self.filename_editor), show_editor) { - div().h_full().w_full().child(editor.clone()) - } else { - div() - .text_color(filename_text_color) - .child(Label::new(details.filename.clone())) - } - .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); + ListItem::new(entry_id.to_proto() as usize) + .indent_level(details.depth) + .indent_step_size(px(settings.indent_size)) + .selected(is_selected) + .child(if let Some(icon) = &details.icon { + div().child(IconElement::from_path(icon.to_string())) } else { - if event.down.modifiers.command { - this.split_entry(entry_id, cx); + div() + }) + .child( + if let (Some(editor), true) = (Some(&self.filename_editor), show_editor) { + div().h_full().w_full().child(editor.clone()) } else { - this.open_entry(entry_id, event.up.click_count > 1, cx); + div() + .text_color(filename_text_color) + .child(Label::new(details.filename.clone())) } - } - } - })) - .on_secondary_mouse_down(cx.listener(move |this, event: &MouseDownEvent, cx| { - this.deploy_context_menu(event.position, entry_id, cx); - })) - // .on_drop::(|this, event, cx| { - // this.move_entry( - // *dragged_entry, - // entry_id, - // matches!(details.kind, EntryKind::File(_)), - // cx, - // ); - // }) + .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 { + if event.down.modifiers.command { + 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) -> KeyContext { @@ -1430,7 +1454,6 @@ impl ProjectPanel { }; dispatch_context.add(identifier); - 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::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 for ProjectPanel {} impl EventEmitter for ProjectPanel {} @@ -1534,12 +1581,14 @@ impl Panel for ProjectPanel { } fn size(&self, cx: &WindowContext) -> f32 { - self.width - .unwrap_or_else(|| ProjectPanelSettings::get_global(cx).default_width) + self.width.map_or_else( + || ProjectPanelSettings::get_global(cx).default_width, + |width| width.0, + ) } fn set_size(&mut self, size: Option, cx: &mut ViewContext) { - self.width = size; + self.width = size.map(px); self.serialize(cx); cx.notify(); } diff --git a/crates/ui2/src/components/list/list_item.rs b/crates/ui2/src/components/list/list_item.rs index 9db70c2ed2..f2cdd2055c 100644 --- a/crates/ui2/src/components/list/list_item.rs +++ b/crates/ui2/src/components/list/list_item.rs @@ -54,6 +54,14 @@ impl ListItem { 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 { self.tooltip = Some(Box::new(tooltip)); self