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    if is_enabled {
98        m = m.clickable().on_click(on_click);
99    }
100
101    Box(m).child(icon)
102}
103
104/// M3 Icon Button - a tappable circular container for an icon.
105pub 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
128/// M3 Filled Icon Button - icon button with a filled container background.
129pub 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
159/// M3 Filled Tonal Icon Button - icon button with a secondary container background.
160pub 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
190/// M3 Outlined Icon Button - icon button with a transparent background and border.
191pub 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}