diff --git a/crates/core/src/switch.rs b/crates/core/src/switch.rs index 611b28a..23f1d27 100644 --- a/crates/core/src/switch.rs +++ b/crates/core/src/switch.rs @@ -96,16 +96,17 @@ impl RenderOnce for Switch { let ramps = theme.ramps; let metrics = theme.sizes.resolve(self.size); - let track_height = metrics.control_height; - let track_width = metrics.control_height * 1.65; + let track_height = metrics.switch_height; + let track_width = metrics.switch_width; - let knob = metrics.indicator_size; - let inset = metrics.plate_inset; + let knob = metrics.switch_knob; + let inset = (track_height - knob) / 2.; let travel = track_width - knob - inset * 2.; let next = !self.checked; let on_change = self.on_change; let a11y_label = self.a11y_label.or_else(|| self.label.clone()); + let id = self.id.clone(); let anim_config = SpringConfig::new(500., 35., 1.); let anim = SpringAnimation::new(anim_config).to(if self.checked { travel } else { px(0.) }); @@ -132,6 +133,10 @@ impl RenderOnce for Switch { div() .flex_none() .relative() + .debug_selector({ + let name = format!("{id}-track"); + move || name + }) .w(track_width) .h(track_height) .rounded(theme.radius.full) @@ -149,8 +154,6 @@ impl RenderOnce for Switch { .size(knob) .rounded(theme.radius.full) .bg(colors.primary_foreground) - .border_1() - .border_color(colors.border) .with_spring("switch-thumb", anim, move |thumb, offset| { thumb.left(inset + offset) }), diff --git a/crates/core/tests/switch.rs b/crates/core/tests/switch.rs index 5882110..ffedc18 100644 --- a/crates/core/tests/switch.rs +++ b/crates/core/tests/switch.rs @@ -83,6 +83,30 @@ fn disabled_switch_does_not_report_changes() { assert!(changes.borrow().is_empty()); } +/// The track renders at the platform's stated switch dims, not the generic +/// control height — the stated metric is the whole chrome. +#[test] +fn track_renders_at_the_stated_switch_dims() { + let (mut cx, _window, _changes) = switch_window(false, false); + let (width, height) = cx.update(|_window, cx| { + let metrics = Theme::global(cx).sizes.medium; + (metrics.switch_width, metrics.switch_height) + }); + let track = cx + .debug_bounds("test-switch-track") + .expect("the switch track should have been laid out"); + assert!( + (track.size.width - width).abs() <= px(0.5), + "track is {:?} wide, stated switch width is {width:?}", + track.size.width + ); + assert!( + (track.size.height - height).abs() <= px(0.5), + "track is {:?} tall, stated switch height is {height:?}", + track.size.height + ); +} + #[test] fn enter_and_space_report_the_next_value_while_focused() { let (mut cx, window, changes) = switch_window(false, false); diff --git a/docs/CHECKLIST.md b/docs/CHECKLIST.md index 758587e..97ac0f9 100644 --- a/docs/CHECKLIST.md +++ b/docs/CHECKLIST.md @@ -14,10 +14,6 @@ is the first downstream consumer; its screens drive the order. Platform metric adoption continues one component at a time; each stop goes through the review gate and a gallery pass. -- [ ] **Switch platform chrome** — stated track and knob dims - (`switch_height` / `switch_width` / `switch_knob`, inset and travel - derived); borderless knob. - Gaps surfaced by the signed comparison; ordered by signed's usage. - [ ] **Virtual list** — generic virtualized variable-height rows with a @@ -143,6 +139,11 @@ Gaps surfaced by the signed comparison; ordered by signed's usage. `Theme::borders.overlays` (and dark mode), taking the surface color where the platform paints none; it rides the dialog elevation, so dark mode's no-shadow rule applies to it too +- [x] Switch platform chrome — the track and knob take the stated + `switch_height` / `switch_width` / `switch_knob` (the knob's inset + derived to center it in the track, its travel from the remaining + width); the knob is borderless, and the track carries a debug selector + for headless geometry tests - [x] Button, Checkbox, Switch, Radio + RadioGroup, Toggle, Link, Slider, Progress, Avatar (image + seeded pixel-art fallback) - [x] Spinner — painted ring + rotating-icon variant, indeterminate and