1
//! Native progress bar widget with customizable backgrounds, height, and
2
//! gradient styling. The main type is [`ProgressBar`], which is rendered
3
//! into a DOM via [`ProgressBar::dom()`].
4

            
5
use azul_core::dom::{Dom, IdOrClass, IdOrClass::Class, IdOrClassVec};
6
#[allow(clippy::wildcard_imports)] // widget/render module pulls in the css property/value types it builds with
7
use azul_css::{
8
    dynamic_selector::{CssPropertyWithConditions, CssPropertyWithConditionsVec},
9
    props::{
10
        basic::*,
11
        layout::*,
12
        property::{CssProperty, *},
13
        style::*,
14
    },
15
    *,
16
};
17
use azul_css::css::BoxOrStatic;
18

            
19
const 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
    })];
30
const 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
    })];
41
const 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
];
106
const 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
/// A native progress bar widget with customizable bar/container backgrounds and height.
173
#[derive(Debug, Clone)]
174
#[repr(C)]
175
pub struct ProgressBar {
176
    pub progressbar_state: ProgressBarState,
177
    pub height: PixelValue,
178
    pub bar_background: StyleBackgroundContentVec,
179
    pub container_background: StyleBackgroundContentVec,
180
}
181

            
182
/// Internal state for a [`ProgressBar`], tracking completion percentage.
183
#[derive(Copy, Debug, Clone)]
184
#[repr(C)]
185
pub struct ProgressBarState {
186
    pub percent_done: f32,
187
    pub display_percentage: bool,
188
}
189

            
190
impl ProgressBar {
191
    /// Creates a new progress bar with the given completion percentage (0.0 to 100.0).
192
    #[inline]
193
11319
    #[must_use] pub const fn create(percent_done: f32) -> Self {
194
11319
        Self {
195
11319
            progressbar_state: ProgressBarState {
196
11319
                percent_done,
197
11319
                display_percentage: false,
198
11319
            },
199
11319
            height: PixelValue::const_px(15),
200
11319
            bar_background: StyleBackgroundContentVec::from_const_slice(
201
11319
                STYLE_BACKGROUND_CONTENT_2688422633177340412_ITEMS,
202
11319
            ),
203
11319
            container_background: StyleBackgroundContentVec::from_const_slice(
204
11319
                STYLE_BACKGROUND_CONTENT_14586281004485141058_ITEMS,
205
11319
            ),
206
11319
        }
207
11319
    }
208

            
209
    /// Replaces `self` with a default (0%) progress bar, returning the previous value.
210
    #[inline]
211
    #[must_use]
212
1017
    pub const fn swap_with_default(&mut self) -> Self {
213
1017
        let mut s = Self::create(0.0);
214
1017
        core::mem::swap(&mut s, self);
215
1017
        s
216
1017
    }
217

            
218
557
    pub fn set_container_background(&mut self, background: StyleBackgroundContentVec) {
219
557
        self.container_background = background;
220
557
    }
221

            
222
55
    #[must_use] pub fn with_container_background(mut self, background: StyleBackgroundContentVec) -> Self {
223
55
        self.set_container_background(background);
224
55
        self
225
55
    }
226

            
227
1562
    pub fn set_bar_background(&mut self, background: StyleBackgroundContentVec) {
228
1562
        self.bar_background = background;
229
1562
    }
230

            
231
60
    #[must_use] pub fn with_bar_background(mut self, background: StyleBackgroundContentVec) -> Self {
232
60
        self.set_bar_background(background);
233
60
        self
234
60
    }
235

            
236
131
    pub const fn set_height(&mut self, height: PixelValue) {
237
131
        self.height = height;
238
131
    }
239

            
240
96
    #[must_use] pub const fn with_height(mut self, height: PixelValue) -> Self {
241
96
        self.set_height(height);
242
96
        self
243
96
    }
244

            
245
    /// Renders this progress bar into a [`Dom`] tree consisting of a container div
246
    /// with two children: the filled bar and the remaining empty space.
247
    #[allow(clippy::too_many_lines)] // large but cohesive: single-purpose layout/render/parse routine (one branch per case)
248
199
    #[must_use] pub fn dom(self) -> Dom {
249
        use azul_core::dom::DomVec;
250

            
251
        // Use percentage widths for the progress bar and remaining space.
252
        // The container uses flex-direction: row, and we set explicit widths
253
        // on the children using CSS percentages.
254
199
        let percent_done = self.progressbar_state.percent_done.clamp(0.0, 100.0);
255

            
256
199
        Dom::create_div()
257
199
            .with_css_props(CssPropertyWithConditionsVec::from_vec(vec![
258
                // .__azul-native-progress-bar-container
259
199
                CssPropertyWithConditions::simple(CssProperty::Height(LayoutHeightValue::Exact(
260
199
                    LayoutHeight::Px(self.height),
261
199
                ))),
262
199
                CssPropertyWithConditions::simple(CssProperty::FlexDirection(
263
199
                    LayoutFlexDirectionValue::Exact(LayoutFlexDirection::Row),
264
199
                )),
265
199
                CssPropertyWithConditions::simple(CssProperty::BoxShadowBottom(
266
199
                    StyleBoxShadowValue::Exact(BoxOrStatic::heap(StyleBoxShadow {
267
199
                        offset_x: PixelValueNoPercent {
268
199
                            inner: PixelValue::const_px(0),
269
199
                        },
270
199
                        offset_y: PixelValueNoPercent {
271
199
                            inner: PixelValue::const_px(0),
272
199
                        },
273
199
                        color: ColorU {
274
199
                            r: 0,
275
199
                            g: 0,
276
199
                            b: 0,
277
199
                            a: 9,
278
199
                        },
279
199
                        blur_radius: PixelValueNoPercent {
280
199
                            inner: PixelValue::const_px(15),
281
199
                        },
282
199
                        spread_radius: PixelValueNoPercent {
283
199
                            inner: PixelValue::const_px(2),
284
199
                        },
285
199
                        clip_mode: BoxShadowClipMode::Inset,
286
199
                    })),
287
199
                )),
288
199
                CssPropertyWithConditions::simple(CssProperty::BoxShadowTop(
289
199
                    StyleBoxShadowValue::Exact(BoxOrStatic::heap(StyleBoxShadow {
290
199
                        offset_x: PixelValueNoPercent {
291
199
                            inner: PixelValue::const_px(0),
292
199
                        },
293
199
                        offset_y: PixelValueNoPercent {
294
199
                            inner: PixelValue::const_px(0),
295
199
                        },
296
199
                        color: ColorU {
297
199
                            r: 0,
298
199
                            g: 0,
299
199
                            b: 0,
300
199
                            a: 9,
301
199
                        },
302
199
                        blur_radius: PixelValueNoPercent {
303
199
                            inner: PixelValue::const_px(15),
304
199
                        },
305
199
                        spread_radius: PixelValueNoPercent {
306
199
                            inner: PixelValue::const_px(2),
307
199
                        },
308
199
                        clip_mode: BoxShadowClipMode::Inset,
309
199
                    })),
310
199
                )),
311
199
                CssPropertyWithConditions::simple(CssProperty::BoxShadowRight(
312
199
                    StyleBoxShadowValue::Exact(BoxOrStatic::heap(StyleBoxShadow {
313
199
                        offset_x: PixelValueNoPercent {
314
199
                            inner: PixelValue::const_px(0),
315
199
                        },
316
199
                        offset_y: PixelValueNoPercent {
317
199
                            inner: PixelValue::const_px(0),
318
199
                        },
319
199
                        color: ColorU {
320
199
                            r: 0,
321
199
                            g: 0,
322
199
                            b: 0,
323
199
                            a: 9,
324
199
                        },
325
199
                        blur_radius: PixelValueNoPercent {
326
199
                            inner: PixelValue::const_px(15),
327
199
                        },
328
199
                        spread_radius: PixelValueNoPercent {
329
199
                            inner: PixelValue::const_px(2),
330
199
                        },
331
199
                        clip_mode: BoxShadowClipMode::Inset,
332
199
                    })),
333
199
                )),
334
199
                CssPropertyWithConditions::simple(CssProperty::BoxShadowLeft(
335
199
                    StyleBoxShadowValue::Exact(BoxOrStatic::heap(StyleBoxShadow {
336
199
                        offset_x: PixelValueNoPercent {
337
199
                            inner: PixelValue::const_px(0),
338
199
                        },
339
199
                        offset_y: PixelValueNoPercent {
340
199
                            inner: PixelValue::const_px(0),
341
199
                        },
342
199
                        color: ColorU {
343
199
                            r: 0,
344
199
                            g: 0,
345
199
                            b: 0,
346
199
                            a: 9,
347
199
                        },
348
199
                        blur_radius: PixelValueNoPercent {
349
199
                            inner: PixelValue::const_px(15),
350
199
                        },
351
199
                        spread_radius: PixelValueNoPercent {
352
199
                            inner: PixelValue::const_px(2),
353
199
                        },
354
199
                        clip_mode: BoxShadowClipMode::Inset,
355
199
                    })),
356
199
                )),
357
199
                CssPropertyWithConditions::simple(CssProperty::BorderBottomRightRadius(
358
199
                    StyleBorderBottomRightRadiusValue::Exact(StyleBorderBottomRightRadius {
359
199
                        inner: PixelValue::const_px(3),
360
199
                    }),
361
199
                )),
362
199
                CssPropertyWithConditions::simple(CssProperty::BorderBottomLeftRadius(
363
199
                    StyleBorderBottomLeftRadiusValue::Exact(StyleBorderBottomLeftRadius {
364
199
                        inner: PixelValue::const_px(3),
365
199
                    }),
366
199
                )),
367
199
                CssPropertyWithConditions::simple(CssProperty::BorderTopRightRadius(
368
199
                    StyleBorderTopRightRadiusValue::Exact(StyleBorderTopRightRadius {
369
199
                        inner: PixelValue::const_px(3),
370
199
                    }),
371
199
                )),
372
199
                CssPropertyWithConditions::simple(CssProperty::BorderTopLeftRadius(
373
199
                    StyleBorderTopLeftRadiusValue::Exact(StyleBorderTopLeftRadius {
374
199
                        inner: PixelValue::const_px(3),
375
199
                    }),
376
199
                )),
377
199
                CssPropertyWithConditions::simple(CssProperty::BorderBottomWidth(
378
199
                    LayoutBorderBottomWidthValue::Exact(LayoutBorderBottomWidth {
379
199
                        inner: PixelValue::const_px(1),
380
199
                    }),
381
199
                )),
382
199
                CssPropertyWithConditions::simple(CssProperty::BorderLeftWidth(
383
199
                    LayoutBorderLeftWidthValue::Exact(LayoutBorderLeftWidth {
384
199
                        inner: PixelValue::const_px(1),
385
199
                    }),
386
199
                )),
387
199
                CssPropertyWithConditions::simple(CssProperty::BorderRightWidth(
388
199
                    LayoutBorderRightWidthValue::Exact(LayoutBorderRightWidth {
389
199
                        inner: PixelValue::const_px(1),
390
199
                    }),
391
199
                )),
392
199
                CssPropertyWithConditions::simple(CssProperty::BorderTopWidth(
393
199
                    LayoutBorderTopWidthValue::Exact(LayoutBorderTopWidth {
394
199
                        inner: PixelValue::const_px(1),
395
199
                    }),
396
199
                )),
397
199
                CssPropertyWithConditions::simple(CssProperty::BorderBottomStyle(
398
199
                    StyleBorderBottomStyleValue::Exact(StyleBorderBottomStyle {
399
199
                        inner: BorderStyle::Solid,
400
199
                    }),
401
199
                )),
402
199
                CssPropertyWithConditions::simple(CssProperty::BorderLeftStyle(
403
199
                    StyleBorderLeftStyleValue::Exact(StyleBorderLeftStyle {
404
199
                        inner: BorderStyle::Solid,
405
199
                    }),
406
199
                )),
407
199
                CssPropertyWithConditions::simple(CssProperty::BorderRightStyle(
408
199
                    StyleBorderRightStyleValue::Exact(StyleBorderRightStyle {
409
199
                        inner: BorderStyle::Solid,
410
199
                    }),
411
199
                )),
412
199
                CssPropertyWithConditions::simple(CssProperty::BorderTopStyle(
413
199
                    StyleBorderTopStyleValue::Exact(StyleBorderTopStyle {
414
199
                        inner: BorderStyle::Solid,
415
199
                    }),
416
199
                )),
417
199
                CssPropertyWithConditions::simple(CssProperty::BorderBottomColor(
418
199
                    StyleBorderBottomColorValue::Exact(StyleBorderBottomColor {
419
199
                        inner: ColorU {
420
199
                            r: 178,
421
199
                            g: 178,
422
199
                            b: 178,
423
199
                            a: 255,
424
199
                        },
425
199
                    }),
426
199
                )),
427
199
                CssPropertyWithConditions::simple(CssProperty::BorderLeftColor(
428
199
                    StyleBorderLeftColorValue::Exact(StyleBorderLeftColor {
429
199
                        inner: ColorU {
430
199
                            r: 178,
431
199
                            g: 178,
432
199
                            b: 178,
433
199
                            a: 255,
434
199
                        },
435
199
                    }),
436
199
                )),
437
199
                CssPropertyWithConditions::simple(CssProperty::BorderRightColor(
438
199
                    StyleBorderRightColorValue::Exact(StyleBorderRightColor {
439
199
                        inner: ColorU {
440
199
                            r: 178,
441
199
                            g: 178,
442
199
                            b: 178,
443
199
                            a: 255,
444
199
                        },
445
199
                    }),
446
199
                )),
447
199
                CssPropertyWithConditions::simple(CssProperty::BorderTopColor(
448
199
                    StyleBorderTopColorValue::Exact(StyleBorderTopColor {
449
199
                        inner: ColorU {
450
199
                            r: 178,
451
199
                            g: 178,
452
199
                            b: 178,
453
199
                            a: 255,
454
199
                        },
455
199
                    }),
456
199
                )),
457
199
                CssPropertyWithConditions::simple(CssProperty::BackgroundContent(
458
199
                    StyleBackgroundContentVecValue::Exact(self.container_background.clone()),
459
199
                )),
460
            ]))
461
199
            .with_ids_and_classes({
462
                const IDS_AND_CLASSES_10874511710181900075: &[IdOrClass] = &[Class(
463
                    AzString::from_const_str("__azul-native-progress-bar-container"),
464
                )];
465
199
                IdOrClassVec::from_const_slice(IDS_AND_CLASSES_10874511710181900075)
466
            })
467
199
            .with_children(DomVec::from_vec(vec![
468
199
                Dom::create_div()
469
199
                    .with_css_props(CssPropertyWithConditionsVec::from_vec(vec![
470
                        // .__azul-native-progress-bar-bar
471
                        // Use percentage width instead of flex-grow hack
472
199
                        CssPropertyWithConditions::simple(CssProperty::Width(
473
199
                            LayoutWidthValue::Exact(LayoutWidth::Px(
474
199
                                PixelValue::percent(percent_done),
475
199
                            )),
476
199
                        )),
477
199
                        CssPropertyWithConditions::simple(CssProperty::BoxShadowBottom(
478
199
                            StyleBoxShadowValue::Exact(BoxOrStatic::heap(StyleBoxShadow {
479
199
                                offset_x: PixelValueNoPercent {
480
199
                                    inner: PixelValue::const_px(0),
481
199
                                },
482
199
                                offset_y: PixelValueNoPercent {
483
199
                                    inner: PixelValue::const_px(0),
484
199
                                },
485
199
                                color: ColorU {
486
199
                                    r: 0,
487
199
                                    g: 51,
488
199
                                    b: 0,
489
199
                                    a: 51,
490
199
                                },
491
199
                                blur_radius: PixelValueNoPercent {
492
199
                                    inner: PixelValue::const_px(15),
493
199
                                },
494
199
                                spread_radius: PixelValueNoPercent {
495
199
                                    inner: PixelValue::const_px(12),
496
199
                                },
497
199
                                clip_mode: BoxShadowClipMode::Inset,
498
199
                            })),
499
199
                        )),
500
199
                        CssPropertyWithConditions::simple(CssProperty::BoxShadowTop(
501
199
                            StyleBoxShadowValue::Exact(BoxOrStatic::heap(StyleBoxShadow {
502
199
                                offset_x: PixelValueNoPercent {
503
199
                                    inner: PixelValue::const_px(0),
504
199
                                },
505
199
                                offset_y: PixelValueNoPercent {
506
199
                                    inner: PixelValue::const_px(0),
507
199
                                },
508
199
                                color: ColorU {
509
199
                                    r: 0,
510
199
                                    g: 51,
511
199
                                    b: 0,
512
199
                                    a: 51,
513
199
                                },
514
199
                                blur_radius: PixelValueNoPercent {
515
199
                                    inner: PixelValue::const_px(15),
516
199
                                },
517
199
                                spread_radius: PixelValueNoPercent {
518
199
                                    inner: PixelValue::const_px(12),
519
199
                                },
520
199
                                clip_mode: BoxShadowClipMode::Inset,
521
199
                            })),
522
199
                        )),
523
199
                        CssPropertyWithConditions::simple(CssProperty::BoxShadowRight(
524
199
                            StyleBoxShadowValue::Exact(BoxOrStatic::heap(StyleBoxShadow {
525
199
                                offset_x: PixelValueNoPercent {
526
199
                                    inner: PixelValue::const_px(0),
527
199
                                },
528
199
                                offset_y: PixelValueNoPercent {
529
199
                                    inner: PixelValue::const_px(0),
530
199
                                },
531
199
                                color: ColorU {
532
199
                                    r: 0,
533
199
                                    g: 51,
534
199
                                    b: 0,
535
199
                                    a: 51,
536
199
                                },
537
199
                                blur_radius: PixelValueNoPercent {
538
199
                                    inner: PixelValue::const_px(15),
539
199
                                },
540
199
                                spread_radius: PixelValueNoPercent {
541
199
                                    inner: PixelValue::const_px(12),
542
199
                                },
543
199
                                clip_mode: BoxShadowClipMode::Inset,
544
199
                            })),
545
199
                        )),
546
199
                        CssPropertyWithConditions::simple(CssProperty::BoxShadowLeft(
547
199
                            StyleBoxShadowValue::Exact(BoxOrStatic::heap(StyleBoxShadow {
548
199
                                offset_x: PixelValueNoPercent {
549
199
                                    inner: PixelValue::const_px(0),
550
199
                                },
551
199
                                offset_y: PixelValueNoPercent {
552
199
                                    inner: PixelValue::const_px(0),
553
199
                                },
554
199
                                color: ColorU {
555
199
                                    r: 0,
556
199
                                    g: 51,
557
199
                                    b: 0,
558
199
                                    a: 51,
559
199
                                },
560
199
                                blur_radius: PixelValueNoPercent {
561
199
                                    inner: PixelValue::const_px(15),
562
199
                                },
563
199
                                spread_radius: PixelValueNoPercent {
564
199
                                    inner: PixelValue::const_px(12),
565
199
                                },
566
199
                                clip_mode: BoxShadowClipMode::Inset,
567
199
                            })),
568
199
                        )),
569
199
                        CssPropertyWithConditions::simple(CssProperty::BorderBottomRightRadius(
570
199
                            StyleBorderBottomRightRadiusValue::Exact(
571
199
                                StyleBorderBottomRightRadius {
572
199
                                    inner: PixelValue::const_px(1),
573
199
                                },
574
199
                            ),
575
199
                        )),
576
199
                        CssPropertyWithConditions::simple(CssProperty::BorderBottomLeftRadius(
577
199
                            StyleBorderBottomLeftRadiusValue::Exact(StyleBorderBottomLeftRadius {
578
199
                                inner: PixelValue::const_px(1),
579
199
                            }),
580
199
                        )),
581
199
                        CssPropertyWithConditions::simple(CssProperty::BorderTopRightRadius(
582
199
                            StyleBorderTopRightRadiusValue::Exact(StyleBorderTopRightRadius {
583
199
                                inner: PixelValue::const_px(1),
584
199
                            }),
585
199
                        )),
586
199
                        CssPropertyWithConditions::simple(CssProperty::BorderTopLeftRadius(
587
199
                            StyleBorderTopLeftRadiusValue::Exact(StyleBorderTopLeftRadius {
588
199
                                inner: PixelValue::const_px(1),
589
199
                            }),
590
199
                        )),
591
199
                        CssPropertyWithConditions::simple(CssProperty::BackgroundContent(
592
199
                            StyleBackgroundContentVecValue::Exact(self.bar_background),
593
199
                        )),
594
                    ]))
595
199
                    .with_ids_and_classes({
596
                        const IDS_AND_CLASSES_16512648314570682783: &[IdOrClass] = &[Class(
597
                            AzString::from_const_str("__azul-native-progress-bar-bar"),
598
                        )];
599
199
                        IdOrClassVec::from_const_slice(IDS_AND_CLASSES_16512648314570682783)
600
                    }),
601
199
                Dom::create_div()
602
199
                    .with_css_props(CssPropertyWithConditionsVec::from_vec(vec![
603
                        // .__azul-native-progress-bar-remaining
604
                        // Use percentage width for the remaining space
605
199
                        CssPropertyWithConditions::simple(CssProperty::Width(
606
199
                            LayoutWidthValue::Exact(LayoutWidth::Px(
607
199
                                PixelValue::percent(100.0 - percent_done),
608
199
                            )),
609
199
                        )),
610
                    ]))
611
199
                    .with_ids_and_classes({
612
                        const IDS_AND_CLASSES_2492405364126620395: &[IdOrClass] = &[Class(
613
                            AzString::from_const_str("__azul-native-progress-bar-remaining"),
614
                        )];
615
199
                        IdOrClassVec::from_const_slice(IDS_AND_CLASSES_2492405364126620395)
616
                    }),
617
            ]))
618
199
    }
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
)]
629
mod autotest_generated {
630
    use std::collections::HashSet;
631

            
632
    use azul_core::dom::NodeType;
633

            
634
    use super::*;
635

            
636
    // ------------------------------------------------------------------
637
    // Helpers
638
    // ------------------------------------------------------------------
639

            
640
    /// Every `f32` a caller can realistically hand to `ProgressBar::create`.
641
    /// The percentage is stored raw and only clamped inside `dom()`, where it is
642
    /// pushed through `PixelValue::percent` — which multiplies by 1000 and casts
643
    /// to `isize`. That cast saturates (NaN → 0, out of range → `isize::MIN/MAX`),
644
    /// so none of these may panic or wrap.
645
    ///
646
    /// `NAN` is deliberately absent: it is the one input that is unordered against
647
    /// the clamp bounds, so it gets its own test.
648
    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
    /// Heights that stress the `f32 → isize` fixed-point encoding behind
668
    /// `PixelValue`: zero, both signed zeroes, the saturating extremes, NaN, and
669
    /// the relative metrics the widget is not supposed to reject.
670
    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
            // The largest whole-pixel value `const_px` can scale by 1000 without
685
            // overflowing `isize` (one more would be a debug-build panic *inside
686
            // the argument*, not inside `set_height`).
687
            PixelValue::const_px(isize::MAX / 1000),
688
        ]
689
    }
690

            
691
    /// The raw fixed-point encoding of a length: `FloatValue` stores `value * 1000`
692
    /// as an `isize`, so this is what actually survives — comparing it avoids a
693
    /// second lossy float round-trip through `get()`.
694
    fn raw(pv: PixelValue) -> isize {
695
        pv.number.number()
696
    }
697

            
698
    /// The addresses `ProgressBar::create` hands out for the two static
699
    /// gradients — the reference every "is this still borrowed?" assertion below
700
    /// compares against.
701
    ///
702
    /// Deliberately NOT `STYLE_BACKGROUND_CONTENT_*_ITEMS.as_ptr()`. Those are
703
    /// `const` items, and every *use site* of a `const &[T]` gets its own
704
    /// promoted read-only allocation; two use sites share an address only if the
705
    /// optimizer merges them, which it does in an optimized build and does not
706
    /// in an unoptimized one. Comparing a `create()` pointer against the const
707
    /// was therefore an accidental green that held only because the suite had
708
    /// never been run on the dev profile. `create()` contains ONE use site of
709
    /// each const, so the address it returns is stable across calls — and that
710
    /// is exactly the property under test: a `create()` that copied the slice
711
    /// into a heap vec would hand out a fresh address every time.
712
    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
    /// A heap-allocated background of `n` distinct solid colours. Heap-backed on
721
    /// purpose: it is the only case where the vec owns memory that can be
722
    /// double-freed or leaked.
723
    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
    /// The filled part (`.__azul-native-progress-bar-bar`).
743
    fn bar(dom: &Dom) -> &Dom {
744
        &kids(dom)[0]
745
    }
746

            
747
    /// The empty part (`.__azul-native-progress-bar-remaining`).
748
    fn remaining(dom: &Dom) -> &Dom {
749
        &kids(dom)[1]
750
    }
751

            
752
    /// The declared properties of a node's inline style, in declaration order.
753
    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
    /// The CSS classes of a node, in declaration order.
762
    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
                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
    /// The background layers a node declares, cloned out of the DOM.
803
    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
    /// The *address* of a node's background buffer — the only way to tell a move
816
    /// from a copy, and a copy from a use-after-free.
817
    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
    /// Every absolute length a chrome property declares (box shadows excluded —
830
    /// they carry `PixelValueNoPercent`, which cannot express a relative unit).
831
    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
    // ------------------------------------------------------------------
847
    // ProgressBar::create
848
    // ------------------------------------------------------------------
849

            
850
    #[test]
851
    fn create_stores_the_percentage_bit_for_bit_and_never_normalises_it() {
852
        // `create` is documented as taking 0.0..=100.0 but performs no validation:
853
        // whatever comes in has to come back out untouched, sign of zero included.
854
        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
        // NaN cannot be compared, only inspected.
868
        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
        // Pointer identity, not just content equality: a `create()` that copied the
888
        // static slice into a heap vec would allocate on every frame, and one that
889
        // kept the static pointer but claimed ownership of it would free `&'static`
890
        // memory on drop.
891
        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
        // Content still pinned to the declared constants, so "shared" cannot
902
        // degrade into "shared with something else".
903
        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
        // 10_000 bars built and dropped: if the destructor of a static-backed vec
920
        // were ever flipped to the owning one, this frees the same `&'static`
921
        // allocation 10_000 times.
922
        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
                    // Unsorted or out-of-range stops make the gradient renderer's
948
                    // interpolation run backwards over a segment.
949
                    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
        // `create` is `const fn`; a caller may therefore build a bar as a `const`
971
        // item. That only const-evaluates while the backgrounds stay
972
        // `from_const_slice` (a heap allocation would not be const-evaluable).
973
        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
    // ------------------------------------------------------------------
981
    // ProgressBar::swap_with_default
982
    // ------------------------------------------------------------------
983

            
984
    #[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,
                "the replacement must be +0.0 — a -0.0 would encode with the sign bit set",
            );
            assert_eq!(raw(pb.height), 15_000, "the replacement must use the default height");
            assert_eq!(
                pb.bar_background.as_ptr(),
                create_gradient_ptrs().0,
                "the replacement must borrow the static gradient again",
            );
        }
    }
    #[test]
    fn swap_with_default_keeps_a_nan_percentage_and_moves_owned_memory_out() {
        let owned = solid(4);
        let ptr = owned.as_ptr();
        let mut pb = ProgressBar::create(f32::NAN).with_bar_background(owned);
        let prev = pb.swap_with_default();
        assert!(
            prev.progressbar_state.percent_done.is_nan(),
            "a NaN percentage did not survive the swap",
        );
        assert_eq!(
            prev.bar_background.as_ptr(),
            ptr,
            "the heap buffer was copied instead of moved out",
        );
        assert_eq!(prev.bar_background.len(), 4);
        // Dropping the previous value frees that heap buffer. If `swap_with_default`
        // had left `self` pointing at it too, everything below would be a
        // use-after-free.
        drop(prev);
        assert_eq!(
            pb.bar_background.as_ref(),
            STYLE_BACKGROUND_CONTENT_2688422633177340412_ITEMS,
            "the swapped-in bar aliased the memory that was just freed",
        );
        assert_eq!(pb.progressbar_state.percent_done, 0.0);
    }
    #[test]
    fn repeated_swaps_never_alias_or_leak_the_backgrounds() {
        let mut pb = ProgressBar::create(1.0);
        let (bar_ptr, _) = create_gradient_ptrs();
        for i in 0..1_000_usize {
            let want = i % 8 + 1;
            pb.set_bar_background(solid(want));
            let prev = pb.swap_with_default();
            assert_eq!(prev.bar_background.len(), want, "round {i} handed back the wrong buffer");
            assert_eq!(pb.progressbar_state.percent_done, 0.0);
            assert_eq!(pb.bar_background.as_ptr(), bar_ptr);
        }
    }
    // ------------------------------------------------------------------
    // set_/with_ background
    // ------------------------------------------------------------------
    #[test]
    fn each_background_setter_touches_exactly_one_field() {
        let mut pb = ProgressBar::create(50.0);
        let bar_ptr = pb.bar_background.as_ptr();
        pb.set_container_background(solid(3));
        assert_eq!(pb.container_background.len(), 3);
        assert_eq!(
            pb.bar_background.as_ptr(),
            bar_ptr,
            "set_container_background clobbered the bar background",
        );
        let mut pb = ProgressBar::create(50.0);
        let container_ptr = pb.container_background.as_ptr();
        pb.set_bar_background(solid(5));
        assert_eq!(pb.bar_background.len(), 5);
        assert_eq!(
            pb.container_background.as_ptr(),
            container_ptr,
            "set_bar_background clobbered the container background",
        );
        assert_eq!(pb.progressbar_state.percent_done, 50.0);
        assert_eq!(raw(pb.height), 15_000);
    }
    #[test]
    fn the_builder_forms_are_exactly_their_setters() {
        let a = ProgressBar::create(7.5)
            .with_bar_background(solid(3))
            .with_container_background(solid(2))
            .with_height(PixelValue::px(-4.5));
        let mut b = ProgressBar::create(7.5);
        b.set_bar_background(solid(3));
        b.set_container_background(solid(2));
        b.set_height(PixelValue::px(-4.5));
        assert_eq!(a.bar_background.as_ref(), b.bar_background.as_ref());
        assert_eq!(a.container_background.as_ref(), b.container_background.as_ref());
        assert_eq!(a.height, b.height);
        assert_eq!(
            a.progressbar_state.percent_done,
            b.progressbar_state.percent_done,
        );
        assert_eq!(
            a.progressbar_state.display_percentage,
            b.progressbar_state.display_percentage,
        );
    }
    #[test]
    fn an_empty_background_stays_an_empty_declaration() {
        let pb = ProgressBar::create(0.0)
            .with_bar_background(StyleBackgroundContentVec::from_vec(Vec::new()))
            .with_container_background(StyleBackgroundContentVec::new());
        assert!(pb.bar_background.is_empty());
        assert_eq!(pb.bar_background.len(), 0);
        assert!(pb.container_background.is_empty());
        let dom = pb.dom();
        assert_eq!(
            background_of(bar(&dom)),
            Some(Vec::new()),
            "an empty background must reach the DOM as an empty layer list, not vanish",
        );
        assert_eq!(background_of(&dom), Some(Vec::new()));
    }
    #[test]
    fn a_background_with_spare_capacity_keeps_its_allocation_intact() {
        // The free path rebuilds a `Vec` from (ptr, len, cap); a `cap` that drifted
        // to `len` frees the wrong layout.
        let mut v = Vec::with_capacity(64);
        v.push(StyleBackgroundContent::Color(ColorU {
            r: 1,
            g: 2,
            b: 3,
            a: 4,
        }));
        let bg = StyleBackgroundContentVec::from_vec(v);
        assert_eq!(bg.len(), 1);
        assert!(bg.capacity() >= 64, "from_vec lost the spare capacity: {}", bg.capacity());
        let pb = ProgressBar::create(0.0).with_container_background(bg);
        assert_eq!(pb.container_background.len(), 1);
        assert!(
            pb.container_background.capacity() >= 64,
            "the setter rewrote the buffer's capacity",
        );
    }
    #[test]
    fn a_very_large_background_is_neither_truncated_nor_copied() {
        let big = solid(10_000);
        let ptr = big.as_ptr();
        let pb = ProgressBar::create(50.0).with_bar_background(big);
        assert_eq!(pb.bar_background.len(), 10_000);
        assert_eq!(pb.bar_background.as_ptr(), ptr, "the setter deep-copied a 10k-layer background");
        let dom = pb.dom();
        assert_eq!(
            background_of(bar(&dom)).map(|v| v.len()),
            Some(10_000),
            "the background was truncated on the way into the DOM",
        );
    }
    #[test]
    fn overwriting_a_background_releases_the_previous_one() {
        // 500 replacements of an owned buffer: a setter that forgot to drop the old
        // value leaks, and one that dropped it twice aborts.
        let n = 500_usize;
        let mut pb = ProgressBar::create(0.0);
        for i in 1..=n {
            pb.set_bar_background(solid(i % 16 + 1));
            pb.set_container_background(solid(i % 4 + 1));
        }
        // The surviving background is whatever the LAST iteration installed, so the
        // expected length is derived from `n` rather than hard-coded.
        assert_eq!(pb.bar_background.len(), n % 16 + 1);
        assert_eq!(pb.container_background.len(), n % 4 + 1);
    }
    #[test]
    fn cloning_deep_copies_owned_backgrounds_but_shares_static_ones() {
        let pb = ProgressBar::create(3.0).with_bar_background(solid(4));
        let copy = pb.clone();
        assert_ne!(
            copy.bar_background.as_ptr(),
            pb.bar_background.as_ptr(),
            "Clone shared an owned heap buffer — dropping both would double-free it",
        );
        assert_eq!(
            copy.container_background.as_ptr(),
            pb.container_background.as_ptr(),
            "the static gradient is never freed, so the clone should keep sharing it",
        );
        drop(pb);
        assert_eq!(copy.bar_background.len(), 4);
        assert_eq!(
            copy.bar_background.as_ref()[3],
            StyleBackgroundContent::Color(ColorU {
                r: 3,
                g: 1,
                b: 2,
                a: 255,
            }),
            "the clone read back garbage after the original was dropped",
        );
    }
    // ------------------------------------------------------------------
    // set_height / with_height
    // ------------------------------------------------------------------
    #[test]
    fn set_height_stores_every_pixel_value_verbatim() {
        for h in adversarial_heights() {
            let mut pb = ProgressBar::create(0.0);
            pb.set_height(h);
            assert_eq!(pb.height.metric, h.metric, "set_height changed the unit of {h:?}");
            assert_eq!(raw(pb.height), raw(h), "set_height re-encoded {h:?}");
            // and nothing else moved
            assert_eq!(pb.progressbar_state.percent_done, 0.0);
            assert_eq!(pb.bar_background.len(), 1);
        }
    }
    #[test]
    fn an_out_of_range_height_saturates_instead_of_wrapping() {
        let mut pb = ProgressBar::create(0.0);
        // `FloatValue::new` computes `value * 1000.0` in `f32` (which overflows to
        // an infinity) and then casts to `isize` — a saturating cast, so the result
        // is a bound, never a wrapped negative.
        pb.set_height(PixelValue::px(f32::MAX));
        assert_eq!(raw(pb.height), isize::MAX, "an overflowing height wrapped instead of saturating");
        assert!(pb.height.number.get().is_finite(), "the saturated height decoded to a non-finite f32");
        pb.set_height(PixelValue::px(f32::INFINITY));
        assert_eq!(raw(pb.height), isize::MAX);
        pb.set_height(PixelValue::px(f32::MIN));
        assert_eq!(raw(pb.height), isize::MIN);
        pb.set_height(PixelValue::px(f32::NEG_INFINITY));
        assert_eq!(raw(pb.height), isize::MIN);
        pb.set_height(PixelValue::px(f32::NAN));
        assert_eq!(raw(pb.height), 0, "a NaN height must land on 0, not on an arbitrary integer");
        pb.set_height(PixelValue::px(-0.0));
        assert_eq!(raw(pb.height), 0, "-0.0 must encode to the same 0 as +0.0");
        // Below the 1/1000 resolution everything truncates to zero, deterministically.
        pb.set_height(PixelValue::px(0.0004));
        assert_eq!(raw(pb.height), 0);
        pb.set_height(PixelValue::px(f32::MIN_POSITIVE));
        assert_eq!(raw(pb.height), 0);
    }
    #[test]
    fn with_height_is_set_height_and_leaves_the_rest_alone() {
        for h in adversarial_heights() {
            let a = ProgressBar::create(42.0).with_height(h);
            let mut b = ProgressBar::create(42.0);
            b.set_height(h);
            assert_eq!(a.height, b.height, "with_height disagreed with set_height for {h:?}");
            assert_eq!(raw(a.height), raw(h));
            assert_eq!(a.progressbar_state.percent_done, 42.0);
            assert_eq!(
                a.bar_background.as_ptr(),
                b.bar_background.as_ptr(),
                "with_height reallocated the background",
            );
        }
    }
    // ------------------------------------------------------------------
    // ProgressBar::dom
    // ------------------------------------------------------------------
    #[test]
    fn dom_is_a_container_div_with_exactly_two_leaf_children() {
        let dom = ProgressBar::create(50.0).dom();
        assert!(matches!(dom.root.get_node_type(), NodeType::Div));
        assert_eq!(kids(&dom).len(), 2, "the progress bar must render bar + remaining");
        assert!(kids(bar(&dom)).is_empty(), "the bar must stay a leaf");
        assert!(kids(remaining(&dom)).is_empty(), "the remaining space must stay a leaf");
        // A cached child count that is too small makes `convert_dom_into_compact_dom`
        // under-allocate its arenas and panic on out-of-bounds writes.
        assert_eq!(dom.estimated_total_children, 2);
        assert_eq!(classes(&dom), vec!["__azul-native-progress-bar-container".to_string()]);
        assert_eq!(classes(bar(&dom)), vec!["__azul-native-progress-bar-bar".to_string()]);
        assert_eq!(
            classes(remaining(&dom)),
            vec!["__azul-native-progress-bar-remaining".to_string()],
        );
    }
    #[test]
    fn dom_clamps_every_out_of_range_percentage_into_zero_to_one_hundred() {
        // (input, bar width, remaining width) — widths in 1/1000 of a percent.
        const CASES: [(f32, isize, isize); 12] = [
            (0.0, 0, 100_000),
            (-0.0, 0, 100_000),
            (50.0, 50_000, 50_000),
            (100.0, 100_000, 0),
            (-1.0, 0, 100_000),
            (101.0, 100_000, 0),
            (-1e30, 0, 100_000),
            (1e30, 100_000, 0),
            (f32::MAX, 100_000, 0),
            (f32::MIN, 0, 100_000),
            (f32::INFINITY, 100_000, 0),
            (f32::NEG_INFINITY, 0, 100_000),
        ];
        for (input, bar_width, remaining_width) in CASES {
            let dom = ProgressBar::create(input).dom();
            let b = width_of(bar(&dom)).expect("the bar must declare a width");
            let r = width_of(remaining(&dom)).expect("the remaining space must declare a width");
            assert_eq!(b.metric, SizeMetric::Percent, "the bar must size in %, not {:?}", b.metric);
            assert_eq!(r.metric, SizeMetric::Percent, "the gap must size in %, not {:?}", r.metric);
            assert_eq!(raw(b), bar_width, "bar width for input {input}");
            assert_eq!(raw(r), remaining_width, "remaining width for input {input}");
            assert!(raw(b) >= 0 && raw(r) >= 0, "a negative width escaped for input {input}");
        }
    }
    #[test]
    fn dom_collapses_a_nan_percentage_to_two_empty_children() {
        // `f32::clamp` propagates NaN rather than clamping it, and the `f32 -> isize`
        // cast inside `FloatValue::new` then turns it into 0. The documented result:
        // BOTH children get 0% — the bar renders as an empty container instead of
        // falling back to 0%/100%. It does not panic, and it is deterministic.
        let dom = ProgressBar::create(f32::NAN).dom();
        let b = width_of(bar(&dom)).expect("the bar must declare a width");
        let r = width_of(remaining(&dom)).expect("the remaining space must declare a width");
        assert_eq!(raw(b), 0);
        assert_eq!(raw(r), 0);
        assert_eq!(b.metric, SizeMetric::Percent);
        assert_eq!(r.metric, SizeMetric::Percent);
        assert_eq!(kids(&dom).len(), 2, "a NaN percentage must not change the tree shape");
    }
    #[test]
    fn dom_splits_the_container_exactly_for_whole_percentages() {
        for i in 0..=100_isize {
            let dom = ProgressBar::create(i as f32).dom();
            let b = raw(width_of(bar(&dom)).unwrap());
            let r = raw(width_of(remaining(&dom)).unwrap());
            assert_eq!(b, i * 1000, "the bar is not {i}% wide");
            assert_eq!(
                b + r,
                100_000,
                "the two halves do not add up to the container at {i}%",
            );
        }
    }
    #[test]
    fn dom_loses_at_most_the_encoding_truncation_for_fractional_percentages() {
        // Each side is truncated to 1/1000 of a percent independently, so the pair
        // may under-fill by two ticks — but never overflow the container, and never
        // go negative.
        for p in [
            0.0005_f32,
            0.5,
            1.0 / 3.0,
            33.333,
            66.667,
            99.999,
            99.9999,
            f32::EPSILON,
            f32::MIN_POSITIVE,
        ] {
            let dom = ProgressBar::create(p).dom();
            let b = raw(width_of(bar(&dom)).unwrap());
            let r = raw(width_of(remaining(&dom)).unwrap());
            assert!(
                (0..=100_000).contains(&b) && (0..=100_000).contains(&r),
                "a width left 0%..100% for {p}: {b} / {r}",
            );
            assert!(
                (b + r - 100_000).abs() <= 10,
                "the two halves drifted apart for {p}: {b} + {r}",
            );
        }
    }
    #[test]
    fn dom_routes_each_background_to_its_own_node() {
        let bar_bg = solid(3);
        let container_bg = solid(5);
        let bar_ptr = bar_bg.as_ptr();
        let container_ptr = container_bg.as_ptr();
        let dom = ProgressBar::create(25.0)
            .with_bar_background(bar_bg)
            .with_container_background(container_bg)
            .dom();
        assert_eq!(
            background_of(&dom).map(|v| v.len()),
            Some(5),
            "the container lost (or swapped) its background",
        );
        assert_eq!(
            background_of(bar(&dom)).map(|v| v.len()),
            Some(3),
            "the bar lost (or swapped) its background",
        );
        assert_eq!(
            background_of(remaining(&dom)),
            None,
            "the remaining space must not paint anything",
        );
        // The bar background is *moved* into the DOM — same allocation, no copy.
        assert_eq!(
            background_ptr(bar(&dom)),
            Some(bar_ptr),
            "the bar background was copied instead of moved",
        );
        // The container background is cloned, because `self` — and with it the
        // original buffer — is dropped at the end of `dom()`. Handing the DOM the
        // same pointer would be a use-after-free.
        assert_ne!(
            background_ptr(&dom),
            Some(container_ptr),
            "the DOM kept a pointer into a buffer that `dom()` then freed",
        );
    }
    #[test]
    fn dom_forwards_any_height_to_the_container_and_to_nobody_else() {
        for h in adversarial_heights() {
            let dom = ProgressBar::create(50.0).with_height(h).dom();
            let got = height_of(&dom).expect("the container must declare a height");
            assert_eq!(got.metric, h.metric, "the height unit changed on the way into the DOM");
            assert_eq!(raw(got), raw(h), "the height was re-encoded on the way into the DOM");
            assert_eq!(height_of(bar(&dom)), None, "the bar must not declare its own height");
            assert_eq!(
                height_of(remaining(&dom)),
                None,
                "the remaining space must not declare its own height",
            );
            assert_eq!(width_of(&dom), None, "the container must not declare a width");
        }
    }
    #[test]
    fn dom_declares_the_expected_style_blocks_and_no_property_twice() {
        let dom = ProgressBar::create(50.0).with_bar_background(solid(1)).dom();
        assert_eq!(inline_props(&dom).len(), 23, "the container style block drifted");
        assert_eq!(inline_props(bar(&dom)).len(), 10, "the bar style block drifted");
        let props = inline_props(remaining(&dom));
        assert_eq!(props.len(), 1, "the remaining space grew a style block: {props:?}");
        assert!(
            matches!(&props[0], CssProperty::Width(_)),
            "the remaining space must only declare its width",
        );
        // A property declared twice means one of the two is silently dead, and which
        // one wins depends on cascade order.
        for node in [&dom, bar(&dom), remaining(&dom)] {
            let mut seen = HashSet::new();
            for p in inline_props(node) {
                assert!(
                    seen.insert(core::mem::discriminant(&p)),
                    "duplicate declaration of {p:?}",
                );
            }
        }
    }
    #[test]
    fn dom_chrome_lengths_are_all_absolute_pixels() {
        // Only the two child widths are relative. A border or radius that slipped
        // into `em`/`%` would resolve against the parent font or box and either
        // vanish or blow up.
        let dom = ProgressBar::create(50.0).dom();
        for node in [&dom, bar(&dom), remaining(&dom)] {
            for p in inline_props(node) {
                for length in lengths_of(&p) {
                    assert_eq!(
                        length.metric,
                        SizeMetric::Px,
                        "{p:?} declares a relative length: {length:?}",
                    );
                }
            }
        }
    }
    #[test]
    fn dom_ignores_display_percentage() {
        // The field is public and settable, but nothing in `dom()` reads it: the
        // rendered tree has to be byte-identical either way.
        let mut with_label = ProgressBar::create(40.0);
        with_label.progressbar_state.display_percentage = true;
        let without_label = ProgressBar::create(40.0);
        assert_eq!(
            with_label.dom(),
            without_label.dom(),
            "display_percentage started changing the tree",
        );
    }
    #[test]
    fn dom_is_deterministic_for_equal_inputs() {
        let a = ProgressBar::create(37.5)
            .with_bar_background(solid(2))
            .with_height(PixelValue::px(7.25))
            .dom();
        let b = ProgressBar::create(37.5)
            .with_bar_background(solid(2))
            .with_height(PixelValue::px(7.25))
            .dom();
        assert_eq!(a, b, "two identically-built progress bars rendered differently");
    }
    #[test]
    fn dom_survives_every_extreme_percentage_and_background_size() {
        for p in ADVERSARIAL_PERCENTS.into_iter().chain([f32::NAN]) {
            for layers in [0_usize, 1, 64] {
                let dom = ProgressBar::create(p)
                    .with_bar_background(solid(layers))
                    .with_container_background(solid(layers))
                    .with_height(PixelValue::px(f32::MAX))
                    .dom();
                assert_eq!(kids(&dom).len(), 2, "shape changed for {p} / {layers} layers");
                assert_eq!(dom.estimated_total_children, 2);
                assert_eq!(background_of(bar(&dom)).map(|v| v.len()), Some(layers));
                assert_eq!(background_of(&dom).map(|v| v.len()), Some(layers));
                let b = width_of(bar(&dom)).expect("the bar must declare a width");
                assert_eq!(b.metric, SizeMetric::Percent);
                assert!((0..=100_000).contains(&raw(b)), "width out of range for {p}");
            }
        }
    }
}