From 847376a4f5f211af79ce5a767b851e9f1b278ad1 Mon Sep 17 00:00:00 2001 From: Julia Date: Mon, 7 Nov 2022 17:00:01 -0500 Subject: [PATCH] Start dragging project panel entries Co-Authored-By: Kay Simmons --- Cargo.lock | 1 + crates/drag_and_drop/src/drag_and_drop.rs | 23 ++- crates/project_panel/Cargo.toml | 1 + crates/project_panel/src/project_panel.rs | 168 ++++++++++++++-------- crates/theme/src/theme.rs | 1 + styles/src/styleTree/projectPanel.ts | 17 ++- styles/src/styleTree/tabBar.ts | 2 +- 7 files changed, 142 insertions(+), 71 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 53d23a0c26..cd0c5ed745 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4265,6 +4265,7 @@ name = "project_panel" version = "0.1.0" dependencies = [ "context_menu", + "drag_and_drop", "editor", "futures 0.3.24", "gpui", diff --git a/crates/drag_and_drop/src/drag_and_drop.rs b/crates/drag_and_drop/src/drag_and_drop.rs index bb660c750f..9a707fd054 100644 --- a/crates/drag_and_drop/src/drag_and_drop.rs +++ b/crates/drag_and_drop/src/drag_and_drop.rs @@ -3,7 +3,7 @@ use std::{any::Any, rc::Rc}; use collections::HashSet; use gpui::{ elements::{MouseEventHandler, Overlay}, - geometry::vector::Vector2F, + geometry::{rect::RectF, vector::Vector2F}, scene::MouseDrag, CursorStyle, Element, ElementBox, EventContext, MouseButton, MutableAppContext, RenderContext, View, WeakViewHandle, @@ -13,6 +13,7 @@ struct State { window_id: usize, position: Vector2F, region_offset: Vector2F, + region: RectF, payload: Rc, render: Rc, &mut RenderContext) -> ElementBox>, } @@ -23,6 +24,7 @@ impl Clone for State { window_id: self.window_id.clone(), position: self.position.clone(), region_offset: self.region_offset.clone(), + region: self.region.clone(), payload: self.payload.clone(), render: self.render.clone(), } @@ -77,15 +79,20 @@ impl DragAndDrop { ) { let window_id = cx.window_id(); cx.update_global::(|this, cx| { - let region_offset = if let Some(previous_state) = this.currently_dragged.as_ref() { - previous_state.region_offset - } else { - event.region.origin() - event.prev_mouse_position - }; + let (region_offset, region) = + if let Some(previous_state) = this.currently_dragged.as_ref() { + (previous_state.region_offset, previous_state.region) + } else { + ( + event.region.origin() - event.prev_mouse_position, + event.region, + ) + }; this.currently_dragged = Some(State { window_id, region_offset, + region, position: event.position, payload, render: Rc::new(move |payload, cx| { @@ -105,6 +112,7 @@ impl DragAndDrop { window_id, region_offset, position, + region, payload, render, }| { @@ -134,6 +142,9 @@ impl DragAndDrop { }) // Don't block hover events or invalidations .with_hoverable(false) + .constrained() + .with_width(region.width()) + .with_height(region.height()) .boxed(), ) .with_anchor_position(position) diff --git a/crates/project_panel/Cargo.toml b/crates/project_panel/Cargo.toml index 6d566699fa..8704f57c8c 100644 --- a/crates/project_panel/Cargo.toml +++ b/crates/project_panel/Cargo.toml @@ -9,6 +9,7 @@ doctest = false [dependencies] context_menu = { path = "../context_menu" } +drag_and_drop = { path = "../drag_and_drop" } editor = { path = "../editor" } gpui = { path = "../gpui" } menu = { path = "../menu" } diff --git a/crates/project_panel/src/project_panel.rs b/crates/project_panel/src/project_panel.rs index 392fd73e03..0c7796358a 100644 --- a/crates/project_panel/src/project_panel.rs +++ b/crates/project_panel/src/project_panel.rs @@ -1,12 +1,13 @@ use context_menu::{ContextMenu, ContextMenuItem}; +use drag_and_drop::Draggable; use editor::{Cancel, Editor}; use futures::stream::StreamExt; use gpui::{ actions, anyhow::{anyhow, Result}, elements::{ - AnchorCorner, ChildView, ConstrainedBox, Empty, Flex, Label, MouseEventHandler, - ParentElement, ScrollTarget, Stack, Svg, UniformList, UniformListState, + AnchorCorner, ChildView, ConstrainedBox, ContainerStyle, Empty, Flex, Label, + MouseEventHandler, ParentElement, ScrollTarget, Stack, Svg, UniformList, UniformListState, }, geometry::vector::Vector2F, impl_internal_actions, keymap, @@ -25,6 +26,7 @@ use std::{ path::{Path, PathBuf}, sync::Arc, }; +use theme::ProjectPanelEntry; use unicase::UniCase; use workspace::Workspace; @@ -70,9 +72,10 @@ pub enum ClipboardEntry { }, } -#[derive(Debug, PartialEq, Eq)] +#[derive(Debug, PartialEq, Eq, Clone)] struct EntryDetails { filename: String, + path: Arc, depth: usize, kind: EntryKind, is_ignored: bool, @@ -220,6 +223,7 @@ impl ProjectPanel { this.update_visible_entries(None, cx); this }); + cx.subscribe(&project_panel, { let project_panel = project_panel.downgrade(); move |workspace, _, event, cx| match event { @@ -950,14 +954,15 @@ impl ProjectPanel { let end_ix = range.end.min(ix + visible_worktree_entries.len()); if let Some(worktree) = self.project.read(cx).worktree_for_id(*worktree_id, cx) { let snapshot = worktree.read(cx).snapshot(); + let root_name = OsStr::new(snapshot.root_name()); let expanded_entry_ids = self .expanded_dir_ids .get(&snapshot.id()) .map(Vec::as_slice) .unwrap_or(&[]); - let root_name = OsStr::new(snapshot.root_name()); - for entry in &visible_worktree_entries[range.start.saturating_sub(ix)..end_ix - ix] - { + + let entry_range = range.start.saturating_sub(ix)..end_ix - ix; + for entry in &visible_worktree_entries[entry_range] { let mut details = EntryDetails { filename: entry .path @@ -965,6 +970,7 @@ impl ProjectPanel { .unwrap_or(root_name) .to_string_lossy() .to_string(), + path: entry.path.clone(), depth: entry.path.components().count(), kind: entry.kind, is_ignored: entry.is_ignored, @@ -978,12 +984,14 @@ impl ProjectPanel { .clipboard_entry .map_or(false, |e| e.is_cut() && e.entry_id() == entry.id), }; + if let Some(edit_state) = &self.edit_state { let is_edited_entry = if edit_state.is_new_entry { entry.id == NEW_ENTRY_ID } else { entry.id == edit_state.entry_id }; + if is_edited_entry { if let Some(processing_filename) = &edit_state.processing_filename { details.is_processing = true; @@ -1005,6 +1013,63 @@ impl ProjectPanel { } } + fn render_entry_visual_element( + details: EntryDetails, + editor: &ViewHandle, + padding: f32, + row_container_style: ContainerStyle, + style: &ProjectPanelEntry, + cx: &mut RenderContext, + ) -> ElementBox { + let kind = details.kind; + let show_editor = details.is_editing && !details.is_processing; + + Flex::row() + .with_child( + ConstrainedBox::new(if kind == EntryKind::Dir { + if details.is_expanded { + Svg::new("icons/chevron_down_8.svg") + .with_color(style.icon_color) + .boxed() + } else { + Svg::new("icons/chevron_right_8.svg") + .with_color(style.icon_color) + .boxed() + } + } else { + Empty::new().boxed() + }) + .with_max_width(style.icon_size) + .with_max_height(style.icon_size) + .aligned() + .constrained() + .with_width(style.icon_size) + .boxed(), + ) + .with_child(if show_editor { + ChildView::new(editor.clone(), cx) + .contained() + .with_margin_left(style.icon_spacing) + .aligned() + .left() + .flex(1.0, true) + .boxed() + } else { + Label::new(details.filename.clone(), style.text.clone()) + .contained() + .with_margin_left(style.icon_spacing) + .aligned() + .left() + .boxed() + }) + .constrained() + .with_height(style.height) + .contained() + .with_style(row_container_style) + .with_padding_left(padding) + .boxed() + } + fn render_entry( entry_id: ProjectEntryId, details: EntryDetails, @@ -1013,69 +1078,34 @@ impl ProjectPanel { cx: &mut RenderContext, ) -> ElementBox { let kind = details.kind; + let padding = theme.container.padding.left + details.depth as f32 * theme.indent_width; + + let entry_style = if details.is_cut { + &theme.cut_entry + } else if details.is_ignored { + &theme.ignored_entry + } else { + &theme.entry + }; + let show_editor = details.is_editing && !details.is_processing; + MouseEventHandler::::new(entry_id.to_usize(), cx, |state, cx| { - let padding = theme.container.padding.left + details.depth as f32 * theme.indent_width; - - let entry_style = if details.is_cut { - &theme.cut_entry - } else if details.is_ignored { - &theme.ignored_entry - } else { - &theme.entry - }; - let style = entry_style.style_for(state, details.is_selected).clone(); - let row_container_style = if show_editor { theme.filename_editor.container } else { style.container }; - Flex::row() - .with_child( - ConstrainedBox::new(if kind == EntryKind::Dir { - if details.is_expanded { - Svg::new("icons/chevron_down_8.svg") - .with_color(style.icon_color) - .boxed() - } else { - Svg::new("icons/chevron_right_8.svg") - .with_color(style.icon_color) - .boxed() - } - } else { - Empty::new().boxed() - }) - .with_max_width(style.icon_size) - .with_max_height(style.icon_size) - .aligned() - .constrained() - .with_width(style.icon_size) - .boxed(), - ) - .with_child(if show_editor { - ChildView::new(editor.clone(), cx) - .contained() - .with_margin_left(theme.entry.default.icon_spacing) - .aligned() - .left() - .flex(1.0, true) - .boxed() - } else { - Label::new(details.filename, style.text.clone()) - .contained() - .with_margin_left(style.icon_spacing) - .aligned() - .left() - .boxed() - }) - .constrained() - .with_height(theme.entry.default.height) - .contained() - .with_style(row_container_style) - .with_padding_left(padding) - .boxed() + + Self::render_entry_visual_element( + details.clone(), + editor, + padding, + row_container_style, + &style, + cx, + ) }) .on_click(MouseButton::Left, move |e, cx| { if kind == EntryKind::Dir { @@ -1093,6 +1123,22 @@ impl ProjectPanel { position: e.position, }) }) + .as_draggable(details.clone(), { + let editor = editor.clone(); + let row_container_style = theme.dragged_entry.container; + + move |payload, cx: &mut RenderContext| { + let theme = cx.global::().theme.clone(); + Self::render_entry_visual_element( + payload.clone(), + &editor, + padding, + row_container_style, + &theme.project_panel.dragged_entry, + cx, + ) + } + }) .with_cursor_style(CursorStyle::PointingHand) .boxed() } diff --git a/crates/theme/src/theme.rs b/crates/theme/src/theme.rs index cf7aa6e551..8d2a2df18e 100644 --- a/crates/theme/src/theme.rs +++ b/crates/theme/src/theme.rs @@ -326,6 +326,7 @@ pub struct ProjectPanel { #[serde(flatten)] pub container: ContainerStyle, pub entry: Interactive, + pub dragged_entry: ProjectPanelEntry, pub ignored_entry: Interactive, pub cut_entry: Interactive, pub filename_editor: FieldEditor, diff --git a/styles/src/styleTree/projectPanel.ts b/styles/src/styleTree/projectPanel.ts index 729150fbc7..357070e674 100644 --- a/styles/src/styleTree/projectPanel.ts +++ b/styles/src/styleTree/projectPanel.ts @@ -1,14 +1,19 @@ import { ColorScheme } from "../themes/common/colorScheme"; -import { background, foreground, text } from "./components"; +import { withOpacity } from "../utils/color"; +import { background, border, foreground, text } from "./components"; export default function projectPanel(colorScheme: ColorScheme) { let layer = colorScheme.middle; - - let entry = { + + let baseEntry = { height: 24, iconColor: foreground(layer, "variant"), iconSize: 8, iconSpacing: 8, + } + + let entry = { + ...baseEntry, text: text(layer, "mono", "variant", { size: "sm" }), hover: { background: background(layer, "variant", "hovered"), @@ -28,6 +33,12 @@ export default function projectPanel(colorScheme: ColorScheme) { padding: { left: 12, right: 12, top: 6, bottom: 6 }, indentWidth: 8, entry, + draggedEntry: { + ...baseEntry, + text: text(layer, "mono", "on", { size: "sm" }), + background: withOpacity(background(layer, "on"), 0.9), + border: border(layer), + }, ignoredEntry: { ...entry, text: text(layer, "mono", "disabled"), diff --git a/styles/src/styleTree/tabBar.ts b/styles/src/styleTree/tabBar.ts index 2824c43483..fcf78dc73f 100644 --- a/styles/src/styleTree/tabBar.ts +++ b/styles/src/styleTree/tabBar.ts @@ -67,7 +67,7 @@ export default function tabBar(colorScheme: ColorScheme) { const draggedTab = { ...activePaneActiveTab, - background: withOpacity(tab.background, 0.95), + background: withOpacity(tab.background, 0.9), border: undefined as any, shadow: colorScheme.popoverShadow, };