1use super::*;
2use crate::node::WeakFlexBox;
3use crate::Border;
4
5#[derive(Clone, Copy)]
6struct ResolvedProgressBarSizing {
7 length: f32,
8 thickness: f32,
9}
10
11fn resolve_sizing(sizing: Option<ProgressBarSizing>) -> ResolvedProgressBarSizing {
12 let sizing = sizing.unwrap_or_default();
13 ResolvedProgressBarSizing {
14 length: if sizing.has_length() {
15 sizing.length_px()
16 } else {
17 PROGRESS_LENGTH
18 },
19 thickness: if sizing.has_thickness() {
20 sizing.thickness_px()
21 } else {
22 PROGRESS_THICKNESS
23 },
24 }
25}
26
27#[derive(Clone)]
28pub struct ProgressBar {
29 root: FlexBox,
30 fill: FlexBox,
31 min: Rc<Cell<f32>>,
32 max: Rc<Cell<f32>>,
33 value: Rc<Cell<f32>>,
34 orientation: Rc<Cell<Orientation>>,
35 sizing_value: Rc<Cell<Option<ProgressBarSizing>>>,
36 colors_value: Rc<Cell<Option<ProgressBarColors>>>,
37 weak_root: WeakFlexBox,
38 weak_fill: WeakFlexBox,
39}
40
41impl Default for ProgressBar {
42 fn default() -> Self {
43 Self::new()
44 }
45}
46
47impl ProgressBar {
48 pub fn new() -> Self {
49 let root = flex_box();
50 let fill = flex_box();
51 root.semantic_orientation(Orientation::Horizontal)
52 .semantic_value_range(0.0, 0.0, 100.0)
53 .child(&fill);
54 let control = Self {
55 weak_root: root.downgrade(),
56 weak_fill: fill.downgrade(),
57 root,
58 fill,
59 min: Rc::new(Cell::new(0.0)),
60 max: Rc::new(Cell::new(100.0)),
61 value: Rc::new(Cell::new(0.0)),
62 orientation: Rc::new(Cell::new(Orientation::Horizontal)),
63 sizing_value: Rc::new(Cell::new(None)),
64 colors_value: Rc::new(Cell::new(None)),
65 };
66 control.install_visual_subscriptions();
67 control.set_value(0.0);
68 control.handle_theme_changed();
69 control
70 }
71
72 pub fn min(&self, value: f32) -> &Self {
73 self.min.set(value);
74 if self.max.get() < value {
75 self.max.set(value);
76 }
77 self.set_value(self.value.get());
78 self
79 }
80
81 pub fn max(&self, value: f32) -> &Self {
82 self.max.set(value);
83 if self.min.get() > value {
84 self.min.set(value);
85 }
86 self.set_value(self.value.get());
87 self
88 }
89
90 pub fn value(&self, value: f32) -> &Self {
91 self.set_value(value);
92 self
93 }
94
95 pub fn length(&self, value: f32) -> &Self {
96 let value = clamp_direct_dimension("length", value);
97 let current = self.sizing_value.get().unwrap_or_default();
98 let mut sizing = ProgressBarSizing::new().length(value);
99 if current.has_thickness() {
100 sizing = sizing.thickness(current.thickness_px());
101 }
102 self.sizing_value.set(Some(sizing));
103 self.sync_geometry();
104 self
105 }
106
107 pub fn thickness(&self, value: f32) -> &Self {
108 let value = clamp_direct_dimension("thickness", value);
109 let current = self.sizing_value.get().unwrap_or_default();
110 let mut sizing = ProgressBarSizing::new().thickness(value);
111 if current.has_length() {
112 sizing = sizing.length(current.length_px());
113 }
114 self.sizing_value.set(Some(sizing));
115 self.sync_geometry();
116 self.sync_visual_state();
117 self
118 }
119
120 pub fn orientation(&self, orientation: Orientation) -> &Self {
121 let orientation = if orientation == Orientation::Vertical {
122 Orientation::Vertical
123 } else {
124 Orientation::Horizontal
125 };
126 self.orientation.set(orientation);
127 self.root.semantic_orientation(orientation);
128 self.sync_geometry();
129 self
130 }
131
132 pub fn sizing(&self, sizing: ProgressBarSizing) -> &Self {
133 self.sizing_value.set(Some(sizing));
134 self.sync_geometry();
135 self.sync_visual_state();
136 self
137 }
138
139 pub fn clear_sizing(&self) -> &Self {
140 self.sizing_value.set(None);
141 self.sync_geometry();
142 self.sync_visual_state();
143 self
144 }
145
146 pub fn colors(&self, colors: ProgressBarColors) -> &Self {
147 self.colors_value.set(Some(colors));
148 self.sync_visual_state();
149 self
150 }
151
152 pub fn clear_colors(&self) -> &Self {
153 self.colors_value.set(None);
154 self.sync_visual_state();
155 self
156 }
157
158 pub fn corner_radius(&self, radius: f32) -> &Self {
159 self.root.corner_radius(radius);
160 self.fill.corner_radius(radius);
161 self
162 }
163
164 pub fn current_value(&self) -> f32 {
165 self.value.get()
166 }
167
168 fn install_visual_subscriptions(&self) {
169 let weak_root = self.weak_root.clone();
170 let weak_fill = self.weak_fill.clone();
171 let sizing_value = self.sizing_value.clone();
172 let colors_value = self.colors_value.clone();
173 let guard = subscribe(move |_theme| {
174 let Some(root) = weak_root.upgrade() else {
175 return;
176 };
177 let Some(fill) = weak_fill.upgrade() else {
178 return;
179 };
180 sync_progress_visual_state(
181 &root,
182 &fill,
183 resolve_sizing(sizing_value.get()),
184 colors_value.get(),
185 );
186 });
187 self.root
188 .retained_node_ref()
189 .retain_attachment(Rc::new(guard));
190 }
191
192 fn handle_theme_changed(&self) {
193 self.sync_visual_state();
194 self.sync_geometry();
195 }
196
197 fn set_value(&self, value: f32) {
198 self.value.set(value.clamp(self.min.get(), self.max.get()));
199 self.sync_geometry();
200 }
201
202 fn sync_geometry(&self) {
203 sync_progress_geometry(
204 &self.root,
205 &self.fill,
206 self.min.get(),
207 self.max.get(),
208 self.value.get(),
209 self.orientation.get(),
210 resolve_sizing(self.sizing_value.get()),
211 );
212 }
213
214 fn sync_visual_state(&self) {
215 sync_progress_visual_state(
216 &self.root,
217 &self.fill,
218 resolve_sizing(self.sizing_value.get()),
219 self.colors_value.get(),
220 );
221 }
222}
223
224#[derive(Clone)]
225struct ProgressBarThemeTarget {
226 root: WeakFlexBox,
227 fill: WeakFlexBox,
228 min: Rc<Cell<f32>>,
229 max: Rc<Cell<f32>>,
230 value: Rc<Cell<f32>>,
231 orientation: Rc<Cell<Orientation>>,
232 sizing_value: Rc<Cell<Option<ProgressBarSizing>>>,
233 colors_value: Rc<Cell<Option<ProgressBarColors>>>,
234}
235
236impl ProgressBarThemeTarget {
237 fn upgrade(&self) -> Option<ProgressBar> {
238 Some(ProgressBar {
239 root: self.root.upgrade()?,
240 fill: self.fill.upgrade()?,
241 min: self.min.clone(),
242 max: self.max.clone(),
243 value: self.value.clone(),
244 orientation: self.orientation.clone(),
245 sizing_value: self.sizing_value.clone(),
246 colors_value: self.colors_value.clone(),
247 weak_root: self.root.clone(),
248 weak_fill: self.fill.clone(),
249 })
250 }
251}
252
253fn clamp_direct_dimension(method: &str, value: f32) -> f32 {
254 if value > 0.0 {
255 return value;
256 }
257 crate::logger::warn(
258 "Layout",
259 &format!("ProgressBar.{method}() received {value}; clamping to 1.0."),
260 );
261 1.0
262}
263
264impl Node for ProgressBar {
265 fn retained_node_ref(&self) -> NodeRef {
266 self.root.retained_node_ref()
267 }
268
269 fn build_self(&self) {
270 self.sync_visual_state();
271 self.sync_geometry();
272 self.root.build_self();
273 }
274}
275
276impl HasFlexBoxRoot for ProgressBar {
277 fn flex_box_root(&self) -> &FlexBox {
278 &self.root
279 }
280}
281
282impl ThemeBindable for ProgressBar {
283 fn theme_binding_node(&self) -> NodeRef {
284 self.root.retained_node_ref()
285 }
286
287 fn weak_theme_target(&self) -> Box<dyn Fn() -> Option<Self>> {
288 let target = ProgressBarThemeTarget {
289 root: self.weak_root.clone(),
290 fill: self.weak_fill.clone(),
291 min: self.min.clone(),
292 max: self.max.clone(),
293 value: self.value.clone(),
294 orientation: self.orientation.clone(),
295 sizing_value: self.sizing_value.clone(),
296 colors_value: self.colors_value.clone(),
297 };
298 Box::new(move || target.upgrade())
299 }
300}
301
302fn sync_progress_geometry(
303 root: &FlexBox,
304 fill: &FlexBox,
305 min: f32,
306 max: f32,
307 current_value: f32,
308 orientation: Orientation,
309 sizing: ResolvedProgressBarSizing,
310) {
311 let range = max - min;
312 let fraction = if range > 0.0 {
313 ((current_value - min) / range).clamp(0.0, 1.0)
314 } else {
315 0.0
316 };
317 let fill_length = sizing.length * fraction;
318 if orientation == Orientation::Vertical {
319 root.flex_direction(FlexDirection::Column)
320 .justify_content(JustifyContent::End)
321 .width(sizing.thickness, Unit::Pixel)
322 .height(sizing.length, Unit::Pixel);
323 fill.width(sizing.thickness, Unit::Pixel)
324 .height(fill_length, Unit::Pixel);
325 } else {
326 root.flex_direction(FlexDirection::Row)
327 .justify_content(JustifyContent::Start)
328 .width(sizing.length, Unit::Pixel)
329 .height(sizing.thickness, Unit::Pixel);
330 fill.width(fill_length, Unit::Pixel)
331 .height(sizing.thickness, Unit::Pixel);
332 }
333 root.semantic_value_range(current_value, min, max)
334 .default_semantic_label(format!(
335 "Progress bar, value {}, range {} to {}",
336 current_value, min, max
337 ));
338}
339
340fn sync_progress_visual_state(
341 root: &FlexBox,
342 fill: &FlexBox,
343 sizing: ResolvedProgressBarSizing,
344 colors: Option<ProgressBarColors>,
345) {
346 let theme = current_theme();
347 let track_color = colors
348 .filter(ProgressBarColors::has_track)
349 .map(|colors| colors.track_color())
350 .unwrap_or(theme.colors.scrollbar_track);
351 let fill_color = colors
352 .filter(ProgressBarColors::has_fill)
353 .map(|colors| colors.fill_color())
354 .unwrap_or(theme.colors.accent);
355 root.apply_presenter_style(
356 crate::PresenterHostStyle::new()
357 .background(track_color)
358 .border(Border::solid(1.0, theme.colors.border))
359 .corners(crate::Corners::all(sizing.thickness * 0.5)),
360 );
361 let corners = root
362 .resolved_host_style()
363 .corners
364 .unwrap_or_else(|| crate::Corners::all(sizing.thickness * 0.5));
365 fill.corners(
366 corners.top_left,
367 corners.top_right,
368 corners.bottom_right,
369 corners.bottom_left,
370 )
371 .bg_color(fill_color);
372}