Cargo.lock 🔗
@@ -1736,6 +1736,7 @@ dependencies = [
"collections",
"context_menu",
"ctor",
+ "drag_and_drop",
"env_logger",
"futures 0.3.24",
"fuzzy",
Kay Simmons created
Drag tabs more places
Cargo.lock | 1
assets/keymaps/default.json | 6
crates/drag_and_drop/src/drag_and_drop.rs | 2
crates/editor/Cargo.toml | 1
crates/editor/src/editor_tests.rs | 2
crates/editor/src/element.rs | 12
crates/gpui/src/elements/flex.rs | 2
crates/gpui/src/elements/mouse_event_handler.rs | 68 ++
crates/gpui/src/elements/overlay.rs | 35
crates/gpui/src/elements/stack.rs | 21
crates/gpui/src/elements/uniform_list.rs | 2
crates/gpui/src/presenter.rs | 25
crates/theme/src/theme.rs | 2
crates/workspace/src/dock.rs | 79 ++
crates/workspace/src/pane.rs | 383 ++++++---------
crates/workspace/src/pane/dragged_item_receiver.rs | 142 +++++
crates/workspace/src/pane_group.rs | 232 +-------
crates/workspace/src/workspace.rs | 64 ++
crates/zed/src/zed.rs | 6
styles/src/styleTree/tabBar.ts | 4
styles/src/styleTree/workspace.ts | 4
21 files changed, 598 insertions(+), 495 deletions(-)
@@ -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);
-
- 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,
- ));
- }
+ 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,
+ ));
+ }
- 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();
- 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 pane_active = self.is_active;
-
- 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 autoscroll = if mem::take(&mut self.autoscroll) {
+ Some(self.active_item_index)
+ } else {
+ None
+ };
- row.add_child({
- MouseEventHandler::<Tab>::new(ix, cx, {
- let item = item.clone();
- let pane = pane.clone();
- let detail = detail.clone();
+ let pane_active = self.is_active;
+ enum Tabs {}
+ 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({
+ enum Tab {}
+ dragged_item_receiver::<Tab, _>(ix, ix, true, None, 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();
-
- 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,
- )
- }
- },
- )
- .boxed()
- })
- }
-
- // 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);
- }
+ },
+ )
+ .boxed()
+ })
+ }
- filler.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_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"),
+ );
- 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()
- }
-
- 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();
- }
+ .with_style(container)
+ .constrained()
+ .with_height(tab_style.height)
+ .boxed()
}
- 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()
}
})
@@ -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(),
}
}
-}
-
-#[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,
- // })
- // );
+ // 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()),
+ ),
+ }
+ }
- // assert_eq!(false, group.remove(1)?);
- // assert_eq!(
- // serde_json::to_value(&group)?,
- // json!({
- // "type": "pane",
- // "paneId": 1,
- // })
- // );
+ pub fn axis(&self) -> Axis {
+ match self {
+ Self::Up | Self::Down => Axis::Vertical,
+ Self::Left | Self::Right => Axis::Horizontal,
+ }
+ }
- // Ok(())
- // }
+ pub fn increasing(&self) -> bool {
+ match self {
+ Self::Left | Self::Up => false,
+ Self::Down | Self::Right => true,
+ }
+ }
}
@@ -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
+ ),
};
}