repose_material/material3/
icon_button.rs1#![allow(non_snake_case)]
2
3use std::rc::Rc;
4
5use repose_core::*;
6use repose_ui::{Box, ViewExt};
7
8use super::*;
9
10#[derive(Clone, Copy, Debug)]
12pub struct IconButtonColors {
13 pub container_color: Color,
14 pub content_color: Color,
15 pub disabled_container_color: Color,
16 pub disabled_content_color: Color,
17}
18
19impl IconButtonColors {
20 pub fn container(&self, enabled: bool) -> Color {
21 if enabled {
22 self.container_color
23 } else {
24 self.disabled_container_color
25 }
26 }
27 pub fn content(&self, enabled: bool) -> Color {
28 if enabled {
29 self.content_color
30 } else {
31 self.disabled_content_color
32 }
33 }
34}
35
36#[derive(Clone, Debug)]
38pub struct IconButtonConfig {
39 pub modifier: Modifier,
40 pub enabled: bool,
41 pub colors: IconButtonColors,
42 pub container_size: Option<f32>,
43 pub interaction_source: Option<MutableInteractionSource>,
44 pub shape_radius: Option<f32>,
45}
46
47impl Default for IconButtonConfig {
48 fn default() -> Self {
49 Self {
50 modifier: Modifier::new(),
51 enabled: true,
52 colors: IconButtonColors {
53 container_color: Color::TRANSPARENT,
54 content_color: IconButtonDefaults::content_color(),
55 disabled_container_color: Color::TRANSPARENT,
56 disabled_content_color: IconButtonDefaults::disabled_content_color(),
57 },
58 container_size: None,
59 interaction_source: None,
60 shape_radius: None,
61 }
62 }
63}
64
65fn icon_button_render(
66 icon: View,
67 on_click: impl Fn() + 'static,
68 config: &IconButtonConfig,
69 sz: f32,
70 bg: Option<Color>,
71 bdr: Option<(f32, Color)>,
72 state_colors: StateColors,
73) -> View {
74 let is_enabled = config.enabled;
75 let content_color = config.colors.content(is_enabled);
76 let radius = config.shape_radius.unwrap_or(sz * 0.5);
77 let mut m = Modifier::new()
78 .size(sz, sz)
79 .clip_rounded(radius)
80 .state_colors(state_colors)
81 .align_items(AlignItems::CENTER)
82 .justify_content(JustifyContent::CENTER)
83 .then(config.modifier.clone());
84
85 if let Some(bg_color) = bg {
86 m = m.background(bg_color);
87 }
88 if let Some((w, c)) = bdr {
89 m = m.border(w, c, radius);
90 }
91 let source: Rc<MutableInteractionSource> = config
92 .interaction_source
93 .clone()
94 .map(Rc::new)
95 .unwrap_or_else(|| remember(MutableInteractionSource::new));
96 m = m.interaction_source(&source);
97 m = m.indication(crate::ripple::ripple(crate::ripple::RippleConfig {
98 color: Some(content_color),
99 bounded: true,
100 ..Default::default()
101 }));
102 if is_enabled {
103 m = m.clickable().on_click(on_click);
104 }
105
106 Box(m).child(icon)
107}
108
109pub fn IconButton(icon: View, on_click: impl Fn() + 'static, config: IconButtonConfig) -> View {
111 let th = theme();
112 let sz = config
113 .container_size
114 .unwrap_or(IconButtonDefaults::CONTAINER_SIZE);
115 icon_button_render(
116 icon,
117 on_click,
118 &config,
119 sz,
120 None,
121 None,
122 StateColors {
123 default: Color::TRANSPARENT,
124 hovered: Color::TRANSPARENT,
125 focused: Color::TRANSPARENT,
126 pressed: Color::TRANSPARENT,
127 dragged: th.on_surface.with_alpha_f32(0.12),
128 disabled: Color::TRANSPARENT,
129 },
130 )
131}
132
133pub fn FilledIconButton(
135 icon: View,
136 on_click: impl Fn() + 'static,
137 config: IconButtonConfig,
138) -> View {
139 let th = theme();
140 let is_enabled = config.enabled;
141 let sz = config
142 .container_size
143 .unwrap_or(IconButtonDefaults::FILLED_CONTAINER_SIZE);
144 let bg = config.colors.container(is_enabled);
145 let content_color = config.colors.content(is_enabled);
146 icon_button_render(
147 icon,
148 on_click,
149 &config,
150 sz,
151 Some(bg),
152 None,
153 StateColors {
154 default: Color::TRANSPARENT,
155 hovered: Color::TRANSPARENT,
156 focused: Color::TRANSPARENT,
157 pressed: Color::TRANSPARENT,
158 dragged: content_color.with_alpha_f32(0.12),
159 disabled: th.on_surface.with_alpha_f32(0.12),
160 },
161 )
162}
163
164pub fn FilledTonalIconButton(
166 icon: View,
167 on_click: impl Fn() + 'static,
168 config: IconButtonConfig,
169) -> View {
170 let th = theme();
171 let is_enabled = config.enabled;
172 let sz = config
173 .container_size
174 .unwrap_or(IconButtonDefaults::FILLED_CONTAINER_SIZE);
175 let bg = config.colors.container(is_enabled);
176 let content_color = config.colors.content(is_enabled);
177 icon_button_render(
178 icon,
179 on_click,
180 &config,
181 sz,
182 Some(bg),
183 None,
184 StateColors {
185 default: Color::TRANSPARENT,
186 hovered: Color::TRANSPARENT,
187 focused: Color::TRANSPARENT,
188 pressed: Color::TRANSPARENT,
189 dragged: content_color.with_alpha_f32(0.12),
190 disabled: th.on_surface.with_alpha_f32(0.12),
191 },
192 )
193}
194
195pub fn OutlinedIconButton(
197 icon: View,
198 on_click: impl Fn() + 'static,
199 config: IconButtonConfig,
200) -> View {
201 let th = theme();
202 let sz = config
203 .container_size
204 .unwrap_or(IconButtonDefaults::CONTAINER_SIZE);
205 let border_color = if config.enabled {
206 th.outline
207 } else {
208 th.on_surface.with_alpha_f32(0.12)
209 };
210 icon_button_render(
211 icon,
212 on_click,
213 &config,
214 sz,
215 None,
216 Some((1.0, border_color)),
217 StateColors {
218 default: Color::TRANSPARENT,
219 hovered: Color::TRANSPARENT,
220 focused: Color::TRANSPARENT,
221 pressed: Color::TRANSPARENT,
222 dragged: th.on_surface.with_alpha_f32(0.12),
223 disabled: Color::TRANSPARENT,
224 },
225 )
226}