tessera_ui_basic_components/
checkmark.rs

1use derive_builder::Builder;
2use tessera_ui::{Color, ComputedData, Dp, Px};
3use tessera_ui_macros::tessera;
4
5use crate::pipelines::CheckmarkCommand;
6
7/// Arguments for the `checkmark` component.
8#[derive(Builder, Clone)]
9#[builder(pattern = "owned")]
10pub struct CheckmarkArgs {
11    /// Color of the checkmark stroke
12    #[builder(default = "Color::new(0.0, 0.6, 0.0, 1.0)")]
13    pub color: Color,
14
15    /// Width of the checkmark stroke in pixels
16    #[builder(default = "5.0")]
17    pub stroke_width: f32,
18
19    /// Animation progress from 0.0 (not drawn) to 1.0 (fully drawn)
20    #[builder(default = "1.0")]
21    pub progress: f32,
22
23    /// Padding around the checkmark within its bounds
24    #[builder(default = "[2.0, 2.0]")]
25    pub padding: [f32; 2], // [horizontal, vertical]
26
27    /// Size of the checkmark area
28    #[builder(default = "Dp(20.0)")]
29    pub size: Dp,
30}
31
32impl std::fmt::Debug for CheckmarkArgs {
33    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
34        f.debug_struct("CheckmarkArgs")
35            .field("color", &self.color)
36            .field("stroke_width", &self.stroke_width)
37            .field("progress", &self.progress)
38            .field("padding", &self.padding)
39            .field("size", &self.size)
40            .finish()
41    }
42}
43
44impl Default for CheckmarkArgs {
45    fn default() -> Self {
46        CheckmarkArgsBuilder::default().build().unwrap()
47    }
48}
49
50/// A checkmark component that renders an animated checkmark using a custom GPU pipeline.
51///
52/// This component provides a robust alternative to emoji-based checkmarks, with support
53/// for linear drawing animation and customizable appearance.
54#[tessera]
55pub fn checkmark(args: impl Into<CheckmarkArgs>) {
56    let args: CheckmarkArgs = args.into();
57
58    let size_px = args.size.to_px();
59
60    // Create the checkmark command
61    let command = CheckmarkCommand {
62        color: args.color,
63        stroke_width: args.stroke_width,
64        progress: args.progress,
65        padding: args.padding,
66    };
67
68    // Measure the component and push the draw command within the measure function
69    measure(Box::new(move |input| {
70        // Push the draw command to the current node's metadata
71        if let Some(mut metadata) = input.metadatas.get_mut(&input.current_node_id) {
72            metadata.push_draw_command(command.clone());
73        }
74
75        Ok(ComputedData {
76            width: Px::new(size_px.to_f32() as i32),
77            height: Px::new(size_px.to_f32() as i32),
78        })
79    }));
80}