repose_material/material3/
card.rs1#![allow(non_snake_case)]
2
3use std::rc::Rc;
4
5use repose_core::*;
6use repose_ui::{Box, Column, TextStyle, ViewExt};
7
8use super::*;
9
10#[derive(Clone, Debug)]
12pub struct CardConfig {
13 pub modifier: Modifier,
14 pub enabled: bool,
16 pub container_color: Color,
17 pub content_color: Color,
18 pub disabled_container_color: Color,
19 pub disabled_content_color: Color,
20 pub shape_radius: f32,
21 pub tonal_elevation: f32,
22 pub state_elevation: Option<StateElevation>,
23 pub border: Option<(f32, Color)>,
24 pub interaction_source: Option<MutableInteractionSource>,
25}
26
27impl Default for CardConfig {
28 fn default() -> Self {
29 Self {
30 modifier: Modifier::new(),
31 enabled: true,
32 container_color: CardDefaults::filled_container_color(),
33 content_color: CardDefaults::filled_content_color(),
34 disabled_container_color: CardDefaults::disabled_container_color(),
35 disabled_content_color: CardDefaults::disabled_content_color(),
36 shape_radius: CardDefaults::SHAPE_RADIUS,
37 tonal_elevation: CardDefaults::ELEVATION,
38 state_elevation: None,
39 border: None,
40 interaction_source: None,
41 }
42 }
43}
44
45pub fn Card(config: CardConfig, content: impl FnOnce() -> View) -> View {
47 let bg = if !config.enabled {
48 config.disabled_container_color
49 } else {
50 config.container_color
51 };
52 let fg = if !config.enabled {
53 config.disabled_content_color
54 } else {
55 config.content_color
56 };
57 let source: Rc<MutableInteractionSource> = config
58 .interaction_source
59 .clone()
60 .map(Rc::new)
61 .unwrap_or_else(|| remember(MutableInteractionSource::new));
62 let mut m = Modifier::new()
63 .background(bg)
64 .clip_rounded(config.shape_radius)
65 .interaction_source(&source)
66 .then(config.modifier);
67 if let Some((w, c)) = config.border {
68 m = m.border(w, c, config.shape_radius);
69 }
70 if let Some(se) = config.state_elevation {
71 m = m.state_elevation(se);
72 } else if config.tonal_elevation > 0.0 {
73 m = m.state_elevation(StateElevation {
74 default: config.tonal_elevation,
75 hovered: config.tonal_elevation,
76 pressed: config.tonal_elevation,
77 dragged: config.tonal_elevation,
78 disabled: 0.0,
79 });
80 }
81 Box(m).color(fg).child(content())
82}
83
84pub fn ElevatedCard(config: CardConfig, content: impl FnOnce() -> View) -> View {
86 let th = theme();
87 Card(
88 CardConfig {
89 container_color: CardDefaults::elevated_container_color(),
90 state_elevation: Some(StateElevation {
91 default: th.elevation.level1,
92 hovered: th.elevation.level2,
93 pressed: th.elevation.level3,
94 dragged: th.elevation.level3,
95 disabled: 0.0,
96 }),
97 ..config
98 },
99 content,
100 )
101}
102
103pub fn OutlinedCard(config: CardConfig, content: impl FnOnce() -> View) -> View {
105 Card(
106 CardConfig {
107 container_color: CardDefaults::outlined_container_color(),
108 border: Some((1.0, CardDefaults::outlined_border_color())),
109 ..config
110 },
111 content,
112 )
113}
114
115fn card_state_colors(bg: Color) -> StateColors {
116 let th = theme();
117 StateColors {
118 default: Color::TRANSPARENT,
119 hovered: th.on_surface.with_alpha_f32(0.08).composite_over(bg),
120 pressed: th.on_surface.with_alpha_f32(0.12).composite_over(bg),
121 dragged: th.on_surface.with_alpha_f32(0.12).composite_over(bg),
122 disabled: th.on_surface.with_alpha_f32(0.12).composite_over(bg),
123 }
124}
125
126fn clickable_card_impl(
127 on_click: impl Fn() + 'static,
128 modifier: Modifier,
129 bg: Color,
130 shape_radius: f32,
131 config: CardConfig,
132 content: impl FnOnce() -> View,
133) -> View {
134 let m = modifier
135 .state_colors(card_state_colors(bg))
136 .clickable()
137 .on_pointer_down({
138 let cb = on_click;
139 let en = config.enabled;
140 move |_| {
141 if en {
142 cb();
143 }
144 }
145 });
146 Card(
147 CardConfig {
148 modifier: m,
149 enabled: config.enabled,
150 container_color: bg,
151 content_color: config.content_color,
152 disabled_container_color: config.disabled_container_color,
153 disabled_content_color: config.disabled_content_color,
154 shape_radius,
155 border: config.border,
156 state_elevation: config.state_elevation,
157 tonal_elevation: config.tonal_elevation,
158 interaction_source: config.interaction_source.clone(),
159 },
160 || Column(Modifier::new().fill_max_size()).child(content()),
161 )
162}
163
164pub fn ClickableCard(
166 on_click: impl Fn() + 'static,
167 modifier: Modifier,
168 config: CardConfig,
169 content: impl FnOnce() -> View,
170) -> View {
171 let th = theme();
172 clickable_card_impl(
173 on_click,
174 modifier,
175 th.surface_container_highest,
176 th.shapes.medium,
177 config,
178 content,
179 )
180}
181
182pub fn ClickableElevatedCard(
184 on_click: impl Fn() + 'static,
185 modifier: Modifier,
186 config: CardConfig,
187 content: impl FnOnce() -> View,
188) -> View {
189 let th = theme();
190 let cfg = CardConfig {
191 state_elevation: Some(StateElevation {
192 default: th.elevation.level1,
193 hovered: th.elevation.level2,
194 pressed: th.elevation.level3,
195 dragged: th.elevation.level3,
196 disabled: 0.0,
197 }),
198 ..config
199 };
200 clickable_card_impl(
201 on_click,
202 modifier,
203 th.surface,
204 th.shapes.medium,
205 cfg,
206 content,
207 )
208}
209
210pub fn ClickableOutlinedCard(
212 on_click: impl Fn() + 'static,
213 modifier: Modifier,
214 config: CardConfig,
215 content: impl FnOnce() -> View,
216) -> View {
217 let th = theme();
218 let cfg = CardConfig {
219 border: Some((1.0, th.outline_variant)),
220 ..config
221 };
222 clickable_card_impl(
223 on_click,
224 modifier,
225 th.surface,
226 th.shapes.medium,
227 cfg,
228 content,
229 )
230}