1use std::cell::Cell;
2
3use iced::widget::{row, text};
4use iced::{Alignment, Element};
5
6use crate::button::{ButtonProps, ButtonRadius, ButtonVariant, button_content};
7use crate::theme::Theme;
8
9use crate::tokens::ControlSize;
10
11#[derive(Clone, Copy, Debug, PartialEq, Eq, Default)]
12pub enum ToggleVariant {
13 #[default]
14 Default,
15 Outline,
16}
17
18#[derive(Clone, Copy, Debug, PartialEq)]
19pub struct ToggleGroupProps {
20 pub variant: ToggleVariant,
21 pub size: ControlSize,
22}
23
24impl Default for ToggleGroupProps {
25 fn default() -> Self {
26 Self {
27 variant: ToggleVariant::Default,
28 size: ControlSize::Md,
29 }
30 }
31}
32
33pub struct ToggleGroupContext {
34 pub variant: ToggleVariant,
35 pub size: ControlSize,
36 item_count: Cell<usize>,
37}
38
39pub fn toggle_group<'a, Message: Clone + 'a>(
40 props: ToggleGroupProps,
41 content: impl FnOnce(&ToggleGroupContext) -> Vec<Element<'a, Message>>,
42) -> Element<'a, Message> {
43 let context = ToggleGroupContext {
44 variant: props.variant,
45 size: props.size,
46 item_count: Cell::new(0),
47 };
48
49 let items = content(&context);
50 row(items).spacing(0).align_y(Alignment::Center).into()
51}
52
53pub fn toggle_group_item<'a, Message: Clone + 'a, F>(
54 theme: &Theme,
55 context: &ToggleGroupContext,
56 on: bool,
57 label: impl Into<String>,
58 on_toggle: Option<F>,
59) -> Element<'a, Message>
60where
61 F: Fn(bool) -> Message + 'a,
62{
63 toggle_group_item_with_position(
64 theme,
65 context,
66 on,
67 label,
68 on_toggle,
69 ToggleGroupItemPosition::Middle,
70 )
71}
72
73pub fn toggle_group_item_last<'a, Message: Clone + 'a, F>(
74 theme: &Theme,
75 context: &ToggleGroupContext,
76 on: bool,
77 label: impl Into<String>,
78 on_toggle: Option<F>,
79) -> Element<'a, Message>
80where
81 F: Fn(bool) -> Message + 'a,
82{
83 toggle_group_item_with_position(
84 theme,
85 context,
86 on,
87 label,
88 on_toggle,
89 ToggleGroupItemPosition::Last,
90 )
91}
92
93#[derive(Clone, Copy, Debug, PartialEq, Eq)]
94enum ToggleGroupItemPosition {
95 Middle,
96 Last,
97}
98
99fn toggle_group_item_with_position<'a, Message: Clone + 'a, F>(
100 theme: &Theme,
101 context: &ToggleGroupContext,
102 on: bool,
103 label: impl Into<String>,
104 on_toggle: Option<F>,
105 position: ToggleGroupItemPosition,
106) -> Element<'a, Message>
107where
108 F: Fn(bool) -> Message + 'a,
109{
110 let index = context.item_count.get();
111 context.item_count.set(index + 1);
112
113 let radius = if index == 0 || matches!(position, ToggleGroupItemPosition::Last) {
114 context.size.radius()
115 } else {
116 ButtonRadius::None
117 };
118
119 let variant = match (context.variant, on) {
120 (ToggleVariant::Default, true) => ButtonVariant::Soft,
121 (ToggleVariant::Default, false) => ButtonVariant::Ghost,
122 (ToggleVariant::Outline, true) => ButtonVariant::Soft,
123 (ToggleVariant::Outline, false) => ButtonVariant::Outline,
124 };
125
126 let on_press = on_toggle.map(|f| f(!on));
127
128 button_content(
129 text(label.into()),
130 on_press,
131 ButtonProps::new()
132 .variant(variant)
133 .size(context.size.button_size())
134 .radius(radius),
135 theme,
136 )
137 .into()
138}