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