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            disabled: Color::TRANSPARENT,
125        },
126    )
127}
128
129/// M3 Filled Icon Button - icon button with a filled container background.
130pub 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
158/// M3 Filled Tonal Icon Button - icon button with a secondary container background.
159pub 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
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            disabled: Color::TRANSPARENT,
214        },
215    )
216}