tessera_ui_basic_components/
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    ripple_state::RippleState,
9    shape_def::Shape,
10    surface::{SurfaceArgsBuilder, surface},
11};
12
13/// Arguments for the `button` component.
14#[derive(Builder, Clone)]
15#[builder(pattern = "owned")]
16pub struct ButtonArgs {
17    /// The fill color of the button (RGBA).
18    #[builder(default = "Color::new(0.2, 0.5, 0.8, 1.0)")]
19    pub color: Color,
20    /// The hover color of the button (RGBA). If None, no hover effect is applied.
21    #[builder(default)]
22    pub hover_color: Option<Color>,
23    /// The shape of the button.
24    #[builder(default = "Shape::RoundedRectangle { corner_radius: 25.0, g2_k_value: 3.0 }")]
25    pub shape: Shape,
26    /// The padding of the button.
27    #[builder(default = "Dp(12.0)")]
28    pub padding: Dp,
29    /// Optional explicit width behavior for the button.
30    #[builder(default, setter(strip_option))]
31    pub width: Option<DimensionValue>,
32    /// Optional explicit height behavior for the button.
33    #[builder(default, setter(strip_option))]
34    pub height: Option<DimensionValue>,
35    /// The click callback function
36    pub on_click: Arc<dyn Fn() + Send + Sync>,
37    /// The ripple color (RGB) for the button.
38    #[builder(default = "Color::from_rgb(1.0, 1.0, 1.0)")]
39    pub ripple_color: Color,
40    /// Width of the border. If > 0, an outline will be drawn.
41    #[builder(default = "0.0")]
42    pub border_width: f32,
43    /// Optional color for the border (RGBA). If None and border_width > 0, `color` will be used.
44    #[builder(default)]
45    pub border_color: Option<Color>,
46}
47
48impl std::fmt::Debug for ButtonArgs {
49    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
50        f.debug_struct("ButtonArgs")
51            .field("color", &self.color)
52            .field("hover_color", &self.hover_color)
53            .field("shape", &self.shape)
54            .field("padding", &self.padding)
55            .field("width", &self.width)
56            .field("height", &self.height)
57            .field("on_click", &"<callback>")
58            .field("ripple_color", &self.ripple_color)
59            .field("border_width", &self.border_width)
60            .field("border_color", &self.border_color)
61            .finish()
62    }
63}
64
65impl Default for ButtonArgs {
66    fn default() -> Self {
67        ButtonArgsBuilder::default()
68            .on_click(Arc::new(|| {}))
69            .build()
70            .unwrap()
71    }
72}
73
74/// Interactive button component that wraps custom child components with ripple effect.
75///
76/// # Example
77/// ```
78/// use tessera_ui_basic_components::button::{button, ButtonArgsBuilder};
79/// use tessera_ui_basic_components::text::{text, TextArgsBuilder};
80/// use tessera_ui_basic_components::ripple_state::RippleState;
81/// use tessera_ui::{Dp, Color};
82/// use std::sync::Arc;
83///
84/// let ripple_state = Arc::new(RippleState::new());
85/// let args = ButtonArgsBuilder::default()
86///     .color(Color::new(0.1, 0.7, 0.3, 1.0)) // Green button
87///     .padding(Dp(16.0))
88///     .on_click(Arc::new(|| println!("Button clicked!")))
89///     .build()
90///     .unwrap();
91///
92/// button(args, ripple_state, || {
93///     text(TextArgsBuilder::default()
94///         .text("Click me!".to_string())
95///         .color(Color::from_rgb_u8(255, 255, 255))
96///         .build()
97///         .unwrap());
98/// });
99/// ```
100#[tessera]
101pub fn button(args: impl Into<ButtonArgs>, ripple_state: Arc<RippleState>, child: impl FnOnce()) {
102    let button_args: ButtonArgs = args.into();
103
104    // Create interactive surface for button
105    surface(create_surface_args(&button_args), Some(ripple_state), child);
106}
107
108/// Create surface arguments based on button configuration
109fn create_surface_args(args: &ButtonArgs) -> crate::surface::SurfaceArgs {
110    let mut builder = SurfaceArgsBuilder::default();
111
112    // Set width if available
113    if let Some(width) = args.width {
114        builder = builder.width(width);
115    }
116
117    // Set height if available
118    if let Some(height) = args.height {
119        builder = builder.height(height);
120    }
121
122    builder
123        .color(args.color)
124        .hover_color(args.hover_color)
125        .shape(args.shape)
126        .padding(args.padding)
127        .border_width(args.border_width)
128        .border_color(args.border_color)
129        .ripple_color(args.ripple_color)
130        .on_click(Some(args.on_click.clone()))
131        .build()
132        .unwrap()
133}
134
135/// Convenience constructors for common button styles
136impl ButtonArgs {
137    /// Create a primary button with default blue styling
138    pub fn primary(on_click: Arc<dyn Fn() + Send + Sync>) -> Self {
139        ButtonArgsBuilder::default()
140            .color(Color::new(0.2, 0.5, 0.8, 1.0)) // Blue
141            .on_click(on_click)
142            .build()
143            .unwrap()
144    }
145
146    /// Create a secondary button with gray styling
147    pub fn secondary(on_click: Arc<dyn Fn() + Send + Sync>) -> Self {
148        ButtonArgsBuilder::default()
149            .color(Color::new(0.6, 0.6, 0.6, 1.0)) // Gray
150            .on_click(on_click)
151            .build()
152            .unwrap()
153    }
154
155    /// Create a success button with green styling
156    pub fn success(on_click: Arc<dyn Fn() + Send + Sync>) -> Self {
157        ButtonArgsBuilder::default()
158            .color(Color::new(0.1, 0.7, 0.3, 1.0)) // Green
159            .on_click(on_click)
160            .build()
161            .unwrap()
162    }
163
164    /// Create a danger button with red styling
165    pub fn danger(on_click: Arc<dyn Fn() + Send + Sync>) -> Self {
166        ButtonArgsBuilder::default()
167            .color(Color::new(0.8, 0.2, 0.2, 1.0)) // Red
168            .on_click(on_click)
169            .build()
170            .unwrap()
171    }
172}
173
174/// Builder methods for fluent API
175impl ButtonArgs {
176    pub fn with_color(mut self, color: Color) -> Self {
177        self.color = color;
178        self
179    }
180
181    pub fn with_hover_color(mut self, hover_color: Color) -> Self {
182        self.hover_color = Some(hover_color);
183        self
184    }
185
186    pub fn with_padding(mut self, padding: Dp) -> Self {
187        self.padding = padding;
188        self
189    }
190
191    pub fn with_shape(mut self, shape: Shape) -> Self {
192        self.shape = shape;
193        self
194    }
195
196    pub fn with_width(mut self, width: DimensionValue) -> Self {
197        self.width = Some(width);
198        self
199    }
200
201    pub fn with_height(mut self, height: DimensionValue) -> Self {
202        self.height = Some(height);
203        self
204    }
205
206    pub fn with_ripple_color(mut self, ripple_color: Color) -> Self {
207        self.ripple_color = ripple_color;
208        self
209    }
210
211    pub fn with_border(mut self, width: f32, color: Option<Color>) -> Self {
212        self.border_width = width;
213        self.border_color = color;
214        self
215    }
216}