1use documented::Documented;
2use gpui::{Hsla, point};
3
4use crate::components::Label;
5use crate::prelude::*;
6
7/// A progress bar is a horizontal bar that communicates the status of a process.
8///
9/// A progress bar should not be used to represent indeterminate progress.
10#[derive(RegisterComponent, Documented)]
11pub struct ProgressBar {
12 id: ElementId,
13 value: f32,
14 max_value: f32,
15 bg_color: Hsla,
16 fg_color: Hsla,
17}
18
19impl ProgressBar {
20 /// Create a new progress bar with the given value and maximum value.
21 pub fn new(
22 id: impl Into<ElementId>,
23 value: f32,
24 max_value: f32,
25 cx: &mut Context<Self>,
26 ) -> Self {
27 Self {
28 id: id.into(),
29 value,
30 max_value,
31 bg_color: cx.theme().colors().background,
32 fg_color: cx.theme().status().info,
33 }
34 }
35
36 /// Set the current value of the progress bar.
37 pub fn value(&mut self, value: f32) -> &mut Self {
38 self.value = value;
39 self
40 }
41
42 /// Set the maximum value of the progress bar.
43 pub fn max_value(&mut self, max_value: f32) -> &mut Self {
44 self.max_value = max_value;
45 self
46 }
47
48 /// Set the background color of the progress bar.
49 pub fn bg_color(&mut self, color: Hsla) -> &mut Self {
50 self.bg_color = color;
51 self
52 }
53
54 /// Set the foreground color of the progress bar.
55 pub fn fg_color(&mut self, color: Hsla) -> &mut Self {
56 self.fg_color = color;
57 self
58 }
59}
60
61impl Render for ProgressBar {
62 fn render(&mut self, _window: &mut Window, _cx: &mut Context<Self>) -> impl IntoElement {
63 let fill_width = (self.value / self.max_value).clamp(0.02, 1.0);
64
65 div()
66 .id(self.id.clone())
67 .w_full()
68 .h(px(8.0))
69 .rounded_full()
70 .py(px(2.0))
71 .px(px(4.0))
72 .bg(self.bg_color)
73 .shadow(smallvec::smallvec![gpui::BoxShadow {
74 color: gpui::black().opacity(0.08),
75 offset: point(px(0.), px(1.)),
76 blur_radius: px(0.),
77 spread_radius: px(0.),
78 }])
79 .child(
80 div()
81 .h_full()
82 .rounded_full()
83 .bg(self.fg_color)
84 .w(relative(fill_width)),
85 )
86 }
87}
88
89impl Component for ProgressBar {
90 fn scope() -> ComponentScope {
91 ComponentScope::Status
92 }
93
94 fn description() -> Option<&'static str> {
95 Some(Self::DOCS)
96 }
97
98 fn preview(_window: &mut Window, cx: &mut App) -> Option<AnyElement> {
99 let max_value = 180.0;
100
101 let empty_progress_bar = cx.new(|cx| ProgressBar::new("empty", 0.0, max_value, cx));
102 let partial_progress_bar =
103 cx.new(|cx| ProgressBar::new("partial", max_value * 0.35, max_value, cx));
104 let filled_progress_bar = cx.new(|cx| ProgressBar::new("filled", max_value, max_value, cx));
105
106 Some(
107 div()
108 .flex()
109 .flex_col()
110 .gap_4()
111 .p_4()
112 .w(px(240.0))
113 .child(div().child("Progress Bar"))
114 .child(
115 div()
116 .flex()
117 .flex_col()
118 .gap_2()
119 .child(
120 div()
121 .flex()
122 .justify_between()
123 .child(Label::new("0%"))
124 .child(Label::new("Empty")),
125 )
126 .child(empty_progress_bar.clone()),
127 )
128 .child(
129 div()
130 .flex()
131 .flex_col()
132 .gap_2()
133 .child(
134 div()
135 .flex()
136 .justify_between()
137 .child(Label::new("38%"))
138 .child(Label::new("Partial")),
139 )
140 .child(partial_progress_bar.clone()),
141 )
142 .child(
143 div()
144 .flex()
145 .flex_col()
146 .gap_2()
147 .child(
148 div()
149 .flex()
150 .justify_between()
151 .child(Label::new("100%"))
152 .child(Label::new("Complete")),
153 )
154 .child(filled_progress_bar.clone()),
155 )
156 .into_any_element(),
157 )
158 }
159}