workspace: Implement Extended Terminal Option (#26211)

Closes #10211 
Closes #7575 

Screenshot of feature:
![Screenshot 2025-03-06 at 1 08
13 PM](https://github.com/user-attachments/assets/73cc4519-248b-4264-9ce8-42d0980cf73c)

Screenshot of proposed menu:
![Screenshot 2025-03-06 at 1 14
30 PM](https://github.com/user-attachments/assets/efc7c18a-a2a5-491f-b3e5-5ed181f23906)

Screenshot of proposed menu closed:
![Screenshot 2025-03-06 at 1 14
57 PM](https://github.com/user-attachments/assets/0b42829c-abe3-48aa-9b81-30a0aeeac8fd)

Release Notes:

- Configuration of bottom_dock_layout in settings.json
- Layout Mode button in Title Bar
- 4 different layout modes for the bottom dock: contained (default),
full (extends below both docks), left-aligned, right-aligned (extends
only below the respective dock)

---------

Co-authored-by: Mikayla Maki <mikayla.c.maki@gmail.com>
This commit is contained in:
Thomas Jensen 2025-04-11 18:18:36 +02:00 committed by GitHub
parent 2f5c662c42
commit 1df01eabfe
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
6 changed files with 409 additions and 79 deletions

View file

@ -102,7 +102,7 @@ use ui::prelude::*;
use util::{ResultExt, TryFutureExt, paths::SanitizedPath, serde::default_true};
use uuid::Uuid;
pub use workspace_settings::{
AutosaveSetting, RestoreOnStartupBehavior, TabBarSettings, WorkspaceSettings,
AutosaveSetting, BottomDockLayout, RestoreOnStartupBehavior, TabBarSettings, WorkspaceSettings,
};
use crate::notifications::NotificationId;
@ -819,6 +819,7 @@ pub struct Workspace {
center: PaneGroup,
left_dock: Entity<Dock>,
bottom_dock: Entity<Dock>,
bottom_dock_layout: BottomDockLayout,
right_dock: Entity<Dock>,
panes: Vec<Entity<Pane>>,
panes_by_item: HashMap<EntityId, WeakEntity<Pane>>,
@ -1044,6 +1045,7 @@ impl Workspace {
let modal_layer = cx.new(|_| ModalLayer::new());
let toast_layer = cx.new(|_| ToastLayer::new());
let bottom_dock_layout = WorkspaceSettings::get_global(cx).bottom_dock_layout;
let left_dock = Dock::new(DockPosition::Left, modal_layer.clone(), window, cx);
let bottom_dock = Dock::new(DockPosition::Bottom, modal_layer.clone(), window, cx);
let right_dock = Dock::new(DockPosition::Right, modal_layer.clone(), window, cx);
@ -1141,6 +1143,7 @@ impl Workspace {
notifications: Default::default(),
left_dock,
bottom_dock,
bottom_dock_layout,
right_dock,
project: project.clone(),
follower_states: Default::default(),
@ -1349,6 +1352,26 @@ impl Workspace {
&self.bottom_dock
}
pub fn bottom_dock_layout(&self) -> BottomDockLayout {
self.bottom_dock_layout
}
pub fn set_bottom_dock_layout(
&mut self,
layout: BottomDockLayout,
window: &mut Window,
cx: &mut Context<Self>,
) {
let fs = self.project().read(cx).fs();
settings::update_settings_file::<WorkspaceSettings>(fs.clone(), cx, move |content, _cx| {
content.bottom_dock_layout = Some(layout);
});
self.bottom_dock_layout = layout;
cx.notify();
self.serialize_workspace(window, cx);
}
pub fn right_dock(&self) -> &Entity<Dock> {
&self.right_dock
}
@ -5535,64 +5558,248 @@ impl Render for Workspace {
},
))
})
.child(
div()
.flex()
.flex_row()
.h_full()
// Left Dock
.children(self.render_dock(
DockPosition::Left,
&self.left_dock,
window,
cx,
))
// Panes
.child(
div()
.flex()
.flex_col()
.flex_1()
.overflow_hidden()
.child(
h_flex()
.flex_1()
.when_some(paddings.0, |this, p| {
this.child(p.border_r_1())
})
.child(self.center.render(
self.zoomed.as_ref(),
&PaneRenderContext {
follower_states:
&self.follower_states,
active_call: self.active_call(),
active_pane: &self.active_pane,
app_state: &self.app_state,
project: &self.project,
workspace: &self.weak_self,
},
window,
cx,
))
.when_some(paddings.1, |this, p| {
this.child(p.border_l_1())
}),
)
.children(self.render_dock(
DockPosition::Bottom,
&self.bottom_dock,
window,
cx,
)),
)
// Right Dock
.children(self.render_dock(
DockPosition::Right,
&self.right_dock,
window,
cx,
)),
)
.child({
match self.bottom_dock_layout {
BottomDockLayout::Full => div()
.flex()
.flex_col()
.h_full()
.child(
div()
.flex()
.flex_row()
.flex_1()
.overflow_hidden()
.children(self.render_dock(
DockPosition::Left,
&self.left_dock,
window,
cx,
))
.child(
div()
.flex()
.flex_col()
.flex_1()
.overflow_hidden()
.child(
h_flex()
.flex_1()
.when_some(
paddings.0,
|this, p| {
this.child(
p.border_r_1(),
)
},
)
.child(self.center.render(
self.zoomed.as_ref(),
&PaneRenderContext {
follower_states:
&self.follower_states,
active_call: self.active_call(),
active_pane: &self.active_pane,
app_state: &self.app_state,
project: &self.project,
workspace: &self.weak_self,
},
window,
cx,
))
.when_some(
paddings.1,
|this, p| {
this.child(
p.border_l_1(),
)
},
),
),
)
.children(self.render_dock(
DockPosition::Right,
&self.right_dock,
window,
cx,
)),
)
.child(div().w_full().children(self.render_dock(
DockPosition::Bottom,
&self.bottom_dock,
window,
cx
))),
BottomDockLayout::LeftAligned => div()
.flex()
.flex_row()
.h_full()
.child(
div()
.flex()
.flex_col()
.flex_1()
.h_full()
.child(
div()
.flex()
.flex_row()
.flex_1()
.children(self.render_dock(DockPosition::Left, &self.left_dock, window, cx))
.child(
div()
.flex()
.flex_col()
.flex_1()
.overflow_hidden()
.child(
h_flex()
.flex_1()
.when_some(paddings.0, |this, p| this.child(p.border_r_1()))
.child(self.center.render(
self.zoomed.as_ref(),
&PaneRenderContext {
follower_states:
&self.follower_states,
active_call: self.active_call(),
active_pane: &self.active_pane,
app_state: &self.app_state,
project: &self.project,
workspace: &self.weak_self,
},
window,
cx,
))
.when_some(paddings.1, |this, p| this.child(p.border_l_1())),
)
)
)
.child(
div()
.w_full()
.children(self.render_dock(DockPosition::Bottom, &self.bottom_dock, window, cx))
),
)
.children(self.render_dock(
DockPosition::Right,
&self.right_dock,
window,
cx,
)),
BottomDockLayout::RightAligned => div()
.flex()
.flex_row()
.h_full()
.children(self.render_dock(
DockPosition::Left,
&self.left_dock,
window,
cx,
))
.child(
div()
.flex()
.flex_col()
.flex_1()
.h_full()
.child(
div()
.flex()
.flex_row()
.flex_1()
.child(
div()
.flex()
.flex_col()
.flex_1()
.overflow_hidden()
.child(
h_flex()
.flex_1()
.when_some(paddings.0, |this, p| this.child(p.border_r_1()))
.child(self.center.render(
self.zoomed.as_ref(),
&PaneRenderContext {
follower_states:
&self.follower_states,
active_call: self.active_call(),
active_pane: &self.active_pane,
app_state: &self.app_state,
project: &self.project,
workspace: &self.weak_self,
},
window,
cx,
))
.when_some(paddings.1, |this, p| this.child(p.border_l_1())),
)
)
.children(self.render_dock(DockPosition::Right, &self.right_dock, window, cx))
)
.child(
div()
.w_full()
.children(self.render_dock(DockPosition::Bottom, &self.bottom_dock, window, cx))
),
),
BottomDockLayout::Contained => div()
.flex()
.flex_row()
.h_full()
.children(self.render_dock(
DockPosition::Left,
&self.left_dock,
window,
cx,
))
.child(
div()
.flex()
.flex_col()
.flex_1()
.overflow_hidden()
.child(
h_flex()
.flex_1()
.when_some(paddings.0, |this, p| {
this.child(p.border_r_1())
})
.child(self.center.render(
self.zoomed.as_ref(),
&PaneRenderContext {
follower_states:
&self.follower_states,
active_call: self.active_call(),
active_pane: &self.active_pane,
app_state: &self.app_state,
project: &self.project,
workspace: &self.weak_self,
},
window,
cx,
))
.when_some(paddings.1, |this, p| {
this.child(p.border_l_1())
}),
)
.children(self.render_dock(
DockPosition::Bottom,
&self.bottom_dock,
window,
cx,
)),
)
.children(self.render_dock(
DockPosition::Right,
&self.right_dock,
window,
cx,
)),
}
})
.children(self.zoomed.as_ref().and_then(|view| {
let zoomed_view = view.upgrade()?;
let div = div()

View file

@ -10,6 +10,7 @@ use settings::{Settings, SettingsSources};
#[derive(Deserialize)]
pub struct WorkspaceSettings {
pub active_pane_modifiers: ActivePanelModifiers,
pub bottom_dock_layout: BottomDockLayout,
pub pane_split_direction_horizontal: PaneSplitDirectionHorizontal,
pub pane_split_direction_vertical: PaneSplitDirectionVertical,
pub centered_layout: CenteredLayoutSettings,
@ -71,6 +72,20 @@ pub struct ActivePanelModifiers {
pub inactive_opacity: Option<f32>,
}
#[derive(Copy, Clone, Debug, Default, Serialize, Deserialize, PartialEq, JsonSchema)]
#[serde(rename_all = "snake_case")]
pub enum BottomDockLayout {
/// Contained between the left and right docks
#[default]
Contained,
/// Takes up the full width of the window
Full,
/// Extends under the left dock while snapping to the right dock
LeftAligned,
/// Extends under the right dock while snapping to the left dock
RightAligned,
}
#[derive(Copy, Clone, Default, Serialize, Deserialize, JsonSchema)]
#[serde(rename_all = "snake_case")]
pub enum CloseWindowWhenNoItems {
@ -109,6 +124,10 @@ pub enum RestoreOnStartupBehavior {
pub struct WorkspaceSettingsContent {
/// Active pane styling settings.
pub active_pane_modifiers: Option<ActivePanelModifiers>,
/// Layout mode for the bottom dock
///
/// Default: contained
pub bottom_dock_layout: Option<BottomDockLayout>,
/// Direction to split horizontally.
///
/// Default: "up"