Skip to main content

tessera_components/
glass_button.rs

1//! An interactive button with a glassmorphic background.
2//!
3//! ## Usage
4//!
5//! Use for visually distinctive actions in layered or modern UIs.
6use tessera_ui::{Callback, Color, Dp, Modifier, RenderSlot, layout::layout, tessera};
7
8use crate::{
9    button::ButtonDefaults,
10    fluid_glass::{GlassAppearance, GlassBorder, GlassMaterial, fluid_glass},
11    modifier::ModifierExt as _,
12    shape_def::Shape,
13};
14
15impl GlassButtonBuilder {
16    /// Applies the primary glass button preset.
17    pub fn primary(&self) -> Self {
18        self.tint_color(Color::new(0.2, 0.5, 0.8, 0.2))
19            .border(GlassBorder::new(Dp(1.0).into()))
20    }
21
22    /// Applies the secondary glass button preset.
23    pub fn secondary(&self) -> Self {
24        self.tint_color(Color::new(0.6, 0.6, 0.6, 0.2))
25            .border(GlassBorder::new(Dp(1.0).into()))
26    }
27
28    /// Applies the success glass button preset.
29    pub fn success(&self) -> Self {
30        self.tint_color(Color::new(0.1, 0.7, 0.3, 0.2))
31            .border(GlassBorder::new(Dp(1.0).into()))
32    }
33
34    /// Applies the danger glass button preset.
35    pub fn danger(&self) -> Self {
36        self.tint_color(Color::new(0.8, 0.2, 0.2, 0.2))
37            .border(GlassBorder::new(Dp(1.0).into()))
38    }
39}
40
41/// # glass_button
42///
43/// Renders an interactive button with a customizable glass effect and ripple
44/// animation.
45///
46/// ## Usage
47///
48/// Use as a primary action button where a modern, layered look is desired.
49///
50/// ## Parameters
51///
52/// - `modifier` — modifier chain applied to the button subtree.
53/// - `on_click` — optional click callback.
54/// - `padding` — optional inner padding.
55/// - `tint_color` — optional glass tint color.
56/// - `shape` — optional shape override.
57/// - `material` — backdrop treatment, defaults to [`GlassMaterial::Regular`].
58/// - `appearance` — optional override for the inherited light or dark appearance.
59/// - `interactive` — optional override for optical press feedback.
60/// - `blur_radius` — optional blur radius.
61/// - `noise_amount` — optional noise amount.
62/// - `noise_scale` — optional noise scale.
63/// - `time` — optional animated time input.
64/// - `contrast` — optional contrast override.
65/// - `border` — optional glass border override.
66/// - `accessibility_label` — optional accessibility label.
67/// - `accessibility_description` — optional accessibility description.
68/// - `accessibility_focusable` — optional accessibility focusable flag.
69/// - `child` — optional child render slot.
70///
71/// ## Examples
72///
73/// ```
74/// use tessera_components::{glass_button::glass_button, text::text};
75/// use tessera_ui::tessera;
76///
77/// #[tessera]
78/// fn component() {
79///     glass_button()
80///         .primary()
81///         .on_click(|| println!("Button clicked!"))
82///         .child(|| {
83///             text("Click Me");
84///         });
85/// }
86///
87/// # tessera_ui::testing::layout_test(component).run();
88/// ```
89#[tessera]
90pub fn glass_button(
91    modifier: Option<Modifier>,
92    on_click: Option<Callback>,
93    padding: Option<Dp>,
94    tint_color: Option<Color>,
95    shape: Option<Shape>,
96    material: Option<GlassMaterial>,
97    appearance: Option<GlassAppearance>,
98    interactive: Option<bool>,
99    blur_radius: Option<Dp>,
100    noise_amount: Option<f32>,
101    noise_scale: Option<f32>,
102    time: Option<f32>,
103    contrast: Option<f32>,
104    border: Option<GlassBorder>,
105    #[prop(into)] accessibility_label: Option<String>,
106    #[prop(into)] accessibility_description: Option<String>,
107    accessibility_focusable: Option<bool>,
108    child: Option<RenderSlot>,
109) {
110    let modifier = modifier.unwrap_or_default();
111    let padding = padding.unwrap_or(Dp(12.0));
112    let noise_amount = noise_amount.unwrap_or(0.0);
113    let noise_scale = noise_scale.unwrap_or(1.0);
114    let time = time.unwrap_or(0.0);
115    let accessibility_focusable = accessibility_focusable.unwrap_or(false);
116    let child = child.unwrap_or_else(RenderSlot::empty);
117    let outer_modifier =
118        modifier.default_min_size(ButtonDefaults::MIN_WIDTH, ButtonDefaults::MIN_HEIGHT);
119
120    layout().modifier(outer_modifier).child(move || {
121        fluid_glass()
122            .modifier(Modifier::new())
123            .tint_color_optional(tint_color)
124            .shape_optional(shape)
125            .material_optional(material)
126            .appearance_optional(appearance)
127            .interactive_optional(interactive)
128            .blur_radius_optional(blur_radius)
129            .noise_amount(noise_amount)
130            .noise_scale(noise_scale)
131            .time(time)
132            .padding(padding)
133            .contrast_optional(contrast)
134            .on_click_optional(on_click)
135            .border_optional(border)
136            .accessibility_label_optional(accessibility_label.clone())
137            .accessibility_description_optional(accessibility_description.clone())
138            .accessibility_focusable_optional(accessibility_focusable.then_some(true))
139            .child_shared(child);
140    });
141}