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 focused: config.tonal_elevation,
77 pressed: config.tonal_elevation,
78 dragged: config.tonal_elevation,
79 disabled: 0.0,
80 });
81 }
82 Box(m).color(fg).child(content())
83}
84
85pub fn ElevatedCard(config: CardConfig, content: impl FnOnce() -> View) -> View {
87 let th = theme();
88 Card(
89 CardConfig {
90 container_color: CardDefaults::elevated_container_color(),
91 state_elevation: Some(StateElevation {
92 default: th.elevation.level1,
93 hovered: th.elevation.level2,
94 focused: th.elevation.level2,
95 pressed: th.elevation.level3,
96 dragged: th.elevation.level3,
97 disabled: 0.0,
98 }),
99 ..config
100 },
101 content,
102 )
103}
104
105pub fn OutlinedCard(config: CardConfig, content: impl FnOnce() -> View) -> View {
107 Card(
108 CardConfig {
109 container_color: CardDefaults::outlined_container_color(),
110 border: Some((1.0, CardDefaults::outlined_border_color())),
111 ..config
112 },
113 content,
114 )
115}
116
117fn card_state_colors(bg: Color) -> StateColors {
118 let th = theme();
119 StateColors {
120 default: Color::TRANSPARENT,
121 hovered: th.on_surface.with_alpha_f32(0.08).composite_over(bg),
122 focused: th.on_surface.with_alpha_f32(0.12).composite_over(bg),
123 pressed: th.on_surface.with_alpha_f32(0.12).composite_over(bg),
124 dragged: th.on_surface.with_alpha_f32(0.12).composite_over(bg),
125 disabled: th.on_surface.with_alpha_f32(0.12).composite_over(bg),
126 }
127}
128
129fn clickable_card_impl(
130 on_click: impl Fn() + 'static,
131 modifier: Modifier,
132 bg: Color,
133 shape_radius: f32,
134 config: CardConfig,
135 content: impl FnOnce() -> View,
136) -> View {
137 let m = modifier
138 .state_colors(card_state_colors(bg))
139 .clickable()
140 .on_pointer_down({
141 let cb = on_click;
142 let en = config.enabled;
143 move |_| {
144 if en {
145 cb();
146 }
147 }
148 });
149 Card(
150 CardConfig {
151 modifier: m,
152 enabled: config.enabled,
153 container_color: bg,
154 content_color: config.content_color,
155 disabled_container_color: config.disabled_container_color,
156 disabled_content_color: config.disabled_content_color,
157 shape_radius,
158 border: config.border,
159 state_elevation: config.state_elevation,
160 tonal_elevation: config.tonal_elevation,
161 interaction_source: config.interaction_source.clone(),
162 },
163 || Column(Modifier::new().fill_max_size()).child(content()),
164 )
165}
166
167pub fn ClickableCard(
169 on_click: impl Fn() + 'static,
170 modifier: Modifier,
171 config: CardConfig,
172 content: impl FnOnce() -> View,
173) -> View {
174 let th = theme();
175 clickable_card_impl(
176 on_click,
177 modifier,
178 th.surface_container_highest,
179 th.shapes.medium,
180 config,
181 content,
182 )
183}
184
185pub fn ClickableElevatedCard(
187 on_click: impl Fn() + 'static,
188 modifier: Modifier,
189 config: CardConfig,
190 content: impl FnOnce() -> View,
191) -> View {
192 let th = theme();
193 let cfg = CardConfig {
194 state_elevation: Some(StateElevation {
195 default: th.elevation.level1,
196 hovered: th.elevation.level2,
197 focused: th.elevation.level2,
198 pressed: th.elevation.level3,
199 dragged: th.elevation.level3,
200 disabled: 0.0,
201 }),
202 ..config
203 };
204 clickable_card_impl(
205 on_click,
206 modifier,
207 th.surface,
208 th.shapes.medium,
209 cfg,
210 content,
211 )
212}
213
214pub fn ClickableOutlinedCard(
216 on_click: impl Fn() + 'static,
217 modifier: Modifier,
218 config: CardConfig,
219 content: impl FnOnce() -> View,
220) -> View {
221 let th = theme();
222 let cfg = CardConfig {
223 border: Some((1.0, th.outline_variant)),
224 ..config
225 };
226 clickable_card_impl(
227 on_click,
228 modifier,
229 th.surface,
230 th.shapes.medium,
231 cfg,
232 content,
233 )
234}