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]
17 Idle,
18 Hovering,
20}
21
22#[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 pub fn get_theme(&self) -> Option<&ChipThemePartial> {
73 self.theme.as_ref()
74 }
75
76 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}