repose_material/material3/
fab.rs1#![allow(non_snake_case)]
2
3use std::rc::Rc;
4
5use repose_core::*;
6use repose_ui::{Box, Row, Text, TextStyle, ViewExt};
7
8use super::util::{apply_m3_clickable, with_button_semantics};
9use super::*;
10
11#[derive(Clone, Debug)]
13pub struct FABConfig {
14 pub modifier: Modifier,
15 pub enabled: bool,
16 pub container_color: Color,
17 pub content_color: Color,
18 pub state_elevation: StateElevation,
19 pub shape_radius: f32,
20 pub size: f32,
21 pub interaction_source: Option<MutableInteractionSource>,
22}
23
24impl Default for FABConfig {
25 fn default() -> Self {
26 Self {
27 modifier: Modifier::new(),
28 enabled: true,
29 container_color: FABDefaults::container_color(),
30 content_color: FABDefaults::content_color(),
31 state_elevation: FABDefaults::state_elevation(),
32 shape_radius: FABDefaults::SHAPE_RADIUS,
33 size: FABDefaults::SIZE,
34 interaction_source: None,
35 }
36 }
37}
38
39fn fab_impl(icon: View, on_click: impl Fn() + 'static, config: FABConfig) -> View {
40 let th = theme();
41 let is_enabled = config.enabled;
42 let bg = if is_enabled {
43 config.container_color
44 } else {
45 th.on_surface
46 .with_alpha_f32(0.12)
47 .composite_over(th.surface_container_low)
48 };
49 let content_color = if is_enabled {
50 config.content_color
51 } else {
52 th.on_surface.with_alpha_f32(0.38)
53 };
54
55 let elev = if is_enabled {
56 config.state_elevation
57 } else {
58 StateElevation {
59 default: 0.0,
60 hovered: 0.0,
61 focused: 0.0,
62 pressed: 0.0,
63 dragged: 0.0,
64 disabled: 0.0,
65 }
66 };
67
68 let mut m = Modifier::new()
69 .size(config.size, config.size)
70 .background(bg)
71 .state_colors(StateColors {
72 default: Color::TRANSPARENT,
73 hovered: Color::TRANSPARENT,
74 focused: Color::TRANSPARENT,
75 pressed: Color::TRANSPARENT,
76 dragged: config.content_color.with_alpha_f32(0.12),
77 disabled: th.on_surface.with_alpha_f32(0.12),
78 })
79 .state_elevation(elev)
80 .clip_rounded(config.shape_radius)
81 .align_items(AlignItems::CENTER)
82 .justify_content(JustifyContent::CENTER)
83 .then(config.modifier);
84
85 let source: Rc<MutableInteractionSource> = config
86 .interaction_source
87 .map(Rc::new)
88 .unwrap_or_else(|| remember(MutableInteractionSource::new));
89 m = apply_m3_clickable(m, &source, content_color, is_enabled, on_click);
90 m = with_button_semantics(m, is_enabled);
91
92 Box(m)
93 .child(with_content_color(content_color, move || icon))
94
95}
96
97pub fn FAB(icon: View, on_click: impl Fn() + 'static, mut config: FABConfig) -> View {
99 config.size = FABDefaults::SIZE;
100 config.shape_radius = FABDefaults::SHAPE_RADIUS;
101 fab_impl(icon, on_click, config)
102}
103
104pub fn SmallFAB(icon: View, on_click: impl Fn() + 'static, mut config: FABConfig) -> View {
106 config.size = FABDefaults::SMALL_SIZE;
107 config.shape_radius = FABDefaults::SMALL_SHAPE_RADIUS;
108 fab_impl(icon, on_click, config)
109}
110
111pub fn LargeFAB(icon: View, on_click: impl Fn() + 'static, mut config: FABConfig) -> View {
113 config.size = FABDefaults::LARGE_SIZE;
114 config.shape_radius = FABDefaults::LARGE_SHAPE_RADIUS;
115 fab_impl(icon, on_click, config)
116}
117
118pub fn ExtendedFAB(
120 icon: Option<View>,
121 label: impl Into<String>,
122 on_click: impl Fn() + 'static,
123 config: FABConfig,
124) -> View {
125 let th = theme();
126 let has_icon = icon.is_some();
127 let is_enabled = config.enabled;
128 let bg = if is_enabled {
129 config.container_color
130 } else {
131 th.on_surface
132 .with_alpha_f32(0.12)
133 .composite_over(th.surface_container_low)
134 };
135 let content_color = if is_enabled {
136 config.content_color
137 } else {
138 th.on_surface.with_alpha_f32(0.38)
139 };
140
141 let elev = if is_enabled {
142 config.state_elevation
143 } else {
144 StateElevation {
145 default: 0.0,
146 hovered: 0.0,
147 focused: 0.0,
148 pressed: 0.0,
149 dragged: 0.0,
150 disabled: 0.0,
151 }
152 };
153
154 let source: Rc<MutableInteractionSource> = config
155 .interaction_source
156 .clone()
157 .map(Rc::new)
158 .unwrap_or_else(|| remember(MutableInteractionSource::new));
159
160 let mut m = Modifier::new()
161 .height(56.0)
162 .min_width(80.0)
163 .background(bg)
164 .state_colors(StateColors {
165 default: Color::TRANSPARENT,
166 hovered: Color::TRANSPARENT,
167 focused: Color::TRANSPARENT,
168 pressed: Color::TRANSPARENT,
169 dragged: config.content_color.with_alpha_f32(0.12),
170 disabled: theme().on_surface.with_alpha_f32(0.12),
171 })
172 .state_elevation(elev)
173 .clip_rounded(FABDefaults::SHAPE_RADIUS)
174 .padding_values(PaddingValues {
175 left: 16.0,
176 right: 20.0,
177 top: 0.0,
178 bottom: 0.0,
179 })
180 .align_items(AlignItems::CENTER);
181
182 m = apply_m3_clickable(m, &source, content_color, is_enabled, on_click);
183 m = with_button_semantics(m, is_enabled);
184 m = m.then(config.modifier);
185 Row(m)
186 .child((
187 icon.map(|v| with_content_color(content_color, move || v))
188 .unwrap_or(Box(Modifier::new())),
189 Box(Modifier::new()
190 .width(if has_icon { 12.0 } else { 0.0 })
191 .fill_max_height()),
192 Text(label)
193 .color(content_color)
194 .size(th.typography.label_large)
195 .single_line(),
196 ))
197
198}