Make the rules library window more consistent with the settings UI (#40948)

Danilo Leal created

Now that we have two surface areas that open as separate windows, it's
important they're consistent with one another. This PR make the settings
UI and rules library windows more similar by having them use the same
minimum window size and similar styles for their navbar given they have
fundamentally the same design (nav on the left and content on the
right).

Release Notes:

- N/A

Change summary

crates/gpui/src/window.rs                 |   7 +
crates/rules_library/src/rules_library.rs | 124 ++++++++++++------------
crates/settings_ui/src/settings_ui.rs     |  10 +-
3 files changed, 74 insertions(+), 67 deletions(-)

Detailed changes

crates/gpui/src/window.rs 🔗

@@ -60,6 +60,13 @@ pub use prompts::*;
 
 pub(crate) const DEFAULT_WINDOW_SIZE: Size<Pixels> = size(px(1536.), px(864.));
 
+/// A 6:5 aspect ratio minimum window size to be used for functional,
+/// additional-to-main-Zed windows, like the settings and rules library windows.
+pub const DEFAULT_ADDITIONAL_WINDOW_SIZE: Size<Pixels> = Size {
+    width: Pixels(900.),
+    height: Pixels(750.),
+};
+
 /// Represents the two different phases when dispatching events.
 #[derive(Default, Copy, Clone, Debug, Eq, PartialEq)]
 pub enum DispatchPhase {

crates/rules_library/src/rules_library.rs 🔗

@@ -3,9 +3,9 @@ use collections::{HashMap, HashSet};
 use editor::{CompletionProvider, SelectionEffects};
 use editor::{CurrentLineHighlight, Editor, EditorElement, EditorEvent, EditorStyle, actions::Tab};
 use gpui::{
-    Action, App, Bounds, Entity, EventEmitter, Focusable, PromptLevel, Subscription, Task,
-    TextStyle, TitlebarOptions, WindowBounds, WindowHandle, WindowOptions, actions, point, size,
-    transparent_black,
+    Action, App, Bounds, DEFAULT_ADDITIONAL_WINDOW_SIZE, Entity, EventEmitter, Focusable,
+    PromptLevel, Subscription, Task, TextStyle, TitlebarOptions, WindowBounds, WindowHandle,
+    WindowOptions, actions, point, size, transparent_black,
 };
 use language::{Buffer, LanguageRegistry, language_settings::SoftWrap};
 use language_model::{
@@ -129,13 +129,13 @@ pub fn open_rules_library(
                     titlebar: Some(TitlebarOptions {
                         title: Some("Rules Library".into()),
                         appears_transparent: true,
-                        traffic_light_position: Some(point(px(9.0), px(9.0))),
+                        traffic_light_position: Some(point(px(12.0), px(12.0))),
                     }),
                     app_id: Some(app_id.to_owned()),
                     window_bounds: Some(WindowBounds::Windowed(bounds)),
                     window_background: cx.theme().window_background_appearance(),
                     window_decorations: Some(window_decorations),
-                    window_min_size: Some(size(px(800.), px(600.))), // 4:3 Aspect Ratio
+                    window_min_size: Some(DEFAULT_ADDITIONAL_WINDOW_SIZE),
                     kind: gpui::WindowKind::Floating,
                     ..Default::default()
                 },
@@ -369,10 +369,9 @@ impl PickerDelegate for RulePickerDelegate {
                         .spacing(ListItemSpacing::Sparse)
                         .toggle_state(selected)
                         .child(
-                            h_flex()
-                                .h_5()
-                                .line_height(relative(1.))
-                                .child(Label::new(rule.title.clone().unwrap_or("Untitled".into()))),
+                            Label::new(rule.title.clone().unwrap_or("Untitled".into()))
+                                .truncate()
+                                .mr_10(),
                         )
                         .end_slot::<IconButton>(default.then(|| {
                             IconButton::new("toggle-default-rule", IconName::Paperclip)
@@ -453,13 +452,15 @@ impl PickerDelegate for RulePickerDelegate {
         cx: &mut Context<Picker<Self>>,
     ) -> Div {
         h_flex()
-            .bg(cx.theme().colors().editor_background)
-            .rounded_sm()
-            .overflow_hidden()
-            .flex_none()
             .py_1()
-            .px_2()
+            .px_1p5()
             .mx_1()
+            .gap_1p5()
+            .rounded_sm()
+            .bg(cx.theme().colors().editor_background)
+            .border_1()
+            .border_color(cx.theme().colors().border)
+            .child(Icon::new(IconName::MagnifyingGlass).color(Color::Muted))
             .child(editor.clone())
     }
 }
@@ -1096,11 +1097,11 @@ impl RulesLibrary {
         v_flex()
             .id("rule-list")
             .capture_action(cx.listener(Self::focus_active_rule))
-            .bg(cx.theme().colors().panel_background)
+            .px_1p5()
             .h_full()
-            .px_1()
-            .w_1_3()
+            .w_64()
             .overflow_x_hidden()
+            .bg(cx.theme().colors().panel_background)
             .child(
                 h_flex()
                     .p(DynamicSpacing::Base04.rems(cx))
@@ -1121,16 +1122,55 @@ impl RulesLibrary {
             .child(div().flex_grow().child(self.picker.clone()))
     }
 
+    fn render_active_rule_editor(
+        &self,
+        editor: &Entity<Editor>,
+        cx: &mut Context<Self>,
+    ) -> impl IntoElement {
+        let settings = ThemeSettings::get_global(cx);
+
+        div()
+            .w_full()
+            .on_action(cx.listener(Self::move_down_from_title))
+            .pl_1()
+            .border_1()
+            .border_color(transparent_black())
+            .rounded_sm()
+            .group_hover("active-editor-header", |this| {
+                this.border_color(cx.theme().colors().border_variant)
+            })
+            .child(EditorElement::new(
+                &editor,
+                EditorStyle {
+                    background: cx.theme().system().transparent,
+                    local_player: cx.theme().players().local(),
+                    text: TextStyle {
+                        color: cx.theme().colors().editor_foreground,
+                        font_family: settings.ui_font.family.clone(),
+                        font_features: settings.ui_font.features.clone(),
+                        font_size: HeadlineSize::Large.rems().into(),
+                        font_weight: settings.ui_font.weight,
+                        line_height: relative(settings.buffer_line_height.value()),
+                        ..Default::default()
+                    },
+                    scrollbar_width: Pixels::ZERO,
+                    syntax: cx.theme().syntax().clone(),
+                    status: cx.theme().status().clone(),
+                    inlay_hints_style: editor::make_inlay_hints_style(cx),
+                    edit_prediction_styles: editor::make_suggestion_styles(cx),
+                    ..EditorStyle::default()
+                },
+            ))
+    }
+
     fn render_active_rule(&mut self, cx: &mut Context<RulesLibrary>) -> gpui::Stateful<Div> {
         div()
-            .w_2_3()
-            .h_full()
             .id("rule-editor")
+            .h_full()
+            .flex_grow()
             .border_l_1()
             .border_color(cx.theme().colors().border)
             .bg(cx.theme().colors().editor_background)
-            .flex_none()
-            .min_w_64()
             .children(self.active_rule_id.and_then(|prompt_id| {
                 let rule_metadata = self.store.read(cx).metadata(prompt_id)?;
                 let rule_editor = &self.rule_editors[&prompt_id];
@@ -1138,7 +1178,6 @@ impl RulesLibrary {
                 let model = LanguageModelRegistry::read_global(cx)
                     .default_model()
                     .map(|default| default.model);
-                let settings = ThemeSettings::get_global(cx);
 
                 Some(
                     v_flex()
@@ -1158,46 +1197,7 @@ impl RulesLibrary {
                                 .gap_2()
                                 .justify_between()
                                 .child(
-                                    div()
-                                        .w_full()
-                                        .on_action(cx.listener(Self::move_down_from_title))
-                                        .pl_1()
-                                        .border_1()
-                                        .border_color(transparent_black())
-                                        .rounded_sm()
-                                        .group_hover("active-editor-header", |this| {
-                                            this.border_color(cx.theme().colors().border_variant)
-                                        })
-                                        .child(EditorElement::new(
-                                            &rule_editor.title_editor,
-                                            EditorStyle {
-                                                background: cx.theme().system().transparent,
-                                                local_player: cx.theme().players().local(),
-                                                text: TextStyle {
-                                                    color: cx.theme().colors().editor_foreground,
-                                                    font_family: settings.ui_font.family.clone(),
-                                                    font_features: settings
-                                                        .ui_font
-                                                        .features
-                                                        .clone(),
-                                                    font_size: HeadlineSize::Large.rems().into(),
-                                                    font_weight: settings.ui_font.weight,
-                                                    line_height: relative(
-                                                        settings.buffer_line_height.value(),
-                                                    ),
-                                                    ..Default::default()
-                                                },
-                                                scrollbar_width: Pixels::ZERO,
-                                                syntax: cx.theme().syntax().clone(),
-                                                status: cx.theme().status().clone(),
-                                                inlay_hints_style: editor::make_inlay_hints_style(
-                                                    cx,
-                                                ),
-                                                edit_prediction_styles:
-                                                    editor::make_suggestion_styles(cx),
-                                                ..EditorStyle::default()
-                                            },
-                                        )),
+                                    self.render_active_rule_editor(&rule_editor.title_editor, cx),
                                 )
                                 .child(
                                     h_flex()

crates/settings_ui/src/settings_ui.rs 🔗

@@ -6,10 +6,10 @@ use editor::{Editor, EditorEvent};
 use feature_flags::FeatureFlag;
 use fuzzy::StringMatchCandidate;
 use gpui::{
-    Action, App, Div, Entity, FocusHandle, Focusable, Global, ListState, ReadGlobal as _,
-    ScrollHandle, Stateful, Subscription, Task, TitlebarOptions, UniformListScrollHandle, Window,
-    WindowBounds, WindowHandle, WindowOptions, actions, div, list, point, prelude::*, px, size,
-    uniform_list,
+    Action, App, DEFAULT_ADDITIONAL_WINDOW_SIZE, Div, Entity, FocusHandle, Focusable, Global,
+    ListState, ReadGlobal as _, ScrollHandle, Stateful, Subscription, Task, TitlebarOptions,
+    UniformListScrollHandle, Window, WindowBounds, WindowHandle, WindowOptions, actions, div, list,
+    point, prelude::*, px, uniform_list,
 };
 use heck::ToTitleCase as _;
 use project::WorktreeId;
@@ -575,7 +575,7 @@ pub fn open_settings_editor(
     cx.defer(move |cx| {
         let current_rem_size: f32 = theme::ThemeSettings::get_global(cx).ui_font_size(cx).into();
 
-        let default_bounds = size(px(900.), px(750.)); // 4:3 Aspect Ratio
+        let default_bounds = DEFAULT_ADDITIONAL_WINDOW_SIZE;
         let default_rem_size = 16.0;
         let scale_factor = current_rem_size / default_rem_size;
         let scaled_bounds: gpui::Size<Pixels> = default_bounds.map(|axis| axis * scale_factor);