1use azul_core::dom::{Dom, IdOrClass, IdOrClass::Class, IdOrClassVec};
6#[allow(clippy::wildcard_imports)] use azul_css::{
8 dynamic_selector::{CssPropertyWithConditions, CssPropertyWithConditionsVec},
9 props::{
10 basic::*,
11 layout::*,
12 property::{CssProperty, *},
13 style::*,
14 },
15 *,
16};
17use azul_css::css::BoxOrStatic;
18
19const STYLE_BACKGROUND_CONTENT_2688422633177340412_ITEMS: &[StyleBackgroundContent] =
20 &[StyleBackgroundContent::LinearGradient(LinearGradient {
21 direction: Direction::FromTo(DirectionCorners {
22 dir_from: DirectionCorner::Top,
23 dir_to: DirectionCorner::Bottom,
24 }),
25 extend_mode: ExtendMode::Clamp,
26 stops: NormalizedLinearColorStopVec::from_const_slice(
27 LINEAR_COLOR_STOP_12009347504665939_ITEMS,
28 ),
29 })];
30const STYLE_BACKGROUND_CONTENT_14586281004485141058_ITEMS: &[StyleBackgroundContent] =
31 &[StyleBackgroundContent::LinearGradient(LinearGradient {
32 direction: Direction::FromTo(DirectionCorners {
33 dir_from: DirectionCorner::Top,
34 dir_to: DirectionCorner::Bottom,
35 }),
36 extend_mode: ExtendMode::Clamp,
37 stops: NormalizedLinearColorStopVec::from_const_slice(
38 LINEAR_COLOR_STOP_3104396762583413726_ITEMS,
39 ),
40 })];
41const LINEAR_COLOR_STOP_12009347504665939_ITEMS: &[NormalizedLinearColorStop] = &[
42 NormalizedLinearColorStop {
43 offset: PercentageValue::const_new(0),
44 color: ColorOrSystem::color(ColorU {
45 r: 193,
46 g: 255,
47 b: 187,
48 a: 255,
49 }),
50 },
51 NormalizedLinearColorStop {
52 offset: PercentageValue::const_new(10),
53 color: ColorOrSystem::color(ColorU {
54 r: 205,
55 g: 255,
56 b: 205,
57 a: 255,
58 }),
59 },
60 NormalizedLinearColorStop {
61 offset: PercentageValue::const_new(15),
62 color: ColorOrSystem::color(ColorU {
63 r: 156,
64 g: 238,
65 b: 172,
66 a: 255,
67 }),
68 },
69 NormalizedLinearColorStop {
70 offset: PercentageValue::const_new(20),
71 color: ColorOrSystem::color(ColorU {
72 r: 0,
73 g: 211,
74 b: 40,
75 a: 255,
76 }),
77 },
78 NormalizedLinearColorStop {
79 offset: PercentageValue::const_new(30),
80 color: ColorOrSystem::color(ColorU {
81 r: 0,
82 g: 211,
83 b: 40,
84 a: 255,
85 }),
86 },
87 NormalizedLinearColorStop {
88 offset: PercentageValue::const_new(70),
89 color: ColorOrSystem::color(ColorU {
90 r: 32,
91 g: 219,
92 b: 65,
93 a: 255,
94 }),
95 },
96 NormalizedLinearColorStop {
97 offset: PercentageValue::const_new(100),
98 color: ColorOrSystem::color(ColorU {
99 r: 32,
100 g: 219,
101 b: 65,
102 a: 255,
103 }),
104 },
105];
106const LINEAR_COLOR_STOP_3104396762583413726_ITEMS: &[NormalizedLinearColorStop] = &[
107 NormalizedLinearColorStop {
108 offset: PercentageValue::const_new(0),
109 color: ColorOrSystem::color(ColorU {
110 r: 243,
111 g: 243,
112 b: 243,
113 a: 255,
114 }),
115 },
116 NormalizedLinearColorStop {
117 offset: PercentageValue::const_new(10),
118 color: ColorOrSystem::color(ColorU {
119 r: 252,
120 g: 252,
121 b: 252,
122 a: 255,
123 }),
124 },
125 NormalizedLinearColorStop {
126 offset: PercentageValue::const_new(15),
127 color: ColorOrSystem::color(ColorU {
128 r: 218,
129 g: 218,
130 b: 218,
131 a: 255,
132 }),
133 },
134 NormalizedLinearColorStop {
135 offset: PercentageValue::const_new(20),
136 color: ColorOrSystem::color(ColorU {
137 r: 201,
138 g: 201,
139 b: 201,
140 a: 255,
141 }),
142 },
143 NormalizedLinearColorStop {
144 offset: PercentageValue::const_new(30),
145 color: ColorOrSystem::color(ColorU {
146 r: 218,
147 g: 218,
148 b: 218,
149 a: 255,
150 }),
151 },
152 NormalizedLinearColorStop {
153 offset: PercentageValue::const_new(70),
154 color: ColorOrSystem::color(ColorU {
155 r: 203,
156 g: 203,
157 b: 203,
158 a: 255,
159 }),
160 },
161 NormalizedLinearColorStop {
162 offset: PercentageValue::const_new(100),
163 color: ColorOrSystem::color(ColorU {
164 r: 203,
165 g: 203,
166 b: 203,
167 a: 255,
168 }),
169 },
170];
171
172#[derive(Debug, Clone)]
174#[repr(C)]
175pub struct ProgressBar {
176 pub progressbar_state: ProgressBarState,
177 pub height: PixelValue,
178 pub bar_background: StyleBackgroundContentVec,
179 pub container_background: StyleBackgroundContentVec,
180}
181
182#[derive(Copy, Debug, Clone)]
184#[repr(C)]
185pub struct ProgressBarState {
186 pub percent_done: f32,
187 pub display_percentage: bool,
188}
189
190impl ProgressBar {
191 #[inline]
193 #[must_use] pub const fn create(percent_done: f32) -> Self {
194 Self {
195 progressbar_state: ProgressBarState {
196 percent_done,
197 display_percentage: false,
198 },
199 height: PixelValue::const_px(15),
200 bar_background: StyleBackgroundContentVec::from_const_slice(
201 STYLE_BACKGROUND_CONTENT_2688422633177340412_ITEMS,
202 ),
203 container_background: StyleBackgroundContentVec::from_const_slice(
204 STYLE_BACKGROUND_CONTENT_14586281004485141058_ITEMS,
205 ),
206 }
207 }
208
209 #[inline]
211 #[must_use]
212 pub const fn swap_with_default(&mut self) -> Self {
213 let mut s = Self::create(0.0);
214 core::mem::swap(&mut s, self);
215 s
216 }
217
218 pub fn set_container_background(&mut self, background: StyleBackgroundContentVec) {
219 self.container_background = background;
220 }
221
222 #[must_use] pub fn with_container_background(mut self, background: StyleBackgroundContentVec) -> Self {
223 self.set_container_background(background);
224 self
225 }
226
227 pub fn set_bar_background(&mut self, background: StyleBackgroundContentVec) {
228 self.bar_background = background;
229 }
230
231 #[must_use] pub fn with_bar_background(mut self, background: StyleBackgroundContentVec) -> Self {
232 self.set_bar_background(background);
233 self
234 }
235
236 pub const fn set_height(&mut self, height: PixelValue) {
237 self.height = height;
238 }
239
240 #[must_use] pub const fn with_height(mut self, height: PixelValue) -> Self {
241 self.set_height(height);
242 self
243 }
244
245 #[allow(clippy::too_many_lines)] #[must_use] pub fn dom(self) -> Dom {
249 use azul_core::dom::DomVec;
250
251 let percent_done = self.progressbar_state.percent_done.clamp(0.0, 100.0);
255
256 Dom::create_div()
257 .with_css_props(CssPropertyWithConditionsVec::from_vec(vec![
258 CssPropertyWithConditions::simple(CssProperty::Height(LayoutHeightValue::Exact(
260 LayoutHeight::Px(self.height),
261 ))),
262 CssPropertyWithConditions::simple(CssProperty::FlexDirection(
263 LayoutFlexDirectionValue::Exact(LayoutFlexDirection::Row),
264 )),
265 CssPropertyWithConditions::simple(CssProperty::BoxShadowBottom(
266 StyleBoxShadowValue::Exact(BoxOrStatic::heap(StyleBoxShadow {
267 offset_x: PixelValueNoPercent {
268 inner: PixelValue::const_px(0),
269 },
270 offset_y: PixelValueNoPercent {
271 inner: PixelValue::const_px(0),
272 },
273 color: ColorU {
274 r: 0,
275 g: 0,
276 b: 0,
277 a: 9,
278 },
279 blur_radius: PixelValueNoPercent {
280 inner: PixelValue::const_px(15),
281 },
282 spread_radius: PixelValueNoPercent {
283 inner: PixelValue::const_px(2),
284 },
285 clip_mode: BoxShadowClipMode::Inset,
286 })),
287 )),
288 CssPropertyWithConditions::simple(CssProperty::BoxShadowTop(
289 StyleBoxShadowValue::Exact(BoxOrStatic::heap(StyleBoxShadow {
290 offset_x: PixelValueNoPercent {
291 inner: PixelValue::const_px(0),
292 },
293 offset_y: PixelValueNoPercent {
294 inner: PixelValue::const_px(0),
295 },
296 color: ColorU {
297 r: 0,
298 g: 0,
299 b: 0,
300 a: 9,
301 },
302 blur_radius: PixelValueNoPercent {
303 inner: PixelValue::const_px(15),
304 },
305 spread_radius: PixelValueNoPercent {
306 inner: PixelValue::const_px(2),
307 },
308 clip_mode: BoxShadowClipMode::Inset,
309 })),
310 )),
311 CssPropertyWithConditions::simple(CssProperty::BoxShadowRight(
312 StyleBoxShadowValue::Exact(BoxOrStatic::heap(StyleBoxShadow {
313 offset_x: PixelValueNoPercent {
314 inner: PixelValue::const_px(0),
315 },
316 offset_y: PixelValueNoPercent {
317 inner: PixelValue::const_px(0),
318 },
319 color: ColorU {
320 r: 0,
321 g: 0,
322 b: 0,
323 a: 9,
324 },
325 blur_radius: PixelValueNoPercent {
326 inner: PixelValue::const_px(15),
327 },
328 spread_radius: PixelValueNoPercent {
329 inner: PixelValue::const_px(2),
330 },
331 clip_mode: BoxShadowClipMode::Inset,
332 })),
333 )),
334 CssPropertyWithConditions::simple(CssProperty::BoxShadowLeft(
335 StyleBoxShadowValue::Exact(BoxOrStatic::heap(StyleBoxShadow {
336 offset_x: PixelValueNoPercent {
337 inner: PixelValue::const_px(0),
338 },
339 offset_y: PixelValueNoPercent {
340 inner: PixelValue::const_px(0),
341 },
342 color: ColorU {
343 r: 0,
344 g: 0,
345 b: 0,
346 a: 9,
347 },
348 blur_radius: PixelValueNoPercent {
349 inner: PixelValue::const_px(15),
350 },
351 spread_radius: PixelValueNoPercent {
352 inner: PixelValue::const_px(2),
353 },
354 clip_mode: BoxShadowClipMode::Inset,
355 })),
356 )),
357 CssPropertyWithConditions::simple(CssProperty::BorderBottomRightRadius(
358 StyleBorderBottomRightRadiusValue::Exact(StyleBorderBottomRightRadius {
359 inner: PixelValue::const_px(3),
360 }),
361 )),
362 CssPropertyWithConditions::simple(CssProperty::BorderBottomLeftRadius(
363 StyleBorderBottomLeftRadiusValue::Exact(StyleBorderBottomLeftRadius {
364 inner: PixelValue::const_px(3),
365 }),
366 )),
367 CssPropertyWithConditions::simple(CssProperty::BorderTopRightRadius(
368 StyleBorderTopRightRadiusValue::Exact(StyleBorderTopRightRadius {
369 inner: PixelValue::const_px(3),
370 }),
371 )),
372 CssPropertyWithConditions::simple(CssProperty::BorderTopLeftRadius(
373 StyleBorderTopLeftRadiusValue::Exact(StyleBorderTopLeftRadius {
374 inner: PixelValue::const_px(3),
375 }),
376 )),
377 CssPropertyWithConditions::simple(CssProperty::BorderBottomWidth(
378 LayoutBorderBottomWidthValue::Exact(LayoutBorderBottomWidth {
379 inner: PixelValue::const_px(1),
380 }),
381 )),
382 CssPropertyWithConditions::simple(CssProperty::BorderLeftWidth(
383 LayoutBorderLeftWidthValue::Exact(LayoutBorderLeftWidth {
384 inner: PixelValue::const_px(1),
385 }),
386 )),
387 CssPropertyWithConditions::simple(CssProperty::BorderRightWidth(
388 LayoutBorderRightWidthValue::Exact(LayoutBorderRightWidth {
389 inner: PixelValue::const_px(1),
390 }),
391 )),
392 CssPropertyWithConditions::simple(CssProperty::BorderTopWidth(
393 LayoutBorderTopWidthValue::Exact(LayoutBorderTopWidth {
394 inner: PixelValue::const_px(1),
395 }),
396 )),
397 CssPropertyWithConditions::simple(CssProperty::BorderBottomStyle(
398 StyleBorderBottomStyleValue::Exact(StyleBorderBottomStyle {
399 inner: BorderStyle::Solid,
400 }),
401 )),
402 CssPropertyWithConditions::simple(CssProperty::BorderLeftStyle(
403 StyleBorderLeftStyleValue::Exact(StyleBorderLeftStyle {
404 inner: BorderStyle::Solid,
405 }),
406 )),
407 CssPropertyWithConditions::simple(CssProperty::BorderRightStyle(
408 StyleBorderRightStyleValue::Exact(StyleBorderRightStyle {
409 inner: BorderStyle::Solid,
410 }),
411 )),
412 CssPropertyWithConditions::simple(CssProperty::BorderTopStyle(
413 StyleBorderTopStyleValue::Exact(StyleBorderTopStyle {
414 inner: BorderStyle::Solid,
415 }),
416 )),
417 CssPropertyWithConditions::simple(CssProperty::BorderBottomColor(
418 StyleBorderBottomColorValue::Exact(StyleBorderBottomColor {
419 inner: ColorU {
420 r: 178,
421 g: 178,
422 b: 178,
423 a: 255,
424 },
425 }),
426 )),
427 CssPropertyWithConditions::simple(CssProperty::BorderLeftColor(
428 StyleBorderLeftColorValue::Exact(StyleBorderLeftColor {
429 inner: ColorU {
430 r: 178,
431 g: 178,
432 b: 178,
433 a: 255,
434 },
435 }),
436 )),
437 CssPropertyWithConditions::simple(CssProperty::BorderRightColor(
438 StyleBorderRightColorValue::Exact(StyleBorderRightColor {
439 inner: ColorU {
440 r: 178,
441 g: 178,
442 b: 178,
443 a: 255,
444 },
445 }),
446 )),
447 CssPropertyWithConditions::simple(CssProperty::BorderTopColor(
448 StyleBorderTopColorValue::Exact(StyleBorderTopColor {
449 inner: ColorU {
450 r: 178,
451 g: 178,
452 b: 178,
453 a: 255,
454 },
455 }),
456 )),
457 CssPropertyWithConditions::simple(CssProperty::BackgroundContent(
458 StyleBackgroundContentVecValue::Exact(self.container_background.clone()),
459 )),
460 ]))
461 .with_ids_and_classes({
462 const IDS_AND_CLASSES_10874511710181900075: &[IdOrClass] = &[Class(
463 AzString::from_const_str("__azul-native-progress-bar-container"),
464 )];
465 IdOrClassVec::from_const_slice(IDS_AND_CLASSES_10874511710181900075)
466 })
467 .with_children(DomVec::from_vec(vec![
468 Dom::create_div()
469 .with_css_props(CssPropertyWithConditionsVec::from_vec(vec![
470 CssPropertyWithConditions::simple(CssProperty::Width(
473 LayoutWidthValue::Exact(LayoutWidth::Px(
474 PixelValue::percent(percent_done),
475 )),
476 )),
477 CssPropertyWithConditions::simple(CssProperty::BoxShadowBottom(
478 StyleBoxShadowValue::Exact(BoxOrStatic::heap(StyleBoxShadow {
479 offset_x: PixelValueNoPercent {
480 inner: PixelValue::const_px(0),
481 },
482 offset_y: PixelValueNoPercent {
483 inner: PixelValue::const_px(0),
484 },
485 color: ColorU {
486 r: 0,
487 g: 51,
488 b: 0,
489 a: 51,
490 },
491 blur_radius: PixelValueNoPercent {
492 inner: PixelValue::const_px(15),
493 },
494 spread_radius: PixelValueNoPercent {
495 inner: PixelValue::const_px(12),
496 },
497 clip_mode: BoxShadowClipMode::Inset,
498 })),
499 )),
500 CssPropertyWithConditions::simple(CssProperty::BoxShadowTop(
501 StyleBoxShadowValue::Exact(BoxOrStatic::heap(StyleBoxShadow {
502 offset_x: PixelValueNoPercent {
503 inner: PixelValue::const_px(0),
504 },
505 offset_y: PixelValueNoPercent {
506 inner: PixelValue::const_px(0),
507 },
508 color: ColorU {
509 r: 0,
510 g: 51,
511 b: 0,
512 a: 51,
513 },
514 blur_radius: PixelValueNoPercent {
515 inner: PixelValue::const_px(15),
516 },
517 spread_radius: PixelValueNoPercent {
518 inner: PixelValue::const_px(12),
519 },
520 clip_mode: BoxShadowClipMode::Inset,
521 })),
522 )),
523 CssPropertyWithConditions::simple(CssProperty::BoxShadowRight(
524 StyleBoxShadowValue::Exact(BoxOrStatic::heap(StyleBoxShadow {
525 offset_x: PixelValueNoPercent {
526 inner: PixelValue::const_px(0),
527 },
528 offset_y: PixelValueNoPercent {
529 inner: PixelValue::const_px(0),
530 },
531 color: ColorU {
532 r: 0,
533 g: 51,
534 b: 0,
535 a: 51,
536 },
537 blur_radius: PixelValueNoPercent {
538 inner: PixelValue::const_px(15),
539 },
540 spread_radius: PixelValueNoPercent {
541 inner: PixelValue::const_px(12),
542 },
543 clip_mode: BoxShadowClipMode::Inset,
544 })),
545 )),
546 CssPropertyWithConditions::simple(CssProperty::BoxShadowLeft(
547 StyleBoxShadowValue::Exact(BoxOrStatic::heap(StyleBoxShadow {
548 offset_x: PixelValueNoPercent {
549 inner: PixelValue::const_px(0),
550 },
551 offset_y: PixelValueNoPercent {
552 inner: PixelValue::const_px(0),
553 },
554 color: ColorU {
555 r: 0,
556 g: 51,
557 b: 0,
558 a: 51,
559 },
560 blur_radius: PixelValueNoPercent {
561 inner: PixelValue::const_px(15),
562 },
563 spread_radius: PixelValueNoPercent {
564 inner: PixelValue::const_px(12),
565 },
566 clip_mode: BoxShadowClipMode::Inset,
567 })),
568 )),
569 CssPropertyWithConditions::simple(CssProperty::BorderBottomRightRadius(
570 StyleBorderBottomRightRadiusValue::Exact(
571 StyleBorderBottomRightRadius {
572 inner: PixelValue::const_px(1),
573 },
574 ),
575 )),
576 CssPropertyWithConditions::simple(CssProperty::BorderBottomLeftRadius(
577 StyleBorderBottomLeftRadiusValue::Exact(StyleBorderBottomLeftRadius {
578 inner: PixelValue::const_px(1),
579 }),
580 )),
581 CssPropertyWithConditions::simple(CssProperty::BorderTopRightRadius(
582 StyleBorderTopRightRadiusValue::Exact(StyleBorderTopRightRadius {
583 inner: PixelValue::const_px(1),
584 }),
585 )),
586 CssPropertyWithConditions::simple(CssProperty::BorderTopLeftRadius(
587 StyleBorderTopLeftRadiusValue::Exact(StyleBorderTopLeftRadius {
588 inner: PixelValue::const_px(1),
589 }),
590 )),
591 CssPropertyWithConditions::simple(CssProperty::BackgroundContent(
592 StyleBackgroundContentVecValue::Exact(self.bar_background),
593 )),
594 ]))
595 .with_ids_and_classes({
596 const IDS_AND_CLASSES_16512648314570682783: &[IdOrClass] = &[Class(
597 AzString::from_const_str("__azul-native-progress-bar-bar"),
598 )];
599 IdOrClassVec::from_const_slice(IDS_AND_CLASSES_16512648314570682783)
600 }),
601 Dom::create_div()
602 .with_css_props(CssPropertyWithConditionsVec::from_vec(vec![
603 CssPropertyWithConditions::simple(CssProperty::Width(
606 LayoutWidthValue::Exact(LayoutWidth::Px(
607 PixelValue::percent(100.0 - percent_done),
608 )),
609 )),
610 ]))
611 .with_ids_and_classes({
612 const IDS_AND_CLASSES_2492405364126620395: &[IdOrClass] = &[Class(
613 AzString::from_const_str("__azul-native-progress-bar-remaining"),
614 )];
615 IdOrClassVec::from_const_slice(IDS_AND_CLASSES_2492405364126620395)
616 }),
617 ]))
618 }
619}
620
621#[cfg(test)]
622#[allow(
623 clippy::float_cmp,
624 clippy::cast_precision_loss,
625 clippy::cast_possible_truncation,
626 clippy::unreadable_literal,
627 clippy::too_many_lines
628)]
629mod autotest_generated {
630 use std::collections::HashSet;
631
632 use azul_core::dom::NodeType;
633
634 use super::*;
635
636 const ADVERSARIAL_PERCENTS: [f32; 16] = [
649 0.0,
650 -0.0,
651 1.0,
652 50.0,
653 100.0,
654 -1.0,
655 101.0,
656 0.001,
657 -0.001,
658 f32::EPSILON,
659 f32::MIN_POSITIVE,
660 -f32::MIN_POSITIVE,
661 f32::MAX,
662 f32::MIN,
663 f32::INFINITY,
664 f32::NEG_INFINITY,
665 ];
666
667 fn adversarial_heights() -> Vec<PixelValue> {
671 vec![
672 PixelValue::zero(),
673 PixelValue::const_px(0),
674 PixelValue::px(-0.0),
675 PixelValue::px(-1.0),
676 PixelValue::px(0.001),
677 PixelValue::px(f32::MAX),
678 PixelValue::px(f32::MIN),
679 PixelValue::px(f32::INFINITY),
680 PixelValue::px(f32::NEG_INFINITY),
681 PixelValue::px(f32::NAN),
682 PixelValue::percent(100.0),
683 PixelValue::em(0.001),
684 PixelValue::const_px(isize::MAX / 1000),
688 ]
689 }
690
691 fn raw(pv: PixelValue) -> isize {
695 pv.number.number()
696 }
697
698 fn create_gradient_ptrs() -> (
713 *const StyleBackgroundContent,
714 *const StyleBackgroundContent,
715 ) {
716 let pb = ProgressBar::create(0.0);
717 (pb.bar_background.as_ptr(), pb.container_background.as_ptr())
718 }
719
720 fn solid(n: usize) -> StyleBackgroundContentVec {
724 StyleBackgroundContentVec::from_vec(
725 (0..n)
726 .map(|i| {
727 StyleBackgroundContent::Color(ColorU {
728 r: (i % 256) as u8,
729 g: 1,
730 b: 2,
731 a: 255,
732 })
733 })
734 .collect(),
735 )
736 }
737
738 fn kids(dom: &Dom) -> &[Dom] {
739 dom.children.as_ref()
740 }
741
742 fn bar(dom: &Dom) -> &Dom {
744 &kids(dom)[0]
745 }
746
747 fn remaining(dom: &Dom) -> &Dom {
749 &kids(dom)[1]
750 }
751
752 fn inline_props(dom: &Dom) -> Vec<CssProperty> {
754 dom.root
755 .style
756 .iter_inline_properties()
757 .map(|(p, _)| p.clone())
758 .collect()
759 }
760
761 fn classes(dom: &Dom) -> Vec<String> {
763 dom.root
764 .get_ids_and_classes()
765 .as_ref()
766 .iter()
767 .filter_map(|c| match c {
768 IdOrClass::Class(s) => Some(s.as_str().to_string()),
769 IdOrClass::Id(_) => None,
770 })
771 .collect()
772 }
773
774 fn width_of(dom: &Dom) -> Option<PixelValue> {
775 dom.root
776 .style
777 .iter_inline_properties()
778 .find_map(|(p, _)| match p {
779 CssProperty::Width(v) => match v.get_property() {
780 Some(LayoutWidth::Px(pv)) => Some(*pv),
781 Some(other) => panic!("the progress bar must size in lengths, got {other:?}"),
782 None => None,
783 },
784 _ => None,
785 })
786 }
787
788 fn height_of(dom: &Dom) -> Option<PixelValue> {
789 dom.root
790 .style
791 .iter_inline_properties()
792 .find_map(|(p, _)| match p {
793 CssProperty::Height(v) => match v.get_property() {
794 Some(LayoutHeight::Px(pv)) => Some(*pv),
795 Some(other) => panic!("the progress bar must size in lengths, got {other:?}"),
796 None => None,
797 },
798 _ => None,
799 })
800 }
801
802 fn background_of(dom: &Dom) -> Option<Vec<StyleBackgroundContent>> {
804 dom.root
805 .style
806 .iter_inline_properties()
807 .find_map(|(p, _)| match p {
808 CssProperty::BackgroundContent(v) => {
809 v.get_property().map(|b| b.as_ref().to_vec())
810 }
811 _ => None,
812 })
813 }
814
815 fn background_ptr(dom: &Dom) -> Option<*const StyleBackgroundContent> {
818 dom.root
819 .style
820 .iter_inline_properties()
821 .find_map(|(p, _)| match p {
822 CssProperty::BackgroundContent(v) => {
823 v.get_property().map(StyleBackgroundContentVec::as_ptr)
824 }
825 _ => None,
826 })
827 }
828
829 fn lengths_of(p: &CssProperty) -> Vec<PixelValue> {
832 let one = |pv: Option<PixelValue>| pv.into_iter().collect::<Vec<_>>();
833 match p {
834 CssProperty::BorderBottomWidth(v) => one(v.get_property().map(|x| x.inner)),
835 CssProperty::BorderLeftWidth(v) => one(v.get_property().map(|x| x.inner)),
836 CssProperty::BorderRightWidth(v) => one(v.get_property().map(|x| x.inner)),
837 CssProperty::BorderTopWidth(v) => one(v.get_property().map(|x| x.inner)),
838 CssProperty::BorderBottomRightRadius(v) => one(v.get_property().map(|x| x.inner)),
839 CssProperty::BorderBottomLeftRadius(v) => one(v.get_property().map(|x| x.inner)),
840 CssProperty::BorderTopRightRadius(v) => one(v.get_property().map(|x| x.inner)),
841 CssProperty::BorderTopLeftRadius(v) => one(v.get_property().map(|x| x.inner)),
842 _ => Vec::new(),
843 }
844 }
845
846 #[test]
851 fn create_stores_the_percentage_bit_for_bit_and_never_normalises_it() {
852 for p in ADVERSARIAL_PERCENTS {
855 let pb = ProgressBar::create(p);
856 assert_eq!(
857 pb.progressbar_state.percent_done.to_bits(),
858 p.to_bits(),
859 "create() rewrote the percentage {p}",
860 );
861 assert!(
862 !pb.progressbar_state.display_percentage,
863 "a fresh progress bar must not opt into the percentage label",
864 );
865 }
866
867 let nan = ProgressBar::create(f32::NAN);
869 assert!(
870 nan.progressbar_state.percent_done.is_nan(),
871 "create() silently replaced a NaN percentage",
872 );
873 }
874
875 #[test]
876 fn create_defaults_to_a_15px_height() {
877 let pb = ProgressBar::create(50.0);
878 assert_eq!(pb.height.metric, SizeMetric::Px, "the default height must be absolute");
879 assert_eq!(raw(pb.height), 15_000, "the default height is 15px in 1/1000 units");
880 }
881
882 #[test]
883 fn create_borrows_the_static_gradients_instead_of_allocating_them() {
884 let pb = ProgressBar::create(50.0);
885 let (bar_ptr, container_ptr) = create_gradient_ptrs();
886
887 assert_eq!(
892 pb.bar_background.as_ptr(),
893 bar_ptr,
894 "the bar gradient stopped being shared with the static slice",
895 );
896 assert_eq!(
897 pb.container_background.as_ptr(),
898 container_ptr,
899 "the container gradient stopped being shared with the static slice",
900 );
901 assert_eq!(
904 pb.bar_background.as_ref(),
905 STYLE_BACKGROUND_CONTENT_2688422633177340412_ITEMS,
906 );
907 assert_eq!(
908 pb.container_background.as_ref(),
909 STYLE_BACKGROUND_CONTENT_14586281004485141058_ITEMS,
910 );
911 assert_eq!(pb.bar_background.len(), 1);
912 assert_eq!(pb.container_background.len(), 1);
913 assert_eq!(
914 pb.bar_background.capacity(),
915 pb.bar_background.len(),
916 "a borrowed buffer must report capacity == len, or the free path over-reads",
917 );
918
919 for i in 0..10_000 {
923 let pb = ProgressBar::create(i as f32);
924 assert_eq!(pb.bar_background.len(), 1);
925 assert_eq!(pb.bar_background.as_ptr(), bar_ptr);
926 }
927 }
928
929 #[test]
930 fn create_backgrounds_are_the_declared_gradients_with_sorted_stops() {
931 let pb = ProgressBar::create(0.0);
932 assert_eq!(
933 pb.bar_background.as_ref(),
934 STYLE_BACKGROUND_CONTENT_2688422633177340412_ITEMS,
935 );
936 assert_eq!(
937 pb.container_background.as_ref(),
938 STYLE_BACKGROUND_CONTENT_14586281004485141058_ITEMS,
939 );
940
941 for bg in [&pb.bar_background, &pb.container_background] {
942 match &bg.as_ref()[0] {
943 StyleBackgroundContent::LinearGradient(g) => {
944 let stops = g.stops.as_ref();
945 assert_eq!(stops.len(), 7, "a gradient lost or gained a colour stop");
946
947 let mut prev = f32::NEG_INFINITY;
950 for s in stops {
951 let offset = s.offset.normalized() * 100.0;
952 assert!(
953 (0.0..=100.0).contains(&offset),
954 "gradient stop outside 0%..100%: {offset}",
955 );
956 assert!(
957 offset >= prev,
958 "gradient stops are not sorted: {offset} follows {prev}",
959 );
960 prev = offset;
961 }
962 }
963 other => panic!("the progress bar gradients degraded to {other:?}"),
964 }
965 }
966 }
967
968 #[test]
969 fn create_is_usable_in_const_context() {
970 const CONST_BAR: ProgressBar = ProgressBar::create(12.5);
974
975 assert_eq!(CONST_BAR.progressbar_state.percent_done, 12.5);
976 assert_eq!(raw(CONST_BAR.height), 15_000);
977 assert_eq!(CONST_BAR.bar_background.len(), 1);
978 }
979
980 #[test]
985 fn swap_with_default_returns_the_previous_bar_and_installs_a_pristine_one() {
986 for p in ADVERSARIAL_PERCENTS {
987 let mut pb = ProgressBar::create(p).with_height(PixelValue::const_px(99));
988 let prev = pb.swap_with_default();
989
990 assert_eq!(
991 prev.progressbar_state.percent_done.to_bits(),
992 p.to_bits(),
993 "the returned bar is not the one that was there ({p})",
994 );
995 assert_eq!(raw(prev.height), 99_000, "the returned bar lost its height");
996
997 assert_eq!(
998 pb.progressbar_state.percent_done.to_bits(),
999 0_u32,
1000 "the replacement must be +0.0 — a -0.0 would encode with the sign bit set",
1001 );
1002 assert_eq!(raw(pb.height), 15_000, "the replacement must use the default height");
1003 assert_eq!(
1004 pb.bar_background.as_ptr(),
1005 create_gradient_ptrs().0,
1006 "the replacement must borrow the static gradient again",
1007 );
1008 }
1009 }
1010
1011 #[test]
1012 fn swap_with_default_keeps_a_nan_percentage_and_moves_owned_memory_out() {
1013 let owned = solid(4);
1014 let ptr = owned.as_ptr();
1015 let mut pb = ProgressBar::create(f32::NAN).with_bar_background(owned);
1016
1017 let prev = pb.swap_with_default();
1018
1019 assert!(
1020 prev.progressbar_state.percent_done.is_nan(),
1021 "a NaN percentage did not survive the swap",
1022 );
1023 assert_eq!(
1024 prev.bar_background.as_ptr(),
1025 ptr,
1026 "the heap buffer was copied instead of moved out",
1027 );
1028 assert_eq!(prev.bar_background.len(), 4);
1029
1030 drop(prev);
1034 assert_eq!(
1035 pb.bar_background.as_ref(),
1036 STYLE_BACKGROUND_CONTENT_2688422633177340412_ITEMS,
1037 "the swapped-in bar aliased the memory that was just freed",
1038 );
1039 assert_eq!(pb.progressbar_state.percent_done, 0.0);
1040 }
1041
1042 #[test]
1043 fn repeated_swaps_never_alias_or_leak_the_backgrounds() {
1044 let mut pb = ProgressBar::create(1.0);
1045 let (bar_ptr, _) = create_gradient_ptrs();
1046 for i in 0..1_000_usize {
1047 let want = i % 8 + 1;
1048 pb.set_bar_background(solid(want));
1049 let prev = pb.swap_with_default();
1050
1051 assert_eq!(prev.bar_background.len(), want, "round {i} handed back the wrong buffer");
1052 assert_eq!(pb.progressbar_state.percent_done, 0.0);
1053 assert_eq!(pb.bar_background.as_ptr(), bar_ptr);
1054 }
1055 }
1056
1057 #[test]
1062 fn each_background_setter_touches_exactly_one_field() {
1063 let mut pb = ProgressBar::create(50.0);
1064 let bar_ptr = pb.bar_background.as_ptr();
1065 pb.set_container_background(solid(3));
1066 assert_eq!(pb.container_background.len(), 3);
1067 assert_eq!(
1068 pb.bar_background.as_ptr(),
1069 bar_ptr,
1070 "set_container_background clobbered the bar background",
1071 );
1072
1073 let mut pb = ProgressBar::create(50.0);
1074 let container_ptr = pb.container_background.as_ptr();
1075 pb.set_bar_background(solid(5));
1076 assert_eq!(pb.bar_background.len(), 5);
1077 assert_eq!(
1078 pb.container_background.as_ptr(),
1079 container_ptr,
1080 "set_bar_background clobbered the container background",
1081 );
1082 assert_eq!(pb.progressbar_state.percent_done, 50.0);
1083 assert_eq!(raw(pb.height), 15_000);
1084 }
1085
1086 #[test]
1087 fn the_builder_forms_are_exactly_their_setters() {
1088 let a = ProgressBar::create(7.5)
1089 .with_bar_background(solid(3))
1090 .with_container_background(solid(2))
1091 .with_height(PixelValue::px(-4.5));
1092
1093 let mut b = ProgressBar::create(7.5);
1094 b.set_bar_background(solid(3));
1095 b.set_container_background(solid(2));
1096 b.set_height(PixelValue::px(-4.5));
1097
1098 assert_eq!(a.bar_background.as_ref(), b.bar_background.as_ref());
1099 assert_eq!(a.container_background.as_ref(), b.container_background.as_ref());
1100 assert_eq!(a.height, b.height);
1101 assert_eq!(
1102 a.progressbar_state.percent_done,
1103 b.progressbar_state.percent_done,
1104 );
1105 assert_eq!(
1106 a.progressbar_state.display_percentage,
1107 b.progressbar_state.display_percentage,
1108 );
1109 }
1110
1111 #[test]
1112 fn an_empty_background_stays_an_empty_declaration() {
1113 let pb = ProgressBar::create(0.0)
1114 .with_bar_background(StyleBackgroundContentVec::from_vec(Vec::new()))
1115 .with_container_background(StyleBackgroundContentVec::new());
1116
1117 assert!(pb.bar_background.is_empty());
1118 assert_eq!(pb.bar_background.len(), 0);
1119 assert!(pb.container_background.is_empty());
1120
1121 let dom = pb.dom();
1122 assert_eq!(
1123 background_of(bar(&dom)),
1124 Some(Vec::new()),
1125 "an empty background must reach the DOM as an empty layer list, not vanish",
1126 );
1127 assert_eq!(background_of(&dom), Some(Vec::new()));
1128 }
1129
1130 #[test]
1131 fn a_background_with_spare_capacity_keeps_its_allocation_intact() {
1132 let mut v = Vec::with_capacity(64);
1135 v.push(StyleBackgroundContent::Color(ColorU {
1136 r: 1,
1137 g: 2,
1138 b: 3,
1139 a: 4,
1140 }));
1141 let bg = StyleBackgroundContentVec::from_vec(v);
1142 assert_eq!(bg.len(), 1);
1143 assert!(bg.capacity() >= 64, "from_vec lost the spare capacity: {}", bg.capacity());
1144
1145 let pb = ProgressBar::create(0.0).with_container_background(bg);
1146 assert_eq!(pb.container_background.len(), 1);
1147 assert!(
1148 pb.container_background.capacity() >= 64,
1149 "the setter rewrote the buffer's capacity",
1150 );
1151 }
1152
1153 #[test]
1154 fn a_very_large_background_is_neither_truncated_nor_copied() {
1155 let big = solid(10_000);
1156 let ptr = big.as_ptr();
1157 let pb = ProgressBar::create(50.0).with_bar_background(big);
1158 assert_eq!(pb.bar_background.len(), 10_000);
1159 assert_eq!(pb.bar_background.as_ptr(), ptr, "the setter deep-copied a 10k-layer background");
1160
1161 let dom = pb.dom();
1162 assert_eq!(
1163 background_of(bar(&dom)).map(|v| v.len()),
1164 Some(10_000),
1165 "the background was truncated on the way into the DOM",
1166 );
1167 }
1168
1169 #[test]
1170 fn overwriting_a_background_releases_the_previous_one() {
1171 let n = 500_usize;
1174 let mut pb = ProgressBar::create(0.0);
1175 for i in 1..=n {
1176 pb.set_bar_background(solid(i % 16 + 1));
1177 pb.set_container_background(solid(i % 4 + 1));
1178 }
1179 assert_eq!(pb.bar_background.len(), n % 16 + 1);
1182 assert_eq!(pb.container_background.len(), n % 4 + 1);
1183 }
1184
1185 #[test]
1186 fn cloning_deep_copies_owned_backgrounds_but_shares_static_ones() {
1187 let pb = ProgressBar::create(3.0).with_bar_background(solid(4));
1188 let copy = pb.clone();
1189
1190 assert_ne!(
1191 copy.bar_background.as_ptr(),
1192 pb.bar_background.as_ptr(),
1193 "Clone shared an owned heap buffer — dropping both would double-free it",
1194 );
1195 assert_eq!(
1196 copy.container_background.as_ptr(),
1197 pb.container_background.as_ptr(),
1198 "the static gradient is never freed, so the clone should keep sharing it",
1199 );
1200
1201 drop(pb);
1202 assert_eq!(copy.bar_background.len(), 4);
1203 assert_eq!(
1204 copy.bar_background.as_ref()[3],
1205 StyleBackgroundContent::Color(ColorU {
1206 r: 3,
1207 g: 1,
1208 b: 2,
1209 a: 255,
1210 }),
1211 "the clone read back garbage after the original was dropped",
1212 );
1213 }
1214
1215 #[test]
1220 fn set_height_stores_every_pixel_value_verbatim() {
1221 for h in adversarial_heights() {
1222 let mut pb = ProgressBar::create(0.0);
1223 pb.set_height(h);
1224 assert_eq!(pb.height.metric, h.metric, "set_height changed the unit of {h:?}");
1225 assert_eq!(raw(pb.height), raw(h), "set_height re-encoded {h:?}");
1226 assert_eq!(pb.progressbar_state.percent_done, 0.0);
1228 assert_eq!(pb.bar_background.len(), 1);
1229 }
1230 }
1231
1232 #[test]
1233 fn an_out_of_range_height_saturates_instead_of_wrapping() {
1234 let mut pb = ProgressBar::create(0.0);
1235
1236 pb.set_height(PixelValue::px(f32::MAX));
1240 assert_eq!(raw(pb.height), isize::MAX, "an overflowing height wrapped instead of saturating");
1241 assert!(pb.height.number.get().is_finite(), "the saturated height decoded to a non-finite f32");
1242
1243 pb.set_height(PixelValue::px(f32::INFINITY));
1244 assert_eq!(raw(pb.height), isize::MAX);
1245
1246 pb.set_height(PixelValue::px(f32::MIN));
1247 assert_eq!(raw(pb.height), isize::MIN);
1248
1249 pb.set_height(PixelValue::px(f32::NEG_INFINITY));
1250 assert_eq!(raw(pb.height), isize::MIN);
1251
1252 pb.set_height(PixelValue::px(f32::NAN));
1253 assert_eq!(raw(pb.height), 0, "a NaN height must land on 0, not on an arbitrary integer");
1254
1255 pb.set_height(PixelValue::px(-0.0));
1256 assert_eq!(raw(pb.height), 0, "-0.0 must encode to the same 0 as +0.0");
1257
1258 pb.set_height(PixelValue::px(0.0004));
1260 assert_eq!(raw(pb.height), 0);
1261 pb.set_height(PixelValue::px(f32::MIN_POSITIVE));
1262 assert_eq!(raw(pb.height), 0);
1263 }
1264
1265 #[test]
1266 fn with_height_is_set_height_and_leaves_the_rest_alone() {
1267 for h in adversarial_heights() {
1268 let a = ProgressBar::create(42.0).with_height(h);
1269 let mut b = ProgressBar::create(42.0);
1270 b.set_height(h);
1271
1272 assert_eq!(a.height, b.height, "with_height disagreed with set_height for {h:?}");
1273 assert_eq!(raw(a.height), raw(h));
1274 assert_eq!(a.progressbar_state.percent_done, 42.0);
1275 assert_eq!(
1276 a.bar_background.as_ptr(),
1277 b.bar_background.as_ptr(),
1278 "with_height reallocated the background",
1279 );
1280 }
1281 }
1282
1283 #[test]
1288 fn dom_is_a_container_div_with_exactly_two_leaf_children() {
1289 let dom = ProgressBar::create(50.0).dom();
1290
1291 assert!(matches!(dom.root.get_node_type(), NodeType::Div));
1292 assert_eq!(kids(&dom).len(), 2, "the progress bar must render bar + remaining");
1293 assert!(kids(bar(&dom)).is_empty(), "the bar must stay a leaf");
1294 assert!(kids(remaining(&dom)).is_empty(), "the remaining space must stay a leaf");
1295
1296 assert_eq!(dom.estimated_total_children, 2);
1299
1300 assert_eq!(classes(&dom), vec!["__azul-native-progress-bar-container".to_string()]);
1301 assert_eq!(classes(bar(&dom)), vec!["__azul-native-progress-bar-bar".to_string()]);
1302 assert_eq!(
1303 classes(remaining(&dom)),
1304 vec!["__azul-native-progress-bar-remaining".to_string()],
1305 );
1306 }
1307
1308 #[test]
1309 fn dom_clamps_every_out_of_range_percentage_into_zero_to_one_hundred() {
1310 const CASES: [(f32, isize, isize); 12] = [
1312 (0.0, 0, 100_000),
1313 (-0.0, 0, 100_000),
1314 (50.0, 50_000, 50_000),
1315 (100.0, 100_000, 0),
1316 (-1.0, 0, 100_000),
1317 (101.0, 100_000, 0),
1318 (-1e30, 0, 100_000),
1319 (1e30, 100_000, 0),
1320 (f32::MAX, 100_000, 0),
1321 (f32::MIN, 0, 100_000),
1322 (f32::INFINITY, 100_000, 0),
1323 (f32::NEG_INFINITY, 0, 100_000),
1324 ];
1325
1326 for (input, bar_width, remaining_width) in CASES {
1327 let dom = ProgressBar::create(input).dom();
1328 let b = width_of(bar(&dom)).expect("the bar must declare a width");
1329 let r = width_of(remaining(&dom)).expect("the remaining space must declare a width");
1330
1331 assert_eq!(b.metric, SizeMetric::Percent, "the bar must size in %, not {:?}", b.metric);
1332 assert_eq!(r.metric, SizeMetric::Percent, "the gap must size in %, not {:?}", r.metric);
1333 assert_eq!(raw(b), bar_width, "bar width for input {input}");
1334 assert_eq!(raw(r), remaining_width, "remaining width for input {input}");
1335 assert!(raw(b) >= 0 && raw(r) >= 0, "a negative width escaped for input {input}");
1336 }
1337 }
1338
1339 #[test]
1340 fn dom_collapses_a_nan_percentage_to_two_empty_children() {
1341 let dom = ProgressBar::create(f32::NAN).dom();
1346 let b = width_of(bar(&dom)).expect("the bar must declare a width");
1347 let r = width_of(remaining(&dom)).expect("the remaining space must declare a width");
1348
1349 assert_eq!(raw(b), 0);
1350 assert_eq!(raw(r), 0);
1351 assert_eq!(b.metric, SizeMetric::Percent);
1352 assert_eq!(r.metric, SizeMetric::Percent);
1353 assert_eq!(kids(&dom).len(), 2, "a NaN percentage must not change the tree shape");
1354 }
1355
1356 #[test]
1357 fn dom_splits_the_container_exactly_for_whole_percentages() {
1358 for i in 0..=100_isize {
1359 let dom = ProgressBar::create(i as f32).dom();
1360 let b = raw(width_of(bar(&dom)).unwrap());
1361 let r = raw(width_of(remaining(&dom)).unwrap());
1362
1363 assert_eq!(b, i * 1000, "the bar is not {i}% wide");
1364 assert_eq!(
1365 b + r,
1366 100_000,
1367 "the two halves do not add up to the container at {i}%",
1368 );
1369 }
1370 }
1371
1372 #[test]
1373 fn dom_loses_at_most_the_encoding_truncation_for_fractional_percentages() {
1374 for p in [
1378 0.0005_f32,
1379 0.5,
1380 1.0 / 3.0,
1381 33.333,
1382 66.667,
1383 99.999,
1384 99.9999,
1385 f32::EPSILON,
1386 f32::MIN_POSITIVE,
1387 ] {
1388 let dom = ProgressBar::create(p).dom();
1389 let b = raw(width_of(bar(&dom)).unwrap());
1390 let r = raw(width_of(remaining(&dom)).unwrap());
1391
1392 assert!(
1393 (0..=100_000).contains(&b) && (0..=100_000).contains(&r),
1394 "a width left 0%..100% for {p}: {b} / {r}",
1395 );
1396 assert!(
1397 (b + r - 100_000).abs() <= 10,
1398 "the two halves drifted apart for {p}: {b} + {r}",
1399 );
1400 }
1401 }
1402
1403 #[test]
1404 fn dom_routes_each_background_to_its_own_node() {
1405 let bar_bg = solid(3);
1406 let container_bg = solid(5);
1407 let bar_ptr = bar_bg.as_ptr();
1408 let container_ptr = container_bg.as_ptr();
1409
1410 let dom = ProgressBar::create(25.0)
1411 .with_bar_background(bar_bg)
1412 .with_container_background(container_bg)
1413 .dom();
1414
1415 assert_eq!(
1416 background_of(&dom).map(|v| v.len()),
1417 Some(5),
1418 "the container lost (or swapped) its background",
1419 );
1420 assert_eq!(
1421 background_of(bar(&dom)).map(|v| v.len()),
1422 Some(3),
1423 "the bar lost (or swapped) its background",
1424 );
1425 assert_eq!(
1426 background_of(remaining(&dom)),
1427 None,
1428 "the remaining space must not paint anything",
1429 );
1430
1431 assert_eq!(
1433 background_ptr(bar(&dom)),
1434 Some(bar_ptr),
1435 "the bar background was copied instead of moved",
1436 );
1437 assert_ne!(
1441 background_ptr(&dom),
1442 Some(container_ptr),
1443 "the DOM kept a pointer into a buffer that `dom()` then freed",
1444 );
1445 }
1446
1447 #[test]
1448 fn dom_forwards_any_height_to_the_container_and_to_nobody_else() {
1449 for h in adversarial_heights() {
1450 let dom = ProgressBar::create(50.0).with_height(h).dom();
1451 let got = height_of(&dom).expect("the container must declare a height");
1452
1453 assert_eq!(got.metric, h.metric, "the height unit changed on the way into the DOM");
1454 assert_eq!(raw(got), raw(h), "the height was re-encoded on the way into the DOM");
1455 assert_eq!(height_of(bar(&dom)), None, "the bar must not declare its own height");
1456 assert_eq!(
1457 height_of(remaining(&dom)),
1458 None,
1459 "the remaining space must not declare its own height",
1460 );
1461 assert_eq!(width_of(&dom), None, "the container must not declare a width");
1462 }
1463 }
1464
1465 #[test]
1466 fn dom_declares_the_expected_style_blocks_and_no_property_twice() {
1467 let dom = ProgressBar::create(50.0).with_bar_background(solid(1)).dom();
1468
1469 assert_eq!(inline_props(&dom).len(), 23, "the container style block drifted");
1470 assert_eq!(inline_props(bar(&dom)).len(), 10, "the bar style block drifted");
1471
1472 let props = inline_props(remaining(&dom));
1473 assert_eq!(props.len(), 1, "the remaining space grew a style block: {props:?}");
1474 assert!(
1475 matches!(&props[0], CssProperty::Width(_)),
1476 "the remaining space must only declare its width",
1477 );
1478
1479 for node in [&dom, bar(&dom), remaining(&dom)] {
1482 let mut seen = HashSet::new();
1483 for p in inline_props(node) {
1484 assert!(
1485 seen.insert(core::mem::discriminant(&p)),
1486 "duplicate declaration of {p:?}",
1487 );
1488 }
1489 }
1490 }
1491
1492 #[test]
1493 fn dom_chrome_lengths_are_all_absolute_pixels() {
1494 let dom = ProgressBar::create(50.0).dom();
1498 for node in [&dom, bar(&dom), remaining(&dom)] {
1499 for p in inline_props(node) {
1500 for length in lengths_of(&p) {
1501 assert_eq!(
1502 length.metric,
1503 SizeMetric::Px,
1504 "{p:?} declares a relative length: {length:?}",
1505 );
1506 }
1507 }
1508 }
1509 }
1510
1511 #[test]
1512 fn dom_ignores_display_percentage() {
1513 let mut with_label = ProgressBar::create(40.0);
1516 with_label.progressbar_state.display_percentage = true;
1517 let without_label = ProgressBar::create(40.0);
1518
1519 assert_eq!(
1520 with_label.dom(),
1521 without_label.dom(),
1522 "display_percentage started changing the tree",
1523 );
1524 }
1525
1526 #[test]
1527 fn dom_is_deterministic_for_equal_inputs() {
1528 let a = ProgressBar::create(37.5)
1529 .with_bar_background(solid(2))
1530 .with_height(PixelValue::px(7.25))
1531 .dom();
1532 let b = ProgressBar::create(37.5)
1533 .with_bar_background(solid(2))
1534 .with_height(PixelValue::px(7.25))
1535 .dom();
1536
1537 assert_eq!(a, b, "two identically-built progress bars rendered differently");
1538 }
1539
1540 #[test]
1541 fn dom_survives_every_extreme_percentage_and_background_size() {
1542 for p in ADVERSARIAL_PERCENTS.into_iter().chain([f32::NAN]) {
1543 for layers in [0_usize, 1, 64] {
1544 let dom = ProgressBar::create(p)
1545 .with_bar_background(solid(layers))
1546 .with_container_background(solid(layers))
1547 .with_height(PixelValue::px(f32::MAX))
1548 .dom();
1549
1550 assert_eq!(kids(&dom).len(), 2, "shape changed for {p} / {layers} layers");
1551 assert_eq!(dom.estimated_total_children, 2);
1552 assert_eq!(background_of(bar(&dom)).map(|v| v.len()), Some(layers));
1553 assert_eq!(background_of(&dom).map(|v| v.len()), Some(layers));
1554
1555 let b = width_of(bar(&dom)).expect("the bar must declare a width");
1556 assert_eq!(b.metric, SizeMetric::Percent);
1557 assert!((0..=100_000).contains(&raw(b)), "width out of range for {p}");
1558 }
1559 }
1560 }
1561}