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, tessera};
12
13use crate::{
14    shape_def::Shape,
15    surface::{SurfaceArgsBuilder, surface},
16};
17
18/// Arguments for the `progress` component.
19#[derive(Builder, Clone, Debug)]
20#[builder(pattern = "owned")]
21pub struct ProgressArgs {
22    /// The current value of the progress bar, ranging from 0.0 to 1.0.
23    #[builder(default = "0.0")]
24    pub value: f32,
25
26    /// The width of the progress bar.
27    #[builder(default = "Dp(200.0)")]
28    pub width: Dp,
29
30    /// The height of the progress bar.
31    #[builder(default = "Dp(8.0)")]
32    pub height: Dp,
33
34    /// The color of the active part of the track.
35    #[builder(default = "Color::new(0.2, 0.5, 0.8, 1.0)")]
36    pub progress_color: Color,
37
38    /// The color of the inactive part of the track.
39    #[builder(default = "Color::new(0.8, 0.8, 0.8, 1.0)")]
40    pub track_color: Color,
41}
42
43#[tessera]
44/// Draws a linear progress indicator that visualizes the completion of a task.
45///
46/// The `progress` component consists of a track and a fill, where the fill
47/// represents the current progress value.
48///
49/// # Arguments
50///
51/// * `value`: A float between `0.0` and `1.0` representing the current progress.
52///   Values outside this range will be clamped.
53///
54/// # Example
55///
56/// ```
57/// # use tessera_ui_basic_components::progress::{progress, ProgressArgsBuilder};
58/// #
59/// // Creates a progress bar that is 75% complete.
60/// progress(
61///     ProgressArgsBuilder::default()
62///         .value(0.75)
63///         .build()
64///         .unwrap(),
65/// );
66/// ```
67pub fn progress(args: impl Into<ProgressArgs>) {
68    let args: ProgressArgs = args.into();
69    let radius_dp = Dp(args.height.0 / 2.0);
70
71    // Child 1: The background track. It's drawn first.
72    surface(
73        SurfaceArgsBuilder::default()
74            .style(args.track_color.into())
75            .shape({
76                Shape::RoundedRectangle {
77                    top_left: radius_dp,
78                    top_right: radius_dp,
79                    bottom_right: radius_dp,
80                    bottom_left: radius_dp,
81                    g2_k_value: 2.0,
82                }
83            })
84            .width(DimensionValue::Fill {
85                min: None,
86                max: None,
87            })
88            .height(DimensionValue::Fill {
89                min: None,
90                max: None,
91            })
92            .build()
93            .unwrap(),
94        None,
95        || {},
96    );
97
98    // Child 2: The progress fill. It's drawn on top of the track.
99    surface(
100        SurfaceArgsBuilder::default()
101            .style(args.progress_color.into())
102            .shape({
103                Shape::RoundedRectangle {
104                    top_left: radius_dp,
105                    top_right: radius_dp,
106                    bottom_right: radius_dp,
107                    bottom_left: radius_dp,
108                    g2_k_value: 2.0,
109                }
110            })
111            .width(DimensionValue::Fill {
112                min: None,
113                max: None,
114            })
115            .height(DimensionValue::Fill {
116                min: None,
117                max: None,
118            })
119            .build()
120            .unwrap(),
121        None,
122        || {},
123    );
124
125    measure(Box::new(move |input| {
126        let self_width = args.width.to_px();
127        let self_height = args.height.to_px();
128
129        let track_id = input.children_ids[0];
130        let progress_id = input.children_ids[1];
131
132        // Measure and place the background track to take the full size of the component.
133        let track_constraint = Constraint::new(
134            DimensionValue::Fixed(self_width),
135            DimensionValue::Fixed(self_height),
136        );
137        input.measure_child(track_id, &track_constraint)?;
138        input.place_child(track_id, PxPosition::new(Px(0), Px(0)));
139
140        // Measure and place the progress fill based on the `value`.
141        let clamped_value = args.value.clamp(0.0, 1.0);
142        let progress_width = Px::saturating_from_f32(self_width.to_f32() * clamped_value);
143        let progress_constraint = Constraint::new(
144            DimensionValue::Fixed(progress_width),
145            DimensionValue::Fixed(self_height),
146        );
147        input.measure_child(progress_id, &progress_constraint)?;
148        input.place_child(progress_id, PxPosition::new(Px(0), Px(0)));
149
150        // The progress component itself is a container, its size is defined by the args.
151        Ok(ComputedData {
152            width: self_width,
153            height: self_height,
154        })
155    }));
156}