gpui_ui_kit/
progress.rs

1//! Progress component
2//!
3//! Progress bars and indicators.
4
5use gpui::prelude::*;
6use gpui::*;
7
8/// Progress variant
9#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
10pub enum ProgressVariant {
11    /// Default blue
12    #[default]
13    Default,
14    /// Success green
15    Success,
16    /// Warning yellow
17    Warning,
18    /// Error red
19    Error,
20}
21
22impl ProgressVariant {
23    fn color(&self) -> Rgba {
24        match self {
25            ProgressVariant::Default => rgb(0x007acc),
26            ProgressVariant::Success => rgb(0x2da44e),
27            ProgressVariant::Warning => rgb(0xd29922),
28            ProgressVariant::Error => rgb(0xcc3333),
29        }
30    }
31}
32
33/// Progress size
34#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
35pub enum ProgressSize {
36    /// Extra small (2px)
37    Xs,
38    /// Small (4px)
39    Sm,
40    /// Medium (8px, default)
41    #[default]
42    Md,
43    /// Large (12px)
44    Lg,
45}
46
47impl ProgressSize {
48    fn height(&self) -> Pixels {
49        match self {
50            ProgressSize::Xs => px(2.0),
51            ProgressSize::Sm => px(4.0),
52            ProgressSize::Md => px(8.0),
53            ProgressSize::Lg => px(12.0),
54        }
55    }
56}
57
58/// A progress bar component
59pub struct Progress {
60    value: f32,
61    max: f32,
62    variant: ProgressVariant,
63    size: ProgressSize,
64    show_label: bool,
65    striped: bool,
66    animated: bool,
67}
68
69impl Progress {
70    /// Create a new progress bar
71    pub fn new(value: f32) -> Self {
72        Self {
73            value,
74            max: 100.0,
75            variant: ProgressVariant::default(),
76            size: ProgressSize::default(),
77            show_label: false,
78            striped: false,
79            animated: false,
80        }
81    }
82
83    /// Set maximum value
84    pub fn max(mut self, max: f32) -> Self {
85        self.max = max;
86        self
87    }
88
89    /// Set variant
90    pub fn variant(mut self, variant: ProgressVariant) -> Self {
91        self.variant = variant;
92        self
93    }
94
95    /// Set size
96    pub fn size(mut self, size: ProgressSize) -> Self {
97        self.size = size;
98        self
99    }
100
101    /// Show percentage label
102    pub fn show_label(mut self, show: bool) -> Self {
103        self.show_label = show;
104        self
105    }
106
107    /// Enable striped appearance
108    pub fn striped(mut self, striped: bool) -> Self {
109        self.striped = striped;
110        self
111    }
112
113    /// Enable animation
114    pub fn animated(mut self, animated: bool) -> Self {
115        self.animated = animated;
116        self
117    }
118
119    /// Build into element
120    pub fn build(self) -> Div {
121        let height = self.size.height();
122        let color = self.variant.color();
123        let percentage = (self.value / self.max * 100.0).clamp(0.0, 100.0);
124
125        let mut container = div().flex().flex_col().gap_1().w_full();
126
127        // Label
128        if self.show_label {
129            container = container.child(
130                div()
131                    .flex()
132                    .justify_between()
133                    .text_xs()
134                    .text_color(rgb(0xcccccc))
135                    .child(format!("{:.0}%", percentage)),
136            );
137        }
138
139        // Track
140        let track = div()
141            .w_full()
142            .h(height)
143            .bg(rgb(0x2a2a2a))
144            .rounded_full()
145            .overflow_hidden()
146            .child(
147                div()
148                    .h_full()
149                    .bg(color)
150                    .rounded_full()
151                    .w(relative(percentage / 100.0)),
152            );
153
154        container = container.child(track);
155
156        container
157    }
158}
159
160impl IntoElement for Progress {
161    type Element = Div;
162
163    fn into_element(self) -> Self::Element {
164        self.build()
165    }
166}
167
168/// A circular progress indicator
169pub struct CircularProgress {
170    value: f32,
171    max: f32,
172    size: Pixels,
173    thickness: Pixels,
174    variant: ProgressVariant,
175    show_label: bool,
176}
177
178impl CircularProgress {
179    /// Create a new circular progress
180    pub fn new(value: f32) -> Self {
181        Self {
182            value,
183            max: 100.0,
184            size: px(48.0),
185            thickness: px(4.0),
186            variant: ProgressVariant::default(),
187            show_label: false,
188        }
189    }
190
191    /// Set maximum value
192    pub fn max(mut self, max: f32) -> Self {
193        self.max = max;
194        self
195    }
196
197    /// Set size
198    pub fn size(mut self, size: Pixels) -> Self {
199        self.size = size;
200        self
201    }
202
203    /// Set thickness
204    pub fn thickness(mut self, thickness: Pixels) -> Self {
205        self.thickness = thickness;
206        self
207    }
208
209    /// Set variant
210    pub fn variant(mut self, variant: ProgressVariant) -> Self {
211        self.variant = variant;
212        self
213    }
214
215    /// Show percentage label in center
216    pub fn show_label(mut self, show: bool) -> Self {
217        self.show_label = show;
218        self
219    }
220
221    /// Build into element
222    /// Note: True circular progress requires canvas/SVG rendering.
223    /// This is a simplified box-based representation.
224    pub fn build(self) -> Div {
225        let percentage = (self.value / self.max * 100.0).clamp(0.0, 100.0);
226        let color = self.variant.color();
227
228        let mut container = div()
229            .flex()
230            .items_center()
231            .justify_center()
232            .w(self.size)
233            .h(self.size)
234            .rounded_full()
235            .border(self.thickness)
236            .border_color(rgb(0x2a2a2a))
237            .relative();
238
239        // Progress arc approximation (using border color)
240        // Note: This is a simplified version - true circular progress needs SVG
241        if percentage > 0.0 {
242            container = container.border_color(color);
243        }
244
245        // Center label
246        if self.show_label {
247            container = container.child(
248                div()
249                    .text_xs()
250                    .font_weight(FontWeight::BOLD)
251                    .text_color(rgb(0xcccccc))
252                    .child(format!("{:.0}%", percentage)),
253            );
254        }
255
256        container
257    }
258}
259
260impl IntoElement for CircularProgress {
261    type Element = Div;
262
263    fn into_element(self) -> Self::Element {
264        self.build()
265    }
266}