Skip to main content

iced_shadcn/
toggle_group.rs

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}