title_bar: Add setting to always show menu for Linux and Windows (#34139)

Smit Barmase , Cole Miller , and Danilo Leal created

Closes #22869

Release Notes:

- Added `show_menus` setting to always show menu bar for Linux and
Windows.

---------

Co-authored-by: Cole Miller <cole@zed.dev>
Co-authored-by: Danilo Leal <daniloleal09@gmail.com>

Change summary

assets/settings/default.json               |  4 +
crates/gpui/src/platform/mac/platform.rs   | 21 ++++++---
crates/title_bar/src/application_menu.rs   | 15 +++++-
crates/title_bar/src/platform_title_bar.rs | 26 ++++++----
crates/title_bar/src/title_bar.rs          | 55 ++++++++++++++++++++---
crates/title_bar/src/title_bar_settings.rs |  5 ++
docs/src/visual-customization.md           |  3 
7 files changed, 97 insertions(+), 32 deletions(-)

Detailed changes

assets/settings/default.json 🔗

@@ -362,7 +362,9 @@
     // Whether to show user picture in the titlebar.
     "show_user_picture": true,
     // Whether to show the sign in button in the titlebar.
-    "show_sign_in": true
+    "show_sign_in": true,
+    // Whether to show the menus in the titlebar.
+    "show_menus": false
   },
   // Scrollbar related settings
   "scrollbar": {

crates/gpui/src/platform/mac/platform.rs 🔗

@@ -7,7 +7,7 @@ use super::{
 use crate::{
     Action, AnyWindowHandle, BackgroundExecutor, ClipboardEntry, ClipboardItem, ClipboardString,
     CursorStyle, ForegroundExecutor, Image, ImageFormat, KeyContext, Keymap, MacDispatcher,
-    MacDisplay, MacWindow, Menu, MenuItem, PathPromptOptions, Platform, PlatformDisplay,
+    MacDisplay, MacWindow, Menu, MenuItem, OwnedMenu, PathPromptOptions, Platform, PlatformDisplay,
     PlatformKeyboardLayout, PlatformTextSystem, PlatformWindow, Result, SemanticVersion, Task,
     WindowAppearance, WindowParams, hash,
 };
@@ -170,6 +170,7 @@ pub(crate) struct MacPlatformState {
     open_urls: Option<Box<dyn FnMut(Vec<String>)>>,
     finish_launching: Option<Box<dyn FnOnce()>>,
     dock_menu: Option<id>,
+    menus: Option<Vec<OwnedMenu>>,
 }
 
 impl Default for MacPlatform {
@@ -207,6 +208,7 @@ impl MacPlatform {
             finish_launching: None,
             dock_menu: None,
             on_keyboard_layout_change: None,
+            menus: None,
         }))
     }
 
@@ -226,7 +228,7 @@ impl MacPlatform {
 
     unsafe fn create_menu_bar(
         &self,
-        menus: Vec<Menu>,
+        menus: &Vec<Menu>,
         delegate: id,
         actions: &mut Vec<Box<dyn Action>>,
         keymap: &Keymap,
@@ -241,7 +243,7 @@ impl MacPlatform {
                 menu.setTitle_(menu_title);
                 menu.setDelegate_(delegate);
 
-                for item_config in menu_config.items {
+                for item_config in &menu_config.items {
                     menu.addItem_(Self::create_menu_item(
                         item_config,
                         delegate,
@@ -277,7 +279,7 @@ impl MacPlatform {
             dock_menu.setDelegate_(delegate);
             for item_config in menu_items {
                 dock_menu.addItem_(Self::create_menu_item(
-                    item_config,
+                    &item_config,
                     delegate,
                     actions,
                     keymap,
@@ -289,7 +291,7 @@ impl MacPlatform {
     }
 
     unsafe fn create_menu_item(
-        item: MenuItem,
+        item: &MenuItem,
         delegate: id,
         actions: &mut Vec<Box<dyn Action>>,
         keymap: &Keymap,
@@ -399,7 +401,7 @@ impl MacPlatform {
 
                     let tag = actions.len() as NSInteger;
                     let _: () = msg_send![item, setTag: tag];
-                    actions.push(action);
+                    actions.push(action.boxed_clone());
                     item
                 }
                 MenuItem::Submenu(Menu { name, items }) => {
@@ -865,10 +867,15 @@ impl Platform for MacPlatform {
             let app: id = msg_send![APP_CLASS, sharedApplication];
             let mut state = self.0.lock();
             let actions = &mut state.menu_actions;
-            let menu = self.create_menu_bar(menus, NSWindow::delegate(app), actions, keymap);
+            let menu = self.create_menu_bar(&menus, NSWindow::delegate(app), actions, keymap);
             drop(state);
             app.setMainMenu_(menu);
         }
+        self.0.lock().menus = Some(menus.into_iter().map(|menu| menu.owned()).collect());
+    }
+
+    fn get_menus(&self) -> Option<Vec<OwnedMenu>> {
+        self.0.lock().menus.clone()
     }
 
     fn set_dock_menu(&self, menu: Vec<MenuItem>, keymap: &Keymap) {

crates/title_bar/src/application_menu.rs 🔗

@@ -1,4 +1,5 @@
 use gpui::{Entity, OwnedMenu, OwnedMenuItem};
+use settings::Settings;
 
 #[cfg(not(target_os = "macos"))]
 use gpui::{Action, actions};
@@ -11,6 +12,8 @@ use serde::Deserialize;
 use smallvec::SmallVec;
 use ui::{ContextMenu, PopoverMenu, PopoverMenuHandle, Tooltip, prelude::*};
 
+use crate::title_bar_settings::TitleBarSettings;
+
 #[cfg(not(target_os = "macos"))]
 actions!(
     app_menu,
@@ -242,15 +245,21 @@ impl ApplicationMenu {
         cx.defer_in(window, move |_, window, cx| next_handle.show(window, cx));
     }
 
-    pub fn all_menus_shown(&self) -> bool {
-        self.entries.iter().any(|entry| entry.handle.is_deployed())
+    pub fn all_menus_shown(&self, cx: &mut Context<Self>) -> bool {
+        show_menus(cx)
+            || self.entries.iter().any(|entry| entry.handle.is_deployed())
             || self.pending_menu_open.is_some()
     }
 }
 
+pub(crate) fn show_menus(cx: &mut App) -> bool {
+    TitleBarSettings::get_global(cx).show_menus
+        && (cfg!(not(target_os = "macos")) || option_env!("ZED_USE_CROSS_PLATFORM_MENU").is_some())
+}
+
 impl Render for ApplicationMenu {
     fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
-        let all_menus_shown = self.all_menus_shown();
+        let all_menus_shown = self.all_menus_shown(cx);
 
         if let Some(pending_menu_open) = self.pending_menu_open.take() {
             if let Some(entry) = self

crates/title_bar/src/platform_title_bar.rs 🔗

@@ -1,6 +1,6 @@
 use gpui::{
-    AnyElement, Context, Decorations, InteractiveElement, IntoElement, MouseButton, ParentElement,
-    Pixels, StatefulInteractiveElement, Styled, Window, WindowControlArea, div, px,
+    AnyElement, Context, Decorations, Hsla, InteractiveElement, IntoElement, MouseButton,
+    ParentElement, Pixels, StatefulInteractiveElement, Styled, Window, WindowControlArea, div, px,
 };
 use smallvec::SmallVec;
 use std::mem;
@@ -37,6 +37,18 @@ impl PlatformTitleBar {
         px(32.)
     }
 
+    pub fn title_bar_color(&self, window: &mut Window, cx: &mut Context<Self>) -> Hsla {
+        if cfg!(any(target_os = "linux", target_os = "freebsd")) {
+            if window.is_window_active() && !self.should_move {
+                cx.theme().colors().title_bar_background
+            } else {
+                cx.theme().colors().title_bar_inactive_background
+            }
+        } else {
+            cx.theme().colors().title_bar_background
+        }
+    }
+
     pub fn set_children<T>(&mut self, children: T)
     where
         T: IntoIterator<Item = AnyElement>,
@@ -50,15 +62,7 @@ impl Render for PlatformTitleBar {
         let supported_controls = window.window_controls();
         let decorations = window.window_decorations();
         let height = Self::height(window);
-        let titlebar_color = if cfg!(any(target_os = "linux", target_os = "freebsd")) {
-            if window.is_window_active() && !self.should_move {
-                cx.theme().colors().title_bar_background
-            } else {
-                cx.theme().colors().title_bar_inactive_background
-            }
-        } else {
-            cx.theme().colors().title_bar_background
-        };
+        let titlebar_color = self.title_bar_color(window, cx);
         let close_action = Box::new(workspace::CloseWindow);
         let children = mem::take(&mut self.children);
 

crates/title_bar/src/title_bar.rs 🔗

@@ -8,7 +8,10 @@ mod title_bar_settings;
 #[cfg(feature = "stories")]
 mod stories;
 
-use crate::{application_menu::ApplicationMenu, platform_title_bar::PlatformTitleBar};
+use crate::{
+    application_menu::{ApplicationMenu, show_menus},
+    platform_title_bar::PlatformTitleBar,
+};
 
 #[cfg(not(target_os = "macos"))]
 use crate::application_menu::{
@@ -133,6 +136,8 @@ impl Render for TitleBar {
     fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
         let title_bar_settings = *TitleBarSettings::get_global(cx);
 
+        let show_menus = show_menus(cx);
+
         let mut children = Vec::new();
 
         children.push(
@@ -142,10 +147,14 @@ impl Render for TitleBar {
                     let mut render_project_items = title_bar_settings.show_branch_name
                         || title_bar_settings.show_project_items;
                     title_bar
-                        .when_some(self.application_menu.clone(), |title_bar, menu| {
-                            render_project_items &= !menu.read(cx).all_menus_shown();
-                            title_bar.child(menu)
-                        })
+                        .when_some(
+                            self.application_menu.clone().filter(|_| !show_menus),
+                            |title_bar, menu| {
+                                render_project_items &=
+                                    !menu.update(cx, |menu, cx| menu.all_menus_shown(cx));
+                                title_bar.child(menu)
+                            },
+                        )
                         .when(render_project_items, |title_bar| {
                             title_bar
                                 .when(title_bar_settings.show_project_items, |title_bar| {
@@ -190,11 +199,39 @@ impl Render for TitleBar {
                 .into_any_element(),
         );
 
-        self.platform_titlebar.update(cx, |this, _| {
-            this.set_children(children);
-        });
+        if show_menus {
+            self.platform_titlebar.update(cx, |this, _| {
+                this.set_children(
+                    self.application_menu
+                        .clone()
+                        .map(|menu| menu.into_any_element()),
+                );
+            });
 
-        self.platform_titlebar.clone().into_any_element()
+            let height = PlatformTitleBar::height(window);
+            let title_bar_color = self.platform_titlebar.update(cx, |platform_titlebar, cx| {
+                platform_titlebar.title_bar_color(window, cx)
+            });
+
+            v_flex()
+                .w_full()
+                .child(self.platform_titlebar.clone().into_any_element())
+                .child(
+                    h_flex()
+                        .bg(title_bar_color)
+                        .h(height)
+                        .pl_2()
+                        .justify_between()
+                        .w_full()
+                        .children(children),
+                )
+                .into_any_element()
+        } else {
+            self.platform_titlebar.update(cx, |this, _| {
+                this.set_children(children);
+            });
+            self.platform_titlebar.clone().into_any_element()
+        }
     }
 }
 

crates/title_bar/src/title_bar_settings.rs 🔗

@@ -11,6 +11,7 @@ pub struct TitleBarSettings {
     pub show_branch_name: bool,
     pub show_project_items: bool,
     pub show_sign_in: bool,
+    pub show_menus: bool,
 }
 
 #[derive(Copy, Clone, Default, Serialize, Deserialize, JsonSchema, Debug)]
@@ -39,6 +40,10 @@ pub struct TitleBarSettingsContent {
     ///
     /// Default: true
     pub show_sign_in: Option<bool>,
+    /// Whether to show the menus in the title bar.
+    ///
+    /// Default: false
+    pub show_menus: Option<bool>,
 }
 
 impl Settings for TitleBarSettings {

docs/src/visual-customization.md 🔗

@@ -112,7 +112,8 @@ To disable this behavior use:
     "show_project_items": true,     // Show/hide project host and name
     "show_onboarding_banner": true, // Show/hide onboarding banners
     "show_user_picture": true,      // Show/hide user avatar
-    "show_sign_in": true            // Show/hide sign-in button
+    "show_sign_in": true,           // Show/hide sign-in button
+    "show_menus": false             // Show/hide menus
   },
 ```