diff --git a/Cargo.lock b/Cargo.lock index b8385ee20db9f68dd981c0544e89f3609f96fc98..1f208e459a7eb0323aa378a168c3648052135200 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -15975,6 +15975,7 @@ dependencies = [ "gpui", "language_model", "menu", + "platform_title_bar", "pretty_assertions", "project", "prompt_store", diff --git a/crates/platform_title_bar/src/platform_title_bar.rs b/crates/platform_title_bar/src/platform_title_bar.rs index 777dd9e2d905e7ba54fcb97b9912610b04c49527..c009d146403b21e592457d0c9a3f24819e80d642 100644 --- a/crates/platform_title_bar/src/platform_title_bar.rs +++ b/crates/platform_title_bar/src/platform_title_bar.rs @@ -1,4 +1,4 @@ -mod platforms; +pub mod platforms; mod system_window_tabs; use feature_flags::{AgentV2FeatureFlag, FeatureFlagAppExt}; @@ -115,6 +115,72 @@ impl PlatformTitleBar { } } +/// Renders the platform-appropriate left-side window controls (e.g. Ubuntu/GNOME close button). +/// +/// Only relevant on Linux with client-side decorations when the window manager +/// places controls on the left. +pub fn render_left_window_controls( + button_layout: Option, + close_action: Box, + window: &Window, +) -> Option { + if PlatformStyle::platform() != PlatformStyle::Linux { + return None; + } + if !matches!(window.window_decorations(), Decorations::Client { .. }) { + return None; + } + let button_layout = button_layout?; + if button_layout.left[0].is_none() { + return None; + } + Some( + platform_linux::LinuxWindowControls::new( + "left-window-controls", + button_layout.left, + close_action, + ) + .into_any_element(), + ) +} + +/// Renders the platform-appropriate right-side window controls (close, minimize, maximize). +/// +/// Returns `None` on Mac or when the platform doesn't need custom controls +/// (e.g. Linux with server-side decorations). +pub fn render_right_window_controls( + button_layout: Option, + close_action: Box, + window: &Window, +) -> Option { + let decorations = window.window_decorations(); + let height = platform_title_bar_height(window); + + match PlatformStyle::platform() { + PlatformStyle::Linux => { + if !matches!(decorations, Decorations::Client { .. }) { + return None; + } + let button_layout = button_layout?; + if button_layout.right[0].is_none() { + return None; + } + Some( + platform_linux::LinuxWindowControls::new( + "right-window-controls", + button_layout.right, + close_action, + ) + .into_any_element(), + ) + } + PlatformStyle::Windows => { + Some(platform_windows::WindowsWindowControls::new(height).into_any_element()) + } + PlatformStyle::Mac => None, + } +} + impl Render for PlatformTitleBar { fn render(&mut self, window: &mut Window, cx: &mut Context) -> impl IntoElement { let supported_controls = window.window_controls(); @@ -173,20 +239,23 @@ impl Render for PlatformTitleBar { }) }) .map(|this| { + let show_left_controls = !(sidebar.open && sidebar.side == SidebarSide::Left); + if window.is_fullscreen() { this.pl_2() - } else if self.platform_style == PlatformStyle::Mac - && !(sidebar.open && sidebar.side == SidebarSide::Left) - { + } else if self.platform_style == PlatformStyle::Mac && show_left_controls { this.pl(px(TRAFFIC_LIGHT_PADDING)) - } else if let Some(button_layout) = - button_layout.filter(|button_layout| button_layout.left[0].is_some()) + } else if let Some(controls) = show_left_controls + .then(|| { + render_left_window_controls( + button_layout, + close_action.as_ref().boxed_clone(), + window, + ) + }) + .flatten() { - this.child(platform_linux::LinuxWindowControls::new( - "left-window-controls", - button_layout.left, - close_action.as_ref().boxed_clone(), - )) + this.child(controls) } else { this.pl_2() } @@ -224,33 +293,30 @@ impl Render for PlatformTitleBar { .children(children), ) .when(!window.is_fullscreen(), |title_bar| { - match self.platform_style { - PlatformStyle::Mac => title_bar, - PlatformStyle::Linux => { - if matches!(decorations, Decorations::Client { .. }) { - let mut result = title_bar; - if let Some(button_layout) = button_layout - .filter(|button_layout| button_layout.right[0].is_some()) - { - result = result.child(platform_linux::LinuxWindowControls::new( - "right-window-controls", - button_layout.right, - close_action.as_ref().boxed_clone(), - )); - } + let show_right_controls = !(sidebar.open && sidebar.side == SidebarSide::Right); - result.when(supported_controls.window_menu, |titlebar| { - titlebar.on_mouse_down(MouseButton::Right, move |ev, window, _| { - window.show_window_menu(ev.position) - }) - }) - } else { - title_bar - } - } - PlatformStyle::Windows => { - title_bar.child(platform_windows::WindowsWindowControls::new(height)) - } + let title_bar = title_bar.children( + show_right_controls + .then(|| { + render_right_window_controls( + button_layout, + close_action.as_ref().boxed_clone(), + window, + ) + }) + .flatten(), + ); + + if self.platform_style == PlatformStyle::Linux + && matches!(decorations, Decorations::Client { .. }) + { + title_bar.when(supported_controls.window_menu, |titlebar| { + titlebar.on_mouse_down(MouseButton::Right, move |ev, window, _| { + window.show_window_menu(ev.position) + }) + }) + } else { + title_bar } }); diff --git a/crates/sidebar/Cargo.toml b/crates/sidebar/Cargo.toml index f66b1632657c9045575f55703be3651efb570bdb..e367d7dc676994cb7facfa1d43ecae6349a2b88e 100644 --- a/crates/sidebar/Cargo.toml +++ b/crates/sidebar/Cargo.toml @@ -30,6 +30,7 @@ fs.workspace = true git.workspace = true gpui.workspace = true menu.workspace = true +platform_title_bar.workspace = true project.workspace = true recent_projects.workspace = true remote.workspace = true diff --git a/crates/sidebar/src/sidebar.rs b/crates/sidebar/src/sidebar.rs index df0d43ea03eb8e411050340eb81d1c1002585266..27dbe85b6c03378907e0abda766a08407b8bbe73 100644 --- a/crates/sidebar/src/sidebar.rs +++ b/crates/sidebar/src/sidebar.rs @@ -39,8 +39,8 @@ use ui::{ use util::ResultExt as _; use util::path_list::PathList; use workspace::{ - AddFolderToProject, FocusWorkspaceSidebar, MultiWorkspace, MultiWorkspaceEvent, Open, - Sidebar as WorkspaceSidebar, SidebarSide, ToggleWorkspaceSidebar, Workspace, WorkspaceId, + AddFolderToProject, CloseWindow, FocusWorkspaceSidebar, MultiWorkspace, MultiWorkspaceEvent, + Open, Sidebar as WorkspaceSidebar, SidebarSide, ToggleWorkspaceSidebar, Workspace, WorkspaceId, sidebar_side_context_menu, }; @@ -3061,22 +3061,31 @@ impl Sidebar { ) -> impl IntoElement { let has_query = self.has_filter_query(cx); let sidebar_on_left = self.side(cx) == SidebarSide::Left; - let traffic_lights = - cfg!(target_os = "macos") && !window.is_fullscreen() && sidebar_on_left; + let sidebar_on_right = self.side(cx) == SidebarSide::Right; + let not_fullscreen = !window.is_fullscreen(); + let traffic_lights = cfg!(target_os = "macos") && not_fullscreen && sidebar_on_left; + let left_window_controls = !cfg!(target_os = "macos") && not_fullscreen && sidebar_on_left; + let right_window_controls = + !cfg!(target_os = "macos") && not_fullscreen && sidebar_on_right; let header_height = platform_title_bar_height(window); h_flex() .h(header_height) .mt_px() .pb_px() + .when(left_window_controls, |this| { + this.children(Self::render_left_window_controls(window, cx)) + }) .map(|this| { if traffic_lights { this.pl(px(ui::utils::TRAFFIC_LIGHT_PADDING)) - } else { + } else if !left_window_controls { this.pl_1p5() + } else { + this } }) - .pr_1p5() + .when(!right_window_controls, |this| this.pr_1p5()) .gap_1() .when(!no_open_projects, |this| { this.border_b_1() @@ -3113,6 +3122,25 @@ impl Sidebar { }), ) }) + .when(right_window_controls, |this| { + this.children(Self::render_right_window_controls(window, cx)) + }) + } + + fn render_left_window_controls(window: &Window, cx: &mut App) -> Option { + platform_title_bar::render_left_window_controls( + cx.button_layout(), + Box::new(CloseWindow), + window, + ) + } + + fn render_right_window_controls(window: &Window, cx: &mut App) -> Option { + platform_title_bar::render_right_window_controls( + cx.button_layout(), + Box::new(CloseWindow), + window, + ) } fn render_sidebar_toggle_button(&self, _cx: &mut Context) -> impl IntoElement {