freya_components/
segmented_button.rs

1use freya_core::prelude::*;
2use torin::size::Size;
3
4use crate::{
5    get_theme,
6    icons::tick::TickIcon,
7    theming::component_themes::{
8        ButtonSegmentTheme,
9        ButtonSegmentThemePartial,
10        SegmentedButtonTheme,
11        SegmentedButtonThemePartial,
12    },
13};
14
15/// Identifies the current status of the [`ButtonSegment`]s.
16#[derive(Debug, Default, PartialEq, Clone, Copy)]
17pub enum ButtonSegmentStatus {
18    /// Default state.
19    #[default]
20    Idle,
21    /// Pointer is hovering the button.
22    Hovering,
23}
24
25/// A segment button to be used within a [`SegmentedButton`].
26///
27/// # Example
28///
29/// ```rust
30/// # use freya::prelude::*;
31/// # use std::collections::HashSet;
32/// fn app() -> impl IntoElement {
33///     let mut selected = use_state(|| HashSet::from([1]));
34///     SegmentedButton::new().children_iter((0..2).map(|i| {
35///         ButtonSegment::new()
36///             .key(i)
37///             .selected(selected.read().contains(&i))
38///             .on_press(move |_| {
39///                 if selected.read().contains(&i) {
40///                     selected.write().remove(&i);
41///                 } else {
42///                     selected.write().insert(i);
43///                 }
44///             })
45///             .child(format!("Option {i}"))
46///             .into()
47///     }))
48/// }
49/// ```
50#[derive(Clone, PartialEq)]
51pub struct ButtonSegment {
52    pub(crate) theme: Option<ButtonSegmentThemePartial>,
53    children: Vec<Element>,
54    on_press: Option<EventHandler<Event<PressEventData>>>,
55    selected: bool,
56    enabled: bool,
57    key: DiffKey,
58}
59
60impl Default for ButtonSegment {
61    fn default() -> Self {
62        Self::new()
63    }
64}
65
66impl ButtonSegment {
67    pub fn new() -> Self {
68        Self {
69            theme: None,
70            children: Vec::new(),
71            on_press: None,
72            selected: false,
73            enabled: true,
74            key: DiffKey::None,
75        }
76    }
77
78    pub fn selected(mut self, selected: impl Into<bool>) -> Self {
79        self.selected = selected.into();
80        self
81    }
82
83    pub fn enabled(mut self, enabled: impl Into<bool>) -> Self {
84        self.enabled = enabled.into();
85        self
86    }
87
88    pub fn on_press(mut self, on_press: impl Into<EventHandler<Event<PressEventData>>>) -> Self {
89        self.on_press = Some(on_press.into());
90        self
91    }
92}
93
94impl ChildrenExt for ButtonSegment {
95    fn get_children(&mut self) -> &mut Vec<Element> {
96        &mut self.children
97    }
98}
99
100impl KeyExt for ButtonSegment {
101    fn write_key(&mut self) -> &mut DiffKey {
102        &mut self.key
103    }
104}
105
106impl Render for ButtonSegment {
107    fn render(&self) -> impl IntoElement {
108        let theme = get_theme!(&self.theme, button_segment);
109        let mut status = use_state(|| ButtonSegmentStatus::Idle);
110        let focus = use_focus();
111        let focus_status = use_focus_status(focus);
112
113        let ButtonSegmentTheme {
114            background,
115            hover_background,
116            disabled_background,
117            selected_background,
118            focus_background,
119            padding,
120            selected_padding,
121            width,
122            height,
123            color,
124            selected_icon_fill,
125        } = theme;
126
127        let enabled = use_reactive(&self.enabled);
128        use_drop(move || {
129            if status() == ButtonSegmentStatus::Hovering && enabled() {
130                Cursor::set(CursorIcon::default());
131            }
132        });
133
134        let on_press = self.on_press.clone();
135        let on_press = move |e: Event<PressEventData>| {
136            focus.request_focus();
137            if let Some(on_press) = &on_press {
138                on_press.call(e);
139            }
140        };
141
142        let on_pointer_enter = move |_| {
143            status.set(ButtonSegmentStatus::Hovering);
144            if enabled() {
145                Cursor::set(CursorIcon::Pointer);
146            } else {
147                Cursor::set(CursorIcon::NotAllowed);
148            }
149        };
150
151        let on_pointer_leave = move |_| {
152            if status() == ButtonSegmentStatus::Hovering {
153                Cursor::set(CursorIcon::default());
154                status.set(ButtonSegmentStatus::Idle);
155            }
156        };
157
158        let background = match status() {
159            _ if !self.enabled => disabled_background,
160            _ if self.selected => selected_background,
161            ButtonSegmentStatus::Hovering => hover_background,
162            ButtonSegmentStatus::Idle => background,
163        };
164
165        let padding = if self.selected {
166            selected_padding
167        } else {
168            padding
169        };
170        let background = if *focus_status.read() == FocusStatus::Keyboard {
171            focus_background
172        } else {
173            background
174        };
175
176        rect()
177            .a11y_id(focus.a11y_id())
178            .a11y_focusable(self.enabled)
179            .a11y_role(AccessibilityRole::Button)
180            .maybe(self.enabled, |rect| rect.on_press(on_press))
181            .on_pointer_enter(on_pointer_enter)
182            .on_pointer_leave(on_pointer_leave)
183            .horizontal()
184            .width(width)
185            .height(height)
186            .padding(padding)
187            .overflow(Overflow::Clip)
188            .color(color.mul_if(!self.enabled, 0.9))
189            .background(background.mul_if(!self.enabled, 0.9))
190            .center()
191            .spacing(4.)
192            .maybe_child(self.selected.then(|| {
193                TickIcon::new()
194                    .fill(selected_icon_fill)
195                    .width(Size::px(12.))
196                    .height(Size::px(12.))
197            }))
198            .children(self.children.clone())
199    }
200
201    fn render_key(&self) -> DiffKey {
202        self.key.clone().or(self.default_key())
203    }
204}
205
206/// A container for grouping [`ButtonSegment`]s together.
207///
208/// # Example
209///
210/// ```rust
211/// # use freya::prelude::*;
212/// # use std::collections::HashSet;
213/// fn app() -> impl IntoElement {
214///     let mut selected = use_state(|| HashSet::from([1]));
215///     SegmentedButton::new().children_iter((0..2).map(|i| {
216///         ButtonSegment::new()
217///             .key(i)
218///             .selected(selected.read().contains(&i))
219///             .on_press(move |_| {
220///                 if selected.read().contains(&i) {
221///                     selected.write().remove(&i);
222///                 } else {
223///                     selected.write().insert(i);
224///                 }
225///             })
226///             .child(format!("Option {i}"))
227///             .into()
228///     }))
229/// }
230/// # use freya_testing::prelude::*;
231/// # launch_doc(|| {
232/// #   rect().center().expanded().child(app())
233/// # }, "./images/gallery_segmented_button.png").render();
234/// ```
235///
236/// # Preview
237/// ![SegmentedButton Preview][segmented_button]
238#[cfg_attr(feature = "docs",
239    doc = embed_doc_image::embed_image!("segmented_button", "images/gallery_segmented_button.png")
240)]
241#[derive(Clone, PartialEq)]
242pub struct SegmentedButton {
243    pub(crate) theme: Option<SegmentedButtonThemePartial>,
244    children: Vec<Element>,
245    key: DiffKey,
246}
247
248impl Default for SegmentedButton {
249    fn default() -> Self {
250        Self::new()
251    }
252}
253
254impl SegmentedButton {
255    pub fn new() -> Self {
256        Self {
257            theme: None,
258            children: Vec::new(),
259            key: DiffKey::None,
260        }
261    }
262
263    pub fn theme(mut self, theme: SegmentedButtonThemePartial) -> Self {
264        self.theme = Some(theme);
265        self
266    }
267}
268
269impl ChildrenExt for SegmentedButton {
270    fn get_children(&mut self) -> &mut Vec<Element> {
271        &mut self.children
272    }
273}
274
275impl KeyExt for SegmentedButton {
276    fn write_key(&mut self) -> &mut DiffKey {
277        &mut self.key
278    }
279}
280
281impl Render for SegmentedButton {
282    fn render(&self) -> impl IntoElement {
283        let theme = get_theme!(&self.theme, segmented_button);
284
285        let SegmentedButtonTheme {
286            background,
287            border_fill,
288            corner_radius,
289        } = theme;
290
291        rect()
292            .overflow(Overflow::Clip)
293            .background(background)
294            .border(
295                Border::new()
296                    .fill(border_fill)
297                    .width(1.)
298                    .alignment(BorderAlignment::Outer),
299            )
300            .corner_radius(corner_radius)
301            .horizontal()
302            .children(self.children.clone())
303    }
304
305    fn render_key(&self) -> DiffKey {
306        self.key.clone().or(self.default_key())
307    }
308}