1use iced::border::Radius;
12use iced::keyboard::key::Named;
13use iced::widget::{self, button::Status, container, row};
14use iced::{Background, Border, Element, Length, Theme};
15
16use crate::icon::Glyph;
17use crate::keys::{self, Chord, Keymap};
18use crate::natural::natural;
19use crate::primitives::toggle::{self, Size};
20use crate::theme::{Tokens, fade, mix, radius, space};
21
22#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
24pub enum Mode {
25 #[default]
27 Single,
28 Multiple,
30}
31
32impl Mode {
33 pub const ALL: [Mode; 2] = [Mode::Single, Mode::Multiple];
34}
35
36#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
38pub enum Variant {
39 #[default]
41 Default,
42 Outline,
44}
45
46impl Variant {
47 pub const ALL: [Variant; 2] = [Variant::Default, Variant::Outline];
48}
49
50#[derive(Debug, Clone, PartialEq)]
52pub struct Item<Id> {
53 pub id: Id,
54 pub label: String,
55 pub icon: Option<Glyph>,
56 pub disabled: bool,
57}
58
59pub fn item<Id>(id: Id, label: impl Into<String>) -> Item<Id> {
61 Item {
62 id,
63 label: label.into(),
64 icon: None,
65 disabled: false,
66 }
67}
68
69impl<Id> Item<Id> {
70 pub fn icon(mut self, glyph: Glyph) -> Self {
71 self.icon = Some(glyph);
72 self
73 }
74
75 pub fn disabled(mut self, disabled: bool) -> Self {
76 self.disabled = disabled;
77 self
78 }
79}
80
81#[derive(Debug, Clone, Copy, PartialEq, Eq)]
83pub enum Event<Id> {
84 Toggle(Id),
87 Next,
89 Previous,
92}
93
94#[derive(Debug, Clone, PartialEq)]
96pub struct State<Id> {
97 items: Vec<Item<Id>>,
98 mode: Mode,
99 required: bool,
100 selected: Vec<bool>,
101}
102
103impl<Id: Copy + PartialEq> State<Id> {
104 pub fn single(items: impl IntoIterator<Item = Item<Id>>) -> Self {
106 Self::new(items, Mode::Single)
107 }
108
109 pub fn multiple(items: impl IntoIterator<Item = Item<Id>>) -> Self {
111 Self::new(items, Mode::Multiple)
112 }
113
114 fn new(items: impl IntoIterator<Item = Item<Id>>, mode: Mode) -> Self {
115 let items: Vec<_> = items.into_iter().collect();
116 let selected = vec![false; items.len()];
117 Self {
118 items,
119 mode,
120 required: false,
121 selected,
122 }
123 }
124
125 pub fn with_selected(mut self, ids: impl IntoIterator<Item = Id>) -> Self {
128 let mut replaced = false;
129 for id in ids {
130 let Some(index) = self.enabled_index(id) else {
131 continue;
132 };
133 if self.mode == Mode::Single {
134 if replaced {
135 break;
136 }
137 self.selected.fill(false);
138 replaced = true;
139 }
140 self.selected[index] = true;
141 }
142 self
143 }
144
145 pub fn required(mut self, required: bool) -> Self {
149 self.required = required;
150 if required
151 && self.mode == Mode::Single
152 && !self.selected.contains(&true)
153 && let Some(index) = self.items.iter().position(|item| !item.disabled)
154 {
155 self.selected[index] = true;
156 }
157 self
158 }
159
160 pub fn mode(&self) -> Mode {
161 self.mode
162 }
163
164 pub fn is_required(&self) -> bool {
165 self.required
166 }
167
168 pub fn items(&self) -> &[Item<Id>] {
169 &self.items
170 }
171
172 pub fn selected(&self) -> Vec<Id> {
174 self.items
175 .iter()
176 .zip(&self.selected)
177 .filter_map(|(item, on)| on.then_some(item.id))
178 .collect()
179 }
180
181 pub fn selected_one(&self) -> Option<Id> {
183 let index = self.selected.iter().position(|on| *on)?;
184 Some(self.items[index].id)
185 }
186
187 pub fn is_selected(&self, id: Id) -> bool {
188 self.index(id).is_some_and(|index| self.selected[index])
189 }
190
191 pub fn set_disabled(&mut self, id: Id, disabled: bool) {
193 if let Some(item) = self.items.iter_mut().find(|item| item.id == id) {
194 item.disabled = disabled;
195 }
196 }
197
198 pub fn update(&mut self, event: Event<Id>) -> Option<Vec<Id>> {
201 let changed = match event {
202 Event::Toggle(id) => self.toggle(id),
203 Event::Next => self.step(true),
204 Event::Previous => self.step(false),
205 };
206 changed.then(|| self.selected())
207 }
208
209 fn toggle(&mut self, id: Id) -> bool {
210 let Some(index) = self.enabled_index(id) else {
211 return false;
212 };
213 let on = self.selected[index];
214 match self.mode {
215 Mode::Multiple => self.selected[index] = !on,
216 Mode::Single if on && self.required => return false,
217 Mode::Single => {
218 self.selected.fill(false);
219 self.selected[index] = !on;
220 }
221 }
222 true
223 }
224
225 fn step(&mut self, forward: bool) -> bool {
228 let len = self.items.len();
229 if self.mode != Mode::Single || len == 0 {
230 return false;
231 }
232 let current = self.selected.iter().position(|on| *on);
233 let start = current.unwrap_or(if forward { len - 1 } else { 0 });
234 let offset = if forward { 1 } else { len - 1 };
235 let Some(target) = (1..=len)
236 .map(|n| (start + offset * n) % len)
237 .find(|&index| !self.items[index].disabled)
238 else {
239 return false;
240 };
241 if current == Some(target) {
242 return false;
243 }
244 self.selected.fill(false);
245 self.selected[target] = true;
246 true
247 }
248
249 fn index(&self, id: Id) -> Option<usize> {
250 self.items.iter().position(|item| item.id == id)
251 }
252
253 fn enabled_index(&self, id: Id) -> Option<usize> {
254 self.index(id).filter(|&index| !self.items[index].disabled)
255 }
256}
257
258#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
260pub enum Action {
261 Next,
262 Previous,
263}
264
265impl Action {
266 pub fn event<Id: Copy + PartialEq>(self, state: &State<Id>) -> Option<Event<Id>> {
269 if state.mode != Mode::Single {
270 return None;
271 }
272 Some(match self {
273 Action::Next => Event::Next,
274 Action::Previous => Event::Previous,
275 })
276 }
277}
278
279impl keys::Action for Action {
280 const ALL: &'static [Self] = &[Action::Next, Action::Previous];
281
282 fn defaults() -> Keymap<Self> {
283 default_keymap()
284 }
285
286 fn name(self) -> &'static str {
287 match self {
288 Action::Next => "Next",
289 Action::Previous => "Previous",
290 }
291 }
292
293 fn description(self) -> &'static str {
294 match self {
295 Action::Next => "Turns on the next enabled item of a single-choice group, wrapping.",
296 Action::Previous => {
297 "Turns on the previous enabled item of a single-choice group, wrapping."
298 }
299 }
300 }
301}
302
303pub fn default_keymap() -> Keymap<Action> {
314 Keymap::new()
315 .bind(Chord::named(Named::ArrowRight), Action::Next)
316 .bind(Chord::named(Named::ArrowLeft), Action::Previous)
317}
318
319pub struct ToggleGroup<'a, Id, Message> {
321 state: &'a State<Id>,
322 variant: Variant,
323 size: Size,
324 width: Length,
325 on_event: Option<Box<dyn Fn(Event<Id>) -> Message + 'a>>,
326}
327
328pub fn toggle_group<Id, Message>(state: &State<Id>) -> ToggleGroup<'_, Id, Message> {
331 ToggleGroup {
332 state,
333 variant: Variant::default(),
334 size: Size::default(),
335 width: Length::Shrink,
336 on_event: None,
337 }
338}
339
340impl<Id: std::fmt::Debug, Message> std::fmt::Debug for ToggleGroup<'_, Id, Message> {
341 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
342 f.debug_struct("ToggleGroup")
343 .field("state", self.state)
344 .field("variant", &self.variant)
345 .field("size", &self.size)
346 .field("width", &self.width)
347 .field("enabled", &self.on_event.is_some())
348 .finish()
349 }
350}
351
352impl<'a, Id, Message> ToggleGroup<'a, Id, Message> {
353 pub fn variant(mut self, variant: Variant) -> Self {
354 self.variant = variant;
355 self
356 }
357
358 pub fn size(mut self, size: Size) -> Self {
359 self.size = size;
360 self
361 }
362
363 pub fn width(mut self, width: impl Into<Length>) -> Self {
367 self.width = width.into();
368 self
369 }
370
371 pub fn on_event(mut self, on_event: impl Fn(Event<Id>) -> Message + 'a) -> Self {
372 self.on_event = Some(Box::new(on_event));
373 self
374 }
375}
376
377#[derive(Debug, Clone, Copy, PartialEq, Eq)]
379pub enum Position {
380 Only,
381 First,
382 Middle,
383 Last,
384}
385
386impl Position {
387 fn of(index: usize, len: usize) -> Self {
388 match (index, len) {
389 (_, 1) => Position::Only,
390 (0, _) => Position::First,
391 (index, len) if index + 1 == len => Position::Last,
392 _ => Position::Middle,
393 }
394 }
395}
396
397impl<'a, Id, Message> From<ToggleGroup<'a, Id, Message>> for Element<'a, Message>
398where
399 Id: Copy + PartialEq + 'a,
400 Message: Clone + 'a,
401{
402 fn from(group: ToggleGroup<'a, Id, Message>) -> Self {
403 let ToggleGroup {
404 state,
405 variant,
406 size,
407 width,
408 on_event,
409 } = group;
410 let len = state.items.len();
411 let shrink = width == Length::Shrink;
412 let item_width = if shrink {
413 Length::Shrink
414 } else {
415 Length::FillPortion(1)
416 };
417 let metrics = match variant {
420 Variant::Default => size.metrics(),
421 Variant::Outline => toggle::Metrics {
422 height: size.metrics().height - 2.0 * BORDER,
423 ..size.metrics()
424 },
425 };
426
427 let items = state.items.iter().enumerate().map(|(index, entry)| {
428 let pressed = state.selected[index];
429 let message = on_event
430 .as_ref()
431 .filter(|_| !entry.disabled)
432 .map(|on_event| on_event(Event::Toggle(entry.id)));
433 let position = Position::of(index, len);
434 toggle::button(
435 entry.label.as_str(),
436 entry.icon,
437 pressed,
438 metrics,
439 item_width,
440 message,
441 move |tokens, status| item_style(tokens, variant, position, pressed, status),
442 )
443 });
444
445 let enabled = on_event.is_some();
446 match variant {
450 Variant::Default if shrink => row(items.map(natural)).spacing(space::XS).wrap().into(),
451 Variant::Default => row(items).spacing(space::XS).width(width).into(),
452 Variant::Outline => {
453 let mut segments = row![].width(if shrink { Length::Shrink } else { Length::Fill });
454 for (index, item) in items.enumerate() {
455 if index > 0 {
456 segments = segments.push(divider(metrics.height, enabled));
457 }
458 segments = segments.push(item);
459 }
460 let group = container(segments)
461 .padding(BORDER)
462 .width(width)
463 .style(move |theme: &Theme| group_style(&Tokens::of(theme), variant, enabled));
464 if shrink { natural(group) } else { group.into() }
465 }
466 }
467 }
468}
469
470fn divider<'a, Message: 'a>(height: f32, enabled: bool) -> Element<'a, Message> {
473 container(widget::space())
474 .width(BORDER)
475 .height(height)
476 .style(move |theme: &Theme| divider_style(&Tokens::of(theme), enabled))
477 .into()
478}
479
480pub fn divider_style(tokens: &Tokens, enabled: bool) -> container::Style {
482 let colour = if enabled {
483 tokens.border
484 } else {
485 fade(tokens.border, 0.5)
486 };
487 container::Style {
488 background: Some(Background::Color(colour)),
489 ..container::Style::default()
490 }
491}
492
493const BORDER: f32 = 1.0;
495
496pub fn item_style(
499 tokens: &Tokens,
500 variant: Variant,
501 position: Position,
502 pressed: bool,
503 status: Status,
504) -> widget::button::Style {
505 let base = toggle::style(tokens, toggle::Variant::Default, pressed, status);
506 if variant == Variant::Default {
507 return base;
508 }
509 let outer = radius::MD - BORDER;
512 let radius = match position {
513 Position::Only => Radius::from(outer),
514 Position::First => Radius::default().left(outer),
515 Position::Middle => Radius::default(),
516 Position::Last => Radius::default().right(outer),
517 };
518 widget::button::Style {
519 background: selected_background(tokens, pressed, status).or(base.background),
520 border: Border {
521 radius,
522 ..base.border
523 },
524 ..base
525 }
526}
527
528fn selected_background(tokens: &Tokens, pressed: bool, status: Status) -> Option<Background> {
532 if !pressed {
533 return None;
534 }
535 let amount = match status {
536 Status::Hovered | Status::Pressed => 0.18,
537 Status::Active => 0.1,
538 Status::Disabled => return None,
539 };
540 Some(Background::Color(mix(
541 tokens.accent,
542 tokens.foreground,
543 amount,
544 )))
545}
546
547pub fn group_style(tokens: &Tokens, variant: Variant, enabled: bool) -> container::Style {
550 match variant {
551 Variant::Default => container::Style::default(),
552 Variant::Outline => container::Style {
553 background: Some(Background::Color(tokens.background)),
554 border: Border {
555 color: if enabled {
556 tokens.border
557 } else {
558 fade(tokens.border, 0.5)
559 },
560 width: BORDER,
561 radius: radius::MD.into(),
562 },
563 ..container::Style::default()
564 },
565 }
566}
567
568#[cfg(test)]
569mod tests {
570 use super::*;
571 use crate::theme::{dark, light};
572
573 fn items() -> [Item<u8>; 4] {
574 [
575 item(0, "A"),
576 item(1, "B").disabled(true),
577 item(2, "C"),
578 item(3, "D"),
579 ]
580 }
581
582 #[test]
583 fn groups_start_with_nothing_on() {
584 for state in [State::single(items()), State::multiple(items())] {
585 assert!(state.selected().is_empty());
586 assert_eq!(state.selected_one(), None);
587 assert!(!state.is_required());
588 }
589 assert_eq!(State::single(items()).mode(), Mode::Single);
590 assert_eq!(State::multiple(items()).mode(), Mode::Multiple);
591 }
592
593 #[test]
594 fn single_mode_moves_the_selection_and_can_clear_it() {
595 let mut state = State::single(items());
596 assert_eq!(state.update(Event::Toggle(0)), Some(vec![0]));
597 assert_eq!(state.update(Event::Toggle(3)), Some(vec![3]));
598 assert!(!state.is_selected(0));
599 assert_eq!(state.update(Event::Toggle(3)), Some(vec![]));
600 assert_eq!(state.selected_one(), None);
601 }
602
603 #[test]
604 fn required_single_mode_never_empties() {
605 let mut state = State::single(items()).required(true);
606 assert_eq!(state.selected(), vec![0]);
607 assert_eq!(state.update(Event::Toggle(0)), None);
608 assert_eq!(state.update(Event::Toggle(2)), Some(vec![2]));
609 assert_eq!(state.update(Event::Toggle(2)), None);
610
611 let kept = State::single(items()).with_selected([3]).required(true);
612 assert_eq!(kept.selected(), vec![3]);
613 let replaced = State::single(items()).required(true).with_selected([3]);
614 assert_eq!(replaced.selected(), vec![3]);
615 }
616
617 #[test]
618 fn multiple_mode_switches_items_independently() {
619 let mut state = State::multiple(items());
620 assert_eq!(state.update(Event::Toggle(0)), Some(vec![0]));
621 assert_eq!(state.update(Event::Toggle(3)), Some(vec![0, 3]));
622 assert_eq!(state.update(Event::Toggle(0)), Some(vec![3]));
623 assert_eq!(state.selected_one(), Some(3));
624 }
625
626 #[test]
627 fn disabled_and_unknown_items_do_not_change() {
628 for mut state in [State::single(items()), State::multiple(items())] {
629 assert_eq!(state.update(Event::Toggle(1)), None);
630 assert_eq!(state.update(Event::Toggle(9)), None);
631 assert!(state.selected().is_empty());
632 }
633 }
634
635 #[test]
636 fn with_selected_skips_disabled_and_keeps_one_in_single_mode() {
637 let single = State::single(items()).with_selected([1, 3, 0]);
638 assert_eq!(single.selected(), vec![3]);
639 let multiple = State::multiple(items()).with_selected([3, 1, 0]);
640 assert_eq!(multiple.selected(), vec![0, 3]);
641 }
642
643 #[test]
644 fn next_and_previous_skip_disabled_and_wrap() {
645 let mut state = State::single(items()).with_selected([0]);
646 assert_eq!(state.update(Event::Next), Some(vec![2]));
647 assert_eq!(state.update(Event::Next), Some(vec![3]));
648 assert_eq!(state.update(Event::Next), Some(vec![0]));
649 assert_eq!(state.update(Event::Previous), Some(vec![3]));
650 assert_eq!(state.update(Event::Previous), Some(vec![2]));
651 assert_eq!(state.update(Event::Previous), Some(vec![0]));
652 }
653
654 #[test]
655 fn next_from_nothing_picks_the_first_and_previous_the_last() {
656 let mut forward = State::single(items());
657 assert_eq!(forward.update(Event::Next), Some(vec![0]));
658 let mut backward = State::single(items());
659 assert_eq!(backward.update(Event::Previous), Some(vec![3]));
660 }
661
662 #[test]
663 fn stepping_does_nothing_in_multiple_mode_or_without_enabled_items() {
664 let mut multiple = State::multiple(items());
665 assert_eq!(multiple.update(Event::Next), None);
666 let mut empty: State<u8> = State::single([]);
667 assert_eq!(empty.update(Event::Next), None);
668 let mut disabled = State::single([item(0, "A").disabled(true)]);
669 assert_eq!(disabled.update(Event::Previous), None);
670 let mut lonely = State::single([item(0, "A")]).with_selected([0]);
671 assert_eq!(lonely.update(Event::Next), None);
672 }
673
674 #[test]
675 fn set_disabled_keeps_the_selection() {
676 let mut state = State::multiple(items()).with_selected([0]);
677 state.set_disabled(0, true);
678 assert!(state.is_selected(0));
679 assert_eq!(state.update(Event::Toggle(0)), None);
680 state.set_disabled(1, false);
681 assert_eq!(state.update(Event::Toggle(1)), Some(vec![0, 1]));
682 }
683
684 fn press(keymap: &Keymap<Action>, chord: &str) -> Option<Action> {
685 let chord: Chord = chord.parse().unwrap();
686 keymap.resolve(chord.key(), chord.modifiers())
687 }
688
689 #[test]
690 fn default_keymap_covers_every_action() {
691 let keymap = default_keymap();
692 assert_eq!(press(&keymap, "ArrowRight"), Some(Action::Next));
693 assert_eq!(press(&keymap, "ArrowLeft"), Some(Action::Previous));
694 assert_eq!(press(&keymap, "Tab"), None);
695 for action in <Action as keys::Action>::ALL {
696 assert!(!keymap.chords(action).is_empty(), "{action:?}");
697 }
698 assert_eq!(Keymap::<Action>::defaults(), keymap);
699 }
700
701 #[test]
702 fn keymap_overrides_and_unbinding() {
703 let keymap = default_keymap()
704 .unbind(&Chord::named(Named::ArrowRight))
705 .bind("Alt+ArrowDown".parse().unwrap(), Action::Next);
706 assert_eq!(press(&keymap, "ArrowRight"), None);
707 assert_eq!(press(&keymap, "Alt+ArrowDown"), Some(Action::Next));
708 assert_eq!(press(&keymap, "ArrowLeft"), Some(Action::Previous));
709 }
710
711 #[test]
712 fn actions_map_to_events_in_single_mode_only() {
713 let mut single = State::single(items()).with_selected([0]);
714 assert_eq!(Action::Next.event(&single), Some(Event::Next));
715 assert_eq!(Action::Previous.event(&single), Some(Event::Previous));
716 let event = Action::Next.event(&single).unwrap();
717 assert_eq!(single.update(event), Some(vec![2]));
718
719 let multiple = State::multiple(items());
720 assert_eq!(Action::Next.event(&multiple), None);
721 assert_eq!(Action::Previous.event(&multiple), None);
722 }
723
724 #[test]
725 fn positions_follow_the_item_index() {
726 assert_eq!(Position::of(0, 1), Position::Only);
727 assert_eq!(Position::of(0, 3), Position::First);
728 assert_eq!(Position::of(1, 3), Position::Middle);
729 assert_eq!(Position::of(2, 3), Position::Last);
730 }
731
732 #[test]
733 fn outlined_items_round_only_their_outer_corners() {
734 let tokens = Tokens::of(&light());
735 let corners = |position| {
736 item_style(&tokens, Variant::Outline, position, true, Status::Active)
737 .border
738 .radius
739 };
740 let outer = radius::MD - 1.0;
741 assert_eq!(corners(Position::Only), Radius::from(outer));
742 assert_eq!(corners(Position::Middle), Radius::default());
743 let first = corners(Position::First);
744 assert_eq!((first.top_left, first.top_right), (outer, 0.0));
745 let last = corners(Position::Last);
746 assert_eq!((last.top_left, last.bottom_right), (0.0, outer));
747 }
748
749 #[test]
750 fn items_never_draw_their_own_border() {
751 for theme in [light(), dark()] {
752 let tokens = Tokens::of(&theme);
753 for variant in Variant::ALL {
754 for pressed in [false, true] {
755 let style =
756 item_style(&tokens, variant, Position::Middle, pressed, Status::Hovered);
757 assert_eq!(style.border.width, 0.0);
758 }
759 }
760 }
761 }
762
763 #[test]
764 fn pressed_items_match_a_pressed_toggle() {
765 for theme in [light(), dark()] {
766 let tokens = Tokens::of(&theme);
767 let item = item_style(
768 &tokens,
769 Variant::Default,
770 Position::Only,
771 true,
772 Status::Active,
773 );
774 let toggle = toggle::style(&tokens, toggle::Variant::Default, true, Status::Active);
775 assert_eq!(item, toggle);
776 assert_eq!(item.background, Some(Background::Color(tokens.accent)));
777 }
778 }
779
780 #[test]
781 fn a_hovered_item_never_looks_selected() {
782 for theme in [light(), dark()] {
783 let tokens = Tokens::of(&theme);
784 for variant in Variant::ALL {
785 let hovered =
786 item_style(&tokens, variant, Position::Middle, false, Status::Hovered);
787 let selected = item_style(&tokens, variant, Position::Middle, true, Status::Active);
788 assert_ne!(hovered.background, selected.background, "{variant:?}");
789 assert_ne!(hovered.text_color, selected.text_color, "{variant:?}");
790 }
791 }
792 }
793
794 #[test]
795 fn only_the_outline_group_has_a_border_and_it_fades_when_disabled() {
796 let tokens = Tokens::of(&dark());
797 assert_eq!(
798 group_style(&tokens, Variant::Default, true).border.width,
799 0.0
800 );
801 let enabled = group_style(&tokens, Variant::Outline, true);
802 let disabled = group_style(&tokens, Variant::Outline, false);
803 assert_eq!(enabled.border.width, 1.0);
804 assert_eq!(enabled.border.color, tokens.border);
805 assert!(disabled.border.color.a < enabled.border.color.a);
806 }
807
808 fn lightness(background: Option<Background>) -> f32 {
809 match background {
810 Some(Background::Color(colour)) => (colour.r + colour.g + colour.b) / 3.0,
811 _ => 0.0,
812 }
813 }
814
815 #[test]
816 fn an_outlined_selected_item_stands_out_more_than_the_plain_accent() {
817 for theme in [light(), dark()] {
818 let tokens = Tokens::of(&theme);
819 let selected = item_style(
820 &tokens,
821 Variant::Outline,
822 Position::First,
823 true,
824 Status::Active,
825 );
826 let plain = toggle::style(&tokens, toggle::Variant::Default, true, Status::Active);
827 let base = (tokens.background.r + tokens.background.g + tokens.background.b) / 3.0;
828 assert!(
829 (lightness(selected.background) - base).abs()
830 > (lightness(plain.background) - base).abs(),
831 "{theme:?}"
832 );
833 let hovered = item_style(
834 &tokens,
835 Variant::Outline,
836 Position::First,
837 true,
838 Status::Hovered,
839 );
840 assert_ne!(hovered.background, selected.background);
841 }
842 }
843
844 #[test]
845 fn dividers_use_the_border_and_fade_when_disabled() {
846 for theme in [light(), dark()] {
847 let tokens = Tokens::of(&theme);
848 let enabled = divider_style(&tokens, true);
849 assert_eq!(enabled.background, Some(Background::Color(tokens.border)));
850 let disabled = divider_style(&tokens, false);
851 assert_eq!(
852 disabled.background,
853 Some(Background::Color(fade(tokens.border, 0.5)))
854 );
855 }
856 }
857}