settings ui: Use the tree view item component and other design tweaks (#39329)

Danilo Leal created

An initial pass at some foundational styles.

Release Notes:

- N/A

Change summary

crates/settings_ui/src/components.rs  |   9 +
crates/settings_ui/src/settings_ui.rs | 114 ++++++++++++++--------------
2 files changed, 64 insertions(+), 59 deletions(-)

Detailed changes

crates/settings_ui/src/components.rs 🔗

@@ -57,11 +57,14 @@ impl RenderOnce for SettingsEditor {
         let theme_colors = cx.theme().colors();
 
         div()
-            .child(editor)
-            .bg(theme_colors.editor_background)
+            .py_1()
+            .px_2()
+            .min_w_64()
+            .rounded_md()
             .border_1()
-            .rounded_lg()
             .border_color(theme_colors.border)
+            .bg(theme_colors.editor_background)
+            .child(editor)
             .when_some(self.confirm, |this, confirm| {
                 this.on_action::<menu::Confirm>({
                     move |_, _, cx| {

crates/settings_ui/src/settings_ui.rs 🔗

@@ -4,9 +4,8 @@ use editor::{Editor, EditorEvent};
 use feature_flags::{FeatureFlag, FeatureFlagAppExt as _};
 use fuzzy::StringMatchCandidate;
 use gpui::{
-    App, AppContext as _, Context, Div, Entity, Global, IntoElement, ReadGlobal as _, Render, Task,
-    TitlebarOptions, UniformListScrollHandle, Window, WindowHandle, WindowOptions, actions, div,
-    point, px, size, uniform_list,
+    App, Div, Entity, Global, ReadGlobal as _, Task, TitlebarOptions, UniformListScrollHandle,
+    Window, WindowHandle, WindowOptions, actions, div, point, px, size, uniform_list,
 };
 use project::WorktreeId;
 use settings::{CursorShape, SaturatingBool, SettingsContent, SettingsStore};
@@ -18,7 +17,10 @@ use std::{
     rc::Rc,
     sync::{Arc, atomic::AtomicBool},
 };
-use ui::{Divider, DropdownMenu, ListItem, Switch, prelude::*};
+use ui::{
+    ContextMenu, Divider, DropdownMenu, DropdownStyle, Switch, SwitchColor, TreeViewItem,
+    prelude::*,
+};
 use util::{paths::PathStyle, rel_path::RelPath};
 
 use crate::components::SettingsEditor;
@@ -360,12 +362,23 @@ impl std::fmt::Debug for SettingsPageItem {
 }
 
 impl SettingsPageItem {
-    fn render(&self, file: SettingsUiFile, window: &mut Window, cx: &mut App) -> AnyElement {
+    fn render(
+        &self,
+        file: SettingsUiFile,
+        is_last: bool,
+        window: &mut Window,
+        cx: &mut App,
+    ) -> AnyElement {
         match self {
             SettingsPageItem::SectionHeader(header) => v_flex()
                 .w_full()
-                .gap_0p5()
-                .child(Label::new(SharedString::new_static(header)).size(LabelSize::Large))
+                .gap_1()
+                .child(
+                    Label::new(SharedString::new_static(header))
+                        .size(LabelSize::XSmall)
+                        .color(Color::Muted)
+                        .buffer_font(cx),
+                )
                 .child(Divider::horizontal().color(ui::DividerColor::BorderVariant))
                 .into_any_element(),
             SettingsPageItem::SettingItem(setting_item) => {
@@ -379,6 +392,11 @@ impl SettingsPageItem {
                     .gap_2()
                     .flex_wrap()
                     .justify_between()
+                    .when(!is_last, |this| {
+                        this.pb_4()
+                            .border_b_1()
+                            .border_color(cx.theme().colors().border_variant)
+                    })
                     .child(
                         v_flex()
                             .max_w_1_2()
@@ -766,52 +784,26 @@ impl SettingsWindow {
                             .map(|ix| {
                                 let entry = &this.navbar_entries[ix];
 
-                                h_flex()
-                                    .id(("settings-ui-section", ix))
-                                    .w_full()
-                                    .pl_2p5()
-                                    .py_0p5()
-                                    .rounded_sm()
-                                    .border_1()
-                                    .border_color(cx.theme().colors().border_transparent)
-                                    .text_color(cx.theme().colors().text_muted)
-                                    .when(this.is_navbar_entry_selected(ix), |this| {
-                                        this.text_color(cx.theme().colors().text)
-                                            .bg(cx.theme().colors().element_selected.opacity(0.2))
-                                            .border_color(cx.theme().colors().border)
+                                TreeViewItem::new(("settings-ui-navbar-entry", ix), entry.title)
+                                    .root_item(entry.is_root)
+                                    .toggle_state(this.is_navbar_entry_selected(ix))
+                                    .when(entry.is_root, |item| {
+                                        item.toggle(
+                                            this.pages[this.page_index_from_navbar_index(ix)]
+                                                .expanded,
+                                        )
+                                        .on_toggle(
+                                            cx.listener(move |this, _, _, cx| {
+                                                this.toggle_navbar_entry(ix);
+                                                cx.notify();
+                                            }),
+                                        )
                                     })
-                                    .child(
-                                        ListItem::new(("settings-ui-navbar-entry", ix))
-                                            .selectable(true)
-                                            .inset(true)
-                                            .indent_step_size(px(1.))
-                                            .indent_level(if entry.is_root { 1 } else { 3 })
-                                            .when(entry.is_root, |item| {
-                                                item.toggle(
-                                                    this.pages
-                                                        [this.page_index_from_navbar_index(ix)]
-                                                    .expanded,
-                                                )
-                                                .always_show_disclosure_icon(true)
-                                                .on_toggle(cx.listener(move |this, _, _, cx| {
-                                                    this.toggle_navbar_entry(ix);
-                                                    cx.notify();
-                                                }))
-                                            })
-                                            .child(
-                                                h_flex()
-                                                    .text_ui(cx)
-                                                    .truncate()
-                                                    .hover(|s| {
-                                                        s.bg(cx.theme().colors().element_hover)
-                                                    })
-                                                    .child(entry.title),
-                                            ),
-                                    )
                                     .on_click(cx.listener(move |this, _, _, cx| {
                                         this.navbar_entry = ix;
                                         cx.notify();
                                     }))
+                                    .into_any_element()
                             })
                             .collect()
                     }),
@@ -835,10 +827,15 @@ impl SettingsWindow {
     }
 
     fn render_page(&self, window: &mut Window, cx: &mut Context<SettingsWindow>) -> Div {
-        v_flex().gap_4().children(
-            self.page_items()
-                .map(|item| item.render(self.current_file.clone(), window, cx)),
-        )
+        let items: Vec<_> = self.page_items().collect();
+        let items_len = items.len();
+
+        v_flex()
+            .gap_4()
+            .children(items.into_iter().enumerate().map(|(index, item)| {
+                let is_last = index == items_len - 1;
+                item.render(self.current_file.clone(), is_last, window, cx)
+            }))
     }
 
     fn current_page_index(&self) -> usize {
@@ -869,10 +866,13 @@ impl SettingsWindow {
 
 impl Render for SettingsWindow {
     fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
+        let ui_font = theme::setup_ui_font(window, cx);
+
         div()
             .flex()
             .flex_row()
             .size_full()
+            .font(ui_font)
             .bg(cx.theme().colors().background)
             .text_color(cx.theme().colors().text)
             .child(self.render_nav(window, cx))
@@ -927,9 +927,9 @@ fn render_toggle_button<B: Into<bool> + From<bool> + Copy>(
     let (_, &value) = SettingsStore::global(cx).get_value_from_file(file.to_settings(), field.pick);
 
     let toggle_state = if value.into() {
-        ui::ToggleState::Selected
+        ToggleState::Selected
     } else {
-        ui::ToggleState::Unselected
+        ToggleState::Unselected
     };
 
     Switch::new("toggle_button", toggle_state)
@@ -944,6 +944,7 @@ fn render_toggle_button<B: Into<bool> + From<bool> + Copy>(
                 });
             }
         })
+        .color(SwitchColor::Accent)
         .into_any_element()
 }
 
@@ -968,7 +969,7 @@ where
     DropdownMenu::new(
         "dropdown",
         current_value_label,
-        ui::ContextMenu::build(window, cx, move |mut menu, _, _| {
+        ContextMenu::build(window, cx, move |mut menu, _, _| {
             for (value, label) in variants()
                 .into_iter()
                 .copied()
@@ -977,7 +978,7 @@ where
                 menu = menu.toggleable_entry(
                     label,
                     value == current_value,
-                    ui::IconPosition::Start,
+                    IconPosition::Start,
                     None,
                     move |_, cx| {
                         if value == current_value {
@@ -997,6 +998,7 @@ where
             menu
         }),
     )
+    .style(DropdownStyle::Outlined)
     .into_any_element()
 }