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(
51    content: impl Fn(PaddingValues) -> View,
52    config: ScaffoldConfig,
53) -> View {
54    let insets = window_insets();
55    let itop = px_to_dp(insets.top);
56    let ibottom = px_to_dp(insets.bottom);
57    let iime = px_to_dp(insets.ime_bottom);
58    let ileft = px_to_dp(insets.left);
59    let iright = px_to_dp(insets.right);
60
61    let content_padding = PaddingValues {
62        top: if config.top_bar.is_some() {
63            ScaffoldDefaults::TOP_BAR_HEIGHT
64        } else {
65            itop
66        },
67        bottom: if config.bottom_bar.is_some() {
68            ScaffoldDefaults::BOTTOM_BAR_HEIGHT + ibottom + iime
69        } else {
70            ibottom + iime
71        },
72        left: ileft,
73        right: iright,
74    };
75
76    Column(
77        config
78            .modifier
79            .fill_max_size()
80            .background(config.container_color),
81    )
82    .child((
83        Box(Modifier::new()
84            .fill_max_size()
85            .padding_values(PaddingValues {
86                top: if config.top_bar.is_some() {
87                    ScaffoldDefaults::TOP_BAR_HEIGHT + itop
88                } else {
89                    0.0
90                },
91                bottom: if config.bottom_bar.is_some() {
92                    ScaffoldDefaults::BOTTOM_BAR_HEIGHT + ibottom + iime
93                } else {
94                    ibottom + iime
95                },
96                ..Default::default()
97            }))
98        .child(content(content_padding)),
99        if let Some(bar) = config.top_bar {
100            Box(Modifier::new()
101                .absolute()
102                .offset(Some(0.0), Some(itop), Some(0.0), None))
103            .child(bar)
104        } else {
105            Box(Modifier::new())
106        },
107        if let Some(bar) = config.bottom_bar {
108            Box(Modifier::new()
109                .absolute()
110                .offset(Some(0.0), None, Some(ibottom + iime), Some(0.0)))
111            .child(bar)
112        } else {
113            Box(Modifier::new())
114        },
115        if let Some(fab) = config.floating_action_button {
116            let mut fab_m = Modifier::new().absolute();
117            match config.fab_position {
118                FabPosition::End => {
119                    fab_m = fab_m.offset(None, None, Some(16.0 + ibottom + iime), Some(16.0));
120                }
121                FabPosition::Center => {
122                    fab_m = fab_m.fill_max_width().align_self(AlignSelf::CENTER).offset(
123                        None,
124                        None,
125                        Some(16.0 + ibottom + iime),
126                        None,
127                    );
128                }
129            }
130            Box(fab_m).child(fab)
131        } else {
132            Box(Modifier::new())
133        },
134        config.snackbar_host.unwrap_or_else(|| Box(Modifier::new())),
135    ))
136}