tessera_ui_basic_components/
glass_button.rs

1//! Provides an interactive button component with a glassmorphic (glass-like) background and ripple effect.
2//!
3//! This module defines `glass_button`, a highly customizable button for modern UI applications.
4//! It combines advanced glass visual effects with interactive feedback, supporting primary, secondary,
5//! success, and danger styles. Typical use cases include visually distinctive action buttons in
6//! glassmorphic or layered interfaces, where both aesthetics and user feedback are important.
7//!
8//! The component is suitable for dashboards, dialogs, toolbars, and any context requiring
9//! a visually appealing, interactive button with a translucent, layered look.
10use std::sync::Arc;
11
12use derive_builder::Builder;
13use tessera_ui::{Color, DimensionValue, Dp, Px};
14use tessera_ui_macros::tessera;
15
16use crate::{
17    fluid_glass::{FluidGlassArgsBuilder, GlassBorder, fluid_glass},
18    ripple_state::RippleState,
19    shape_def::Shape,
20};
21
22/// Arguments for the `glass_button` component.
23#[derive(Builder, Clone, Default)]
24#[builder(pattern = "owned", setter(into, strip_option), default)]
25pub struct GlassButtonArgs {
26    /// The click callback function
27    #[builder(setter(strip_option, into = false))]
28    pub on_click: Option<Arc<dyn Fn() + Send + Sync>>,
29
30    // Ripple effect properties
31    /// The ripple color (RGB) for the button.
32    #[builder(default = "Color::from_rgb(1.0, 1.0, 1.0)")]
33    pub ripple_color: Color,
34
35    // Layout properties
36    /// The padding of the button.
37    #[builder(default = "Dp(12.0)")]
38    pub padding: Dp,
39    /// Optional explicit width behavior for the button.
40    #[builder(default, setter(strip_option))]
41    pub width: Option<DimensionValue>,
42    /// Optional explicit height behavior for the button.
43    #[builder(default, setter(strip_option))]
44    pub height: Option<DimensionValue>,
45
46    // Glass visual properties
47    #[builder(default = "Color::new(0.5, 0.5, 0.5, 0.1)")]
48    pub tint_color: Color,
49    #[builder(default = "Shape::RoundedRectangle { corner_radius: 25.0, g2_k_value: 3.0 }")]
50    pub shape: Shape,
51    #[builder(default = "0.0")]
52    pub blur_radius: f32,
53    #[builder(default = "25.0")]
54    pub dispersion_height: f32,
55    #[builder(default = "1.2")]
56    pub chroma_multiplier: f32,
57    #[builder(default = "24.0")]
58    pub refraction_height: f32,
59    #[builder(default = "32.0")]
60    pub refraction_amount: f32,
61    #[builder(default = "0.02")]
62    pub noise_amount: f32,
63    #[builder(default = "1.0")]
64    pub noise_scale: f32,
65    #[builder(default = "0.0")]
66    pub time: f32,
67    #[builder(default, setter(strip_option))]
68    pub contrast: Option<f32>,
69    #[builder(default, setter(strip_option))]
70    pub border: Option<GlassBorder>,
71}
72
73/// Convenience constructors for common glass button styles
74impl GlassButtonArgs {
75    /// Create a primary glass button with default blue tint
76    pub fn primary(on_click: Arc<dyn Fn() + Send + Sync>) -> Self {
77        GlassButtonArgsBuilder::default()
78            .on_click(on_click)
79            .tint_color(Color::new(0.2, 0.5, 0.8, 0.2)) // Blue tint
80            .border(GlassBorder::new(Px(1)))
81            .build()
82            .unwrap()
83    }
84
85    /// Create a secondary glass button with gray tint
86    pub fn secondary(on_click: Arc<dyn Fn() + Send + Sync>) -> Self {
87        GlassButtonArgsBuilder::default()
88            .on_click(on_click)
89            .tint_color(Color::new(0.6, 0.6, 0.6, 0.2)) // Gray tint
90            .border(GlassBorder::new(Px(1)))
91            .build()
92            .unwrap()
93    }
94
95    /// Create a success glass button with green tint
96    pub fn success(on_click: Arc<dyn Fn() + Send + Sync>) -> Self {
97        GlassButtonArgsBuilder::default()
98            .on_click(on_click)
99            .tint_color(Color::new(0.1, 0.7, 0.3, 0.2)) // Green tint
100            .border(GlassBorder::new(Px(1)))
101            .build()
102            .unwrap()
103    }
104
105    /// Create a danger glass button with red tint
106    pub fn danger(on_click: Arc<dyn Fn() + Send + Sync>) -> Self {
107        GlassButtonArgsBuilder::default()
108            .on_click(on_click)
109            .tint_color(Color::new(0.8, 0.2, 0.2, 0.2)) // Red tint
110            .border(GlassBorder::new(Px(1)))
111            .build()
112            .unwrap()
113    }
114}
115
116/// An interactive button with a fluid, glass-like background and a ripple effect on click.
117///
118/// This component combines a `fluid_glass` background for advanced visual effects with a
119/// ripple animation to provide clear user feedback. It is highly customizable, allowing
120/// control over the glass appearance, layout, and interaction behavior.
121///
122/// # Arguments
123///
124/// * `args` - A struct that provides detailed configuration for the button's appearance
125///   and behavior. See [`GlassButtonArgs`] for more details.
126/// * `ripple_state` - The state manager for the ripple animation. It should be created
127///   once and shared across recompositions.
128/// * `child` - A closure that defines the content displayed inside the button, such as text
129///   or an icon.
130///
131/// # Example
132///
133/// ```
134/// use std::sync::Arc;
135/// use tessera_ui::Color;
136/// use tessera_ui_basic_components::{
137///     glass_button::{glass_button, GlassButtonArgs},
138///     ripple_state::RippleState,
139///     text::text,
140/// };
141///
142/// let ripple_state = Arc::new(RippleState::new());
143/// glass_button(
144///     GlassButtonArgs {
145///         on_click: Some(Arc::new(|| { /* Handle click */ })),
146///         tint_color: Color::new(0.3, 0.2, 0.5, 0.4),
147///         ..Default::default()
148///     },
149///     ripple_state,
150///     || text("Click Me".to_string()),
151/// );
152/// ```
153/// An interactive button with a fluid glass background and a ripple effect.
154///
155/// This component is a composite of `fluid_glass` for the visuals and a transparent
156/// `surface` for interaction handling and the ripple animation.
157#[tessera]
158pub fn glass_button(
159    args: impl Into<GlassButtonArgs>,
160    ripple_state: Arc<RippleState>,
161    child: impl FnOnce() + Send + Sync + 'static,
162) {
163    let args: GlassButtonArgs = args.into();
164
165    let mut glass_args_builder = FluidGlassArgsBuilder::default();
166    if let Some((progress, center)) = ripple_state.get_animation_progress() {
167        let ripple_alpha = (1.0 - progress) * 0.3; // Fade out
168        glass_args_builder = glass_args_builder
169            .ripple_center(center)
170            .ripple_radius(progress)
171            .ripple_alpha(ripple_alpha)
172            .ripple_strength(progress);
173    }
174
175    if let Some(width) = args.width {
176        glass_args_builder = glass_args_builder.width(width);
177    }
178    if let Some(height) = args.height {
179        glass_args_builder = glass_args_builder.height(height);
180    }
181    if let Some(contrast) = args.contrast {
182        glass_args_builder = glass_args_builder.contrast(contrast);
183    }
184
185    let mut glass_args = glass_args_builder
186        .tint_color(args.tint_color)
187        .shape(args.shape)
188        .blur_radius(args.blur_radius)
189        .dispersion_height(args.dispersion_height)
190        .chroma_multiplier(args.chroma_multiplier)
191        .refraction_height(args.refraction_height)
192        .refraction_amount(args.refraction_amount)
193        .noise_amount(args.noise_amount)
194        .noise_scale(args.noise_scale)
195        .time(args.time)
196        .padding(args.padding);
197
198    if let Some(on_click) = args.on_click {
199        glass_args = glass_args.on_click(on_click);
200    }
201
202    if let Some(border) = args.border {
203        glass_args = glass_args.border(border);
204    }
205
206    let glass_args = glass_args.build().unwrap();
207
208    fluid_glass(glass_args, Some(ripple_state), child);
209}