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