Skip to main content

azul_layout/widgets/
tabs.rs

1//! Native-styled tab widget consisting of a [`TabHeader`] (the clickable tab bar)
2//! and [`TabContent`] (the panel shown for the active tab).
3//!
4//! Styling emulates the Windows-native tab control appearance via inline CSS
5//! constants.
6
7use azul_core::{
8    callbacks::{CoreCallback, CoreCallbackData, Update},
9    dom::{Dom, DomVec, EventFilter, HoverEventFilter, IdOrClass, IdOrClass::Class, IdOrClassVec},
10    refany::RefAny,
11};
12#[allow(clippy::wildcard_imports)] // widget/render module pulls in the css property/value types it builds with
13use azul_css::{
14    dynamic_selector::{CssPropertyWithConditions, CssPropertyWithConditionsVec},
15    props::{
16        basic::*,
17        layout::*,
18        property::{CssProperty, *},
19        style::*,
20    },
21    *,
22};
23use azul_css::css::BoxOrStatic;
24
25use crate::callbacks::{Callback, CallbackInfo};
26
27const STRING_16146701490593874959: AzString = AzString::from_const_str("system:ui");
28const STYLE_BACKGROUND_CONTENT_8560341490937422656_ITEMS: &[StyleBackgroundContent] =
29    &[StyleBackgroundContent::LinearGradient(LinearGradient {
30        direction: Direction::FromTo(DirectionCorners {
31            dir_from: DirectionCorner::Top,
32            dir_to: DirectionCorner::Bottom,
33        }),
34        extend_mode: ExtendMode::Clamp,
35        stops: NormalizedLinearColorStopVec::from_const_slice(
36            LINEAR_COLOR_STOP_1400070954008106244_ITEMS,
37        ),
38    })];
39
40const STYLE_BACKGROUND_CONTENT_15534185073326444643_ITEMS: &[StyleBackgroundContent] =
41    &[StyleBackgroundContent::LinearGradient(LinearGradient {
42        direction: Direction::FromTo(DirectionCorners {
43            dir_from: DirectionCorner::Top,
44            dir_to: DirectionCorner::Bottom,
45        }),
46        extend_mode: ExtendMode::Clamp,
47        stops: NormalizedLinearColorStopVec::from_const_slice(
48            LINEAR_COLOR_STOP_16259001466875079747_ITEMS,
49        ),
50    })];
51const STYLE_BACKGROUND_CONTENT_16746671892555275291_ITEMS: &[StyleBackgroundContent] =
52    &[StyleBackgroundContent::Color(ColorU {
53        r: 255,
54        g: 255,
55        b: 255,
56        a: 255,
57    })];
58const STYLE_FONT_FAMILY_8122988506401935406_ITEMS: &[StyleFontFamily] =
59    &[StyleFontFamily::System(STRING_16146701490593874959)];
60const LINEAR_COLOR_STOP_1400070954008106244_ITEMS: &[NormalizedLinearColorStop] = &[
61    NormalizedLinearColorStop {
62        offset: PercentageValue::const_new(0),
63        color: ColorOrSystem::color(ColorU {
64            r: 240,
65            g: 240,
66            b: 240,
67            a: 255,
68        }),
69    },
70    NormalizedLinearColorStop {
71        offset: PercentageValue::const_new(100),
72        color: ColorOrSystem::color(ColorU {
73            r: 229,
74            g: 229,
75            b: 229,
76            a: 255,
77        }),
78    },
79];
80const LINEAR_COLOR_STOP_16259001466875079747_ITEMS: &[NormalizedLinearColorStop] = &[
81    NormalizedLinearColorStop {
82        offset: PercentageValue::const_new(0),
83        color: ColorOrSystem::color(ColorU {
84            r: 236,
85            g: 244,
86            b: 252,
87            a: 255,
88        }),
89    },
90    NormalizedLinearColorStop {
91        offset: PercentageValue::const_new(100),
92        color: ColorOrSystem::color(ColorU {
93            r: 221,
94            g: 237,
95            b: 252,
96            a: 255,
97        }),
98    },
99];
100
101const CSS_MATCH_13824480602841492081_PROPERTIES: &[CssPropertyWithConditions] = &[
102    // .__azul-native-tabs-header p.__azul-native-tabs-tab-not-active:hover
103    CssPropertyWithConditions::on_hover(CssProperty::BorderBottomWidth(
104        LayoutBorderBottomWidthValue::Exact(LayoutBorderBottomWidth {
105            inner: PixelValue::const_px(1),
106        }),
107    )),
108    CssPropertyWithConditions::on_hover(CssProperty::BorderLeftWidth(
109        LayoutBorderLeftWidthValue::Exact(LayoutBorderLeftWidth {
110            inner: PixelValue::const_px(1),
111        }),
112    )),
113    CssPropertyWithConditions::on_hover(CssProperty::BorderRightWidth(
114        LayoutBorderRightWidthValue::Exact(LayoutBorderRightWidth {
115            inner: PixelValue::const_px(1),
116        }),
117    )),
118    CssPropertyWithConditions::on_hover(CssProperty::BorderTopWidth(
119        LayoutBorderTopWidthValue::Exact(LayoutBorderTopWidth {
120            inner: PixelValue::const_px(1),
121        }),
122    )),
123    CssPropertyWithConditions::on_hover(CssProperty::BorderBottomStyle(
124        StyleBorderBottomStyleValue::Exact(StyleBorderBottomStyle {
125            inner: BorderStyle::Solid,
126        }),
127    )),
128    CssPropertyWithConditions::on_hover(CssProperty::BorderLeftStyle(
129        StyleBorderLeftStyleValue::Exact(StyleBorderLeftStyle {
130            inner: BorderStyle::Solid,
131        }),
132    )),
133    CssPropertyWithConditions::on_hover(CssProperty::BorderRightStyle(
134        StyleBorderRightStyleValue::Exact(StyleBorderRightStyle {
135            inner: BorderStyle::Solid,
136        }),
137    )),
138    CssPropertyWithConditions::on_hover(CssProperty::BorderTopStyle(
139        StyleBorderTopStyleValue::Exact(StyleBorderTopStyle {
140            inner: BorderStyle::Solid,
141        }),
142    )),
143    CssPropertyWithConditions::on_hover(CssProperty::BorderBottomColor(
144        StyleBorderBottomColorValue::Exact(StyleBorderBottomColor {
145            inner: ColorU {
146                r: 126,
147                g: 180,
148                b: 234,
149                a: 255,
150            },
151        }),
152    )),
153    CssPropertyWithConditions::on_hover(CssProperty::BorderLeftColor(
154        StyleBorderLeftColorValue::Exact(StyleBorderLeftColor {
155            inner: ColorU {
156                r: 126,
157                g: 180,
158                b: 234,
159                a: 255,
160            },
161        }),
162    )),
163    CssPropertyWithConditions::on_hover(CssProperty::BorderRightColor(
164        StyleBorderRightColorValue::Exact(StyleBorderRightColor {
165            inner: ColorU {
166                r: 126,
167                g: 180,
168                b: 234,
169                a: 255,
170            },
171        }),
172    )),
173    CssPropertyWithConditions::on_hover(CssProperty::BorderTopColor(
174        StyleBorderTopColorValue::Exact(StyleBorderTopColor {
175            inner: ColorU {
176                r: 126,
177                g: 180,
178                b: 234,
179                a: 255,
180            },
181        }),
182    )),
183    CssPropertyWithConditions::on_hover(CssProperty::BackgroundContent(
184        StyleBackgroundContentVecValue::Exact(StyleBackgroundContentVec::from_const_slice(
185            STYLE_BACKGROUND_CONTENT_15534185073326444643_ITEMS,
186        )),
187    )),
188    // .__azul-native-tabs-header p.__azul-native-tabs-tab-noleftborder
189    CssPropertyWithConditions::simple(CssProperty::BorderLeftWidth(
190        LayoutBorderLeftWidthValue::None,
191    )),
192    CssPropertyWithConditions::simple(CssProperty::BorderLeftStyle(
193        StyleBorderLeftStyleValue::None,
194    )),
195    CssPropertyWithConditions::simple(CssProperty::BorderLeftColor(
196        StyleBorderLeftColorValue::None,
197    )),
198    // .__azul-native-tabs-header p.__azul-native-tabs-tab-not-active
199    CssPropertyWithConditions::simple(CssProperty::PaddingRight(LayoutPaddingRightValue::Exact(
200        LayoutPaddingRight {
201            inner: PixelValue::const_px(5),
202        },
203    ))),
204    CssPropertyWithConditions::simple(CssProperty::PaddingLeft(LayoutPaddingLeftValue::Exact(
205        LayoutPaddingLeft {
206            inner: PixelValue::const_px(5),
207        },
208    ))),
209    CssPropertyWithConditions::simple(CssProperty::PaddingBottom(LayoutPaddingBottomValue::Exact(
210        LayoutPaddingBottom {
211            inner: PixelValue::const_px(1),
212        },
213    ))),
214    CssPropertyWithConditions::simple(CssProperty::PaddingTop(LayoutPaddingTopValue::Exact(
215        LayoutPaddingTop {
216            inner: PixelValue::const_px(1),
217        },
218    ))),
219    CssPropertyWithConditions::simple(CssProperty::MarginTop(LayoutMarginTopValue::Exact(
220        LayoutMarginTop {
221            inner: PixelValue::const_px(2),
222        },
223    ))),
224    // .__azul-native-tabs-header p
225    CssPropertyWithConditions::simple(CssProperty::TextAlign(StyleTextAlignValue::Exact(
226        StyleTextAlign::Center,
227    ))),
228    CssPropertyWithConditions::simple(CssProperty::Height(LayoutHeightValue::Exact(
229        LayoutHeight::Px(PixelValue::const_px(21)),
230    ))),
231    CssPropertyWithConditions::simple(CssProperty::BorderBottomWidth(
232        LayoutBorderBottomWidthValue::Exact(LayoutBorderBottomWidth {
233            inner: PixelValue::const_px(1),
234        }),
235    )),
236    CssPropertyWithConditions::simple(CssProperty::BorderLeftWidth(
237        LayoutBorderLeftWidthValue::Exact(LayoutBorderLeftWidth {
238            inner: PixelValue::const_px(1),
239        }),
240    )),
241    CssPropertyWithConditions::simple(CssProperty::BorderRightWidth(
242        LayoutBorderRightWidthValue::Exact(LayoutBorderRightWidth {
243            inner: PixelValue::const_px(1),
244        }),
245    )),
246    CssPropertyWithConditions::simple(CssProperty::BorderTopWidth(
247        LayoutBorderTopWidthValue::Exact(LayoutBorderTopWidth {
248            inner: PixelValue::const_px(1),
249        }),
250    )),
251    CssPropertyWithConditions::simple(CssProperty::BorderBottomStyle(
252        StyleBorderBottomStyleValue::Exact(StyleBorderBottomStyle {
253            inner: BorderStyle::Solid,
254        }),
255    )),
256    CssPropertyWithConditions::simple(CssProperty::BorderLeftStyle(
257        StyleBorderLeftStyleValue::Exact(StyleBorderLeftStyle {
258            inner: BorderStyle::Solid,
259        }),
260    )),
261    CssPropertyWithConditions::simple(CssProperty::BorderRightStyle(
262        StyleBorderRightStyleValue::Exact(StyleBorderRightStyle {
263            inner: BorderStyle::Solid,
264        }),
265    )),
266    CssPropertyWithConditions::simple(CssProperty::BorderTopStyle(
267        StyleBorderTopStyleValue::Exact(StyleBorderTopStyle {
268            inner: BorderStyle::Solid,
269        }),
270    )),
271    CssPropertyWithConditions::simple(CssProperty::BorderBottomColor(
272        StyleBorderBottomColorValue::Exact(StyleBorderBottomColor {
273            inner: ColorU {
274                r: 172,
275                g: 172,
276                b: 172,
277                a: 255,
278            },
279        }),
280    )),
281    CssPropertyWithConditions::simple(CssProperty::BorderLeftColor(
282        StyleBorderLeftColorValue::Exact(StyleBorderLeftColor {
283            inner: ColorU {
284                r: 172,
285                g: 172,
286                b: 172,
287                a: 255,
288            },
289        }),
290    )),
291    CssPropertyWithConditions::simple(CssProperty::BorderRightColor(
292        StyleBorderRightColorValue::Exact(StyleBorderRightColor {
293            inner: ColorU {
294                r: 172,
295                g: 172,
296                b: 172,
297                a: 255,
298            },
299        }),
300    )),
301    CssPropertyWithConditions::simple(CssProperty::BorderTopColor(
302        StyleBorderTopColorValue::Exact(StyleBorderTopColor {
303            inner: ColorU {
304                r: 172,
305                g: 172,
306                b: 172,
307                a: 255,
308            },
309        }),
310    )),
311    CssPropertyWithConditions::simple(CssProperty::BackgroundContent(
312        StyleBackgroundContentVecValue::Exact(StyleBackgroundContentVec::from_const_slice(
313            STYLE_BACKGROUND_CONTENT_8560341490937422656_ITEMS,
314        )),
315    )),
316    CssPropertyWithConditions::simple(CssProperty::AlignItems(LayoutAlignItemsValue::Exact(
317        LayoutAlignItems::Center,
318    ))),
319];
320const CSS_MATCH_13824480602841492081: CssPropertyWithConditionsVec =
321    CssPropertyWithConditionsVec::from_const_slice(CSS_MATCH_13824480602841492081_PROPERTIES);
322
323const CSS_MATCH_14575853790110873394_PROPERTIES: &[CssPropertyWithConditions] = &[
324    // .__azul-native-tabs-header p.__azul-native-tabs-tab-active
325    CssPropertyWithConditions::simple(CssProperty::PaddingRight(LayoutPaddingRightValue::Exact(
326        LayoutPaddingRight {
327            inner: PixelValue::const_px(7),
328        },
329    ))),
330    CssPropertyWithConditions::simple(CssProperty::PaddingLeft(LayoutPaddingLeftValue::Exact(
331        LayoutPaddingLeft {
332            inner: PixelValue::const_px(7),
333        },
334    ))),
335    CssPropertyWithConditions::simple(CssProperty::PaddingBottom(LayoutPaddingBottomValue::Exact(
336        LayoutPaddingBottom {
337            inner: PixelValue::const_px(3),
338        },
339    ))),
340    CssPropertyWithConditions::simple(CssProperty::PaddingTop(LayoutPaddingTopValue::Exact(
341        LayoutPaddingTop {
342            inner: PixelValue::const_px(3),
343        },
344    ))),
345    CssPropertyWithConditions::simple(CssProperty::Height(LayoutHeightValue::Exact(
346        LayoutHeight::Px(PixelValue::const_px(23)),
347    ))),
348    CssPropertyWithConditions::simple(CssProperty::BoxSizing(LayoutBoxSizingValue::Exact(
349        LayoutBoxSizing::ContentBox,
350    ))),
351    CssPropertyWithConditions::simple(CssProperty::BorderBottomWidth(
352        LayoutBorderBottomWidthValue::Exact(LayoutBorderBottomWidth {
353            inner: PixelValue::const_px(1),
354        }),
355    )),
356    CssPropertyWithConditions::simple(CssProperty::BorderBottomStyle(
357        StyleBorderBottomStyleValue::Exact(StyleBorderBottomStyle {
358            inner: BorderStyle::Solid,
359        }),
360    )),
361    CssPropertyWithConditions::simple(CssProperty::BorderBottomColor(
362        StyleBorderBottomColorValue::Exact(StyleBorderBottomColor {
363            inner: ColorU {
364                r: 255,
365                g: 255,
366                b: 255,
367                a: 255,
368            },
369        }),
370    )),
371    CssPropertyWithConditions::simple(CssProperty::BackgroundContent(
372        StyleBackgroundContentVecValue::Exact(StyleBackgroundContentVec::from_const_slice(
373            STYLE_BACKGROUND_CONTENT_16746671892555275291_ITEMS,
374        )),
375    )),
376    // .__azul-native-tabs-header p
377    CssPropertyWithConditions::simple(CssProperty::TextAlign(StyleTextAlignValue::Exact(
378        StyleTextAlign::Center,
379    ))),
380    CssPropertyWithConditions::simple(CssProperty::Height(LayoutHeightValue::Exact(
381        LayoutHeight::Px(PixelValue::const_px(21)),
382    ))),
383    CssPropertyWithConditions::simple(CssProperty::BorderBottomWidth(
384        LayoutBorderBottomWidthValue::Exact(LayoutBorderBottomWidth {
385            inner: PixelValue::const_px(1),
386        }),
387    )),
388    CssPropertyWithConditions::simple(CssProperty::BorderLeftWidth(
389        LayoutBorderLeftWidthValue::Exact(LayoutBorderLeftWidth {
390            inner: PixelValue::const_px(1),
391        }),
392    )),
393    CssPropertyWithConditions::simple(CssProperty::BorderRightWidth(
394        LayoutBorderRightWidthValue::Exact(LayoutBorderRightWidth {
395            inner: PixelValue::const_px(1),
396        }),
397    )),
398    CssPropertyWithConditions::simple(CssProperty::BorderTopWidth(
399        LayoutBorderTopWidthValue::Exact(LayoutBorderTopWidth {
400            inner: PixelValue::const_px(1),
401        }),
402    )),
403    CssPropertyWithConditions::simple(CssProperty::BorderBottomStyle(
404        StyleBorderBottomStyleValue::Exact(StyleBorderBottomStyle {
405            inner: BorderStyle::Solid,
406        }),
407    )),
408    CssPropertyWithConditions::simple(CssProperty::BorderLeftStyle(
409        StyleBorderLeftStyleValue::Exact(StyleBorderLeftStyle {
410            inner: BorderStyle::Solid,
411        }),
412    )),
413    CssPropertyWithConditions::simple(CssProperty::BorderRightStyle(
414        StyleBorderRightStyleValue::Exact(StyleBorderRightStyle {
415            inner: BorderStyle::Solid,
416        }),
417    )),
418    CssPropertyWithConditions::simple(CssProperty::BorderTopStyle(
419        StyleBorderTopStyleValue::Exact(StyleBorderTopStyle {
420            inner: BorderStyle::Solid,
421        }),
422    )),
423    CssPropertyWithConditions::simple(CssProperty::BorderBottomColor(
424        StyleBorderBottomColorValue::Exact(StyleBorderBottomColor {
425            inner: ColorU {
426                r: 172,
427                g: 172,
428                b: 172,
429                a: 255,
430            },
431        }),
432    )),
433    CssPropertyWithConditions::simple(CssProperty::BorderLeftColor(
434        StyleBorderLeftColorValue::Exact(StyleBorderLeftColor {
435            inner: ColorU {
436                r: 172,
437                g: 172,
438                b: 172,
439                a: 255,
440            },
441        }),
442    )),
443    CssPropertyWithConditions::simple(CssProperty::BorderRightColor(
444        StyleBorderRightColorValue::Exact(StyleBorderRightColor {
445            inner: ColorU {
446                r: 172,
447                g: 172,
448                b: 172,
449                a: 255,
450            },
451        }),
452    )),
453    CssPropertyWithConditions::simple(CssProperty::BorderTopColor(
454        StyleBorderTopColorValue::Exact(StyleBorderTopColor {
455            inner: ColorU {
456                r: 172,
457                g: 172,
458                b: 172,
459                a: 255,
460            },
461        }),
462    )),
463    CssPropertyWithConditions::simple(CssProperty::BackgroundContent(
464        StyleBackgroundContentVecValue::Exact(StyleBackgroundContentVec::from_const_slice(
465            STYLE_BACKGROUND_CONTENT_8560341490937422656_ITEMS,
466        )),
467    )),
468    CssPropertyWithConditions::simple(CssProperty::AlignItems(LayoutAlignItemsValue::Exact(
469        LayoutAlignItems::Center,
470    ))),
471];
472const CSS_MATCH_14575853790110873394: CssPropertyWithConditionsVec =
473    CssPropertyWithConditionsVec::from_const_slice(CSS_MATCH_14575853790110873394_PROPERTIES);
474
475const CSS_MATCH_17290739305197504468_PROPERTIES: &[CssPropertyWithConditions] = &[
476    // .__azul-native-tabs-header .__azul-native-tabs-before-tabs
477    CssPropertyWithConditions::simple(CssProperty::Width(LayoutWidthValue::Exact(
478        LayoutWidth::Px(PixelValue::const_px(2)),
479    ))),
480    CssPropertyWithConditions::simple(CssProperty::FlexGrow(LayoutFlexGrowValue::Exact(
481        LayoutFlexGrow {
482            inner: FloatValue::const_new(1),
483        },
484    ))),
485    CssPropertyWithConditions::simple(CssProperty::BorderBottomWidth(
486        LayoutBorderBottomWidthValue::Exact(LayoutBorderBottomWidth {
487            inner: PixelValue::const_px(1),
488        }),
489    )),
490    CssPropertyWithConditions::simple(CssProperty::BorderBottomStyle(
491        StyleBorderBottomStyleValue::Exact(StyleBorderBottomStyle {
492            inner: BorderStyle::Solid,
493        }),
494    )),
495    CssPropertyWithConditions::simple(CssProperty::BorderBottomColor(
496        StyleBorderBottomColorValue::Exact(StyleBorderBottomColor {
497            inner: ColorU {
498                r: 172,
499                g: 172,
500                b: 172,
501                a: 255,
502            },
503        }),
504    )),
505];
506const CSS_MATCH_17290739305197504468: CssPropertyWithConditionsVec =
507    CssPropertyWithConditionsVec::from_const_slice(CSS_MATCH_17290739305197504468_PROPERTIES);
508
509const CSS_MATCH_18014909903571752977_PROPERTIES: &[CssPropertyWithConditions] = &[
510    // .__azul-native-tabs-content
511    CssPropertyWithConditions::simple(CssProperty::PaddingRight(LayoutPaddingRightValue::Exact(
512        LayoutPaddingRight {
513            inner: PixelValue::const_px(5),
514        },
515    ))),
516    CssPropertyWithConditions::simple(CssProperty::PaddingLeft(LayoutPaddingLeftValue::Exact(
517        LayoutPaddingLeft {
518            inner: PixelValue::const_px(5),
519        },
520    ))),
521    CssPropertyWithConditions::simple(CssProperty::PaddingBottom(LayoutPaddingBottomValue::Exact(
522        LayoutPaddingBottom {
523            inner: PixelValue::const_px(5),
524        },
525    ))),
526    CssPropertyWithConditions::simple(CssProperty::PaddingTop(LayoutPaddingTopValue::Exact(
527        LayoutPaddingTop {
528            inner: PixelValue::const_px(5),
529        },
530    ))),
531    CssPropertyWithConditions::simple(CssProperty::FlexGrow(LayoutFlexGrowValue::Exact(
532        LayoutFlexGrow {
533            inner: FloatValue::const_new(1),
534        },
535    ))),
536    CssPropertyWithConditions::simple(CssProperty::BorderTopWidth(LayoutBorderTopWidthValue::None)),
537    CssPropertyWithConditions::simple(CssProperty::BorderTopStyle(StyleBorderTopStyleValue::None)),
538    CssPropertyWithConditions::simple(CssProperty::BorderTopColor(StyleBorderTopColorValue::None)),
539    CssPropertyWithConditions::simple(CssProperty::BorderBottomWidth(
540        LayoutBorderBottomWidthValue::Exact(LayoutBorderBottomWidth {
541            inner: PixelValue::const_px(1),
542        }),
543    )),
544    CssPropertyWithConditions::simple(CssProperty::BorderLeftWidth(
545        LayoutBorderLeftWidthValue::Exact(LayoutBorderLeftWidth {
546            inner: PixelValue::const_px(1),
547        }),
548    )),
549    CssPropertyWithConditions::simple(CssProperty::BorderRightWidth(
550        LayoutBorderRightWidthValue::Exact(LayoutBorderRightWidth {
551            inner: PixelValue::const_px(1),
552        }),
553    )),
554    CssPropertyWithConditions::simple(CssProperty::BorderBottomStyle(
555        StyleBorderBottomStyleValue::Exact(StyleBorderBottomStyle {
556            inner: BorderStyle::Solid,
557        }),
558    )),
559    CssPropertyWithConditions::simple(CssProperty::BorderLeftStyle(
560        StyleBorderLeftStyleValue::Exact(StyleBorderLeftStyle {
561            inner: BorderStyle::Solid,
562        }),
563    )),
564    CssPropertyWithConditions::simple(CssProperty::BorderRightStyle(
565        StyleBorderRightStyleValue::Exact(StyleBorderRightStyle {
566            inner: BorderStyle::Solid,
567        }),
568    )),
569    CssPropertyWithConditions::simple(CssProperty::BorderBottomColor(
570        StyleBorderBottomColorValue::Exact(StyleBorderBottomColor {
571            inner: ColorU {
572                r: 172,
573                g: 172,
574                b: 172,
575                a: 255,
576            },
577        }),
578    )),
579    CssPropertyWithConditions::simple(CssProperty::BorderLeftColor(
580        StyleBorderLeftColorValue::Exact(StyleBorderLeftColor {
581            inner: ColorU {
582                r: 172,
583                g: 172,
584                b: 172,
585                a: 255,
586            },
587        }),
588    )),
589    CssPropertyWithConditions::simple(CssProperty::BorderRightColor(
590        StyleBorderRightColorValue::Exact(StyleBorderRightColor {
591            inner: ColorU {
592                r: 172,
593                g: 172,
594                b: 172,
595                a: 255,
596            },
597        }),
598    )),
599    CssPropertyWithConditions::simple(CssProperty::BackgroundContent(
600        StyleBackgroundContentVecValue::Exact(StyleBackgroundContentVec::from_const_slice(
601            STYLE_BACKGROUND_CONTENT_16746671892555275291_ITEMS,
602        )),
603    )),
604];
605const CSS_MATCH_18014909903571752977: CssPropertyWithConditionsVec =
606    CssPropertyWithConditionsVec::from_const_slice(CSS_MATCH_18014909903571752977_PROPERTIES);
607
608const CSS_MATCH_3088386549906605418_PROPERTIES: &[CssPropertyWithConditions] = &[
609    // .__azul-native-tabs-header .__azul-native-tabs-after-tabs
610    CssPropertyWithConditions::simple(CssProperty::FlexGrow(LayoutFlexGrowValue::Exact(
611        LayoutFlexGrow {
612            inner: FloatValue::const_new(1),
613        },
614    ))),
615    CssPropertyWithConditions::simple(CssProperty::BorderBottomWidth(
616        LayoutBorderBottomWidthValue::Exact(LayoutBorderBottomWidth {
617            inner: PixelValue::const_px(1),
618        }),
619    )),
620    CssPropertyWithConditions::simple(CssProperty::BorderBottomStyle(
621        StyleBorderBottomStyleValue::Exact(StyleBorderBottomStyle {
622            inner: BorderStyle::Solid,
623        }),
624    )),
625    CssPropertyWithConditions::simple(CssProperty::BorderBottomColor(
626        StyleBorderBottomColorValue::Exact(StyleBorderBottomColor {
627            inner: ColorU {
628                r: 172,
629                g: 172,
630                b: 172,
631                a: 255,
632            },
633        }),
634    )),
635];
636const CSS_MATCH_3088386549906605418: CssPropertyWithConditionsVec =
637    CssPropertyWithConditionsVec::from_const_slice(CSS_MATCH_3088386549906605418_PROPERTIES);
638
639const CSS_MATCH_4415083954137121609_PROPERTIES: &[CssPropertyWithConditions] = &[
640    // .__azul-native-tabs-header p.__azul-native-tabs-tab-not-active:hover
641    CssPropertyWithConditions::on_hover(CssProperty::BorderBottomWidth(
642        LayoutBorderBottomWidthValue::Exact(LayoutBorderBottomWidth {
643            inner: PixelValue::const_px(1),
644        }),
645    )),
646    CssPropertyWithConditions::on_hover(CssProperty::BorderLeftWidth(
647        LayoutBorderLeftWidthValue::Exact(LayoutBorderLeftWidth {
648            inner: PixelValue::const_px(1),
649        }),
650    )),
651    CssPropertyWithConditions::on_hover(CssProperty::BorderRightWidth(
652        LayoutBorderRightWidthValue::Exact(LayoutBorderRightWidth {
653            inner: PixelValue::const_px(1),
654        }),
655    )),
656    CssPropertyWithConditions::on_hover(CssProperty::BorderTopWidth(
657        LayoutBorderTopWidthValue::Exact(LayoutBorderTopWidth {
658            inner: PixelValue::const_px(1),
659        }),
660    )),
661    CssPropertyWithConditions::on_hover(CssProperty::BorderBottomStyle(
662        StyleBorderBottomStyleValue::Exact(StyleBorderBottomStyle {
663            inner: BorderStyle::Solid,
664        }),
665    )),
666    CssPropertyWithConditions::on_hover(CssProperty::BorderLeftStyle(
667        StyleBorderLeftStyleValue::Exact(StyleBorderLeftStyle {
668            inner: BorderStyle::Solid,
669        }),
670    )),
671    CssPropertyWithConditions::on_hover(CssProperty::BorderRightStyle(
672        StyleBorderRightStyleValue::Exact(StyleBorderRightStyle {
673            inner: BorderStyle::Solid,
674        }),
675    )),
676    CssPropertyWithConditions::on_hover(CssProperty::BorderTopStyle(
677        StyleBorderTopStyleValue::Exact(StyleBorderTopStyle {
678            inner: BorderStyle::Solid,
679        }),
680    )),
681    CssPropertyWithConditions::on_hover(CssProperty::BorderBottomColor(
682        StyleBorderBottomColorValue::Exact(StyleBorderBottomColor {
683            inner: ColorU {
684                r: 126,
685                g: 180,
686                b: 234,
687                a: 255,
688            },
689        }),
690    )),
691    CssPropertyWithConditions::on_hover(CssProperty::BorderLeftColor(
692        StyleBorderLeftColorValue::Exact(StyleBorderLeftColor {
693            inner: ColorU {
694                r: 126,
695                g: 180,
696                b: 234,
697                a: 255,
698            },
699        }),
700    )),
701    CssPropertyWithConditions::on_hover(CssProperty::BorderRightColor(
702        StyleBorderRightColorValue::Exact(StyleBorderRightColor {
703            inner: ColorU {
704                r: 126,
705                g: 180,
706                b: 234,
707                a: 255,
708            },
709        }),
710    )),
711    CssPropertyWithConditions::on_hover(CssProperty::BorderTopColor(
712        StyleBorderTopColorValue::Exact(StyleBorderTopColor {
713            inner: ColorU {
714                r: 126,
715                g: 180,
716                b: 234,
717                a: 255,
718            },
719        }),
720    )),
721    CssPropertyWithConditions::on_hover(CssProperty::BackgroundContent(
722        StyleBackgroundContentVecValue::Exact(StyleBackgroundContentVec::from_const_slice(
723            STYLE_BACKGROUND_CONTENT_15534185073326444643_ITEMS,
724        )),
725    )),
726    // .__azul-native-tabs-header p.__azul-native-tabs-tab-norightborder
727    CssPropertyWithConditions::simple(CssProperty::BorderRightWidth(
728        LayoutBorderRightWidthValue::None,
729    )),
730    CssPropertyWithConditions::simple(CssProperty::BorderRightStyle(
731        StyleBorderRightStyleValue::None,
732    )),
733    CssPropertyWithConditions::simple(CssProperty::BorderRightColor(
734        StyleBorderRightColorValue::None,
735    )),
736    // .__azul-native-tabs-header p.__azul-native-tabs-tab-not-active
737    CssPropertyWithConditions::simple(CssProperty::PaddingRight(LayoutPaddingRightValue::Exact(
738        LayoutPaddingRight {
739            inner: PixelValue::const_px(5),
740        },
741    ))),
742    CssPropertyWithConditions::simple(CssProperty::PaddingLeft(LayoutPaddingLeftValue::Exact(
743        LayoutPaddingLeft {
744            inner: PixelValue::const_px(5),
745        },
746    ))),
747    CssPropertyWithConditions::simple(CssProperty::PaddingBottom(LayoutPaddingBottomValue::Exact(
748        LayoutPaddingBottom {
749            inner: PixelValue::const_px(1),
750        },
751    ))),
752    CssPropertyWithConditions::simple(CssProperty::PaddingTop(LayoutPaddingTopValue::Exact(
753        LayoutPaddingTop {
754            inner: PixelValue::const_px(1),
755        },
756    ))),
757    CssPropertyWithConditions::simple(CssProperty::MarginTop(LayoutMarginTopValue::Exact(
758        LayoutMarginTop {
759            inner: PixelValue::const_px(2),
760        },
761    ))),
762    // .__azul-native-tabs-header p
763    CssPropertyWithConditions::simple(CssProperty::TextAlign(StyleTextAlignValue::Exact(
764        StyleTextAlign::Center,
765    ))),
766    CssPropertyWithConditions::simple(CssProperty::Height(LayoutHeightValue::Exact(
767        LayoutHeight::Px(PixelValue::const_px(21)),
768    ))),
769    CssPropertyWithConditions::simple(CssProperty::BorderBottomWidth(
770        LayoutBorderBottomWidthValue::Exact(LayoutBorderBottomWidth {
771            inner: PixelValue::const_px(1),
772        }),
773    )),
774    CssPropertyWithConditions::simple(CssProperty::BorderLeftWidth(
775        LayoutBorderLeftWidthValue::Exact(LayoutBorderLeftWidth {
776            inner: PixelValue::const_px(1),
777        }),
778    )),
779    CssPropertyWithConditions::simple(CssProperty::BorderRightWidth(
780        LayoutBorderRightWidthValue::Exact(LayoutBorderRightWidth {
781            inner: PixelValue::const_px(1),
782        }),
783    )),
784    CssPropertyWithConditions::simple(CssProperty::BorderTopWidth(
785        LayoutBorderTopWidthValue::Exact(LayoutBorderTopWidth {
786            inner: PixelValue::const_px(1),
787        }),
788    )),
789    CssPropertyWithConditions::simple(CssProperty::BorderBottomStyle(
790        StyleBorderBottomStyleValue::Exact(StyleBorderBottomStyle {
791            inner: BorderStyle::Solid,
792        }),
793    )),
794    CssPropertyWithConditions::simple(CssProperty::BorderLeftStyle(
795        StyleBorderLeftStyleValue::Exact(StyleBorderLeftStyle {
796            inner: BorderStyle::Solid,
797        }),
798    )),
799    CssPropertyWithConditions::simple(CssProperty::BorderRightStyle(
800        StyleBorderRightStyleValue::Exact(StyleBorderRightStyle {
801            inner: BorderStyle::Solid,
802        }),
803    )),
804    CssPropertyWithConditions::simple(CssProperty::BorderTopStyle(
805        StyleBorderTopStyleValue::Exact(StyleBorderTopStyle {
806            inner: BorderStyle::Solid,
807        }),
808    )),
809    CssPropertyWithConditions::simple(CssProperty::BorderBottomColor(
810        StyleBorderBottomColorValue::Exact(StyleBorderBottomColor {
811            inner: ColorU {
812                r: 172,
813                g: 172,
814                b: 172,
815                a: 255,
816            },
817        }),
818    )),
819    CssPropertyWithConditions::simple(CssProperty::BorderLeftColor(
820        StyleBorderLeftColorValue::Exact(StyleBorderLeftColor {
821            inner: ColorU {
822                r: 172,
823                g: 172,
824                b: 172,
825                a: 255,
826            },
827        }),
828    )),
829    CssPropertyWithConditions::simple(CssProperty::BorderRightColor(
830        StyleBorderRightColorValue::Exact(StyleBorderRightColor {
831            inner: ColorU {
832                r: 172,
833                g: 172,
834                b: 172,
835                a: 255,
836            },
837        }),
838    )),
839    CssPropertyWithConditions::simple(CssProperty::BorderTopColor(
840        StyleBorderTopColorValue::Exact(StyleBorderTopColor {
841            inner: ColorU {
842                r: 172,
843                g: 172,
844                b: 172,
845                a: 255,
846            },
847        }),
848    )),
849    CssPropertyWithConditions::simple(CssProperty::BackgroundContent(
850        StyleBackgroundContentVecValue::Exact(StyleBackgroundContentVec::from_const_slice(
851            STYLE_BACKGROUND_CONTENT_8560341490937422656_ITEMS,
852        )),
853    )),
854    CssPropertyWithConditions::simple(CssProperty::AlignItems(LayoutAlignItemsValue::Exact(
855        LayoutAlignItems::Center,
856    ))),
857];
858const CSS_MATCH_4415083954137121609: CssPropertyWithConditionsVec =
859    CssPropertyWithConditionsVec::from_const_slice(CSS_MATCH_4415083954137121609_PROPERTIES);
860
861const CSS_MATCH_4738503469417034630_PROPERTIES: &[CssPropertyWithConditions] = &[
862    // .__azul-native-tabs-container
863    CssPropertyWithConditions::simple(CssProperty::PaddingRight(LayoutPaddingRightValue::Exact(
864        LayoutPaddingRight {
865            inner: PixelValue::const_px(5),
866        },
867    ))),
868    CssPropertyWithConditions::simple(CssProperty::PaddingLeft(LayoutPaddingLeftValue::Exact(
869        LayoutPaddingLeft {
870            inner: PixelValue::const_px(5),
871        },
872    ))),
873    CssPropertyWithConditions::simple(CssProperty::PaddingBottom(LayoutPaddingBottomValue::Exact(
874        LayoutPaddingBottom {
875            inner: PixelValue::const_px(5),
876        },
877    ))),
878    CssPropertyWithConditions::simple(CssProperty::PaddingTop(LayoutPaddingTopValue::Exact(
879        LayoutPaddingTop {
880            inner: PixelValue::const_px(5),
881        },
882    ))),
883    CssPropertyWithConditions::simple(CssProperty::FlexGrow(LayoutFlexGrowValue::Exact(
884        LayoutFlexGrow {
885            inner: FloatValue::const_new(1),
886        },
887    ))),
888];
889const CSS_MATCH_4738503469417034630: CssPropertyWithConditionsVec =
890    CssPropertyWithConditionsVec::from_const_slice(CSS_MATCH_4738503469417034630_PROPERTIES);
891
892const CSS_MATCH_9988039989460234263_PROPERTIES: &[CssPropertyWithConditions] = &[
893    // .__azul-native-tabs-header
894    CssPropertyWithConditions::simple(CssProperty::FontSize(StyleFontSizeValue::Exact(
895        StyleFontSize {
896            inner: PixelValue::const_px(11),
897        },
898    ))),
899    CssPropertyWithConditions::simple(CssProperty::FontFamily(StyleFontFamilyVecValue::Exact(
900        StyleFontFamilyVec::from_const_slice(STYLE_FONT_FAMILY_8122988506401935406_ITEMS),
901    ))),
902    CssPropertyWithConditions::simple(CssProperty::FlexDirection(LayoutFlexDirectionValue::Exact(
903        LayoutFlexDirection::Row,
904    ))),
905];
906const CSS_MATCH_9988039989460234263: CssPropertyWithConditionsVec =
907    CssPropertyWithConditionsVec::from_const_slice(CSS_MATCH_9988039989460234263_PROPERTIES);
908
909// -- NO PADDING
910const CSS_MATCH_18014909903571752977_PROPERTIES_NO_PADDING: &[CssPropertyWithConditions] = &[
911    // .__azul-native-tabs-content
912    CssPropertyWithConditions::simple(CssProperty::FlexGrow(LayoutFlexGrowValue::Exact(
913        LayoutFlexGrow {
914            inner: FloatValue::const_new(1),
915        },
916    ))),
917    CssPropertyWithConditions::simple(CssProperty::BackgroundContent(
918        StyleBackgroundContentVecValue::Exact(StyleBackgroundContentVec::from_const_slice(
919            STYLE_BACKGROUND_CONTENT_16746671892555275291_ITEMS,
920        )),
921    )),
922];
923const CSS_MATCH_18014909903571752977_NO_PADDING: CssPropertyWithConditionsVec =
924    CssPropertyWithConditionsVec::from_const_slice(
925        CSS_MATCH_18014909903571752977_PROPERTIES_NO_PADDING,
926    );
927
928const CSS_MATCH_4738503469417034630_PROPERTIES_NO_PADDING: &[CssPropertyWithConditions] = &[
929    // .__azul-native-tabs-container
930    CssPropertyWithConditions::simple(CssProperty::FlexGrow(LayoutFlexGrowValue::Exact(
931        LayoutFlexGrow {
932            inner: FloatValue::const_new(1),
933        },
934    ))),
935];
936const CSS_MATCH_4738503469417034630_NO_PADDING: CssPropertyWithConditionsVec =
937    CssPropertyWithConditionsVec::from_const_slice(
938        CSS_MATCH_4738503469417034630_PROPERTIES_NO_PADDING,
939    );
940
941// -- REGULAR_INACTIVE_TAB
942
943const CSS_MATCH_11510695043643111367_PROPERTIES: &[CssPropertyWithConditions] = &[
944    // .__azul-native-tabs-header p.__azul-native-tabs-tab-not-active:hover
945    CssPropertyWithConditions::on_hover(CssProperty::BorderBottomWidth(
946        LayoutBorderBottomWidthValue::Exact(LayoutBorderBottomWidth {
947            inner: PixelValue::const_px(1),
948        }),
949    )),
950    CssPropertyWithConditions::on_hover(CssProperty::BorderLeftWidth(
951        LayoutBorderLeftWidthValue::Exact(LayoutBorderLeftWidth {
952            inner: PixelValue::const_px(1),
953        }),
954    )),
955    CssPropertyWithConditions::on_hover(CssProperty::BorderRightWidth(
956        LayoutBorderRightWidthValue::Exact(LayoutBorderRightWidth {
957            inner: PixelValue::const_px(1),
958        }),
959    )),
960    CssPropertyWithConditions::on_hover(CssProperty::BorderTopWidth(
961        LayoutBorderTopWidthValue::Exact(LayoutBorderTopWidth {
962            inner: PixelValue::const_px(1),
963        }),
964    )),
965    CssPropertyWithConditions::on_hover(CssProperty::BorderBottomStyle(
966        StyleBorderBottomStyleValue::Exact(StyleBorderBottomStyle {
967            inner: BorderStyle::Solid,
968        }),
969    )),
970    CssPropertyWithConditions::on_hover(CssProperty::BorderLeftStyle(
971        StyleBorderLeftStyleValue::Exact(StyleBorderLeftStyle {
972            inner: BorderStyle::Solid,
973        }),
974    )),
975    CssPropertyWithConditions::on_hover(CssProperty::BorderRightStyle(
976        StyleBorderRightStyleValue::Exact(StyleBorderRightStyle {
977            inner: BorderStyle::Solid,
978        }),
979    )),
980    CssPropertyWithConditions::on_hover(CssProperty::BorderTopStyle(
981        StyleBorderTopStyleValue::Exact(StyleBorderTopStyle {
982            inner: BorderStyle::Solid,
983        }),
984    )),
985    CssPropertyWithConditions::on_hover(CssProperty::BorderBottomColor(
986        StyleBorderBottomColorValue::Exact(StyleBorderBottomColor {
987            inner: ColorU {
988                r: 126,
989                g: 180,
990                b: 234,
991                a: 255,
992            },
993        }),
994    )),
995    CssPropertyWithConditions::on_hover(CssProperty::BorderLeftColor(
996        StyleBorderLeftColorValue::Exact(StyleBorderLeftColor {
997            inner: ColorU {
998                r: 126,
999                g: 180,
1000                b: 234,
1001                a: 255,
1002            },
1003        }),
1004    )),
1005    CssPropertyWithConditions::on_hover(CssProperty::BorderRightColor(
1006        StyleBorderRightColorValue::Exact(StyleBorderRightColor {
1007            inner: ColorU {
1008                r: 126,
1009                g: 180,
1010                b: 234,
1011                a: 255,
1012            },
1013        }),
1014    )),
1015    CssPropertyWithConditions::on_hover(CssProperty::BorderTopColor(
1016        StyleBorderTopColorValue::Exact(StyleBorderTopColor {
1017            inner: ColorU {
1018                r: 126,
1019                g: 180,
1020                b: 234,
1021                a: 255,
1022            },
1023        }),
1024    )),
1025    CssPropertyWithConditions::on_hover(CssProperty::BackgroundContent(
1026        StyleBackgroundContentVecValue::Exact(StyleBackgroundContentVec::from_const_slice(
1027            STYLE_BACKGROUND_CONTENT_15534185073326444643_ITEMS,
1028        )),
1029    )),
1030    // .__azul-native-tabs-header p.__azul-native-tabs-tab-not-active
1031    CssPropertyWithConditions::simple(CssProperty::PaddingRight(LayoutPaddingRightValue::Exact(
1032        LayoutPaddingRight {
1033            inner: PixelValue::const_px(5),
1034        },
1035    ))),
1036    CssPropertyWithConditions::simple(CssProperty::PaddingLeft(LayoutPaddingLeftValue::Exact(
1037        LayoutPaddingLeft {
1038            inner: PixelValue::const_px(5),
1039        },
1040    ))),
1041    CssPropertyWithConditions::simple(CssProperty::PaddingBottom(LayoutPaddingBottomValue::Exact(
1042        LayoutPaddingBottom {
1043            inner: PixelValue::const_px(1),
1044        },
1045    ))),
1046    CssPropertyWithConditions::simple(CssProperty::PaddingTop(LayoutPaddingTopValue::Exact(
1047        LayoutPaddingTop {
1048            inner: PixelValue::const_px(1),
1049        },
1050    ))),
1051    CssPropertyWithConditions::simple(CssProperty::MarginTop(LayoutMarginTopValue::Exact(
1052        LayoutMarginTop {
1053            inner: PixelValue::const_px(2),
1054        },
1055    ))),
1056    // .__azul-native-tabs-header p
1057    CssPropertyWithConditions::simple(CssProperty::TextAlign(StyleTextAlignValue::Exact(
1058        StyleTextAlign::Center,
1059    ))),
1060    CssPropertyWithConditions::simple(CssProperty::Height(LayoutHeightValue::Exact(
1061        LayoutHeight::Px(PixelValue::const_px(21)),
1062    ))),
1063    CssPropertyWithConditions::simple(CssProperty::BorderBottomWidth(
1064        LayoutBorderBottomWidthValue::Exact(LayoutBorderBottomWidth {
1065            inner: PixelValue::const_px(1),
1066        }),
1067    )),
1068    CssPropertyWithConditions::simple(CssProperty::BorderLeftWidth(
1069        LayoutBorderLeftWidthValue::Exact(LayoutBorderLeftWidth {
1070            inner: PixelValue::const_px(1),
1071        }),
1072    )),
1073    CssPropertyWithConditions::simple(CssProperty::BorderRightWidth(
1074        LayoutBorderRightWidthValue::Exact(LayoutBorderRightWidth {
1075            inner: PixelValue::const_px(1),
1076        }),
1077    )),
1078    CssPropertyWithConditions::simple(CssProperty::BorderTopWidth(
1079        LayoutBorderTopWidthValue::Exact(LayoutBorderTopWidth {
1080            inner: PixelValue::const_px(1),
1081        }),
1082    )),
1083    CssPropertyWithConditions::simple(CssProperty::BorderBottomStyle(
1084        StyleBorderBottomStyleValue::Exact(StyleBorderBottomStyle {
1085            inner: BorderStyle::Solid,
1086        }),
1087    )),
1088    CssPropertyWithConditions::simple(CssProperty::BorderLeftStyle(
1089        StyleBorderLeftStyleValue::Exact(StyleBorderLeftStyle {
1090            inner: BorderStyle::Solid,
1091        }),
1092    )),
1093    CssPropertyWithConditions::simple(CssProperty::BorderRightStyle(
1094        StyleBorderRightStyleValue::Exact(StyleBorderRightStyle {
1095            inner: BorderStyle::Solid,
1096        }),
1097    )),
1098    CssPropertyWithConditions::simple(CssProperty::BorderTopStyle(
1099        StyleBorderTopStyleValue::Exact(StyleBorderTopStyle {
1100            inner: BorderStyle::Solid,
1101        }),
1102    )),
1103    CssPropertyWithConditions::simple(CssProperty::BorderBottomColor(
1104        StyleBorderBottomColorValue::Exact(StyleBorderBottomColor {
1105            inner: ColorU {
1106                r: 172,
1107                g: 172,
1108                b: 172,
1109                a: 255,
1110            },
1111        }),
1112    )),
1113    CssPropertyWithConditions::simple(CssProperty::BorderLeftColor(
1114        StyleBorderLeftColorValue::Exact(StyleBorderLeftColor {
1115            inner: ColorU {
1116                r: 172,
1117                g: 172,
1118                b: 172,
1119                a: 255,
1120            },
1121        }),
1122    )),
1123    CssPropertyWithConditions::simple(CssProperty::BorderRightColor(
1124        StyleBorderRightColorValue::Exact(StyleBorderRightColor {
1125            inner: ColorU {
1126                r: 172,
1127                g: 172,
1128                b: 172,
1129                a: 255,
1130            },
1131        }),
1132    )),
1133    CssPropertyWithConditions::simple(CssProperty::BorderTopColor(
1134        StyleBorderTopColorValue::Exact(StyleBorderTopColor {
1135            inner: ColorU {
1136                r: 172,
1137                g: 172,
1138                b: 172,
1139                a: 255,
1140            },
1141        }),
1142    )),
1143    CssPropertyWithConditions::simple(CssProperty::BackgroundContent(
1144        StyleBackgroundContentVecValue::Exact(StyleBackgroundContentVec::from_const_slice(
1145            STYLE_BACKGROUND_CONTENT_8560341490937422656_ITEMS,
1146        )),
1147    )),
1148    CssPropertyWithConditions::simple(CssProperty::AlignItems(LayoutAlignItemsValue::Exact(
1149        LayoutAlignItems::Center,
1150    ))),
1151];
1152const CSS_MATCH_11510695043643111367: CssPropertyWithConditionsVec =
1153    CssPropertyWithConditionsVec::from_const_slice(CSS_MATCH_11510695043643111367_PROPERTIES);
1154
1155/// Header bar for a tab widget, containing the clickable tab labels.
1156#[derive(Debug, Clone)]
1157#[repr(C)]
1158pub struct TabHeader {
1159    /// Labels for each tab.
1160    pub tabs: StringVec,
1161    /// Zero-based index of the currently active tab.
1162    pub active_tab: usize,
1163    /// Optional callback invoked when a tab is clicked.
1164    pub on_click: OptionTabOnClick,
1165}
1166
1167impl Default for TabHeader {
1168    fn default() -> Self {
1169        Self {
1170            tabs: StringVec::from_const_slice(&[]),
1171            active_tab: 0,
1172            on_click: None.into(),
1173        }
1174    }
1175}
1176
1177/// State passed to the tab-click callback, indicating which tab was selected.
1178#[derive(Debug, Clone, Copy, PartialEq, Eq)]
1179#[repr(C)]
1180pub struct TabHeaderState {
1181    /// Zero-based index of the newly selected tab.
1182    pub active_tab: usize,
1183}
1184
1185/// Signature for the tab-click callback function.
1186pub type TabOnClickCallbackType = extern "C" fn(RefAny, CallbackInfo, TabHeaderState) -> Update;
1187impl_widget_callback!(
1188    TabOnClick,
1189    OptionTabOnClick,
1190    TabOnClickCallback,
1191    TabOnClickCallbackType
1192);
1193
1194azul_core::impl_managed_callback! {
1195    wrapper:        TabOnClickCallback,
1196    info_ty:        CallbackInfo,
1197    return_ty:      Update,
1198    default_ret:    Update::DoNothing,
1199    invoker_static: TAB_ON_CLICK_INVOKER,
1200    invoker_ty:     AzTabOnClickCallbackInvoker,
1201    thunk_fn:       az_tab_on_click_callback_thunk,
1202    setter_fn:      AzApp_setTabOnClickCallbackInvoker,
1203    from_handle_fn: AzTabOnClickCallback_createFromHostHandle,
1204    extra_args:     [ state: TabHeaderState ],
1205}
1206
1207impl TabHeader {
1208    #[must_use] pub fn create(tabs: StringVec) -> Self {
1209        Self {
1210            tabs,
1211            active_tab: 0,
1212            on_click: None.into(),
1213        }
1214    }
1215
1216    #[must_use]
1217    pub fn swap_with_default(&mut self) -> Self {
1218        let mut default = Self::default();
1219        core::mem::swap(&mut default, self);
1220        default
1221    }
1222
1223    pub const fn set_active_tab(&mut self, active_tab: usize) {
1224        self.active_tab = active_tab;
1225    }
1226
1227    #[must_use] pub const fn with_active_tab(mut self, active_tab: usize) -> Self {
1228        self.set_active_tab(active_tab);
1229        self
1230    }
1231
1232    pub fn set_on_click<C: Into<TabOnClickCallback>>(&mut self, refany: RefAny, on_click: C) {
1233        self.on_click = Some(TabOnClick {
1234            refany,
1235            callback: on_click.into(),
1236        })
1237        .into();
1238    }
1239
1240    #[must_use]
1241    pub fn with_on_click<C: Into<TabOnClickCallback>>(
1242        mut self,
1243        refany: RefAny,
1244        on_click: C,
1245    ) -> Self {
1246        self.set_on_click(refany, on_click);
1247        self
1248    }
1249
1250    #[allow(clippy::too_many_lines)] // large but cohesive: single-purpose layout/render/parse routine (one branch per case)
1251    #[must_use] pub fn dom(self) -> Dom {
1252        use azul_core::callbacks::CoreCallbackDataVec;
1253
1254        // classes for previous tab
1255        const IDS_AND_CLASSES_5117007530891373979: &[IdOrClass] = &[
1256            Class(AzString::from_const_str(
1257                "__azul-native-tabs-tab-norightborder",
1258            )),
1259            Class(AzString::from_const_str(
1260                "__azul-native-tabs-tab-not-active",
1261            )),
1262        ]; // CSS_MATCH_4415083954137121609
1263
1264        // classes for current tab
1265        const IDS_AND_CLASSES_15002865554973741556: &[IdOrClass] = &[Class(
1266            AzString::from_const_str("__azul-native-tabs-tab-active"),
1267        )];
1268
1269        // classes for next tab
1270        const IDS_AND_CLASSES_16877793354714897051: &[IdOrClass] = &[
1271            Class(AzString::from_const_str(
1272                "__azul-native-tabs-tab-noleftborder",
1273            )),
1274            Class(AzString::from_const_str(
1275                "__azul-native-tabs-tab-not-active",
1276            )),
1277        ];
1278
1279        // classes for default inactive tab
1280        const IDS_AND_CLASSES_INACTIVE: &[IdOrClass] = &[Class(
1281            AzString::from_const_str("__azul-native-tabs-tab-not-active"),
1282        )];
1283
1284        let on_click_is_some = self.on_click.is_some();
1285
1286        Dom::create_div()
1287            .with_css_props(CSS_MATCH_9988039989460234263)
1288            .with_ids_and_classes({
1289                const IDS_AND_CLASSES_6172459441955124689: &[IdOrClass] =
1290                    &[Class(AzString::from_const_str("__azul-native-tabs-header"))];
1291                IdOrClassVec::from_const_slice(IDS_AND_CLASSES_6172459441955124689)
1292            })
1293            .with_children({
1294                let mut tab_items = vec![Dom::create_div()
1295                    .with_css_props(CSS_MATCH_17290739305197504468)
1296                    .with_ids_and_classes({
1297                        const IDS_AND_CLASSES_8360971686689797550: &[IdOrClass] = &[Class(
1298                            AzString::from_const_str("__azul-native-tabs-before-tabs"),
1299                        )];
1300                        IdOrClassVec::from_const_slice(IDS_AND_CLASSES_8360971686689797550)
1301                    })];
1302
1303                let dataset = TabLocalDataset {
1304                    tab_idx: 0,
1305                    on_click: self.on_click,
1306                };
1307
1308                for (tab_idx, tab) in self.tabs.as_ref().iter().enumerate() {
1309                    let next_tab_is_active = self.active_tab == tab_idx.saturating_add(1);
1310                    let previous_tab_was_active = if self.active_tab == 0 {
1311                        false
1312                    } else {
1313                        self.active_tab == tab_idx.saturating_sub(1)
1314                    };
1315
1316                    let tab_is_active = self.active_tab == tab_idx;
1317
1318                    let (ids_and_classes, css_props) = if tab_is_active {
1319                        (
1320                            IDS_AND_CLASSES_15002865554973741556,
1321                            CSS_MATCH_14575853790110873394,
1322                        )
1323                    } else if next_tab_is_active {
1324                        // tab before the active tab
1325                        (
1326                            IDS_AND_CLASSES_5117007530891373979,
1327                            CSS_MATCH_4415083954137121609,
1328                        )
1329                    } else if previous_tab_was_active {
1330                        // tab after the active tab
1331                        (
1332                            IDS_AND_CLASSES_16877793354714897051,
1333                            CSS_MATCH_13824480602841492081,
1334                        )
1335                    } else {
1336                        // default inactive tab
1337                        (IDS_AND_CLASSES_INACTIVE, CSS_MATCH_11510695043643111367)
1338                    };
1339
1340                    let mut dataset = dataset.clone();
1341                    dataset.tab_idx = tab_idx;
1342                    let dataset = RefAny::new(dataset);
1343
1344                    tab_items.push(
1345                        Dom::create_text(tab.clone())
1346                            .with_callbacks(if on_click_is_some {
1347                                vec![CoreCallbackData {
1348                                    event: EventFilter::Hover(HoverEventFilter::MouseUp),
1349                                    callback: CoreCallback {
1350                                        cb: on_tab_click as usize,
1351                                        ctx: azul_core::refany::OptionRefAny::None,
1352                                    },
1353                                    refany: dataset.clone(),
1354                                }]
1355                                .into()
1356                            } else {
1357                                CoreCallbackDataVec::from_const_slice(&[])
1358                            })
1359                            .with_dataset(Some(dataset).into())
1360                            .with_css_props(css_props)
1361                            .with_ids_and_classes(IdOrClassVec::from_const_slice(ids_and_classes)),
1362                    );
1363                }
1364
1365                tab_items.push(
1366                    Dom::create_div()
1367                        .with_css_props(CSS_MATCH_3088386549906605418)
1368                        .with_ids_and_classes({
1369                            const IDS_AND_CLASSES_11001585590816277275: &[IdOrClass] = &[Class(
1370                                AzString::from_const_str("__azul-native-tabs-after-tabs"),
1371                            )];
1372                            IdOrClassVec::from_const_slice(IDS_AND_CLASSES_11001585590816277275)
1373                        }),
1374                );
1375
1376                tab_items.into()
1377            })
1378    }
1379}
1380
1381/// Content panel displayed beneath the active tab in a tab widget.
1382#[derive(Debug, Clone)]
1383#[repr(C)]
1384pub struct TabContent {
1385    /// The DOM subtree shown as the tab's content area.
1386    pub content: Dom,
1387    /// Whether the content area includes default padding.
1388    pub has_padding: bool,
1389}
1390
1391impl Default for TabContent {
1392    fn default() -> Self {
1393        Self {
1394            content: Dom::create_div(),
1395            has_padding: true,
1396        }
1397    }
1398}
1399
1400impl TabContent {
1401    #[must_use] pub const fn new(content: Dom) -> Self {
1402        Self {
1403            content,
1404            has_padding: true,
1405        }
1406    }
1407
1408    #[must_use]
1409    pub fn swap_with_default(&mut self) -> Self {
1410        let mut default = Self::default();
1411        core::mem::swap(&mut default, self);
1412        default
1413    }
1414
1415    #[must_use] pub const fn with_padding(mut self, padding: bool) -> Self {
1416        self.set_padding(padding);
1417        self
1418    }
1419
1420    pub const fn set_padding(&mut self, padding: bool) {
1421        self.has_padding = padding;
1422    }
1423
1424    #[must_use] pub fn dom(self) -> Dom {
1425        const IDS_AND_CLASSES_2989815829020816222: &[IdOrClass] = &[Class(
1426            AzString::from_const_str("__azul-native-tabs-content"),
1427        )];
1428
1429        let tab_content_css_style = if self.has_padding {
1430            CSS_MATCH_18014909903571752977
1431        } else {
1432            CSS_MATCH_18014909903571752977_NO_PADDING
1433        };
1434
1435        Dom::create_div()
1436            .with_css_props(tab_content_css_style)
1437            .with_children(DomVec::from_vec(vec![Dom::create_div()
1438                .with_ids_and_classes(IdOrClassVec::from_const_slice(
1439                    IDS_AND_CLASSES_2989815829020816222,
1440                ))
1441                .with_children(DomVec::from_vec(vec![self.content]))]))
1442    }
1443}
1444
1445#[derive(Clone)]
1446struct TabLocalDataset {
1447    tab_idx: usize,
1448    on_click: OptionTabOnClick,
1449}
1450
1451extern "C" fn on_tab_click(mut refany: RefAny, info: CallbackInfo) -> Update {
1452    fn select_new_tab_inner(mut refany: RefAny, info: &CallbackInfo) -> Option<Update> {
1453        let mut tab_local_dataset = refany.downcast_mut::<TabLocalDataset>()?;
1454        let tab_idx = tab_local_dataset.tab_idx;
1455        let tab_header_state = TabHeaderState {
1456            active_tab: tab_idx,
1457        };
1458
1459        let result = {
1460            // rustc doesn't understand the borrowing lifetime here
1461            let tab_local_dataset = &mut *tab_local_dataset;
1462            let onclick = &mut tab_local_dataset.on_click;
1463
1464            match onclick.as_mut() {
1465                Some(TabOnClick { callback, refany }) => {
1466                    (callback.cb)(refany.clone(), *info, tab_header_state)
1467                }
1468                None => Update::DoNothing,
1469            }
1470        };
1471
1472        Some(result)
1473    }
1474
1475    select_new_tab_inner(refany, &info).unwrap_or(Update::RefreshDom)
1476}
1477
1478#[cfg(test)]
1479mod autotest_generated {
1480    use std::{
1481        collections::BTreeMap,
1482        sync::{Arc, Mutex},
1483    };
1484
1485    use azul_core::{
1486        dom::{DomId, DomNodeId, NodeId, NodeType},
1487        geom::OptionLogicalPosition,
1488        gl::OptionGlContextPtr,
1489        hit_test::ScrollPosition,
1490        refany::OptionRefAny,
1491        resources::RendererResources,
1492        styled_dom::{NodeHierarchyItemId, StyledDom},
1493        window::{MonitorVec, RawWindowHandle},
1494    };
1495    use azul_css::system::SystemStyle;
1496    use rust_fontconfig::FcFontCache;
1497
1498    use super::*;
1499    #[cfg(feature = "icu")]
1500    use crate::icu::IcuLocalizerHandle;
1501    use crate::{
1502        callbacks::{CallbackChange, CallbackInfoRefData, ExternalSystemCallbacks},
1503        window::LayoutWindow,
1504        window_state::FullWindowState,
1505    };
1506
1507    // ------------------------------------------------------------------
1508    // Helpers
1509    // ------------------------------------------------------------------
1510
1511    const CLASS_ACTIVE: &str = "__azul-native-tabs-tab-active";
1512    const CLASS_NOT_ACTIVE: &str = "__azul-native-tabs-tab-not-active";
1513    const CLASS_NO_LEFT: &str = "__azul-native-tabs-tab-noleftborder";
1514    const CLASS_NO_RIGHT: &str = "__azul-native-tabs-tab-norightborder";
1515    const CLASS_HEADER: &str = "__azul-native-tabs-header";
1516    const CLASS_BEFORE: &str = "__azul-native-tabs-before-tabs";
1517    const CLASS_AFTER: &str = "__azul-native-tabs-after-tabs";
1518    const CLASS_CONTENT: &str = "__azul-native-tabs-content";
1519
1520    fn strings(items: &[&str]) -> StringVec {
1521        StringVec::from_vec(items.iter().map(|s| AzString::from(*s)).collect())
1522    }
1523
1524    fn numbered_labels(n: usize) -> StringVec {
1525        StringVec::from_vec((0..n).map(|i| AzString::from(format!("tab {i}"))).collect())
1526    }
1527
1528    /// The text of a `NodeType::Text` node (`None` for any other node type).
1529    fn text_of(node: &Dom) -> Option<&str> {
1530        match node.root.get_node_type() {
1531            NodeType::Text(s) => Some(s.as_ref().as_str()),
1532            _ => None,
1533        }
1534    }
1535
1536    fn classes(node: &Dom) -> Vec<String> {
1537        node.root
1538            .get_ids_and_classes()
1539            .as_ref()
1540            .iter()
1541            .filter_map(|c| match c {
1542                IdOrClass::Class(s) => Some(s.as_str().to_string()),
1543                IdOrClass::Id(_) => None,
1544            })
1545            .collect()
1546    }
1547
1548    fn class_strs(node: &Dom) -> Vec<&'static str> {
1549        // The widget only ever attaches the eight `&'static str` classes above;
1550        // map back to them so the tests can compare against string literals.
1551        classes(node)
1552            .into_iter()
1553            .map(|c| {
1554                [
1555                    CLASS_ACTIVE,
1556                    CLASS_NOT_ACTIVE,
1557                    CLASS_NO_LEFT,
1558                    CLASS_NO_RIGHT,
1559                    CLASS_HEADER,
1560                    CLASS_BEFORE,
1561                    CLASS_AFTER,
1562                    CLASS_CONTENT,
1563                ]
1564                .into_iter()
1565                .find(|known| *known == c)
1566                .unwrap_or_else(|| panic!("tabs.rs emitted an unknown class: {c}"))
1567            })
1568            .collect()
1569    }
1570
1571    /// A style vec as `(property, condition-count)` pairs in declaration order —
1572    /// the exact shape `Css::from(CssPropertyWithConditionsVec)` preserves.
1573    fn declared(v: &CssPropertyWithConditionsVec) -> Vec<(CssProperty, usize)> {
1574        v.as_ref()
1575            .iter()
1576            .map(|p| (p.property.clone(), p.apply_if.as_ref().len()))
1577            .collect()
1578    }
1579
1580    /// The same view, read back off a rendered node's inline `Css`.
1581    fn inline_declared(node: &Dom) -> Vec<(CssProperty, usize)> {
1582        node.root
1583            .style
1584            .iter_inline_properties()
1585            .map(|(p, conds)| (p.clone(), conds.as_ref().len()))
1586            .collect()
1587    }
1588
1589    /// Every declaration in `v` matching `pred`, in declaration order.
1590    fn decls_where(
1591        v: &CssPropertyWithConditionsVec,
1592        pred: fn(&CssProperty) -> bool,
1593    ) -> Vec<CssProperty> {
1594        v.as_ref()
1595            .iter()
1596            .filter(|p| pred(&p.property))
1597            .map(|p| p.property.clone())
1598            .collect()
1599    }
1600
1601    /// Same, but skipping the `:hover` block — these styles declare the border
1602    /// properties once for hover and again unconditionally, so a raw filter would
1603    /// mix the two sets.
1604    fn plain_decls_where(
1605        v: &CssPropertyWithConditionsVec,
1606        pred: fn(&CssProperty) -> bool,
1607    ) -> Vec<CssProperty> {
1608        v.as_ref()
1609            .iter()
1610            .filter(|p| p.apply_if.as_ref().is_empty() && pred(&p.property))
1611            .map(|p| p.property.clone())
1612            .collect()
1613    }
1614
1615    /// The style vec the widget must pair with a given class combination.
1616    fn style_for_classes(cls: &[&str]) -> CssPropertyWithConditionsVec {
1617        let cls = cls.to_vec();
1618        if cls == [CLASS_ACTIVE] {
1619            CSS_MATCH_14575853790110873394
1620        } else if cls == [CLASS_NO_RIGHT, CLASS_NOT_ACTIVE] {
1621            CSS_MATCH_4415083954137121609
1622        } else if cls == [CLASS_NO_LEFT, CLASS_NOT_ACTIVE] {
1623            CSS_MATCH_13824480602841492081
1624        } else if cls == [CLASS_NOT_ACTIVE] {
1625            CSS_MATCH_11510695043643111367
1626        } else {
1627            panic!("unexpected class combination on a tab node: {cls:?}");
1628        }
1629    }
1630
1631    /// The dataset `RefAny` a rendered tab node carries (cloned, so the caller
1632    /// can `downcast_*` it without borrowing the Dom mutably).
1633    fn dataset_of(node: &Dom) -> RefAny {
1634        node.root
1635            .get_dataset()
1636            .expect("every tab node carries a TabLocalDataset")
1637            .clone()
1638    }
1639
1640    fn tab_idx_of(node: &Dom) -> usize {
1641        let mut ds = dataset_of(node);
1642        let tab_idx = ds
1643            .downcast_ref::<TabLocalDataset>()
1644            .expect("the dataset must be a TabLocalDataset")
1645            .tab_idx;
1646        tab_idx
1647    }
1648
1649    /// A `RefAny` payload recording every state a user `on_click` observes.
1650    #[derive(Default)]
1651    struct ClickLog {
1652        seen: Vec<TabHeaderState>,
1653    }
1654
1655    extern "C" fn record_click(
1656        mut refany: RefAny,
1657        _: CallbackInfo,
1658        state: TabHeaderState,
1659    ) -> Update {
1660        if let Some(mut log) = refany.downcast_mut::<ClickLog>() {
1661            log.seen.push(state);
1662        }
1663        Update::RefreshDom
1664    }
1665
1666    extern "C" fn click_do_nothing(_: RefAny, _: CallbackInfo, _: TabHeaderState) -> Update {
1667        Update::DoNothing
1668    }
1669
1670    extern "C" fn click_refresh_all(_: RefAny, _: CallbackInfo, _: TabHeaderState) -> Update {
1671        Update::RefreshDomAllWindows
1672    }
1673
1674    /// Forces the `fn`-item -> `fn`-pointer coercion the `Into` bound needs.
1675    fn cb(f: TabOnClickCallbackType) -> TabOnClickCallback {
1676        f.into()
1677    }
1678
1679    fn logged(refany: &mut RefAny) -> Vec<TabHeaderState> {
1680        refany
1681            .downcast_ref::<ClickLog>()
1682            .expect("payload must still be a ClickLog")
1683            .seen
1684            .clone()
1685    }
1686
1687    fn dataset(tab_idx: usize, on_click: OptionTabOnClick) -> RefAny {
1688        RefAny::new(TabLocalDataset { tab_idx, on_click })
1689    }
1690
1691    fn click_handler(refany: RefAny, callback: TabOnClickCallbackType) -> OptionTabOnClick {
1692        Some(TabOnClick {
1693            refany,
1694            callback: cb(callback),
1695        })
1696        .into()
1697    }
1698
1699    /// Invokes `on_tab_click` with a minimal `CallbackInfo` (the handler never
1700    /// touches the layout window — it only downcasts its own dataset — so an
1701    /// empty `LayoutWindow` and node 0 as the hit node are enough).
1702    /// Returns the `Update` plus every recorded `CallbackChange`.
1703    fn run_click(data: RefAny) -> (Update, Vec<CallbackChange>) {
1704        let layout_window =
1705            LayoutWindow::new(FcFontCache::default()).expect("LayoutWindow::new failed");
1706
1707        let renderer_resources = RendererResources::default();
1708        let previous_window_state: Option<FullWindowState> = None;
1709        let current_window_state = FullWindowState::default();
1710        let gl_context = OptionGlContextPtr::None;
1711        let scroll_states: BTreeMap<DomId, BTreeMap<NodeHierarchyItemId, ScrollPosition>> =
1712            BTreeMap::new();
1713        let window_handle = RawWindowHandle::Unsupported;
1714        let system_callbacks = ExternalSystemCallbacks::rust_internal();
1715
1716        let ref_data = CallbackInfoRefData {
1717            layout_window: &layout_window,
1718            renderer_resources: &renderer_resources,
1719            previous_window_state: &previous_window_state,
1720            current_window_state: &current_window_state,
1721            gl_context: &gl_context,
1722            current_scroll_manager: &scroll_states,
1723            current_window_handle: &window_handle,
1724            system_callbacks: &system_callbacks,
1725            system_style: Arc::new(SystemStyle::default()),
1726            monitors: Arc::new(Mutex::new(MonitorVec::from_const_slice(&[]))),
1727            #[cfg(feature = "icu")]
1728            icu_localizer: IcuLocalizerHandle::default(),
1729            ctx: OptionRefAny::None,
1730        };
1731
1732        let changes: Arc<Mutex<Vec<CallbackChange>>> = Arc::new(Mutex::new(Vec::new()));
1733
1734        let info = CallbackInfo::new(
1735            &ref_data,
1736            &changes,
1737            DomNodeId {
1738                dom: DomId::ROOT_ID,
1739                node: NodeHierarchyItemId::from_crate_internal(Some(NodeId::new(0))),
1740            },
1741            OptionLogicalPosition::None,
1742            OptionLogicalPosition::None,
1743        );
1744
1745        let update = on_tab_click(data, info);
1746        let recorded = core::mem::take(&mut *changes.lock().expect("change log poisoned"));
1747        (update, recorded)
1748    }
1749
1750    // ==================================================================
1751    // TabHeader::create
1752    // ==================================================================
1753
1754    #[test]
1755    fn create_keeps_the_label_vec_verbatim_and_starts_inert() {
1756        for labels in [
1757            strings(&[]),
1758            strings(&["only"]),
1759            strings(&["a", "b", "c"]),
1760            numbered_labels(1000),
1761        ] {
1762            let expected: Vec<AzString> = labels.as_ref().to_vec();
1763            let header = TabHeader::create(labels);
1764
1765            assert_eq!(
1766                header.tabs.as_ref(),
1767                expected.as_slice(),
1768                "create must not reorder, drop or rewrite labels"
1769            );
1770            assert_eq!(header.active_tab, 0, "a fresh header selects the first tab");
1771            assert!(
1772                header.on_click.is_none(),
1773                "create must not install a callback"
1774            );
1775        }
1776    }
1777
1778    #[test]
1779    fn create_accepts_an_empty_label_vec_and_still_renders_the_spacers() {
1780        // Zero tabs is the degenerate case: the loop body never runs, but the
1781        // before/after spacers must still be emitted or the header collapses.
1782        let dom = TabHeader::create(strings(&[])).dom();
1783        let children = dom.children.as_ref();
1784
1785        assert_eq!(children.len(), 2, "an empty header is just the two spacers");
1786        assert_eq!(class_strs(&children[0]), vec![CLASS_BEFORE]);
1787        assert_eq!(class_strs(&children[1]), vec![CLASS_AFTER]);
1788    }
1789
1790    #[test]
1791    fn create_round_trips_pathological_labels_through_the_dom() {
1792        // The label is only ever cloned into a `NodeType::Text`, never parsed,
1793        // trimmed or NUL-terminated — so every byte must survive verbatim.
1794        let long = "\u{e9}".repeat(50_000);
1795        let pathological: Vec<String> = vec![
1796            String::new(),
1797            " ".to_string(),
1798            "\0embedded\0nul\0".to_string(),
1799            "\u{1F600}\u{1F3F4}\u{E0067}\u{E007F}".to_string(), // emoji + tag sequence
1800            "a\u{0301}\u{0327}\u{0328}".to_string(),            // stacked combining marks
1801            "\u{202E}reversed\u{202C}".to_string(),             // RTL override
1802            "\r\n\t".to_string(),
1803            "\u{FFFD}\u{FEFF}".to_string(), // replacement char + BOM
1804            long.clone(),
1805        ];
1806
1807        let labels = StringVec::from_vec(
1808            pathological
1809                .iter()
1810                .map(|s| AzString::from(s.clone()))
1811                .collect(),
1812        );
1813        let dom = TabHeader::create(labels).dom();
1814        let children = dom.children.as_ref();
1815
1816        assert_eq!(children.len(), pathological.len() + 2);
1817        for (i, expected) in pathological.iter().enumerate() {
1818            assert_eq!(
1819                text_of(&children[i + 1]),
1820                Some(expected.as_str()),
1821                "label {i} did not round-trip through the DOM"
1822            );
1823        }
1824    }
1825
1826    // ==================================================================
1827    // TabHeader::set_active_tab / with_active_tab  (numeric)
1828    // ==================================================================
1829
1830    #[test]
1831    fn set_active_tab_stores_any_usize_without_clamping_or_panicking() {
1832        // `active_tab` is an unsigned index with no documented upper bound and
1833        // no relation to `tabs.len()` — there is no signed path to test, so the
1834        // adversarial values are 0, the wrap-around of 0 and the two extremes.
1835        let extremes = [
1836            0usize,
1837            1,
1838            2,
1839            usize::MAX / 2,
1840            usize::MAX - 1,
1841            usize::MAX,
1842            0usize.wrapping_sub(1),
1843        ];
1844
1845        for value in extremes {
1846            let mut header = TabHeader::create(strings(&["a", "b", "c"]));
1847            header.set_active_tab(value);
1848            assert_eq!(
1849                header.active_tab, value,
1850                "set_active_tab must store the index verbatim (no clamp to len)"
1851            );
1852
1853            // Idempotent + last-write-wins.
1854            header.set_active_tab(value);
1855            assert_eq!(header.active_tab, value);
1856            header.set_active_tab(0);
1857            assert_eq!(header.active_tab, 0, "the last write must win");
1858        }
1859    }
1860
1861    #[test]
1862    fn set_active_tab_touches_nothing_but_the_index() {
1863        let mut header = TabHeader::create(strings(&["a", "b"]));
1864        header.set_on_click(RefAny::new(ClickLog::default()), cb(record_click));
1865
1866        header.set_active_tab(usize::MAX);
1867
1868        assert_eq!(
1869            header.tabs.as_ref(),
1870            strings(&["a", "b"]).as_ref(),
1871            "the labels must be untouched"
1872        );
1873        assert!(
1874            header.on_click.is_some(),
1875            "the callback must survive a selection change"
1876        );
1877    }
1878
1879    #[test]
1880    fn with_active_tab_agrees_with_set_active_tab_at_every_extreme() {
1881        for value in [0usize, 1, usize::MAX / 2, usize::MAX - 1, usize::MAX] {
1882            let built = TabHeader::create(strings(&["a", "b"])).with_active_tab(value);
1883            let mut mutated = TabHeader::create(strings(&["a", "b"]));
1884            mutated.set_active_tab(value);
1885
1886            assert_eq!(built.active_tab, mutated.active_tab);
1887            assert_eq!(built.active_tab, value);
1888            assert_eq!(built.tabs.as_ref(), mutated.tabs.as_ref());
1889            assert!(built.on_click.is_none() && mutated.on_click.is_none());
1890        }
1891    }
1892
1893    #[test]
1894    fn with_active_tab_chains_last_wins() {
1895        let header = TabHeader::create(strings(&["a"]))
1896            .with_active_tab(usize::MAX)
1897            .with_active_tab(7)
1898            .with_active_tab(0);
1899        assert_eq!(header.active_tab, 0);
1900    }
1901
1902    #[test]
1903    fn dom_never_marks_a_tab_active_when_the_index_is_out_of_range() {
1904        // An out-of-range selection must degrade to "nothing selected" rather
1905        // than panicking or wrapping onto some other tab.
1906        let n = 4usize;
1907        for active in [n, n + 1, n + 2, usize::MAX / 2, usize::MAX - 1, usize::MAX] {
1908            let dom = TabHeader::create(numbered_labels(n))
1909                .with_active_tab(active)
1910                .dom();
1911            let children = dom.children.as_ref();
1912            assert_eq!(children.len(), n + 2, "active={active}: wrong child count");
1913
1914            for (i, node) in children[1..=n].iter().enumerate() {
1915                let cls = class_strs(node);
1916                assert!(
1917                    !cls.contains(&CLASS_ACTIVE),
1918                    "active={active}: tab {i} must not be styled active"
1919                );
1920                assert!(
1921                    cls.contains(&CLASS_NOT_ACTIVE),
1922                    "active={active}: tab {i} must be styled inactive"
1923                );
1924            }
1925        }
1926    }
1927
1928    #[test]
1929    fn dom_at_usize_max_gives_every_tab_the_plain_inactive_style() {
1930        // `tab_idx.saturating_add(1)` / `saturating_sub(1)` must not wrap into a
1931        // false neighbour match at the extreme.
1932        let n = 5usize;
1933        let dom = TabHeader::create(numbered_labels(n))
1934            .with_active_tab(usize::MAX)
1935            .dom();
1936
1937        for (i, node) in dom.children.as_ref()[1..=n].iter().enumerate() {
1938            assert_eq!(
1939                class_strs(node),
1940                vec![CLASS_NOT_ACTIVE],
1941                "tab {i} must carry the plain inactive class only"
1942            );
1943            assert_eq!(
1944                inline_declared(node),
1945                declared(&CSS_MATCH_11510695043643111367),
1946                "tab {i} must carry the plain inactive style"
1947            );
1948        }
1949    }
1950
1951    #[test]
1952    fn dom_one_past_the_end_still_seams_the_last_tab() {
1953        // PINNED QUIRK: with `active_tab == tabs.len()` no tab is active, yet the
1954        // *last* tab still matches `active_tab == tab_idx + 1` and loses its right
1955        // border — a visible seam against the after-tabs spacer. Deliberately
1956        // pinned: if the widget starts range-checking `active_tab`, this flips.
1957        let n = 3usize;
1958        let dom = TabHeader::create(numbered_labels(n)).with_active_tab(n).dom();
1959        let children = dom.children.as_ref();
1960
1961        assert_eq!(
1962            class_strs(&children[n]),
1963            vec![CLASS_NO_RIGHT, CLASS_NOT_ACTIVE],
1964            "the last tab is styled as if the (non-existent) next tab were active"
1965        );
1966        for (i, node) in children[1..n].iter().enumerate() {
1967            assert_eq!(class_strs(node), vec![CLASS_NOT_ACTIVE], "tab {i}");
1968        }
1969    }
1970
1971    // ==================================================================
1972    // TabHeader::swap_with_default
1973    // ==================================================================
1974
1975    #[test]
1976    fn swap_with_default_moves_the_state_out_and_leaves_a_default() {
1977        let mut header = TabHeader::create(strings(&["a", "b", "c"])).with_active_tab(2);
1978        header.set_on_click(RefAny::new(ClickLog::default()), cb(record_click));
1979
1980        let taken = header.swap_with_default();
1981
1982        assert_eq!(taken.tabs.as_ref(), strings(&["a", "b", "c"]).as_ref());
1983        assert_eq!(taken.active_tab, 2);
1984        assert!(taken.on_click.is_some(), "the callback moves out with the state");
1985
1986        assert!(header.tabs.as_ref().is_empty(), "the husk must have no tabs");
1987        assert_eq!(header.active_tab, 0);
1988        assert!(
1989            header.on_click.is_none(),
1990            "the husk must not keep a live callback"
1991        );
1992    }
1993
1994    #[test]
1995    fn swap_with_default_twice_yields_a_default_the_second_time() {
1996        let mut header = TabHeader::create(strings(&["a"])).with_active_tab(usize::MAX);
1997        let first = header.swap_with_default();
1998        let second = header.swap_with_default();
1999
2000        assert_eq!(first.active_tab, usize::MAX);
2001        assert_eq!(first.tabs.as_ref().len(), 1);
2002        assert_eq!(second.active_tab, 0);
2003        assert!(second.tabs.as_ref().is_empty());
2004    }
2005
2006    #[test]
2007    fn swap_with_default_releases_the_callback_payload_when_dropped() {
2008        let user = RefAny::new(ClickLog::default());
2009        let mut header = TabHeader::create(strings(&["a"]));
2010        header.set_on_click(user.clone(), cb(record_click));
2011        assert_eq!(user.get_ref_count(), 2);
2012
2013        drop(header.swap_with_default());
2014        assert_eq!(
2015            user.get_ref_count(),
2016            1,
2017            "the swapped-out header must drop its payload clone"
2018        );
2019    }
2020
2021    // ==================================================================
2022    // TabHeader::set_on_click / with_on_click
2023    // ==================================================================
2024
2025    #[test]
2026    fn with_on_click_installs_the_callback_and_touches_nothing_else() {
2027        let before = TabHeader::create(strings(&["a", "b"])).with_active_tab(1);
2028        let mut after = TabHeader::create(strings(&["a", "b"]))
2029            .with_active_tab(1)
2030            .with_on_click(RefAny::new(ClickLog::default()), cb(record_click));
2031
2032        assert_eq!(after.tabs.as_ref(), before.tabs.as_ref());
2033        assert_eq!(after.active_tab, before.active_tab);
2034
2035        let installed = after
2036            .on_click
2037            .as_mut()
2038            .expect("with_on_click must install Some(..)");
2039        assert_eq!(installed.callback.cb as usize, record_click as usize);
2040        assert!(
2041            installed.refany.downcast_ref::<ClickLog>().is_some(),
2042            "the payload must be stored as handed in"
2043        );
2044    }
2045
2046    #[test]
2047    fn set_on_click_overwrites_the_previous_callback_and_payload() {
2048        let mut header = TabHeader::create(strings(&["a"]));
2049        header.set_on_click(RefAny::new(ClickLog::default()), cb(record_click));
2050        header.set_on_click(RefAny::new(42u32), cb(click_do_nothing));
2051
2052        let installed = header.on_click.as_mut().expect("still Some after overwrite");
2053        assert_eq!(
2054            installed.callback.cb as usize,
2055            click_do_nothing as usize,
2056            "the last set_on_click must win"
2057        );
2058        assert_eq!(
2059            installed.refany.downcast_ref::<u32>().map(|v| *v),
2060            Some(42),
2061            "the payload must be replaced together with the fn pointer"
2062        );
2063        assert!(
2064            installed.refany.downcast_ref::<ClickLog>().is_none(),
2065            "the stale payload must be gone"
2066        );
2067    }
2068
2069    #[test]
2070    fn set_on_click_drops_the_previous_payload() {
2071        let first = RefAny::new(ClickLog::default());
2072        let second = RefAny::new(ClickLog::default());
2073        let mut header = TabHeader::create(strings(&["a"]));
2074
2075        header.set_on_click(first.clone(), cb(record_click));
2076        assert_eq!(first.get_ref_count(), 2);
2077        header.set_on_click(second.clone(), cb(record_click));
2078
2079        assert_eq!(
2080            first.get_ref_count(),
2081            1,
2082            "overwriting the callback must release the old payload"
2083        );
2084        assert_eq!(second.get_ref_count(), 2);
2085    }
2086
2087    #[test]
2088    fn generic_callback_conversion_round_trips_the_fn_pointer() {
2089        // The FFI path (`From<Callback>`) transmutes the fn pointer; a corrupted
2090        // value would be an unconditional jump into garbage at click time.
2091        // (Never invoked here.)
2092        let raw = record_click as usize;
2093        let generic = Callback {
2094            cb: unsafe { core::mem::transmute::<usize, crate::callbacks::CallbackType>(raw) },
2095            ctx: azul_core::refany::OptionRefAny::None,
2096        };
2097        let converted: TabOnClickCallback = generic.into();
2098        assert_eq!(converted.cb as usize, raw);
2099    }
2100
2101    // ==================================================================
2102    // TabHeader::dom — structure
2103    // ==================================================================
2104
2105    #[test]
2106    fn dom_is_a_header_div_wrapping_spacer_tabs_spacer() {
2107        for n in [0usize, 1, 2, 3, 17] {
2108            let dom = TabHeader::create(numbered_labels(n)).dom();
2109
2110            assert_eq!(class_strs(&dom), vec![CLASS_HEADER]);
2111            assert_eq!(inline_declared(&dom), declared(&CSS_MATCH_9988039989460234263));
2112            assert!(
2113                matches!(dom.root.get_node_type(), NodeType::Div),
2114                "the header itself is a plain div"
2115            );
2116
2117            let children = dom.children.as_ref();
2118            assert_eq!(children.len(), n + 2, "n={n}: spacer + {n} tabs + spacer");
2119
2120            assert_eq!(class_strs(&children[0]), vec![CLASS_BEFORE]);
2121            assert_eq!(
2122                inline_declared(&children[0]),
2123                declared(&CSS_MATCH_17290739305197504468)
2124            );
2125            assert_eq!(class_strs(&children[n + 1]), vec![CLASS_AFTER]);
2126            assert_eq!(
2127                inline_declared(&children[n + 1]),
2128                declared(&CSS_MATCH_3088386549906605418)
2129            );
2130
2131            for (i, node) in children[1..=n].iter().enumerate() {
2132                assert_eq!(
2133                    text_of(node),
2134                    Some(format!("tab {i}").as_str()),
2135                    "n={n}: tab {i} sits at sibling position {}",
2136                    i + 1
2137                );
2138            }
2139        }
2140    }
2141
2142    #[test]
2143    fn dom_marks_exactly_one_tab_active_for_every_in_range_index() {
2144        let n = 6usize;
2145        for active in 0..n {
2146            let dom = TabHeader::create(numbered_labels(n))
2147                .with_active_tab(active)
2148                .dom();
2149            let children = dom.children.as_ref();
2150
2151            let active_nodes: Vec<usize> = children[1..=n]
2152                .iter()
2153                .enumerate()
2154                .filter(|(_, node)| class_strs(node).contains(&CLASS_ACTIVE))
2155                .map(|(i, _)| i)
2156                .collect();
2157            assert_eq!(
2158                active_nodes,
2159                vec![active],
2160                "exactly the selected tab must carry the active class"
2161            );
2162
2163            for (i, node) in children[1..=n].iter().enumerate() {
2164                let cls = class_strs(node);
2165                assert_eq!(
2166                    cls.contains(&CLASS_ACTIVE),
2167                    !cls.contains(&CLASS_NOT_ACTIVE),
2168                    "active={active}, tab {i}: active/not-active must be exclusive"
2169                );
2170                assert!(
2171                    !(cls.contains(&CLASS_NO_LEFT) && cls.contains(&CLASS_NO_RIGHT)),
2172                    "active={active}, tab {i}: a tab cannot drop both side borders"
2173                );
2174                assert!(
2175                    !(cls.contains(&CLASS_ACTIVE)
2176                        && (cls.contains(&CLASS_NO_LEFT) || cls.contains(&CLASS_NO_RIGHT))),
2177                    "active={active}, tab {i}: the active tab keeps both side borders"
2178                );
2179            }
2180        }
2181    }
2182
2183    #[test]
2184    fn dom_gives_the_neighbours_of_the_active_tab_their_seam_classes() {
2185        // active = 2 of 5: tab 1 loses its right border, tab 3 its left one.
2186        let dom = TabHeader::create(numbered_labels(5)).with_active_tab(2).dom();
2187        let children = dom.children.as_ref();
2188
2189        assert_eq!(class_strs(&children[1]), vec![CLASS_NOT_ACTIVE], "tab 0");
2190        assert_eq!(
2191            class_strs(&children[2]),
2192            vec![CLASS_NO_RIGHT, CLASS_NOT_ACTIVE],
2193            "tab 1 sits before the active tab"
2194        );
2195        assert_eq!(class_strs(&children[3]), vec![CLASS_ACTIVE], "tab 2");
2196        assert_eq!(
2197            class_strs(&children[4]),
2198            vec![CLASS_NO_LEFT, CLASS_NOT_ACTIVE],
2199            "tab 3 sits after the active tab"
2200        );
2201        assert_eq!(class_strs(&children[5]), vec![CLASS_NOT_ACTIVE], "tab 4");
2202    }
2203
2204    #[test]
2205    fn dom_first_tab_active_leaves_the_second_tab_with_a_left_border() {
2206        // PINNED BUG: `previous_tab_was_active` short-circuits on
2207        // `self.active_tab == 0`, so for the *default* selection (tab 0) the tab
2208        // right after the active one never gets `-noleftborder` — it draws a left
2209        // border straight against the active tab's right edge. Every other
2210        // selection (see the test above) does emit the class. Pinned as-is so the
2211        // fix flips this test loudly.
2212        let dom = TabHeader::create(numbered_labels(4)).with_active_tab(0).dom();
2213        let children = dom.children.as_ref();
2214
2215        assert_eq!(class_strs(&children[1]), vec![CLASS_ACTIVE], "tab 0");
2216        assert_eq!(
2217            class_strs(&children[2]),
2218            vec![CLASS_NOT_ACTIVE],
2219            "tab 1 should be [-noleftborder, -not-active] like the active=1..n case"
2220        );
2221        assert_eq!(class_strs(&children[3]), vec![CLASS_NOT_ACTIVE], "tab 2");
2222
2223        // The symmetric neighbour (active = 1) *does* get the class, which is what
2224        // makes the case above an inconsistency rather than a design choice.
2225        let dom = TabHeader::create(numbered_labels(4)).with_active_tab(1).dom();
2226        assert_eq!(
2227            class_strs(&dom.children.as_ref()[3]),
2228            vec![CLASS_NO_LEFT, CLASS_NOT_ACTIVE],
2229            "tab 2 after active tab 1"
2230        );
2231    }
2232
2233    #[test]
2234    fn dom_pairs_every_tab_style_with_the_classes_it_advertises() {
2235        // A swapped class/style pair (e.g. the "noleftborder" node getting the
2236        // style that nulls the *right* border) would be invisible in a class-only
2237        // check, so compare the rendered inline style against the vec the class
2238        // combination demands.
2239        let n = 6usize;
2240        for active in [0usize, 1, 2, n - 1, n, usize::MAX] {
2241            let dom = TabHeader::create(numbered_labels(n))
2242                .with_active_tab(active)
2243                .dom();
2244            for (i, node) in dom.children.as_ref()[1..=n].iter().enumerate() {
2245                let cls = class_strs(node);
2246                assert_eq!(
2247                    inline_declared(node),
2248                    declared(&style_for_classes(&cls)),
2249                    "active={active}, tab {i}: style does not match classes {cls:?}"
2250                );
2251            }
2252        }
2253    }
2254
2255    #[test]
2256    fn neighbour_styles_null_the_border_on_the_side_their_class_names_claim() {
2257        let no_left = plain_decls_where(&CSS_MATCH_13824480602841492081, |p| {
2258            matches!(p, CssProperty::BorderLeftWidth(_))
2259        });
2260        let no_right = plain_decls_where(&CSS_MATCH_4415083954137121609, |p| {
2261            matches!(p, CssProperty::BorderRightWidth(_))
2262        });
2263
2264        assert_eq!(
2265            no_left.first(),
2266            Some(&CssProperty::BorderLeftWidth(
2267                LayoutBorderLeftWidthValue::None
2268            )),
2269            "the -noleftborder style must null the LEFT border first"
2270        );
2271        assert_eq!(
2272            no_right.first(),
2273            Some(&CssProperty::BorderRightWidth(
2274                LayoutBorderRightWidthValue::None
2275            )),
2276            "the -norightborder style must null the RIGHT border first"
2277        );
2278
2279        // ...and must not null the opposite side.
2280        assert!(
2281            !decls_where(&CSS_MATCH_13824480602841492081, |p| matches!(
2282                p,
2283                CssProperty::BorderRightWidth(_)
2284            ))
2285            .contains(&CssProperty::BorderRightWidth(
2286                LayoutBorderRightWidthValue::None
2287            )),
2288            "the -noleftborder style must keep the right border"
2289        );
2290        assert!(
2291            !decls_where(&CSS_MATCH_4415083954137121609, |p| matches!(
2292                p,
2293                CssProperty::BorderLeftWidth(_)
2294            ))
2295            .contains(&CssProperty::BorderLeftWidth(
2296                LayoutBorderLeftWidthValue::None
2297            )),
2298            "the -norightborder style must keep the left border"
2299        );
2300
2301        // The plain inactive style (no active neighbour) keeps both side borders:
2302        // exactly one unconditional declaration per side, and neither is `None`.
2303        let plain_left = plain_decls_where(&CSS_MATCH_11510695043643111367, |p| {
2304            matches!(p, CssProperty::BorderLeftWidth(_))
2305        });
2306        let plain_right = plain_decls_where(&CSS_MATCH_11510695043643111367, |p| {
2307            matches!(p, CssProperty::BorderRightWidth(_))
2308        });
2309        assert_eq!(plain_left.len(), 1, "one unconditional left-border width");
2310        assert_eq!(plain_right.len(), 1, "one unconditional right-border width");
2311        assert_ne!(
2312            plain_left[0],
2313            CssProperty::BorderLeftWidth(LayoutBorderLeftWidthValue::None),
2314            "a tab with no active neighbour keeps its left border"
2315        );
2316        assert_ne!(
2317            plain_right[0],
2318            CssProperty::BorderRightWidth(LayoutBorderRightWidthValue::None),
2319            "a tab with no active neighbour keeps its right border"
2320        );
2321    }
2322
2323    #[test]
2324    fn tab_styles_redeclare_properties_and_therefore_depend_on_declaration_order() {
2325        // PINNED HAZARD: each vec is emitted most-specific-block-first, so the
2326        // generic `.__azul-native-tabs-header p` block *repeats* properties the
2327        // state-specific block already set, with different values. Which one wins
2328        // is decided by the resolver, and the two in azul disagree:
2329        // `PropertyCache::get_property` takes the FIRST match (specific wins,
2330        // intended), `get_property_with_context` takes the LAST ("last wins",
2331        // which silently repaints the active tab like an inactive one).
2332        let heights = decls_where(&CSS_MATCH_14575853790110873394, |p| {
2333            matches!(p, CssProperty::Height(_))
2334        });
2335        assert_eq!(heights.len(), 2, "the active tab declares height twice");
2336        assert_eq!(
2337            heights[0],
2338            CssProperty::Height(LayoutHeightValue::Exact(LayoutHeight::Px(
2339                PixelValue::const_px(23)
2340            ))),
2341            "the active-tab block (23px) must come first"
2342        );
2343        assert_eq!(
2344            heights[1],
2345            CssProperty::Height(LayoutHeightValue::Exact(LayoutHeight::Px(
2346                PixelValue::const_px(21)
2347            ))),
2348            "the generic p block (21px) shadows it under last-wins"
2349        );
2350
2351        let backgrounds = decls_where(&CSS_MATCH_14575853790110873394, |p| {
2352            matches!(p, CssProperty::BackgroundContent(_))
2353        });
2354        assert_eq!(backgrounds.len(), 2);
2355        let kind = |p: &CssProperty| -> &'static str {
2356            let CssProperty::BackgroundContent(v) = p else {
2357                unreachable!()
2358            };
2359            match v
2360                .get_property()
2361                .expect("an exact background")
2362                .as_ref()
2363                .first()
2364                .expect("one layer")
2365            {
2366                StyleBackgroundContent::Color(_) => "flat",
2367                StyleBackgroundContent::LinearGradient(_) => "gradient",
2368                _ => "other",
2369            }
2370        };
2371        assert_eq!(kind(&backgrounds[0]), "flat", "active = white fill");
2372        assert_eq!(
2373            kind(&backgrounds[1]),
2374            "gradient",
2375            "the generic p block re-declares the inactive gradient"
2376        );
2377
2378        // Same shape on the seam styles: the None triple is re-declared as 1px.
2379        let left = plain_decls_where(&CSS_MATCH_13824480602841492081, |p| {
2380            matches!(p, CssProperty::BorderLeftWidth(_))
2381        });
2382        assert_eq!(left.len(), 2, "the -noleftborder style declares it twice");
2383        assert_eq!(left[0], CssProperty::BorderLeftWidth(LayoutBorderLeftWidthValue::None));
2384        assert_eq!(
2385            left[1],
2386            CssProperty::BorderLeftWidth(LayoutBorderLeftWidthValue::Exact(
2387                LayoutBorderLeftWidth {
2388                    inner: PixelValue::const_px(1)
2389                }
2390            ))
2391        );
2392    }
2393
2394    #[test]
2395    fn hover_declarations_stay_conditional_and_the_rest_stay_unconditional() {
2396        // A hover rule leaking into the unconditional set would permanently paint
2397        // every inactive tab in the hover colour.
2398        for style in [
2399            CSS_MATCH_11510695043643111367,
2400            CSS_MATCH_13824480602841492081,
2401            CSS_MATCH_4415083954137121609,
2402        ] {
2403            let conditional = style
2404                .as_ref()
2405                .iter()
2406                .filter(|p| !p.apply_if.as_ref().is_empty())
2407                .count();
2408            assert_eq!(
2409                conditional, 13,
2410                "each inactive-tab style carries exactly the 13 :hover declarations"
2411            );
2412        }
2413
2414        for style in [
2415            CSS_MATCH_14575853790110873394,
2416            CSS_MATCH_9988039989460234263,
2417            CSS_MATCH_17290739305197504468,
2418            CSS_MATCH_3088386549906605418,
2419            CSS_MATCH_18014909903571752977,
2420            CSS_MATCH_18014909903571752977_NO_PADDING,
2421            CSS_MATCH_4738503469417034630,
2422            CSS_MATCH_4738503469417034630_NO_PADDING,
2423        ] {
2424            assert!(
2425                style.as_ref().iter().all(|p| p.apply_if.as_ref().is_empty()),
2426                "this style must apply unconditionally"
2427            );
2428        }
2429    }
2430
2431    #[test]
2432    fn dom_datasets_carry_the_position_of_their_tab() {
2433        for n in [1usize, 2, 5, 1000] {
2434            let dom = TabHeader::create(numbered_labels(n)).dom();
2435            let children = dom.children.as_ref();
2436            for (i, node) in children[1..=n].iter().enumerate() {
2437                assert_eq!(
2438                    tab_idx_of(node),
2439                    i,
2440                    "n={n}: tab {i} must carry its own index, not the loop seed"
2441                );
2442            }
2443        }
2444    }
2445
2446    #[test]
2447    fn dom_attaches_a_dataset_even_without_a_click_callback() {
2448        let dom = TabHeader::create(numbered_labels(2)).dom();
2449        for node in &dom.children.as_ref()[1..=2] {
2450            assert!(
2451                node.root.get_dataset().is_some(),
2452                "the dataset is unconditional (only the callback is not)"
2453            );
2454            assert!(
2455                node.root.get_callbacks().as_ref().is_empty(),
2456                "no callback must be attached when on_click is None"
2457            );
2458        }
2459    }
2460
2461    #[test]
2462    fn dom_attaches_exactly_one_mouseup_callback_per_tab_when_on_click_is_set() {
2463        let n = 4usize;
2464        let dom = TabHeader::create(numbered_labels(n))
2465            .with_on_click(RefAny::new(ClickLog::default()), cb(record_click))
2466            .dom();
2467        let children = dom.children.as_ref();
2468
2469        for node in &children[1..=n] {
2470            let cbs = node.root.get_callbacks();
2471            assert_eq!(cbs.as_ref().len(), 1, "exactly one callback per tab");
2472            let data = &cbs.as_ref()[0];
2473            assert_eq!(
2474                data.event,
2475                EventFilter::Hover(HoverEventFilter::MouseUp),
2476                "tabs must react on mouse-up, not mouse-down"
2477            );
2478            assert_eq!(
2479                data.callback.cb,
2480                on_tab_click as usize,
2481                "the dispatcher must be the widget's own trampoline"
2482            );
2483        }
2484
2485        // The spacers must stay inert — a click on the filler must not select.
2486        assert!(children[0].root.get_callbacks().as_ref().is_empty());
2487        assert!(children[n + 1].root.get_callbacks().as_ref().is_empty());
2488    }
2489
2490    #[test]
2491    fn dom_keeps_the_estimated_child_count_consistent() {
2492        // `estimated_total_children` is what sizes the flat arena; a stale value
2493        // makes `convert_dom_into_compact_dom` under-allocate and panic.
2494        for n in [0usize, 1, 2, 50] {
2495            let dom = TabHeader::create(numbered_labels(n)).dom();
2496            assert_eq!(
2497                dom.estimated_total_children,
2498                dom.recompute_estimated_total_children(),
2499                "n={n}: cached descendant count desynced"
2500            );
2501            assert_eq!(dom.node_count(), n + 3, "header + spacer + {n} tabs + spacer");
2502
2503            let styled = StyledDom::create_from_dom(dom);
2504            assert_eq!(
2505                styled.node_hierarchy.as_ref().len(),
2506                n + 3,
2507                "n={n}: the flattened arena must match node_count()"
2508            );
2509        }
2510    }
2511
2512    #[test]
2513    fn dom_releases_every_dataset_clone_when_the_dom_is_dropped() {
2514        // Each tab clones the user payload into its own `TabLocalDataset`; if the
2515        // widget leaked one, the app state would outlive the DOM forever.
2516        for n in [0usize, 1, 5] {
2517            let user = RefAny::new(ClickLog::default());
2518            let dom = TabHeader::create(numbered_labels(n))
2519                .with_on_click(user.clone(), cb(record_click))
2520                .dom();
2521
2522            assert_eq!(
2523                user.get_ref_count(),
2524                n + 1,
2525                "n={n}: one payload clone per tab, plus the caller's handle"
2526            );
2527
2528            drop(dom);
2529            assert_eq!(
2530                user.get_ref_count(),
2531                1,
2532                "n={n}: dropping the DOM must release every payload clone"
2533            );
2534        }
2535    }
2536
2537    #[test]
2538    fn dom_is_deterministic() {
2539        let build = || {
2540            let dom = TabHeader::create(numbered_labels(5))
2541                .with_active_tab(2)
2542                .dom();
2543            let children = dom.children.as_ref();
2544            let shape: Vec<(Option<String>, Vec<&'static str>)> = children
2545                .iter()
2546                .map(|c| (text_of(c).map(str::to_string), class_strs(c)))
2547                .collect();
2548            shape
2549        };
2550        assert_eq!(build(), build(), "dom() must be a pure function of its state");
2551    }
2552
2553    // ==================================================================
2554    // TabContent
2555    // ==================================================================
2556
2557    fn nested(depth: usize) -> Dom {
2558        let mut dom = Dom::create_text("leaf");
2559        for _ in 0..depth {
2560            dom = Dom::create_div().with_child(dom);
2561        }
2562        dom
2563    }
2564
2565    #[test]
2566    fn content_new_defaults_to_padding_and_keeps_the_content_verbatim() {
2567        let content = nested(3);
2568        let tab_content = TabContent::new(content.clone());
2569
2570        assert!(tab_content.has_padding, "new() defaults to padded");
2571        assert_eq!(
2572            tab_content.content, content,
2573            "new() must not rewrap or normalise the content"
2574        );
2575        assert!(
2576            TabContent::default().has_padding,
2577            "Default agrees with new()"
2578        );
2579    }
2580
2581    #[test]
2582    fn with_padding_and_set_padding_agree_and_are_last_wins() {
2583        for flag in [true, false] {
2584            let built = TabContent::new(Dom::create_div()).with_padding(flag);
2585            let mut mutated = TabContent::new(Dom::create_div());
2586            mutated.set_padding(flag);
2587            assert_eq!(built.has_padding, flag);
2588            assert_eq!(built.has_padding, mutated.has_padding);
2589        }
2590
2591        let toggled = TabContent::new(Dom::create_div())
2592            .with_padding(false)
2593            .with_padding(true)
2594            .with_padding(false);
2595        assert!(!toggled.has_padding, "the last write must win");
2596    }
2597
2598    #[test]
2599    fn content_dom_nests_the_content_under_the_content_class() {
2600        let content = nested(2);
2601        let dom = TabContent::new(content.clone()).dom();
2602
2603        assert!(
2604            class_strs(&dom).is_empty(),
2605            "the outer wrapper carries the style, not the class"
2606        );
2607        assert_eq!(dom.children.as_ref().len(), 1, "one wrapper child");
2608
2609        let inner = &dom.children.as_ref()[0];
2610        assert_eq!(class_strs(inner), vec![CLASS_CONTENT]);
2611        assert!(
2612            inline_declared(inner).is_empty(),
2613            "the classed node carries no inline style of its own"
2614        );
2615        assert_eq!(inner.children.as_ref().len(), 1);
2616        assert_eq!(
2617            inner.children.as_ref()[0],
2618            content,
2619            "the user content must survive the two wrappers untouched"
2620        );
2621    }
2622
2623    #[test]
2624    fn content_dom_picks_the_style_vec_the_padding_flag_asks_for() {
2625        let padded = TabContent::new(Dom::create_div()).with_padding(true).dom();
2626        assert_eq!(
2627            inline_declared(&padded),
2628            declared(&CSS_MATCH_18014909903571752977)
2629        );
2630
2631        let bare = TabContent::new(Dom::create_div()).with_padding(false).dom();
2632        assert_eq!(
2633            inline_declared(&bare),
2634            declared(&CSS_MATCH_18014909903571752977_NO_PADDING)
2635        );
2636
2637        assert_ne!(
2638            inline_declared(&padded),
2639            inline_declared(&bare),
2640            "the two padding modes must not collapse to the same style"
2641        );
2642    }
2643
2644    #[test]
2645    fn the_unpadded_content_style_declares_no_padding_at_all() {
2646        let padding_decls = decls_where(&CSS_MATCH_18014909903571752977_NO_PADDING, |p| {
2647            matches!(
2648                p,
2649                CssProperty::PaddingTop(_)
2650                    | CssProperty::PaddingBottom(_)
2651                    | CssProperty::PaddingLeft(_)
2652                    | CssProperty::PaddingRight(_)
2653            )
2654        });
2655        assert!(
2656            padding_decls.is_empty(),
2657            "has_padding == false must not leave a padding declaration behind"
2658        );
2659
2660        let padded = decls_where(&CSS_MATCH_18014909903571752977, |p| {
2661            matches!(
2662                p,
2663                CssProperty::PaddingTop(_)
2664                    | CssProperty::PaddingBottom(_)
2665                    | CssProperty::PaddingLeft(_)
2666                    | CssProperty::PaddingRight(_)
2667            )
2668        });
2669        assert_eq!(padded.len(), 4, "the padded variant sets all four sides");
2670    }
2671
2672    #[test]
2673    fn content_dom_keeps_the_estimated_child_count_consistent() {
2674        for depth in [0usize, 1, 3, 64] {
2675            let content = nested(depth);
2676            let expected = content.node_count() + 2; // outer wrapper + classed wrapper
2677            let dom = TabContent::new(content).dom();
2678
2679            assert_eq!(
2680                dom.estimated_total_children,
2681                dom.recompute_estimated_total_children(),
2682                "depth={depth}: cached descendant count desynced"
2683            );
2684            assert_eq!(dom.node_count(), expected, "depth={depth}");
2685
2686            let styled = StyledDom::create_from_dom(dom);
2687            assert_eq!(
2688                styled.node_hierarchy.as_ref().len(),
2689                expected,
2690                "depth={depth}: the flattened arena must match node_count()"
2691            );
2692        }
2693    }
2694
2695    #[test]
2696    fn content_swap_with_default_returns_the_old_state_and_leaves_an_empty_div() {
2697        let content = nested(2);
2698        let mut tab_content = TabContent::new(content.clone()).with_padding(false);
2699
2700        let taken = tab_content.swap_with_default();
2701        assert_eq!(taken.content, content);
2702        assert!(!taken.has_padding, "the flag moves out with the content");
2703
2704        assert_eq!(
2705            tab_content.content,
2706            Dom::create_div(),
2707            "the husk must be an empty div"
2708        );
2709        assert!(
2710            tab_content.has_padding,
2711            "the husk is a Default, i.e. padded again"
2712        );
2713    }
2714
2715    // ==================================================================
2716    // on_tab_click
2717    // ==================================================================
2718
2719    #[test]
2720    fn on_tab_click_reports_the_clicked_index_to_the_user_callback() {
2721        for idx in [0usize, 1, 7, usize::MAX / 2, usize::MAX - 1, usize::MAX] {
2722            let mut user = RefAny::new(ClickLog::default());
2723            let (update, changes) =
2724                run_click(dataset(idx, click_handler(user.clone(), record_click)));
2725
2726            assert_eq!(update, Update::RefreshDom);
2727            assert!(
2728                changes.is_empty(),
2729                "the tab handler is stateless — it must not push CallbackChanges"
2730            );
2731            assert_eq!(
2732                logged(&mut user),
2733                vec![TabHeaderState { active_tab: idx }],
2734                "the index must be forwarded verbatim, without arithmetic"
2735            );
2736        }
2737    }
2738
2739    #[test]
2740    fn on_tab_click_propagates_the_user_update_verbatim() {
2741        for (callback, expected) in [
2742            (record_click as TabOnClickCallbackType, Update::RefreshDom),
2743            (click_do_nothing, Update::DoNothing),
2744            (click_refresh_all, Update::RefreshDomAllWindows),
2745        ] {
2746            let (update, _) = run_click(dataset(
2747                3,
2748                click_handler(RefAny::new(ClickLog::default()), callback),
2749            ));
2750            assert_eq!(update, expected, "the user verdict must not be overridden");
2751        }
2752    }
2753
2754    #[test]
2755    fn on_tab_click_without_a_user_callback_does_nothing() {
2756        // A dataset with no `on_click` is reachable only by hand (dom() attaches
2757        // the trampoline only when a callback exists) — it must stay silent
2758        // rather than force a relayout.
2759        let (update, changes) = run_click(dataset(2, None.into()));
2760        assert_eq!(update, Update::DoNothing);
2761        assert!(changes.is_empty());
2762    }
2763
2764    #[test]
2765    fn on_tab_click_on_a_foreign_payload_falls_back_to_refresh() {
2766        // Wrong-typed payload => `downcast_mut` returns None => the documented
2767        // `unwrap_or(RefreshDom)` fallback. Note this is the *opposite* verdict
2768        // from the no-callback case above, which is the asymmetry to watch.
2769        for foreign in [RefAny::new(42u32), RefAny::new(String::from("nope"))] {
2770            let (update, changes) = run_click(foreign);
2771            assert_eq!(update, Update::RefreshDom);
2772            assert!(changes.is_empty());
2773        }
2774    }
2775
2776    #[test]
2777    fn on_tab_click_declines_while_the_dataset_is_already_borrowed() {
2778        // Borrow tracking is shared across clones, so a live `Ref` from anywhere
2779        // must make the handler bail out safely instead of aliasing or panicking.
2780        let mut held = dataset(1, None.into());
2781        let clone = held.clone();
2782        let guard = held
2783            .downcast_ref::<TabLocalDataset>()
2784            .expect("the fixture is a TabLocalDataset");
2785
2786        let (update, changes) = run_click(clone);
2787        assert_eq!(
2788            update,
2789            Update::RefreshDom,
2790            "a contended dataset must fall back, not panic"
2791        );
2792        assert!(changes.is_empty());
2793
2794        drop(guard);
2795
2796        // ...and the borrow must be released again afterwards.
2797        let (update, _) = run_click(held.clone());
2798        assert_eq!(update, Update::DoNothing);
2799    }
2800
2801    #[test]
2802    fn on_tab_click_releases_its_borrow_so_repeated_clicks_keep_working() {
2803        let mut user = RefAny::new(ClickLog::default());
2804        let data = dataset(4, click_handler(user.clone(), record_click));
2805
2806        let (first, _) = run_click(data.clone());
2807        let (second, _) = run_click(data.clone());
2808        let (third, _) = run_click(data.clone());
2809
2810        assert_eq!(
2811            [first, second, third],
2812            [Update::RefreshDom; 3],
2813            "a leaked RefMut would turn later clicks into the RefreshDom fallback \
2814             without ever reaching the user callback"
2815        );
2816        assert_eq!(
2817            logged(&mut user),
2818            vec![TabHeaderState { active_tab: 4 }; 3],
2819            "every click must reach the user callback"
2820        );
2821    }
2822
2823    #[test]
2824    fn clicking_a_rendered_tab_selects_that_tab_end_to_end() {
2825        let n = 5usize;
2826        let mut user = RefAny::new(ClickLog::default());
2827        let dom = TabHeader::create(numbered_labels(n))
2828            .with_on_click(user.clone(), cb(record_click))
2829            .dom();
2830
2831        for i in 0..n {
2832            let data = dom.children.as_ref()[i + 1]
2833                .root
2834                .get_callbacks()
2835                .as_ref()
2836                .first()
2837                .expect("every tab carries the click callback")
2838                .refany
2839                .clone();
2840            let (update, changes) = run_click(data);
2841            assert_eq!(update, Update::RefreshDom);
2842            assert!(changes.is_empty());
2843        }
2844
2845        assert_eq!(
2846            logged(&mut user),
2847            (0..n)
2848                .map(|active_tab| TabHeaderState { active_tab })
2849                .collect::<Vec<_>>(),
2850            "clicking tab i must report exactly i"
2851        );
2852    }
2853
2854    #[test]
2855    fn clicking_a_tab_does_not_move_the_active_tab_by_itself() {
2856        // The widget is stateless: the handler reports the click and nothing
2857        // else. Re-rendering the *same* header must therefore keep tab 0 active.
2858        let user = RefAny::new(ClickLog::default());
2859        let header = TabHeader::create(numbered_labels(3))
2860            .with_on_click(user.clone(), cb(record_click));
2861        let dom = header.clone().dom();
2862
2863        let data = dom.children.as_ref()[3]
2864            .root
2865            .get_callbacks()
2866            .as_ref()
2867            .first()
2868            .expect("tab 2 carries the click callback")
2869            .refany
2870            .clone();
2871        let (update, _) = run_click(data);
2872        assert_eq!(update, Update::RefreshDom);
2873
2874        assert_eq!(
2875            header.active_tab, 0,
2876            "the header itself must be untouched by a click"
2877        );
2878        assert_eq!(
2879            class_strs(&header.dom().children.as_ref()[1]),
2880            vec![CLASS_ACTIVE],
2881            "re-rendering the unchanged header keeps tab 0 active"
2882        );
2883    }
2884}