repose_material/material3/
card.rs1#![allow(non_snake_case)]
2
3use std::rc::Rc;
4
5use repose_core::*;
6use repose_ui::{
7 Box, Column, TextStyle,
8 ViewExt,
9};
10
11use super::*;
12
13#[derive(Clone, Debug)]
15pub struct CardConfig {
16 pub modifier: Modifier,
17 pub enabled: bool,
19 pub container_color: Color,
20 pub content_color: Color,
21 pub disabled_container_color: Color,
22 pub disabled_content_color: Color,
23 pub shape_radius: f32,
24 pub tonal_elevation: f32,
25 pub state_elevation: Option<StateElevation>,
26 pub border: Option<(f32, Color)>,
27 pub interaction_source: Option<MutableInteractionSource>,
28}
29
30impl Default for CardConfig {
31 fn default() -> Self {
32 Self {
33 modifier: Modifier::new(),
34 enabled: true,
35 container_color: CardDefaults::filled_container_color(),
36 content_color: CardDefaults::filled_content_color(),
37 disabled_container_color: CardDefaults::disabled_container_color(),
38 disabled_content_color: CardDefaults::disabled_content_color(),
39 shape_radius: CardDefaults::SHAPE_RADIUS,
40 tonal_elevation: CardDefaults::ELEVATION,
41 state_elevation: None,
42 border: None,
43 interaction_source: None,
44 }
45 }
46}
47
48pub fn Card(config: CardConfig, content: impl FnOnce() -> View) -> View {
50 let bg = if !config.enabled {
51 config.disabled_container_color
52 } else {
53 config.container_color
54 };
55 let fg = if !config.enabled {
56 config.disabled_content_color
57 } else {
58 config.content_color
59 };
60 let source: Rc<MutableInteractionSource> = config
61 .interaction_source
62 .clone()
63 .map(Rc::new)
64 .unwrap_or_else(|| remember(MutableInteractionSource::new));
65 let mut m = Modifier::new()
66 .background(bg)
67 .clip_rounded(config.shape_radius)
68 .interaction_source(&*source)
69 .then(config.modifier);
70 if let Some((w, c)) = config.border {
71 m = m.border(w, c, config.shape_radius);
72 }
73 if let Some(se) = config.state_elevation {
74 m = m.state_elevation(se);
75 } else if config.tonal_elevation > 0.0 {
76 m = m.state_elevation(StateElevation {
77 default: config.tonal_elevation,
78 hovered: config.tonal_elevation,
79 pressed: config.tonal_elevation,
80 disabled: 0.0,
81 });
82 }
83 Box(m).color(fg).child(content())
84}
85
86pub fn ElevatedCard(config: CardConfig, content: impl FnOnce() -> View) -> View {
88 let th = theme();
89 Card(
90 CardConfig {
91 container_color: CardDefaults::elevated_container_color(),
92 state_elevation: Some(StateElevation {
93 default: th.elevation.level1,
94 hovered: th.elevation.level2,
95 pressed: th.elevation.level3,
96 disabled: 0.0,
97 }),
98 ..config
99 },
100 content,
101 )
102}
103
104pub fn OutlinedCard(config: CardConfig, content: impl FnOnce() -> View) -> View {
106 Card(
107 CardConfig {
108 container_color: CardDefaults::outlined_container_color(),
109 border: Some((1.0, CardDefaults::outlined_border_color())),
110 ..config
111 },
112 content,
113 )
114}
115
116fn card_state_colors(bg: Color) -> StateColors {
117 let th = theme();
118 StateColors {
119 default: Color::TRANSPARENT,
120 hovered: th.on_surface.with_alpha_f32(0.08).composite_over(bg),
121 pressed: 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 disabled: 0.0,
196 }),
197 ..config
198 };
199 clickable_card_impl(
200 on_click,
201 modifier,
202 th.surface,
203 th.shapes.medium,
204 cfg,
205 content,
206 )
207}
208
209pub fn ClickableOutlinedCard(
211 on_click: impl Fn() + 'static,
212 modifier: Modifier,
213 config: CardConfig,
214 content: impl FnOnce() -> View,
215) -> View {
216 let th = theme();
217 let cfg = CardConfig {
218 border: Some((1.0, th.outline_variant)),
219 ..config
220 };
221 clickable_card_impl(
222 on_click,
223 modifier,
224 th.surface,
225 th.shapes.medium,
226 cfg,
227 content,
228 )
229}