Skip to main content

repose_material/material3/
icon_button.rs

1#![allow(non_snake_case)]
2
3use std::rc::Rc;
4
5use repose_core::*;
6use repose_ui::{Box, ViewExt};
7
8use super::*;
9
10/// Color slots for icon buttons.
11#[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/// Configuration for [`IconButton`], [`FilledIconButton`], [`FilledTonalIconButton`], and [`OutlinedIconButton`].
37#[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
109/// M3 Icon Button - a tappable circular container for an icon.
110pub 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
133/// M3 Filled Icon Button - icon button with a filled container background.
134pub 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
164/// M3 Filled Tonal Icon Button - icon button with a secondary container background.
165pub 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
195/// M3 Outlined Icon Button - icon button with a transparent background and border.
196pub 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}