tessera_ui_basic_components/
progress.rs

1//! A linear progress bar component.
2//!
3//! ## Usage
4//!
5//! Use to indicate the completion of a task or a specific value in a range.
6use derive_builder::Builder;
7use tessera_ui::{Color, ComputedData, Constraint, DimensionValue, Dp, Px, PxPosition, tessera};
8
9use crate::{
10    shape_def::Shape,
11    surface::{SurfaceArgsBuilder, surface},
12};
13
14/// Arguments for the `progress` component.
15#[derive(Builder, Clone, Debug)]
16#[builder(pattern = "owned")]
17pub struct ProgressArgs {
18    /// The current value of the progress bar, ranging from 0.0 to 1.0.
19    #[builder(default = "0.0")]
20    pub value: f32,
21
22    /// The width of the progress bar.
23    #[builder(default = "Dp(200.0)")]
24    pub width: Dp,
25
26    /// The height of the progress bar.
27    #[builder(default = "Dp(8.0)")]
28    pub height: Dp,
29
30    /// The color of the active part of the track.
31    #[builder(default = "crate::material_color::global_material_scheme().primary")]
32    pub progress_color: Color,
33
34    /// The color of the inactive part of the track.
35    #[builder(default = "crate::material_color::global_material_scheme().surface_variant")]
36    pub track_color: Color,
37}
38
39/// # progress
40///
41/// Renders a linear progress indicator that visualizes a value from 0.0 to 1.0.
42///
43/// ## Usage
44///
45/// Display the status of an ongoing operation, such as a download or a setup process.
46///
47/// ## Parameters
48///
49/// - `args` — configures the progress bar's value and appearance; see [`ProgressArgs`].
50///
51/// ## Examples
52///
53/// ```
54/// use tessera_ui_basic_components::progress::{progress, ProgressArgsBuilder};
55///
56/// // Creates a progress bar that is 75% complete.
57/// progress(
58///     ProgressArgsBuilder::default()
59///         .value(0.75)
60///         .build()
61///         .unwrap(),
62/// );
63/// ```
64#[tessera]
65pub fn progress(args: impl Into<ProgressArgs>) {
66    let args: ProgressArgs = args.into();
67
68    // Child 1: The background track. It's drawn first.
69    surface(
70        SurfaceArgsBuilder::default()
71            .style(args.track_color.into())
72            .shape(Shape::capsule())
73            .width(DimensionValue::Fill {
74                min: None,
75                max: None,
76            })
77            .height(DimensionValue::Fill {
78                min: None,
79                max: None,
80            })
81            .build()
82            .expect("builder construction failed"),
83        None,
84        || {},
85    );
86
87    // Child 2: The progress fill. It's drawn on top of the track.
88    surface(
89        SurfaceArgsBuilder::default()
90            .style(args.progress_color.into())
91            .shape(Shape::capsule())
92            .width(DimensionValue::Fill {
93                min: None,
94                max: None,
95            })
96            .height(DimensionValue::Fill {
97                min: None,
98                max: None,
99            })
100            .build()
101            .expect("builder construction failed"),
102        None,
103        || {},
104    );
105
106    measure(Box::new(move |input| {
107        let self_width = args.width.to_px();
108        let self_height = args.height.to_px();
109
110        let track_id = input.children_ids[0];
111        let progress_id = input.children_ids[1];
112
113        // Measure and place the background track to take the full size of the component.
114        let track_constraint = Constraint::new(
115            DimensionValue::Fixed(self_width),
116            DimensionValue::Fixed(self_height),
117        );
118        input.measure_child(track_id, &track_constraint)?;
119        input.place_child(track_id, PxPosition::new(Px(0), Px(0)));
120
121        // Measure and place the progress fill based on the `value`.
122        let clamped_value = args.value.clamp(0.0, 1.0);
123        let progress_width = Px::saturating_from_f32(self_width.to_f32() * clamped_value);
124        let progress_constraint = Constraint::new(
125            DimensionValue::Fixed(progress_width),
126            DimensionValue::Fixed(self_height),
127        );
128        input.measure_child(progress_id, &progress_constraint)?;
129        input.place_child(progress_id, PxPosition::new(Px(0), Px(0)));
130
131        // The progress component itself is a container, its size is defined by the args.
132        Ok(ComputedData {
133            width: self_width,
134            height: self_height,
135        })
136    }));
137}