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#[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}