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 disabled: Color::TRANSPARENT,
125 },
126 )
127}
128
129pub fn FilledIconButton(
131 icon: View,
132 on_click: impl Fn() + 'static,
133 config: IconButtonConfig,
134) -> View {
135 let th = theme();
136 let is_enabled = config.enabled;
137 let sz = config
138 .container_size
139 .unwrap_or(IconButtonDefaults::FILLED_CONTAINER_SIZE);
140 let bg = config.colors.container(is_enabled);
141 let content_color = config.colors.content(is_enabled);
142 icon_button_render(
143 icon,
144 on_click,
145 &config,
146 sz,
147 Some(bg),
148 None,
149 StateColors {
150 default: Color::TRANSPARENT,
151 hovered: content_color.with_alpha_f32(0.08),
152 pressed: content_color.with_alpha_f32(0.12),
153 disabled: th.on_surface.with_alpha_f32(0.12),
154 },
155 )
156}
157
158pub fn FilledTonalIconButton(
160 icon: View,
161 on_click: impl Fn() + 'static,
162 config: IconButtonConfig,
163) -> View {
164 let th = theme();
165 let is_enabled = config.enabled;
166 let sz = config
167 .container_size
168 .unwrap_or(IconButtonDefaults::FILLED_CONTAINER_SIZE);
169 let bg = config.colors.container(is_enabled);
170 let content_color = config.colors.content(is_enabled);
171 icon_button_render(
172 icon,
173 on_click,
174 &config,
175 sz,
176 Some(bg),
177 None,
178 StateColors {
179 default: Color::TRANSPARENT,
180 hovered: content_color.with_alpha_f32(0.08),
181 pressed: 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 disabled: Color::TRANSPARENT,
214 },
215 )
216}