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 dragged: config.tonal_elevation,
81 disabled: 0.0,
82 });
83 }
84 Box(m).color(fg).child(content())
85}
86
87pub fn ElevatedCard(config: CardConfig, content: impl FnOnce() -> View) -> View {
89 let th = theme();
90 Card(
91 CardConfig {
92 container_color: CardDefaults::elevated_container_color(),
93 state_elevation: Some(StateElevation {
94 default: th.elevation.level1,
95 hovered: th.elevation.level2,
96 pressed: th.elevation.level3,
97 dragged: th.elevation.level3,
98 disabled: 0.0,
99 }),
100 ..config
101 },
102 content,
103 )
104}
105
106pub fn OutlinedCard(config: CardConfig, content: impl FnOnce() -> View) -> View {
108 Card(
109 CardConfig {
110 container_color: CardDefaults::outlined_container_color(),
111 border: Some((1.0, CardDefaults::outlined_border_color())),
112 ..config
113 },
114 content,
115 )
116}
117
118fn card_state_colors(bg: Color) -> StateColors {
119 let th = theme();
120 StateColors {
121 default: Color::TRANSPARENT,
122 hovered: th.on_surface.with_alpha_f32(0.08).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 pressed: th.elevation.level3,
198 dragged: th.elevation.level3,
199 disabled: 0.0,
200 }),
201 ..config
202 };
203 clickable_card_impl(
204 on_click,
205 modifier,
206 th.surface,
207 th.shapes.medium,
208 cfg,
209 content,
210 )
211}
212
213pub fn ClickableOutlinedCard(
215 on_click: impl Fn() + 'static,
216 modifier: Modifier,
217 config: CardConfig,
218 content: impl FnOnce() -> View,
219) -> View {
220 let th = theme();
221 let cfg = CardConfig {
222 border: Some((1.0, th.outline_variant)),
223 ..config
224 };
225 clickable_card_impl(
226 on_click,
227 modifier,
228 th.surface,
229 th.shapes.medium,
230 cfg,
231 content,
232 )
233}