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