//! The progress bar: determinate fill or an indeterminate sweep. use std::time::Duration; use gpui::prelude::FluentBuilder as _; use gpui::{ Animation, AnimationExt as _, App, ElementId, InteractiveElement as _, IntoElement, ParentElement as _, RenderOnce, Role, SharedString, StatefulInteractiveElement as _, Styled as _, Window, div, relative, }; use gpui_native_theme::{ActiveTheme as _, Size}; use crate::common::Sizable; /// A progress bar. With a `value` in `0..=1` it paints a determinate fill. #[derive(IntoElement)] pub struct Progress { id: ElementId, value: f32, indeterminate: bool, size: Size, a11y_label: Option, } impl Progress { /// Creates a determinate progress bar at `value` (`0..=1`). pub fn new(id: I, value: f32) -> Self where I: Into, { Self { id: id.into(), value, indeterminate: false, size: Size::default(), a11y_label: None, } } /// Sweeps instead of painting a fill, for work of unknown length. pub fn indeterminate(mut self, indeterminate: bool) -> Self { self.indeterminate = indeterminate; self } /// Sets the accessibility label. pub fn accessibility_label(mut self, label: L) -> Self where L: Into, { self.a11y_label = Some(label.into()); self } } impl Sizable for Progress { fn apply_size(&mut self, size: Size) { self.size = size; } } impl RenderOnce for Progress { fn render(self, _window: &mut Window, cx: &mut App) -> impl IntoElement { let theme = cx.theme(); let colors = theme.colors; let ramps = theme.ramps; let radius = theme.radius; let metrics = theme.sizes.resolve(self.size); let track_height = metrics.icon_size / 4.0; let value = self.value.clamp(0.0, 1.0); div() .id(self.id) .w_full() .role(Role::ProgressIndicator) .when_some(self.a11y_label, |progress, label| { progress.aria_label(label) }) .when(!self.indeterminate, |progress| { progress .aria_numeric_value(f64::from(value)) .aria_min_numeric_value(0.0) .aria_max_numeric_value(1.0) }) .child( div() .w_full() .h(track_height) .rounded(radius.full) .bg(colors.border) .child(if self.indeterminate { div() .h_full() .w(relative(0.3)) .rounded(radius.full) .bg(ramps.primary.base) .with_animation( "indeterminate", Animation::new(Duration::from_millis(1500)).repeat(), |pill, delta| pill.ml(relative(delta * 0.7)), ) .into_any_element() } else { div() .h_full() .w(relative(value)) .rounded(radius.full) .bg(ramps.primary.base) .into_any_element() }), ) } }