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#[derive(PartialEq)]
25pub struct Chip {
26    pub(crate) theme: Option<ChipThemePartial>,
27    children: Vec<Element>,
28    on_press: Option<EventHandler<Event<PressEventData>>>,
29    selected: bool,
30    enabled: bool,
31    key: DiffKey,
32}
33
34impl Default for Chip {
35    fn default() -> Self {
36        Self {
37            theme: None,
38            children: Vec::new(),
39            on_press: None,
40            selected: false,
41            enabled: true,
42            key: DiffKey::None,
43        }
44    }
45}
46
47impl Chip {
48    pub fn new() -> Self {
49        Self::default()
50    }
51
52    pub fn theme(mut self, theme: ChipThemePartial) -> Self {
53        self.theme = Some(theme);
54        self
55    }
56
57    pub fn selected(mut self, selected: impl Into<bool>) -> Self {
58        self.selected = selected.into();
59        self
60    }
61
62    pub fn enabled(mut self, enabled: impl Into<bool>) -> Self {
63        self.enabled = enabled.into();
64        self
65    }
66
67    pub fn on_press(mut self, handler: impl Into<EventHandler<Event<PressEventData>>>) -> Self {
68        self.on_press = Some(handler.into());
69        self
70    }
71}
72
73impl ChildrenExt for Chip {
74    fn get_children(&mut self) -> &mut Vec<Element> {
75        &mut self.children
76    }
77}
78
79impl KeyExt for Chip {
80    fn write_key(&mut self) -> &mut DiffKey {
81        &mut self.key
82    }
83}
84
85impl Render for Chip {
86    fn render(&self) -> impl IntoElement {
87        let theme = get_theme!(&self.theme, chip);
88        let mut status = use_state(|| ChipStatus::Idle);
89        let focus = use_focus();
90        let focus_status = use_focus_status(focus);
91
92        let ChipTheme {
93            background,
94            hover_background,
95            selected_background,
96            border_fill,
97            selected_border_fill,
98            hover_border_fill,
99            focus_border_fill,
100            padding,
101            margin,
102            corner_radius,
103            width,
104            height,
105            color,
106            hover_color,
107            selected_color,
108            hover_icon_fill,
109            selected_icon_fill,
110        } = theme;
111
112        let enabled = use_reactive(&self.enabled);
113        use_drop(move || {
114            if status() == ChipStatus::Hovering && enabled() {
115                Cursor::set(CursorIcon::default());
116            }
117        });
118
119        let on_press = self.on_press.clone();
120        let on_press = move |e: Event<PressEventData>| {
121            focus.request_focus();
122            if let Some(on_press) = &on_press {
123                on_press.call(e);
124            }
125        };
126
127        let on_pointer_enter = move |_| {
128            status.set(ChipStatus::Hovering);
129            if enabled() {
130                Cursor::set(CursorIcon::Pointer);
131            } else {
132                Cursor::set(CursorIcon::NotAllowed);
133            }
134        };
135
136        let on_pointer_leave = move |_| {
137            if status() == ChipStatus::Hovering {
138                Cursor::set(CursorIcon::default());
139                status.set(ChipStatus::Idle);
140            }
141        };
142
143        let background = match status() {
144            ChipStatus::Hovering if enabled() => hover_background,
145            _ if self.selected => selected_background,
146            _ => background,
147        };
148        let color = match status() {
149            ChipStatus::Hovering if enabled() => hover_color,
150            _ if self.selected => selected_color,
151            _ => color,
152        };
153        let border_fill = match status() {
154            ChipStatus::Hovering if enabled() => hover_border_fill,
155            _ if self.selected => selected_border_fill,
156            _ => border_fill,
157        };
158        let icon_fill = match status() {
159            ChipStatus::Hovering if self.selected && enabled() => Some(hover_icon_fill),
160            _ if self.selected => Some(selected_icon_fill),
161            _ => None,
162        };
163        let border = if self.enabled && focus_status() == FocusStatus::Keyboard {
164            Border::new()
165                .fill(focus_border_fill)
166                .width(2.)
167                .alignment(BorderAlignment::Inner)
168        } else {
169            Border::new()
170                .fill(border_fill.mul_if(!self.enabled, 0.9))
171                .width(1.)
172                .alignment(BorderAlignment::Inner)
173        };
174
175        rect()
176            .a11y_id(focus.a11y_id())
177            .a11y_focusable(self.enabled)
178            .a11y_role(AccessibilityRole::Button)
179            .maybe(self.enabled, |rect| rect.on_press(on_press))
180            .on_pointer_enter(on_pointer_enter)
181            .on_pointer_leave(on_pointer_leave)
182            .width(width)
183            .height(height)
184            .padding(padding)
185            .margin(margin)
186            .overflow(Overflow::Clip)
187            .border(border)
188            .corner_radius(corner_radius)
189            .color(color.mul_if(!self.enabled, 0.9))
190            .background(background.mul_if(!self.enabled, 0.9))
191            .center()
192            .horizontal()
193            .spacing(4.)
194            .maybe_child(icon_fill.map(|icon_fill| {
195                TickIcon::new()
196                    .fill(icon_fill)
197                    .width(Size::px(12.))
198                    .height(Size::px(12.))
199            }))
200            .children(self.children.clone())
201    }
202
203    fn render_key(&self) -> DiffKey {
204        self.key.clone().or(self.default_key())
205    }
206}