tessera_ui_basic_components/
progress.rs

1//! This module provides a customizable linear progress bar component for visualizing task completion.
2//!
3//! The `progress` component displays a horizontal bar with configurable width, height, colors, and shape,
4//! where the filled portion represents the current progress value (from 0.0 to 1.0).
5//! It is suitable for indicating the status of ongoing operations such as loading, uploading, or processing tasks
6//! in user interfaces.
7//!
8//! Typical usage involves specifying the progress value and optional appearance parameters.
9//! The component is designed for integration into Tessera UI applications.
10use derive_builder::Builder;
11use tessera_ui::{Color, ComputedData, Constraint, DimensionValue, Dp, Px, PxPosition};
12use tessera_ui_macros::tessera;
13
14use crate::{
15    shape_def::Shape,
16    surface::{SurfaceArgsBuilder, surface},
17};
18
19/// Arguments for the `progress` component.
20#[derive(Builder, Clone, Debug)]
21#[builder(pattern = "owned")]
22pub struct ProgressArgs {
23    /// The current value of the progress bar, ranging from 0.0 to 1.0.
24    #[builder(default = "0.0")]
25    pub value: f32,
26
27    /// The width of the progress bar.
28    #[builder(default = "Dp(200.0)")]
29    pub width: Dp,
30
31    /// The height of the progress bar.
32    #[builder(default = "Dp(8.0)")]
33    pub height: Dp,
34
35    /// The color of the active part of the track.
36    #[builder(default = "Color::new(0.2, 0.5, 0.8, 1.0)")]
37    pub progress_color: Color,
38
39    /// The color of the inactive part of the track.
40    #[builder(default = "Color::new(0.8, 0.8, 0.8, 1.0)")]
41    pub track_color: Color,
42}
43
44#[tessera]
45/// Draws a linear progress indicator that visualizes the completion of a task.
46///
47/// The `progress` component consists of a track and a fill, where the fill
48/// represents the current progress value.
49///
50/// # Arguments
51///
52/// * `value`: A float between `0.0` and `1.0` representing the current progress.
53///   Values outside this range will be clamped.
54///
55/// # Example
56///
57/// ```
58/// # use tessera_ui_basic_components::progress::{progress, ProgressArgsBuilder};
59/// #
60/// // Creates a progress bar that is 75% complete.
61/// progress(
62///     ProgressArgsBuilder::default()
63///         .value(0.75)
64///         .build()
65///         .unwrap(),
66/// );
67/// ```
68pub fn progress(args: impl Into<ProgressArgs>) {
69    let args: ProgressArgs = args.into();
70
71    // Child 1: The background track. It's drawn first.
72    surface(
73        SurfaceArgsBuilder::default()
74            .color(args.track_color)
75            .shape(Shape::RoundedRectangle {
76                corner_radius: args.height.to_px().to_f32() / 2.0,
77                g2_k_value: 2.0,
78            })
79            .width(DimensionValue::Fill {
80                min: None,
81                max: None,
82            })
83            .height(DimensionValue::Fill {
84                min: None,
85                max: None,
86            })
87            .build()
88            .unwrap(),
89        None,
90        || {},
91    );
92
93    // Child 2: The progress fill. It's drawn on top of the track.
94    surface(
95        SurfaceArgsBuilder::default()
96            .color(args.progress_color)
97            .shape(Shape::RoundedRectangle {
98                corner_radius: args.height.to_px().to_f32() / 2.0,
99                g2_k_value: 2.0,
100            })
101            .width(DimensionValue::Fill {
102                min: None,
103                max: None,
104            })
105            .height(DimensionValue::Fill {
106                min: None,
107                max: None,
108            })
109            .build()
110            .unwrap(),
111        None,
112        || {},
113    );
114
115    measure(Box::new(move |input| {
116        let self_width = args.width.to_px();
117        let self_height = args.height.to_px();
118
119        let track_id = input.children_ids[0];
120        let progress_id = input.children_ids[1];
121
122        // Measure and place the background track to take the full size of the component.
123        let track_constraint = Constraint::new(
124            DimensionValue::Fixed(self_width),
125            DimensionValue::Fixed(self_height),
126        );
127        input.measure_child(track_id, &track_constraint)?;
128        input.place_child(track_id, PxPosition::new(Px(0), Px(0)));
129
130        // Measure and place the progress fill based on the `value`.
131        let progress_width = Px((self_width.to_f32() * args.value.clamp(0.0, 1.0)) as i32);
132        let progress_constraint = Constraint::new(
133            DimensionValue::Fixed(progress_width),
134            DimensionValue::Fixed(self_height),
135        );
136        input.measure_child(progress_id, &progress_constraint)?;
137        input.place_child(progress_id, PxPosition::new(Px(0), Px(0)));
138
139        // The progress component itself is a container, its size is defined by the args.
140        Ok(ComputedData {
141            width: self_width,
142            height: self_height,
143        })
144    }));
145}