diff --git a/assets/icons/layout.svg b/assets/icons/layout.svg
new file mode 100644
index 0000000000..79464013b1
--- /dev/null
+++ b/assets/icons/layout.svg
@@ -0,0 +1,5 @@
+
diff --git a/assets/settings/default.json b/assets/settings/default.json
index 2a845fac0b..df1a4a01af 100644
--- a/assets/settings/default.json
+++ b/assets/settings/default.json
@@ -80,6 +80,8 @@
// Values are clamped to the [0.0, 1.0] range.
"inactive_opacity": 1.0
},
+ // Layout mode of the bottom dock. Defaults to "contained"
+ "bottom_dock_layout": "contained",
// The direction that you want to split panes horizontally. Defaults to "up"
"pane_split_direction_horizontal": "up",
// The direction that you want to split panes horizontally. Defaults to "left"
diff --git a/crates/icons/src/icons.rs b/crates/icons/src/icons.rs
index aa8dcaf587..6c448c03ed 100644
--- a/crates/icons/src/icons.rs
+++ b/crates/icons/src/icons.rs
@@ -10,8 +10,8 @@ use strum::{EnumIter, EnumString, IntoStaticStr};
pub enum IconName {
Ai,
AiAnthropic,
- AiBedrock,
AiAnthropicHosted,
+ AiBedrock,
AiDeepSeek,
AiEdit,
AiGoogle,
@@ -61,6 +61,7 @@ pub enum IconName {
CircleOff,
Clipboard,
Close,
+ Cloud,
Code,
Cog,
Command,
@@ -74,22 +75,22 @@ pub enum IconName {
CountdownTimer,
CursorIBeam,
Dash,
+ DatabaseZap,
+ Debug,
DebugBreakpoint,
+ DebugContinue,
DebugDisabledBreakpoint,
DebugDisabledLogBreakpoint,
+ DebugDisconnect,
DebugIgnoreBreakpoints,
+ DebugLogBreakpoint,
DebugPause,
- DebugContinue,
- DebugStepOver,
+ DebugRestart,
+ DebugStepBack,
DebugStepInto,
DebugStepOut,
- DebugStepBack,
- DebugRestart,
- Debug,
+ DebugStepOver,
DebugStop,
- DebugDisconnect,
- DebugLogBreakpoint,
- DatabaseZap,
Delete,
Diff,
Disconnected,
@@ -99,18 +100,18 @@ pub enum IconName {
Envelope,
Eraser,
Escape,
- ExpandVertical,
Exit,
- ExternalLink,
- ExpandUp,
ExpandDown,
+ ExpandUp,
+ ExpandVertical,
+ ExternalLink,
Eye,
File,
FileCode,
FileCreate,
FileDelete,
- FileDoc,
FileDiff,
+ FileDoc,
FileGeneric,
FileGit,
FileLock,
@@ -133,16 +134,17 @@ pub enum IconName {
GenericMaximize,
GenericMinimize,
GenericRestore,
- Github,
- Globe,
GitBranch,
GitBranchSmall,
+ Github,
+ Globe,
Hash,
HistoryRerun,
Indicator,
Info,
InlayHint,
Keyboard,
+ Layout,
Library,
LightBulb,
LineHeight,
@@ -155,7 +157,6 @@ pub enum IconName {
Maximize,
Menu,
MessageBubbles,
- Cloud,
Mic,
MicMute,
Microscope,
@@ -227,8 +228,8 @@ pub enum IconName {
Tab,
Terminal,
TextSnippet,
- ThumbsUp,
ThumbsDown,
+ ThumbsUp,
Trash,
TrashAlt,
Triangle,
@@ -247,10 +248,10 @@ pub enum IconName {
ZedAssistant,
ZedAssistantFilled,
ZedPredict,
- ZedPredictUp,
- ZedPredictDown,
ZedPredictDisabled,
+ ZedPredictDown,
ZedPredictError,
+ ZedPredictUp,
ZedXCopilot,
}
diff --git a/crates/title_bar/src/title_bar.rs b/crates/title_bar/src/title_bar.rs
index ed929cb1f3..225c3613ce 100644
--- a/crates/title_bar/src/title_bar.rs
+++ b/crates/title_bar/src/title_bar.rs
@@ -36,7 +36,7 @@ use ui::{
IconWithIndicator, Indicator, PopoverMenu, Tooltip, h_flex, prelude::*,
};
use util::ResultExt;
-use workspace::{Workspace, notifications::NotifyResultExt};
+use workspace::{BottomDockLayout, Workspace, notifications::NotifyResultExt};
use zed_actions::{OpenBrowser, OpenRecent, OpenRemote};
pub use onboarding_banner::restore_banner;
@@ -210,6 +210,7 @@ impl Render for TitleBar {
.pr_1()
.on_mouse_down(MouseButton::Left, |_, _, cx| cx.stop_propagation())
.children(self.render_call_controls(window, cx))
+ .child(self.render_bottom_dock_layout_menu(cx))
.map(|el| {
let status = self.client.status();
let status = &*status.borrow();
@@ -622,6 +623,101 @@ impl TitleBar {
}
}
+ pub fn render_bottom_dock_layout_menu(&self, cx: &mut Context) -> impl IntoElement {
+ let workspace = self.workspace.upgrade().unwrap();
+ let current_layout = workspace.update(cx, |workspace, _cx| workspace.bottom_dock_layout());
+
+ PopoverMenu::new("layout-menu")
+ .trigger(
+ IconButton::new("toggle_layout", IconName::Layout)
+ .icon_size(IconSize::Small)
+ .tooltip(Tooltip::text("Toggle Layout Menu")),
+ )
+ .anchor(gpui::Corner::TopRight)
+ .menu(move |window, cx| {
+ ContextMenu::build(window, cx, {
+ let workspace = workspace.clone();
+ move |menu, _, _| {
+ menu.label("Bottom Dock")
+ .separator()
+ .toggleable_entry(
+ "Contained",
+ current_layout == BottomDockLayout::Contained,
+ ui::IconPosition::End,
+ None,
+ {
+ let workspace = workspace.clone();
+ move |window, cx| {
+ workspace.update(cx, |workspace, cx| {
+ workspace.set_bottom_dock_layout(
+ BottomDockLayout::Contained,
+ window,
+ cx,
+ );
+ });
+ }
+ },
+ )
+ .toggleable_entry(
+ "Full",
+ current_layout == BottomDockLayout::Full,
+ ui::IconPosition::End,
+ None,
+ {
+ let workspace = workspace.clone();
+ move |window, cx| {
+ workspace.update(cx, |workspace, cx| {
+ workspace.set_bottom_dock_layout(
+ BottomDockLayout::Full,
+ window,
+ cx,
+ );
+ });
+ }
+ },
+ )
+ .toggleable_entry(
+ "Left Aligned",
+ current_layout == BottomDockLayout::LeftAligned,
+ ui::IconPosition::End,
+ None,
+ {
+ let workspace = workspace.clone();
+ move |window, cx| {
+ workspace.update(cx, |workspace, cx| {
+ workspace.set_bottom_dock_layout(
+ BottomDockLayout::LeftAligned,
+ window,
+ cx,
+ );
+ });
+ }
+ },
+ )
+ .toggleable_entry(
+ "Right Aligned",
+ current_layout == BottomDockLayout::RightAligned,
+ ui::IconPosition::End,
+ None,
+ {
+ let workspace = workspace.clone();
+ move |window, cx| {
+ workspace.update(cx, |workspace, cx| {
+ workspace.set_bottom_dock_layout(
+ BottomDockLayout::RightAligned,
+ window,
+ cx,
+ );
+ });
+ }
+ },
+ )
+ }
+ })
+ .into()
+ })
+ }
+
pub fn render_sign_in_button(&mut self, _: &mut Context) -> Button {
let client = self.client.clone();
Button::new("sign_in", "Sign in")
diff --git a/crates/workspace/src/workspace.rs b/crates/workspace/src/workspace.rs
index 5a9dce7c01..01d836f48d 100644
--- a/crates/workspace/src/workspace.rs
+++ b/crates/workspace/src/workspace.rs
@@ -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,
bottom_dock: Entity,
+ bottom_dock_layout: BottomDockLayout,
right_dock: Entity,
panes: Vec>,
panes_by_item: HashMap>,
@@ -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,
+ ) {
+ let fs = self.project().read(cx).fs();
+ settings::update_settings_file::(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 {
&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()
diff --git a/crates/workspace/src/workspace_settings.rs b/crates/workspace/src/workspace_settings.rs
index 2dda042288..a61a987b1c 100644
--- a/crates/workspace/src/workspace_settings.rs
+++ b/crates/workspace/src/workspace_settings.rs
@@ -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,
}
+#[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,
+ /// Layout mode for the bottom dock
+ ///
+ /// Default: contained
+ pub bottom_dock_layout: Option,
/// Direction to split horizontally.
///
/// Default: "up"