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 if is_enabled {
98 m = m.clickable().on_click(on_click);
99 }
100
101 Box(m).child(icon)
102}
103
104pub fn IconButton(icon: View, on_click: impl Fn() + 'static, config: IconButtonConfig) -> View {
106 let th = theme();
107 let sz = config
108 .container_size
109 .unwrap_or(IconButtonDefaults::CONTAINER_SIZE);
110 icon_button_render(
111 icon,
112 on_click,
113 &config,
114 sz,
115 None,
116 None,
117 StateColors {
118 default: Color::TRANSPARENT,
119 hovered: th.on_surface.with_alpha_f32(0.08),
120 focused: th.on_surface.with_alpha_f32(0.12),
121 pressed: th.on_surface.with_alpha_f32(0.12),
122 dragged: th.on_surface.with_alpha_f32(0.12),
123 disabled: Color::TRANSPARENT,
124 },
125 )
126}
127
128pub fn FilledIconButton(
130 icon: View,
131 on_click: impl Fn() + 'static,
132 config: IconButtonConfig,
133) -> View {
134 let th = theme();
135 let is_enabled = config.enabled;
136 let sz = config
137 .container_size
138 .unwrap_or(IconButtonDefaults::FILLED_CONTAINER_SIZE);
139 let bg = config.colors.container(is_enabled);
140 let content_color = config.colors.content(is_enabled);
141 icon_button_render(
142 icon,
143 on_click,
144 &config,
145 sz,
146 Some(bg),
147 None,
148 StateColors {
149 default: Color::TRANSPARENT,
150 hovered: content_color.with_alpha_f32(0.08),
151 focused: content_color.with_alpha_f32(0.12),
152 pressed: content_color.with_alpha_f32(0.12),
153 dragged: content_color.with_alpha_f32(0.12),
154 disabled: th.on_surface.with_alpha_f32(0.12),
155 },
156 )
157}
158
159pub fn FilledTonalIconButton(
161 icon: View,
162 on_click: impl Fn() + 'static,
163 config: IconButtonConfig,
164) -> View {
165 let th = theme();
166 let is_enabled = config.enabled;
167 let sz = config
168 .container_size
169 .unwrap_or(IconButtonDefaults::FILLED_CONTAINER_SIZE);
170 let bg = config.colors.container(is_enabled);
171 let content_color = config.colors.content(is_enabled);
172 icon_button_render(
173 icon,
174 on_click,
175 &config,
176 sz,
177 Some(bg),
178 None,
179 StateColors {
180 default: Color::TRANSPARENT,
181 hovered: content_color.with_alpha_f32(0.08),
182 focused: content_color.with_alpha_f32(0.12),
183 pressed: content_color.with_alpha_f32(0.12),
184 dragged: content_color.with_alpha_f32(0.12),
185 disabled: th.on_surface.with_alpha_f32(0.12),
186 },
187 )
188}
189
190pub fn OutlinedIconButton(
192 icon: View,
193 on_click: impl Fn() + 'static,
194 config: IconButtonConfig,
195) -> View {
196 let th = theme();
197 let sz = config
198 .container_size
199 .unwrap_or(IconButtonDefaults::CONTAINER_SIZE);
200 let border_color = if config.enabled {
201 th.outline
202 } else {
203 th.on_surface.with_alpha_f32(0.12)
204 };
205 icon_button_render(
206 icon,
207 on_click,
208 &config,
209 sz,
210 None,
211 Some((1.0, border_color)),
212 StateColors {
213 default: Color::TRANSPARENT,
214 hovered: th.on_surface.with_alpha_f32(0.08),
215 focused: th.on_surface.with_alpha_f32(0.12),
216 pressed: th.on_surface.with_alpha_f32(0.12),
217 dragged: th.on_surface.with_alpha_f32(0.12),
218 disabled: Color::TRANSPARENT,
219 },
220 )
221}