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