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

An initial pass at some foundational styles.

Release Notes:

- N/A
This commit is contained in:
Danilo Leal
2025-10-02 03:35:10 +00:00
committed by GitHub
parent 86ebb1890d
commit 62f90fec77
2 changed files with 64 additions and 59 deletions
+6 -3
View File
@@ -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| {
+58 -56
View File
@@ -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()
}