crates/project_panel/src/project_panel.rs 🔗
@@ -79,6 +79,7 @@ pub struct ProjectPanel {
/// Relevant only for auto-fold dirs, where a single project panel entry may actually consist of several
/// project entries (and all non-leaf nodes are guaranteed to be directories).
ancestors: HashMap<ProjectEntryId, FoldedAncestors>,
+ folded_directory_drag_target: Option<FoldedDirectoryDragTarget>,
last_worktree_root_id: Option<ProjectEntryId>,
last_selection_drag_over_entry: Option<ProjectEntryId>,
last_external_paths_drag_over_entry: Option<ProjectEntryId>,
@@ -107,6 +108,14 @@ pub struct ProjectPanel {
hover_expand_task: Option<Task<()>>,
}
+#[derive(Copy, Clone, Debug)]
+struct FoldedDirectoryDragTarget {
+ entry_id: ProjectEntryId,
+ index: usize,
+ /// Whether we are dragging over the delimiter rather than the component itself.
+ is_delimiter_target: bool,
+}
+
#[derive(Clone, Debug)]
struct EditState {
worktree_id: WorktreeId,
@@ -249,7 +258,6 @@ struct SerializedProjectPanel {
struct DraggedProjectEntryView {
selection: SelectedEntry,
details: EntryDetails,
- width: Pixels,
click_offset: Point<Pixels>,
selections: Arc<BTreeSet<SelectedEntry>>,
}
@@ -418,6 +426,7 @@ impl ProjectPanel {
focus_handle,
visible_entries: Default::default(),
ancestors: Default::default(),
+ folded_directory_drag_target: None,
last_worktree_root_id: Default::default(),
last_external_paths_drag_over_entry: None,
last_selection_drag_over_entry: None,
@@ -3464,7 +3473,6 @@ impl ProjectPanel {
.selection
.map_or(false, |selection| selection.entry_id == entry_id);
- let width = self.size(window, cx);
let file_name = details.filename.clone();
let mut icon = details.icon.clone();
@@ -3523,6 +3531,8 @@ impl ProjectPanel {
bg_hover_color
};
+ let folded_directory_drag_target = self.folded_directory_drag_target;
+
div()
.id(entry_id.to_proto() as usize)
.group(GROUP_NAME)
@@ -3634,18 +3644,25 @@ impl ProjectPanel {
move |selection, click_offset, _window, cx| {
cx.new(|_| DraggedProjectEntryView {
details: details.clone(),
- width,
click_offset,
selection: selection.active_selection,
selections: selection.marked_selections.clone(),
})
},
)
- .drag_over::<DraggedSelection>(move |style, _, _, _| style.bg(item_colors.drag_over))
+ .drag_over::<DraggedSelection>(move |style, _, _, _| {
+ if folded_directory_drag_target.is_some() {
+ return style;
+ }
+ style.bg(item_colors.drag_over)
+ })
.on_drop(
cx.listener(move |this, selections: &DraggedSelection, window, cx| {
this.hover_scroll_task.take();
this.hover_expand_task.take();
+ if folded_directory_drag_target.is_some() {
+ return;
+ }
this.drag_onto(selections, entry_id, kind.is_file(), window, cx);
}),
)
@@ -3832,15 +3849,51 @@ impl ProjectPanel {
let active_index = components_len
- 1
- folded_ancestors.current_ancestor_depth;
- const DELIMITER: SharedString =
+ const DELIMITER: SharedString =
SharedString::new_static(std::path::MAIN_SEPARATOR_STR);
for (index, component) in components.into_iter().enumerate() {
if index != 0 {
- this = this.child(
- Label::new(DELIMITER.clone())
- .single_line()
- .color(filename_text_color),
- );
+ let delimiter_target_index = index - 1;
+ let target_entry_id = folded_ancestors.ancestors.get(components_len - 1 - delimiter_target_index).cloned();
+ this = this.child(
+ div()
+ .on_drop(cx.listener(move |this, selections: &DraggedSelection, window, cx| {
+ this.hover_scroll_task.take();
+ this.folded_directory_drag_target = None;
+ if let Some(target_entry_id) = target_entry_id {
+ this.drag_onto(selections, target_entry_id, kind.is_file(), window, cx);
+ }
+ }))
+ .on_drag_move(cx.listener(
+ move |this, event: &DragMoveEvent<DraggedSelection>, _, _| {
+ if event.bounds.contains(&event.event.position) {
+ this.folded_directory_drag_target = Some(
+ FoldedDirectoryDragTarget {
+ entry_id,
+ index: delimiter_target_index,
+ is_delimiter_target: true,
+ }
+ );
+ } else {
+ let is_current_target = this.folded_directory_drag_target
+ .map_or(false, |target|
+ target.entry_id == entry_id &&
+ target.index == delimiter_target_index &&
+ target.is_delimiter_target
+ );
+ if is_current_target {
+ this.folded_directory_drag_target = None;
+ }
+ }
+
+ },
+ ))
+ .child(
+ Label::new(DELIMITER.clone())
+ .single_line()
+ .color(filename_text_color)
+ )
+ );
}
let id = SharedString::from(format!(
"project_panel_path_component_{}_{index}",
@@ -3859,6 +3912,47 @@ impl ProjectPanel {
}
}
}))
+ .when(index != components_len - 1, |div|{
+ let target_entry_id = folded_ancestors.ancestors.get(components_len - 1 - index).cloned();
+ div
+ .on_drag_move(cx.listener(
+ move |this, event: &DragMoveEvent<DraggedSelection>, _, _| {
+ if event.bounds.contains(&event.event.position) {
+ this.folded_directory_drag_target = Some(
+ FoldedDirectoryDragTarget {
+ entry_id,
+ index,
+ is_delimiter_target: false,
+ }
+ );
+ } else {
+ let is_current_target = this.folded_directory_drag_target
+ .as_ref()
+ .map_or(false, |target|
+ target.entry_id == entry_id &&
+ target.index == index &&
+ !target.is_delimiter_target
+ );
+ if is_current_target {
+ this.folded_directory_drag_target = None;
+ }
+ }
+ },
+ ))
+ .on_drop(cx.listener(move |this, selections: &DraggedSelection, window,cx| {
+ this.hover_scroll_task.take();
+ this.folded_directory_drag_target = None;
+ if let Some(target_entry_id) = target_entry_id {
+ this.drag_onto(selections, target_entry_id, kind.is_file(), window, cx);
+ }
+ }))
+ .when(folded_directory_drag_target.map_or(false, |target|
+ target.entry_id == entry_id &&
+ target.index == index
+ ), |this| {
+ this.bg(item_colors.drag_over)
+ })
+ })
.child(
Label::new(component)
.single_line()
@@ -4547,35 +4641,33 @@ impl Render for ProjectPanel {
impl Render for DraggedProjectEntryView {
fn render(&mut self, _: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
- let settings = ProjectPanelSettings::get_global(cx);
let ui_font = ThemeSettings::get_global(cx).ui_font.clone();
-
- h_flex().font(ui_font).map(|this| {
- if self.selections.len() > 1 && self.selections.contains(&self.selection) {
- this.flex_none()
- .w(self.width)
- .child(div().w(self.click_offset.x))
- .child(
- div()
- .p_1()
- .rounded_xl()
- .bg(cx.theme().colors().background)
- .child(Label::new(format!("{} entries", self.selections.len()))),
- )
- } else {
- this.w(self.width).bg(cx.theme().colors().background).child(
- ListItem::new(self.selection.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(Icon::from_path(icon.clone()))
+ h_flex()
+ .font(ui_font)
+ .pl(self.click_offset.x + px(12.))
+ .pt(self.click_offset.y + px(12.))
+ .child(
+ div()
+ .flex()
+ .gap_1()
+ .items_center()
+ .py_1()
+ .px_2()
+ .rounded_lg()
+ .bg(cx.theme().colors().background)
+ .map(|this| {
+ if self.selections.len() > 1 && self.selections.contains(&self.selection) {
+ this.child(Label::new(format!("{} entries", self.selections.len())))
} else {
- div()
- })
- .child(Label::new(self.details.filename.clone())),
- )
- }
- })
+ this.child(if let Some(icon) = &self.details.icon {
+ div().child(Icon::from_path(icon.clone()))
+ } else {
+ div()
+ })
+ .child(Label::new(self.details.filename.clone()))
+ }
+ }),
+ )
}
}