Settings: controls give way, notices fold, and every edit needs its owner
A settings row's control slot shrinks to the box, so a control's text wraps and a palette's row folds; the font size's sample wraps in a plain block. A notice's boxes carry their channel's name and fold under the kind's name when narrow. canvas::panel::editing takes the owner's handler itself: the design panels, the structure editor, the index manager and the privacy, notice and startup settings take theirs at render and fail loud without one. Apply fails loud if pressed while the proxy does not read. Tests cover Apply resting on a bad port, the way back resting at the default size and a box pressed off. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
93de2a6bd6
commit
9a8dbcb92e
15 files changed
+172
-85
No files matched your search
@@ -397,3 +397,4 @@ Per-component lines live in `tasks/`. Tags there:
|
|||||||
| T33d | 2 | PASS | |
|
| T33d | 2 | PASS | |
|
||||||
| T34 | 1 | FAIL | a focused tile's Left and Right, and the guard that leaves arrows to a focused control inside a card, untested |
|
| T34 | 1 | FAIL | a focused tile's Left and Right, and the guard that leaves arrows to a focused control inside a card, untested |
|
||||||
| T34 | 2 | PASS | |
|
| T34 | 2 | PASS | |
|
||||||
|
| T35 | 1 | FAIL | a row's control kept its full width past a 280px box; a notice's name shrank to nothing; privacy, notices and startup dropped edits with no handler; Apply's and the reset's rest and a box turned off untested |
|
||||||
@@ -127,7 +127,10 @@ impl AutoLayoutControls {
|
|||||||
|
|
||||||
impl RenderOnce for AutoLayoutControls {
|
impl RenderOnce for AutoLayoutControls {
|
||||||
fn render(self, window: &mut Window, cx: &mut App) -> impl IntoElement {
|
fn render(self, window: &mut Window, cx: &mut App) -> impl IntoElement {
|
||||||
let (id, layout, on_change) = (self.id, self.layout, &self.on_change);
|
let on_change = self
|
||||||
|
.on_change
|
||||||
|
.unwrap_or_else(|| panic!("auto layout controls {:?} has no on_change", self.id));
|
||||||
|
let (id, layout, on_change) = (self.id, self.layout, &on_change);
|
||||||
let place = Rc::new(editing(
|
let place = Rc::new(editing(
|
||||||
"auto layout",
|
"auto layout",
|
||||||
&layout,
|
&layout,
|
||||||
|
|||||||
@@ -135,7 +135,10 @@ fn hairline(x: f32, y: f32, len: f32, across: bool, color: Hsla) -> gpui::Div {
|
|||||||
|
|
||||||
impl RenderOnce for ConstraintsEditor {
|
impl RenderOnce for ConstraintsEditor {
|
||||||
fn render(self, _: &mut Window, cx: &mut App) -> impl IntoElement {
|
fn render(self, _: &mut Window, cx: &mut App) -> impl IntoElement {
|
||||||
let (id, now, on_change) = (self.id, self.constraints, &self.on_change);
|
let on_change = self
|
||||||
|
.on_change
|
||||||
|
.unwrap_or_else(|| panic!("constraints editor {:?} has no on_change", self.id));
|
||||||
|
let (id, now, on_change) = (self.id, self.constraints, &on_change);
|
||||||
let theme = cx.theme();
|
let theme = cx.theme();
|
||||||
let (lit, rest) = (theme.colors.accent, theme.colors.border);
|
let (lit, rest) = (theme.colors.accent, theme.colors.border);
|
||||||
let pin = |on: bool| if on { lit } else { rest };
|
let pin = |on: bool| if on { lit } else { rest };
|
||||||
|
|||||||
@@ -138,7 +138,13 @@ impl ExportPanel {
|
|||||||
|
|
||||||
impl RenderOnce for ExportPanel {
|
impl RenderOnce for ExportPanel {
|
||||||
fn render(self, _: &mut Window, cx: &mut App) -> impl IntoElement {
|
fn render(self, _: &mut Window, cx: &mut App) -> impl IntoElement {
|
||||||
let (id, settings, on_change) = (self.id, self.settings, &self.on_change);
|
let on_change = self
|
||||||
|
.on_change
|
||||||
|
.unwrap_or_else(|| panic!("export panel {:?} has no on_change", self.id));
|
||||||
|
let on_export = self
|
||||||
|
.on_export
|
||||||
|
.unwrap_or_else(|| panic!("export panel {:?} has no on_export", self.id));
|
||||||
|
let (id, settings, on_change) = (self.id, self.settings, &on_change);
|
||||||
let files = export_files(&self.name, &settings);
|
let files = export_files(&self.name, &settings);
|
||||||
let next = added(&settings);
|
let next = added(&settings);
|
||||||
let add = editing("export panel", &settings, on_change, move |all, _: ()| {
|
let add = editing("export panel", &settings, on_change, move |all, _: ()| {
|
||||||
@@ -204,7 +210,7 @@ impl RenderOnce for ExportPanel {
|
|||||||
)
|
)
|
||||||
});
|
});
|
||||||
let theme = cx.theme();
|
let theme = cx.theme();
|
||||||
let (exported, on_export) = (files.clone(), self.on_export);
|
let (exported, on_export) = (files.clone(), on_export);
|
||||||
div()
|
div()
|
||||||
.flex()
|
.flex()
|
||||||
.flex_col()
|
.flex_col()
|
||||||
@@ -233,9 +239,7 @@ impl RenderOnce for ExportPanel {
|
|||||||
.disabled(exported.is_empty())
|
.disabled(exported.is_empty())
|
||||||
.on_click(move |_, window, cx| {
|
.on_click(move |_, window, cx| {
|
||||||
log::info!("export panel: {exported:?}");
|
log::info!("export panel: {exported:?}");
|
||||||
if let Some(on_export) = &on_export {
|
on_export(exported.clone(), window, cx);
|
||||||
on_export(exported.clone(), window, cx);
|
|
||||||
}
|
|
||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -45,6 +45,9 @@ impl InspectorPanel {
|
|||||||
|
|
||||||
impl RenderOnce for InspectorPanel {
|
impl RenderOnce for InspectorPanel {
|
||||||
fn render(self, _: &mut Window, cx: &mut App) -> impl IntoElement {
|
fn render(self, _: &mut Window, cx: &mut App) -> impl IntoElement {
|
||||||
|
let on_change = self
|
||||||
|
.on_change
|
||||||
|
.unwrap_or_else(|| panic!("inspector panel {:?} has no on_change", self.id));
|
||||||
let id = self.id;
|
let id = self.id;
|
||||||
let [shape] = self.selection.as_slice() else {
|
let [shape] = self.selection.as_slice() else {
|
||||||
let title: SharedString = match self.selection.len() {
|
let title: SharedString = match self.selection.len() {
|
||||||
@@ -55,7 +58,7 @@ impl RenderOnce for InspectorPanel {
|
|||||||
.body("Select one layer to see its properties.")
|
.body("Select one layer to see its properties.")
|
||||||
.into_any_element();
|
.into_any_element();
|
||||||
};
|
};
|
||||||
let on_change = &self.on_change;
|
let on_change = &on_change;
|
||||||
let at = |edit: fn(&mut Shape, f64)| editing("inspector", shape, on_change, edit);
|
let at = |edit: fn(&mut Shape, f64)| editing("inspector", shape, on_change, edit);
|
||||||
let scrub = |name: &'static str, value: f32, least: f64| {
|
let scrub = |name: &'static str, value: f32, least: f64| {
|
||||||
ScrubInput::new((id.clone(), name), name, value as f64)
|
ScrubInput::new((id.clone(), name), name, value as f64)
|
||||||
|
|||||||
+2
-4
@@ -17,7 +17,7 @@ pub(crate) type OnEdit<V> = Rc<dyn Fn(V, &mut Window, &mut App)>;
|
|||||||
pub(crate) fn editing<V, T>(
|
pub(crate) fn editing<V, T>(
|
||||||
what: &'static str,
|
what: &'static str,
|
||||||
value: &V,
|
value: &V,
|
||||||
on_change: &Option<OnEdit<V>>,
|
on_change: &OnEdit<V>,
|
||||||
edit: impl Fn(&mut V, T) + 'static,
|
edit: impl Fn(&mut V, T) + 'static,
|
||||||
) -> impl Fn(T, &mut Window, &mut App) + 'static
|
) -> impl Fn(T, &mut Window, &mut App) + 'static
|
||||||
where
|
where
|
||||||
@@ -29,9 +29,7 @@ where
|
|||||||
let mut next = value.clone();
|
let mut next = value.clone();
|
||||||
edit(&mut next, input);
|
edit(&mut next, input);
|
||||||
log::info!("{what}: {next:?}");
|
log::info!("{what}: {next:?}");
|
||||||
if let Some(on_change) = &on_change {
|
on_change(next, window, cx);
|
||||||
on_change(next, window, cx);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+14
-7
@@ -56,13 +56,14 @@ impl ColorPanel {
|
|||||||
|
|
||||||
impl RenderOnce for ColorPanel {
|
impl RenderOnce for ColorPanel {
|
||||||
fn render(self, _: &mut Window, cx: &mut App) -> impl IntoElement {
|
fn render(self, _: &mut Window, cx: &mut App) -> impl IntoElement {
|
||||||
let (picked, chosen) = (self.on_change.clone(), self.on_change);
|
let on_change = self
|
||||||
let report = |on_change: Option<OnEdit<Hsla>>| {
|
.on_change
|
||||||
|
.unwrap_or_else(|| panic!("color panel {:?} has no on_change", self.id));
|
||||||
|
let (picked, chosen) = (on_change.clone(), on_change);
|
||||||
|
let report = |on_change: OnEdit<Hsla>| {
|
||||||
move |color: Hsla, window: &mut Window, cx: &mut App| {
|
move |color: Hsla, window: &mut Window, cx: &mut App| {
|
||||||
log::info!("color panel: {color:?}");
|
log::info!("color panel: {color:?}");
|
||||||
if let Some(on_change) = &on_change {
|
on_change(color, window, cx);
|
||||||
on_change(color, window, cx);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
div()
|
div()
|
||||||
@@ -120,6 +121,9 @@ impl ShadowEditor {
|
|||||||
|
|
||||||
impl RenderOnce for ShadowEditor {
|
impl RenderOnce for ShadowEditor {
|
||||||
fn render(self, _: &mut Window, cx: &mut App) -> impl IntoElement {
|
fn render(self, _: &mut Window, cx: &mut App) -> impl IntoElement {
|
||||||
|
let on_change = self
|
||||||
|
.on_change
|
||||||
|
.unwrap_or_else(|| panic!("shadow editor {:?} has no on_change", self.id));
|
||||||
let lift = cx
|
let lift = cx
|
||||||
.theme()
|
.theme()
|
||||||
.elevation(Elevation::Floating)
|
.elevation(Elevation::Floating)
|
||||||
@@ -132,7 +136,7 @@ impl RenderOnce for ShadowEditor {
|
|||||||
color: shadow.color,
|
color: shadow.color,
|
||||||
})
|
})
|
||||||
.expect("a floating elevation casts a shadow");
|
.expect("a floating elevation casts a shadow");
|
||||||
let (id, shadows, on_change) = (self.id, self.shadows, &self.on_change);
|
let (id, shadows, on_change) = (self.id, self.shadows, &on_change);
|
||||||
let add = editing("shadow editor", &shadows, on_change, move |all, _: ()| {
|
let add = editing("shadow editor", &shadows, on_change, move |all, _: ()| {
|
||||||
all.push(lift)
|
all.push(lift)
|
||||||
});
|
});
|
||||||
@@ -265,7 +269,10 @@ const CORNERS: [&str; 4] = ["TL", "TR", "BR", "BL"];
|
|||||||
|
|
||||||
impl RenderOnce for BorderEditor {
|
impl RenderOnce for BorderEditor {
|
||||||
fn render(self, window: &mut Window, cx: &mut App) -> impl IntoElement {
|
fn render(self, window: &mut Window, cx: &mut App) -> impl IntoElement {
|
||||||
let (id, border, on_change) = (self.id, self.border, &self.on_change);
|
let on_change = self
|
||||||
|
.on_change
|
||||||
|
.unwrap_or_else(|| panic!("border editor {:?} has no on_change", self.id));
|
||||||
|
let (id, border, on_change) = (self.id, self.border, &on_change);
|
||||||
let parted = window.use_keyed_state((id.clone(), "split"), cx, |_, _| false);
|
let parted = window.use_keyed_state((id.clone(), "split"), cx, |_, _| false);
|
||||||
let uneven = border.radii.iter().any(|radius| *radius != border.radii[0]);
|
let uneven = border.radii.iter().any(|radius| *radius != border.radii[0]);
|
||||||
let split = *parted.read(cx) || uneven;
|
let split = *parted.read(cx) || uneven;
|
||||||
|
|||||||
@@ -73,7 +73,10 @@ impl TypographyPanel {
|
|||||||
|
|
||||||
impl RenderOnce for TypographyPanel {
|
impl RenderOnce for TypographyPanel {
|
||||||
fn render(self, _: &mut Window, cx: &mut App) -> impl IntoElement {
|
fn render(self, _: &mut Window, cx: &mut App) -> impl IntoElement {
|
||||||
let (id, style, on_change) = (self.id, self.style, &self.on_change);
|
let on_change = self
|
||||||
|
.on_change
|
||||||
|
.unwrap_or_else(|| panic!("typography panel {:?} has no on_change", self.id));
|
||||||
|
let (id, style, on_change) = (self.id, self.style, &on_change);
|
||||||
let theme = cx.theme();
|
let theme = cx.theme();
|
||||||
let family = editing(
|
let family = editing(
|
||||||
"typography panel",
|
"typography panel",
|
||||||
|
|||||||
@@ -123,7 +123,10 @@ impl TableStructureEditor {
|
|||||||
|
|
||||||
impl RenderOnce for TableStructureEditor {
|
impl RenderOnce for TableStructureEditor {
|
||||||
fn render(self, window: &mut Window, cx: &mut App) -> impl IntoElement {
|
fn render(self, window: &mut Window, cx: &mut App) -> impl IntoElement {
|
||||||
let (id, fields, on_change) = (self.id, self.fields, &self.on_change);
|
let on_change = self
|
||||||
|
.on_change
|
||||||
|
.unwrap_or_else(|| panic!("table structure editor {:?} has no on_change", self.id));
|
||||||
|
let (id, fields, on_change) = (self.id, self.fields, &on_change);
|
||||||
let room = window.use_keyed_state((id.clone(), "room"), cx, |_, _| Room::default());
|
let room = window.use_keyed_state((id.clone(), "room"), cx, |_, _| Room::default());
|
||||||
let rem = window.rem_size();
|
let rem = window.rem_size();
|
||||||
let theme = cx.theme();
|
let theme = cx.theme();
|
||||||
@@ -336,6 +339,8 @@ impl IndexManager {
|
|||||||
|
|
||||||
impl RenderOnce for IndexManager {
|
impl RenderOnce for IndexManager {
|
||||||
fn render(self, window: &mut Window, cx: &mut App) -> impl IntoElement {
|
fn render(self, window: &mut Window, cx: &mut App) -> impl IntoElement {
|
||||||
|
let on_create = self.on_create.expect("an index manager needs on_create");
|
||||||
|
let on_drop = self.on_drop.expect("an index manager needs on_drop");
|
||||||
let id = self.id;
|
let id = self.id;
|
||||||
let draft = window.use_keyed_state((id.clone(), "draft"), cx, |window, cx| Draft {
|
let draft = window.use_keyed_state((id.clone(), "draft"), cx, |window, cx| Draft {
|
||||||
name: cx.new(|cx| TextInput::new(window, cx).placeholder("Index name")),
|
name: cx.new(|cx| TextInput::new(window, cx).placeholder("Index name")),
|
||||||
@@ -345,7 +350,7 @@ impl RenderOnce for IndexManager {
|
|||||||
});
|
});
|
||||||
let theme = cx.theme();
|
let theme = cx.theme();
|
||||||
let rows = self.indexes.iter().map(|index| {
|
let rows = self.indexes.iter().map(|index| {
|
||||||
let (name, on_drop) = (index.name.clone(), self.on_drop.clone());
|
let (name, on_drop) = (index.name.clone(), on_drop.clone());
|
||||||
div()
|
div()
|
||||||
.flex()
|
.flex()
|
||||||
.items_center()
|
.items_center()
|
||||||
@@ -391,9 +396,7 @@ impl RenderOnce for IndexManager {
|
|||||||
.tooltip("Drop the index")
|
.tooltip("Drop the index")
|
||||||
.on_click(move |_, window, cx| {
|
.on_click(move |_, window, cx| {
|
||||||
log::info!("index manager: drop {name}");
|
log::info!("index manager: drop {name}");
|
||||||
if let Some(on_drop) = &on_drop {
|
on_drop(&name, window, cx);
|
||||||
on_drop(&name, window, cx);
|
|
||||||
}
|
|
||||||
}),
|
}),
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
@@ -407,7 +410,6 @@ impl RenderOnce for IndexManager {
|
|||||||
);
|
);
|
||||||
let ready = !name.read(cx).text().trim().is_empty() && !picked.is_empty();
|
let ready = !name.read(cx).text().trim().is_empty() && !picked.is_empty();
|
||||||
let [chose, flipped, pick_method, made] = [(); 4].map(|_| draft.clone());
|
let [chose, flipped, pick_method, made] = [(); 4].map(|_| draft.clone());
|
||||||
let on_create = self.on_create;
|
|
||||||
div()
|
div()
|
||||||
.flex()
|
.flex()
|
||||||
.flex_col()
|
.flex_col()
|
||||||
@@ -487,9 +489,7 @@ impl RenderOnce for IndexManager {
|
|||||||
index
|
index
|
||||||
});
|
});
|
||||||
log::info!("index manager: create {}", index.name);
|
log::info!("index manager: create {}", index.name);
|
||||||
if let Some(on_create) = &on_create {
|
on_create(index, window, cx);
|
||||||
on_create(index, window, cx);
|
|
||||||
}
|
|
||||||
}),
|
}),
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -204,13 +204,16 @@ fn another_engine_brings_its_port(cx: &mut TestAppContext) {
|
|||||||
fn narrow(_: &Bench, _: &mut Window, _: &mut App, _: Entity<Bench>) -> AnyElement {
|
fn narrow(_: &Bench, _: &mut Window, _: &mut App, _: Entity<Bench>) -> AnyElement {
|
||||||
div()
|
div()
|
||||||
.w(px(280.0))
|
.w(px(280.0))
|
||||||
.child(TableStructureEditor::new(
|
.child(
|
||||||
"structure",
|
TableStructureEditor::new(
|
||||||
[
|
"structure",
|
||||||
Field::new("user_id", "timestamptz"),
|
[
|
||||||
Field::new("total", "numeric"),
|
Field::new("user_id", "timestamptz"),
|
||||||
],
|
Field::new("total", "numeric"),
|
||||||
))
|
],
|
||||||
|
)
|
||||||
|
.on_change(|_, _, _| {}),
|
||||||
|
)
|
||||||
.into_any_element()
|
.into_any_element()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -236,10 +236,14 @@ impl RenderOnce for FontSizeControl {
|
|||||||
),
|
),
|
||||||
)
|
)
|
||||||
.child(
|
.child(
|
||||||
div()
|
div().flex().child(
|
||||||
.text_size(Pixels::from(self.size))
|
div()
|
||||||
.text_color(theme.colors.fg)
|
.flex_1()
|
||||||
.child("The quick brown fox jumps over the lazy dog."),
|
.min_w_0()
|
||||||
|
.text_size(Pixels::from(self.size))
|
||||||
|
.text_color(theme.colors.fg)
|
||||||
|
.child("The quick brown fox jumps over the lazy dog."),
|
||||||
|
),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -129,7 +129,10 @@ impl RenderOnce for ProxySettings {
|
|||||||
"manual" => manual(&text(&host), &text(&port), &text(&bypass)),
|
"manual" => manual(&text(&host), &text(&port), &text(&bypass)),
|
||||||
other => panic!("proxy settings: no way named {other}"),
|
other => panic!("proxy settings: no way named {other}"),
|
||||||
};
|
};
|
||||||
let (switched, on_apply) = (fields.clone(), self.on_apply);
|
let on_apply = self
|
||||||
|
.on_apply
|
||||||
|
.unwrap_or_else(|| panic!("proxy settings {id:?} has no on_apply"));
|
||||||
|
let switched = fields.clone();
|
||||||
div()
|
div()
|
||||||
.flex()
|
.flex()
|
||||||
.flex_col()
|
.flex_col()
|
||||||
@@ -181,10 +184,9 @@ impl RenderOnce for ProxySettings {
|
|||||||
.variant(ButtonVariant::Primary)
|
.variant(ButtonVariant::Primary)
|
||||||
.disabled(read.is_none())
|
.disabled(read.is_none())
|
||||||
.on_click(move |_, window, cx| {
|
.on_click(move |_, window, cx| {
|
||||||
if let (Some(proxy), Some(on_apply)) = (read.clone(), &on_apply) {
|
let proxy = read.clone().expect("Apply rests until the proxy reads");
|
||||||
log::info!("proxy settings: apply {proxy:?}");
|
log::info!("proxy settings: apply {proxy:?}");
|
||||||
on_apply(proxy, window, cx);
|
on_apply(proxy, window, cx);
|
||||||
}
|
|
||||||
}),
|
}),
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
|||||||
+44
-39
@@ -46,7 +46,10 @@ impl PrivacySettings {
|
|||||||
|
|
||||||
impl RenderOnce for PrivacySettings {
|
impl RenderOnce for PrivacySettings {
|
||||||
fn render(self, _: &mut Window, _: &mut App) -> impl IntoElement {
|
fn render(self, _: &mut Window, _: &mut App) -> impl IntoElement {
|
||||||
let (id, privacy, on_change) = (self.id, self.privacy, &self.on_change);
|
let on_change = self
|
||||||
|
.on_change
|
||||||
|
.unwrap_or_else(|| panic!("privacy settings {:?} has no on_change", self.id));
|
||||||
|
let (id, privacy, on_change) = (self.id, self.privacy, &on_change);
|
||||||
let row = |key: &'static str,
|
let row = |key: &'static str,
|
||||||
title: &'static str,
|
title: &'static str,
|
||||||
text: &'static str,
|
text: &'static str,
|
||||||
@@ -100,7 +103,7 @@ pub struct Notices {
|
|||||||
pub quiet: bool,
|
pub quiet: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A grid of notices: a row for each kind, a box for each channel it may arrive by, and a switch that holds every notice back.
|
/// Notices by kind: a row for each, a named box for each channel it may arrive by that folds under the name when narrow, and a switch that holds every notice back.
|
||||||
#[derive(IntoElement)]
|
#[derive(IntoElement)]
|
||||||
pub struct NotificationSettings {
|
pub struct NotificationSettings {
|
||||||
id: ElementId,
|
id: ElementId,
|
||||||
@@ -125,55 +128,54 @@ impl NotificationSettings {
|
|||||||
|
|
||||||
impl RenderOnce for NotificationSettings {
|
impl RenderOnce for NotificationSettings {
|
||||||
fn render(self, _: &mut Window, cx: &mut App) -> impl IntoElement {
|
fn render(self, _: &mut Window, cx: &mut App) -> impl IntoElement {
|
||||||
let (id, notices, on_change) = (self.id, self.notices, &self.on_change);
|
let on_change = self
|
||||||
|
.on_change
|
||||||
|
.unwrap_or_else(|| panic!("notification settings {:?} has no on_change", self.id));
|
||||||
|
let (id, notices, on_change) = (self.id, self.notices, &on_change);
|
||||||
let theme = cx.theme();
|
let theme = cx.theme();
|
||||||
let cell = theme.label_width() * 0.5;
|
|
||||||
let head = div()
|
|
||||||
.flex()
|
|
||||||
.items_center()
|
|
||||||
.gap_2()
|
|
||||||
.text_size(theme.text_size(TextSize::Xs))
|
|
||||||
.text_color(theme.colors.fg_subtle)
|
|
||||||
.child(div().flex_1().min_w_0())
|
|
||||||
.children(notices.channels.iter().map(|channel| {
|
|
||||||
div()
|
|
||||||
.flex_none()
|
|
||||||
.w(cell)
|
|
||||||
.child(Ellipsis::new(channel.clone()))
|
|
||||||
}));
|
|
||||||
let rows = notices.kinds.iter().map(|(kind, name)| {
|
let rows = notices.kinds.iter().map(|(kind, name)| {
|
||||||
|
let boxes = notices.channels.iter().map(|channel| {
|
||||||
|
let pair = (kind.clone(), channel.clone());
|
||||||
|
let on = notices.on.contains(&pair);
|
||||||
|
let set = editing(
|
||||||
|
"notifications",
|
||||||
|
¬ices,
|
||||||
|
on_change,
|
||||||
|
move |notices: &mut Notices, on: bool| {
|
||||||
|
notices.on.retain(|each| *each != pair);
|
||||||
|
if on {
|
||||||
|
notices.on.push(pair.clone());
|
||||||
|
}
|
||||||
|
},
|
||||||
|
);
|
||||||
|
Checkbox::new((id.clone(), format!("{kind}-{channel}")), on)
|
||||||
|
.label(channel.clone())
|
||||||
|
.on_change(set)
|
||||||
|
});
|
||||||
div()
|
div()
|
||||||
.flex()
|
.flex()
|
||||||
|
.flex_wrap()
|
||||||
.items_center()
|
.items_center()
|
||||||
.gap_2()
|
.gap_x_4()
|
||||||
|
.gap_y_1()
|
||||||
.py_2()
|
.py_2()
|
||||||
.border_b_1()
|
.border_b_1()
|
||||||
.border_color(theme.colors.border)
|
.border_color(theme.colors.border)
|
||||||
.child(
|
.child(
|
||||||
div()
|
div()
|
||||||
.flex_1()
|
.flex_1()
|
||||||
.min_w_0()
|
.min_w(theme.label_width())
|
||||||
.text_size(theme.text_size(TextSize::Sm))
|
.text_size(theme.text_size(TextSize::Sm))
|
||||||
.child(Ellipsis::new(name.clone())),
|
.child(Ellipsis::new(name.clone())),
|
||||||
)
|
)
|
||||||
.children(notices.channels.iter().map(|channel| {
|
.child(
|
||||||
let pair = (kind.clone(), channel.clone());
|
div()
|
||||||
let on = notices.on.contains(&pair);
|
.flex()
|
||||||
let set = editing(
|
.flex_wrap()
|
||||||
"notifications",
|
.flex_none()
|
||||||
¬ices,
|
.gap_x_3()
|
||||||
on_change,
|
.children(boxes),
|
||||||
move |notices: &mut Notices, on: bool| {
|
)
|
||||||
notices.on.retain(|each| *each != pair);
|
|
||||||
if on {
|
|
||||||
notices.on.push(pair.clone());
|
|
||||||
}
|
|
||||||
},
|
|
||||||
);
|
|
||||||
div().flex_none().w(cell).child(
|
|
||||||
Checkbox::new((id.clone(), format!("{kind}-{channel}")), on).on_change(set),
|
|
||||||
)
|
|
||||||
}))
|
|
||||||
});
|
});
|
||||||
let quiet = editing(
|
let quiet = editing(
|
||||||
"notifications",
|
"notifications",
|
||||||
@@ -188,7 +190,7 @@ impl RenderOnce for NotificationSettings {
|
|||||||
.description("Hold every notice back until it is off.")
|
.description("Hold every notice back until it is off.")
|
||||||
.control(Switch::new((id.clone(), "quiet"), notices.quiet).on_change(quiet)),
|
.control(Switch::new((id.clone(), "quiet"), notices.quiet).on_change(quiet)),
|
||||||
)
|
)
|
||||||
.row(div().pt_3().child(head).children(rows))
|
.row(div().pt_3().children(rows))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -232,7 +234,10 @@ impl StartupSettings {
|
|||||||
|
|
||||||
impl RenderOnce for StartupSettings {
|
impl RenderOnce for StartupSettings {
|
||||||
fn render(self, _: &mut Window, cx: &mut App) -> impl IntoElement {
|
fn render(self, _: &mut Window, cx: &mut App) -> impl IntoElement {
|
||||||
let (id, startup, on_change) = (self.id, self.startup, &self.on_change);
|
let on_change = self
|
||||||
|
.on_change
|
||||||
|
.unwrap_or_else(|| panic!("startup settings {:?} has no on_change", self.id));
|
||||||
|
let (id, startup, on_change) = (self.id, self.startup, &on_change);
|
||||||
let switch = |key: &'static str, on: bool, set: fn(&mut Startup, bool)| {
|
let switch = |key: &'static str, on: bool, set: fn(&mut Startup, bool)| {
|
||||||
Switch::new((id.clone(), key), on)
|
Switch::new((id.clone(), key), on)
|
||||||
.on_change(editing("startup", &startup, on_change, set))
|
.on_change(editing("startup", &startup, on_change, set))
|
||||||
|
|||||||
@@ -217,6 +217,9 @@ impl RenderOnce for SettingsRow {
|
|||||||
.child(text)
|
.child(text)
|
||||||
})),
|
})),
|
||||||
)
|
)
|
||||||
.children(self.control.map(|control| div().flex_none().child(control)))
|
.children(
|
||||||
|
self.control
|
||||||
|
.map(|control| div().flex_none().min_w_0().max_w_full().child(control)),
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -430,3 +430,51 @@ fn export_asks_the_owner(cx: &mut TestAppContext) {
|
|||||||
tap("space", cx);
|
tap("space", cx);
|
||||||
assert_eq!(said(&host, cx), ["export"]);
|
assert_eq!(said(&host, cx), ["export"]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn bad_port(_: &mut Window, _: &mut App, owner: Entity<Desk>) -> AnyElement {
|
||||||
|
let proxy = Proxy::Manual {
|
||||||
|
host: "proxy.corp".into(),
|
||||||
|
port: 0,
|
||||||
|
bypass: Vec::new(),
|
||||||
|
};
|
||||||
|
ProxySettings::new("proxy", proxy)
|
||||||
|
.on_apply(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.
|
||||||
|
#[gpui::test]
|
||||||
|
fn apply_rests_while_the_port_is_bad(cx: &mut TestAppContext) {
|
||||||
|
let (host, cx) = desk(bad_port, cx);
|
||||||
|
cx.update(|window, _| {
|
||||||
|
window.blur();
|
||||||
|
window.focus_prev();
|
||||||
|
});
|
||||||
|
settle(cx);
|
||||||
|
tap("space", cx);
|
||||||
|
assert!(said(&host, cx).is_empty());
|
||||||
|
}
|
||||||
|
|
||||||
|
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()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// At the default size the way back rests, so the second stop wraps to the slider.
|
||||||
|
#[gpui::test]
|
||||||
|
fn the_way_back_rests_at_the_default_size(cx: &mut TestAppContext) {
|
||||||
|
let (host, cx) = desk(default_size, cx);
|
||||||
|
tab(2, cx);
|
||||||
|
tap("space", cx);
|
||||||
|
assert!(said(&host, cx).is_empty());
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Stops: Do not disturb, then App, which is on.
|
||||||
|
#[gpui::test]
|
||||||
|
fn a_box_pressed_off_lets_its_pair_go(cx: &mut TestAppContext) {
|
||||||
|
let (host, cx) = desk(notices, cx);
|
||||||
|
tab(2, cx);
|
||||||
|
tap("space", cx);
|
||||||
|
assert_eq!(said(&host, cx), ["[] false"]);
|
||||||
|
}
|
||||||
Reference in new issue
Block a user