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#[derive(Clone, PartialEq)]
66pub struct Card {
67 pub(crate) theme_colors: Option<CardColorsThemePartial>,
68 pub(crate) theme_layout: Option<CardLayoutThemePartial>,
69 layout: LayoutData,
70 accessibility: AccessibilityData,
71 elements: Vec<Element>,
72 on_press: Option<EventHandler<Event<PressEventData>>>,
73 key: DiffKey,
74 style_variant: CardStyleVariant,
75 layout_variant: CardLayoutVariant,
76 hoverable: bool,
77 cursor_icon: Option<CursorIcon>,
78}
79
80impl Default for Card {
81 fn default() -> Self {
82 Self::new()
83 }
84}
85
86impl ChildrenExt for Card {
87 fn children_mut(&mut self) -> &mut Vec<Element> {
88 &mut self.elements
89 }
90}
91
92impl KeyExt for Card {
93 fn key_mut(&mut self) -> &mut DiffKey {
94 &mut self.key
95 }
96}
97
98impl LayoutExt for Card {
99 fn layout_mut(&mut self) -> &mut LayoutData {
100 &mut self.layout
101 }
102}
103
104impl ContainerSizeExt for Card {}
105
106impl ContainerPositionExt for Card {}
107
108impl ContainerConstraintsExt for Card {}
109
110impl ContainerWithContentExt for Card {}
111
112impl AccessibilityExt for Card {
113 fn accessibility_data_mut(&mut self) -> &mut AccessibilityData {
114 &mut self.accessibility
115 }
116}
117
118impl CornerRadiusExt for Card {
119 fn with_corner_radius(self, corner_radius: f32) -> Self {
120 self.corner_radius(corner_radius)
121 }
122}
123
124impl Card {
125 pub fn new() -> Self {
126 Self {
127 theme_colors: None,
128 theme_layout: None,
129 layout: LayoutData::default(),
130 accessibility: AccessibilityData::default(),
131 style_variant: CardStyleVariant::Outline,
132 layout_variant: CardLayoutVariant::Normal,
133 on_press: None,
134 elements: Vec::default(),
135 hoverable: false,
136 cursor_icon: None,
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 pub fn cursor(mut self, cursor_icon: impl Into<Option<CursorIcon>>) -> Self {
204 self.cursor_icon = cursor_icon.into();
205 self
206 }
207}
208
209impl Component for Card {
210 fn render(&self) -> impl IntoElement {
211 let mut hovering = use_state(|| false);
212 let a11y_id = use_a11y();
213 let focus = use_focus(a11y_id);
214
215 let is_hoverable = self.hoverable;
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() == Focus::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(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 a11y_id.request_focus();
278 on_press.call(e);
279 })
280 })
281 .maybe(is_hoverable, |rect| {
282 rect.cursor(self.cursor_icon)
283 .on_pointer_enter(move |_| {
284 hovering.set(true);
285 })
286 .on_pointer_leave(move |_| {
287 hovering.set_if_modified(false);
288 })
289 })
290 .children(self.elements.clone())
291 }
292
293 fn render_key(&self) -> DiffKey {
294 self.key.clone().or(self.default_key())
295 }
296}