Panics · P35 Settings: handlers in new; gone sections and themes mark none, drifted font sizes peg

This commit is contained in:
ZacharyZhang-NY committed 2026-10-07 05:38:45 -04:00
1 parent 45cecebea5
commit ad472490e0
18 files changed
+384 -427

No files matched your search

+90 -77
View File
@@ -38,36 +38,36 @@ fn appearance(prefs: &Entity<Prefs>, fields: &Fields, cx: &App) -> AnyElement {
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("Theme").control(ThemeSelector::new(
"settings-theme",
now.appearance,
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)),
),
.control(AccentColorPicker::new(
"settings-accent",
now.accent,
presets,
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),
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),
),
),
.control(DensitySelector::new(
"settings-density",
now.density,
move |next, _, cx| change(&density, cx, |prefs| prefs.density = next),
)),
)
.row(
SettingsRow::new("Font")
@@ -126,25 +126,32 @@ fn keyboard(prefs: &Entity<Prefs>, fields: &Fields, cx: &App) -> AnyElement {
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)),
)
.row(ProxySettings::new(
"settings-proxy",
prefs.read(cx).proxy.clone(),
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()
PrivacySettings::new(
"settings-privacy",
prefs.read(cx).privacy,
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()
NotificationSettings::new(
"settings-notices",
prefs.read(cx).notices.clone(),
move |next, _, cx| change(&notices, cx, |prefs| prefs.notices = next),
)
.into_any_element()
}
fn startup(prefs: &Entity<Prefs>, cx: &App) -> AnyElement {
@@ -154,9 +161,13 @@ fn startup(prefs: &Entity<Prefs>, cx: &App) -> AnyElement {
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()
StartupSettings::new(
"settings-startup",
prefs.read(cx).startup.clone(),
pages,
move |next, _, cx| change(&startup, cx, |prefs| prefs.startup = next),
)
.into_any_element()
}
fn storage(prefs: &Entity<Prefs>, cx: &App) -> AnyElement {
@@ -175,37 +186,36 @@ fn storage(prefs: &Entity<Prefs>, cx: &App) -> AnyElement {
.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)
})
},
),
),
.row(StorageSettings::new(
"settings-storage",
now.stores.clone(),
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())
}),
),
SettingsSection::new("Import and export").row(ImportExportSettings::new(
"settings-files",
|_, _| log::info!("gallery: settings exported"),
|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)),
),
.row(ResetToDefault::new(
"settings-reset",
changed,
move |_, _, cx| change(&reset, cx, |prefs| prefs.size = SIZE),
move |_, cx| change(&all, cx, |prefs| prefs.size = SIZE),
)),
)
.into_any_element()
}
@@ -220,22 +230,25 @@ fn advanced(prefs: &Entity<Prefs>, fields: &Fields, cx: &App) -> AnyElement {
.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)),
.row(FeatureFlags::new(
"settings-flags",
now.flags.clone(),
&fields.flags,
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,
move |on, _, cx| change(&developer, cx, |prefs| prefs.developer = on),
))
.into_any_element()
}
+2 -3
View File
@@ -259,14 +259,13 @@ pub fn settings(window: &mut Window, cx: &mut App) -> impl IntoElement + use<> {
.flex()
.flex_col()
.gap_6()
.child(div().w(px(360.)).child(SettingsSearch::new("settings-search", entries, &search).on_pick(
.child(div().w(px(360.)).child(SettingsSearch::new("settings-search", entries, &search,
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()))
SettingsLayout::new("settings-layout", SECTIONS, selected, move |key, _, cx| change(&picked, cx, |prefs| prefs.section = key.clone()))
.page(page),
),
)),
+1 -1
View File
@@ -49,7 +49,7 @@ pub fn editor(_: &mut Window, cx: &mut App) -> impl IntoElement + use<> {
.flex()
.flex_col()
.gap_4()
.child(ThemeEditor::new("theme-editor", draft).on_change(|draft, _, cx| apply(draft, cx)))
.child(ThemeEditor::new("theme-editor", draft, |draft, _, cx| apply(draft, cx)))
.child(
div().flex().child(
Button::new("theme-restore", "Back to Ely's theme")
+2 -2
View File
@@ -21,7 +21,7 @@ pub fn importing(window: &mut Window, cx: &mut App) -> impl IntoElement + use<>
specimens()
.child(specimen(
"ThemeImporter",
div().w(px(360.)).child(ThemeImporter::new("theme-importer").on_apply(|read, _, cx| {
div().w(px(360.)).child(ThemeImporter::new("theme-importer", |read, _, cx| {
Theme::set_palette(read.mode, Some(read.colors.clone()), cx);
Theme::set_mode(read.mode, cx);
})),
@@ -29,7 +29,7 @@ pub fn importing(window: &mut Window, cx: &mut App) -> impl IntoElement + use<>
))
.child(specimen(
"SyntaxThemePicker",
div().w(px(360.)).child(SyntaxThemePicker::new("theme-syntax", syntax_themes(), name).on_change(
div().w(px(360.)).child(SyntaxThemePicker::new("theme-syntax", syntax_themes(), name,
move |picked, _, cx| {
let theme = syntax_themes()
.into_iter()
+12 -24
View File
@@ -40,7 +40,7 @@ pub struct FeatureFlags {
id: ElementId,
flags: Vec<Flag>,
search: Entity<TextInput>,
on_toggle: Option<OnFlag>,
on_toggle: OnFlag,
}
impl FeatureFlags {
@@ -49,22 +49,15 @@ impl FeatureFlags {
id: impl Into<ElementId>,
flags: impl IntoIterator<Item = Flag>,
search: &Entity<TextInput>,
on_toggle: impl Fn(&SharedString, bool, &mut Window, &mut App) + 'static,
) -> Self {
Self {
id: id.into(),
flags: flags.into_iter().collect(),
search: search.clone(),
on_toggle: None,
on_toggle: Rc::new(on_toggle),
}
}
pub fn on_toggle(
mut self,
handler: impl Fn(&SharedString, bool, &mut Window, &mut App) + 'static,
) -> Self {
self.on_toggle = Some(Rc::new(handler));
self
}
}
/// The flags whose name or description holds every word of `query`.
@@ -83,9 +76,7 @@ fn kept_flags<'a>(flags: &'a [Flag], query: &str) -> Vec<&'a Flag> {
impl RenderOnce for FeatureFlags {
fn render(self, _: &mut Window, cx: &mut App) -> impl IntoElement {
let on_toggle = self
.on_toggle
.unwrap_or_else(|| panic!("feature flags {:?} has no on_toggle", self.id));
let on_toggle = self.on_toggle;
let query = self.search.read(cx).text().to_string();
let theme = cx.theme();
let rows = kept_flags(&self.flags, &query).into_iter().map(|flag| {
@@ -134,29 +125,26 @@ type OnBool = Rc<dyn Fn(bool, &mut Window, &mut App)>;
pub struct DeveloperModeToggle {
id: ElementId,
on: bool,
on_change: Option<OnBool>,
on_change: OnBool,
}
impl DeveloperModeToggle {
pub fn new(id: impl Into<ElementId>, on: bool) -> Self {
pub fn new(
id: impl Into<ElementId>,
on: bool,
on_change: impl Fn(bool, &mut Window, &mut App) + 'static,
) -> Self {
Self {
id: id.into(),
on,
on_change: None,
on_change: Rc::new(on_change),
}
}
pub fn on_change(mut self, handler: impl Fn(bool, &mut Window, &mut App) + 'static) -> Self {
self.on_change = Some(Rc::new(handler));
self
}
}
impl RenderOnce for DeveloperModeToggle {
fn render(self, _: &mut Window, _: &mut App) -> impl IntoElement {
let on_change = self
.on_change
.unwrap_or_else(|| panic!("developer mode toggle {:?} has no on_change", self.id));
let on_change = self.on_change;
let on_change = on_change;
div()
.flex()
+39 -54
View File
@@ -45,32 +45,26 @@ type OnAppearance = Rc<dyn Fn(Appearance, &mut Window, &mut App)>;
pub struct ThemeSelector {
id: ElementId,
appearance: Appearance,
on_change: Option<OnAppearance>,
on_change: OnAppearance,
}
impl ThemeSelector {
pub fn new(id: impl Into<ElementId>, appearance: Appearance) -> Self {
pub fn new(
id: impl Into<ElementId>,
appearance: Appearance,
on_change: impl Fn(Appearance, &mut Window, &mut App) + 'static,
) -> Self {
Self {
id: id.into(),
appearance,
on_change: None,
on_change: Rc::new(on_change),
}
}
pub fn on_change(
mut self,
handler: impl Fn(Appearance, &mut Window, &mut App) + 'static,
) -> Self {
self.on_change = Some(Rc::new(handler));
self
}
}
impl RenderOnce for ThemeSelector {
fn render(self, _: &mut Window, _: &mut App) -> impl IntoElement {
let on_change = self
.on_change
.unwrap_or_else(|| panic!("theme selector {:?} has no on_change", self.id));
let on_change = self.on_change;
let (_, words, _) = Appearance::ALL
.iter()
.find(|(each, _, _)| *each == self.appearance)
@@ -100,7 +94,7 @@ pub struct AccentColorPicker {
id: ElementId,
color: Hsla,
presets: Vec<(SharedString, Hsla)>,
on_change: Option<OnColor>,
on_change: OnColor,
}
impl AccentColorPicker {
@@ -108,6 +102,7 @@ impl AccentColorPicker {
id: impl Into<ElementId>,
color: impl Into<Hsla>,
presets: impl IntoIterator<Item = (impl Into<SharedString>, impl Into<Hsla>)>,
on_change: impl Fn(Hsla, &mut Window, &mut App) + 'static,
) -> Self {
Self {
id: id.into(),
@@ -116,21 +111,14 @@ impl AccentColorPicker {
.into_iter()
.map(|(name, color)| (name.into(), color.into()))
.collect(),
on_change: None,
on_change: Rc::new(on_change),
}
}
pub fn on_change(mut self, handler: impl Fn(Hsla, &mut Window, &mut App) + 'static) -> Self {
self.on_change = Some(Rc::new(handler));
self
}
}
impl RenderOnce for AccentColorPicker {
fn render(self, _: &mut Window, _: &mut App) -> impl IntoElement {
let on_change = self
.on_change
.unwrap_or_else(|| panic!("accent color picker {:?} has no on_change", self.id));
let on_change = self.on_change;
let (picked, custom) = (on_change.clone(), on_change);
let report = |on_change: OnColor| {
move |color: Hsla, window: &mut Window, cx: &mut App| {
@@ -169,38 +157,38 @@ pub struct FontSizeControl {
size: f32,
default: f32,
range: (f32, f32),
on_change: Option<OnSize>,
on_change: OnSize,
}
impl FontSizeControl {
/// `size` and `default` lie within `range`, in pixels.
pub fn new(id: impl Into<ElementId>, size: f32, default: f32, range: (f32, f32)) -> Self {
assert!(
range.0 < range.1
&& (range.0..=range.1).contains(&size)
&& (range.0..=range.1).contains(&default),
"font size {size} and default {default} outside {range:?}"
);
pub fn new(
id: impl Into<ElementId>,
size: f32,
default: f32,
range: (f32, f32),
on_change: impl Fn(f32, &mut Window, &mut App) + 'static,
) -> Self {
assert!(range.0 < range.1, "font sizes {range:?} run backwards");
let held = |what: &str, value: f32| {
if !(range.0..=range.1).contains(&value) {
log::error!("font size control: {what} {value} outside {range:?}; pegged");
}
value.clamp(range.0, range.1)
};
Self {
id: id.into(),
size,
default,
size: held("size", size),
default: held("default", default),
range,
on_change: None,
on_change: Rc::new(on_change),
}
}
pub fn on_change(mut self, handler: impl Fn(f32, &mut Window, &mut App) + 'static) -> Self {
self.on_change = Some(Rc::new(handler));
self
}
}
impl RenderOnce for FontSizeControl {
fn render(self, _: &mut Window, cx: &mut App) -> impl IntoElement {
let on_change = self
.on_change
.unwrap_or_else(|| panic!("font size control {:?} has no on_change", self.id));
let on_change = self.on_change;
let theme = cx.theme();
let (slid, reset, default) = (on_change.clone(), on_change, self.default);
let (low, high) = self.range;
@@ -258,7 +246,7 @@ type OnDensity = Rc<dyn Fn(Density, &mut Window, &mut App)>;
pub struct DensitySelector {
id: ElementId,
density: Density,
on_change: Option<OnDensity>,
on_change: OnDensity,
}
const DENSITIES: [(Density, &str); 3] = [
@@ -268,25 +256,22 @@ const DENSITIES: [(Density, &str); 3] = [
];
impl DensitySelector {
pub fn new(id: impl Into<ElementId>, density: Density) -> Self {
pub fn new(
id: impl Into<ElementId>,
density: Density,
on_change: impl Fn(Density, &mut Window, &mut App) + 'static,
) -> Self {
Self {
id: id.into(),
density,
on_change: None,
on_change: Rc::new(on_change),
}
}
pub fn on_change(mut self, handler: impl Fn(Density, &mut Window, &mut App) + 'static) -> Self {
self.on_change = Some(Rc::new(handler));
self
}
}
impl RenderOnce for DensitySelector {
fn render(self, _: &mut Window, _: &mut App) -> impl IntoElement {
let on_change = self
.on_change
.unwrap_or_else(|| panic!("density selector {:?} has no on_change", self.id));
let on_change = self.on_change;
let (_, words) = DENSITIES
.iter()
.find(|(each, _)| *each == self.density)
+15 -18
View File
@@ -58,33 +58,27 @@ type OnDraft = Rc<dyn Fn(&ThemeDraft, &mut Window, &mut App)>;
pub struct ThemeEditor {
id: ElementId,
draft: ThemeDraft,
on_change: Option<OnDraft>,
on_change: OnDraft,
}
impl ThemeEditor {
pub fn new(id: impl Into<ElementId>, draft: ThemeDraft) -> Self {
pub fn new(
id: impl Into<ElementId>,
draft: ThemeDraft,
on_change: impl Fn(&ThemeDraft, &mut Window, &mut App) + 'static,
) -> Self {
Self {
id: id.into(),
draft,
on_change: None,
on_change: Rc::new(on_change),
}
}
pub fn on_change(
mut self,
handler: impl Fn(&ThemeDraft, &mut Window, &mut App) + 'static,
) -> Self {
self.on_change = Some(Rc::new(handler));
self
}
}
impl RenderOnce for ThemeEditor {
fn render(self, _: &mut Window, cx: &mut App) -> impl IntoElement {
let id = self.id;
let on_change = self
.on_change
.unwrap_or_else(|| panic!("theme editor {id:?} has no on_change"));
let on_change = self.on_change;
let draft = Rc::new(self.draft);
let edit = {
let (draft, on_change) = (draft.clone(), on_change);
@@ -104,11 +98,14 @@ impl RenderOnce for ThemeEditor {
let look = SettingsSection::new("Look")
.row(row(
"Density",
DensitySelector::new((id.clone(), "density"), draft.density)
.on_change(move |next, window, cx| {
DensitySelector::new(
(id.clone(), "density"),
draft.density,
move |next, window, cx| {
density(&|d: &mut ThemeDraft| d.density = next, window, cx)
})
.into_any_element(),
},
)
.into_any_element(),
))
.row(row(
"Corners",
+7 -14
View File
@@ -46,32 +46,25 @@ pub(super) type OnTheme = Rc<dyn Fn(&VsCodeTheme, &mut Window, &mut App)>;
#[derive(IntoElement)]
pub struct ThemeImporter {
id: ElementId,
on_apply: Option<OnTheme>,
on_apply: OnTheme,
}
impl ThemeImporter {
pub fn new(id: impl Into<ElementId>) -> Self {
pub fn new(
id: impl Into<ElementId>,
on_apply: impl Fn(&VsCodeTheme, &mut Window, &mut App) + 'static,
) -> Self {
Self {
id: id.into(),
on_apply: None,
on_apply: Rc::new(on_apply),
}
}
pub fn on_apply(
mut self,
handler: impl Fn(&VsCodeTheme, &mut Window, &mut App) + 'static,
) -> Self {
self.on_apply = Some(Rc::new(handler));
self
}
}
impl RenderOnce for ThemeImporter {
fn render(self, window: &mut Window, cx: &mut App) -> impl IntoElement {
let id = self.id;
let on_apply = self
.on_apply
.unwrap_or_else(|| panic!("theme importer {id:?} has no on_apply"));
let on_apply = self.on_apply;
let last = window.use_keyed_state((id.clone(), "read"), cx, |_, _| None::<Read>);
let took = last.clone();
let zone = DropZone::new((id.clone(), "zone"))
+8 -11
View File
@@ -54,22 +54,21 @@ type OnProxy = Rc<dyn Fn(Proxy, &mut Window, &mut App)>;
pub struct ProxySettings {
id: ElementId,
proxy: Proxy,
on_apply: Option<OnProxy>,
on_apply: OnProxy,
}
impl ProxySettings {
pub fn new(id: impl Into<ElementId>, proxy: Proxy) -> Self {
pub fn new(
id: impl Into<ElementId>,
proxy: Proxy,
on_apply: impl Fn(Proxy, &mut Window, &mut App) + 'static,
) -> Self {
Self {
id: id.into(),
proxy,
on_apply: None,
on_apply: Rc::new(on_apply),
}
}
pub fn on_apply(mut self, handler: impl Fn(Proxy, &mut Window, &mut App) + 'static) -> Self {
self.on_apply = Some(Rc::new(handler));
self
}
}
impl RenderOnce for ProxySettings {
@@ -129,9 +128,7 @@ impl RenderOnce for ProxySettings {
"manual" => manual(&text(&host), &text(&port), &text(&bypass)),
other => panic!("proxy settings: no way named {other}"),
};
let on_apply = self
.on_apply
.unwrap_or_else(|| panic!("proxy settings {id:?} has no on_apply"));
let on_apply = self.on_apply;
let switched = fields.clone();
div()
.flex()
+20 -32
View File
@@ -26,29 +26,26 @@ pub struct Privacy {
pub struct PrivacySettings {
id: ElementId,
privacy: Privacy,
on_change: Option<OnEdit<Privacy>>,
on_change: OnEdit<Privacy>,
}
impl PrivacySettings {
pub fn new(id: impl Into<ElementId>, privacy: Privacy) -> Self {
pub fn new(
id: impl Into<ElementId>,
privacy: Privacy,
on_change: impl Fn(Privacy, &mut Window, &mut App) + 'static,
) -> Self {
Self {
id: id.into(),
privacy,
on_change: None,
on_change: Rc::new(on_change),
}
}
pub fn on_change(mut self, handler: impl Fn(Privacy, &mut Window, &mut App) + 'static) -> Self {
self.on_change = Some(Rc::new(handler));
self
}
}
impl RenderOnce for PrivacySettings {
fn render(self, _: &mut Window, _: &mut App) -> impl IntoElement {
let on_change = self
.on_change
.unwrap_or_else(|| panic!("privacy settings {:?} has no on_change", self.id));
let on_change = self.on_change;
let (id, privacy, on_change) = (self.id, self.privacy, &on_change);
let row = |key: &'static str,
title: &'static str,
@@ -108,29 +105,26 @@ pub struct Notices {
pub struct NotificationSettings {
id: ElementId,
notices: Notices,
on_change: Option<OnEdit<Notices>>,
on_change: OnEdit<Notices>,
}
impl NotificationSettings {
pub fn new(id: impl Into<ElementId>, notices: Notices) -> Self {
pub fn new(
id: impl Into<ElementId>,
notices: Notices,
on_change: impl Fn(Notices, &mut Window, &mut App) + 'static,
) -> Self {
Self {
id: id.into(),
notices,
on_change: None,
on_change: Rc::new(on_change),
}
}
pub fn on_change(mut self, handler: impl Fn(Notices, &mut Window, &mut App) + 'static) -> Self {
self.on_change = Some(Rc::new(handler));
self
}
}
impl RenderOnce for NotificationSettings {
fn render(self, _: &mut Window, cx: &mut App) -> impl IntoElement {
let on_change = self
.on_change
.unwrap_or_else(|| panic!("notification settings {:?} has no on_change", self.id));
let on_change = self.on_change;
let (id, notices, on_change) = (self.id, self.notices, &on_change);
let theme = cx.theme();
let rows = notices.kinds.iter().map(|(kind, name)| {
@@ -209,7 +203,7 @@ pub struct StartupSettings {
id: ElementId,
startup: Startup,
pages: Vec<Choice>,
on_change: Option<OnEdit<Startup>>,
on_change: OnEdit<Startup>,
}
impl StartupSettings {
@@ -217,26 +211,20 @@ impl StartupSettings {
id: impl Into<ElementId>,
startup: Startup,
pages: impl IntoIterator<Item = Choice>,
on_change: impl Fn(Startup, &mut Window, &mut App) + 'static,
) -> Self {
Self {
id: id.into(),
startup,
pages: pages.into_iter().collect(),
on_change: None,
on_change: Rc::new(on_change),
}
}
pub fn on_change(mut self, handler: impl Fn(Startup, &mut Window, &mut App) + 'static) -> Self {
self.on_change = Some(Rc::new(handler));
self
}
}
impl RenderOnce for StartupSettings {
fn render(self, _: &mut Window, cx: &mut App) -> impl IntoElement {
let on_change = self
.on_change
.unwrap_or_else(|| panic!("startup settings {:?} has no on_change", self.id));
let on_change = self.on_change;
let (id, startup, on_change) = (self.id, self.startup, &on_change);
let switch = |key: &'static str, on: bool, set: fn(&mut Startup, bool)| {
Switch::new((id.clone(), key), on)
+4 -13
View File
@@ -46,7 +46,7 @@ pub struct SettingsSearch {
id: ElementId,
entries: Vec<SettingEntry>,
search: Entity<TextInput>,
on_pick: Option<OnKey>,
on_pick: OnKey,
}
impl SettingsSearch {
@@ -55,29 +55,20 @@ impl SettingsSearch {
id: impl Into<ElementId>,
entries: impl IntoIterator<Item = SettingEntry>,
search: &Entity<TextInput>,
on_pick: impl Fn(&SharedString, &mut Window, &mut App) + 'static,
) -> Self {
Self {
id: id.into(),
entries: entries.into_iter().collect(),
search: search.clone(),
on_pick: None,
on_pick: Rc::new(on_pick),
}
}
pub fn on_pick(
mut self,
handler: impl Fn(&SharedString, &mut Window, &mut App) + 'static,
) -> Self {
self.on_pick = Some(Rc::new(handler));
self
}
}
impl RenderOnce for SettingsSearch {
fn render(self, _: &mut Window, cx: &mut App) -> impl IntoElement {
let on_pick = self
.on_pick
.unwrap_or_else(|| panic!("settings search {:?} has no on_pick", self.id));
let on_pick = self.on_pick;
let query = self.search.read(cx).text().trim().to_string();
let theme = cx.theme();
let found = if query.is_empty() {
+7 -17
View File
@@ -20,7 +20,7 @@ pub struct SettingsLayout {
sections: Vec<(SharedString, SharedString, IconName)>,
selected: SharedString,
page: Option<AnyElement>,
on_select: Option<OnKey>,
on_select: OnKey,
}
impl SettingsLayout {
@@ -29,22 +29,22 @@ impl SettingsLayout {
id: impl Into<ElementId>,
sections: impl IntoIterator<Item = (impl Into<SharedString>, impl Into<SharedString>, IconName)>,
selected: impl Into<SharedString>,
on_select: impl Fn(&SharedString, &mut Window, &mut App) + 'static,
) -> Self {
let sections: Vec<_> = sections
.into_iter()
.map(|(key, name, icon)| (key.into(), name.into(), icon))
.collect();
let selected = selected.into();
assert!(
sections.iter().any(|(key, _, _)| *key == selected),
"settings layout: no section {selected}"
);
if !sections.iter().any(|(key, _, _)| *key == selected) {
log::error!("settings layout: no section {selected}; none open");
}
Self {
id: id.into(),
sections,
selected,
page: None,
on_select: None,
on_select: Rc::new(on_select),
}
}
@@ -53,21 +53,11 @@ impl SettingsLayout {
self.page = Some(page.into_any_element());
self
}
pub fn on_select(
mut self,
handler: impl Fn(&SharedString, &mut Window, &mut App) + 'static,
) -> Self {
self.on_select = Some(Rc::new(handler));
self
}
}
impl RenderOnce for SettingsLayout {
fn render(self, _: &mut Window, cx: &mut App) -> impl IntoElement {
let on_select = self
.on_select
.unwrap_or_else(|| panic!("settings layout {:?} has no on_select", self.id));
let on_select = self.on_select;
let theme = cx.theme();
let muted = theme.colors.fg_muted;
let list = self.sections.iter().fold(
+31 -59
View File
@@ -29,32 +29,26 @@ type OnKey = Rc<dyn Fn(&SharedString, &mut Window, &mut App)>;
pub struct StorageSettings {
id: ElementId,
stores: Vec<Store>,
on_clear: Option<OnKey>,
on_clear: OnKey,
}
impl StorageSettings {
pub fn new(id: impl Into<ElementId>, stores: impl IntoIterator<Item = Store>) -> Self {
pub fn new(
id: impl Into<ElementId>,
stores: impl IntoIterator<Item = Store>,
on_clear: impl Fn(&SharedString, &mut Window, &mut App) + 'static,
) -> Self {
Self {
id: id.into(),
stores: stores.into_iter().collect(),
on_clear: None,
on_clear: Rc::new(on_clear),
}
}
pub fn on_clear(
mut self,
handler: impl Fn(&SharedString, &mut Window, &mut App) + 'static,
) -> Self {
self.on_clear = Some(Rc::new(handler));
self
}
}
impl RenderOnce for StorageSettings {
fn render(self, _: &mut Window, cx: &mut App) -> impl IntoElement {
let on_clear = self
.on_clear
.unwrap_or_else(|| panic!("storage settings {:?} has no on_clear", self.id));
let on_clear = self.on_clear;
let theme = cx.theme();
let total: u64 = self.stores.iter().map(|store| store.bytes).sum();
let bar = self.stores.iter().fold(
@@ -134,42 +128,30 @@ type Run = Rc<dyn Fn(&mut Window, &mut App)>;
pub struct ResetToDefault {
id: ElementId,
changed: Vec<Changed>,
on_reset: Option<OnKey>,
on_reset_all: Option<Run>,
on_reset: OnKey,
on_reset_all: Run,
}
impl ResetToDefault {
pub fn new(id: impl Into<ElementId>, changed: impl IntoIterator<Item = Changed>) -> Self {
pub fn new(
id: impl Into<ElementId>,
changed: impl IntoIterator<Item = Changed>,
on_reset: impl Fn(&SharedString, &mut Window, &mut App) + 'static,
on_reset_all: impl Fn(&mut Window, &mut App) + 'static,
) -> Self {
Self {
id: id.into(),
changed: changed.into_iter().collect(),
on_reset: None,
on_reset_all: None,
on_reset: Rc::new(on_reset),
on_reset_all: Rc::new(on_reset_all),
}
}
pub fn on_reset(
mut self,
handler: impl Fn(&SharedString, &mut Window, &mut App) + 'static,
) -> Self {
self.on_reset = Some(Rc::new(handler));
self
}
pub fn on_reset_all(mut self, handler: impl Fn(&mut Window, &mut App) + 'static) -> Self {
self.on_reset_all = Some(Rc::new(handler));
self
}
}
impl RenderOnce for ResetToDefault {
fn render(self, _: &mut Window, cx: &mut App) -> impl IntoElement {
let on_reset = self
.on_reset
.unwrap_or_else(|| panic!("reset to default {:?} has no on_reset", self.id));
let on_reset_all = self
.on_reset_all
.unwrap_or_else(|| panic!("reset to default {:?} has no on_reset_all", self.id));
let on_reset = self.on_reset;
let on_reset_all = self.on_reset_all;
let theme = cx.theme();
if self.changed.is_empty() {
return div()
@@ -249,38 +231,28 @@ type OnFile = Rc<dyn Fn(PathBuf, &mut Window, &mut App)>;
#[derive(IntoElement)]
pub struct ImportExportSettings {
id: ElementId,
on_export: Option<Run>,
on_import: Option<OnFile>,
on_export: Run,
on_import: OnFile,
}
impl ImportExportSettings {
pub fn new(id: impl Into<ElementId>) -> Self {
pub fn new(
id: impl Into<ElementId>,
on_export: impl Fn(&mut Window, &mut App) + 'static,
on_import: impl Fn(PathBuf, &mut Window, &mut App) + 'static,
) -> Self {
Self {
id: id.into(),
on_export: None,
on_import: None,
on_export: Rc::new(on_export),
on_import: Rc::new(on_import),
}
}
pub fn on_export(mut self, handler: impl Fn(&mut Window, &mut App) + 'static) -> Self {
self.on_export = Some(Rc::new(handler));
self
}
pub fn on_import(mut self, handler: impl Fn(PathBuf, &mut Window, &mut App) + 'static) -> Self {
self.on_import = Some(Rc::new(handler));
self
}
}
impl RenderOnce for ImportExportSettings {
fn render(self, _: &mut Window, _: &mut App) -> impl IntoElement {
let on_export = self
.on_export
.unwrap_or_else(|| panic!("import export settings {:?} has no on_export", self.id));
let on_import = self
.on_import
.unwrap_or_else(|| panic!("import export settings {:?} has no on_import", self.id));
let on_export = self.on_export;
let on_import = self.on_import;
let (on_export, on_import) = (on_export, on_import);
div()
.flex()
+7 -17
View File
@@ -57,7 +57,7 @@ pub struct SyntaxThemePicker {
id: ElementId,
themes: Vec<SyntaxTheme>,
selected: SharedString,
on_change: Option<OnName>,
on_change: OnName,
}
impl SyntaxThemePicker {
@@ -66,36 +66,26 @@ impl SyntaxThemePicker {
id: impl Into<ElementId>,
themes: impl IntoIterator<Item = SyntaxTheme>,
selected: impl Into<SharedString>,
on_change: impl Fn(&'static str, &mut Window, &mut App) + 'static,
) -> Self {
let themes: Vec<SyntaxTheme> = themes.into_iter().collect();
let selected = selected.into();
assert!(
themes.iter().any(|theme| theme.name == selected.as_ref()),
"syntax theme picker: no theme {selected}"
);
if !themes.iter().any(|theme| theme.name == selected.as_ref()) {
log::error!("syntax theme picker: no theme {selected}; none chosen");
}
Self {
id: id.into(),
themes,
selected,
on_change: None,
on_change: Rc::new(on_change),
}
}
pub fn on_change(
mut self,
handler: impl Fn(&'static str, &mut Window, &mut App) + 'static,
) -> Self {
self.on_change = Some(Rc::new(handler));
self
}
}
impl RenderOnce for SyntaxThemePicker {
fn render(self, _: &mut Window, cx: &mut App) -> impl IntoElement {
let id = self.id;
let on_change = self
.on_change
.unwrap_or_else(|| panic!("syntax theme picker {id:?} has no on_change"));
let on_change = self.on_change;
let mode: Mode = cx.theme().mode();
let cards = self.themes.iter().map(|theme| {
let (pick, name) = (on_change.clone(), theme.name);
+49 -7
View File
@@ -8,9 +8,13 @@ use crate::settings::{AccentColorPicker, FontSizeControl};
fn accent(_: &mut Window, _: &mut App, owner: Entity<Desk>) -> AnyElement {
let (red, blue) = (hsla(0.0, 0.6, 0.5, 1.0), hsla(0.6, 0.6, 0.5, 1.0));
AccentColorPicker::new("accent", red, [("Red", red), ("Blue", blue)])
.on_change(move |color, _, cx| say(&owner, format!("blue {}", color == blue), cx))
.into_any_element()
AccentColorPicker::new(
"accent",
red,
[("Red", red), ("Blue", blue)],
move |color, _, cx| say(&owner, format!("blue {}", color == blue), cx),
)
.into_any_element()
}
/// Stops: the presets, Red then Blue, then the custom well.
@@ -23,9 +27,10 @@ fn a_preset_sets_the_accent(cx: &mut TestAppContext) {
}
fn default_size(_: &mut Window, _: &mut App, owner: Entity<Desk>) -> AnyElement {
FontSizeControl::new("size", 14.0, 14.0, (12.0, 24.0))
.on_change(move |size, _, cx| say(&owner, format!("size {size}"), cx))
.into_any_element()
FontSizeControl::new("size", 14.0, 14.0, (12.0, 24.0), move |size, _, cx| {
say(&owner, format!("size {size}"), cx)
})
.into_any_element()
}
/// At the default size the way back rests, so the second stop wraps to the slider.
@@ -43,7 +48,12 @@ fn many_presets(_: &mut Window, _: &mut App, _: Entity<Desk>) -> AnyElement {
.collect();
div()
.w(px(280.0))
.child(AccentColorPicker::new("accent", presets[0].1, presets).on_change(|_, _, _| {}))
.child(AccentColorPicker::new(
"accent",
presets[0].1,
presets,
|_, _, _| {},
))
.into_any_element()
}
@@ -61,3 +71,35 @@ fn many_presets_fold_inside_a_narrow_box(cx: &mut TestAppContext) {
"its swatches fold to a second row: {picker:?}"
);
}
/// A size saved past the range, a section and a code palette no longer offered.
fn drifted(_: &mut Window, _: &mut App, _: Entity<Desk>) -> AnyElement {
let sections = [("a", "Appearance", crate::primitives::IconName::Palette)];
div()
.w(px(480.0))
.child(FontSizeControl::new(
"size",
40.0,
9.0,
(12.0, 24.0),
|_, _, _| {},
))
.child(crate::settings::SettingsLayout::new(
"layout",
sections,
"gone",
|_, _, _| {},
))
.child(crate::settings::SyntaxThemePicker::new(
"syntax",
crate::theme::syntax_themes(),
"Gone",
|_, _, _| {},
))
.into_any_element()
}
#[gpui::test]
fn settings_that_drifted_draw(cx: &mut TestAppContext) {
let _ = desk(drifted, cx);
}
+60 -47
View File
@@ -96,8 +96,8 @@ fn search(window: &mut Window, cx: &mut App, owner: Entity<Desk>) -> AnyElement
entry("size", "Font size", &["zoom"]),
],
&field,
move |key, _, cx| say(&owner, format!("go {key}"), cx),
)
.on_pick(move |key, _, cx| say(&owner, format!("go {key}"), cx))
.into_any_element()
}
@@ -121,10 +121,13 @@ fn reset(_: &mut Window, _: &mut App, owner: Entity<Desk>) -> AnyElement {
default: "off".into(),
};
let (one, all) = (owner.clone(), owner);
ResetToDefault::new("reset", [changed("a"), changed("b")])
.on_reset(move |key, _, cx| say(&one, format!("reset {key}"), cx))
.on_reset_all(move |_, cx| say(&all, "reset all".into(), cx))
.into_any_element()
ResetToDefault::new(
"reset",
[changed("a"), changed("b")],
move |key, _, cx| say(&one, format!("reset {key}"), cx),
move |_, cx| say(&all, "reset all".into(), cx),
)
.into_any_element()
}
/// Stops: each row's way back, then Reset all, which asks twice.
@@ -149,9 +152,13 @@ fn flags(window: &mut Window, cx: &mut App, owner: Entity<Desk>) -> AnyElement {
stage: Stage::Beta,
on,
};
FeatureFlags::new("flags", [flag("tabs", false), flag("sync", true)], &field)
.on_toggle(move |key, on, _, cx| say(&owner, format!("{key} {on}"), cx))
.into_any_element()
FeatureFlags::new(
"flags",
[flag("tabs", false), flag("sync", true)],
&field,
move |key, on, _, cx| say(&owner, format!("{key} {on}"), cx),
)
.into_any_element()
}
/// Stops: the search, then each flag's switch.
@@ -170,11 +177,10 @@ fn notices(_: &mut Window, _: &mut App, owner: Entity<Desk>) -> AnyElement {
on: vec![("mention".into(), "App".into())],
quiet: false,
};
NotificationSettings::new("notices", notices)
.on_change(move |notices, _, cx| {
say(&owner, format!("{:?} {}", notices.on, notices.quiet), cx)
})
.into_any_element()
NotificationSettings::new("notices", notices, move |notices, _, cx| {
say(&owner, format!("{:?} {}", notices.on, notices.quiet), cx)
})
.into_any_element()
}
/// Stops: Do not disturb, then a box per channel.
@@ -211,8 +217,8 @@ fn storage(_: &mut Window, _: &mut App, owner: Entity<Desk>) -> AnyElement {
clearable: false,
},
],
move |key, _, cx| say(&owner, format!("clear {key}"), cx),
)
.on_clear(move |key, _, cx| say(&owner, format!("clear {key}"), cx))
.into_any_element()
}
@@ -227,9 +233,10 @@ fn a_store_clears_on_the_second_press(cx: &mut TestAppContext) {
}
fn size(_: &mut Window, _: &mut App, owner: Entity<Desk>) -> AnyElement {
FontSizeControl::new("size", 18.0, 14.0, (12.0, 24.0))
.on_change(move |size, _, cx| say(&owner, format!("size {size}"), cx))
.into_any_element()
FontSizeControl::new("size", 18.0, 14.0, (12.0, 24.0), move |size, _, cx| {
say(&owner, format!("size {size}"), cx)
})
.into_any_element()
}
/// Stops: the slider, then the way back.
@@ -242,9 +249,10 @@ fn the_way_back_gives_the_default_size(cx: &mut TestAppContext) {
}
fn theme(_: &mut Window, _: &mut App, owner: Entity<Desk>) -> AnyElement {
ThemeSelector::new("theme", Appearance::Light)
.on_change(move |appearance, _, cx| say(&owner, format!("{appearance:?}"), cx))
.into_any_element()
ThemeSelector::new("theme", Appearance::Light, move |appearance, _, cx| {
say(&owner, format!("{appearance:?}"), cx)
})
.into_any_element()
}
/// Stops: a segment each.
@@ -262,9 +270,10 @@ fn proxy(_: &mut Window, _: &mut App, owner: Entity<Desk>) -> AnyElement {
port: 8080,
bypass: vec!["localhost".into()],
};
ProxySettings::new("proxy", proxy)
.on_apply(move |proxy, _, cx| say(&owner, format!("{proxy:?}"), cx))
.into_any_element()
ProxySettings::new("proxy", proxy, move |proxy, _, cx| {
say(&owner, format!("{proxy:?}"), cx)
})
.into_any_element()
}
/// Apply is the last stop, so a step back from nothing reaches it.
@@ -291,8 +300,8 @@ fn layout(_: &mut Window, _: &mut App, owner: Entity<Desk>) -> AnyElement {
("b", "Network", IconName::Globe),
],
"a",
move |key, _, cx| say(&owner, format!("open {key}"), cx),
)
.on_select(move |key, _, cx| say(&owner, format!("open {key}"), cx))
.page(div())
.into_any_element()
}
@@ -313,9 +322,10 @@ fn privacy(_: &mut Window, _: &mut App, owner: Entity<Desk>) -> AnyElement {
personalization: false,
history: true,
};
PrivacySettings::new("privacy", privacy)
.on_change(move |next, _, cx| say(&owner, format!("{next:?}"), cx))
.into_any_element()
PrivacySettings::new("privacy", privacy, move |next, _, cx| {
say(&owner, format!("{next:?}"), cx)
})
.into_any_element()
}
/// Stops: a switch per field, telemetry first.
@@ -339,15 +349,14 @@ fn quiet(_: &mut Window, _: &mut App, owner: Entity<Desk>) -> AnyElement {
on: vec![("replies".into(), "App".into())],
quiet: false,
};
NotificationSettings::new("notices", notices)
.on_change(move |next, _, cx| {
say(
&owner,
format!("quiet {} on {}", next.quiet, next.on.len()),
cx,
)
})
.into_any_element()
NotificationSettings::new("notices", notices, move |next, _, cx| {
say(
&owner,
format!("quiet {} on {}", next.quiet, next.on.len()),
cx,
)
})
.into_any_element()
}
/// Stops: Do not disturb, then a box per kind and channel.
@@ -370,8 +379,8 @@ fn startup(_: &mut Window, _: &mut App, owner: Entity<Desk>) -> AnyElement {
"startup",
startup,
[Choice::new("last", "Where I left off")],
move |next, _, cx| say(&owner, format!("login {}", next.at_login), cx),
)
.on_change(move |next, _, cx| say(&owner, format!("login {}", next.at_login), cx))
.into_any_element()
}
@@ -385,9 +394,10 @@ fn open_at_login_turns_on(cx: &mut TestAppContext) {
}
fn developer(_: &mut Window, _: &mut App, owner: Entity<Desk>) -> AnyElement {
DeveloperModeToggle::new("developer", false)
.on_change(move |on, _, cx| say(&owner, format!("developer {on}"), cx))
.into_any_element()
DeveloperModeToggle::new("developer", false, move |on, _, cx| {
say(&owner, format!("developer {on}"), cx)
})
.into_any_element()
}
/// Stops: the switch.
@@ -400,10 +410,12 @@ fn developer_mode_turns_on(cx: &mut TestAppContext) {
}
fn files(_: &mut Window, _: &mut App, owner: Entity<Desk>) -> AnyElement {
ImportExportSettings::new("files")
.on_export(move |_, cx| say(&owner, "export".into(), cx))
.on_import(|_, _, _| {})
.into_any_element()
ImportExportSettings::new(
"files",
move |_, cx| say(&owner, "export".into(), cx),
|_, _, _| {},
)
.into_any_element()
}
/// Stops: Export first.
@@ -421,9 +433,10 @@ fn bad_port(_: &mut Window, _: &mut App, owner: Entity<Desk>) -> AnyElement {
port: 0,
bypass: Vec::new(),
};
ProxySettings::new("proxy", proxy)
.on_apply(move |proxy, _, cx| say(&owner, format!("{proxy:?}"), cx))
.into_any_element()
ProxySettings::new("proxy", proxy, move |proxy, _, cx| {
say(&owner, format!("{proxy:?}"), cx)
})
.into_any_element()
}
/// With port 0 Apply rests, so a step back from nothing lands on the field before it.
+28 -30
View File
@@ -14,18 +14,17 @@ use crate::{
};
fn editor(_: &mut Window, cx: &mut App, owner: Entity<Desk>) -> AnyElement {
ThemeEditor::new("editor", ThemeDraft::of(cx.theme()))
.on_change(move |draft, _, cx| {
say(
&owner,
format!(
"{:?} contrast {} still {}",
draft.density, draft.high_contrast, draft.reduced_motion
),
cx,
)
})
.into_any_element()
ThemeEditor::new("editor", ThemeDraft::of(cx.theme()), move |draft, _, cx| {
say(
&owner,
format!(
"{:?} contrast {} still {}",
draft.density, draft.high_contrast, draft.reduced_motion
),
cx,
)
})
.into_any_element()
}
/// Stops: the three densities, corners, text size, high contrast, reduced motion, then the colors. A switch hands on a draft with its own flag turned and the rest as it stood.
@@ -47,12 +46,11 @@ fn a_switch_turns_its_own_flag(cx: &mut TestAppContext) {
}
fn colors(_: &mut Window, cx: &mut App, owner: Entity<Desk>) -> AnyElement {
ThemeEditor::new("editor", ThemeDraft::of(cx.theme()))
.on_change(move |draft, _, cx| {
let background = gpui::Rgba::from(draft.colors.bg);
say(&owner, crate::forms::hex(background), cx)
})
.into_any_element()
ThemeEditor::new("editor", ThemeDraft::of(cx.theme()), move |draft, _, cx| {
let background = gpui::Rgba::from(draft.colors.bg);
say(&owner, crate::forms::hex(background), cx)
})
.into_any_element()
}
/// The first color is the eighth stop; Enter opens its picker, where the hex field follows the eyedropper, a stop on macOS alone, and takes a new background.
@@ -71,15 +69,14 @@ fn a_color_well_sets_its_own_color(cx: &mut TestAppContext) {
}
fn look(_: &mut Window, cx: &mut App, owner: Entity<Desk>) -> AnyElement {
ThemeEditor::new("editor", ThemeDraft::of(cx.theme()))
.on_change(move |draft, _, cx| {
let words = format!(
"{:?} corners {} text {}",
draft.density, draft.radius_scale, draft.font_scale
);
say(&owner, words, cx)
})
.into_any_element()
ThemeEditor::new("editor", ThemeDraft::of(cx.theme()), move |draft, _, cx| {
let words = format!(
"{:?} corners {} text {}",
draft.density, draft.radius_scale, draft.font_scale
);
say(&owner, words, cx)
})
.into_any_element()
}
/// Stops: Compact, Standard, Comfortable, corners, text size. Compact picks the density alone, and Right on each slider moves its own measure a step.
@@ -157,9 +154,10 @@ fn the_thumb_covers_the_chosen_segment(cx: &mut TestAppContext) {
}
fn syntax(_: &mut Window, _: &mut App, owner: Entity<Desk>) -> AnyElement {
SyntaxThemePicker::new("syntax", syntax_themes(), "Ely")
.on_change(move |name, _, cx| say(&owner, name.to_string(), cx))
.into_any_element()
SyntaxThemePicker::new("syntax", syntax_themes(), "Ely", move |name, _, cx| {
say(&owner, name.to_string(), cx)
})
.into_any_element()
}
/// Stops: a card for each code palette; Space on the third picks Paper.
+2 -1
View File
@@ -49,7 +49,7 @@ Each item reads every site in its folders, fixes what the rules name, updates ca
- [x] P32 Canvas & Design — `src/canvas`
- [x] P33 DB & Dev Tools — `src/devtools`
- [x] P34 Dashboard — `src/dashboard`
- [ ] P35 Settings — `src/settings`
- [x] P35 Settings — `src/settings`
- [ ] P36 Account — `src/account`
- [ ] P37 Onboarding — `src/onboarding`
- [ ] P38 Interaction — `src/interaction`
@@ -100,3 +100,4 @@ One line per item: what changed, what stayed and why, review rounds.
- P32: the eight design panels take their handlers in `new` (AGENTS.md says so); edges, links, wires, links in progress, resizes, restacks, topics and whiteboard edits that name a node, port, shape, layer or topic gone from this frame log and change nothing; a wire's release fits its source as it now stands; `Topic::adding`, `removing` and `renaming` return `Option`; a mind map's selection, a palette's tool and a history position no longer held mark none. Kept: one-value contracts (alignment counts, frames, zoom, polygon sides, brush hues and type weights among the theme's tables), lookups over the panels' own option tables, button-gated expects. Review: 2 rounds.
- P33: six devtools take their handlers in `new` (contrast checker, environment selector, GraphQL explorer, WebSocket console, structure editor, index manager); `collection::moved` returns `Option` for a gone key or target or a drop into a request; a gone environment shows none chosen; an ER table without a place stays out. Kept: readings, costs, lookups over the components' own tables, button-gated expects, JSON that read writes back. Review: 1 round.
- P34: the refresh interval selector, filter bar and dashboard grid take their handlers in `new`; `tiles::arranged` and `stepped` return `Option` for a tile gone from the grid, so a landing or step after the owner removed it moves nothing; tiles without cards and cards without tiles stay out. Kept: columns, cells, intervals offered, services and uptimes, lookups from one render. Review: 1 round.
- P35: eighteen settings components take their handlers in `new`; a section or syntax theme not offered marks none; a font size or default outside its range pegs. Kept: options offered by construction, the proxy's Apply behind its read, drops that bring a file. Review: 1 round.