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