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).child(super::util::icon_content_with_color(content_color, icon))
93}
94
95pub fn FAB(icon: View, on_click: impl Fn() + 'static, mut config: FABConfig) -> View {
97 config.size = FABDefaults::SIZE;
98 config.shape_radius = FABDefaults::SHAPE_RADIUS;
99 fab_impl(icon, on_click, config)
100}
101
102pub fn SmallFAB(icon: View, on_click: impl Fn() + 'static, mut config: FABConfig) -> View {
104 config.size = FABDefaults::SMALL_SIZE;
105 config.shape_radius = FABDefaults::SMALL_SHAPE_RADIUS;
106 fab_impl(icon, on_click, config)
107}
108
109pub fn LargeFAB(icon: View, on_click: impl Fn() + 'static, mut config: FABConfig) -> View {
111 config.size = FABDefaults::LARGE_SIZE;
112 config.shape_radius = FABDefaults::LARGE_SHAPE_RADIUS;
113 fab_impl(icon, on_click, config)
114}
115
116pub fn ExtendedFAB(
118 icon: Option<View>,
119 label: impl Into<String>,
120 on_click: impl Fn() + 'static,
121 config: FABConfig,
122) -> View {
123 let th = theme();
124 let has_icon = icon.is_some();
125 let is_enabled = config.enabled;
126 let bg = if is_enabled {
127 config.container_color
128 } else {
129 th.on_surface
130 .with_alpha_f32(0.12)
131 .composite_over(th.surface_container_low)
132 };
133 let content_color = if is_enabled {
134 config.content_color
135 } else {
136 th.on_surface.with_alpha_f32(0.38)
137 };
138
139 let elev = if is_enabled {
140 config.state_elevation
141 } else {
142 StateElevation {
143 default: 0.0,
144 hovered: 0.0,
145 focused: 0.0,
146 pressed: 0.0,
147 dragged: 0.0,
148 disabled: 0.0,
149 }
150 };
151
152 let source: Rc<MutableInteractionSource> = config
153 .interaction_source
154 .clone()
155 .map(Rc::new)
156 .unwrap_or_else(|| remember(MutableInteractionSource::new));
157
158 let mut m = Modifier::new()
159 .height(56.0)
160 .min_width(80.0)
161 .background(bg)
162 .state_colors(StateColors {
163 default: Color::TRANSPARENT,
164 hovered: Color::TRANSPARENT,
165 focused: Color::TRANSPARENT,
166 pressed: Color::TRANSPARENT,
167 dragged: config.content_color.with_alpha_f32(0.12),
168 disabled: theme().on_surface.with_alpha_f32(0.12),
169 })
170 .state_elevation(elev)
171 .clip_rounded(FABDefaults::SHAPE_RADIUS)
172 .padding_values(PaddingValues {
173 left: 16.0,
174 right: 20.0,
175 top: 0.0,
176 bottom: 0.0,
177 })
178 .align_items(AlignItems::CENTER);
179
180 m = apply_m3_clickable(m, &source, content_color, is_enabled, on_click);
181 m = with_button_semantics(m, is_enabled);
182 m = m.then(config.modifier);
183 Row(m)
184 .child((
185 icon.map(|v| super::util::icon_content_with_color(content_color, v))
186 .unwrap_or(Box(Modifier::new())),
187 Box(Modifier::new()
188 .width(if has_icon { 12.0 } else { 0.0 })
189 .fill_max_height()),
190 Text(label)
191 .color(content_color)
192 .size(th.typography.label_large)
193 .single_line(),
194 ))
195
196}