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.
275 lines
8.8 KiB
Rust
275 lines
8.8 KiB
Rust
use ely_gpui_component::{
|
|
forms::TextInput,
|
|
primitives::IconName,
|
|
settings::{
|
|
Appearance, Flag, Notices, Privacy, Proxy, SettingEntry, SettingsLayout, SettingsSearch,
|
|
Stage, Startup, Store,
|
|
},
|
|
theme::{ActiveTheme, Density},
|
|
};
|
|
use gpui::{App, Entity, Hsla, IntoElement, ParentElement, SharedString, Styled, Window, div, px};
|
|
|
|
use super::pages;
|
|
use crate::{
|
|
probe::probe,
|
|
ui::{change, keep, section},
|
|
};
|
|
|
|
pub(super) const SECTIONS: [(&str, &str, IconName); 8] = [
|
|
("appearance", "Appearance", IconName::Palette),
|
|
("keyboard", "Keyboard", IconName::Keyboard),
|
|
("network", "Network", IconName::Globe),
|
|
("privacy", "Privacy", IconName::Shield),
|
|
("notifications", "Notifications", IconName::Bell),
|
|
("startup", "Startup", IconName::Power),
|
|
("storage", "Storage", IconName::HardDrive),
|
|
("advanced", "Advanced", IconName::Wrench),
|
|
];
|
|
|
|
/// Everything the settings window holds.
|
|
pub(super) struct Prefs {
|
|
pub section: SharedString,
|
|
pub appearance: Appearance,
|
|
pub accent: Hsla,
|
|
pub size: f32,
|
|
pub density: Density,
|
|
pub family: SharedString,
|
|
pub language: SharedString,
|
|
pub proxy: Proxy,
|
|
pub privacy: Privacy,
|
|
pub notices: Notices,
|
|
pub startup: Startup,
|
|
pub stores: Vec<Store>,
|
|
pub flags: Vec<Flag>,
|
|
pub developer: bool,
|
|
}
|
|
|
|
pub(super) const SIZE: f32 = 14.0;
|
|
|
|
fn flag(key: &str, name: &str, description: &str, stage: Stage, on: bool) -> Flag {
|
|
Flag {
|
|
key: key.to_string().into(),
|
|
name: name.to_string().into(),
|
|
description: description.to_string().into(),
|
|
stage,
|
|
on,
|
|
}
|
|
}
|
|
|
|
fn pair(kind: &str, channel: &str) -> (SharedString, SharedString) {
|
|
(kind.to_string().into(), channel.to_string().into())
|
|
}
|
|
|
|
impl Prefs {
|
|
fn new(accent: Hsla) -> Self {
|
|
Self {
|
|
section: "appearance".into(),
|
|
appearance: Appearance::System,
|
|
accent,
|
|
size: SIZE,
|
|
density: Density::Standard,
|
|
family: "Inter".into(),
|
|
language: "en".into(),
|
|
proxy: Proxy::System,
|
|
privacy: Privacy {
|
|
telemetry: true,
|
|
crash_reports: true,
|
|
personalization: false,
|
|
history: true,
|
|
},
|
|
notices: Notices {
|
|
channels: vec!["App".into(), "Email".into(), "Push".into()],
|
|
kinds: vec![
|
|
pair("mentions", "Mentions"),
|
|
pair("replies", "Replies"),
|
|
pair("digest", "Weekly digest"),
|
|
],
|
|
on: vec![
|
|
pair("mentions", "App"),
|
|
pair("mentions", "Push"),
|
|
pair("replies", "App"),
|
|
pair("digest", "Email"),
|
|
],
|
|
quiet: false,
|
|
},
|
|
startup: Startup {
|
|
at_login: false,
|
|
restore: true,
|
|
opens: "last".into(),
|
|
updates: true,
|
|
},
|
|
stores: vec![
|
|
Store {
|
|
key: "cache".into(),
|
|
name: "Cache".into(),
|
|
bytes: 312 << 20,
|
|
clearable: true,
|
|
},
|
|
Store {
|
|
key: "logs".into(),
|
|
name: "Logs".into(),
|
|
bytes: 48 << 20,
|
|
clearable: true,
|
|
},
|
|
Store {
|
|
key: "documents".into(),
|
|
name: "Documents".into(),
|
|
bytes: 1_240 << 20,
|
|
clearable: false,
|
|
},
|
|
],
|
|
flags: vec![
|
|
flag(
|
|
"tabs",
|
|
"Vertical tabs",
|
|
"Tabs down the side of the window.",
|
|
Stage::Beta,
|
|
false,
|
|
),
|
|
flag(
|
|
"sync",
|
|
"Settings sync",
|
|
"The same settings on every device.",
|
|
Stage::Stable,
|
|
true,
|
|
),
|
|
flag(
|
|
"suggest",
|
|
"Inline suggestions",
|
|
"The rest of a line, offered as you type.",
|
|
Stage::Experimental,
|
|
false,
|
|
),
|
|
],
|
|
developer: false,
|
|
}
|
|
}
|
|
}
|
|
|
|
/// The window's text fields, each kept across frames.
|
|
pub(super) struct Fields {
|
|
pub font: Entity<TextInput>,
|
|
pub language: Entity<TextInput>,
|
|
pub shortcuts: Entity<TextInput>,
|
|
pub flags: Entity<TextInput>,
|
|
}
|
|
|
|
fn field(
|
|
key: &'static str,
|
|
hint: &'static str,
|
|
window: &mut Window,
|
|
cx: &mut App,
|
|
) -> Entity<TextInput> {
|
|
window.use_keyed_state(key, cx, move |window, cx| {
|
|
TextInput::new(window, cx).placeholder(hint)
|
|
})
|
|
}
|
|
|
|
fn entry(section: &str, key: &str, name: &str, words: &[&str]) -> SettingEntry {
|
|
let (_, title, _) = SECTIONS
|
|
.iter()
|
|
.find(|(each, _, _)| *each == section)
|
|
.expect("a settings section");
|
|
SettingEntry {
|
|
key: key.to_string().into(),
|
|
section: (*title).into(),
|
|
name: name.to_string().into(),
|
|
words: words
|
|
.iter()
|
|
.map(|word| SharedString::from(word.to_string()))
|
|
.collect(),
|
|
}
|
|
}
|
|
|
|
/// The section each searchable setting lives in, by the setting's key.
|
|
fn home(key: &str) -> &'static str {
|
|
match key {
|
|
"theme" | "accent" | "size" | "density" | "font" => "appearance",
|
|
"language" | "shortcuts" => "keyboard",
|
|
"proxy" => "network",
|
|
"telemetry" => "privacy",
|
|
"quiet" => "notifications",
|
|
"login" => "startup",
|
|
"cache" => "storage",
|
|
"developer" => "advanced",
|
|
other => panic!("settings demo: no home for {other}"),
|
|
}
|
|
}
|
|
|
|
pub fn settings(window: &mut Window, cx: &mut App) -> impl IntoElement + use<> {
|
|
let accent = cx.theme().colors.accent;
|
|
let prefs = keep("settings-prefs", move || Prefs::new(accent), window, cx);
|
|
let search = field("settings-search", "Search settings", window, cx);
|
|
let fields = Fields {
|
|
font: field("settings-font", "Font", window, cx),
|
|
language: field("settings-language", "Language", window, cx),
|
|
shortcuts: field("settings-shortcuts", "Find a shortcut", window, cx),
|
|
flags: field("settings-flags", "Find a feature", window, cx),
|
|
};
|
|
let entries = [
|
|
entry("appearance", "theme", "Theme", &["dark", "light", "mode"]),
|
|
entry("appearance", "accent", "Accent color", &["color", "hue"]),
|
|
entry("appearance", "size", "Font size", &["text", "zoom"]),
|
|
entry("appearance", "density", "Density", &["compact", "spacing"]),
|
|
entry("appearance", "font", "Font", &["typeface", "family"]),
|
|
entry(
|
|
"keyboard",
|
|
"language",
|
|
"Language",
|
|
&["locale", "translation"],
|
|
),
|
|
entry(
|
|
"keyboard",
|
|
"shortcuts",
|
|
"Keyboard shortcuts",
|
|
&["keys", "bindings"],
|
|
),
|
|
entry("network", "proxy", "Proxy", &["http", "socks"]),
|
|
entry(
|
|
"privacy",
|
|
"telemetry",
|
|
"Usage data",
|
|
&["telemetry", "analytics"],
|
|
),
|
|
entry(
|
|
"notifications",
|
|
"quiet",
|
|
"Hold notices",
|
|
&["do not disturb", "quiet"],
|
|
),
|
|
entry("startup", "login", "Open at login", &["launch", "boot"]),
|
|
entry("storage", "cache", "Clear cache", &["disk", "space"]),
|
|
entry(
|
|
"advanced",
|
|
"developer",
|
|
"Developer mode",
|
|
&["debug", "inspector"],
|
|
),
|
|
];
|
|
let selected = prefs.read(cx).section.clone();
|
|
let page = pages::page(&prefs, &fields, &selected, cx);
|
|
let [found, picked] = [(); 2].map(|_| prefs.clone());
|
|
section(
|
|
"SettingsLayout · SettingsSection · SettingsRow · SettingsSearch",
|
|
"A settings window: sections down the side and the open one beside them, the list going above in a narrow window. The search finds a setting by its name or the words it answers to, and Enter opens its section.",
|
|
cx,
|
|
)
|
|
.child(
|
|
div()
|
|
.flex()
|
|
.flex_col()
|
|
.gap_6()
|
|
.child(div().w(px(360.)).child(SettingsSearch::new("settings-search", entries, &search).on_pick(
|
|
move |key, _, cx| change(&found, cx, |prefs| prefs.section = home(key).into()),
|
|
)))
|
|
.child(probe(
|
|
"settings-layout",
|
|
div().w(px(880.)).child(
|
|
SettingsLayout::new("settings-layout", SECTIONS, selected)
|
|
.on_select(move |key, _, cx| change(&picked, cx, |prefs| prefs.section = key.clone()))
|
|
.page(page),
|
|
),
|
|
)),
|
|
)
|
|
}
|