mirror of
https://github.com/zed-industries/zed.git
synced 2024-12-25 01:34:02 +00:00
Merge pull request #1791 from zed-industries/drag-tabs-more-places
Drag tabs more places
This commit is contained in:
commit
8bd9577318
21 changed files with 599 additions and 496 deletions
1
Cargo.lock
generated
1
Cargo.lock
generated
|
@ -1736,6 +1736,7 @@ dependencies = [
|
|||
"collections",
|
||||
"context_menu",
|
||||
"ctor",
|
||||
"drag_and_drop",
|
||||
"env_logger",
|
||||
"futures 0.3.24",
|
||||
"fuzzy",
|
||||
|
|
|
@ -431,6 +431,12 @@
|
|||
"shift-escape": "dock::HideDock"
|
||||
}
|
||||
},
|
||||
{
|
||||
"context": "Pane",
|
||||
"bindings": {
|
||||
"cmd-escape": "dock::MoveActiveItemToDock"
|
||||
}
|
||||
},
|
||||
{
|
||||
"context": "ProjectPanel",
|
||||
"bindings": {
|
||||
|
|
|
@ -125,7 +125,7 @@ impl<V: View> DragAndDrop<V> {
|
|||
cx.defer(|cx| {
|
||||
cx.update_global::<Self, _, _>(|this, cx| this.stop_dragging(cx));
|
||||
});
|
||||
cx.propogate_event();
|
||||
cx.propagate_event();
|
||||
})
|
||||
.on_up_out(MouseButton::Left, |_, cx| {
|
||||
cx.defer(|cx| {
|
||||
|
|
|
@ -20,6 +20,7 @@ test-support = [
|
|||
]
|
||||
|
||||
[dependencies]
|
||||
drag_and_drop = { path = "../drag_and_drop" }
|
||||
text = { path = "../text" }
|
||||
clock = { path = "../clock" }
|
||||
collections = { path = "../collections" }
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
use std::{cell::RefCell, rc::Rc, time::Instant};
|
||||
|
||||
use drag_and_drop::DragAndDrop;
|
||||
use futures::StreamExt;
|
||||
use indoc::indoc;
|
||||
use unindent::Unindent;
|
||||
|
@ -472,6 +473,7 @@ fn test_clone(cx: &mut gpui::MutableAppContext) {
|
|||
#[gpui::test]
|
||||
fn test_navigation_history(cx: &mut gpui::MutableAppContext) {
|
||||
cx.set_global(Settings::test(cx));
|
||||
cx.set_global(DragAndDrop::<Workspace>::default());
|
||||
use workspace::Item;
|
||||
let (_, pane) = cx.add_window(Default::default(), |cx| Pane::new(None, cx));
|
||||
let buffer = MultiBuffer::build_simple(&sample_text(300, 5, 'a'), cx);
|
||||
|
|
|
@ -137,7 +137,7 @@ impl EditorElement {
|
|||
gutter_bounds,
|
||||
cx,
|
||||
) {
|
||||
cx.propogate_event();
|
||||
cx.propagate_event();
|
||||
}
|
||||
}
|
||||
})
|
||||
|
@ -150,7 +150,7 @@ impl EditorElement {
|
|||
text_bounds,
|
||||
cx,
|
||||
) {
|
||||
cx.propogate_event();
|
||||
cx.propagate_event();
|
||||
}
|
||||
}
|
||||
})
|
||||
|
@ -167,7 +167,7 @@ impl EditorElement {
|
|||
text_bounds,
|
||||
cx,
|
||||
) {
|
||||
cx.propogate_event()
|
||||
cx.propagate_event()
|
||||
}
|
||||
}
|
||||
})
|
||||
|
@ -182,7 +182,7 @@ impl EditorElement {
|
|||
text_bounds,
|
||||
cx,
|
||||
) {
|
||||
cx.propogate_event()
|
||||
cx.propagate_event()
|
||||
}
|
||||
}
|
||||
})
|
||||
|
@ -190,7 +190,7 @@ impl EditorElement {
|
|||
let position_map = position_map.clone();
|
||||
move |e, cx| {
|
||||
if !Self::mouse_moved(e.platform_event, &position_map, text_bounds, cx) {
|
||||
cx.propogate_event()
|
||||
cx.propagate_event()
|
||||
}
|
||||
}
|
||||
})
|
||||
|
@ -199,7 +199,7 @@ impl EditorElement {
|
|||
move |e, cx| {
|
||||
if !Self::scroll(e.position, e.delta, e.precise, &position_map, bounds, cx)
|
||||
{
|
||||
cx.propogate_event()
|
||||
cx.propagate_event()
|
||||
}
|
||||
}
|
||||
}),
|
||||
|
|
|
@ -277,7 +277,7 @@ impl Element for Flex {
|
|||
|
||||
cx.notify();
|
||||
} else {
|
||||
cx.propogate_event();
|
||||
cx.propagate_event();
|
||||
}
|
||||
}
|
||||
})
|
||||
|
|
|
@ -23,10 +23,13 @@ pub struct MouseEventHandler<Tag: 'static> {
|
|||
hoverable: bool,
|
||||
notify_on_hover: bool,
|
||||
notify_on_click: bool,
|
||||
above: bool,
|
||||
padding: Padding,
|
||||
_tag: PhantomData<Tag>,
|
||||
}
|
||||
|
||||
/// Element which provides a render_child callback with a MouseState and paints a mouse
|
||||
/// region under (or above) it for easy mouse event handling.
|
||||
impl<Tag> MouseEventHandler<Tag> {
|
||||
pub fn new<V, F>(region_id: usize, cx: &mut RenderContext<V>, render_child: F) -> Self
|
||||
where
|
||||
|
@ -45,11 +48,25 @@ impl<Tag> MouseEventHandler<Tag> {
|
|||
notify_on_hover,
|
||||
notify_on_click,
|
||||
hoverable: true,
|
||||
above: false,
|
||||
padding: Default::default(),
|
||||
_tag: PhantomData,
|
||||
}
|
||||
}
|
||||
|
||||
/// Modifies the MouseEventHandler to render the MouseRegion above the child element. Useful
|
||||
/// for drag and drop handling and similar events which should be captured before the child
|
||||
/// gets the opportunity
|
||||
pub fn above<V, F>(region_id: usize, cx: &mut RenderContext<V>, render_child: F) -> Self
|
||||
where
|
||||
V: View,
|
||||
F: FnOnce(&mut MouseState, &mut RenderContext<V>) -> ElementBox,
|
||||
{
|
||||
let mut handler = Self::new(region_id, cx, render_child);
|
||||
handler.above = true;
|
||||
handler
|
||||
}
|
||||
|
||||
pub fn with_cursor_style(mut self, cursor: CursorStyle) -> Self {
|
||||
self.cursor_style = Some(cursor);
|
||||
self
|
||||
|
@ -149,6 +166,29 @@ impl<Tag> MouseEventHandler<Tag> {
|
|||
)
|
||||
.round_out()
|
||||
}
|
||||
|
||||
fn paint_regions(&self, bounds: RectF, visible_bounds: RectF, cx: &mut PaintContext) {
|
||||
let visible_bounds = visible_bounds.intersection(bounds).unwrap_or_default();
|
||||
let hit_bounds = self.hit_bounds(visible_bounds);
|
||||
|
||||
if let Some(style) = self.cursor_style {
|
||||
cx.scene.push_cursor_region(CursorRegion {
|
||||
bounds: hit_bounds,
|
||||
style,
|
||||
});
|
||||
}
|
||||
cx.scene.push_mouse_region(
|
||||
MouseRegion::from_handlers::<Tag>(
|
||||
cx.current_view_id(),
|
||||
self.region_id,
|
||||
hit_bounds,
|
||||
self.handlers.clone(),
|
||||
)
|
||||
.with_hoverable(self.hoverable)
|
||||
.with_notify_on_hover(self.notify_on_hover)
|
||||
.with_notify_on_click(self.notify_on_click),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
impl<Tag> Element for MouseEventHandler<Tag> {
|
||||
|
@ -170,28 +210,16 @@ impl<Tag> Element for MouseEventHandler<Tag> {
|
|||
_: &mut Self::LayoutState,
|
||||
cx: &mut PaintContext,
|
||||
) -> Self::PaintState {
|
||||
let visible_bounds = visible_bounds.intersection(bounds).unwrap_or_default();
|
||||
let hit_bounds = self.hit_bounds(visible_bounds);
|
||||
if let Some(style) = self.cursor_style {
|
||||
cx.scene.push_cursor_region(CursorRegion {
|
||||
bounds: hit_bounds,
|
||||
style,
|
||||
if self.above {
|
||||
self.child.paint(bounds.origin(), visible_bounds, cx);
|
||||
|
||||
cx.paint_layer(None, |cx| {
|
||||
self.paint_regions(bounds, visible_bounds, cx);
|
||||
});
|
||||
} else {
|
||||
self.paint_regions(bounds, visible_bounds, cx);
|
||||
self.child.paint(bounds.origin(), visible_bounds, cx);
|
||||
}
|
||||
|
||||
cx.scene.push_mouse_region(
|
||||
MouseRegion::from_handlers::<Tag>(
|
||||
cx.current_view_id(),
|
||||
self.region_id,
|
||||
hit_bounds,
|
||||
self.handlers.clone(),
|
||||
)
|
||||
.with_hoverable(self.hoverable)
|
||||
.with_notify_on_hover(self.notify_on_hover)
|
||||
.with_notify_on_click(self.notify_on_click),
|
||||
);
|
||||
|
||||
self.child.paint(bounds.origin(), visible_bounds, cx);
|
||||
}
|
||||
|
||||
fn rect_for_text_range(
|
||||
|
|
|
@ -204,25 +204,24 @@ impl Element for Overlay {
|
|||
OverlayFitMode::None => {}
|
||||
}
|
||||
|
||||
cx.scene.push_stacking_context(None);
|
||||
cx.paint_stacking_context(None, |cx| {
|
||||
if self.hoverable {
|
||||
enum OverlayHoverCapture {}
|
||||
// Block hovers in lower stacking contexts
|
||||
cx.scene
|
||||
.push_mouse_region(MouseRegion::new::<OverlayHoverCapture>(
|
||||
cx.current_view_id(),
|
||||
cx.current_view_id(),
|
||||
bounds,
|
||||
));
|
||||
}
|
||||
|
||||
if self.hoverable {
|
||||
enum OverlayHoverCapture {}
|
||||
// Block hovers in lower stacking contexts
|
||||
cx.scene
|
||||
.push_mouse_region(MouseRegion::new::<OverlayHoverCapture>(
|
||||
cx.current_view_id(),
|
||||
cx.current_view_id(),
|
||||
bounds,
|
||||
));
|
||||
}
|
||||
|
||||
self.child.paint(
|
||||
bounds.origin(),
|
||||
RectF::new(Vector2F::zero(), cx.window_size),
|
||||
cx,
|
||||
);
|
||||
cx.scene.pop_stacking_context();
|
||||
self.child.paint(
|
||||
bounds.origin(),
|
||||
RectF::new(Vector2F::zero(), cx.window_size),
|
||||
cx,
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
fn rect_for_text_range(
|
||||
|
|
|
@ -7,6 +7,8 @@ use crate::{
|
|||
DebugContext, Element, ElementBox, LayoutContext, PaintContext, SizeConstraint,
|
||||
};
|
||||
|
||||
/// Element which renders it's children in a stack on top of each other.
|
||||
/// The first child determines the size of the others.
|
||||
#[derive(Default)]
|
||||
pub struct Stack {
|
||||
children: Vec<ElementBox>,
|
||||
|
@ -24,13 +26,20 @@ 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));
|
||||
let mut children = self.children.iter_mut();
|
||||
if let Some(bottom_child) = children.next() {
|
||||
size = bottom_child.layout(constraint, cx);
|
||||
constraint = SizeConstraint::strict(size);
|
||||
}
|
||||
|
||||
for child in children {
|
||||
child.layout(constraint, cx);
|
||||
}
|
||||
|
||||
(size, ())
|
||||
}
|
||||
|
||||
|
@ -42,9 +51,9 @@ impl Element for Stack {
|
|||
cx: &mut PaintContext,
|
||||
) -> Self::PaintState {
|
||||
for child in &mut self.children {
|
||||
cx.scene.push_layer(None);
|
||||
child.paint(bounds.origin(), visible_bounds, cx);
|
||||
cx.scene.pop_layer();
|
||||
cx.paint_layer(None, |cx| {
|
||||
child.paint(bounds.origin(), visible_bounds, cx);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -304,7 +304,7 @@ impl Element for UniformList {
|
|||
},
|
||||
cx| {
|
||||
if !Self::scroll(state.clone(), position, delta, precise, scroll_max, cx) {
|
||||
cx.propogate_event();
|
||||
cx.propagate_event();
|
||||
}
|
||||
}
|
||||
}),
|
||||
|
|
|
@ -402,10 +402,10 @@ impl Presenter {
|
|||
MouseEvent::Down(_) | MouseEvent::Up(_) => {
|
||||
for (region, _) in self.mouse_regions.iter().rev() {
|
||||
if region.bounds.contains_point(self.mouse_position) {
|
||||
valid_regions.push(region.clone());
|
||||
if region.notify_on_click {
|
||||
notified_views.insert(region.id().view_id());
|
||||
}
|
||||
valid_regions.push(region.clone());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -485,9 +485,7 @@ impl Presenter {
|
|||
event_cx.handled = true;
|
||||
event_cx.with_current_view(valid_region.id().view_id(), {
|
||||
let region_event = mouse_event.clone();
|
||||
|cx| {
|
||||
callback(region_event, cx);
|
||||
}
|
||||
|cx| callback(region_event, cx)
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -707,6 +705,16 @@ impl<'a> PaintContext<'a> {
|
|||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn paint_stacking_context<F>(&mut self, clip_bounds: Option<RectF>, f: F)
|
||||
where
|
||||
F: FnOnce(&mut Self),
|
||||
{
|
||||
self.scene.push_stacking_context(clip_bounds);
|
||||
f(self);
|
||||
self.scene.pop_stacking_context();
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn paint_layer<F>(&mut self, clip_bounds: Option<RectF>, f: F)
|
||||
where
|
||||
|
@ -794,7 +802,7 @@ impl<'a> EventContext<'a> {
|
|||
self.notify_count
|
||||
}
|
||||
|
||||
pub fn propogate_event(&mut self) {
|
||||
pub fn propagate_event(&mut self) {
|
||||
self.handled = false;
|
||||
}
|
||||
}
|
||||
|
@ -853,6 +861,13 @@ impl Axis {
|
|||
Self::Vertical => Self::Horizontal,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn component(&self, point: Vector2F) -> f32 {
|
||||
match self {
|
||||
Self::Horizontal => point.x(),
|
||||
Self::Vertical => point.y(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl ToJson for Axis {
|
||||
|
|
|
@ -62,6 +62,7 @@ pub struct Workspace {
|
|||
pub joining_project_message: ContainedText,
|
||||
pub external_location_message: ContainedText,
|
||||
pub dock: Dock,
|
||||
pub drop_target_overlay_color: Color,
|
||||
}
|
||||
|
||||
#[derive(Clone, Deserialize, Default)]
|
||||
|
@ -150,7 +151,6 @@ pub struct TabBar {
|
|||
pub inactive_pane: TabStyles,
|
||||
pub dragged_tab: Tab,
|
||||
pub height: f32,
|
||||
pub drop_target_overlay_color: Color,
|
||||
}
|
||||
|
||||
impl TabBar {
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
use collections::HashMap;
|
||||
use gpui::{
|
||||
actions,
|
||||
elements::{ChildView, Container, Empty, MouseEventHandler, Side, Svg},
|
||||
elements::{ChildView, Container, Empty, MouseEventHandler, ParentElement, Side, Stack, Svg},
|
||||
impl_internal_actions, Border, CursorStyle, Element, ElementBox, Entity, MouseButton,
|
||||
MutableAppContext, RenderContext, View, ViewContext, ViewHandle, WeakViewHandle,
|
||||
};
|
||||
|
@ -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);
|
||||
|
@ -24,7 +26,8 @@ actions!(
|
|||
HideDock,
|
||||
AnchorDockRight,
|
||||
AnchorDockBottom,
|
||||
ExpandDock
|
||||
ExpandDock,
|
||||
MoveActiveItemToDock,
|
||||
]
|
||||
);
|
||||
impl_internal_actions!(dock, [MoveDock, AddDefaultItemToDock]);
|
||||
|
@ -48,6 +51,30 @@ pub fn init(cx: &mut MutableAppContext) {
|
|||
Dock::move_dock(workspace, &MoveDock(DockAnchor::Expanded), cx)
|
||||
},
|
||||
);
|
||||
cx.add_action(
|
||||
|workspace: &mut Workspace, _: &MoveActiveItemToDock, cx: &mut ViewContext<Workspace>| {
|
||||
if let Some(active_item) = workspace.active_item(cx) {
|
||||
let item_id = active_item.id();
|
||||
|
||||
let from = workspace.active_pane();
|
||||
let to = workspace.dock_pane();
|
||||
if from.id() == to.id() {
|
||||
return;
|
||||
}
|
||||
|
||||
let destination_index = to.read(cx).items_len() + 1;
|
||||
|
||||
Pane::move_item(
|
||||
workspace,
|
||||
from.clone(),
|
||||
to.clone(),
|
||||
item_id,
|
||||
destination_index,
|
||||
cx,
|
||||
);
|
||||
}
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, PartialEq, Eq, Debug)]
|
||||
|
@ -283,25 +310,34 @@ impl Dock {
|
|||
DockAnchor::Expanded => {
|
||||
enum ExpandedDockWash {}
|
||||
enum ExpandedDockPane {}
|
||||
Container::new(
|
||||
MouseEventHandler::<ExpandedDockWash>::new(0, cx, |_state, cx| {
|
||||
Stack::new()
|
||||
.with_child(
|
||||
// Render wash under the dock which when clicked hides it
|
||||
MouseEventHandler::<ExpandedDockWash>::new(0, cx, |_, _| {
|
||||
Empty::new()
|
||||
.contained()
|
||||
.with_background_color(style.wash_color)
|
||||
.boxed()
|
||||
})
|
||||
.capture_all()
|
||||
.on_down(MouseButton::Left, |_, cx| {
|
||||
cx.dispatch_action(HideDock);
|
||||
})
|
||||
.with_cursor_style(CursorStyle::Arrow)
|
||||
.boxed(),
|
||||
)
|
||||
.with_child(
|
||||
MouseEventHandler::<ExpandedDockPane>::new(0, cx, |_state, cx| {
|
||||
ChildView::new(&self.pane, cx).boxed()
|
||||
})
|
||||
// Make sure all events directly under the dock pane
|
||||
// are captured
|
||||
.capture_all()
|
||||
.contained()
|
||||
.with_style(style.maximized)
|
||||
.boxed()
|
||||
})
|
||||
.capture_all()
|
||||
.on_down(MouseButton::Left, |_, cx| {
|
||||
cx.dispatch_action(HideDock);
|
||||
})
|
||||
.with_cursor_style(CursorStyle::Arrow)
|
||||
.boxed(),
|
||||
)
|
||||
.with_background_color(style.wash_color)
|
||||
.boxed()
|
||||
.boxed(),
|
||||
)
|
||||
.boxed()
|
||||
}
|
||||
})
|
||||
}
|
||||
|
@ -338,9 +374,11 @@ impl View for ToggleDockButton {
|
|||
return Empty::new().boxed();
|
||||
}
|
||||
|
||||
let dock_position = workspace.unwrap().read(cx).dock.position;
|
||||
let workspace = workspace.unwrap();
|
||||
let dock_position = workspace.read(cx).dock.position;
|
||||
|
||||
let theme = cx.global::<Settings>().theme.clone();
|
||||
|
||||
let button = MouseEventHandler::<Self>::new(0, cx, {
|
||||
let theme = theme.clone();
|
||||
move |state, _| {
|
||||
|
@ -361,7 +399,12 @@ impl View for ToggleDockButton {
|
|||
.boxed()
|
||||
}
|
||||
})
|
||||
.with_cursor_style(CursorStyle::PointingHand);
|
||||
.with_cursor_style(CursorStyle::PointingHand)
|
||||
.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;
|
||||
handle_dropped_item(event, &dock_pane.downgrade(), drop_index, false, None, cx);
|
||||
});
|
||||
|
||||
if dock_position.is_visible() {
|
||||
button
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
mod dragged_item_receiver;
|
||||
|
||||
use super::{ItemHandle, SplitDirection};
|
||||
use crate::{
|
||||
dock::{icon_for_dock_anchor, AnchorDockBottom, AnchorDockRight, ExpandDock, HideDock},
|
||||
|
@ -7,11 +9,11 @@ use crate::{
|
|||
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,
|
||||
|
@ -98,7 +100,7 @@ impl_internal_actions!(
|
|||
DeploySplitMenu,
|
||||
DeployNewMenu,
|
||||
DeployDockMenu,
|
||||
MoveItem
|
||||
MoveItem,
|
||||
]
|
||||
);
|
||||
|
||||
|
@ -575,6 +577,10 @@ impl Pane {
|
|||
}
|
||||
}
|
||||
|
||||
pub fn items_len(&self) -> usize {
|
||||
self.items.len()
|
||||
}
|
||||
|
||||
pub fn items(&self) -> impl Iterator<Item = &Box<dyn ItemHandle>> {
|
||||
self.items.iter()
|
||||
}
|
||||
|
@ -943,11 +949,11 @@ impl Pane {
|
|||
}
|
||||
}
|
||||
|
||||
fn move_item(
|
||||
pub fn move_item(
|
||||
workspace: &mut Workspace,
|
||||
from: ViewHandle<Pane>,
|
||||
to: ViewHandle<Pane>,
|
||||
item_to_move: usize,
|
||||
item_id_to_move: usize,
|
||||
destination_index: usize,
|
||||
cx: &mut ViewContext<Workspace>,
|
||||
) {
|
||||
|
@ -955,7 +961,7 @@ impl Pane {
|
|||
.read(cx)
|
||||
.items()
|
||||
.enumerate()
|
||||
.find(|(_, item_handle)| item_handle.id() == item_to_move);
|
||||
.find(|(_, item_handle)| item_handle.id() == item_id_to_move);
|
||||
|
||||
if item_to_move.is_none() {
|
||||
log::warn!("Tried to move item handle which was not in `from` pane. Maybe tab was closed during drop");
|
||||
|
@ -1051,133 +1057,107 @@ impl Pane {
|
|||
|
||||
fn render_tabs(&mut self, cx: &mut RenderContext<Self>) -> impl Element {
|
||||
let theme = cx.global::<Settings>().theme.clone();
|
||||
let filler_index = self.items.len();
|
||||
|
||||
let pane = cx.handle();
|
||||
let autoscroll = if mem::take(&mut self.autoscroll) {
|
||||
Some(self.active_item_index)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
let pane_active = self.is_active;
|
||||
|
||||
enum Tabs {}
|
||||
enum Tab {}
|
||||
enum Filler {}
|
||||
let pane = cx.handle();
|
||||
MouseEventHandler::<Tabs>::new(0, cx, |_, cx| {
|
||||
let autoscroll = if mem::take(&mut self.autoscroll) {
|
||||
Some(self.active_item_index)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
let mut row = Flex::row().scrollable::<Tabs, _>(1, autoscroll, cx);
|
||||
for (ix, (item, detail)) in self
|
||||
.items
|
||||
.iter()
|
||||
.cloned()
|
||||
.zip(self.tab_details(cx))
|
||||
.enumerate()
|
||||
{
|
||||
let detail = if detail == 0 { None } else { Some(detail) };
|
||||
let tab_active = ix == self.active_item_index;
|
||||
|
||||
let pane_active = self.is_active;
|
||||
row.add_child({
|
||||
enum Tab {}
|
||||
dragged_item_receiver::<Tab, _>(ix, ix, true, None, cx, {
|
||||
let item = item.clone();
|
||||
let pane = pane.clone();
|
||||
let detail = detail.clone();
|
||||
|
||||
let mut row = Flex::row().scrollable::<Tabs, _>(1, autoscroll, cx);
|
||||
for (ix, (item, detail)) in self
|
||||
.items
|
||||
.iter()
|
||||
.cloned()
|
||||
.zip(self.tab_details(cx))
|
||||
.enumerate()
|
||||
{
|
||||
let detail = if detail == 0 { None } else { Some(detail) };
|
||||
let tab_active = ix == self.active_item_index;
|
||||
|
||||
row.add_child({
|
||||
MouseEventHandler::<Tab>::new(ix, cx, {
|
||||
let item = item.clone();
|
||||
let pane = pane.clone();
|
||||
let detail = detail.clone();
|
||||
let theme = cx.global::<Settings>().theme.clone();
|
||||
|
||||
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, tab_style, cx)
|
||||
}
|
||||
})
|
||||
.with_cursor_style(if pane_active && tab_active {
|
||||
CursorStyle::Arrow
|
||||
} else {
|
||||
CursorStyle::PointingHand
|
||||
})
|
||||
.on_down(MouseButton::Left, move |_, cx| {
|
||||
cx.dispatch_action(ActivateItem(ix));
|
||||
cx.propagate_event();
|
||||
})
|
||||
.on_click(MouseButton::Middle, {
|
||||
let item = item.clone();
|
||||
let pane = pane.clone();
|
||||
move |_, cx: &mut EventContext| {
|
||||
cx.dispatch_action(CloseItem {
|
||||
item_id: item.id(),
|
||||
pane: pane.clone(),
|
||||
})
|
||||
}
|
||||
})
|
||||
.as_draggable(
|
||||
DraggedItem {
|
||||
item,
|
||||
pane: pane.clone(),
|
||||
},
|
||||
{
|
||||
let theme = cx.global::<Settings>().theme.clone();
|
||||
|
||||
move |mouse_state, cx| {
|
||||
let tab_style =
|
||||
theme.workspace.tab_bar.tab_style(pane_active, tab_active);
|
||||
let hovered = mouse_state.hovered();
|
||||
let detail = detail.clone();
|
||||
move |dragged_item, cx: &mut RenderContext<Workspace>| {
|
||||
let tab_style = &theme.workspace.tab_bar.dragged_tab;
|
||||
Self::render_tab(
|
||||
&item,
|
||||
pane,
|
||||
ix == 0,
|
||||
&dragged_item.item,
|
||||
dragged_item.pane.clone(),
|
||||
false,
|
||||
detail,
|
||||
hovered,
|
||||
Self::tab_overlay_color(hovered, theme.as_ref(), cx),
|
||||
tab_style,
|
||||
false,
|
||||
&tab_style,
|
||||
cx,
|
||||
)
|
||||
}
|
||||
})
|
||||
.with_cursor_style(if pane_active && tab_active {
|
||||
CursorStyle::Arrow
|
||||
} else {
|
||||
CursorStyle::PointingHand
|
||||
})
|
||||
.on_down(MouseButton::Left, move |_, cx| {
|
||||
cx.dispatch_action(ActivateItem(ix));
|
||||
})
|
||||
.on_click(MouseButton::Middle, {
|
||||
let item = item.clone();
|
||||
let pane = pane.clone();
|
||||
move |_, cx: &mut EventContext| {
|
||||
cx.dispatch_action(CloseItem {
|
||||
item_id: item.id(),
|
||||
pane: pane.clone(),
|
||||
})
|
||||
}
|
||||
})
|
||||
.on_up(MouseButton::Left, {
|
||||
let pane = pane.clone();
|
||||
move |_, cx: &mut EventContext| Pane::handle_dropped_item(&pane, ix, cx)
|
||||
})
|
||||
.as_draggable(
|
||||
DraggedItem {
|
||||
item,
|
||||
pane: pane.clone(),
|
||||
},
|
||||
{
|
||||
let theme = cx.global::<Settings>().theme.clone();
|
||||
},
|
||||
)
|
||||
.boxed()
|
||||
})
|
||||
}
|
||||
|
||||
let detail = detail.clone();
|
||||
move |dragged_item, cx: &mut RenderContext<Workspace>| {
|
||||
let tab_style = &theme.workspace.tab_bar.dragged_tab;
|
||||
Self::render_tab(
|
||||
&dragged_item.item,
|
||||
dragged_item.pane.clone(),
|
||||
false,
|
||||
detail,
|
||||
false,
|
||||
None,
|
||||
&tab_style,
|
||||
cx,
|
||||
)
|
||||
}
|
||||
},
|
||||
)
|
||||
// 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(
|
||||
dragged_item_receiver::<Filler, _>(0, filler_index, true, None, cx, |_, _| {
|
||||
Empty::new()
|
||||
.contained()
|
||||
.with_style(filler_style.container)
|
||||
.with_border(filler_style.container.border)
|
||||
.boxed()
|
||||
})
|
||||
}
|
||||
})
|
||||
.flex(1., true)
|
||||
.named("filler"),
|
||||
);
|
||||
|
||||
// Use the inactive tab style along with the current pane's active status to decide how to render
|
||||
// the filler
|
||||
let filler_style = theme.workspace.tab_bar.tab_style(pane_active, false);
|
||||
row.add_child(
|
||||
MouseEventHandler::<Filler>::new(0, cx, |mouse_state, cx| {
|
||||
let mut filler = 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(), &theme, cx)
|
||||
{
|
||||
filler = filler.with_overlay_color(overlay);
|
||||
}
|
||||
|
||||
filler.boxed()
|
||||
})
|
||||
.flex(1., true)
|
||||
.named("filler"),
|
||||
);
|
||||
|
||||
row.boxed()
|
||||
})
|
||||
.on_up(MouseButton::Left, move |_, cx| {
|
||||
Pane::handle_dropped_item(&pane, filler_index, cx)
|
||||
})
|
||||
row
|
||||
}
|
||||
|
||||
fn tab_details(&self, cx: &AppContext) -> Vec<usize> {
|
||||
|
@ -1223,7 +1203,6 @@ impl Pane {
|
|||
first: bool,
|
||||
detail: Option<usize>,
|
||||
hovered: bool,
|
||||
overlay: Option<Color>,
|
||||
tab_style: &theme::Tab,
|
||||
cx: &mut RenderContext<V>,
|
||||
) -> ElementBox {
|
||||
|
@ -1233,7 +1212,7 @@ impl Pane {
|
|||
container.border.left = false;
|
||||
}
|
||||
|
||||
let mut tab = Flex::row()
|
||||
Flex::row()
|
||||
.with_child(
|
||||
Align::new({
|
||||
let diameter = 7.0;
|
||||
|
@ -1301,7 +1280,6 @@ impl Pane {
|
|||
})
|
||||
}
|
||||
})
|
||||
.on_click(MouseButton::Middle, |_, cx| cx.propogate_event())
|
||||
.named("close-tab-icon")
|
||||
} else {
|
||||
Empty::new().boxed()
|
||||
|
@ -1312,46 +1290,46 @@ 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 handle_dropped_item(pane: &WeakViewHandle<Pane>, index: usize, cx: &mut EventContext) {
|
||||
if let Some((_, dragged_item)) = cx
|
||||
.global::<DragAndDrop<Workspace>>()
|
||||
.currently_dragged::<DraggedItem>(cx.window_id)
|
||||
{
|
||||
cx.dispatch_action(MoveItem {
|
||||
item_id: dragged_item.item.id(),
|
||||
from: dragged_item.pane.clone(),
|
||||
to: pane.clone(),
|
||||
destination_index: index,
|
||||
})
|
||||
} else {
|
||||
cx.propogate_event();
|
||||
}
|
||||
}
|
||||
|
||||
fn tab_overlay_color(
|
||||
hovered: bool,
|
||||
fn render_tab_bar_buttons(
|
||||
&mut self,
|
||||
theme: &Theme,
|
||||
cx: &mut RenderContext<Self>,
|
||||
) -> Option<Color> {
|
||||
if hovered
|
||||
&& cx
|
||||
.global::<DragAndDrop<Workspace>>()
|
||||
.currently_dragged::<DraggedItem>(cx.window_id())
|
||||
.is_some()
|
||||
{
|
||||
Some(theme.workspace.tab_bar.drop_target_overlay_color)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
) -> ElementBox {
|
||||
Flex::row()
|
||||
// New menu
|
||||
.with_child(tab_bar_button(0, "icons/plus_12.svg", cx, |position| {
|
||||
DeployNewMenu { position }
|
||||
}))
|
||||
.with_child(
|
||||
self.docked
|
||||
.map(|anchor| {
|
||||
// Add the dock menu button if this pane is a dock
|
||||
let dock_icon = icon_for_dock_anchor(anchor);
|
||||
|
||||
tab_bar_button(1, dock_icon, cx, |position| DeployDockMenu { position })
|
||||
})
|
||||
.unwrap_or_else(|| {
|
||||
// Add the split menu if this pane is not a dock
|
||||
tab_bar_button(2, "icons/split_12.svg", cx, |position| DeploySplitMenu {
|
||||
position,
|
||||
})
|
||||
}),
|
||||
)
|
||||
// Add the close dock button if this pane is a dock
|
||||
.with_children(
|
||||
self.docked
|
||||
.map(|_| tab_bar_button(3, "icons/x_mark_thin_8.svg", cx, |_| HideDock)),
|
||||
)
|
||||
.contained()
|
||||
.with_style(theme.workspace.tab_bar.pane_button_container)
|
||||
.flex(1., false)
|
||||
.boxed()
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1376,60 +1354,12 @@ impl View for Pane {
|
|||
Flex::column()
|
||||
.with_child({
|
||||
let mut tab_row = Flex::row()
|
||||
.with_child(self.render_tabs(cx).flex(1.0, true).named("tabs"));
|
||||
.with_child(self.render_tabs(cx).flex(1., true).named("tabs"));
|
||||
|
||||
// Render pane buttons
|
||||
let theme = cx.global::<Settings>().theme.clone();
|
||||
if self.is_active {
|
||||
tab_row.add_child(
|
||||
Flex::row()
|
||||
// New menu
|
||||
.with_child(tab_bar_button(
|
||||
0,
|
||||
"icons/plus_12.svg",
|
||||
cx,
|
||||
|position| DeployNewMenu { position },
|
||||
))
|
||||
.with_child(
|
||||
self.docked
|
||||
.map(|anchor| {
|
||||
// Add the dock menu button if this pane is a dock
|
||||
let dock_icon =
|
||||
icon_for_dock_anchor(anchor);
|
||||
|
||||
tab_bar_button(
|
||||
1,
|
||||
dock_icon,
|
||||
cx,
|
||||
|position| DeployDockMenu { position },
|
||||
)
|
||||
})
|
||||
.unwrap_or_else(|| {
|
||||
// Add the split menu if this pane is not a dock
|
||||
tab_bar_button(
|
||||
2,
|
||||
"icons/split_12.svg",
|
||||
cx,
|
||||
|position| DeploySplitMenu { position },
|
||||
)
|
||||
}),
|
||||
)
|
||||
// Add the close dock button if this pane is a dock
|
||||
.with_children(self.docked.map(|_| {
|
||||
tab_bar_button(
|
||||
3,
|
||||
"icons/x_mark_thin_8.svg",
|
||||
cx,
|
||||
|_| HideDock,
|
||||
)
|
||||
}))
|
||||
.contained()
|
||||
.with_style(
|
||||
theme.workspace.tab_bar.pane_button_container,
|
||||
)
|
||||
.flex(1., false)
|
||||
.boxed(),
|
||||
)
|
||||
tab_row.add_child(self.render_tab_bar_buttons(&theme, cx))
|
||||
}
|
||||
|
||||
tab_row
|
||||
|
@ -1440,14 +1370,39 @@ impl View for Pane {
|
|||
.flex(1., false)
|
||||
.named("tab bar")
|
||||
})
|
||||
.with_child(ChildView::new(&self.toolbar, cx).expanded().boxed())
|
||||
.with_child(ChildView::new(active_item, cx).flex(1., true).boxed())
|
||||
.with_child({
|
||||
enum PaneContentTabDropTarget {}
|
||||
dragged_item_receiver::<PaneContentTabDropTarget, _>(
|
||||
0,
|
||||
self.active_item_index + 1,
|
||||
false,
|
||||
Some(100.),
|
||||
cx,
|
||||
{
|
||||
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()
|
||||
}
|
||||
},
|
||||
)
|
||||
.flex(1., true)
|
||||
.boxed()
|
||||
})
|
||||
.boxed()
|
||||
} else {
|
||||
enum EmptyPane {}
|
||||
let theme = cx.global::<Settings>().theme.clone();
|
||||
|
||||
MouseEventHandler::<EmptyPane>::new(0, cx, |_, _| {
|
||||
dragged_item_receiver::<EmptyPane, _>(0, 0, false, None, cx, |_, _| {
|
||||
Empty::new()
|
||||
.contained()
|
||||
.with_background_color(theme.workspace.background)
|
||||
|
@ -1456,10 +1411,6 @@ impl View for Pane {
|
|||
.on_down(MouseButton::Left, |_, cx| {
|
||||
cx.focus_parent_view();
|
||||
})
|
||||
.on_up(MouseButton::Left, {
|
||||
let pane = this.clone();
|
||||
move |_, cx: &mut EventContext| Pane::handle_dropped_item(&pane, 0, cx)
|
||||
})
|
||||
.boxed()
|
||||
}
|
||||
})
|
||||
|
|
142
crates/workspace/src/pane/dragged_item_receiver.rs
Normal file
142
crates/workspace/src/pane/dragged_item_receiver.rs
Normal file
|
@ -0,0 +1,142 @@
|
|||
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<Tag, F>(
|
||||
region_id: usize,
|
||||
drop_index: usize,
|
||||
allow_same_pane: bool,
|
||||
split_margin: Option<f32>,
|
||||
cx: &mut RenderContext<Pane>,
|
||||
render_child: F,
|
||||
) -> MouseEventHandler<Tag>
|
||||
where
|
||||
Tag: 'static,
|
||||
F: FnOnce(&mut MouseState, &mut RenderContext<Pane>) -> ElementBox,
|
||||
{
|
||||
MouseEventHandler::<Tag>::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::<DragAndDrop<Workspace>>()
|
||||
.currently_dragged::<DraggedItem>(cx.window_id())
|
||||
.filter(|_| hovered)
|
||||
.map(|(drag_position, _)| drag_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();
|
||||
}
|
||||
})
|
||||
.on_move(|_, cx| {
|
||||
if cx
|
||||
.global::<DragAndDrop<Workspace>>()
|
||||
.currently_dragged::<DraggedItem>(cx.window_id())
|
||||
.is_some()
|
||||
{
|
||||
cx.notify();
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
pub fn handle_dropped_item(
|
||||
event: MouseUp,
|
||||
pane: &WeakViewHandle<Pane>,
|
||||
index: usize,
|
||||
allow_same_pane: bool,
|
||||
split_margin: Option<f32>,
|
||||
cx: &mut EventContext,
|
||||
) {
|
||||
if let Some((_, dragged_item)) = cx
|
||||
.global::<DragAndDrop<Workspace>>()
|
||||
.currently_dragged::<DraggedItem>(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<SplitDirection> {
|
||||
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::<Settings>()
|
||||
.theme
|
||||
.workspace
|
||||
.drop_target_overlay_color
|
||||
}
|
|
@ -2,7 +2,9 @@ use crate::{FollowerStatesByLeader, JoinProject, Pane, Workspace};
|
|||
use anyhow::{anyhow, Result};
|
||||
use call::{ActiveCall, ParticipantLocation};
|
||||
use gpui::{
|
||||
elements::*, Axis, Border, CursorStyle, ModelHandle, MouseButton, RenderContext, ViewHandle,
|
||||
elements::*,
|
||||
geometry::{rect::RectF, vector::Vector2F},
|
||||
Axis, Border, CursorStyle, ModelHandle, MouseButton, RenderContext, ViewHandle,
|
||||
};
|
||||
use project::Project;
|
||||
use serde::Deserialize;
|
||||
|
@ -263,9 +265,7 @@ impl PaneAxis {
|
|||
new_pane: &ViewHandle<Pane>,
|
||||
direction: SplitDirection,
|
||||
) -> Result<()> {
|
||||
use SplitDirection::*;
|
||||
|
||||
for (idx, member) in self.members.iter_mut().enumerate() {
|
||||
for (mut idx, member) in self.members.iter_mut().enumerate() {
|
||||
match member {
|
||||
Member::Axis(axis) => {
|
||||
if axis.split(old_pane, new_pane, direction).is_ok() {
|
||||
|
@ -274,15 +274,12 @@ impl PaneAxis {
|
|||
}
|
||||
Member::Pane(pane) => {
|
||||
if pane == old_pane {
|
||||
if direction.matches_axis(self.axis) {
|
||||
match direction {
|
||||
Up | Left => {
|
||||
self.members.insert(idx, Member::Pane(new_pane.clone()));
|
||||
}
|
||||
Down | Right => {
|
||||
self.members.insert(idx + 1, Member::Pane(new_pane.clone()));
|
||||
}
|
||||
if direction.axis() == self.axis {
|
||||
if direction.increasing() {
|
||||
idx += 1;
|
||||
}
|
||||
|
||||
self.members.insert(idx, Member::Pane(new_pane.clone()));
|
||||
} else {
|
||||
*member =
|
||||
Member::new_axis(old_pane.clone(), new_pane.clone(), direction);
|
||||
|
@ -374,187 +371,46 @@ pub enum SplitDirection {
|
|||
}
|
||||
|
||||
impl SplitDirection {
|
||||
fn matches_axis(self, orientation: Axis) -> bool {
|
||||
use Axis::*;
|
||||
use SplitDirection::*;
|
||||
pub fn all() -> [Self; 4] {
|
||||
[Self::Up, Self::Down, Self::Left, Self::Right]
|
||||
}
|
||||
|
||||
pub fn edge(&self, rect: RectF) -> f32 {
|
||||
match self {
|
||||
Up | Down => match orientation {
|
||||
Vertical => true,
|
||||
Horizontal => false,
|
||||
},
|
||||
Left | Right => match orientation {
|
||||
Vertical => false,
|
||||
Horizontal => true,
|
||||
},
|
||||
Self::Up => rect.min_y(),
|
||||
Self::Down => rect.max_y(),
|
||||
Self::Left => rect.min_x(),
|
||||
Self::Right => rect.max_x(),
|
||||
}
|
||||
}
|
||||
|
||||
// Returns a new rectangle which shares an edge in SplitDirection and has `size` along SplitDirection
|
||||
pub fn along_edge(&self, rect: RectF, size: f32) -> RectF {
|
||||
match self {
|
||||
Self::Up => RectF::new(rect.origin(), Vector2F::new(rect.width(), size)),
|
||||
Self::Down => RectF::new(
|
||||
rect.lower_left() - Vector2F::new(0., size),
|
||||
Vector2F::new(rect.width(), size),
|
||||
),
|
||||
Self::Left => RectF::new(rect.origin(), Vector2F::new(size, rect.height())),
|
||||
Self::Right => RectF::new(
|
||||
rect.upper_right() - Vector2F::new(size, 0.),
|
||||
Vector2F::new(size, rect.height()),
|
||||
),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn axis(&self) -> Axis {
|
||||
match self {
|
||||
Self::Up | Self::Down => Axis::Vertical,
|
||||
Self::Left | Self::Right => Axis::Horizontal,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn increasing(&self) -> bool {
|
||||
match self {
|
||||
Self::Left | Self::Up => false,
|
||||
Self::Down | Self::Right => true,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
// use super::*;
|
||||
// use serde_json::json;
|
||||
|
||||
// #[test]
|
||||
// fn test_split_and_remove() -> Result<()> {
|
||||
// let mut group = PaneGroup::new(1);
|
||||
// assert_eq!(
|
||||
// serde_json::to_value(&group)?,
|
||||
// json!({
|
||||
// "type": "pane",
|
||||
// "paneId": 1,
|
||||
// })
|
||||
// );
|
||||
|
||||
// group.split(1, 2, SplitDirection::Right)?;
|
||||
// assert_eq!(
|
||||
// serde_json::to_value(&group)?,
|
||||
// json!({
|
||||
// "type": "axis",
|
||||
// "orientation": "horizontal",
|
||||
// "members": [
|
||||
// {"type": "pane", "paneId": 1},
|
||||
// {"type": "pane", "paneId": 2},
|
||||
// ]
|
||||
// })
|
||||
// );
|
||||
|
||||
// group.split(2, 3, SplitDirection::Up)?;
|
||||
// assert_eq!(
|
||||
// serde_json::to_value(&group)?,
|
||||
// json!({
|
||||
// "type": "axis",
|
||||
// "orientation": "horizontal",
|
||||
// "members": [
|
||||
// {"type": "pane", "paneId": 1},
|
||||
// {
|
||||
// "type": "axis",
|
||||
// "orientation": "vertical",
|
||||
// "members": [
|
||||
// {"type": "pane", "paneId": 3},
|
||||
// {"type": "pane", "paneId": 2},
|
||||
// ]
|
||||
// },
|
||||
// ]
|
||||
// })
|
||||
// );
|
||||
|
||||
// group.split(1, 4, SplitDirection::Right)?;
|
||||
// assert_eq!(
|
||||
// serde_json::to_value(&group)?,
|
||||
// json!({
|
||||
// "type": "axis",
|
||||
// "orientation": "horizontal",
|
||||
// "members": [
|
||||
// {"type": "pane", "paneId": 1},
|
||||
// {"type": "pane", "paneId": 4},
|
||||
// {
|
||||
// "type": "axis",
|
||||
// "orientation": "vertical",
|
||||
// "members": [
|
||||
// {"type": "pane", "paneId": 3},
|
||||
// {"type": "pane", "paneId": 2},
|
||||
// ]
|
||||
// },
|
||||
// ]
|
||||
// })
|
||||
// );
|
||||
|
||||
// group.split(2, 5, SplitDirection::Up)?;
|
||||
// assert_eq!(
|
||||
// serde_json::to_value(&group)?,
|
||||
// json!({
|
||||
// "type": "axis",
|
||||
// "orientation": "horizontal",
|
||||
// "members": [
|
||||
// {"type": "pane", "paneId": 1},
|
||||
// {"type": "pane", "paneId": 4},
|
||||
// {
|
||||
// "type": "axis",
|
||||
// "orientation": "vertical",
|
||||
// "members": [
|
||||
// {"type": "pane", "paneId": 3},
|
||||
// {"type": "pane", "paneId": 5},
|
||||
// {"type": "pane", "paneId": 2},
|
||||
// ]
|
||||
// },
|
||||
// ]
|
||||
// })
|
||||
// );
|
||||
|
||||
// assert_eq!(true, group.remove(5)?);
|
||||
// assert_eq!(
|
||||
// serde_json::to_value(&group)?,
|
||||
// json!({
|
||||
// "type": "axis",
|
||||
// "orientation": "horizontal",
|
||||
// "members": [
|
||||
// {"type": "pane", "paneId": 1},
|
||||
// {"type": "pane", "paneId": 4},
|
||||
// {
|
||||
// "type": "axis",
|
||||
// "orientation": "vertical",
|
||||
// "members": [
|
||||
// {"type": "pane", "paneId": 3},
|
||||
// {"type": "pane", "paneId": 2},
|
||||
// ]
|
||||
// },
|
||||
// ]
|
||||
// })
|
||||
// );
|
||||
|
||||
// assert_eq!(true, group.remove(4)?);
|
||||
// assert_eq!(
|
||||
// serde_json::to_value(&group)?,
|
||||
// json!({
|
||||
// "type": "axis",
|
||||
// "orientation": "horizontal",
|
||||
// "members": [
|
||||
// {"type": "pane", "paneId": 1},
|
||||
// {
|
||||
// "type": "axis",
|
||||
// "orientation": "vertical",
|
||||
// "members": [
|
||||
// {"type": "pane", "paneId": 3},
|
||||
// {"type": "pane", "paneId": 2},
|
||||
// ]
|
||||
// },
|
||||
// ]
|
||||
// })
|
||||
// );
|
||||
|
||||
// assert_eq!(true, group.remove(3)?);
|
||||
// assert_eq!(
|
||||
// serde_json::to_value(&group)?,
|
||||
// json!({
|
||||
// "type": "axis",
|
||||
// "orientation": "horizontal",
|
||||
// "members": [
|
||||
// {"type": "pane", "paneId": 1},
|
||||
// {"type": "pane", "paneId": 2},
|
||||
// ]
|
||||
// })
|
||||
// );
|
||||
|
||||
// assert_eq!(true, group.remove(2)?);
|
||||
// assert_eq!(
|
||||
// serde_json::to_value(&group)?,
|
||||
// json!({
|
||||
// "type": "pane",
|
||||
// "paneId": 1,
|
||||
// })
|
||||
// );
|
||||
|
||||
// assert_eq!(false, group.remove(1)?);
|
||||
// assert_eq!(
|
||||
// serde_json::to_value(&group)?,
|
||||
// json!({
|
||||
// "type": "pane",
|
||||
// "paneId": 1,
|
||||
// })
|
||||
// );
|
||||
|
||||
// Ok(())
|
||||
// }
|
||||
}
|
||||
|
|
|
@ -100,7 +100,7 @@ actions!(
|
|||
ToggleLeftSidebar,
|
||||
ToggleRightSidebar,
|
||||
NewTerminal,
|
||||
NewSearch
|
||||
NewSearch,
|
||||
]
|
||||
);
|
||||
|
||||
|
@ -126,6 +126,14 @@ pub struct OpenSharedScreen {
|
|||
pub peer_id: PeerId,
|
||||
}
|
||||
|
||||
#[derive(Clone, PartialEq)]
|
||||
pub struct SplitWithItem {
|
||||
from: WeakViewHandle<Pane>,
|
||||
pane_to_split: WeakViewHandle<Pane>,
|
||||
split_direction: SplitDirection,
|
||||
item_id_to_move: usize,
|
||||
}
|
||||
|
||||
impl_internal_actions!(
|
||||
workspace,
|
||||
[
|
||||
|
@ -133,7 +141,8 @@ impl_internal_actions!(
|
|||
ToggleFollow,
|
||||
JoinProject,
|
||||
OpenSharedScreen,
|
||||
RemoveWorktreeFromProject
|
||||
RemoveWorktreeFromProject,
|
||||
SplitWithItem,
|
||||
]
|
||||
);
|
||||
impl_actions!(workspace, [ActivatePane]);
|
||||
|
@ -206,6 +215,24 @@ pub fn init(app_state: Arc<AppState>, cx: &mut MutableAppContext) {
|
|||
workspace.toggle_sidebar(SidebarSide::Right, cx);
|
||||
});
|
||||
cx.add_action(Workspace::activate_pane_at_index);
|
||||
cx.add_action(
|
||||
|workspace: &mut Workspace,
|
||||
SplitWithItem {
|
||||
from,
|
||||
pane_to_split,
|
||||
item_id_to_move,
|
||||
split_direction,
|
||||
}: &_,
|
||||
cx| {
|
||||
workspace.split_pane_with_item(
|
||||
from.clone(),
|
||||
pane_to_split.clone(),
|
||||
*item_id_to_move,
|
||||
*split_direction,
|
||||
cx,
|
||||
)
|
||||
},
|
||||
);
|
||||
|
||||
let client = &app_state.client;
|
||||
client.add_view_request_handler(Workspace::handle_follow);
|
||||
|
@ -1950,6 +1977,29 @@ impl Workspace {
|
|||
})
|
||||
}
|
||||
|
||||
pub fn split_pane_with_item(
|
||||
&mut self,
|
||||
from: WeakViewHandle<Pane>,
|
||||
pane_to_split: WeakViewHandle<Pane>,
|
||||
item_id_to_move: usize,
|
||||
split_direction: SplitDirection,
|
||||
cx: &mut ViewContext<Self>,
|
||||
) {
|
||||
if let Some((pane_to_split, from)) = pane_to_split.upgrade(cx).zip(from.upgrade(cx)) {
|
||||
if &pane_to_split == self.dock_pane() {
|
||||
warn!("Can't split dock pane.");
|
||||
return;
|
||||
}
|
||||
|
||||
let new_pane = self.add_pane(cx);
|
||||
Pane::move_item(self, from.clone(), new_pane.clone(), item_id_to_move, 0, cx);
|
||||
self.center
|
||||
.split(&pane_to_split, &new_pane, split_direction)
|
||||
.unwrap();
|
||||
cx.notify();
|
||||
}
|
||||
}
|
||||
|
||||
fn remove_pane(&mut self, pane: ViewHandle<Pane>, cx: &mut ViewContext<Self>) {
|
||||
if self.center.remove(&pane).unwrap() {
|
||||
self.panes.retain(|p| p != &pane);
|
||||
|
@ -3175,7 +3225,7 @@ mod tests {
|
|||
|
||||
cx.foreground().run_until_parked();
|
||||
pane.read_with(cx, |pane, _| {
|
||||
assert_eq!(pane.items().count(), 4);
|
||||
assert_eq!(pane.items_len(), 4);
|
||||
assert_eq!(pane.active_item().unwrap().id(), item1.id());
|
||||
});
|
||||
|
||||
|
@ -3185,7 +3235,7 @@ mod tests {
|
|||
assert_eq!(item1.read(cx).save_count, 1);
|
||||
assert_eq!(item1.read(cx).save_as_count, 0);
|
||||
assert_eq!(item1.read(cx).reload_count, 0);
|
||||
assert_eq!(pane.items().count(), 3);
|
||||
assert_eq!(pane.items_len(), 3);
|
||||
assert_eq!(pane.active_item().unwrap().id(), item3.id());
|
||||
});
|
||||
|
||||
|
@ -3195,7 +3245,7 @@ mod tests {
|
|||
assert_eq!(item3.read(cx).save_count, 0);
|
||||
assert_eq!(item3.read(cx).save_as_count, 0);
|
||||
assert_eq!(item3.read(cx).reload_count, 1);
|
||||
assert_eq!(pane.items().count(), 2);
|
||||
assert_eq!(pane.items_len(), 2);
|
||||
assert_eq!(pane.active_item().unwrap().id(), item4.id());
|
||||
});
|
||||
|
||||
|
@ -3207,7 +3257,7 @@ mod tests {
|
|||
assert_eq!(item4.read(cx).save_count, 0);
|
||||
assert_eq!(item4.read(cx).save_as_count, 1);
|
||||
assert_eq!(item4.read(cx).reload_count, 0);
|
||||
assert_eq!(pane.items().count(), 1);
|
||||
assert_eq!(pane.items_len(), 1);
|
||||
assert_eq!(pane.active_item().unwrap().id(), item2.id());
|
||||
});
|
||||
}
|
||||
|
@ -3309,7 +3359,7 @@ mod tests {
|
|||
cx.foreground().run_until_parked();
|
||||
close.await.unwrap();
|
||||
left_pane.read_with(cx, |pane, _| {
|
||||
assert_eq!(pane.items().count(), 0);
|
||||
assert_eq!(pane.items_len(), 0);
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
@ -811,7 +811,7 @@ mod tests {
|
|||
pane.active_item().unwrap().project_path(cx),
|
||||
Some(file1.clone())
|
||||
);
|
||||
assert_eq!(pane.items().count(), 1);
|
||||
assert_eq!(pane.items_len(), 1);
|
||||
});
|
||||
|
||||
// Open the second entry
|
||||
|
@ -825,7 +825,7 @@ mod tests {
|
|||
pane.active_item().unwrap().project_path(cx),
|
||||
Some(file2.clone())
|
||||
);
|
||||
assert_eq!(pane.items().count(), 2);
|
||||
assert_eq!(pane.items_len(), 2);
|
||||
});
|
||||
|
||||
// Open the first entry again. The existing pane item is activated.
|
||||
|
@ -841,7 +841,7 @@ mod tests {
|
|||
pane.active_item().unwrap().project_path(cx),
|
||||
Some(file1.clone())
|
||||
);
|
||||
assert_eq!(pane.items().count(), 2);
|
||||
assert_eq!(pane.items_len(), 2);
|
||||
});
|
||||
|
||||
// Split the pane with the first entry, then open the second entry again.
|
||||
|
|
|
@ -75,10 +75,6 @@ export default function tabBar(colorScheme: ColorScheme) {
|
|||
return {
|
||||
height,
|
||||
background: background(layer),
|
||||
dropTargetOverlayColor: withOpacity(
|
||||
foreground(layer),
|
||||
0.6
|
||||
),
|
||||
activePane: {
|
||||
activeTab: activePaneActiveTab,
|
||||
inactiveTab: tab,
|
||||
|
|
|
@ -227,5 +227,9 @@ export default function workspace(colorScheme: ColorScheme) {
|
|||
shadow: colorScheme.modalShadow,
|
||||
},
|
||||
},
|
||||
dropTargetOverlayColor: withOpacity(
|
||||
foreground(layer, "variant"),
|
||||
0.5
|
||||
),
|
||||
};
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue