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 = "Color::new(0.2, 0.5, 0.8, 1.0)")]
32    pub progress_color: Color,
33
34    /// The color of the inactive part of the track.
35    #[builder(default = "Color::new(0.8, 0.8, 0.8, 1.0)")]
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    let radius_dp = Dp(args.height.0 / 2.0);
68
69    // Child 1: The background track. It's drawn first.
70    surface(
71        SurfaceArgsBuilder::default()
72            .style(args.track_color.into())
73            .shape({
74                Shape::RoundedRectangle {
75                    top_left: radius_dp,
76                    top_right: radius_dp,
77                    bottom_right: radius_dp,
78                    bottom_left: radius_dp,
79                    g2_k_value: 2.0,
80                }
81            })
82            .width(DimensionValue::Fill {
83                min: None,
84                max: None,
85            })
86            .height(DimensionValue::Fill {
87                min: None,
88                max: None,
89            })
90            .build()
91            .unwrap(),
92        None,
93        || {},
94    );
95
96    // Child 2: The progress fill. It's drawn on top of the track.
97    surface(
98        SurfaceArgsBuilder::default()
99            .style(args.progress_color.into())
100            .shape({
101                Shape::RoundedRectangle {
102                    top_left: radius_dp,
103                    top_right: radius_dp,
104                    bottom_right: radius_dp,
105                    bottom_left: radius_dp,
106                    g2_k_value: 2.0,
107                }
108            })
109            .width(DimensionValue::Fill {
110                min: None,
111                max: None,
112            })
113            .height(DimensionValue::Fill {
114                min: None,
115                max: None,
116            })
117            .build()
118            .unwrap(),
119        None,
120        || {},
121    );
122
123    measure(Box::new(move |input| {
124        let self_width = args.width.to_px();
125        let self_height = args.height.to_px();
126
127        let track_id = input.children_ids[0];
128        let progress_id = input.children_ids[1];
129
130        // Measure and place the background track to take the full size of the component.
131        let track_constraint = Constraint::new(
132            DimensionValue::Fixed(self_width),
133            DimensionValue::Fixed(self_height),
134        );
135        input.measure_child(track_id, &track_constraint)?;
136        input.place_child(track_id, PxPosition::new(Px(0), Px(0)));
137
138        // Measure and place the progress fill based on the `value`.
139        let clamped_value = args.value.clamp(0.0, 1.0);
140        let progress_width = Px::saturating_from_f32(self_width.to_f32() * clamped_value);
141        let progress_constraint = Constraint::new(
142            DimensionValue::Fixed(progress_width),
143            DimensionValue::Fixed(self_height),
144        );
145        input.measure_child(progress_id, &progress_constraint)?;
146        input.place_child(progress_id, PxPosition::new(Px(0), Px(0)));
147
148        // The progress component itself is a container, its size is defined by the args.
149        Ok(ComputedData {
150            width: self_width,
151            height: self_height,
152        })
153    }));
154}