From 62f90fec77992689537d630f8aa25507bb552e84 Mon Sep 17 00:00:00 2001 From: Danilo Leal <67129314+danilo-leal@users.noreply.github.com> Date: Thu, 2 Oct 2025 00:35:10 -0300 Subject: [PATCH] settings ui: Use the tree view item component and other design tweaks (#39329) An initial pass at some foundational styles. Release Notes: - N/A --- crates/settings_ui/src/components.rs | 9 +- crates/settings_ui/src/settings_ui.rs | 114 +++++++++++++------------- 2 files changed, 64 insertions(+), 59 deletions(-) diff --git a/crates/settings_ui/src/components.rs b/crates/settings_ui/src/components.rs index 42544d99c27b4c786da1217531b4139ad8db6db4..35afed363123d42a024d417aebb32da7ba39a316 100644 --- a/crates/settings_ui/src/components.rs +++ b/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::({ move |_, _, cx| { diff --git a/crates/settings_ui/src/settings_ui.rs b/crates/settings_ui/src/settings_ui.rs index 2e7dad519fb71de8882d4c7c32ac0d6af5540e96..b05335dd6f541ead35f4e703f63de2eb067dc5e9 100644 --- a/crates/settings_ui/src/settings_ui.rs +++ b/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) -> 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) -> 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 + From + 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 + From + 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() }