tessera_ui_basic_components/
glass_progress.rs

1//! Provides a glassmorphism-style progress bar component for visualizing task completion.
2//!
3//! The `glass_progress` module implements a customizable, frosted glass effect progress bar,
4//! featuring a blurred background, tint colors, and borders. It is designed to display a
5//! progress value from 0.0 to 1.0, making it suitable for loading screens, dashboards, or
6//! any interface requiring a modern and visually appealing progress indicator.
7
8use derive_builder::Builder;
9use tessera_ui::{Color, ComputedData, Constraint, DimensionValue, Dp, Px, PxPosition, tessera};
10
11use crate::{
12    fluid_glass::{FluidGlassArgsBuilder, GlassBorder, fluid_glass},
13    shape_def::Shape,
14};
15
16/// Arguments for the `glass_progress` component.
17#[derive(Builder, Clone, Debug)]
18#[builder(pattern = "owned")]
19pub struct GlassProgressArgs {
20    /// The current value of the progress bar, ranging from 0.0 to 1.0.
21    #[builder(default = "0.0")]
22    pub value: f32,
23
24    /// The width of the progress bar.
25    #[builder(default = "Dp(200.0)")]
26    pub width: Dp,
27
28    /// The height of the progress bar.
29    #[builder(default = "Dp(12.0)")]
30    pub height: Dp,
31
32    /// Glass tint color for the track background.
33    #[builder(default = "Color::new(0.3, 0.3, 0.3, 0.15)")]
34    pub track_tint_color: Color,
35
36    /// Glass tint color for the progress fill.
37    #[builder(default = "Color::new(0.5, 0.7, 1.0, 0.25)")]
38    pub progress_tint_color: Color,
39
40    /// Glass blur radius for all components.
41    #[builder(default = "8.0")]
42    pub blur_radius: f32,
43
44    /// Border width for the track.
45    #[builder(default = "Dp(1.0)")]
46    pub track_border_width: Dp,
47}
48
49/// Produce a capsule-shaped RoundedRectangle shape for the given height (px).
50fn capsule_shape_for_height(height: Dp) -> Shape {
51    let radius = Dp(height.0 / 2.0);
52    Shape::RoundedRectangle {
53        top_left: radius,
54        top_right: radius,
55        bottom_right: radius,
56        bottom_left: radius,
57        g2_k_value: 2.0,
58    }
59}
60
61/// Compute progress width and inner effective height (excluding borders).
62/// Returns None when progress width is zero or negative.
63fn compute_progress_dims(args: &GlassProgressArgs) -> Option<(Px, f32)> {
64    let progress_width = (args.width.to_px().to_f32() * args.value.clamp(0.0, 1.0))
65        - (args.track_border_width.to_px().to_f32() * 2.0);
66    let effective_height =
67        args.height.to_px().to_f32() - (args.track_border_width.to_px().to_f32() * 2.0);
68
69    if progress_width > 0.0 {
70        Some((Px(progress_width as i32), effective_height))
71    } else {
72        None
73    }
74}
75
76/// Render the outer track and the inner progress fill.
77/// Extracted to reduce the size of `glass_progress` and keep each unit focused.
78fn render_track_and_fill(args: GlassProgressArgs) {
79    fluid_glass(
80        FluidGlassArgsBuilder::default()
81            .width(DimensionValue::Fixed(args.width.to_px()))
82            .height(DimensionValue::Fixed(args.height.to_px()))
83            .tint_color(args.track_tint_color)
84            .blur_radius(args.blur_radius)
85            .shape(capsule_shape_for_height(args.height))
86            .border(GlassBorder::new(args.track_border_width.into()))
87            .padding(args.track_border_width)
88            .build()
89            .unwrap(),
90        None,
91        move || {
92            // Internal progress fill - capsule shape
93            if let Some((progress_px, effective_height)) = compute_progress_dims(&args) {
94                fluid_glass(
95                    FluidGlassArgsBuilder::default()
96                        .width(DimensionValue::Fixed(progress_px))
97                        .height(DimensionValue::Fill {
98                            min: None,
99                            max: None,
100                        })
101                        .tint_color(args.progress_tint_color)
102                        .shape(capsule_shape_for_height(Dp::from_pixels_f32(
103                            effective_height,
104                        )))
105                        .refraction_amount(0.0)
106                        .build()
107                        .unwrap(),
108                    None,
109                    || {},
110                );
111            }
112        },
113    );
114}
115
116/// Creates a progress bar component with a frosted glass effect.
117///
118/// The `glass_progress` displays a value from a continuous range (0.0 to 1.0)
119/// with a modern, semi-transparent "glassmorphism" aesthetic, including a
120/// blurred background and subtle highlights.
121///
122/// # Arguments
123///
124/// * `args` - An instance of `GlassProgressArgs` or `GlassProgressArgsBuilder`
125///   to configure the progress bar's appearance.
126///   - `value`: The current progress value, must be between 0.0 and 1.0.
127///
128/// # Example
129///
130/// ```
131/// use tessera_ui_basic_components::glass_progress::{glass_progress, GlassProgressArgsBuilder};
132///
133/// // In your component function
134/// glass_progress(
135///     GlassProgressArgsBuilder::default()
136///         .value(0.75)
137///         .build()
138///         .unwrap(),
139/// );
140/// ```
141#[tessera]
142pub fn glass_progress(args: impl Into<GlassProgressArgs>) {
143    let args: GlassProgressArgs = args.into();
144
145    // Render track and inner fill using extracted helper.
146    let args_for_render = args.clone();
147    render_track_and_fill(args_for_render);
148
149    measure(Box::new(move |input| {
150        let self_width = args.width.to_px();
151        let self_height = args.height.to_px();
152
153        let track_id = input.children_ids[0];
154
155        // Measure track
156        let track_constraint = Constraint::new(
157            DimensionValue::Fixed(self_width),
158            DimensionValue::Fixed(self_height),
159        );
160        input.measure_child(track_id, &track_constraint)?;
161        input.place_child(track_id, PxPosition::new(Px(0), Px(0)));
162
163        Ok(ComputedData {
164            width: self_width,
165            height: self_height,
166        })
167    }));
168}