workspace: Add action to move focused panel to next dock position (#23317)
This Pull Request introduces a new command `workspace: move focused panel to next position` which finds the currently focused panel, if such panel exists, and moves it to the next valid dock position, following the order of `Left → Bottom → Right` and then starting again from the left position. In order to achieve this the following changes have been introduced: * Add a new default implementation for `PanelHandle`, namely `PanelHandle::move_to_next_position` which leverages `PanelHandle::position`, `PanelHandle::position_is_valid` and `PanelHandle::set_position` methods to update the panel's position to the next valid position. * Add a new method to the `workspace` module, ` move_focused_panel_to_next_position`, which is responsible for finding the currently focused panel, if such a panel exists, and calling the `move_to_next_position` method in the panel's handle. * Add a new action to the `workspace` module, `MoveFocusedPanelToNextPosition`, which is handled by the `move_focused_panel_to_next_position` method. Tests have also been added to the `workspace` module in order to guarantee that the action is correctly updating the focused panel's position. Here's a quick video of it, in action 🔽 https://github.com/user-attachments/assets/264d382b-5239-40aa-bc5e-5d569dec0734 Closes #23115 Release Notes: - Added new command to move the focused panel to the next valid dock position – `workspace: move focused panel to next position` .
This commit is contained in:
parent
7302be8ebd
commit
0ff803fe10
2 changed files with 102 additions and 0 deletions
|
@ -76,6 +76,21 @@ pub trait PanelHandle: Send + Sync {
|
||||||
fn focus_handle(&self, cx: &AppContext) -> FocusHandle;
|
fn focus_handle(&self, cx: &AppContext) -> FocusHandle;
|
||||||
fn to_any(&self) -> AnyView;
|
fn to_any(&self) -> AnyView;
|
||||||
fn activation_priority(&self, cx: &AppContext) -> u32;
|
fn activation_priority(&self, cx: &AppContext) -> u32;
|
||||||
|
fn move_to_next_position(&self, cx: &mut WindowContext) {
|
||||||
|
let current_position = self.position(cx);
|
||||||
|
let next_position = [
|
||||||
|
DockPosition::Left,
|
||||||
|
DockPosition::Bottom,
|
||||||
|
DockPosition::Right,
|
||||||
|
]
|
||||||
|
.into_iter()
|
||||||
|
.filter(|position| self.position_is_valid(*position, cx))
|
||||||
|
.skip_while(|valid_position| *valid_position != current_position)
|
||||||
|
.nth(1)
|
||||||
|
.unwrap_or(DockPosition::Left);
|
||||||
|
|
||||||
|
self.set_position(next_position, cx);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<T> PanelHandle for View<T>
|
impl<T> PanelHandle for View<T>
|
||||||
|
|
|
@ -133,6 +133,7 @@ actions!(
|
||||||
CopyRelativePath,
|
CopyRelativePath,
|
||||||
Feedback,
|
Feedback,
|
||||||
FollowNextCollaborator,
|
FollowNextCollaborator,
|
||||||
|
MoveFocusedPanelToNextPosition,
|
||||||
NewCenterTerminal,
|
NewCenterTerminal,
|
||||||
NewFile,
|
NewFile,
|
||||||
NewFileSplitVertical,
|
NewFileSplitVertical,
|
||||||
|
@ -1749,6 +1750,29 @@ impl Workspace {
|
||||||
.detach_and_log_err(cx)
|
.detach_and_log_err(cx)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn move_focused_panel_to_next_position(
|
||||||
|
&mut self,
|
||||||
|
_: &MoveFocusedPanelToNextPosition,
|
||||||
|
cx: &mut ViewContext<Self>,
|
||||||
|
) {
|
||||||
|
let docks = [&self.left_dock, &self.bottom_dock, &self.right_dock];
|
||||||
|
let active_dock = docks
|
||||||
|
.into_iter()
|
||||||
|
.find(|dock| dock.focus_handle(cx).contains_focused(cx));
|
||||||
|
|
||||||
|
if let Some(dock) = active_dock {
|
||||||
|
dock.update(cx, |dock, cx| {
|
||||||
|
let active_panel = dock
|
||||||
|
.active_panel()
|
||||||
|
.filter(|panel| panel.focus_handle(cx).contains_focused(cx));
|
||||||
|
|
||||||
|
if let Some(panel) = active_panel {
|
||||||
|
panel.move_to_next_position(cx);
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub fn prepare_to_close(
|
pub fn prepare_to_close(
|
||||||
&mut self,
|
&mut self,
|
||||||
close_intent: CloseIntent,
|
close_intent: CloseIntent,
|
||||||
|
@ -4492,6 +4516,7 @@ impl Workspace {
|
||||||
.on_action(cx.listener(Self::close_window))
|
.on_action(cx.listener(Self::close_window))
|
||||||
.on_action(cx.listener(Self::activate_pane_at_index))
|
.on_action(cx.listener(Self::activate_pane_at_index))
|
||||||
.on_action(cx.listener(Self::move_item_to_pane_at_index))
|
.on_action(cx.listener(Self::move_item_to_pane_at_index))
|
||||||
|
.on_action(cx.listener(Self::move_focused_panel_to_next_position))
|
||||||
.on_action(cx.listener(|workspace, _: &Unfollow, cx| {
|
.on_action(cx.listener(|workspace, _: &Unfollow, cx| {
|
||||||
let pane = workspace.active_pane().clone();
|
let pane = workspace.active_pane().clone();
|
||||||
workspace.unfollow_in_pane(&pane, cx);
|
workspace.unfollow_in_pane(&pane, cx);
|
||||||
|
@ -7971,6 +7996,68 @@ mod tests {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[gpui::test]
|
||||||
|
async fn test_move_focused_panel_to_next_position(cx: &mut gpui::TestAppContext) {
|
||||||
|
init_test(cx);
|
||||||
|
let fs = FakeFs::new(cx.executor());
|
||||||
|
let project = Project::test(fs, [], cx).await;
|
||||||
|
let (workspace, cx) = cx.add_window_view(|cx| Workspace::test_new(project, cx));
|
||||||
|
|
||||||
|
// Add a new panel to the right dock, opening the dock and setting the
|
||||||
|
// focus to the new panel.
|
||||||
|
let panel = workspace.update(cx, |workspace, cx| {
|
||||||
|
let panel = cx.new_view(|cx| TestPanel::new(DockPosition::Right, cx));
|
||||||
|
workspace.add_panel(panel.clone(), cx);
|
||||||
|
|
||||||
|
workspace
|
||||||
|
.right_dock()
|
||||||
|
.update(cx, |right_dock, cx| right_dock.set_open(true, cx));
|
||||||
|
|
||||||
|
workspace.toggle_panel_focus::<TestPanel>(cx);
|
||||||
|
|
||||||
|
panel
|
||||||
|
});
|
||||||
|
|
||||||
|
// Dispatch the `MoveFocusedPanelToNextPosition` action, moving the
|
||||||
|
// panel to the next valid position which, in this case, is the left
|
||||||
|
// dock.
|
||||||
|
cx.dispatch_action(MoveFocusedPanelToNextPosition);
|
||||||
|
workspace.update(cx, |workspace, cx| {
|
||||||
|
assert!(workspace.left_dock().read(cx).is_open());
|
||||||
|
assert_eq!(panel.read(cx).position, DockPosition::Left);
|
||||||
|
});
|
||||||
|
|
||||||
|
// Dispatch the `MoveFocusedPanelToNextPosition` action, moving the
|
||||||
|
// panel to the next valid position which, in this case, is the bottom
|
||||||
|
// dock.
|
||||||
|
cx.dispatch_action(MoveFocusedPanelToNextPosition);
|
||||||
|
workspace.update(cx, |workspace, cx| {
|
||||||
|
assert!(workspace.bottom_dock().read(cx).is_open());
|
||||||
|
assert_eq!(panel.read(cx).position, DockPosition::Bottom);
|
||||||
|
});
|
||||||
|
|
||||||
|
// Dispatch the `MoveFocusedPanelToNextPosition` action again, this time
|
||||||
|
// around moving the panel to its initial position, the right dock.
|
||||||
|
cx.dispatch_action(MoveFocusedPanelToNextPosition);
|
||||||
|
workspace.update(cx, |workspace, cx| {
|
||||||
|
assert!(workspace.right_dock().read(cx).is_open());
|
||||||
|
assert_eq!(panel.read(cx).position, DockPosition::Right);
|
||||||
|
});
|
||||||
|
|
||||||
|
// Remove focus from the panel, ensuring that, if the panel is not
|
||||||
|
// focused, the `MoveFocusedPanelToNextPosition` action does not update
|
||||||
|
// the panel's position, so the panel is still in the right dock.
|
||||||
|
workspace.update(cx, |workspace, cx| {
|
||||||
|
workspace.toggle_panel_focus::<TestPanel>(cx);
|
||||||
|
});
|
||||||
|
|
||||||
|
cx.dispatch_action(MoveFocusedPanelToNextPosition);
|
||||||
|
workspace.update(cx, |workspace, cx| {
|
||||||
|
assert!(workspace.right_dock().read(cx).is_open());
|
||||||
|
assert_eq!(panel.read(cx).position, DockPosition::Right);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
mod register_project_item_tests {
|
mod register_project_item_tests {
|
||||||
use gpui::Context as _;
|
use gpui::Context as _;
|
||||||
|
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue