Skip to main content

zenthra_widgets/
progress_bar.rs

1use crate::ui::{Ui, DrawCommand, RectDraw};
2use zenthra_core::{Color, EdgeInsets, Response, Id};
3use zenthra_render::RectInstance;
4
5pub struct ProgressBarBuilder<'u, 'a> {
6    ui: &'u mut Ui<'a>,
7    id: Id,
8    value: f32, // 0.0 to 1.0
9    
10    // 1. Overall Widget Style
11    width: Option<f32>,
12    height: Option<f32>,
13    padding: EdgeInsets,
14    bg: Option<Color>,
15    radius: [f32; 4],
16    border_color: Option<Color>,
17    border_width: f32,
18    opacity: f32,
19
20    // 2. Track Style (The background of the bar)
21    track_h: f32,
22    track_color: Color,
23
24    // 3. Fill Style (The actual progress)
25    fill_color: Color,
26    
27    // 4. Shadows
28    shadow_color: Option<Color>,
29    shadow_offset: [f32; 2],
30    shadow_blur: f32,
31    shadow_opacity: f32,
32
33    fill_shadow_color: Option<Color>,
34    fill_shadow_offset: [f32; 2],
35    fill_shadow_blur: f32,
36    fill_shadow_opacity: f32,
37
38    // 5. Effects
39    shimmer: bool,
40}
41
42impl<'u, 'a> ProgressBarBuilder<'u, 'a> {
43    pub fn new(ui: &'u mut Ui<'a>, value: f32) -> Self {
44        let id = ui.id();
45        Self {
46            ui,
47            id,
48            value: value.clamp(0.0, 1.0),
49            
50            width: None,
51            height: None,
52            padding: EdgeInsets::symmetric(4.0, 4.0),
53            bg: None,
54            radius: [0.0; 4],
55            border_color: None,
56            border_width: 0.0,
57            opacity: 1.0,
58
59            track_h: 8.0,
60            track_color: Color::rgb(0.15, 0.15, 0.15),
61
62            fill_color: Color::rgb(0.4, 0.6, 1.0),
63
64            shadow_color: None,
65            shadow_offset: [0.0, 2.0],
66            shadow_blur: 5.0,
67            shadow_opacity: 0.3,
68
69            fill_shadow_color: None,
70            fill_shadow_offset: [0.0, 1.0],
71            fill_shadow_blur: 3.0,
72            fill_shadow_opacity: 0.4,
73            
74            shimmer: false,
75        }
76    }
77
78    pub fn id(mut self, id: impl std::hash::Hash) -> Self {
79        let mut hasher = std::collections::hash_map::DefaultHasher::new();
80        use std::hash::Hasher;
81        id.hash(&mut hasher);
82        self.id = Id::from_u64(hasher.finish());
83        self
84    }
85
86    // --- 1. Overall Widget Methods ---
87    pub fn size(mut self, w: f32, h: f32) -> Self {
88        self.width = Some(w);
89        self.height = Some(h);
90        self
91    }
92
93    pub fn width(mut self, w: f32) -> Self {
94        self.width = Some(w);
95        self
96    }
97
98    pub fn height(mut self, h: f32) -> Self {
99        self.height = Some(h);
100        self
101    }
102
103    pub fn track_size(mut self, w: f32, h: f32) -> Self {
104        self.width = Some(w);
105        self.track_h = h;
106        self
107    }
108
109    pub fn radius(mut self, tl: f32, tr: f32, br: f32, bl: f32) -> Self {
110        self.radius = [tl, tr, br, bl];
111        self
112    }
113
114    pub fn radius_all(mut self, r: f32) -> Self {
115        self.radius = [r; 4];
116        self
117    }
118
119    pub fn radius_top(mut self, r: f32) -> Self {
120        self.radius[0] = r;
121        self.radius[1] = r;
122        self
123    }
124
125    pub fn radius_bottom(mut self, r: f32) -> Self {
126        self.radius[2] = r;
127        self.radius[3] = r;
128        self
129    }
130
131    pub fn radius_top_left(mut self, r: f32) -> Self {
132        self.radius[0] = r;
133        self
134    }
135
136    pub fn radius_top_right(mut self, r: f32) -> Self {
137        self.radius[1] = r;
138        self
139    }
140
141    pub fn radius_bottom_right(mut self, r: f32) -> Self {
142        self.radius[2] = r;
143        self
144    }
145
146    pub fn radius_bottom_left(mut self, r: f32) -> Self {
147        self.radius[3] = r;
148        self
149    }
150
151    pub fn radius_left(mut self, r: f32) -> Self {
152        self.radius[0] = r;
153        self.radius[3] = r;
154        self
155    }
156
157    pub fn radius_right(mut self, r: f32) -> Self {
158        self.radius[1] = r;
159        self.radius[2] = r;
160        self
161    }
162
163    pub fn bg(mut self, color: Color) -> Self {
164        self.bg = Some(color);
165        self
166    }
167
168    pub fn border(mut self, color: Color, width: f32) -> Self {
169        self.border_color = Some(color);
170        self.border_width = width;
171        self
172    }
173
174    pub fn opacity(mut self, o: f32) -> Self {
175        self.opacity = o;
176        self
177    }
178
179    pub fn padding(mut self, t: f32, r: f32, b: f32, l: f32) -> Self {
180        self.padding = EdgeInsets { top: t, right: r, bottom: b, left: l };
181        self
182    }
183
184    // --- 2. Track Methods ---
185    pub fn track_height(mut self, h: f32) -> Self {
186        self.track_h = h;
187        self
188    }
189
190    pub fn track_color(mut self, color: Color) -> Self {
191        self.track_color = color;
192        self
193    }
194
195    // --- 3. Fill Methods ---
196    pub fn fill_color(mut self, color: Color) -> Self {
197        self.fill_color = color;
198        self
199    }
200
201    // --- 4. Shadow Methods ---
202    pub fn shadow(mut self, color: Color, x: f32, y: f32, blur: f32) -> Self {
203        self.shadow_color = Some(color);
204        self.shadow_offset = [x, y];
205        self.shadow_blur = blur;
206        self
207    }
208
209    pub fn shadow_opacity(mut self, opacity: f32) -> Self {
210        self.shadow_opacity = opacity;
211        self
212    }
213
214    pub fn fill_shadow(mut self, color: Color, x: f32, y: f32, blur: f32) -> Self {
215        self.fill_shadow_color = Some(color);
216        self.fill_shadow_offset = [x, y];
217        self.fill_shadow_blur = blur;
218        self
219    }
220
221    pub fn fill_shadow_opacity(mut self, opacity: f32) -> Self {
222        self.fill_shadow_opacity = opacity;
223        self
224    }
225
226    pub fn shimmer(mut self, enabled: bool) -> Self {
227        self.shimmer = enabled;
228        self
229    }
230
231    pub fn show(self) -> Response {
232        let x = self.ui.cursor_x;
233        let y = self.ui.cursor_y;
234        let w = self.width.unwrap_or(self.ui.available_width.max(100.0));
235        
236        let h = self.height.unwrap_or_else(|| {
237            self.track_h.max(12.0) + self.padding.vertical()
238        });
239
240        let is_hovered = self.ui.mouse_in_rect(x + self.ui.offset_x, y + self.ui.offset_y, w, h);
241        let start_draw = self.ui.draws.len();
242
243        // 0. Background
244        if let Some(bg) = self.bg {
245            self.ui.draws.push(DrawCommand::Rect(RectDraw {
246                instance: RectInstance {
247                    pos: [x, y],
248                    size: [w, h],
249                    color: bg.to_array(),
250                    radius: self.radius,
251                    border_width: self.border_width,
252                    border_color: self.border_color.map(|c| c.to_array()).unwrap_or([0.0; 4]),
253                    opacity: self.opacity,
254                    ..Default::default()
255                }
256            }));
257        }
258
259        // 1. Draw Track
260        let track_w = w - self.padding.horizontal();
261        let track_x = x + self.padding.left;
262        let ty = y + (h - self.track_h) / 2.0;
263
264        let mut track_shadow_color = [0.0; 4];
265        if let Some(c) = self.shadow_color {
266            track_shadow_color = c.to_array();
267            track_shadow_color[3] *= self.shadow_opacity;
268        }
269
270        self.ui.draws.push(DrawCommand::Rect(RectDraw {
271            instance: RectInstance {
272                pos: [track_x, ty],
273                size: [track_w, self.track_h],
274                color: self.track_color.to_array(),
275                radius: self.radius,
276                opacity: self.opacity,
277                shadow_color: track_shadow_color,
278                shadow_offset: self.shadow_offset,
279                shadow_blur: self.shadow_blur,
280                ..Default::default()
281            }
282        }));
283
284        // 2. Draw Fill
285        let fill_w = track_w * self.value;
286        if fill_w > 0.1 {
287            let mut fill_shadow_color = [0.0; 4];
288            if let Some(c) = self.fill_shadow_color {
289                fill_shadow_color = c.to_array();
290                fill_shadow_color[3] *= self.fill_shadow_opacity;
291            }
292
293            self.ui.draws.push(DrawCommand::Rect(RectDraw {
294                instance: RectInstance {
295                    pos: [track_x, ty],
296                    size: [fill_w, self.track_h],
297                    color: self.fill_color.to_array(),
298                    radius: self.radius,
299                    opacity: self.opacity,
300                    shadow_color: fill_shadow_color,
301                    shadow_offset: self.fill_shadow_offset,
302                    shadow_blur: self.fill_shadow_blur,
303                    ..Default::default()
304                }
305            }));
306
307            // 3. Shimmer Effect
308            if self.shimmer {
309                let shimmer_t = (self.ui.elapsed_time * 1.5) % 2.0 - 0.5; // Moves from -0.5 to 1.5
310                let shimmer_x = track_x + shimmer_t * track_w;
311                let shimmer_w = track_w * 0.2;
312                
313                if shimmer_x + shimmer_w > track_x && shimmer_x < track_x + fill_w {
314                    // Clip shimmer to the fill bar
315                    let actual_shimmer_x = shimmer_x.max(track_x);
316                    let actual_shimmer_w = (shimmer_x + shimmer_w).min(track_x + fill_w) - actual_shimmer_x;
317
318                    if actual_shimmer_w > 0.0 {
319                        self.ui.draws.push(DrawCommand::Rect(RectDraw {
320                            instance: RectInstance {
321                                pos: [actual_shimmer_x, ty],
322                                size: [actual_shimmer_w, self.track_h],
323                                color: [1.0, 1.0, 1.0, 0.2],
324                                radius: self.radius,
325                                brightness: 1.5,
326                                ..Default::default()
327                            }
328                        }));
329                        self.ui.request_redraw(); // Keep animating
330                    }
331                }
332            }
333        }
334
335        self.ui.record_layout(self.id, zenthra_core::Rect::new(x, y, w, h));
336        self.ui.advance(w, h, start_draw);
337        
338        Response {
339            clicked: self.ui.clicked && is_hovered,
340            hovered: is_hovered,
341            pressed: self.ui.mouse_down && is_hovered,
342        }
343    }
344}