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