Skip to main content

repose_material/material3/
surface.rs

1#![allow(non_snake_case)]
2
3use std::rc::Rc;
4
5use repose_core::*;
6use repose_ui::{Box, TextStyle, ViewExt};
7
8use super::util::{apply_m3_clickable, apply_tonal_elevation};
9use super::*;
10
11/// Configuration for [`Surface`].
12#[derive(Clone, Debug)]
13pub struct SurfaceConfig {
14    pub modifier: Modifier,
15    pub enabled: bool,
16    pub color: Color,
17    pub content_color: Color,
18    pub shape_radius: f32,
19    pub tonal_elevation: f32,
20    pub shadow_elevation: f32,
21    pub border: Option<(f32, Color)>,
22    pub interaction_source: Option<MutableInteractionSource>,
23}
24
25impl Default for SurfaceConfig {
26    fn default() -> Self {
27        Self {
28            modifier: Modifier::new(),
29            enabled: true,
30            color: SurfaceDefaults::color(),
31            content_color: SurfaceDefaults::content_color(),
32            shape_radius: SurfaceDefaults::SHAPE_RADIUS,
33            tonal_elevation: SurfaceDefaults::TONAL_ELEVATION,
34            shadow_elevation: SurfaceDefaults::SHADOW_ELEVATION,
35            border: None,
36            interaction_source: None,
37        }
38    }
39}
40
41/// M3 Surface - a basic container with shape, color, elevation, and border.
42/// Sets the ContentColor local for children based on the surface color.
43pub fn Surface(config: SurfaceConfig, content: impl FnOnce() -> View) -> View {
44    let sf_source: Rc<MutableInteractionSource> = config
45        .interaction_source
46        .clone()
47        .map(Rc::new)
48        .unwrap_or_else(|| remember(MutableInteractionSource::new));
49    let mut m = Modifier::new().background(config.color);
50    m = apply_tonal_elevation(m, config.tonal_elevation, config.color);
51    m = m
52        .clip_rounded(config.shape_radius)
53        .interaction_source(&sf_source)
54        .then(config.modifier);
55    if config.shadow_elevation > 0.0 {
56        m = m.shadow(config.shadow_elevation, 0.0);
57    }
58    if let Some((w, c)) = config.border {
59        m = m.border(w, c, config.shape_radius);
60    }
61    if !config.enabled {
62        m = m.enabled(false);
63    }
64    Box(m)
65        .color(config.content_color)
66        .child(with_content_color(config.content_color, content))
67}
68
69/// M3 Clickable Surface - Compose `Surface(onClick = ...)` equivalent.
70pub fn ClickableSurface(
71    on_click: impl Fn() + 'static,
72    config: SurfaceConfig,
73    content: impl FnOnce() -> View,
74) -> View {
75    let sf_source: Rc<MutableInteractionSource> = config
76        .interaction_source
77        .clone()
78        .map(Rc::new)
79        .unwrap_or_else(|| remember(MutableInteractionSource::new));
80    let mut m = Modifier::new()
81        .min_width(48.0)
82        .min_height(48.0)
83        .background(config.color);
84    m = apply_tonal_elevation(m, config.tonal_elevation, config.color);
85    m = m
86        .clip_rounded(config.shape_radius)
87        .interaction_source(&sf_source)
88        .then(config.modifier);
89    if config.shadow_elevation > 0.0 {
90        m = m.shadow(config.shadow_elevation, 0.0);
91    }
92    if let Some((w, c)) = config.border {
93        m = m.border(w, c, config.shape_radius);
94    }
95    m = apply_m3_clickable(
96        m,
97        &sf_source,
98        config.content_color,
99        config.enabled,
100        on_click,
101    );
102    Box(m)
103        .color(config.content_color)
104        .child(with_content_color(config.content_color, content))
105}