Files
Ely-GPUI-Components/examples/gallery/pages/settings/pages.rs
T
ZacharyZhang-NY 380cdb0676 Settings: a settings window, its sections, a search, and what fills them
SettingsLayout lists sections down the side and shows the open one, the
list going above the page in a narrow box; SettingsSection and
SettingsRow title and hold settings, a row's control wrapping under its
name. SettingsSearch finds a setting by its name or words and opens its
section. ThemeSelector, AccentColorPicker, FontSizeControl and
DensitySelector set the look; KeyboardShortcutsList finds shortcuts by
every word; ProxySettings, PrivacySettings, NotificationSettings,
StartupSettings, StorageSettings with its clear, ImportExportSettings,
ResetToDefault, FeatureFlags and DeveloperModeToggle fill the rest.
Every handler a control needs is required and fails loud when missing.
ColorPalette wraps its swatches in a row capped at its columns, so it
keeps their size in a box sized by its content, and its swatches carry
the palette's id. ThemeSwitcher points here: src/theme sits under the
chapters that draw with buttons.
2026-09-27 12:22:58 -04:00

242 lines
9.4 KiB
Rust

use ely_gpui_component::{
forms::{Choice, Combobox, FontPicker},
settings::{
AccentColorPicker, Changed, DensitySelector, DeveloperModeToggle, FeatureFlags,
FontSizeControl, ImportExportSettings, KeyboardShortcutsList, NotificationSettings,
PrivacySettings, ProxySettings, ResetToDefault, SettingsRow, SettingsSection, Shortcut,
StartupSettings, StorageSettings, ThemeSelector,
},
theme::{ActiveTheme, HUE_NAMES},
};
use gpui::{AnyElement, App, Entity, IntoElement, ParentElement, Styled, div, px};
use super::window::{Fields, Prefs, SIZE};
use crate::ui::change;
/// The open section's page.
pub(super) fn page(prefs: &Entity<Prefs>, fields: &Fields, section: &str, cx: &App) -> AnyElement {
match section {
"appearance" => appearance(prefs, fields, cx),
"keyboard" => keyboard(prefs, fields, cx),
"network" => network(prefs, cx),
"privacy" => privacy(prefs, cx),
"notifications" => notifications(prefs, cx),
"startup" => startup(prefs, cx),
"storage" => storage(prefs, cx),
"advanced" => advanced(prefs, fields, cx),
other => panic!("settings demo: no page {other}"),
}
}
fn appearance(prefs: &Entity<Prefs>, fields: &Fields, cx: &App) -> AnyElement {
let now = prefs.read(cx);
let presets = HUE_NAMES
.iter()
.take(6)
.enumerate()
.map(|(ix, name)| (*name, cx.theme().colors.hue(ix, "accent presets")));
let [theme, accent, size, density, font] = [(); 5].map(|_| prefs.clone());
SettingsSection::new("Appearance")
.description("How the app looks.")
.row(
SettingsRow::new("Theme").control(
ThemeSelector::new("settings-theme", now.appearance).on_change(
move |next, _, cx| change(&theme, cx, |prefs| prefs.appearance = next),
),
),
)
.row(
SettingsRow::new("Accent color")
.description("Buttons, links and what is chosen.")
.control(
AccentColorPicker::new("settings-accent", now.accent, presets).on_change(
move |next, _, cx| change(&accent, cx, |prefs| prefs.accent = next),
),
),
)
.row(
SettingsRow::new("Font size").control(
FontSizeControl::new("settings-size", now.size, SIZE, (11.0, 20.0))
.on_change(move |next, _, cx| change(&size, cx, |prefs| prefs.size = next)),
),
)
.row(
SettingsRow::new("Density")
.description("How close things sit.")
.control(
DensitySelector::new("settings-density", now.density).on_change(
move |next, _, cx| change(&density, cx, |prefs| prefs.density = next),
),
),
)
.row(
SettingsRow::new("Font")
.description("FontFamilySelect → forms::FontPicker")
.control(
div().w(px(220.)).child(
FontPicker::new("settings-font", &fields.font)
.selected(now.family.clone())
.on_change(move |next, _, cx| {
change(&font, cx, |prefs| prefs.family = next.clone())
}),
),
),
)
.into_any_element()
}
fn shortcut(group: &str, name: &str, keys: &str) -> Shortcut {
Shortcut {
group: group.to_string().into(),
name: name.to_string().into(),
keys: keys.to_string().into(),
}
}
fn keyboard(prefs: &Entity<Prefs>, fields: &Fields, cx: &App) -> AnyElement {
let language = prefs.clone();
SettingsSection::new("Keyboard and language")
.description("ShortcutEditor → editor::KeybindingsEditor")
.row(
SettingsRow::new("Language")
.description("LanguageSelector → forms::Combobox::languages")
.control(
div().w(px(220.)).child(
Combobox::languages("settings-language", &fields.language)
.selected(prefs.read(cx).language.clone())
.on_change(move |next, _, cx| {
change(&language, cx, |prefs| prefs.language = next.clone())
}),
),
),
)
.row(KeyboardShortcutsList::new(
[
shortcut("General", "Command palette", "cmd-shift-p"),
shortcut("General", "Settings", "cmd-,"),
shortcut("Editing", "Undo", "cmd-z"),
shortcut("Editing", "Find", "cmd-f"),
shortcut("Window", "New window", "cmd-shift-n"),
],
&fields.shortcuts,
))
.into_any_element()
}
fn network(prefs: &Entity<Prefs>, cx: &App) -> AnyElement {
let proxy = prefs.clone();
SettingsSection::new("Network")
.row(
ProxySettings::new("settings-proxy", prefs.read(cx).proxy.clone())
.on_apply(move |next, _, cx| change(&proxy, cx, |prefs| prefs.proxy = next)),
)
.into_any_element()
}
fn privacy(prefs: &Entity<Prefs>, cx: &App) -> AnyElement {
let privacy = prefs.clone();
PrivacySettings::new("settings-privacy", prefs.read(cx).privacy)
.on_change(move |next, _, cx| change(&privacy, cx, |prefs| prefs.privacy = next))
.into_any_element()
}
fn notifications(prefs: &Entity<Prefs>, cx: &App) -> AnyElement {
let notices = prefs.clone();
NotificationSettings::new("settings-notices", prefs.read(cx).notices.clone())
.on_change(move |next, _, cx| change(&notices, cx, |prefs| prefs.notices = next))
.into_any_element()
}
fn startup(prefs: &Entity<Prefs>, cx: &App) -> AnyElement {
let startup = prefs.clone();
let pages = [
Choice::new("last", "Where I left off"),
Choice::new("home", "The home page"),
Choice::new("blank", "A blank window"),
];
StartupSettings::new("settings-startup", prefs.read(cx).startup.clone(), pages)
.on_change(move |next, _, cx| change(&startup, cx, |prefs| prefs.startup = next))
.into_any_element()
}
fn storage(prefs: &Entity<Prefs>, cx: &App) -> AnyElement {
let now = prefs.read(cx);
let [cleared, reset, all] = [(); 3].map(|_| prefs.clone());
let changed = (now.size != SIZE).then(|| Changed {
key: "size".into(),
name: "Font size".into(),
now: format!("{} pt", now.size).into(),
default: format!("{SIZE} pt").into(),
});
div()
.flex()
.flex_col()
.gap_8()
.child(
SettingsSection::new("Storage")
.description("What the app keeps on this device.")
.row(
StorageSettings::new("settings-storage", now.stores.clone()).on_clear(
move |key, _, cx| {
change(&cleared, cx, |prefs| {
prefs
.stores
.iter_mut()
.filter(|store| store.key == *key)
.for_each(|store| store.bytes = 0)
})
},
),
),
)
.child(
SettingsSection::new("Import and export").row(
ImportExportSettings::new("settings-files")
.on_export(|_, _| log::info!("gallery: settings exported"))
.on_import(|path, _, _| {
log::info!("gallery: settings imported from {}", path.display())
}),
),
)
.child(
SettingsSection::new("Defaults")
.description("A larger font size shows here.")
.row(
ResetToDefault::new("settings-reset", changed)
.on_reset(move |_, _, cx| change(&reset, cx, |prefs| prefs.size = SIZE))
.on_reset_all(move |_, cx| change(&all, cx, |prefs| prefs.size = SIZE)),
),
)
.into_any_element()
}
fn advanced(prefs: &Entity<Prefs>, fields: &Fields, cx: &App) -> AnyElement {
let now = prefs.read(cx);
let [toggled, developer] = [(); 2].map(|_| prefs.clone());
div()
.flex()
.flex_col()
.gap_8()
.child(
SettingsSection::new("Features")
.description("AdvancedSettings / FeatureFlags")
.row(
FeatureFlags::new("settings-flags", now.flags.clone(), &fields.flags)
.on_toggle(move |key, on, _, cx| {
change(&toggled, cx, |prefs| {
prefs
.flags
.iter_mut()
.filter(|flag| flag.key == *key)
.for_each(|flag| flag.on = on)
})
}),
),
)
.child(
DeveloperModeToggle::new("settings-developer", now.developer)
.on_change(move |on, _, cx| change(&developer, cx, |prefs| prefs.developer = on)),
)
.into_any_element()
}