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            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
127/// M3 Filled Icon Button - icon button with a filled container background.
128pub 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
157/// M3 Filled Tonal Icon Button - icon button with a secondary container background.
158pub 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
187/// M3 Outlined Icon Button - icon button with a transparent background and border.
188pub 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}