diff --git a/crates/gpui/src/elements/stack.rs b/crates/gpui/src/elements/stack.rs index 92a41749b3..b81749787e 100644 --- a/crates/gpui/src/elements/stack.rs +++ b/crates/gpui/src/elements/stack.rs @@ -24,12 +24,13 @@ impl Element for Stack { fn layout( &mut self, - constraint: SizeConstraint, + mut constraint: SizeConstraint, cx: &mut LayoutContext, ) -> (Vector2F, Self::LayoutState) { let mut size = constraint.min; for child in &mut self.children { size = size.max(child.layout(constraint, cx)); + constraint.min = size; } (size, ()) } diff --git a/crates/workspace/src/dock.rs b/crates/workspace/src/dock.rs index 283b50f4c3..b17a7ea22e 100644 --- a/crates/workspace/src/dock.rs +++ b/crates/workspace/src/dock.rs @@ -9,7 +9,9 @@ use serde::Deserialize; use settings::{DockAnchor, Settings}; use theme::Theme; -use crate::{sidebar::SidebarSide, ItemHandle, Pane, StatusItemView, Workspace}; +use crate::{ + handle_dropped_item, sidebar::SidebarSide, ItemHandle, Pane, StatusItemView, Workspace, +}; #[derive(PartialEq, Clone, Deserialize)] pub struct MoveDock(pub DockAnchor); @@ -376,6 +378,7 @@ impl View for ToggleDockButton { let dock_position = workspace.read(cx).dock.position; let theme = cx.global::().theme.clone(); + let button = MouseEventHandler::::new(0, cx, { let theme = theme.clone(); move |state, _| { @@ -400,7 +403,7 @@ impl View for ToggleDockButton { .on_up(MouseButton::Left, move |event, cx| { let dock_pane = workspace.read(cx.app).dock_pane(); let drop_index = dock_pane.read(cx.app).items_len() + 1; - Pane::handle_dropped_item(event, &dock_pane.downgrade(), drop_index, false, None, cx); + handle_dropped_item(event, &dock_pane.downgrade(), drop_index, false, None, cx); }); if dock_position.is_visible() { diff --git a/crates/workspace/src/pane.rs b/crates/workspace/src/pane.rs index 1d50a846b4..651afce1c6 100644 --- a/crates/workspace/src/pane.rs +++ b/crates/workspace/src/pane.rs @@ -1,17 +1,19 @@ +mod dragged_item_receiver; + use super::{ItemHandle, SplitDirection}; use crate::{ dock::{icon_for_dock_anchor, AnchorDockBottom, AnchorDockRight, ExpandDock, HideDock}, toolbar::Toolbar, - Item, NewFile, NewSearch, NewTerminal, SplitWithItem, WeakItemHandle, Workspace, + Item, NewFile, NewSearch, NewTerminal, WeakItemHandle, Workspace, }; use anyhow::Result; use collections::{HashMap, HashSet, VecDeque}; use context_menu::{ContextMenu, ContextMenuItem}; -use drag_and_drop::{DragAndDrop, Draggable}; +use drag_and_drop::Draggable; +pub use dragged_item_receiver::{dragged_item_receiver, handle_dropped_item}; use futures::StreamExt; use gpui::{ actions, - color::Color, elements::*, geometry::{ rect::RectF, @@ -19,7 +21,6 @@ use gpui::{ }, impl_actions, impl_internal_actions, platform::{CursorStyle, NavigationDirection}, - scene::MouseUp, Action, AnyViewHandle, AnyWeakViewHandle, AppContext, AsyncAppContext, Entity, EventContext, ModelHandle, MouseButton, MutableAppContext, PromptLevel, Quad, RenderContext, Task, View, ViewContext, ViewHandle, WeakViewHandle, @@ -1056,11 +1057,7 @@ impl Pane { fn render_tabs(&mut self, cx: &mut RenderContext) -> impl Element { let theme = cx.global::().theme.clone(); - let filler_index = self.items.len(); - enum Tabs {} - enum Tab {} - enum Filler {} let pane = cx.handle(); let autoscroll = if mem::take(&mut self.autoscroll) { Some(self.active_item_index) @@ -1070,6 +1067,7 @@ impl Pane { let pane_active = self.is_active; + enum Tabs {} let mut row = Flex::row().scrollable::(1, autoscroll, cx); for (ix, (item, detail)) in self .items @@ -1082,7 +1080,8 @@ impl Pane { let tab_active = ix == self.active_item_index; row.add_child({ - MouseEventHandler::::above(ix, cx, { + enum Tab {} + dragged_item_receiver::(ix, ix, true, None, cx, { let item = item.clone(); let pane = pane.clone(); let detail = detail.clone(); @@ -1092,16 +1091,7 @@ impl Pane { move |mouse_state, cx| { let tab_style = theme.workspace.tab_bar.tab_style(pane_active, tab_active); let hovered = mouse_state.hovered(); - Self::render_tab( - &item, - pane, - ix == 0, - detail, - hovered, - Self::tab_overlay_color(hovered, cx), - tab_style, - cx, - ) + Self::render_tab(&item, pane, ix == 0, detail, hovered, tab_style, cx) } }) .with_cursor_style(if pane_active && tab_active { @@ -1123,10 +1113,6 @@ impl Pane { }) } }) - .on_up(MouseButton::Left, { - let pane = pane.clone(); - move |event, cx| Pane::handle_dropped_item(event, &pane, ix, true, None, cx) - }) .as_draggable( DraggedItem { item, @@ -1144,7 +1130,6 @@ impl Pane { false, detail, false, - None, &tab_style, cx, ) @@ -1157,22 +1142,16 @@ impl Pane { // Use the inactive tab style along with the current pane's active status to decide how to render // the filler + let filler_index = self.items.len(); let filler_style = theme.workspace.tab_bar.tab_style(pane_active, false); + enum Filler {} row.add_child( - MouseEventHandler::::new(0, cx, |mouse_state, cx| { - let mut filler = Empty::new() + dragged_item_receiver::(0, filler_index, true, None, cx, |_, _| { + Empty::new() .contained() .with_style(filler_style.container) - .with_border(filler_style.container.border); - - if let Some(overlay) = Self::tab_overlay_color(mouse_state.hovered(), cx) { - filler = filler.with_overlay_color(overlay); - } - - filler.boxed() - }) - .on_up(MouseButton::Left, move |event, cx| { - Pane::handle_dropped_item(event, &pane, filler_index, true, None, cx) + .with_border(filler_style.container.border) + .boxed() }) .flex(1., true) .named("filler"), @@ -1224,7 +1203,6 @@ impl Pane { first: bool, detail: Option, hovered: bool, - overlay: Option, tab_style: &theme::Tab, cx: &mut RenderContext, ) -> ElementBox { @@ -1234,7 +1212,7 @@ impl Pane { container.border.left = false; } - let mut tab = Flex::row() + Flex::row() .with_child( Align::new({ let diameter = 7.0; @@ -1312,13 +1290,10 @@ impl Pane { .boxed(), ) .contained() - .with_style(container); - - if let Some(overlay) = overlay { - tab = tab.with_overlay_color(overlay); - } - - tab.constrained().with_height(tab_style.height).boxed() + .with_style(container) + .constrained() + .with_height(tab_style.height) + .boxed() } fn render_tab_bar_buttons( @@ -1356,79 +1331,6 @@ impl Pane { .flex(1., false) .boxed() } - - pub fn handle_dropped_item( - event: MouseUp, - pane: &WeakViewHandle, - index: usize, - allow_same_pane: bool, - split_margin: Option, - cx: &mut EventContext, - ) { - if let Some((_, dragged_item)) = cx - .global::>() - .currently_dragged::(cx.window_id) - { - if let Some(split_direction) = split_margin - .and_then(|margin| Self::drop_split_direction(event.position, event.region, margin)) - { - cx.dispatch_action(SplitWithItem { - from: dragged_item.pane.clone(), - item_id_to_move: dragged_item.item.id(), - pane_to_split: pane.clone(), - split_direction, - }); - } else if pane != &dragged_item.pane || allow_same_pane { - // If no split margin or not close enough to the edge, just move the item - cx.dispatch_action(MoveItem { - item_id: dragged_item.item.id(), - from: dragged_item.pane.clone(), - to: pane.clone(), - destination_index: index, - }) - } - } else { - cx.propagate_event(); - } - } - - fn drop_split_direction( - position: Vector2F, - region: RectF, - split_margin: f32, - ) -> Option { - let mut min_direction = None; - let mut min_distance = split_margin; - for direction in SplitDirection::all() { - let edge_distance = - (direction.edge(region) - direction.axis().component(position)).abs(); - - if edge_distance < min_distance { - min_direction = Some(direction); - min_distance = edge_distance; - } - } - - min_direction - } - - fn tab_overlay_color(hovered: bool, cx: &mut RenderContext) -> Option { - if hovered - && cx - .global::>() - .currently_dragged::(cx.window_id()) - .is_some() - { - Some( - cx.global::() - .theme - .workspace - .drop_target_overlay_color, - ) - } else { - None - } - } } impl Entity for Pane { @@ -1449,8 +1351,6 @@ impl View for Pane { .with_child( MouseEventHandler::::new(0, cx, |_, cx| { if let Some(active_item) = self.active_item() { - enum PaneContentTabDropTarget {} - Flex::column() .with_child({ let mut tab_row = Flex::row() @@ -1471,78 +1371,29 @@ impl View for Pane { .named("tab bar") }) .with_child({ - let drop_index = self.active_item_index + 1; - MouseEventHandler::::above( + enum PaneContentTabDropTarget {} + dragged_item_receiver::( 0, + self.active_item_index + 1, + false, + Some(100.), cx, - |state, cx| { - let overlay_color = Self::tab_overlay_color(true, cx); - // Hovered will cause a render when the mouse enters regardless - // of if mouse position was accessed before - let hovered = state.hovered(); - let drag_position = cx - .global::>() - .currently_dragged::(cx.window_id()) - .filter(|_| hovered) - .map(|_| state.mouse_position()); - - Stack::new() - .with_child( - Flex::column() - .with_child( - ChildView::new(&self.toolbar, cx) - .expanded() - .boxed(), - ) - .with_child( - ChildView::new(active_item, cx) - .flex(1., true) - .boxed(), - ) - .boxed(), - ) - .with_children(drag_position.map(|drag_position| { - Canvas::new(move |region, _, cx| { - if region.contains_point(drag_position) { - let overlay_region = if let Some( - split_direction, - ) = - Self::drop_split_direction( - drag_position, - region, - 100., /* Replace with theme value */ - ) { - split_direction.along_edge(region, 100.) - } else { - region - }; - - cx.scene.push_quad(Quad { - bounds: overlay_region, - background: overlay_color, - border: Default::default(), - corner_radius: 0., - }); - } - }) + { + let toolbar = self.toolbar.clone(); + move |_, cx| { + Flex::column() + .with_child( + ChildView::new(&toolbar, cx).expanded().boxed(), + ) + .with_child( + ChildView::new(active_item, cx) + .flex(1., true) + .boxed(), + ) .boxed() - })) - .boxed() + } }, ) - .on_up(MouseButton::Left, { - let pane = cx.handle(); - move |event, cx| { - Pane::handle_dropped_item( - event, - &pane, - drop_index, - false, - Some(100.), /* Use theme value */ - cx, - ) - } - }) .flex(1., true) .boxed() }) @@ -1551,7 +1402,7 @@ impl View for Pane { enum EmptyPane {} let theme = cx.global::().theme.clone(); - MouseEventHandler::::new(0, cx, |_, _| { + dragged_item_receiver::(0, 0, false, None, cx, |_, _| { Empty::new() .contained() .with_background_color(theme.workspace.background) @@ -1560,12 +1411,6 @@ impl View for Pane { .on_down(MouseButton::Left, |_, cx| { cx.focus_parent_view(); }) - .on_up(MouseButton::Left, { - let pane = this.clone(); - move |event, cx| { - Pane::handle_dropped_item(event, &pane, 0, true, None, cx) - } - }) .boxed() } }) diff --git a/crates/workspace/src/pane/dragged_item_receiver.rs b/crates/workspace/src/pane/dragged_item_receiver.rs new file mode 100644 index 0000000000..afcb6b0fb9 --- /dev/null +++ b/crates/workspace/src/pane/dragged_item_receiver.rs @@ -0,0 +1,133 @@ +use drag_and_drop::DragAndDrop; +use gpui::{ + color::Color, + elements::{Canvas, MouseEventHandler, ParentElement, Stack}, + geometry::{rect::RectF, vector::Vector2F}, + scene::MouseUp, + AppContext, Element, ElementBox, EventContext, MouseButton, MouseState, Quad, RenderContext, + WeakViewHandle, +}; +use settings::Settings; + +use crate::{MoveItem, Pane, SplitDirection, SplitWithItem, Workspace}; + +use super::DraggedItem; + +pub fn dragged_item_receiver( + region_id: usize, + drop_index: usize, + allow_same_pane: bool, + split_margin: Option, + cx: &mut RenderContext, + render_child: F, +) -> MouseEventHandler +where + Tag: 'static, + F: FnOnce(&mut MouseState, &mut RenderContext) -> ElementBox, +{ + MouseEventHandler::::above(region_id, cx, |state, cx| { + // Observing hovered will cause a render when the mouse enters regardless + // of if mouse position was accessed before + let hovered = state.hovered(); + let drag_position = cx + .global::>() + .currently_dragged::(cx.window_id()) + .filter(|_| hovered) + .map(|_| state.mouse_position()); + + Stack::new() + .with_child(render_child(state, cx)) + .with_children(drag_position.map(|drag_position| { + Canvas::new(move |bounds, _, cx| { + if bounds.contains_point(drag_position) { + let overlay_region = split_margin + .and_then(|split_margin| { + drop_split_direction(drag_position, bounds, split_margin) + .map(|dir| (dir, split_margin)) + }) + .map(|(dir, margin)| dir.along_edge(bounds, margin)) + .unwrap_or(bounds); + + cx.paint_stacking_context(None, |cx| { + cx.scene.push_quad(Quad { + bounds: overlay_region, + background: Some(overlay_color(cx)), + border: Default::default(), + corner_radius: 0., + }); + }); + } + }) + .boxed() + })) + .boxed() + }) + .on_up(MouseButton::Left, { + let pane = cx.handle(); + move |event, cx| { + handle_dropped_item(event, &pane, drop_index, allow_same_pane, split_margin, cx); + cx.notify(); + } + }) +} + +pub fn handle_dropped_item( + event: MouseUp, + pane: &WeakViewHandle, + index: usize, + allow_same_pane: bool, + split_margin: Option, + cx: &mut EventContext, +) { + if let Some((_, dragged_item)) = cx + .global::>() + .currently_dragged::(cx.window_id) + { + if let Some(split_direction) = split_margin + .and_then(|margin| drop_split_direction(event.position, event.region, margin)) + { + cx.dispatch_action(SplitWithItem { + from: dragged_item.pane.clone(), + item_id_to_move: dragged_item.item.id(), + pane_to_split: pane.clone(), + split_direction, + }); + } else if pane != &dragged_item.pane || allow_same_pane { + // If no split margin or not close enough to the edge, just move the item + cx.dispatch_action(MoveItem { + item_id: dragged_item.item.id(), + from: dragged_item.pane.clone(), + to: pane.clone(), + destination_index: index, + }) + } + } else { + cx.propagate_event(); + } +} + +fn drop_split_direction( + position: Vector2F, + region: RectF, + split_margin: f32, +) -> Option { + let mut min_direction = None; + let mut min_distance = split_margin; + for direction in SplitDirection::all() { + let edge_distance = (direction.edge(region) - direction.axis().component(position)).abs(); + + if edge_distance < min_distance { + min_direction = Some(direction); + min_distance = edge_distance; + } + } + + min_direction +} + +fn overlay_color(cx: &AppContext) -> Color { + cx.global::() + .theme + .workspace + .drop_target_overlay_color +}