1use freya_core::prelude::*;
2use torin::gaps::Gaps;
3
4use crate::{
5 define_theme,
6 get_theme,
7};
8
9define_theme! {
10 for = Card;
11 theme_field = theme_layout;
12
13 %[component]
14 pub CardLayout {
15 %[fields]
16 corner_radius: CornerRadius,
17 padding: Gaps,
18 }
19}
20
21define_theme! {
22 for = Card;
23 theme_field = theme_colors;
24
25 %[component]
26 pub CardColors {
27 %[fields]
28 background: Color,
29 hover_background: Color,
30 border_fill: Color,
31 color: Color,
32 shadow: Color,
33 }
34}
35
36#[derive(Clone, PartialEq)]
38pub enum CardStyleVariant {
39 Filled,
40 Outline,
41}
42
43#[derive(Clone, PartialEq)]
45pub enum CardLayoutVariant {
46 Normal,
47 Compact,
48}
49
50#[cfg_attr(feature = "docs",
71 doc = embed_doc_image::embed_image!("card", "images/gallery_card.png"),
72)]
73#[derive(Clone, PartialEq)]
74pub struct Card {
75 pub(crate) theme_colors: Option<CardColorsThemePartial>,
76 pub(crate) theme_layout: Option<CardLayoutThemePartial>,
77 layout: LayoutData,
78 accessibility: AccessibilityData,
79 elements: Vec<Element>,
80 on_press: Option<EventHandler<Event<PressEventData>>>,
81 key: DiffKey,
82 style_variant: CardStyleVariant,
83 layout_variant: CardLayoutVariant,
84 hoverable: bool,
85}
86
87impl Default for Card {
88 fn default() -> Self {
89 Self::new()
90 }
91}
92
93impl ChildrenExt for Card {
94 fn get_children(&mut self) -> &mut Vec<Element> {
95 &mut self.elements
96 }
97}
98
99impl KeyExt for Card {
100 fn write_key(&mut self) -> &mut DiffKey {
101 &mut self.key
102 }
103}
104
105impl LayoutExt for Card {
106 fn get_layout(&mut self) -> &mut LayoutData {
107 &mut self.layout
108 }
109}
110
111impl ContainerExt for Card {}
112
113impl AccessibilityExt for Card {
114 fn get_accessibility_data(&mut self) -> &mut AccessibilityData {
115 &mut self.accessibility
116 }
117}
118
119impl CornerRadiusExt for Card {
120 fn with_corner_radius(self, corner_radius: f32) -> Self {
121 self.corner_radius(corner_radius)
122 }
123}
124
125impl Card {
126 pub fn new() -> Self {
127 Self {
128 theme_colors: None,
129 theme_layout: None,
130 layout: LayoutData::default(),
131 accessibility: AccessibilityData::default(),
132 style_variant: CardStyleVariant::Outline,
133 layout_variant: CardLayoutVariant::Normal,
134 on_press: None,
135 elements: Vec::default(),
136 hoverable: false,
137 key: DiffKey::None,
138 }
139 }
140
141 pub fn get_layout_variant(&self) -> &CardLayoutVariant {
143 &self.layout_variant
144 }
145
146 pub fn get_theme_layout(&self) -> Option<&CardLayoutThemePartial> {
148 self.theme_layout.as_ref()
149 }
150
151 pub fn style_variant(mut self, style_variant: impl Into<CardStyleVariant>) -> Self {
153 self.style_variant = style_variant.into();
154 self
155 }
156
157 pub fn layout_variant(mut self, layout_variant: impl Into<CardLayoutVariant>) -> Self {
159 self.layout_variant = layout_variant.into();
160 self
161 }
162
163 pub fn hoverable(mut self, hoverable: impl Into<bool>) -> Self {
165 self.hoverable = hoverable.into();
166 self
167 }
168
169 pub fn on_press(mut self, on_press: impl Into<EventHandler<Event<PressEventData>>>) -> Self {
171 self.on_press = Some(on_press.into());
172 self
173 }
174
175 pub fn theme_colors(mut self, theme: CardColorsThemePartial) -> Self {
177 self.theme_colors = Some(theme);
178 self
179 }
180
181 pub fn theme_layout(mut self, theme: CardLayoutThemePartial) -> Self {
183 self.theme_layout = Some(theme);
184 self
185 }
186
187 pub fn filled(self) -> Self {
189 self.style_variant(CardStyleVariant::Filled)
190 }
191
192 pub fn outline(self) -> Self {
194 self.style_variant(CardStyleVariant::Outline)
195 }
196
197 pub fn compact(self) -> Self {
199 self.layout_variant(CardLayoutVariant::Compact)
200 }
201}
202
203impl Component for Card {
204 fn render(&self) -> impl IntoElement {
205 let mut hovering = use_state(|| false);
206 let focus = use_focus();
207 let focus_status = use_focus_status(focus);
208
209 let is_hoverable = self.hoverable;
210
211 use_drop(move || {
212 if hovering() && is_hoverable {
213 Cursor::set(CursorIcon::default());
214 }
215 });
216
217 let theme_colors = match self.style_variant {
218 CardStyleVariant::Filled => {
219 get_theme!(&self.theme_colors, CardColorsThemePreference, "filled_card")
220 }
221 CardStyleVariant::Outline => get_theme!(
222 &self.theme_colors,
223 CardColorsThemePreference,
224 "outline_card"
225 ),
226 };
227 let theme_layout = match self.layout_variant {
228 CardLayoutVariant::Normal => {
229 get_theme!(&self.theme_layout, CardLayoutThemePreference, "card_layout")
230 }
231 CardLayoutVariant::Compact => get_theme!(
232 &self.theme_layout,
233 CardLayoutThemePreference,
234 "compact_card_layout"
235 ),
236 };
237
238 let border = if focus_status() == FocusStatus::Keyboard {
239 Border::new()
240 .fill(theme_colors.border_fill)
241 .width(2.)
242 .alignment(BorderAlignment::Inner)
243 } else {
244 Border::new()
245 .fill(theme_colors.border_fill)
246 .width(1.)
247 .alignment(BorderAlignment::Inner)
248 };
249
250 let background = if is_hoverable && hovering() {
251 theme_colors.hover_background
252 } else {
253 theme_colors.background
254 };
255
256 let shadow = if is_hoverable && hovering() {
257 Some(Shadow::new().y(4.).blur(8.).color(theme_colors.shadow))
258 } else {
259 None
260 };
261
262 rect()
263 .layout(self.layout.clone())
264 .overflow(Overflow::Clip)
265 .a11y_id(focus.a11y_id())
266 .a11y_focusable(is_hoverable)
267 .a11y_role(AccessibilityRole::GenericContainer)
268 .accessibility(self.accessibility.clone())
269 .background(background)
270 .border(border)
271 .padding(theme_layout.padding)
272 .corner_radius(theme_layout.corner_radius)
273 .color(theme_colors.color)
274 .map(shadow, |rect, shadow| rect.shadow(shadow))
275 .map(self.on_press.clone(), |rect, on_press| {
276 rect.on_press(move |e: Event<PressEventData>| {
277 focus.request_focus();
278 on_press.call(e);
279 })
280 })
281 .maybe(is_hoverable, |rect| {
282 rect.on_pointer_enter(move |_| {
283 hovering.set(true);
284 Cursor::set(CursorIcon::Pointer);
285 })
286 .on_pointer_leave(move |_| {
287 if hovering() {
288 Cursor::set(CursorIcon::default());
289 hovering.set(false);
290 }
291 })
292 })
293 .children(self.elements.clone())
294 }
295
296 fn render_key(&self) -> DiffKey {
297 self.key.clone().or(self.default_key())
298 }
299}