Skip to main content

freya_components/
chip.rs

1use freya_core::prelude::*;
2use torin::size::Size;
3
4use crate::{
5    get_theme,
6    icons::tick::TickIcon,
7    theming::component_themes::{
8        ChipTheme,
9        ChipThemePartial,
10    },
11};
12
13#[derive(Debug, Default, PartialEq, Clone, Copy)]
14pub enum ChipStatus {
15    /// Default state.
16    #[default]
17    Idle,
18    /// Mouse is hovering the chip.
19    Hovering,
20}
21
22// TODO: Add layout and style variants
23// TODO: Ability to hide/customize icon
24///
25/// Chip component.
26///
27/// ```rust
28/// # use freya::prelude::*;
29/// fn app() -> impl IntoElement {
30///     Chip::new().child("Chip")
31/// }
32/// # use freya_testing::prelude::*;
33/// # launch_doc(|| {
34/// #   rect().center().expanded().child(app())
35/// # }, "./images/gallery_chip.png").render();
36/// ```
37///
38/// # Preview
39/// ![Chip Preview][chip]
40#[cfg_attr(feature = "docs",
41    doc = embed_doc_image::embed_image!("chip", "images/gallery_chip.png"),
42)]
43#[derive(Clone, PartialEq)]
44pub struct Chip {
45    pub(crate) theme: Option<ChipThemePartial>,
46    children: Vec<Element>,
47    on_press: Option<EventHandler<Event<PressEventData>>>,
48    selected: bool,
49    enabled: bool,
50    key: DiffKey,
51}
52
53impl Default for Chip {
54    fn default() -> Self {
55        Self {
56            theme: None,
57            children: Vec::new(),
58            on_press: None,
59            selected: false,
60            enabled: true,
61            key: DiffKey::None,
62        }
63    }
64}
65
66impl Chip {
67    pub fn new() -> Self {
68        Self::default()
69    }
70
71    /// Get the theme override for this component.
72    pub fn get_theme(&self) -> Option<&ChipThemePartial> {
73        self.theme.as_ref()
74    }
75
76    /// Set a theme override for this component.
77    pub fn theme(mut self, theme: ChipThemePartial) -> Self {
78        self.theme = Some(theme);
79        self
80    }
81
82    pub fn selected(mut self, selected: impl Into<bool>) -> Self {
83        self.selected = selected.into();
84        self
85    }
86
87    pub fn enabled(mut self, enabled: impl Into<bool>) -> Self {
88        self.enabled = enabled.into();
89        self
90    }
91
92    pub fn on_press(mut self, handler: impl Into<EventHandler<Event<PressEventData>>>) -> Self {
93        self.on_press = Some(handler.into());
94        self
95    }
96}
97
98impl ChildrenExt for Chip {
99    fn get_children(&mut self) -> &mut Vec<Element> {
100        &mut self.children
101    }
102}
103
104impl KeyExt for Chip {
105    fn write_key(&mut self) -> &mut DiffKey {
106        &mut self.key
107    }
108}
109
110impl Component for Chip {
111    fn render(&self) -> impl IntoElement {
112        let theme = get_theme!(&self.theme, chip);
113        let mut status = use_state(|| ChipStatus::Idle);
114        let focus = use_focus();
115        let focus_status = use_focus_status(focus);
116
117        let ChipTheme {
118            background,
119            hover_background,
120            selected_background,
121            border_fill,
122            selected_border_fill,
123            hover_border_fill,
124            focus_border_fill,
125            padding,
126            margin,
127            corner_radius,
128            width,
129            height,
130            color,
131            hover_color,
132            selected_color,
133            hover_icon_fill,
134            selected_icon_fill,
135        } = theme;
136
137        let enabled = use_reactive(&self.enabled);
138        use_drop(move || {
139            if status() == ChipStatus::Hovering && enabled() {
140                Cursor::set(CursorIcon::default());
141            }
142        });
143
144        let on_press = self.on_press.clone();
145        let on_press = move |e: Event<PressEventData>| {
146            focus.request_focus();
147            if let Some(on_press) = &on_press {
148                on_press.call(e);
149            }
150        };
151
152        let on_pointer_enter = move |_| {
153            status.set(ChipStatus::Hovering);
154            if enabled() {
155                Cursor::set(CursorIcon::Pointer);
156            } else {
157                Cursor::set(CursorIcon::NotAllowed);
158            }
159        };
160
161        let on_pointer_leave = move |_| {
162            if status() == ChipStatus::Hovering {
163                Cursor::set(CursorIcon::default());
164                status.set(ChipStatus::Idle);
165            }
166        };
167
168        let background = match status() {
169            ChipStatus::Hovering if enabled() => hover_background,
170            _ if self.selected => selected_background,
171            _ => background,
172        };
173        let color = match status() {
174            ChipStatus::Hovering if enabled() => hover_color,
175            _ if self.selected => selected_color,
176            _ => color,
177        };
178        let border_fill = match status() {
179            ChipStatus::Hovering if enabled() => hover_border_fill,
180            _ if self.selected => selected_border_fill,
181            _ => border_fill,
182        };
183        let icon_fill = match status() {
184            ChipStatus::Hovering if self.selected && enabled() => Some(hover_icon_fill),
185            _ if self.selected => Some(selected_icon_fill),
186            _ => None,
187        };
188        let border = if self.enabled && focus_status() == FocusStatus::Keyboard {
189            Border::new()
190                .fill(focus_border_fill)
191                .width(2.)
192                .alignment(BorderAlignment::Inner)
193        } else {
194            Border::new()
195                .fill(border_fill.mul_if(!self.enabled, 0.9))
196                .width(1.)
197                .alignment(BorderAlignment::Inner)
198        };
199
200        rect()
201            .a11y_id(focus.a11y_id())
202            .a11y_focusable(self.enabled)
203            .a11y_role(AccessibilityRole::Button)
204            .maybe(self.enabled, |rect| rect.on_press(on_press))
205            .on_pointer_enter(on_pointer_enter)
206            .on_pointer_leave(on_pointer_leave)
207            .width(width)
208            .height(height)
209            .padding(padding)
210            .margin(margin)
211            .overflow(Overflow::Clip)
212            .border(border)
213            .corner_radius(corner_radius)
214            .color(color.mul_if(!self.enabled, 0.9))
215            .background(background.mul_if(!self.enabled, 0.9))
216            .center()
217            .horizontal()
218            .spacing(4.)
219            .maybe_child(icon_fill.map(|icon_fill| {
220                TickIcon::new()
221                    .fill(icon_fill)
222                    .width(Size::px(12.))
223                    .height(Size::px(12.))
224            }))
225            .children(self.children.clone())
226    }
227
228    fn render_key(&self) -> DiffKey {
229        self.key.clone().or(self.default_key())
230    }
231}