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}