Skip to main content

repose_material/material3/
scaffold.rs

1#![allow(non_snake_case)]
2
3use repose_core::*;
4use repose_ui::{Box, Column, ViewExt};
5
6use super::*;
7
8/// Position of the floating action button within a Scaffold.
9#[derive(Clone, Copy, Debug, PartialEq, Default)]
10pub enum FabPosition {
11    #[default]
12    End,
13    Center,
14}
15
16#[derive(Clone)]
17pub struct ScaffoldConfig {
18    pub modifier: Modifier,
19    pub top_bar: Option<View>,
20    pub bottom_bar: Option<View>,
21    pub floating_action_button: Option<View>,
22    pub snackbar_host: Option<View>,
23    pub container_color: Color,
24    pub content_color: Color,
25    pub fab_position: FabPosition,
26}
27
28impl Default for ScaffoldConfig {
29    fn default() -> Self {
30        Self {
31            modifier: Modifier::new(),
32            top_bar: None,
33            bottom_bar: None,
34            floating_action_button: None,
35            snackbar_host: None,
36            container_color: ScaffoldDefaults::container_color(),
37            content_color: ScaffoldDefaults::content_color(),
38            fab_position: FabPosition::End,
39        }
40    }
41}
42
43pub fn Scaffold(content: impl Fn(PaddingValues) -> View, config: ScaffoldConfig) -> View {
44    // Scaffold is the usual M3 app root: install LocalIndication so plain
45    // `.clickable()` surfaces receive the default ripple even when the app
46    // does not wrap the whole tree in `MaterialTheme`.
47    with_material_indication(|| scaffold_inner(content, config))
48}
49
50fn scaffold_inner(content: impl Fn(PaddingValues) -> View, config: ScaffoldConfig) -> View {
51    let insets = window_insets();
52    let itop = px_to_dp(insets.top);
53    let ibottom = px_to_dp(insets.bottom);
54    let iime = px_to_dp(insets.ime_bottom);
55    let ileft = px_to_dp(insets.left);
56    let iright = px_to_dp(insets.right);
57
58    let content_padding = PaddingValues {
59        top: if config.top_bar.is_some() {
60            ScaffoldDefaults::TOP_BAR_HEIGHT
61        } else {
62            itop
63        },
64        bottom: if config.bottom_bar.is_some() {
65            ScaffoldDefaults::BOTTOM_BAR_HEIGHT + ibottom + iime
66        } else {
67            ibottom + iime
68        },
69        left: ileft,
70        right: iright,
71    };
72
73    Column(
74        config
75            .modifier
76            .fill_max_size()
77            .background(config.container_color),
78    )
79    .child((
80        Box(Modifier::new()
81            .fill_max_size()
82            .padding_values(PaddingValues {
83                top: if config.top_bar.is_some() {
84                    ScaffoldDefaults::TOP_BAR_HEIGHT + itop
85                } else {
86                    0.0
87                },
88                bottom: if config.bottom_bar.is_some() {
89                    ScaffoldDefaults::BOTTOM_BAR_HEIGHT + ibottom + iime
90                } else {
91                    ibottom + iime
92                },
93                ..Default::default()
94            }))
95        .child(content(content_padding)),
96        if let Some(bar) = config.top_bar {
97            Box(Modifier::new()
98                .absolute()
99                .offset(Some(0.0), Some(itop), Some(0.0), None))
100            .child(bar)
101        } else {
102            Box(Modifier::new())
103        },
104        if let Some(bar) = config.bottom_bar {
105            Box(Modifier::new()
106                .absolute()
107                .offset(Some(0.0), None, Some(ibottom + iime), Some(0.0)))
108            .child(bar)
109        } else {
110            Box(Modifier::new())
111        },
112        if let Some(fab) = config.floating_action_button {
113            let mut fab_m = Modifier::new().absolute();
114            match config.fab_position {
115                FabPosition::End => {
116                    fab_m = fab_m.offset(None, None, Some(16.0 + ibottom + iime), Some(16.0));
117                }
118                FabPosition::Center => {
119                    fab_m = fab_m.fill_max_width().align_self(AlignSelf::CENTER).offset(
120                        None,
121                        None,
122                        Some(16.0 + ibottom + iime),
123                        None,
124                    );
125                }
126            }
127            Box(fab_m).child(fab)
128        } else {
129            Box(Modifier::new())
130        },
131        config.snackbar_host.unwrap_or_else(|| Box(Modifier::new())),
132    ))
133}