Skip to main content

freya_components/
radio_item.rs

1use freya_animation::prelude::*;
2use freya_core::prelude::*;
3use torin::prelude::*;
4
5use crate::{
6    get_theme,
7    theming::component_themes::{
8        RadioItemTheme,
9        RadioItemThemePartial,
10    },
11};
12
13/// Radio component.
14///
15/// # Example
16///
17/// ```rust
18/// # use std::collections::HashSet;
19/// # use freya::prelude::*;
20/// fn app() -> impl IntoElement {
21///     let mut checked = use_state(|| false);
22///
23///     rect()
24///         .child(
25///             Tile::new()
26///                 .on_select(move |_| checked.toggle())
27///                 .child(RadioItem::new().selected(checked()))
28///                 .leading("Click to check"),
29///         )
30///         .child(
31///             Tile::new()
32///                 .on_select(move |_| checked.toggle())
33///                 .child(RadioItem::new().selected(!checked()))
34///                 .child("Click to check"),
35///         )
36/// }
37///
38/// # use freya_testing::prelude::*;
39/// # launch_doc(|| {
40/// #   rect().spacing(8.).center().expanded().child(app())
41/// # }, "./images/gallery_radio.png").render();
42/// ```
43///
44/// # Preview
45/// ![Radio Preview][radio]
46#[cfg_attr(feature = "docs",
47    doc = embed_doc_image::embed_image!("radio", "images/gallery_radio.png")
48)]
49#[derive(Clone, PartialEq)]
50pub struct RadioItem {
51    pub(crate) theme: Option<RadioItemThemePartial>,
52    key: DiffKey,
53    selected: bool,
54    size: f32,
55}
56
57impl KeyExt for RadioItem {
58    fn write_key(&mut self) -> &mut DiffKey {
59        &mut self.key
60    }
61}
62
63impl Default for RadioItem {
64    fn default() -> Self {
65        Self::new()
66    }
67}
68
69impl RadioItem {
70    pub fn new() -> Self {
71        Self {
72            selected: false,
73            theme: None,
74            key: DiffKey::None,
75            size: 20.,
76        }
77    }
78
79    pub fn selected(mut self, selected: bool) -> Self {
80        self.selected = selected;
81        self
82    }
83
84    pub fn theme(mut self, theme: RadioItemThemePartial) -> Self {
85        self.theme = Some(theme);
86        self
87    }
88
89    pub fn size(mut self, size: impl Into<f32>) -> Self {
90        self.size = size.into();
91        self
92    }
93}
94
95impl Component for RadioItem {
96    fn render(&self) -> impl IntoElement {
97        let focus = use_focus();
98        let focus_status = use_focus_status(focus);
99        let RadioItemTheme {
100            unselected_fill,
101            selected_fill,
102            border_fill,
103        } = get_theme!(&self.theme, radio);
104
105        let animation = use_animation_with_dependencies(&self.selected, move |conf, selected| {
106            conf.on_change(OnChange::Rerun);
107            conf.on_creation(OnCreation::Finish);
108
109            let scale = AnimNum::new(0.7, 1.)
110                .time(250)
111                .ease(Ease::Out)
112                .function(Function::Expo);
113            let opacity = AnimNum::new(0., 1.)
114                .time(250)
115                .ease(Ease::Out)
116                .function(Function::Expo);
117
118            if *selected {
119                (scale, opacity)
120            } else {
121                (scale.into_reversed(), opacity.into_reversed())
122            }
123        });
124
125        let (scale, opacity) = animation.read().value();
126
127        let fill = if self.selected {
128            selected_fill
129        } else {
130            unselected_fill
131        };
132
133        let border = Border::new()
134            .fill(fill)
135            .width(2.)
136            .alignment(BorderAlignment::Inner);
137
138        let focused_border = (focus_status() == FocusStatus::Keyboard).then(|| {
139            Border::new()
140                .fill(border_fill)
141                .width((self.size * 0.15).ceil())
142                .alignment(BorderAlignment::Outer)
143        });
144
145        rect()
146            .a11y_id(focus.a11y_id())
147            .a11y_focusable(Focusable::Enabled)
148            .a11y_role(AccessibilityRole::RadioButton)
149            .width(Size::px(self.size))
150            .height(Size::px(self.size))
151            .border(border)
152            .border(focused_border)
153            .padding(Gaps::new_all(4.0))
154            .main_align(Alignment::center())
155            .cross_align(Alignment::center())
156            .corner_radius(CornerRadius::new_all(99.))
157            .on_key_down(move |e: Event<KeyboardEventData>| {
158                if !Focus::is_pressed(&e) {
159                    e.stop_propagation();
160                }
161            })
162            .maybe_child((self.selected || opacity > 0.).then(|| {
163                rect()
164                    .opacity(opacity)
165                    .scale(scale)
166                    .width(Size::px((self.size * 0.55).floor()))
167                    .height(Size::px((self.size * 0.55).floor()))
168                    .background(fill)
169                    .corner_radius(CornerRadius::new_all(99.))
170            }))
171    }
172
173    fn render_key(&self) -> DiffKey {
174        self.key.clone().or(self.default_key())
175    }
176}
177
178pub fn radio() -> RadioItem {
179    RadioItem::new()
180}