1use freya_core::prelude::*;
2
3use crate::{
4 get_theme,
5 theming::component_themes::{
6 CardColorsThemePartial,
7 CardLayoutThemePartial,
8 CardLayoutThemePartialExt,
9 },
10};
11
12#[derive(Clone, PartialEq)]
14pub enum CardStyleVariant {
15 Filled,
16 Outline,
17}
18
19#[derive(Clone, PartialEq)]
21pub enum CardLayoutVariant {
22 Normal,
23 Compact,
24}
25
26#[cfg_attr(feature = "docs",
47 doc = embed_doc_image::embed_image!("card", "images/gallery_card.png"),
48)]
49#[derive(Clone, PartialEq)]
50pub struct Card {
51 pub(crate) theme_colors: Option<CardColorsThemePartial>,
52 pub(crate) theme_layout: Option<CardLayoutThemePartial>,
53 layout: LayoutData,
54 accessibility: AccessibilityData,
55 elements: Vec<Element>,
56 on_press: Option<EventHandler<Event<PressEventData>>>,
57 key: DiffKey,
58 style_variant: CardStyleVariant,
59 layout_variant: CardLayoutVariant,
60 hoverable: bool,
61}
62
63impl Default for Card {
64 fn default() -> Self {
65 Self::new()
66 }
67}
68
69impl ChildrenExt for Card {
70 fn get_children(&mut self) -> &mut Vec<Element> {
71 &mut self.elements
72 }
73}
74
75impl KeyExt for Card {
76 fn write_key(&mut self) -> &mut DiffKey {
77 &mut self.key
78 }
79}
80
81impl LayoutExt for Card {
82 fn get_layout(&mut self) -> &mut LayoutData {
83 &mut self.layout
84 }
85}
86
87impl ContainerExt for Card {}
88
89impl AccessibilityExt for Card {
90 fn get_accessibility_data(&mut self) -> &mut AccessibilityData {
91 &mut self.accessibility
92 }
93}
94
95impl CornerRadiusExt for Card {
96 fn with_corner_radius(self, corner_radius: f32) -> Self {
97 self.corner_radius(corner_radius)
98 }
99}
100
101impl Card {
102 pub fn new() -> Self {
103 Self {
104 theme_colors: None,
105 theme_layout: None,
106 layout: LayoutData::default(),
107 accessibility: AccessibilityData::default(),
108 style_variant: CardStyleVariant::Outline,
109 layout_variant: CardLayoutVariant::Normal,
110 on_press: None,
111 elements: Vec::default(),
112 hoverable: false,
113 key: DiffKey::None,
114 }
115 }
116
117 pub fn get_layout_variant(&self) -> &CardLayoutVariant {
119 &self.layout_variant
120 }
121
122 pub fn get_theme_layout(&self) -> Option<&CardLayoutThemePartial> {
124 self.theme_layout.as_ref()
125 }
126
127 pub fn style_variant(mut self, style_variant: impl Into<CardStyleVariant>) -> Self {
129 self.style_variant = style_variant.into();
130 self
131 }
132
133 pub fn layout_variant(mut self, layout_variant: impl Into<CardLayoutVariant>) -> Self {
135 self.layout_variant = layout_variant.into();
136 self
137 }
138
139 pub fn hoverable(mut self, hoverable: impl Into<bool>) -> Self {
141 self.hoverable = hoverable.into();
142 self
143 }
144
145 pub fn on_press(mut self, on_press: impl Into<EventHandler<Event<PressEventData>>>) -> Self {
147 self.on_press = Some(on_press.into());
148 self
149 }
150
151 pub fn theme_colors(mut self, theme: CardColorsThemePartial) -> Self {
153 self.theme_colors = Some(theme);
154 self
155 }
156
157 pub fn theme_layout(mut self, theme: CardLayoutThemePartial) -> Self {
159 self.theme_layout = Some(theme);
160 self
161 }
162
163 pub fn filled(self) -> Self {
165 self.style_variant(CardStyleVariant::Filled)
166 }
167
168 pub fn outline(self) -> Self {
170 self.style_variant(CardStyleVariant::Outline)
171 }
172
173 pub fn compact(self) -> Self {
175 self.layout_variant(CardLayoutVariant::Compact)
176 }
177}
178
179impl Component for Card {
180 fn render(&self) -> impl IntoElement {
181 let mut hovering = use_state(|| false);
182 let focus = use_focus();
183 let focus_status = use_focus_status(focus);
184
185 let is_hoverable = self.hoverable;
186
187 use_drop(move || {
188 if hovering() && is_hoverable {
189 Cursor::set(CursorIcon::default());
190 }
191 });
192
193 let theme_colors = match self.style_variant {
194 CardStyleVariant::Filled => get_theme!(&self.theme_colors, filled_card),
195 CardStyleVariant::Outline => get_theme!(&self.theme_colors, outline_card),
196 };
197 let theme_layout = match self.layout_variant {
198 CardLayoutVariant::Normal => get_theme!(&self.theme_layout, card_layout),
199 CardLayoutVariant::Compact => get_theme!(&self.theme_layout, compact_card_layout),
200 };
201
202 let border = if focus_status() == FocusStatus::Keyboard {
203 Border::new()
204 .fill(theme_colors.border_fill)
205 .width(2.)
206 .alignment(BorderAlignment::Inner)
207 } else {
208 Border::new()
209 .fill(theme_colors.border_fill)
210 .width(1.)
211 .alignment(BorderAlignment::Inner)
212 };
213
214 let background = if is_hoverable && hovering() {
215 theme_colors.hover_background
216 } else {
217 theme_colors.background
218 };
219
220 let shadow = if is_hoverable && hovering() {
221 Some(Shadow::new().y(4.).blur(8.).color(theme_colors.shadow))
222 } else {
223 None
224 };
225
226 rect()
227 .layout(self.layout.clone())
228 .overflow(Overflow::Clip)
229 .a11y_id(focus.a11y_id())
230 .a11y_focusable(is_hoverable)
231 .a11y_role(AccessibilityRole::GenericContainer)
232 .accessibility(self.accessibility.clone())
233 .background(background)
234 .border(border)
235 .padding(theme_layout.padding)
236 .corner_radius(theme_layout.corner_radius)
237 .color(theme_colors.color)
238 .map(shadow, |rect, shadow| rect.shadow(shadow))
239 .map(self.on_press.clone(), |rect, on_press| {
240 rect.on_press(move |e: Event<PressEventData>| {
241 focus.request_focus();
242 on_press.call(e);
243 })
244 })
245 .maybe(is_hoverable, |rect| {
246 rect.on_pointer_enter(move |_| {
247 hovering.set(true);
248 Cursor::set(CursorIcon::Pointer);
249 })
250 .on_pointer_leave(move |_| {
251 if hovering() {
252 Cursor::set(CursorIcon::default());
253 hovering.set(false);
254 }
255 })
256 })
257 .children(self.elements.clone())
258 }
259
260 fn render_key(&self) -> DiffKey {
261 self.key.clone().or(self.default_key())
262 }
263}