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::{
7    Box,
8    ViewExt,
9};
10
11use super::*;
12
13/// Color slots for icon buttons.
14#[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/// Configuration for [`IconButton`], [`FilledIconButton`], [`FilledTonalIconButton`], and [`OutlinedIconButton`].
40#[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
107/// M3 Icon Button - a tappable circular container for an icon.
108pub 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
130/// M3 Filled Icon Button - icon button with a filled container background.
131pub 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
160/// M3 Filled Tonal Icon Button - icon button with a secondary container background.
161pub 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
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            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}