Detailed changes
@@ -260,6 +260,32 @@ impl strum::VariantNames for BaseKeymapContent {
];
}
+/// Position of window control buttons on Linux.
+///
+/// Valid values: "left" (macOS style) or "right" (Windows/Linux style)
+#[derive(
+ Copy,
+ Clone,
+ Debug,
+ Serialize,
+ Deserialize,
+ JsonSchema,
+ MergeFrom,
+ PartialEq,
+ Eq,
+ Default,
+ strum::VariantArray,
+ strum::VariantNames,
+)]
+#[serde(rename_all = "snake_case")]
+pub enum WindowControlsPosition {
+ /// Window controls on the left side (macOS style)
+ Left,
+ /// Window controls on the right side (Windows style)
+ #[default]
+ Right,
+}
+
#[skip_serializing_none]
#[derive(Clone, PartialEq, Default, Serialize, Deserialize, JsonSchema, MergeFrom, Debug)]
pub struct TitleBarSettingsContent {
@@ -291,6 +317,10 @@ pub struct TitleBarSettingsContent {
///
/// Default: false
pub show_menus: Option<bool>,
+ /// Position of window control buttons (minimize, maximize, close) on Linux.
+ ///
+ /// Default: right
+ pub window_controls_position: Option<WindowControlsPosition>,
}
/// Configuration of audio in Zed.
@@ -2,6 +2,7 @@ use gpui::{
AnyElement, Context, Decorations, Entity, Hsla, InteractiveElement, IntoElement, MouseButton,
ParentElement, Pixels, StatefulInteractiveElement, Styled, Window, WindowControlArea, div, px,
};
+use settings::{Settings, WindowControlsPosition};
use smallvec::SmallVec;
use std::mem;
use ui::prelude::*;
@@ -9,6 +10,7 @@ use ui::prelude::*;
use crate::{
platforms::{platform_linux, platform_mac, platform_windows},
system_window_tabs::SystemWindowTabs,
+ title_bar_settings::TitleBarSettings,
};
pub struct PlatformTitleBar {
@@ -134,35 +136,78 @@ impl Render for PlatformTitleBar {
PlatformStyle::Mac => title_bar,
PlatformStyle::Linux => {
if matches!(decorations, Decorations::Client { .. }) {
- title_bar
- .child(platform_linux::LinuxWindowControls::new(close_action))
- .when(supported_controls.window_menu, |titlebar| {
- titlebar
- .on_mouse_down(MouseButton::Right, move |ev, window, _| {
- window.show_window_menu(ev.position)
- })
- })
- .on_mouse_move(cx.listener(move |this, _ev, window, _| {
- if this.should_move {
- this.should_move = false;
- window.start_window_move();
- }
- }))
- .on_mouse_down_out(cx.listener(move |this, _ev, _window, _cx| {
- this.should_move = false;
- }))
- .on_mouse_up(
- MouseButton::Left,
- cx.listener(move |this, _ev, _window, _cx| {
- this.should_move = false;
- }),
- )
- .on_mouse_down(
- MouseButton::Left,
- cx.listener(move |this, _ev, _window, _cx| {
- this.should_move = true;
- }),
- )
+ let title_bar_settings = TitleBarSettings::get(None, cx);
+ match title_bar_settings.window_controls_position {
+ WindowControlsPosition::Left => h_flex()
+ .w_full()
+ .bg(titlebar_color)
+ .child(platform_linux::LinuxWindowControls::new(close_action))
+ .child(title_bar)
+ .when(supported_controls.window_menu, |titlebar| {
+ titlebar.on_mouse_down(
+ MouseButton::Right,
+ move |ev, window, _| {
+ window.show_window_menu(ev.position)
+ },
+ )
+ })
+ .on_mouse_move(cx.listener(move |this, _ev, window, _| {
+ if this.should_move {
+ this.should_move = false;
+ window.start_window_move();
+ }
+ }))
+ .on_mouse_down_out(cx.listener(
+ move |this, _ev, _window, _cx| {
+ this.should_move = false;
+ },
+ ))
+ .on_mouse_up(
+ MouseButton::Left,
+ cx.listener(move |this, _ev, _window, _cx| {
+ this.should_move = false;
+ }),
+ )
+ .on_mouse_down(
+ MouseButton::Left,
+ cx.listener(move |this, _ev, _window, _cx| {
+ this.should_move = true;
+ }),
+ ),
+ WindowControlsPosition::Right => title_bar
+ .child(platform_linux::LinuxWindowControls::new(close_action))
+ .when(supported_controls.window_menu, |titlebar| {
+ titlebar.on_mouse_down(
+ MouseButton::Right,
+ move |ev, window, _| {
+ window.show_window_menu(ev.position)
+ },
+ )
+ })
+ .on_mouse_move(cx.listener(move |this, _ev, window, _| {
+ if this.should_move {
+ this.should_move = false;
+ window.start_window_move();
+ }
+ }))
+ .on_mouse_down_out(cx.listener(
+ move |this, _ev, _window, _cx| {
+ this.should_move = false;
+ },
+ ))
+ .on_mouse_up(
+ MouseButton::Left,
+ cx.listener(move |this, _ev, _window, _cx| {
+ this.should_move = false;
+ }),
+ )
+ .on_mouse_down(
+ MouseButton::Left,
+ cx.listener(move |this, _ev, _window, _cx| {
+ this.should_move = true;
+ }),
+ ),
+ }
} else {
title_bar
}
@@ -1,4 +1,6 @@
+use crate::title_bar_settings::TitleBarSettings;
use gpui::{Action, Hsla, MouseButton, prelude::*, svg};
+use settings::{Settings, WindowControlsPosition};
use ui::prelude::*;
#[derive(IntoElement)]
@@ -14,33 +16,62 @@ impl LinuxWindowControls {
}
}
+impl LinuxWindowControls {
+ /// Builds the window controls based on the position setting.
+ fn build_controls(
+ position: WindowControlsPosition,
+ window: &Window,
+ close_action: Box<dyn Action>,
+ cx: &mut App,
+ ) -> Vec<WindowControl> {
+ let maximize_type = if window.is_maximized() {
+ WindowControlType::Restore
+ } else {
+ WindowControlType::Maximize
+ };
+
+ match position {
+ WindowControlsPosition::Left => {
+ // Left side: Close, Minimize, Maximize (left to right)
+ vec![
+ WindowControl::new_close("close", WindowControlType::Close, close_action, cx),
+ WindowControl::new("minimize", WindowControlType::Minimize, cx),
+ WindowControl::new("maximize-or-restore", maximize_type, cx),
+ ]
+ }
+ WindowControlsPosition::Right => {
+ // Right side: Minimize, Maximize, Close (left to right)
+ vec![
+ WindowControl::new("minimize", WindowControlType::Minimize, cx),
+ WindowControl::new("maximize-or-restore", maximize_type, cx),
+ WindowControl::new_close("close", WindowControlType::Close, close_action, cx),
+ ]
+ }
+ }
+ }
+}
+
impl RenderOnce for LinuxWindowControls {
fn render(self, window: &mut Window, cx: &mut App) -> impl IntoElement {
- h_flex()
+ let title_bar_settings = TitleBarSettings::get(None, cx);
+ let controls = Self::build_controls(
+ title_bar_settings.window_controls_position,
+ window,
+ self.close_window_action,
+ cx,
+ );
+
+ let mut element = h_flex()
.id("generic-window-controls")
.px_3()
.gap_3()
- .on_mouse_down(MouseButton::Left, |_, _, cx| cx.stop_propagation())
- .child(WindowControl::new(
- "minimize",
- WindowControlType::Minimize,
- cx,
- ))
- .child(WindowControl::new(
- "maximize-or-restore",
- if window.is_maximized() {
- WindowControlType::Restore
- } else {
- WindowControlType::Maximize
- },
- cx,
- ))
- .child(WindowControl::new_close(
- "close",
- WindowControlType::Close,
- self.close_window_action,
- cx,
- ))
+ .on_mouse_down(MouseButton::Left, |_, _, cx| cx.stop_propagation());
+
+ for control in controls {
+ element = element.child(control);
+ }
+
+ element
}
}
@@ -80,7 +111,7 @@ impl WindowControlStyle {
let colors = cx.theme().colors();
Self {
- background: colors.ghost_element_background,
+ background: colors.title_bar_background,
background_hover: colors.ghost_element_hover,
icon: colors.icon,
icon_hover: colors.icon_muted,
@@ -185,6 +216,7 @@ impl RenderOnce for WindowControl {
.rounded_2xl()
.w_5()
.h_5()
+ .bg(self.style.background)
.hover(|this| this.bg(self.style.background_hover))
.active(|this| this.bg(self.style.background_hover))
.child(icon)
@@ -1,4 +1,4 @@
-use settings::{Settings, SettingsContent};
+use settings::{Settings, SettingsContent, WindowControlsPosition};
#[derive(Copy, Clone, Debug)]
pub struct TitleBarSettings {
@@ -9,6 +9,7 @@ pub struct TitleBarSettings {
pub show_project_items: bool,
pub show_sign_in: bool,
pub show_menus: bool,
+ pub window_controls_position: WindowControlsPosition,
}
impl Settings for TitleBarSettings {
@@ -22,6 +23,7 @@ impl Settings for TitleBarSettings {
show_project_items: content.show_project_items.unwrap(),
show_sign_in: content.show_sign_in.unwrap(),
show_menus: content.show_menus.unwrap(),
+ window_controls_position: content.window_controls_position.unwrap_or_default(),
}
}
}