tessera_ui_basic_components/
button.rs1use std::sync::Arc;
7
8use derive_builder::Builder;
9use tessera_ui::{Color, DimensionValue, Dp, accesskit::Role, tessera};
10
11use crate::{
12 ShadowProps,
13 material_color::global_material_scheme,
14 ripple_state::RippleState,
15 shape_def::Shape,
16 surface::{SurfaceArgsBuilder, surface},
17};
18
19#[derive(Builder, Clone)]
21#[builder(pattern = "owned")]
22pub struct ButtonArgs {
23 #[builder(default = "crate::material_color::global_material_scheme().primary")]
25 pub color: Color,
26 #[builder(
28 default = "Some(crate::material_color::blend_over(crate::material_color::global_material_scheme().primary, crate::material_color::global_material_scheme().on_primary, 0.08))"
29 )]
30 pub hover_color: Option<Color>,
31 #[builder(default = "Shape::rounded_rectangle(Dp(20.0))")]
33 pub shape: Shape,
34 #[builder(default = "Dp(10.0)")]
36 pub padding: Dp,
37 #[builder(default = "DimensionValue::WRAP", setter(into))]
39 pub width: DimensionValue,
40 #[builder(default = "DimensionValue::WRAP", setter(into))]
42 pub height: DimensionValue,
43 #[builder(default, setter(strip_option))]
45 pub on_click: Option<Arc<dyn Fn() + Send + Sync>>,
46 #[builder(
48 default = "crate::material_color::global_material_scheme().on_primary.with_alpha(0.12)"
49 )]
50 pub ripple_color: Color,
51 #[builder(default = "Dp(0.0)")]
53 pub border_width: Dp,
54 #[builder(default)]
56 pub border_color: Option<Color>,
57 #[builder(default, setter(strip_option))]
59 pub shadow: Option<ShadowProps>,
60 #[builder(default, setter(strip_option, into))]
62 pub accessibility_label: Option<String>,
63 #[builder(default, setter(strip_option, into))]
65 pub accessibility_description: Option<String>,
66}
67
68impl Default for ButtonArgs {
69 fn default() -> Self {
70 ButtonArgsBuilder::default()
71 .on_click(Arc::new(|| {}))
72 .build()
73 .expect("ButtonArgsBuilder default build should succeed")
74 }
75}
76
77#[tessera]
113pub fn button(args: impl Into<ButtonArgs>, ripple_state: RippleState, child: impl FnOnce()) {
114 let button_args: ButtonArgs = args.into();
115
116 surface(create_surface_args(&button_args), Some(ripple_state), child);
118}
119
120fn create_surface_args(args: &ButtonArgs) -> crate::surface::SurfaceArgs {
122 let style = if args.border_width.to_pixels_f32() > 0.0 {
123 crate::surface::SurfaceStyle::FilledOutlined {
124 fill_color: args.color,
125 border_color: args.border_color.unwrap_or(args.color),
126 border_width: args.border_width,
127 }
128 } else {
129 crate::surface::SurfaceStyle::Filled { color: args.color }
130 };
131
132 let hover_style = if let Some(hover_color) = args.hover_color {
133 let style = if args.border_width.to_pixels_f32() > 0.0 {
134 crate::surface::SurfaceStyle::FilledOutlined {
135 fill_color: hover_color,
136 border_color: args.border_color.unwrap_or(hover_color),
137 border_width: args.border_width,
138 }
139 } else {
140 crate::surface::SurfaceStyle::Filled { color: hover_color }
141 };
142 Some(style)
143 } else {
144 None
145 };
146
147 let mut builder = SurfaceArgsBuilder::default();
148
149 if let Some(shadow) = args.shadow {
151 builder = builder.shadow(shadow);
152 }
153
154 if let Some(on_click) = args.on_click.clone() {
156 builder = builder.on_click(on_click);
157 }
158
159 if let Some(label) = args.accessibility_label.clone() {
160 builder = builder.accessibility_label(label);
161 }
162
163 if let Some(description) = args.accessibility_description.clone() {
164 builder = builder.accessibility_description(description);
165 }
166
167 builder
168 .style(style)
169 .hover_style(hover_style)
170 .shape(args.shape)
171 .padding(args.padding)
172 .ripple_color(args.ripple_color)
173 .width(args.width)
174 .height(args.height)
175 .accessibility_role(Role::Button)
176 .accessibility_focusable(true)
177 .build()
178 .expect("SurfaceArgsBuilder failed with required button fields set")
179}
180
181impl ButtonArgs {
183 pub fn filled(on_click: Arc<dyn Fn() + Send + Sync>) -> Self {
186 let scheme = global_material_scheme();
187 ButtonArgsBuilder::default()
188 .color(scheme.primary)
189 .hover_color(Some(crate::material_color::blend_over(
190 scheme.primary,
191 scheme.on_primary,
192 0.08,
193 )))
194 .ripple_color(scheme.on_primary.with_alpha(0.12))
195 .on_click(on_click)
196 .build()
197 .expect("ButtonArgsBuilder failed for filled button")
198 }
199
200 pub fn elevated(on_click: Arc<dyn Fn() + Send + Sync>) -> Self {
203 let scheme = global_material_scheme();
204 ButtonArgsBuilder::default()
205 .color(scheme.surface)
206 .hover_color(Some(crate::material_color::blend_over(
207 scheme.surface,
208 scheme.primary,
209 0.08,
210 )))
211 .ripple_color(scheme.primary.with_alpha(0.12))
212 .shadow(ShadowProps::default())
213 .on_click(on_click)
214 .build()
215 .expect("ButtonArgsBuilder failed for elevated button")
216 }
217
218 pub fn tonal(on_click: Arc<dyn Fn() + Send + Sync>) -> Self {
221 let scheme = global_material_scheme();
222 ButtonArgsBuilder::default()
223 .color(scheme.secondary_container)
224 .hover_color(Some(crate::material_color::blend_over(
225 scheme.secondary_container,
226 scheme.on_secondary_container,
227 0.08,
228 )))
229 .ripple_color(scheme.on_secondary_container.with_alpha(0.12))
230 .on_click(on_click)
231 .build()
232 .expect("ButtonArgsBuilder failed for tonal button")
233 }
234
235 pub fn outlined(on_click: Arc<dyn Fn() + Send + Sync>) -> Self {
238 let scheme = global_material_scheme();
239 ButtonArgsBuilder::default()
240 .color(Color::TRANSPARENT)
241 .hover_color(Some(crate::material_color::blend_over(
242 Color::TRANSPARENT,
243 scheme.primary,
244 0.08,
245 )))
246 .ripple_color(scheme.primary.with_alpha(0.12))
247 .border_width(Dp(1.0))
248 .border_color(Some(scheme.outline))
249 .on_click(on_click)
250 .build()
251 .expect("ButtonArgsBuilder failed for outlined button")
252 }
253
254 pub fn text(on_click: Arc<dyn Fn() + Send + Sync>) -> Self {
257 let scheme = global_material_scheme();
258 ButtonArgsBuilder::default()
259 .color(Color::TRANSPARENT)
260 .hover_color(Some(crate::material_color::blend_over(
261 Color::TRANSPARENT,
262 scheme.primary,
263 0.08,
264 )))
265 .ripple_color(scheme.primary.with_alpha(0.12))
266 .on_click(on_click)
267 .build()
268 .expect("ButtonArgsBuilder failed for text button")
269 }
270}
271
272impl ButtonArgs {
274 pub fn with_color(mut self, color: Color) -> Self {
276 self.color = color;
277 self
278 }
279
280 pub fn with_hover_color(mut self, hover_color: Color) -> Self {
282 self.hover_color = Some(hover_color);
283 self
284 }
285
286 pub fn with_padding(mut self, padding: Dp) -> Self {
288 self.padding = padding;
289 self
290 }
291
292 pub fn with_shape(mut self, shape: Shape) -> Self {
294 self.shape = shape;
295 self
296 }
297
298 pub fn with_width(mut self, width: DimensionValue) -> Self {
300 self.width = width;
301 self
302 }
303
304 pub fn with_height(mut self, height: DimensionValue) -> Self {
306 self.height = height;
307 self
308 }
309
310 pub fn with_ripple_color(mut self, ripple_color: Color) -> Self {
312 self.ripple_color = ripple_color;
313 self
314 }
315
316 pub fn with_border(mut self, width: Dp, color: Option<Color>) -> Self {
318 self.border_width = width;
319 self.border_color = color;
320 self
321 }
322}