Detailed changes
@@ -21,6 +21,7 @@ use std::{
use anyhow::{anyhow, Context, Result};
use lazy_static::lazy_static;
use parking_lot::Mutex;
+use pathfinder_geometry::vector::Vector2F;
use postage::oneshot;
use smallvec::SmallVec;
use smol::prelude::*;
@@ -939,6 +940,7 @@ impl MutableAppContext {
window_id,
view_id,
titlebar_height,
+ mouse_position: Default::default(),
hovered_region_ids: Default::default(),
clicked_region_ids: None,
refreshing: false,
@@ -3895,6 +3897,7 @@ pub struct RenderParams {
pub window_id: usize,
pub view_id: usize,
pub titlebar_height: f32,
+ pub mouse_position: Vector2F,
pub hovered_region_ids: HashSet<MouseRegionId>,
pub clicked_region_ids: Option<(HashSet<MouseRegionId>, MouseButton)>,
pub refreshing: bool,
@@ -3905,6 +3908,7 @@ pub struct RenderContext<'a, T: View> {
pub(crate) window_id: usize,
pub(crate) view_id: usize,
pub(crate) view_type: PhantomData<T>,
+ pub(crate) mouse_position: Vector2F,
pub(crate) hovered_region_ids: HashSet<MouseRegionId>,
pub(crate) clicked_region_ids: Option<(HashSet<MouseRegionId>, MouseButton)>,
pub app: &'a mut MutableAppContext,
@@ -3916,12 +3920,19 @@ pub struct RenderContext<'a, T: View> {
#[derive(Clone, Default)]
pub struct MouseState {
hovered: bool,
+ mouse_position: Vector2F,
clicked: Option<MouseButton>,
+ accessed_mouse_position: bool,
accessed_hovered: bool,
accessed_clicked: bool,
}
impl MouseState {
+ pub fn mouse_position(&mut self) -> Vector2F {
+ self.accessed_mouse_position = true;
+ self.mouse_position
+ }
+
pub fn hovered(&mut self) -> bool {
self.accessed_hovered = true;
self.hovered
@@ -3932,6 +3943,10 @@ impl MouseState {
self.clicked
}
+ pub fn accessed_mouse_position(&self) -> bool {
+ self.accessed_mouse_position
+ }
+
pub fn accessed_hovered(&self) -> bool {
self.accessed_hovered
}
@@ -3949,6 +3964,7 @@ impl<'a, V: View> RenderContext<'a, V> {
view_id: params.view_id,
view_type: PhantomData,
titlebar_height: params.titlebar_height,
+ mouse_position: params.mouse_position,
hovered_region_ids: params.hovered_region_ids.clone(),
clicked_region_ids: params.clicked_region_ids.clone(),
refreshing: params.refreshing,
@@ -3971,6 +3987,7 @@ impl<'a, V: View> RenderContext<'a, V> {
pub fn mouse_state<Tag: 'static>(&self, region_id: usize) -> MouseState {
let region_id = MouseRegionId::new::<Tag>(self.view_id, region_id);
MouseState {
+ mouse_position: self.mouse_position.clone(),
hovered: self.hovered_region_ids.contains(®ion_id),
clicked: self.clicked_region_ids.as_ref().and_then(|(ids, button)| {
if ids.contains(®ion_id) {
@@ -3979,6 +3996,7 @@ impl<'a, V: View> RenderContext<'a, V> {
None
}
}),
+ accessed_mouse_position: false,
accessed_hovered: false,
accessed_clicked: false,
}
@@ -186,6 +186,7 @@ impl TestAppContext {
view_id: handle.id(),
view_type: PhantomData,
titlebar_height: 0.,
+ mouse_position: Default::default(),
hovered_region_ids: Default::default(),
clicked_region_ids: None,
refreshing: false,
@@ -21,6 +21,7 @@ pub struct MouseEventHandler<Tag: 'static> {
cursor_style: Option<CursorStyle>,
handlers: HandlerSet,
hoverable: bool,
+ notify_on_move: bool,
notify_on_hover: bool,
notify_on_click: bool,
above: bool,
@@ -28,9 +29,8 @@ pub struct MouseEventHandler<Tag: 'static> {
_tag: PhantomData<Tag>,
}
-// MouseEventHandler::new
-// MouseEventHandler::above
-
+/// 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
@@ -39,6 +39,7 @@ impl<Tag> MouseEventHandler<Tag> {
{
let mut mouse_state = cx.mouse_state::<Tag>(region_id);
let child = render_child(&mut mouse_state, cx);
+ let notify_on_move = mouse_state.accessed_mouse_position();
let notify_on_hover = mouse_state.accessed_hovered();
let notify_on_click = mouse_state.accessed_clicked();
Self {
@@ -46,6 +47,7 @@ impl<Tag> MouseEventHandler<Tag> {
region_id,
cursor_style: None,
handlers: Default::default(),
+ notify_on_move,
notify_on_hover,
notify_on_click,
hoverable: true,
@@ -55,6 +57,9 @@ impl<Tag> MouseEventHandler<Tag> {
}
}
+ /// 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,
@@ -183,6 +188,7 @@ impl<Tag> MouseEventHandler<Tag> {
self.handlers.clone(),
)
.with_hoverable(self.hoverable)
+ .with_notify_on_move(self.notify_on_move)
.with_notify_on_hover(self.notify_on_hover)
.with_notify_on_click(self.notify_on_click),
);
@@ -90,6 +90,7 @@ impl Presenter {
window_id: self.window_id,
view_id: *view_id,
titlebar_height: self.titlebar_height,
+ mouse_position: self.mouse_position.clone(),
hovered_region_ids: self.hovered_region_ids.clone(),
clicked_region_ids: self
.clicked_button
@@ -116,6 +117,7 @@ impl Presenter {
window_id: self.window_id,
view_id: *view_id,
titlebar_height: self.titlebar_height,
+ mouse_position: self.mouse_position.clone(),
hovered_region_ids: self.hovered_region_ids.clone(),
clicked_region_ids: self
.clicked_button
@@ -183,6 +185,7 @@ impl Presenter {
asset_cache: &self.asset_cache,
view_stack: Vec::new(),
refreshing,
+ mouse_position: self.mouse_position.clone(),
hovered_region_ids: self.hovered_region_ids.clone(),
clicked_region_ids: self
.clicked_button
@@ -231,6 +234,10 @@ impl Presenter {
let mut mouse_events = SmallVec::<[_; 2]>::new();
let mut notified_views: HashSet<usize> = Default::default();
+ if let Some(mouse_position) = event.position() {
+ self.mouse_position = mouse_position;
+ }
+
// 1. Handle platform event. Keyboard events get dispatched immediately, while mouse events
// get mapped into the mouse-specific MouseEvent type.
// -> These are usually small: [Mouse Down] or [Mouse up, Click] or [Mouse Moved, Mouse Dragged?]
@@ -402,10 +409,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());
}
}
}
@@ -447,6 +454,16 @@ impl Presenter {
}
}
}
+ MouseEvent::Move(_) => {
+ for (mouse_region, _) in self.mouse_regions.iter().rev() {
+ if mouse_region.bounds.contains_point(self.mouse_position) {
+ valid_regions.push(mouse_region.clone());
+ if mouse_region.notify_on_move {
+ notified_views.insert(mouse_region.id().view_id());
+ }
+ }
+ }
+ }
_ => {
for (mouse_region, _) in self.mouse_regions.iter().rev() {
// Contains
@@ -551,6 +568,7 @@ pub struct LayoutContext<'a> {
pub window_size: Vector2F,
titlebar_height: f32,
appearance: Appearance,
+ mouse_position: Vector2F,
hovered_region_ids: HashSet<MouseRegionId>,
clicked_region_ids: Option<(HashSet<MouseRegionId>, MouseButton)>,
}
@@ -622,6 +640,7 @@ impl<'a> LayoutContext<'a> {
view_id: handle.id(),
view_type: PhantomData,
titlebar_height: self.titlebar_height,
+ mouse_position: self.mouse_position.clone(),
hovered_region_ids: self.hovered_region_ids.clone(),
clicked_region_ids: self.clicked_region_ids.clone(),
refreshing: self.refreshing,
@@ -861,6 +880,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 {
@@ -20,6 +20,7 @@ pub struct MouseRegion {
pub bounds: RectF,
pub handlers: HandlerSet,
pub hoverable: bool,
+ pub notify_on_move: bool,
pub notify_on_hover: bool,
pub notify_on_click: bool,
}
@@ -54,6 +55,7 @@ impl MouseRegion {
bounds,
handlers,
hoverable: true,
+ notify_on_move: false,
notify_on_hover: false,
notify_on_click: false,
}
@@ -136,6 +138,11 @@ impl MouseRegion {
self
}
+ pub fn with_notify_on_move(mut self, notify: bool) -> Self {
+ self.notify_on_move = notify;
+ self
+ }
+
pub fn with_notify_on_hover(mut self, notify: bool) -> Self {
self.notify_on_hover = notify;
self
@@ -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 {
@@ -397,10 +397,10 @@ impl View for ToggleDockButton {
}
})
.with_cursor_style(CursorStyle::PointingHand)
- .on_up(MouseButton::Left, move |_, cx| {
+ .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(&dock_pane.downgrade(), drop_index, false, cx);
+ Pane::handle_dropped_item(event, &dock_pane.downgrade(), drop_index, false, None, cx);
});
if dock_position.is_visible() {
@@ -2,7 +2,7 @@ use super::{ItemHandle, SplitDirection};
use crate::{
dock::{icon_for_dock_anchor, AnchorDockBottom, AnchorDockRight, ExpandDock, HideDock},
toolbar::Toolbar,
- Item, NewFile, NewSearch, NewTerminal, WeakItemHandle, Workspace,
+ Item, NewFile, NewSearch, NewTerminal, SplitWithItem, WeakItemHandle, Workspace,
};
use anyhow::Result;
use collections::{HashMap, HashSet, VecDeque};
@@ -19,6 +19,7 @@ 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,
@@ -98,7 +99,7 @@ impl_internal_actions!(
DeploySplitMenu,
DeployNewMenu,
DeployDockMenu,
- MoveItem
+ MoveItem,
]
);
@@ -1097,7 +1098,7 @@ impl Pane {
ix == 0,
detail,
hovered,
- Self::tab_overlay_color(hovered, theme.as_ref(), cx),
+ Self::tab_overlay_color(hovered, cx),
tab_style,
cx,
)
@@ -1124,7 +1125,7 @@ impl Pane {
})
.on_up(MouseButton::Left, {
let pane = pane.clone();
- move |_, cx: &mut EventContext| Pane::handle_dropped_item(&pane, ix, true, cx)
+ move |event, cx| Pane::handle_dropped_item(event, &pane, ix, true, None, cx)
})
.as_draggable(
DraggedItem {
@@ -1164,14 +1165,14 @@ impl Pane {
.with_style(filler_style.container)
.with_border(filler_style.container.border);
- if let Some(overlay) = Self::tab_overlay_color(mouse_state.hovered(), &theme, cx) {
+ 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 |_, cx| {
- Pane::handle_dropped_item(&pane, filler_index, true, cx)
+ .on_up(MouseButton::Left, move |event, cx| {
+ Pane::handle_dropped_item(event, &pane, filler_index, true, None, cx)
})
.flex(1., true)
.named("filler"),
@@ -1320,17 +1321,64 @@ impl Pane {
tab.constrained().with_height(tab_style.height).boxed()
}
+ fn render_tab_bar_buttons(
+ &mut self,
+ theme: &Theme,
+ cx: &mut RenderContext<Self>,
+ ) -> 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()
+ }
+
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 pane != &dragged_item.pane || allow_same_pane {
+ if let Some(split_direction) = split_margin
+ .and_then(|margin| Self::drop_split_direction(event.position, event.region, margin))
+ {
+ cx.dispatch_action(SplitWithItem {
+ 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(),
@@ -1343,18 +1391,39 @@ impl Pane {
}
}
- fn tab_overlay_color(
- hovered: bool,
- theme: &Theme,
- cx: &mut RenderContext<Self>,
- ) -> Option<Color> {
+ 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 tab_overlay_color(hovered: bool, 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)
+ Some(
+ cx.global::<Settings>()
+ .theme
+ .workspace
+ .drop_target_overlay_color,
+ )
} else {
None
}
@@ -1389,55 +1458,7 @@ impl View for Pane {
// 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
@@ -1453,25 +1474,66 @@ impl View for Pane {
MouseEventHandler::<PaneContentTabDropTarget>::above(
0,
cx,
- |_, cx| {
- Flex::column()
+ |state, cx| {
+ let overlay_color = Self::tab_overlay_color(true, cx);
+ let drag_position = cx
+ .global::<DragAndDrop<Workspace>>()
+ .currently_dragged::<DraggedItem>(cx.window_id())
+ .map(|_| state.mouse_position());
+
+ Stack::new()
.with_child(
- ChildView::new(&self.toolbar, cx)
- .expanded()
- .boxed(),
- )
- .with_child(
- ChildView::new(active_item, cx)
- .flex(1., true)
+ 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| {
+ 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.,
+ });
+ })
+ .boxed()
+ }))
.boxed()
},
)
.on_up(MouseButton::Left, {
let pane = cx.handle();
- move |_, cx: &mut EventContext| {
- Pane::handle_dropped_item(&pane, drop_index, false, cx)
+ move |event, cx| {
+ Pane::handle_dropped_item(
+ event,
+ &pane,
+ drop_index,
+ false,
+ Some(100.), /* Use theme value */
+ cx,
+ )
}
})
.flex(1., true)
@@ -1493,8 +1555,8 @@ impl View for Pane {
})
.on_up(MouseButton::Left, {
let pane = this.clone();
- move |_, cx: &mut EventContext| {
- Pane::handle_dropped_item(&pane, 0, true, cx)
+ move |event, cx| {
+ Pane::handle_dropped_item(event, &pane, 0, true, None, cx)
}
})
.boxed()
@@ -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,12 @@ pub struct OpenSharedScreen {
pub peer_id: PeerId,
}
+pub struct SplitWithItem {
+ pane_to_split: WeakViewHandle<Pane>,
+ split_direction: SplitDirection,
+ item_id_to_move: usize,
+}
+
impl_internal_actions!(
workspace,
[
@@ -133,7 +139,8 @@ impl_internal_actions!(
ToggleFollow,
JoinProject,
OpenSharedScreen,
- RemoveWorktreeFromProject
+ RemoveWorktreeFromProject,
+ SplitWithItem,
]
);
impl_actions!(workspace, [ActivatePane]);
@@ -206,6 +213,22 @@ 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 {
+ pane_to_split,
+ item_id_to_move,
+ split_direction,
+ }: &_,
+ cx| {
+ workspace.split_pane_with_item(
+ 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 +1973,35 @@ impl Workspace {
})
}
+ pub fn split_pane_with_item(
+ &mut self,
+ pane_to_split: WeakViewHandle<Pane>,
+ item_id_to_move: usize,
+ split_direction: SplitDirection,
+ cx: &mut ViewContext<Self>,
+ ) {
+ if let Some(pane_to_split) = pane_to_split.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,
+ pane_to_split.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);
@@ -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),
+ 0.6
+ ),
};
}