diff --git a/crates/gpui2/src/elements/div.rs b/crates/gpui2/src/elements/div.rs index aa77480944b77ebfa1a3d95b45f3e188ea289dea..fb730a0700dc870b7b5a74953e595848e1396291 100644 --- a/crates/gpui2/src/elements/div.rs +++ b/crates/gpui2/src/elements/div.rs @@ -33,6 +33,7 @@ pub struct GroupStyle { pub struct DragMoveEvent { pub event: MouseMoveEvent, + pub bounds: Bounds, drag: PhantomData, } @@ -208,7 +209,7 @@ pub trait InteractiveElement: Sized { self } - fn on_drag_move( + fn on_drag_move( mut self, listener: impl Fn(&DragMoveEvent, &mut WindowContext) + 'static, ) -> Self @@ -223,11 +224,12 @@ pub trait InteractiveElement: Sized { if cx .active_drag .as_ref() - .is_some_and(|drag| (*drag.value).type_id() == TypeId::of::()) + .is_some_and(|drag| drag.value.as_ref().type_id() == TypeId::of::()) { (listener)( &DragMoveEvent { event: event.clone(), + bounds: bounds.bounds, drag: PhantomData, }, cx, diff --git a/crates/workspace2/src/pane.rs b/crates/workspace2/src/pane.rs index 4c436daada47625f8db784f11978129b3b4082c6..712e447905b4d3062ed686a695ddc01529557213 100644 --- a/crates/workspace2/src/pane.rs +++ b/crates/workspace2/src/pane.rs @@ -8,9 +8,10 @@ use anyhow::Result; use collections::{HashMap, HashSet, VecDeque}; use gpui::{ actions, impl_actions, overlay, prelude::*, Action, AnchorCorner, AppContext, - AsyncWindowContext, DismissEvent, Div, EntityId, EventEmitter, FocusHandle, Focusable, - FocusableView, Model, MouseButton, NavigationDirection, Pixels, Point, PromptLevel, Render, - ScrollHandle, Subscription, Task, View, ViewContext, VisualContext, WeakView, WindowContext, + AsyncWindowContext, DismissEvent, Div, DragMoveEvent, EntityId, EventEmitter, FocusHandle, + Focusable, FocusableView, Model, MouseButton, NavigationDirection, Pixels, Point, PromptLevel, + Render, ScrollHandle, Subscription, Task, View, ViewContext, VisualContext, WeakView, + WindowContext, }; use parking_lot::Mutex; use project::{Project, ProjectEntryId, ProjectPath}; @@ -28,8 +29,8 @@ use std::{ use theme::ThemeSettings; use ui::{ - h_stack, prelude::*, right_click_menu, ButtonSize, Color, Icon, IconButton, IconSize, - Indicator, Label, Tab, TabBar, TabPosition, Tooltip, + prelude::*, right_click_menu, ButtonSize, Color, Icon, IconButton, IconSize, Indicator, Label, + Tab, TabBar, TabPosition, Tooltip, }; use ui::{v_stack, ContextMenu}; use util::{maybe, truncate_and_remove_front, ResultExt}; @@ -179,6 +180,7 @@ pub struct Pane { // tab_context_menu: ViewHandle, workspace: WeakView, project: Model, + drag_split_direction: Option, // can_drop: Rc, &WindowContext) -> bool>, can_split: bool, // render_tab_bar_buttons: Rc) -> AnyElement>, @@ -361,6 +363,7 @@ impl Pane { new_item_menu: None, split_item_menu: None, tab_bar_scroll_handle: ScrollHandle::new(), + drag_split_direction: None, // tab_bar_context_menu: TabBarContextMenu { // kind: TabBarContextMenuKind::New, // handle: context_menu, @@ -1503,9 +1506,11 @@ impl Pane { .drag_over::(|tab| tab.bg(cx.theme().colors().tab_active_background)) .drag_over::(|tab| tab.bg(gpui::red())) .on_drop(cx.listener(move |this, dragged_tab: &DraggedTab, cx| { + this.drag_split_direction = None; this.handle_tab_drop(dragged_tab, ix, cx) })) .on_drop(cx.listener(move |this, entry_id: &ProjectEntryId, cx| { + this.drag_split_direction = None; this.handle_project_entry_drop(entry_id, cx) })) .when_some(item.tab_tooltip_text(cx), |tab, text| { @@ -1655,9 +1660,11 @@ impl Pane { }) .drag_over::(|bar| bar.bg(gpui::red())) .on_drop(cx.listener(move |this, dragged_tab: &DraggedTab, cx| { + this.drag_split_direction = None; this.handle_tab_drop(dragged_tab, this.items.len(), cx) })) .on_drop(cx.listener(move |this, entry_id: &ProjectEntryId, cx| { + this.drag_split_direction = None; this.handle_project_entry_drop(entry_id, cx) })), ) @@ -1719,18 +1726,42 @@ impl Pane { self.zoomed } + fn handle_drag_move(&mut self, event: &DragMoveEvent, cx: &mut ViewContext) { + let edge_width = cx.rem_size() * 8; + let cursor = event.event.position; + let direction = if cursor.x < event.bounds.left() + edge_width { + Some(SplitDirection::Left) + } else if cursor.x > event.bounds.right() - edge_width { + Some(SplitDirection::Right) + } else if cursor.y < event.bounds.top() + edge_width { + Some(SplitDirection::Up) + } else if cursor.y > event.bounds.bottom() - edge_width { + Some(SplitDirection::Down) + } else { + None + }; + if direction != self.drag_split_direction { + self.drag_split_direction = direction; + cx.notify(); + } + } + fn handle_tab_drop( &mut self, dragged_tab: &DraggedTab, ix: usize, cx: &mut ViewContext<'_, Pane>, ) { + let mut to_pane = cx.view().clone(); + let split_direction = self.drag_split_direction; let item_id = dragged_tab.item_id; let from_pane = dragged_tab.pane.clone(); - let to_pane = cx.view().clone(); self.workspace .update(cx, |_, cx| { cx.defer(move |workspace, cx| { + if let Some(split_direction) = split_direction { + to_pane = workspace.split_pane(to_pane, split_direction, cx); + } workspace.move_item(from_pane, to_pane, item_id, ix, cx); }); }) @@ -1742,52 +1773,9 @@ impl Pane { project_entry_id: &ProjectEntryId, cx: &mut ViewContext<'_, Pane>, ) { - let to_pane = cx.view().downgrade(); - let project_entry_id = *project_entry_id; - self.workspace - .update(cx, |_, cx| { - cx.defer(move |workspace, cx| { - if let Some(path) = workspace - .project() - .read(cx) - .path_for_entry(project_entry_id, cx) - { - workspace - .open_path(path, Some(to_pane), true, cx) - .detach_and_log_err(cx); - } - }); - }) - .log_err(); - } - - fn handle_split_tab_drop( - &mut self, - dragged_tab: &DraggedTab, - split_direction: SplitDirection, - cx: &mut ViewContext<'_, Pane>, - ) { - let item_id = dragged_tab.item_id; - let from_pane = dragged_tab.pane.clone(); - let to_pane = cx.view().clone(); - self.workspace - .update(cx, |_, cx| { - cx.defer(move |workspace, cx| { - let pane = workspace.split_pane(to_pane, split_direction, cx); - workspace.move_item(from_pane, pane, item_id, 0, cx); - }); - }) - .log_err(); - } - - fn handle_split_project_entry_drop( - &mut self, - project_entry_id: &ProjectEntryId, - split_direction: SplitDirection, - cx: &mut ViewContext<'_, Pane>, - ) { + let mut to_pane = cx.view().clone(); + let split_direction = self.drag_split_direction; let project_entry_id = *project_entry_id; - let current_pane = cx.view().clone(); self.workspace .update(cx, |_, cx| { cx.defer(move |workspace, cx| { @@ -1796,9 +1784,11 @@ impl Pane { .read(cx) .path_for_entry(project_entry_id, cx) { - let pane = workspace.split_pane(current_pane, split_direction, cx); + if let Some(split_direction) = split_direction { + to_pane = workspace.split_pane(to_pane, split_direction, cx); + } workspace - .open_path(path, Some(pane.downgrade()), true, cx) + .open_path(path, Some(to_pane.downgrade()), true, cx) .detach_and_log_err(cx); } }); @@ -1817,6 +1807,9 @@ impl Render for Pane { type Element = Focusable
; fn render(&mut self, cx: &mut ViewContext) -> Self::Element { + let mut drag_target_color = cx.theme().colors().text; + drag_target_color.a = 0.5; + v_stack() .key_context("Pane") .track_focus(&self.focus_handle) @@ -1894,71 +1887,54 @@ impl Render for Pane { }), ) .child(self.render_tab_bar(cx)) - .child(self.toolbar.clone()) - .child(if let Some(item) = self.active_item() { - let mut drag_target_color = cx.theme().colors().text; - drag_target_color.a = 0.5; - - div() - .flex() + .child( + // main content + v_stack() .flex_1() .relative() - .child(item.to_any()) + .group("") + .on_drag_move::(cx.listener(Self::handle_drag_move)) + .on_drag_move::(cx.listener(Self::handle_drag_move)) + .child(self.toolbar.clone()) + .map(|div| { + if let Some(item) = self.active_item() { + div.child(item.to_any()) + } else { + div.items_center().size_full().justify_center().child( + Label::new("Open a file or project to get started.") + .color(Color::Muted), + ) + } + }) .child( + // drag target div() + .invisible() .absolute() - .full() - .z_index(1) - .drag_over::(|style| style.bg(drag_target_color)) - .drag_over::(|style| style.bg(gpui::red())) - .on_drop(cx.listener(move |this, dragged_tab: &DraggedTab, cx| { + .bg(drag_target_color) + .group_drag_over::("", |style| style.visible()) + .group_drag_over::("", |style| style.visible()) + .on_drop(cx.listener(move |this, dragged_tab, cx| { this.handle_tab_drop(dragged_tab, this.active_item_index(), cx) })) - .on_drop(cx.listener(move |this, entry_id: &ProjectEntryId, cx| { + .on_drop(cx.listener(move |this, entry_id, cx| { this.handle_project_entry_drop(entry_id, cx) - })), - ) - .children( - [ - (SplitDirection::Up, 2), - (SplitDirection::Down, 2), - (SplitDirection::Left, 3), - (SplitDirection::Right, 3), - ] - .into_iter() - .map(|(direction, z_index)| { - let div = div() - .absolute() - .z_index(z_index) - .invisible() - .bg(drag_target_color) - .drag_over::(|style| style.visible()) - .drag_over::(|style| style.visible()) - .on_drop(cx.listener(move |this, dragged_tab: &DraggedTab, cx| { - this.handle_split_tab_drop(dragged_tab, direction, cx) - })) - .on_drop(cx.listener( - move |this, entry_id: &ProjectEntryId, cx| { - this.handle_split_project_entry_drop( - entry_id, direction, cx, - ) - }, - )); - match direction { - SplitDirection::Up => div.top_0().left_0().right_0().h_32(), - SplitDirection::Down => div.left_0().bottom_0().right_0().h_32(), - SplitDirection::Left => div.top_0().left_0().bottom_0().w_32(), - SplitDirection::Right => div.top_0().bottom_0().right_0().w_32(), - } - }), - ) - } else { - h_stack() - .items_center() - .size_full() - .justify_center() - .child(Label::new("Open a file or project to get started.").color(Color::Muted)) - }) + })) + .map(|div| match self.drag_split_direction { + None => div.full(), + Some(SplitDirection::Up) => div.top_0().left_0().right_0().h_32(), + Some(SplitDirection::Down) => { + div.left_0().bottom_0().right_0().h_32() + } + Some(SplitDirection::Left) => { + div.top_0().left_0().bottom_0().w_32() + } + Some(SplitDirection::Right) => { + div.top_0().bottom_0().right_0().w_32() + } + }), + ), + ) .on_mouse_down( MouseButton::Navigate(NavigationDirection::Back), cx.listener(|pane, _, cx| {