tessera_ui_basic_components/
glass_button.rs

1use std::sync::Arc;
2
3use derive_builder::Builder;
4use tessera_ui::{Color, DimensionValue, Dp};
5use tessera_ui_macros::tessera;
6
7use crate::{
8    fluid_glass::{FluidGlassArgsBuilder, GlassBorder, fluid_glass},
9    ripple_state::RippleState,
10    shape_def::Shape,
11};
12
13/// Arguments for the `glass_button` component.
14#[derive(Builder, Clone, Default)]
15#[builder(pattern = "owned", setter(into, strip_option), default)]
16pub struct GlassButtonArgs {
17    /// The click callback function
18    #[builder(setter(strip_option, into = false))]
19    pub on_click: Option<Arc<dyn Fn() + Send + Sync>>,
20
21    // Ripple effect properties
22    /// The ripple color (RGB) for the button.
23    #[builder(default = "Color::from_rgb(1.0, 1.0, 1.0)")]
24    pub ripple_color: Color,
25
26    // Layout properties
27    /// The padding of the button.
28    #[builder(default = "Dp(12.0)")]
29    pub padding: Dp,
30    /// Optional explicit width behavior for the button.
31    #[builder(default, setter(strip_option))]
32    pub width: Option<DimensionValue>,
33    /// Optional explicit height behavior for the button.
34    #[builder(default, setter(strip_option))]
35    pub height: Option<DimensionValue>,
36
37    // Glass visual properties
38    #[builder(default = "Color::new(0.5, 0.5, 0.5, 0.1)")]
39    pub tint_color: Color,
40    #[builder(default = "Shape::RoundedRectangle { corner_radius: 25.0, g2_k_value: 3.0 }")]
41    pub shape: Shape,
42    #[builder(default = "0.0")]
43    pub blur_radius: f32,
44    #[builder(default = "25.0")]
45    pub dispersion_height: f32,
46    #[builder(default = "1.2")]
47    pub chroma_multiplier: f32,
48    #[builder(default = "24.0")]
49    pub refraction_height: f32,
50    #[builder(default = "32.0")]
51    pub refraction_amount: f32,
52    #[builder(default = "0.02")]
53    pub noise_amount: f32,
54    #[builder(default = "1.0")]
55    pub noise_scale: f32,
56    #[builder(default = "0.0")]
57    pub time: f32,
58    #[builder(default, setter(strip_option))]
59    pub contrast: Option<f32>,
60    #[builder(default, setter(strip_option))]
61    pub border: Option<GlassBorder>,
62}
63
64/// Convenience constructors for common glass button styles
65impl GlassButtonArgs {
66    /// Create a primary glass button with default blue tint
67    pub fn primary(on_click: Arc<dyn Fn() + Send + Sync>) -> Self {
68        GlassButtonArgsBuilder::default()
69            .on_click(on_click)
70            .tint_color(Color::new(0.2, 0.5, 0.8, 0.2)) // Blue tint
71            .border(GlassBorder::new(Dp(2.0), Color::new(0.2, 0.5, 0.8, 0.5)))
72            .build()
73            .unwrap()
74    }
75
76    /// Create a secondary glass button with gray tint
77    pub fn secondary(on_click: Arc<dyn Fn() + Send + Sync>) -> Self {
78        GlassButtonArgsBuilder::default()
79            .on_click(on_click)
80            .tint_color(Color::new(0.6, 0.6, 0.6, 0.2)) // Gray tint
81            .border(GlassBorder::new(Dp(2.0), Color::new(0.6, 0.6, 0.6, 0.5)))
82            .build()
83            .unwrap()
84    }
85
86    /// Create a success glass button with green tint
87    pub fn success(on_click: Arc<dyn Fn() + Send + Sync>) -> Self {
88        GlassButtonArgsBuilder::default()
89            .on_click(on_click)
90            .tint_color(Color::new(0.1, 0.7, 0.3, 0.2)) // Green tint
91            .border(GlassBorder::new(Dp(2.0), Color::new(0.1, 0.7, 0.3, 0.5)))
92            .build()
93            .unwrap()
94    }
95
96    /// Create a danger glass button with red tint
97    pub fn danger(on_click: Arc<dyn Fn() + Send + Sync>) -> Self {
98        GlassButtonArgsBuilder::default()
99            .on_click(on_click)
100            .tint_color(Color::new(0.8, 0.2, 0.2, 0.2)) // Red tint
101            .border(GlassBorder::new(Dp(2.0), Color::new(0.8, 0.2, 0.2, 0.5)))
102            .build()
103            .unwrap()
104    }
105}
106
107/// An interactive button with a fluid glass background and a ripple effect.
108///
109/// This component is a composite of `fluid_glass` for the visuals and a transparent
110/// `surface` for interaction handling and the ripple animation.
111#[tessera]
112pub fn glass_button(
113    args: impl Into<GlassButtonArgs>,
114    ripple_state: Arc<RippleState>,
115    child: impl FnOnce() + Send + Sync + 'static,
116) {
117    let args: GlassButtonArgs = args.into();
118
119    let mut glass_args_builder = FluidGlassArgsBuilder::default();
120    if let Some((progress, center)) = ripple_state.get_animation_progress() {
121        let ripple_alpha = (1.0 - progress) * 0.3; // Fade out
122        glass_args_builder = glass_args_builder
123            .ripple_center(center)
124            .ripple_radius(progress)
125            .ripple_alpha(ripple_alpha)
126            .ripple_strength(progress);
127    }
128
129    if let Some(width) = args.width {
130        glass_args_builder = glass_args_builder.width(width);
131    }
132    if let Some(height) = args.height {
133        glass_args_builder = glass_args_builder.height(height);
134    }
135    if let Some(contrast) = args.contrast {
136        glass_args_builder = glass_args_builder.contrast(contrast);
137    }
138
139    let mut glass_args = glass_args_builder
140        .tint_color(args.tint_color)
141        .shape(args.shape)
142        .blur_radius(args.blur_radius)
143        .dispersion_height(args.dispersion_height)
144        .chroma_multiplier(args.chroma_multiplier)
145        .refraction_height(args.refraction_height)
146        .refraction_amount(args.refraction_amount)
147        .noise_amount(args.noise_amount)
148        .noise_scale(args.noise_scale)
149        .time(args.time)
150        .padding(args.padding);
151
152    if let Some(on_click) = args.on_click {
153        glass_args = glass_args.on_click(on_click);
154    }
155
156    if let Some(border) = args.border {
157        glass_args = glass_args.border(border);
158    }
159
160    let glass_args = glass_args.build().unwrap();
161
162    fluid_glass(glass_args, Some(ripple_state), child);
163}