1
use azul_core::{
2
    a11y::{AccessibilityInfo, AccessibilityRole},
3
    callbacks::{CoreCallbackData, VirtualViewCallbackInfo, VirtualViewReturn},
4
    dom::{
5
        Dom, EventFilter, HoverEventFilter, IdOrClass, IdOrClass::Class, IdOrClassVec, NodeType,
6
        TabIndex,
7
    },
8
    geom::{LogicalPosition, LogicalRect},
9
    refany::RefAny,
10
};
11
use azul_css::{css::BoxOrStatic, AzString};
12
#[allow(clippy::wildcard_imports)]
13
use azul_css::{
14
    dynamic_selector::{CssPropertyWithConditions, CssPropertyWithConditionsVec},
15
    props::{
16
        basic::*,
17
        layout::*,
18
        property::{CssProperty, *},
19
        style::*,
20
    },
21
    *,
22
};
23

            
24
use crate::widgets::button::{Button, ButtonOnClick};
25

            
26
// ---------------------------------------------------------------------------
27
// The flat palette.
28
//
29
// THE TOKEN NAMES ARE THE SAME ONES `flora` USES, deliberately. A widget that
30
// reaches for `SUR` or `INK` instead of an inline `ColorU { r: 178, .. }` can
31
// then be written once and read correctly under either theme, which is the
32
// whole point of having themes rather than two hand-maintained copies of every
33
// widget. The VALUES are what makes this theme flat: plain surfaces, one thin
34
// border weight, and "gradient" stops that are simply equal.
35
//
36
// Names follow flora.css so the two stay comparable:
37
//   PG/SUR/DESK/STRIP/TRACK  surfaces, back to front
38
//   BD..BD5, SEP, SEP2       borders and separators
39
//   INK/INK2/INTRO/SOFT1..3  text, most to least prominent
40
//   RT/RB, HT/HB, PT/PB      raised / hover / pressed control faces
41
//   FLD/FLD2                 input fields
42
//   DISBG/DISTX              disabled
43
//   QT/QT2                   quiet (toolbar) fills
44
//   ACC/DEEP/SOFT/GLOW/ON_ACC accent ramp and what sits on it
45
// ---------------------------------------------------------------------------
46

            
47
// Light mode colors
48
/// Page: the window canvas behind everything.
49
pub const LIGHT_PG: ColorU = ColorU {
50
    r: 255,
51
    g: 255,
52
    b: 255,
53
    a: 255,
54
};
55
/// Surface: panels and cards sitting on the page.
56
pub const LIGHT_SUR: ColorU = ColorU {
57
    r: 248,
58
    g: 249,
59
    b: 250,
60
    a: 255,
61
};
62
/// Desk: the recessed area a document sits on.
63
pub const LIGHT_DESK: ColorU = ColorU {
64
    r: 241,
65
    g: 243,
66
    b: 245,
67
    a: 255,
68
};
69
/// Strip: toolbars and header bands.
70
pub const LIGHT_STRIP: ColorU = ColorU {
71
    r: 233,
72
    g: 236,
73
    b: 239,
74
    a: 255,
75
};
76
/// Track: scrollbar and slider grooves.
77
pub const LIGHT_TRACK: ColorU = ColorU {
78
    r: 222,
79
    g: 226,
80
    b: 230,
81
    a: 255,
82
};
83
/// Default border.
84
pub const LIGHT_BD: ColorU = ColorU {
85
    r: 206,
86
    g: 212,
87
    b: 218,
88
    a: 255,
89
};
90
/// Lighter border, for internal divisions.
91
pub const LIGHT_BD2: ColorU = ColorU {
92
    r: 222,
93
    g: 226,
94
    b: 230,
95
    a: 255,
96
};
97
/// Stronger border, for emphasis or focus.
98
pub const LIGHT_BD3: ColorU = ColorU {
99
    r: 173,
100
    g: 181,
101
    b: 189,
102
    a: 255,
103
};
104
/// Faintest border.
105
pub const LIGHT_BD4: ColorU = ColorU {
106
    r: 233,
107
    g: 236,
108
    b: 239,
109
    a: 255,
110
};
111
/// Border that reads as a highlight.
112
pub const LIGHT_BD5: ColorU = ColorU {
113
    r: 248,
114
    g: 249,
115
    b: 250,
116
    a: 255,
117
};
118
/// Separator line.
119
pub const LIGHT_SEP: ColorU = ColorU {
120
    r: 222,
121
    g: 226,
122
    b: 230,
123
    a: 255,
124
};
125
/// Fainter separator.
126
pub const LIGHT_SEP2: ColorU = ColorU {
127
    r: 233,
128
    g: 236,
129
    b: 239,
130
    a: 255,
131
};
132
/// Body text.
133
pub const LIGHT_INK: ColorU = ColorU {
134
    r: 33,
135
    g: 37,
136
    b: 41,
137
    a: 255,
138
};
139
/// Secondary text.
140
pub const LIGHT_INK2: ColorU = ColorU {
141
    r: 73,
142
    g: 80,
143
    b: 87,
144
    a: 255,
145
};
146
/// Introductory / lead text.
147
pub const LIGHT_INTRO: ColorU = ColorU {
148
    r: 108,
149
    g: 117,
150
    b: 125,
151
    a: 255,
152
};
153
/// Muted text, still readable.
154
pub const LIGHT_SOFT1: ColorU = ColorU {
155
    r: 134,
156
    g: 142,
157
    b: 150,
158
    a: 255,
159
};
160
/// Muted text, hint level.
161
pub const LIGHT_SOFT2: ColorU = ColorU {
162
    r: 173,
163
    g: 181,
164
    b: 189,
165
    a: 255,
166
};
167
/// Barely-there text.
168
pub const LIGHT_SOFT3: ColorU = ColorU {
169
    r: 206,
170
    g: 212,
171
    b: 218,
172
    a: 255,
173
};
174
/// Icon glyphs.
175
pub const LIGHT_ICON: ColorU = ColorU {
176
    r: 73,
177
    g: 80,
178
    b: 87,
179
    a: 255,
180
};
181
/// Raised control, top of the face (flat: equal to RB).
182
pub const LIGHT_RT: ColorU = ColorU {
183
    r: 255,
184
    g: 255,
185
    b: 255,
186
    a: 255,
187
};
188
/// Raised control, bottom of the face.
189
pub const LIGHT_RB: ColorU = ColorU {
190
    r: 248,
191
    g: 249,
192
    b: 250,
193
    a: 255,
194
};
195
/// Hovered control, top of the face.
196
pub const LIGHT_HT: ColorU = ColorU {
197
    r: 241,
198
    g: 243,
199
    b: 245,
200
    a: 255,
201
};
202
/// Hovered control, bottom of the face.
203
pub const LIGHT_HB: ColorU = ColorU {
204
    r: 233,
205
    g: 236,
206
    b: 239,
207
    a: 255,
208
};
209
/// Pressed control, top of the face.
210
pub const LIGHT_PT: ColorU = ColorU {
211
    r: 222,
212
    g: 226,
213
    b: 230,
214
    a: 255,
215
};
216
/// Pressed control, bottom of the face.
217
pub const LIGHT_PB: ColorU = ColorU {
218
    r: 206,
219
    g: 212,
220
    b: 218,
221
    a: 255,
222
};
223
/// Input field fill.
224
pub const LIGHT_FLD: ColorU = ColorU {
225
    r: 255,
226
    g: 255,
227
    b: 255,
228
    a: 255,
229
};
230
/// Secondary field fill (readonly, inset).
231
pub const LIGHT_FLD2: ColorU = ColorU {
232
    r: 248,
233
    g: 249,
234
    b: 250,
235
    a: 255,
236
};
237
/// Disabled control fill.
238
pub const LIGHT_DISBG: ColorU = ColorU {
239
    r: 233,
240
    g: 236,
241
    b: 239,
242
    a: 255,
243
};
244
/// Disabled control text.
245
pub const LIGHT_DISTX: ColorU = ColorU {
246
    r: 173,
247
    g: 181,
248
    b: 189,
249
    a: 255,
250
};
251
/// Quiet fill, e.g. a toolbar button at rest.
252
pub const LIGHT_QT: ColorU = ColorU {
253
    r: 248,
254
    g: 249,
255
    b: 250,
256
    a: 255,
257
};
258
/// Quiet fill, one step stronger.
259
pub const LIGHT_QT2: ColorU = ColorU {
260
    r: 241,
261
    g: 243,
262
    b: 245,
263
    a: 255,
264
};
265
/// Accent.
266
pub const LIGHT_ACC: ColorU = ColorU {
267
    r: 13,
268
    g: 110,
269
    b: 253,
270
    a: 255,
271
};
272
/// Accent, pressed.
273
pub const LIGHT_DEEP: ColorU = ColorU {
274
    r: 10,
275
    g: 88,
276
    b: 202,
277
    a: 255,
278
};
279
/// Accent, muted.
280
pub const LIGHT_SOFT: ColorU = ColorU {
281
    r: 110,
282
    g: 168,
283
    b: 254,
284
    a: 255,
285
};
286
/// Accent as a focus glow (translucent).
287
pub const LIGHT_GLOW: ColorU = ColorU {
288
    r: 13,
289
    g: 110,
290
    b: 253,
291
    a: 64,
292
};
293
/// Text and icons ON an accent fill.
294
pub const LIGHT_ON_ACC: ColorU = ColorU {
295
    r: 255,
296
    g: 255,
297
    b: 255,
298
    a: 255,
299
};
300

            
301
// Dark mode colors
302
/// Page: the window canvas behind everything.
303
pub const DARK_PG: ColorU = ColorU {
304
    r: 33,
305
    g: 37,
306
    b: 41,
307
    a: 255,
308
};
309
/// Surface: panels and cards sitting on the page.
310
pub const DARK_SUR: ColorU = ColorU {
311
    r: 52,
312
    g: 58,
313
    b: 64,
314
    a: 255,
315
};
316
/// Desk: the recessed area a document sits on.
317
pub const DARK_DESK: ColorU = ColorU {
318
    r: 26,
319
    g: 29,
320
    b: 33,
321
    a: 255,
322
};
323
/// Strip: toolbars and header bands.
324
pub const DARK_STRIP: ColorU = ColorU {
325
    r: 43,
326
    g: 48,
327
    b: 53,
328
    a: 255,
329
};
330
/// Track: scrollbar and slider grooves.
331
pub const DARK_TRACK: ColorU = ColorU {
332
    r: 73,
333
    g: 80,
334
    b: 87,
335
    a: 255,
336
};
337
/// Default border.
338
pub const DARK_BD: ColorU = ColorU {
339
    r: 73,
340
    g: 80,
341
    b: 87,
342
    a: 255,
343
};
344
/// Lighter border, for internal divisions.
345
pub const DARK_BD2: ColorU = ColorU {
346
    r: 52,
347
    g: 58,
348
    b: 64,
349
    a: 255,
350
};
351
/// Stronger border, for emphasis or focus.
352
pub const DARK_BD3: ColorU = ColorU {
353
    r: 108,
354
    g: 117,
355
    b: 125,
356
    a: 255,
357
};
358
/// Faintest border.
359
pub const DARK_BD4: ColorU = ColorU {
360
    r: 43,
361
    g: 48,
362
    b: 53,
363
    a: 255,
364
};
365
/// Border that reads as a highlight.
366
pub const DARK_BD5: ColorU = ColorU {
367
    r: 33,
368
    g: 37,
369
    b: 41,
370
    a: 255,
371
};
372
/// Separator line.
373
pub const DARK_SEP: ColorU = ColorU {
374
    r: 73,
375
    g: 80,
376
    b: 87,
377
    a: 255,
378
};
379
/// Fainter separator.
380
pub const DARK_SEP2: ColorU = ColorU {
381
    r: 52,
382
    g: 58,
383
    b: 64,
384
    a: 255,
385
};
386
/// Body text.
387
pub const DARK_INK: ColorU = ColorU {
388
    r: 248,
389
    g: 249,
390
    b: 250,
391
    a: 255,
392
};
393
/// Secondary text.
394
pub const DARK_INK2: ColorU = ColorU {
395
    r: 222,
396
    g: 226,
397
    b: 230,
398
    a: 255,
399
};
400
/// Introductory / lead text.
401
pub const DARK_INTRO: ColorU = ColorU {
402
    r: 173,
403
    g: 181,
404
    b: 189,
405
    a: 255,
406
};
407
/// Muted text, still readable.
408
pub const DARK_SOFT1: ColorU = ColorU {
409
    r: 134,
410
    g: 142,
411
    b: 150,
412
    a: 255,
413
};
414
/// Muted text, hint level.
415
pub const DARK_SOFT2: ColorU = ColorU {
416
    r: 108,
417
    g: 117,
418
    b: 125,
419
    a: 255,
420
};
421
/// Barely-there text.
422
pub const DARK_SOFT3: ColorU = ColorU {
423
    r: 73,
424
    g: 80,
425
    b: 87,
426
    a: 255,
427
};
428
/// Icon glyphs.
429
pub const DARK_ICON: ColorU = ColorU {
430
    r: 222,
431
    g: 226,
432
    b: 230,
433
    a: 255,
434
};
435
/// Raised control, top of the face (flat: equal to RB).
436
pub const DARK_RT: ColorU = ColorU {
437
    r: 60,
438
    g: 66,
439
    b: 73,
440
    a: 255,
441
};
442
/// Raised control, bottom of the face.
443
pub const DARK_RB: ColorU = ColorU {
444
    r: 52,
445
    g: 58,
446
    b: 64,
447
    a: 255,
448
};
449
/// Hovered control, top of the face.
450
pub const DARK_HT: ColorU = ColorU {
451
    r: 73,
452
    g: 80,
453
    b: 87,
454
    a: 255,
455
};
456
/// Hovered control, bottom of the face.
457
pub const DARK_HB: ColorU = ColorU {
458
    r: 60,
459
    g: 66,
460
    b: 73,
461
    a: 255,
462
};
463
/// Pressed control, top of the face.
464
pub const DARK_PT: ColorU = ColorU {
465
    r: 43,
466
    g: 48,
467
    b: 53,
468
    a: 255,
469
};
470
/// Pressed control, bottom of the face.
471
pub const DARK_PB: ColorU = ColorU {
472
    r: 33,
473
    g: 37,
474
    b: 41,
475
    a: 255,
476
};
477
/// Input field fill.
478
pub const DARK_FLD: ColorU = ColorU {
479
    r: 43,
480
    g: 48,
481
    b: 53,
482
    a: 255,
483
};
484
/// Secondary field fill (readonly, inset).
485
pub const DARK_FLD2: ColorU = ColorU {
486
    r: 52,
487
    g: 58,
488
    b: 64,
489
    a: 255,
490
};
491
/// Disabled control fill.
492
pub const DARK_DISBG: ColorU = ColorU {
493
    r: 52,
494
    g: 58,
495
    b: 64,
496
    a: 255,
497
};
498
/// Disabled control text.
499
pub const DARK_DISTX: ColorU = ColorU {
500
    r: 108,
501
    g: 117,
502
    b: 125,
503
    a: 255,
504
};
505
/// Quiet fill, e.g. a toolbar button at rest.
506
pub const DARK_QT: ColorU = ColorU {
507
    r: 43,
508
    g: 48,
509
    b: 53,
510
    a: 255,
511
};
512
/// Quiet fill, one step stronger.
513
pub const DARK_QT2: ColorU = ColorU {
514
    r: 52,
515
    g: 58,
516
    b: 64,
517
    a: 255,
518
};
519
/// Accent.
520
pub const DARK_ACC: ColorU = ColorU {
521
    r: 59,
522
    g: 130,
523
    b: 246,
524
    a: 255,
525
};
526
/// Accent, pressed.
527
pub const DARK_DEEP: ColorU = ColorU {
528
    r: 37,
529
    g: 99,
530
    b: 235,
531
    a: 255,
532
};
533
/// Accent, muted.
534
pub const DARK_SOFT: ColorU = ColorU {
535
    r: 96,
536
    g: 165,
537
    b: 250,
538
    a: 255,
539
};
540
/// Accent as a focus glow (translucent).
541
pub const DARK_GLOW: ColorU = ColorU {
542
    r: 59,
543
    g: 130,
544
    b: 246,
545
    a: 80,
546
};
547
/// Text and icons ON an accent fill.
548
pub const DARK_ON_ACC: ColorU = ColorU {
549
    r: 255,
550
    g: 255,
551
    b: 255,
552
    a: 255,
553
};
554

            
555
// The names this theme used before it had a full palette. Kept so existing
556
// callers keep compiling and keep meaning the same thing.
557
/// Deprecated alias for [`LIGHT_SUR`].
558
pub const LIGHT_BG: ColorU = LIGHT_SUR;
559
/// Deprecated alias for [`LIGHT_INK`].
560
pub const LIGHT_FG: ColorU = LIGHT_INK;
561
/// Deprecated alias for [`DARK_SUR`].
562
pub const DARK_BG: ColorU = DARK_SUR;
563
/// Deprecated alias for [`DARK_INK`].
564
pub const DARK_FG: ColorU = DARK_INK;
565

            
566
#[must_use]
567
16192
pub fn button(btn: Button) -> Dom {
568
16192
    let callbacks = match btn.on_click.into_option() {
569
        Some(ButtonOnClick {
570
198
            refany: data,
571
198
            callback,
572
198
        }) => vec![CoreCallbackData {
573
198
            event: EventFilter::Hover(HoverEventFilter::Click),
574
198
            callback: azul_core::callbacks::CoreCallback {
575
198
                cb: callback.cb as *const () as usize,
576
198
                ctx: callback.ctx,
577
198
            },
578
198
            refany: data,
579
198
        }],
580
15994
        None => Vec::new(),
581
    };
582

            
583
16192
    let btn_type = btn.button_type;
584
16192
    let type_class = btn.button_type.class_name();
585
16192
    let classes: Vec<IdOrClass> = vec![
586
16192
        Class(AzString::from("__azul-native-button")),
587
16192
        Class(AzString::from(type_class)),
588
16192
        Class(AzString::from("__azul-theme-flat")),
589
    ];
590

            
591
16192
    let mut button = Dom::create_node(NodeType::Button);
592

            
593
16192
    let has_icon = !btn.icon.as_str().is_empty() || btn.icon_dom.is_some();
594
16192
    let has_image = btn.image.is_some();
595
16192
    let has_trailing_icon = !btn.trailing_icon.as_str().is_empty();
596

            
597
    // Resolved before `btn`'s fields are moved into the tree below.
598
16192
    let btn_container_style = btn.resolved_container_style();
599
    // A caller who injected a container style (`Some`) chose every property in
600
    // it — resting face, dark colours and states included. The chrome widgets
601
    // hand in part styles complete with their own hover/pressed pairs, and
602
    // anything this theme appended after them would win the cascade (inline
603
    // resolution is last-match) and paint the theme's greys over the ribbon's
604
    // blue. So the theme adds to its OWN default only.
605
16192
    let btn_owns_style = btn.container_style.as_ref().is_none();
606
16192
    let btn_label_style = btn.resolved_label_style();
607
16192
    let btn_image_style = btn.resolved_image_style();
608
16192
    let btn_icon_style = btn.resolved_icon_style();
609
16192
    let btn_trailing_icon_style = btn.resolved_trailing_icon_style();
610

            
611
16192
    let a11y_name_src: String = if btn.label.as_str().is_empty() {
612
4068
        if has_icon {
613
4064
            btn.icon.as_str().to_string()
614
        } else {
615
4
            String::new()
616
        }
617
    } else {
618
12124
        btn.label.as_str().to_string()
619
    };
620

            
621
16192
    if has_icon {
622
15987
        button = button.with_child(match btn.icon_dom.into_option() {
623
33
            Some(dom) => dom,
624
15954
            None => Dom::create_icon(btn.icon).with_css_props(btn_icon_style),
625
        });
626
205
    }
627

            
628
16192
    if let Some(image) = btn.image.into_option() {
629
8
        button = button.with_child(Dom::create_image(image).with_css_props(btn_image_style));
630
16184
    }
631

            
632
16192
    let skip_label = btn.label.as_str().is_empty() && (has_icon || has_image || has_trailing_icon);
633
16192
    if !skip_label {
634
12128
        button = button.with_child(
635
12128
            crate::widgets::widget_p()
636
12128
                .with_css_props(btn_label_style)
637
12128
                .with_children(azul_core::dom::DomVec::from_vec(vec![
638
12128
                    Dom::create_text_do_not_use_without_block_level_wrapper(btn.label),
639
12128
                ])),
640
12128
        );
641
12128
    }
642

            
643
16192
    if has_trailing_icon {
644
2
        button = button.with_child(
645
2
            Dom::create_icon(btn.trailing_icon).with_css_props(btn_trailing_icon_style),
646
2
        );
647
16190
    }
648

            
649
16192
    let a11y_name = a11y_name_src;
650
16192
    let mut a11y = AccessibilityInfo {
651
16192
        role: AccessibilityRole::PushButton,
652
16192
        ..AccessibilityInfo::default()
653
16192
    };
654
16192
    if !a11y_name.is_empty() {
655
16188
        a11y.accessibility_name = Some(AzString::from(a11y_name)).into();
656
16188
    }
657

            
658
    // Add dark mode colors to container style
659
16192
    let mut container_style: Vec<CssPropertyWithConditions> =
660
16192
        btn_container_style.as_slice().to_vec();
661

            
662
16192
    if btn_owns_style {
663
        // The dark face and ink — for the NEUTRAL surface only. A coloured
664
        // command (primary, danger, ...) is its own colour in both modes and
665
        // a link has no face; painting `DARK_BG` over every type, as this
666
        // did, turned a blue primary button into an invisible dark box on a
667
        // dark window. (`ButtonType::surface` is the one place that rule
668
        // lives; `button_states` reads it too.)
669
205
        if btn_type.surface() == crate::widgets::button::ButtonSurface::Neutral {
670
134
            container_style.push(CssPropertyWithConditions::dark_theme(
671
134
                CssProperty::BackgroundContent(
672
134
                    StyleBackgroundContentVec::from_vec(vec![StyleBackgroundContent::Color(
673
134
                        DARK_BG,
674
134
                    )])
675
134
                    .into(),
676
134
                ),
677
134
            ));
678
134
            container_style.push(CssPropertyWithConditions::dark_theme(
679
134
                CssProperty::TextColor(StyleTextColor { inner: DARK_FG }.into()),
680
134
            ));
681
134
        }
682

            
683
        // The interactive states go LAST. Inline declarations resolve last-match
684
        // wins and a `dark_theme(..)` rule matches in every pseudo-state, so any
685
        // dark resting colour pushed after a `dark_on_hover` / `dark_on_focus` twin
686
        // would shadow it — no ring, no hover face, in dark mode.
687
205
        container_style.extend(button_states(btn_type));
688
15987
    }
689

            
690
16192
    button
691
16192
        .with_css_props(CssPropertyWithConditionsVec::from_vec(container_style))
692
16192
        .with_ids_and_classes(IdOrClassVec::from_vec(classes))
693
16192
        .with_callbacks(callbacks.into())
694
16192
        .with_tab_index(TabIndex::Auto)
695
16192
        .with_accessibility_info(a11y)
696
16192
}
697

            
698
use crate::widgets::check_box::CheckBox;
699

            
700
#[must_use]
701
153
pub fn check_box(cb: CheckBox) -> Dom {
702
153
    let cb_name = cb.accessibility_name.clone();
703
153
    crate::widgets::warn_widget_needs_a_name("check_box", cb_name.is_some());
704

            
705
153
    let checked_now = cb.check_box_state.inner.checked;
706

            
707
    use azul_core::{
708
        callbacks::{CoreCallback, CoreCallbackData},
709
        dom::{EventFilter, HoverEventFilter},
710
    };
711

            
712
153
    let mut container_style: Vec<CssPropertyWithConditions> =
713
153
        cb.resolved_container_style().as_slice().to_vec();
714
153
    container_style.push(CssPropertyWithConditions::dark_theme(
715
153
        CssProperty::BackgroundContent(
716
153
            StyleBackgroundContentVec::from_vec(vec![StyleBackgroundContent::Color(DARK_BG)])
717
153
                .into(),
718
153
        ),
719
    ));
720
    // Flat checkmark background in dark mode
721
153
    let is_checked = cb.check_box_state.inner.checked;
722
153
    let mut content_style: Vec<CssPropertyWithConditions> =
723
153
        cb.resolved_content_style().as_slice().to_vec();
724
153
    if checked_now {
725
20
        content_style.push(CssPropertyWithConditions::dark_theme(
726
20
            CssProperty::BackgroundContent(
727
20
                StyleBackgroundContentVec::from_vec(vec![StyleBackgroundContent::Color(DARK_FG)])
728
20
                    .into(),
729
20
            ),
730
20
        ));
731
133
    }
732

            
733
153
    Dom::create_div()
734
153
        .with_ids_and_classes(IdOrClassVec::from(
735
153
            crate::widgets::check_box::CHECKBOX_CONTAINER_CLASS,
736
        ))
737
153
        .with_css_props(CssPropertyWithConditionsVec::from_vec(container_style))
738
153
        .with_callbacks(
739
153
            vec![CoreCallbackData {
740
153
                event: EventFilter::Hover(HoverEventFilter::Click),
741
153
                callback: CoreCallback {
742
153
                    cb: crate::widgets::check_box::input::default_on_checkbox_clicked as usize,
743
153
                    ctx: azul_core::refany::OptionRefAny::None,
744
153
                },
745
153
                refany: RefAny::new(cb.check_box_state),
746
153
            }]
747
153
            .into(),
748
        )
749
153
        .with_tab_index(TabIndex::Auto)
750
153
        .with_accessibility_info(AccessibilityInfo {
751
153
            role: AccessibilityRole::CheckButton,
752
153
            accessibility_name: cb_name,
753
153
            states: azul_core::a11y::AccessibilityStateVec::from_const_slice(if checked_now {
754
20
                &[azul_core::a11y::AccessibilityState::CheckedTrue]
755
            } else {
756
133
                &[azul_core::a11y::AccessibilityState::CheckedFalse]
757
            }),
758
153
            ..Default::default()
759
        })
760
153
        .with_children(
761
153
            vec![Dom::create_div()
762
153
                .with_ids_and_classes(IdOrClassVec::from(
763
153
                    crate::widgets::check_box::CHECKBOX_CONTENT_CLASS,
764
                ))
765
153
                .with_css_props(CssPropertyWithConditionsVec::from_vec(content_style))]
766
153
            .into(),
767
        )
768
153
}
769

            
770
use crate::widgets::text_input::{
771
    default_on_focus_lost, default_on_focus_received, default_on_mouse_hover,
772
    default_on_text_input, default_on_virtual_key_down, TextInput, TEXT_INPUT_CONTAINER_CLASS,
773
    TEXT_INPUT_LABEL_CLASS,
774
};
775

            
776
#[must_use]
777
780
pub fn text_input(mut ti: TextInput) -> Dom {
778
780
    let a11y_name: Option<AzString> = ti.text_input_state.inner.placeholder.as_ref().cloned();
779
780
    let a11y_value: String = ti
780
780
        .text_input_state
781
780
        .inner
782
780
        .text
783
780
        .as_ref()
784
780
        .iter()
785
52829
        .filter_map(|c| core::char::from_u32(*c))
786
780
        .collect();
787

            
788
    use azul_core::{
789
        callbacks::{CoreCallback, CoreCallbackData},
790
        dom::{
791
            AttributeType, DomVec, EventFilter, FocusEventFilter, HoverEventFilter,
792
            IdOrClass::Class, TabIndex,
793
        },
794
    };
795

            
796
780
    ti.text_input_state.inner.cursor_pos = ti.text_input_state.inner.text.len();
797

            
798
780
    let label_text: String = ti
799
780
        .text_input_state
800
780
        .inner
801
780
        .text
802
780
        .iter()
803
52829
        .filter_map(|s| core::char::from_u32(*s))
804
780
        .collect();
805

            
806
780
    let placeholder = ti
807
780
        .text_input_state
808
780
        .inner
809
780
        .placeholder
810
780
        .as_ref()
811
780
        .map(|s| s.as_str().to_string())
812
780
        .unwrap_or_default();
813

            
814
    // Resolved before `ti.text_input_state` is moved out below, and through the
815
    // widget's own resolver rather than a second copy of its default — the point
816
    // of the resolver is that flat and flora cannot drift on this answer.
817
780
    let resolved_container_style = ti.resolved_container_style();
818
780
    let resolved_label_style = ti.resolved_label_style();
819

            
820
780
    let state_ref = RefAny::new(ti.text_input_state);
821

            
822
780
    let mut container_style: Vec<CssPropertyWithConditions> =
823
780
        resolved_container_style.as_slice().to_vec();
824
780
    container_style.push(CssPropertyWithConditions::dark_theme(
825
780
        CssProperty::BackgroundContent(
826
780
            StyleBackgroundContentVec::from_vec(vec![StyleBackgroundContent::Color(DARK_BG)])
827
780
                .into(),
828
780
        ),
829
    ));
830
780
    container_style.push(CssPropertyWithConditions::dark_theme(
831
780
        CssProperty::TextColor(StyleTextColor { inner: DARK_FG }.into()),
832
    ));
833

            
834
    // The interactive states the widget no longer declares. Appended LAST —
835
    // after the base style and after the theme's own dark resting colours —
836
    // because the last matching inline declaration wins: a `dark_theme` border
837
    // pushed after these would beat the dark hover/focus ring. One array so
838
    // half of them cannot ship.
839
780
    container_style.extend_from_slice(&FIELD_BORDER_STATES);
840

            
841
780
    let mut label_style: Vec<CssPropertyWithConditions> = resolved_label_style.as_slice().to_vec();
842
780
    label_style.push(CssPropertyWithConditions::dark_theme(
843
780
        CssProperty::TextColor(StyleTextColor { inner: DARK_FG }.into()),
844
    ));
845

            
846
780
    Dom::create_div()
847
780
        .with_ids_and_classes(vec![Class(TEXT_INPUT_CONTAINER_CLASS.into())].into())
848
780
        .with_css_props(CssPropertyWithConditionsVec::from_vec(container_style))
849
780
        .with_tab_index(TabIndex::Auto)
850
780
        .with_accessibility_info(AccessibilityInfo {
851
780
            role: AccessibilityRole::Text,
852
780
            accessibility_name: a11y_name.into(),
853
780
            accessibility_value: Some(AzString::from(a11y_value)).into(),
854
780
            ..Default::default()
855
780
        })
856
780
        .with_contenteditable(true)
857
780
        .with_dataset(Some(state_ref.clone()).into())
858
780
        .with_callbacks(
859
780
            vec![
860
780
                CoreCallbackData {
861
780
                    event: EventFilter::Focus(FocusEventFilter::FocusReceived),
862
780
                    refany: state_ref.clone(),
863
780
                    callback: CoreCallback {
864
780
                        cb: default_on_focus_received as usize,
865
780
                        ctx: azul_core::refany::OptionRefAny::None,
866
780
                    },
867
780
                },
868
780
                CoreCallbackData {
869
780
                    event: EventFilter::Focus(FocusEventFilter::FocusLost),
870
780
                    refany: state_ref.clone(),
871
780
                    callback: CoreCallback {
872
780
                        cb: default_on_focus_lost as usize,
873
780
                        ctx: azul_core::refany::OptionRefAny::None,
874
780
                    },
875
780
                },
876
780
                CoreCallbackData {
877
780
                    event: EventFilter::Focus(FocusEventFilter::TextInput),
878
780
                    refany: state_ref.clone(),
879
780
                    callback: CoreCallback {
880
780
                        cb: default_on_text_input as usize,
881
780
                        ctx: azul_core::refany::OptionRefAny::None,
882
780
                    },
883
780
                },
884
780
                CoreCallbackData {
885
780
                    event: EventFilter::Focus(FocusEventFilter::VirtualKeyDown),
886
780
                    refany: state_ref.clone(),
887
780
                    callback: CoreCallback {
888
780
                        cb: default_on_virtual_key_down as usize,
889
780
                        ctx: azul_core::refany::OptionRefAny::None,
890
780
                    },
891
780
                },
892
780
                CoreCallbackData {
893
780
                    event: EventFilter::Hover(HoverEventFilter::MouseOver),
894
780
                    refany: state_ref,
895
780
                    callback: CoreCallback {
896
780
                        cb: default_on_mouse_hover as usize,
897
780
                        ctx: azul_core::refany::OptionRefAny::None,
898
780
                    },
899
780
                },
900
            ]
901
780
            .into(),
902
        )
903
780
        .with_children(
904
780
            vec![crate::widgets::widget_p()
905
780
                .with_ids_and_classes(vec![Class(TEXT_INPUT_LABEL_CLASS.into())].into())
906
780
                .with_css_props(CssPropertyWithConditionsVec::from_vec(label_style))
907
780
                .with_attribute(AttributeType::Placeholder(placeholder.into()))
908
780
                .with_children(DomVec::from_vec(vec![
909
780
                    Dom::create_text_do_not_use_without_block_level_wrapper(label_text),
910
                ]))]
911
780
            .into(),
912
        )
913
780
}
914

            
915
#[must_use]
916
pub fn label(l: crate::widgets::label::Label) -> Dom {
917
    use azul_core::dom::{IdOrClass::Class, IdOrClassVec};
918
    use AzString;
919

            
920
    static LABEL_CLASS: &[IdOrClass] = &[Class(AzString::from_const_str("__azul-native-label"))];
921

            
922
    // Resolved before `l.string` is moved out below.
923
    let label_style = l.resolved_label_style();
924

            
925
    crate::widgets::widget_p_with_text(l.string)
926
        .with_ids_and_classes(IdOrClassVec::from_const_slice(LABEL_CLASS))
927
        .with_css_props(label_style)
928
}
929

            
930
#[must_use]
931
119
pub fn switch(s: crate::widgets::switch::Switch) -> Dom {
932
119
    let is_checked = s.switch_state.inner.checked;
933
    // Resolved up front: the knob's Dom is built after `s.switch_state` has
934
    // been moved into the callback's RefAny, and the resolver needs the whole
935
    // widget.
936
119
    let resolved_track_style = s.resolved_track_style();
937
119
    let resolved_knob_style = s.resolved_knob_style();
938
    use azul_core::{
939
        callbacks::{CoreCallback, CoreCallbackData},
940
        dom::{Dom, EventFilter, HoverEventFilter, IdOrClassVec, TabIndex},
941
    };
942

            
943
119
    let sw_name = s.accessibility_name.clone();
944
119
    crate::widgets::warn_widget_needs_a_name("switch", sw_name.is_some());
945

            
946
119
    let switch_checked = s.switch_state.inner.checked;
947

            
948
119
    Dom::create_div()
949
119
        .with_ids_and_classes(IdOrClassVec::from(
950
119
            crate::widgets::switch::SWITCH_TRACK_CLASS,
951
        ))
952
119
        .with_css_props(resolved_track_style.as_slice().to_vec().into())
953
119
        .with_callbacks(
954
119
            alloc::vec![CoreCallbackData {
955
119
                event: EventFilter::Hover(HoverEventFilter::Click),
956
119
                callback: CoreCallback {
957
119
                    cb: crate::widgets::switch::input::default_on_switch_clicked as usize,
958
119
                    ctx: azul_core::refany::OptionRefAny::None,
959
119
                },
960
119
                refany: RefAny::new(s.switch_state),
961
119
            }]
962
119
            .into(),
963
        )
964
119
        .with_tab_index(TabIndex::Auto)
965
119
        .with_accessibility_info(AccessibilityInfo {
966
119
            role: AccessibilityRole::CheckButton,
967
119
            accessibility_name: sw_name,
968
119
            states: azul_core::a11y::AccessibilityStateVec::from_vec(alloc::vec![
969
119
                if switch_checked {
970
19
                    azul_core::a11y::AccessibilityState::CheckedTrue
971
                } else {
972
100
                    azul_core::a11y::AccessibilityState::CheckedFalse
973
                },
974
            ]),
975
119
            ..Default::default()
976
        })
977
119
        .with_children(
978
119
            alloc::vec![Dom::create_div()
979
119
                .with_ids_and_classes(IdOrClassVec::from(
980
119
                    crate::widgets::switch::SWITCH_KNOB_CLASS
981
                ))
982
119
                .with_css_props(resolved_knob_style.as_slice().to_vec().into())]
983
119
            .into(),
984
        )
985
119
}
986

            
987
// -----------------------------------------------------------------------------
988
// PROGRESSBAR
989
// -----------------------------------------------------------------------------
990

            
991
#[must_use]
992
9
pub fn progressbar(bar: crate::widgets::progressbar::ProgressBar) -> Dom {
993
9
    let height = bar.height;
994
9
    let dataset = RefAny::new(crate::widgets::progressbar::ProgressBarLocalDataset { bar });
995
9
    Dom::create_virtual_view(
996
9
        dataset.clone(),
997
9
        azul_core::callbacks::VirtualViewCallback::create(progressbar_render_virtual_view),
998
    )
999
9
    .with_dataset(Some(dataset).into())
9
    .with_css_props(CssPropertyWithConditionsVec::from_vec(vec![
9
        CssPropertyWithConditions::simple(CssProperty::Height(LayoutHeightValue::Exact(
9
            LayoutHeight::Px(height),
9
        ))),
9
        CssPropertyWithConditions::simple(CssProperty::Width(LayoutWidthValue::Exact(
9
            LayoutWidth::Px(PixelValue::percent(100.0)),
9
        ))),
9
        CssPropertyWithConditions::simple(CssProperty::OverflowX(LayoutOverflowValue::Exact(
9
            LayoutOverflow::Hidden,
9
        ))),
9
        CssPropertyWithConditions::simple(CssProperty::OverflowY(LayoutOverflowValue::Exact(
9
            LayoutOverflow::Hidden,
9
        ))),
    ]))
9
}
/// The render core behind [`ProgressBar::render_bar`] (percentage widths,
/// `bounds_px: None`) and the `VirtualView` callback (absolute pixel sizes
/// computed from the node's known bounds, `Some((width, height))`).
///
/// The split exists because the two contexts size differently. Percentages
/// inside a `VirtualView` DO resolve correctly against the view's bounds
/// (the child DOM lays out against its own viewport - it briefly resolved
/// against the WINDOW, fixed 2026-08-29, pinned by
/// `a_virtual_view_child_lays_out_against_the_view_bounds_not_the_window`),
/// but the bounds mode stays PIXEL-based for what percentages cannot
/// express: the container is sized to `bounds - 2px borders` so its 1px
/// border ring lands INSIDE the box - with the normal-flow sizing (content
/// height + borders) the ring overflowed the VV node and was clipped away
/// at the right and bottom ("oddly cut off", user report 2026-08-29) - and
/// the fill is an exact device-pixel split of the known content width.
#[allow(clippy::too_many_lines)]
#[must_use]
207
pub fn progressbar_render_bar_impl(
207
    bar: crate::widgets::progressbar::ProgressBar,
207
    bounds_px: Option<(f32, f32)>,
207
) -> Dom {
    {
        use azul_core::dom::DomVec;
207
        let this = bar;
207
        let percent_done = this.progressbar_state.percent_done.clamp(0.0, 100.0);
        // Sizes resolved per context (see fn docs). The bounds branch
        // subtracts the container's 1px border ring so children + borders
        // exactly fill the VV box.
207
        let (bar_width, remaining_width) = match bounds_px {
2
            Some((w, _)) => {
2
                let inner = (w - 2.0).max(0.0);
2
                let filled = inner * percent_done / 100.0;
2
                (PixelValue::px(filled), PixelValue::px(inner - filled))
            }
205
            None => (
205
                PixelValue::percent(percent_done),
205
                PixelValue::percent(100.0 - percent_done),
205
            ),
        };
207
        let container_height = match bounds_px {
2
            Some((_, h)) => PixelValue::px((h - 2.0).max(0.0)),
205
            None => this.height,
        };
207
        let mut container_props = vec![
            // .__azul-native-progress-bar-container
207
            CssPropertyWithConditions::simple(CssProperty::Height(LayoutHeightValue::Exact(
207
                LayoutHeight::Px(container_height),
207
            ))),
            // `display: flex` is LOAD-BEARING: azul's default display is
            // BLOCK, so `flex-direction: row` alone stacks the two
            // children as full-width, zero-height block boxes - the fill
            // never painted anywhere the widget was used (found 2026-08-29
            // via the azpaint pressure meter; also the real culprit behind
            // the "inline-width meter never repaints" ledger entry).
207
            CssPropertyWithConditions::simple(CssProperty::Display(LayoutDisplayValue::Exact(
207
                LayoutDisplay::Flex,
207
            ))),
207
            CssPropertyWithConditions::simple(CssProperty::FlexDirection(
207
                LayoutFlexDirectionValue::Exact(LayoutFlexDirection::Row),
207
            )),
207
            CssPropertyWithConditions::simple(CssProperty::BorderBottomRightRadius(
207
                StyleBorderBottomRightRadiusValue::Exact(StyleBorderBottomRightRadius {
207
                    inner: PixelValue::const_px(3),
207
                }),
207
            )),
207
            CssPropertyWithConditions::simple(CssProperty::BorderBottomLeftRadius(
207
                StyleBorderBottomLeftRadiusValue::Exact(StyleBorderBottomLeftRadius {
207
                    inner: PixelValue::const_px(3),
207
                }),
207
            )),
207
            CssPropertyWithConditions::simple(CssProperty::BorderTopRightRadius(
207
                StyleBorderTopRightRadiusValue::Exact(StyleBorderTopRightRadius {
207
                    inner: PixelValue::const_px(3),
207
                }),
207
            )),
207
            CssPropertyWithConditions::simple(CssProperty::BorderTopLeftRadius(
207
                StyleBorderTopLeftRadiusValue::Exact(StyleBorderTopLeftRadius {
207
                    inner: PixelValue::const_px(3),
207
                }),
207
            )),
207
            CssPropertyWithConditions::simple(CssProperty::BorderBottomWidth(
207
                LayoutBorderBottomWidthValue::Exact(LayoutBorderBottomWidth {
207
                    inner: PixelValue::const_px(1),
207
                }),
207
            )),
207
            CssPropertyWithConditions::simple(CssProperty::BorderLeftWidth(
207
                LayoutBorderLeftWidthValue::Exact(LayoutBorderLeftWidth {
207
                    inner: PixelValue::const_px(1),
207
                }),
207
            )),
207
            CssPropertyWithConditions::simple(CssProperty::BorderRightWidth(
207
                LayoutBorderRightWidthValue::Exact(LayoutBorderRightWidth {
207
                    inner: PixelValue::const_px(1),
207
                }),
207
            )),
207
            CssPropertyWithConditions::simple(CssProperty::BorderTopWidth(
207
                LayoutBorderTopWidthValue::Exact(LayoutBorderTopWidth {
207
                    inner: PixelValue::const_px(1),
207
                }),
207
            )),
207
            CssPropertyWithConditions::simple(CssProperty::BorderBottomStyle(
207
                StyleBorderBottomStyleValue::Exact(StyleBorderBottomStyle {
207
                    inner: BorderStyle::Solid,
207
                }),
207
            )),
207
            CssPropertyWithConditions::simple(CssProperty::BorderLeftStyle(
207
                StyleBorderLeftStyleValue::Exact(StyleBorderLeftStyle {
207
                    inner: BorderStyle::Solid,
207
                }),
207
            )),
207
            CssPropertyWithConditions::simple(CssProperty::BorderRightStyle(
207
                StyleBorderRightStyleValue::Exact(StyleBorderRightStyle {
207
                    inner: BorderStyle::Solid,
207
                }),
207
            )),
207
            CssPropertyWithConditions::simple(CssProperty::BorderTopStyle(
207
                StyleBorderTopStyleValue::Exact(StyleBorderTopStyle {
207
                    inner: BorderStyle::Solid,
207
                }),
207
            )),
207
            CssPropertyWithConditions::simple(CssProperty::BorderBottomColor(
207
                StyleBorderBottomColorValue::Exact(StyleBorderBottomColor { inner: LIGHT_BD3 }),
207
            )),
207
            CssPropertyWithConditions::simple(CssProperty::BorderLeftColor(
207
                StyleBorderLeftColorValue::Exact(StyleBorderLeftColor { inner: LIGHT_BD3 }),
207
            )),
207
            CssPropertyWithConditions::simple(CssProperty::BorderRightColor(
207
                StyleBorderRightColorValue::Exact(StyleBorderRightColor { inner: LIGHT_BD3 }),
207
            )),
207
            CssPropertyWithConditions::simple(CssProperty::BorderTopColor(
207
                StyleBorderTopColorValue::Exact(StyleBorderTopColor { inner: LIGHT_BD3 }),
207
            )),
207
            CssPropertyWithConditions::simple(CssProperty::BackgroundContent(
207
                StyleBackgroundContentVecValue::Exact(this.container_background.clone()),
207
            )),
        ];
207
        if let Some((w, _)) = bounds_px {
2
            container_props.push(CssPropertyWithConditions::simple(CssProperty::Width(
2
                LayoutWidthValue::Exact(LayoutWidth::Px(PixelValue::px((w - 2.0).max(0.0)))),
2
            )));
205
        }
207
        Dom::create_div()
207
            .with_css_props(CssPropertyWithConditionsVec::from_vec(container_props))
207
            .with_ids_and_classes({
                const IDS_AND_CLASSES_10874511710181900075: &[IdOrClass] = &[Class(
                    AzString::from_const_str("__azul-native-progress-bar-container"),
                )];
207
                IdOrClassVec::from_const_slice(IDS_AND_CLASSES_10874511710181900075)
            })
            // For a progress bar the VALUE is the content: two coloured divs
            // say nothing to a screen reader, "75%" says everything. Published
            // on every build so it tracks the bar; a callback that moves the
            // bar live without a rebuild keeps it current with
            // `CallbackInfo::set_accessibility_value` on this node.
207
            .with_accessibility_info(AccessibilityInfo {
207
                role: AccessibilityRole::ProgressBar,
207
                accessibility_value: Some(AzString::from(alloc::format!(
207
                    "{:.0}%",
                    // NaN clamps to NaN and would read "NaN%"; an unknown
                    // value announces as empty, like the bar it draws.
207
                    if percent_done.is_finite() { percent_done } else { 0.0 }
                )))
207
                .into(),
207
                ..Default::default()
            })
207
            .with_children(DomVec::from_vec(vec![
207
                Dom::create_div()
207
                    .with_css_props(CssPropertyWithConditionsVec::from_vec(vec![
                        // .__azul-native-progress-bar-bar
                        // Use percentage width instead of flex-grow hack
207
                        CssPropertyWithConditions::simple(CssProperty::Width(
207
                            LayoutWidthValue::Exact(LayoutWidth::Px(bar_width)),
207
                        )),
207
                        CssPropertyWithConditions::simple(CssProperty::BoxShadowBottom(
207
                            StyleBoxShadowValue::Exact(BoxOrStatic::heap(StyleBoxShadow {
207
                                offset_x: PixelValueNoPercent {
207
                                    inner: PixelValue::const_px(0),
207
                                },
207
                                offset_y: PixelValueNoPercent {
207
                                    inner: PixelValue::const_px(0),
207
                                },
207
                                color: ColorU {
207
                                    r: 0,
207
                                    g: 51,
207
                                    b: 0,
207
                                    a: 51,
207
                                },
207
                                blur_radius: PixelValueNoPercent {
207
                                    inner: PixelValue::const_px(15),
207
                                },
207
                                spread_radius: PixelValueNoPercent {
207
                                    inner: PixelValue::const_px(12),
207
                                },
207
                                clip_mode: BoxShadowClipMode::Inset,
207
                            })),
207
                        )),
207
                        CssPropertyWithConditions::simple(CssProperty::BoxShadowTop(
207
                            StyleBoxShadowValue::Exact(BoxOrStatic::heap(StyleBoxShadow {
207
                                offset_x: PixelValueNoPercent {
207
                                    inner: PixelValue::const_px(0),
207
                                },
207
                                offset_y: PixelValueNoPercent {
207
                                    inner: PixelValue::const_px(0),
207
                                },
207
                                color: ColorU {
207
                                    r: 0,
207
                                    g: 51,
207
                                    b: 0,
207
                                    a: 51,
207
                                },
207
                                blur_radius: PixelValueNoPercent {
207
                                    inner: PixelValue::const_px(15),
207
                                },
207
                                spread_radius: PixelValueNoPercent {
207
                                    inner: PixelValue::const_px(12),
207
                                },
207
                                clip_mode: BoxShadowClipMode::Inset,
207
                            })),
207
                        )),
207
                        CssPropertyWithConditions::simple(CssProperty::BoxShadowRight(
207
                            StyleBoxShadowValue::Exact(BoxOrStatic::heap(StyleBoxShadow {
207
                                offset_x: PixelValueNoPercent {
207
                                    inner: PixelValue::const_px(0),
207
                                },
207
                                offset_y: PixelValueNoPercent {
207
                                    inner: PixelValue::const_px(0),
207
                                },
207
                                color: ColorU {
207
                                    r: 0,
207
                                    g: 51,
207
                                    b: 0,
207
                                    a: 51,
207
                                },
207
                                blur_radius: PixelValueNoPercent {
207
                                    inner: PixelValue::const_px(15),
207
                                },
207
                                spread_radius: PixelValueNoPercent {
207
                                    inner: PixelValue::const_px(12),
207
                                },
207
                                clip_mode: BoxShadowClipMode::Inset,
207
                            })),
207
                        )),
207
                        CssPropertyWithConditions::simple(CssProperty::BoxShadowLeft(
207
                            StyleBoxShadowValue::Exact(BoxOrStatic::heap(StyleBoxShadow {
207
                                offset_x: PixelValueNoPercent {
207
                                    inner: PixelValue::const_px(0),
207
                                },
207
                                offset_y: PixelValueNoPercent {
207
                                    inner: PixelValue::const_px(0),
207
                                },
207
                                color: ColorU {
207
                                    r: 0,
207
                                    g: 51,
207
                                    b: 0,
207
                                    a: 51,
207
                                },
207
                                blur_radius: PixelValueNoPercent {
207
                                    inner: PixelValue::const_px(15),
207
                                },
207
                                spread_radius: PixelValueNoPercent {
207
                                    inner: PixelValue::const_px(12),
207
                                },
207
                                clip_mode: BoxShadowClipMode::Inset,
207
                            })),
207
                        )),
207
                        CssPropertyWithConditions::simple(CssProperty::BorderBottomRightRadius(
207
                            StyleBorderBottomRightRadiusValue::Exact(
207
                                StyleBorderBottomRightRadius {
207
                                    inner: PixelValue::const_px(1),
207
                                },
207
                            ),
207
                        )),
207
                        CssPropertyWithConditions::simple(CssProperty::BorderBottomLeftRadius(
207
                            StyleBorderBottomLeftRadiusValue::Exact(StyleBorderBottomLeftRadius {
207
                                inner: PixelValue::const_px(1),
207
                            }),
207
                        )),
207
                        CssPropertyWithConditions::simple(CssProperty::BorderTopRightRadius(
207
                            StyleBorderTopRightRadiusValue::Exact(StyleBorderTopRightRadius {
207
                                inner: PixelValue::const_px(1),
207
                            }),
207
                        )),
207
                        CssPropertyWithConditions::simple(CssProperty::BorderTopLeftRadius(
207
                            StyleBorderTopLeftRadiusValue::Exact(StyleBorderTopLeftRadius {
207
                                inner: PixelValue::const_px(1),
207
                            }),
207
                        )),
207
                        CssPropertyWithConditions::simple(CssProperty::BackgroundContent(
207
                            StyleBackgroundContentVecValue::Exact(this.bar_background),
207
                        )),
                    ]))
207
                    .with_ids_and_classes({
                        const IDS_AND_CLASSES_16512648314570682783: &[IdOrClass] = &[Class(
                            AzString::from_const_str("__azul-native-progress-bar-bar"),
                        )];
207
                        IdOrClassVec::from_const_slice(IDS_AND_CLASSES_16512648314570682783)
                    }),
207
                Dom::create_div()
207
                    .with_css_props(CssPropertyWithConditionsVec::from_vec(vec![
                        // .__azul-native-progress-bar-remaining
                        // Use percentage width for the remaining space
207
                        CssPropertyWithConditions::simple(CssProperty::Width(
207
                            LayoutWidthValue::Exact(LayoutWidth::Px(remaining_width)),
207
                        )),
                    ]))
207
                    .with_ids_and_classes({
                        const IDS_AND_CLASSES_2492405364126620395: &[IdOrClass] = &[Class(
                            AzString::from_const_str("__azul-native-progress-bar-remaining"),
                        )];
207
                        IdOrClassVec::from_const_slice(IDS_AND_CLASSES_2492405364126620395)
                    }),
            ]))
    }
207
}
#[must_use]
/// The widget's `VirtualView` callback: render the CURRENT state of the bar
/// into the node's bounds. Invoked on mount and again every time
/// [`ProgressBar::update_progress`] queues a re-render.
///
/// The bar is not scrollable content, so all three rects collapse to one:
/// `materialized` == `virtual_rect` == the container's box at origin zero.
3
pub extern "C" fn progressbar_render_virtual_view(
3
    mut data: RefAny,
3
    info: VirtualViewCallbackInfo,
3
) -> VirtualViewReturn {
3
    let Some(state) = data.downcast_ref::<crate::widgets::progressbar::ProgressBarLocalDataset>()
    else {
        // Foreign payload: render nothing rather than lying about bounds.
1
        return VirtualViewReturn::default();
    };
2
    let size = info.bounds.get_logical_size();
2
    let rect = LogicalRect::new(LogicalPosition::zero(), size);
    // Clone-per-render is two enum copies + an `AzString`-less state copy; the
    // backgrounds are either `&'static` (shared, no alloc) or a caller-owned
    // heap vec that must be preserved for the NEXT render anyway. Pixel
    // widths, not percentages: the callback knows its bounds (see
    // `render_bar_impl`).
2
    VirtualViewReturn::with_dom(
2
        progressbar_render_bar_impl(state.bar.clone(), Some((size.width, size.height))),
2
        rect,
2
        rect,
    )
3
}
217
pub fn slider(slider: crate::widgets::slider::Slider) -> Dom {
217
    let value_now = slider.slider_state.inner.value;
217
    let a11y_name = slider.accessibility_name.clone();
217
    crate::widgets::warn_widget_needs_a_name("Slider", a11y_name.is_some());
    use azul_core::{
        callbacks::{CoreCallback, CoreCallbackData},
        dom::{EventFilter, HoverEventFilter, TabIndex},
        refany::{OptionRefAny, RefAny},
    };
    // Resolved before `slider.slider_state` is moved out below; the resolvers
    // borrow `&slider`, and the thumb's margin is derived from the state.
217
    let resolved_track_style = slider.resolved_track_style();
217
    let resolved_thumb_style = slider.resolved_thumb_style();
217
    let state = RefAny::new(slider.slider_state);
217
    let mk = |event: EventFilter, cb: usize| CoreCallbackData {
1736
        event,
1736
        callback: CoreCallback {
1736
            cb,
1736
            ctx: OptionRefAny::None,
1736
        },
1736
        refany: state.clone(),
1736
    };
217
    let callbacks = vec![
217
        mk(
217
            EventFilter::Hover(HoverEventFilter::MouseDown),
217
            crate::widgets::slider::on_slider_pointer_down as usize,
217
        ),
217
        mk(
217
            EventFilter::Hover(HoverEventFilter::MouseMove),
217
            crate::widgets::slider::on_slider_pointer_move as usize,
217
        ),
217
        mk(
217
            EventFilter::Hover(HoverEventFilter::MouseUp),
217
            crate::widgets::slider::on_slider_pointer_up as usize,
217
        ),
217
        mk(
217
            EventFilter::Focus(azul_core::events::FocusEventFilter::VirtualKeyDown),
217
            crate::widgets::slider::on_slider_key as usize,
217
        ),
217
        mk(
217
            EventFilter::Hover(HoverEventFilter::MouseLeave),
217
            crate::widgets::slider::on_slider_pointer_leave as usize,
217
        ),
217
        mk(
217
            EventFilter::Hover(HoverEventFilter::TouchStart),
217
            crate::widgets::slider::on_slider_pointer_down as usize,
217
        ),
217
        mk(
217
            EventFilter::Hover(HoverEventFilter::TouchMove),
217
            crate::widgets::slider::on_slider_pointer_move as usize,
217
        ),
217
        mk(
217
            EventFilter::Hover(HoverEventFilter::TouchEnd),
217
            crate::widgets::slider::on_slider_pointer_up as usize,
217
        ),
    ];
217
    let mut track_style = resolved_track_style.as_slice().to_vec();
217
    let mut thumb_style = resolved_thumb_style.as_slice().to_vec();
    // Flat specific:
217
    track_style.push(CssPropertyWithConditions::dark_theme(
217
        CssProperty::BackgroundContent(
217
            StyleBackgroundContentVec::from_vec(vec![StyleBackgroundContent::Color(DARK_BG)])
217
                .into(),
217
        ),
    ));
217
    thumb_style.push(CssPropertyWithConditions::dark_theme(
217
        CssProperty::BackgroundContent(
217
            StyleBackgroundContentVec::from_vec(vec![StyleBackgroundContent::Color(DARK_FG)])
217
                .into(),
217
        ),
    ));
217
    Dom::create_div()
217
        .with_ids_and_classes(IdOrClassVec::from_vec(vec![Class(
217
            AzString::from_const_str("__azul-native-slider"),
217
        )]))
217
        .with_css_props(CssPropertyWithConditionsVec::from_vec(track_style))
217
        .with_callbacks(callbacks.into())
217
        .with_dataset(OptionRefAny::Some(state))
217
        .with_merge_callback(azul_core::dom::DatasetMergeCallback::from_ptr(
217
            crate::widgets::slider::merge_slider_state,
        ))
217
        .with_tab_index(TabIndex::Auto)
217
        .with_accessibility_info(AccessibilityInfo {
217
            role: AccessibilityRole::Slider,
217
            accessibility_name: a11y_name,
217
            accessibility_value: Some(AzString::from(alloc::format!("{value_now}"))).into(),
217
            ..Default::default()
217
        })
217
        .with_children(
217
            vec![Dom::create_div()
217
                .with_ids_and_classes(IdOrClassVec::from_vec(vec![Class(
217
                    AzString::from_const_str("__azul-native-slider-thumb"),
217
                )]))
217
                .with_css_props(CssPropertyWithConditionsVec::from_vec(thumb_style))]
217
            .into(),
        )
217
}
#[must_use]
267
pub fn text_area(mut ta: crate::widgets::text_area::TextArea) -> Dom {
267
    let ta_name: Option<AzString> = ta.text_area_state.inner.placeholder.as_ref().cloned();
    use azul_core::dom::{
        AttributeType, DomVec, EventFilter, FocusEventFilter, IdOrClass::Class, TabIndex,
    };
267
    ta.text_area_state.inner.cursor_pos = ta.text_area_state.inner.text.len();
    // Resolved before `ta.text_area_state` is moved out below, and through the
    // widget's resolver rather than a second copy of its default.
267
    let resolved_container_style = ta.resolved_container_style();
267
    let label_text: String = ta
267
        .text_area_state
267
        .inner
267
        .text
267
        .iter()
242916
        .filter_map(|s| core::char::from_u32(*s))
267
        .collect();
267
    let placeholder = ta
267
        .text_area_state
267
        .inner
267
        .placeholder
267
        .as_ref()
267
        .map(|s| s.as_str().to_string())
267
        .unwrap_or_default();
267
    let state_ref = RefAny::new(ta.text_area_state);
267
    let mut container_style: Vec<CssPropertyWithConditions> =
267
        resolved_container_style.as_slice().to_vec();
267
    container_style.push(CssPropertyWithConditions::dark_theme(
267
        CssProperty::BackgroundContent(
267
            StyleBackgroundContentVec::from_vec(vec![StyleBackgroundContent::Color(DARK_BG)])
267
                .into(),
267
        ),
    ));
267
    container_style.push(CssPropertyWithConditions::dark_theme(
267
        CssProperty::TextColor(StyleTextColor { inner: DARK_FG }.into()),
    ));
267
    let mut label_style: Vec<CssPropertyWithConditions> = match &ta.label_style {
        azul_css::dynamic_selector::OptionCssPropertyWithConditionsVec::Some(s) => {
            s.as_slice().to_vec()
        }
        azul_css::dynamic_selector::OptionCssPropertyWithConditionsVec::None => {
267
            crate::widgets::text_area::TEXT_AREA_LABEL_PROPS.to_vec()
        }
    };
267
    label_style.push(CssPropertyWithConditions::dark_theme(
267
        CssProperty::TextColor(StyleTextColor { inner: DARK_FG }.into()),
    ));
    // The interactive states go LAST. Inline declarations resolve last-match
    // wins and a `dark_theme(..)` rule matches in every pseudo-state, so any
    // dark resting colour pushed after a `dark_on_hover` / `dark_on_focus` twin
    // would shadow it — no ring, no hover face, in dark mode.
267
    container_style.extend_from_slice(&FIELD_BORDER_STATES);
267
    Dom::create_div()
267
        .with_ids_and_classes(vec![Class("__azul-native-text-area-container".into())].into())
267
        .with_css_props(CssPropertyWithConditionsVec::from_vec(container_style))
267
        .with_tab_index(TabIndex::Auto)
267
        .with_accessibility_info(AccessibilityInfo {
267
            role: AccessibilityRole::Text,
267
            accessibility_name: ta_name.into(),
267
            ..Default::default()
267
        })
267
        .with_contenteditable(true)
267
        .with_dataset(Some(state_ref.clone()).into())
267
        .with_callbacks(
267
            vec![
267
                CoreCallbackData {
267
                    event: EventFilter::Focus(FocusEventFilter::FocusReceived),
267
                    refany: state_ref.clone(),
267
                    callback: azul_core::callbacks::CoreCallback {
267
                        cb: crate::widgets::text_area::default_on_focus_received as usize,
267
                        ctx: azul_core::refany::OptionRefAny::None,
267
                    },
267
                },
267
                CoreCallbackData {
267
                    event: EventFilter::Focus(FocusEventFilter::FocusLost),
267
                    refany: state_ref.clone(),
267
                    callback: azul_core::callbacks::CoreCallback {
267
                        cb: crate::widgets::text_area::default_on_focus_lost as usize,
267
                        ctx: azul_core::refany::OptionRefAny::None,
267
                    },
267
                },
267
                CoreCallbackData {
267
                    event: EventFilter::Focus(FocusEventFilter::TextInput),
267
                    refany: state_ref.clone(),
267
                    callback: azul_core::callbacks::CoreCallback {
267
                        cb: crate::widgets::text_area::default_on_text_input as usize,
267
                        ctx: azul_core::refany::OptionRefAny::None,
267
                    },
267
                },
267
                CoreCallbackData {
267
                    event: EventFilter::Focus(FocusEventFilter::VirtualKeyDown),
267
                    refany: state_ref,
267
                    callback: azul_core::callbacks::CoreCallback {
267
                        cb: crate::widgets::text_area::default_on_virtual_key_down as usize,
267
                        ctx: azul_core::refany::OptionRefAny::None,
267
                    },
267
                },
            ]
267
            .into(),
        )
267
        .with_children(
267
            vec![crate::widgets::widget_p()
267
                .with_ids_and_classes(vec![Class("__azul-native-text-area-label".into())].into())
267
                .with_css_props(CssPropertyWithConditionsVec::from_vec(label_style))
267
                .with_attribute(AttributeType::Placeholder(placeholder.into()))
267
                .with_children(DomVec::from_vec(vec![
267
                    Dom::create_text_do_not_use_without_block_level_wrapper(label_text),
                ]))]
267
            .into(),
        )
267
}
const SYSTEM_UI_STR: AzString = AzString::from_const_str("system:ui");
const SYSTEM_UI_FAMILIES: &[StyleFontFamily] = &[StyleFontFamily::System(SYSTEM_UI_STR)];
const SYSTEM_UI_FAMILY: StyleFontFamilyVec =
    StyleFontFamilyVec::from_const_slice(SYSTEM_UI_FAMILIES);
/// The dropdown's border, as a palette token rather than a private literal.
///
/// It had no dark counterpart, so a dropdown kept a light-grey outline on a
/// dark surface; the rules that use it now pair it with `DARK_BD`.
const FLAT_BORDER_NORMAL: ColorU = LIGHT_BD;
const FLAT_DROPDOWN_WRAPPER_STYLE: &[CssPropertyWithConditions] = &[
    CssPropertyWithConditions::simple(CssProperty::const_display(LayoutDisplay::InlineFlex)),
    CssPropertyWithConditions::simple(CssProperty::const_flex_direction(LayoutFlexDirection::Row)),
    CssPropertyWithConditions::simple(CssProperty::const_flex_grow(LayoutFlexGrow::const_new(0))),
    CssPropertyWithConditions::simple(CssProperty::const_align_items(LayoutAlignItems::Center)),
    CssPropertyWithConditions::simple(CssProperty::const_cursor(StyleCursor::Pointer)),
    CssPropertyWithConditions::simple(CssProperty::const_font_size(StyleFontSize::const_px(13))),
    CssPropertyWithConditions::simple(CssProperty::const_font_family(SYSTEM_UI_FAMILY)),
    CssPropertyWithConditions::simple(CssProperty::const_padding_left(
        LayoutPaddingLeft::const_px(4),
    )),
    CssPropertyWithConditions::simple(CssProperty::const_padding_right(
        LayoutPaddingRight::const_px(4),
    )),
    CssPropertyWithConditions::simple(CssProperty::const_padding_top(LayoutPaddingTop::const_px(
        2,
    ))),
    CssPropertyWithConditions::simple(CssProperty::const_padding_bottom(
        LayoutPaddingBottom::const_px(2),
    )),
    CssPropertyWithConditions::simple(CssProperty::const_border_top_width(
        LayoutBorderTopWidth::const_px(1),
    )),
    CssPropertyWithConditions::simple(CssProperty::const_border_bottom_width(
        LayoutBorderBottomWidth::const_px(1),
    )),
    CssPropertyWithConditions::simple(CssProperty::const_border_left_width(
        LayoutBorderLeftWidth::const_px(1),
    )),
    CssPropertyWithConditions::simple(CssProperty::const_border_right_width(
        LayoutBorderRightWidth::const_px(1),
    )),
    CssPropertyWithConditions::simple(CssProperty::const_border_top_style(StyleBorderTopStyle {
        inner: BorderStyle::Solid,
    })),
    CssPropertyWithConditions::simple(CssProperty::const_border_bottom_style(
        StyleBorderBottomStyle {
            inner: BorderStyle::Solid,
        },
    )),
    CssPropertyWithConditions::simple(CssProperty::const_border_left_style(StyleBorderLeftStyle {
        inner: BorderStyle::Solid,
    })),
    CssPropertyWithConditions::simple(CssProperty::const_border_right_style(
        StyleBorderRightStyle {
            inner: BorderStyle::Solid,
        },
    )),
    CssPropertyWithConditions::simple(CssProperty::const_background_content(
        StyleBackgroundContentVec::from_const_slice(&[StyleBackgroundContent::Color(LIGHT_BG)]),
    )),
    CssPropertyWithConditions::simple(CssProperty::const_text_color(StyleTextColor {
        inner: LIGHT_FG,
    })),
    CssPropertyWithConditions::simple(CssProperty::const_border_top_color(StyleBorderTopColor {
        inner: FLAT_BORDER_NORMAL,
    })),
    CssPropertyWithConditions::simple(CssProperty::const_border_bottom_color(
        StyleBorderBottomColor {
            inner: FLAT_BORDER_NORMAL,
        },
    )),
    CssPropertyWithConditions::simple(CssProperty::const_border_left_color(StyleBorderLeftColor {
        inner: FLAT_BORDER_NORMAL,
    })),
    CssPropertyWithConditions::simple(CssProperty::const_border_right_color(
        StyleBorderRightColor {
            inner: FLAT_BORDER_NORMAL,
        },
    )),
    CssPropertyWithConditions::dark_theme(CssProperty::const_background_content(
        StyleBackgroundContentVec::from_const_slice(&[StyleBackgroundContent::Color(DARK_BG)]),
    )),
    // The four border colours above are light-mode values; without these the
    // dropdown kept a light-grey outline on a dark surface.
    CssPropertyWithConditions::dark_theme(CssProperty::const_border_top_color(
        StyleBorderTopColor { inner: DARK_BD },
    )),
    CssPropertyWithConditions::dark_theme(CssProperty::const_border_bottom_color(
        StyleBorderBottomColor { inner: DARK_BD },
    )),
    CssPropertyWithConditions::dark_theme(CssProperty::const_border_left_color(
        StyleBorderLeftColor { inner: DARK_BD },
    )),
    CssPropertyWithConditions::dark_theme(CssProperty::const_border_right_color(
        StyleBorderRightColor { inner: DARK_BD },
    )),
    CssPropertyWithConditions::dark_theme(CssProperty::const_text_color(StyleTextColor {
        inner: DARK_FG,
    })),
];
const FLAT_DROPDOWN_LABEL_STYLE: &[CssPropertyWithConditions] = &[
    CssPropertyWithConditions::simple(CssProperty::const_flex_grow(LayoutFlexGrow::const_new(1))),
    CssPropertyWithConditions::simple(CssProperty::const_padding_right(
        LayoutPaddingRight::const_px(8),
    )),
    CssPropertyWithConditions::simple(CssProperty::const_text_color(StyleTextColor {
        inner: LIGHT_FG,
    })),
    CssPropertyWithConditions::dark_theme(CssProperty::const_text_color(StyleTextColor {
        inner: DARK_FG,
    })),
];
const FLAT_DROPDOWN_ARROW_STYLE: &[CssPropertyWithConditions] = &[
    CssPropertyWithConditions::simple(CssProperty::const_font_size(StyleFontSize::const_px(18))),
    CssPropertyWithConditions::simple(CssProperty::const_flex_grow(LayoutFlexGrow::const_new(0))),
    CssPropertyWithConditions::simple(CssProperty::const_text_color(StyleTextColor {
        inner: LIGHT_FG,
    })),
    CssPropertyWithConditions::dark_theme(CssProperty::const_text_color(StyleTextColor {
        inner: DARK_FG,
    })),
];
#[must_use]
53
pub fn drop_down(dd: crate::widgets::drop_down::DropDown) -> Dom {
    use azul_core::{
        callbacks::{CoreCallback, CoreCallbackData},
        dom::{
            Dom, DomVec, EventFilter, FocusEventFilter, IdOrClass::Class, IdOrClassVec, TabIndex,
        },
        refany::RefAny,
    };
    use azul_css::AzString;
53
    let selected_label: Option<AzString> = dd
53
        .choices
53
        .as_ref()
53
        .get(dd.selected)
53
        .map(|o| AzString::from(o.as_str().to_string()));
    const DROPDOWN_CLASS: &[IdOrClass] =
        &[Class(AzString::from_const_str("__azul-native-dropdown"))];
53
    let selected_text = dd
53
        .choices
53
        .as_slice()
53
        .get(dd.selected)
53
        .cloned()
53
        .unwrap_or_else(|| AzString::from_const_str(""));
53
    let refany = RefAny::new(dd);
53
    Dom::create_div()
53
        .with_css_props(CssPropertyWithConditionsVec::from_const_slice(
            FLAT_DROPDOWN_WRAPPER_STYLE,
        ))
53
        .with_ids_and_classes(IdOrClassVec::from_const_slice(DROPDOWN_CLASS))
53
        .with_tab_index(TabIndex::Auto)
53
        .with_accessibility_info(AccessibilityInfo {
53
            role: AccessibilityRole::ComboBox,
53
            accessibility_value: selected_label.into(),
53
            ..Default::default()
53
        })
53
        .with_callbacks(
53
            vec![CoreCallbackData {
53
                event: EventFilter::Focus(FocusEventFilter::FocusReceived),
53
                refany,
53
                callback: CoreCallback {
53
                    cb: crate::widgets::drop_down::on_dropdown_click as usize,
53
                    ctx: azul_core::refany::OptionRefAny::None,
53
                },
53
            }]
53
            .into(),
        )
53
        .with_children(DomVec::from_vec(vec![
53
            crate::widgets::widget_p()
53
                .with_css_props(CssPropertyWithConditionsVec::from_const_slice(
                    FLAT_DROPDOWN_LABEL_STYLE,
                ))
53
                .with_children(DomVec::from_vec(vec![
53
                    Dom::create_text_do_not_use_without_block_level_wrapper(selected_text),
                ])),
53
            Dom::create_icon(AzString::from_const_str("arrow_drop_down")).with_css_props(
53
                CssPropertyWithConditionsVec::from_const_slice(FLAT_DROPDOWN_ARROW_STYLE),
            ),
        ]))
53
}
#[must_use]
23
pub fn avatar(a: crate::widgets::avatar::Avatar) -> Dom {
    use azul_core::dom::{Dom, IdOrClassVec};
23
    let size = a.size;
23
    let child = match a.image.into_option() {
8
        Some(image) => Dom::create_image(image)
8
            .with_ids_and_classes(IdOrClassVec::from_const_slice(
8
                crate::widgets::avatar::AVATAR_IMAGE_CLASS,
            ))
8
            .with_css_props(crate::widgets::avatar::build_image_style(size)),
15
        None => crate::widgets::widget_p_with_text(a.initials).with_ids_and_classes(
15
            IdOrClassVec::from_const_slice(crate::widgets::avatar::AVATAR_INITIALS_CLASS),
        ),
    };
23
    Dom::create_div()
23
        .with_ids_and_classes(IdOrClassVec::from_const_slice(
23
            crate::widgets::avatar::AVATAR_CLASS,
        ))
23
        .with_css_props(
23
            match a.avatar_style {
                azul_css::dynamic_selector::OptionCssPropertyWithConditionsVec::Some(style) => {
                    style.as_slice().to_vec()
                }
                azul_css::dynamic_selector::OptionCssPropertyWithConditionsVec::None => {
23
                    crate::widgets::avatar::build_avatar_style(size)
23
                        .as_slice()
23
                        .to_vec()
                }
            }
23
            .into(),
        )
23
        .with_children(alloc::vec![child].into())
23
}
// ---------------------------------------------------------------------------
// INTERACTIVE STATES
// ---------------------------------------------------------------------------
//
// Every hover / pressed / focus rule the flat theme paints lives here, in
// LIGHT+DARK pairs, and the widget files reference these consts instead of
// declaring their own.
//
// The reason is the dark half. A widget file cannot see `DARK_HT` — the palette
// is in this module — so a rule written there could only ever name a light
// colour, and that is exactly how every interactive state in this toolkit came
// to paint its light-mode fill onto a dark surface. Declaring the pair together,
// where both halves are in scope, makes the light-only version unwriteable.
//
// These are `const` rather than builder functions because the widgets that use
// them hold their styles in `static [CssPropertyWithConditions]` slices, and a
// const slice cannot splice a function's return value.
/// Row hover, light mode: the Explorer selection tint (#E5F3FF).
///
/// Deliberately NOT [`LIGHT_HT`]. A list or tree ROW hovers to the selection
/// blue; a control FACE hovers to the neutral grey. They are different surfaces
/// and the two tokens are not interchangeable.
pub const LIGHT_ROW_HOVER: ColorU = ColorU {
    r: 229,
    g: 243,
    b: 255,
    a: 255,
};
/// Row hover, dark mode: the twin of [`LIGHT_ROW_HOVER`].
pub const DARK_ROW_HOVER: ColorU = ColorU {
    r: 42,
    g: 45,
    b: 46,
    a: 255,
};
/// The hover fill for one row of a list, tree or menu — light mode.
///
/// Always use it with [`ROW_HOVER_DARK`]; either alone is the bug this section
/// exists to prevent.
pub const ROW_HOVER: CssPropertyWithConditions = CssPropertyWithConditions::on_hover(
    CssProperty::const_background_content(StyleBackgroundContentVec::from_const_slice(&[
        StyleBackgroundContent::Color(LIGHT_ROW_HOVER),
    ])),
);
/// The dark twin of [`ROW_HOVER`].
pub const ROW_HOVER_DARK: CssPropertyWithConditions = CssPropertyWithConditions::dark_on_hover(
    CssProperty::const_background_content(StyleBackgroundContentVec::from_const_slice(&[
        StyleBackgroundContent::Color(DARK_ROW_HOVER),
    ])),
);
/// The text fields' ring — the border every text field takes on hover and on
/// focus — is `#4286f4`, the value `TextInput`, `TextArea` and `NumberInput`
/// carried before the theme owned their states (the widgets' own
/// `COLOR_4286F4`). It is deliberately NOT [`LIGHT_ACC`]: the accent is the
/// theme's newer blue, and a migration that swaps one light value for another
/// changes what every existing app looks like. The dark twin is [`DARK_ACC`],
/// where the fields had no dark ring at all before.
pub const FIELD_RING: ColorU = ColorU {
    r: 66,
    g: 134,
    b: 244,
    a: 255,
};
/// The focus ring: the focused control's border takes the accent colour.
///
/// One const per edge, because a border colour is four properties and a focus
/// ring that sets only some of them leaves the rest at their resting colour.
/// Each has a dark twin using [`DARK_ACC`] — the accent is the one state colour
/// that genuinely has a per-mode value in both palettes, which is why the plan
/// names it.
pub const FOCUS_BORDER_TOP: CssPropertyWithConditions =
    CssPropertyWithConditions::on_focus(CssProperty::const_border_top_color(StyleBorderTopColor {
        inner: FIELD_RING,
    }));
/// See [`FOCUS_BORDER_TOP`].
pub const FOCUS_BORDER_BOTTOM: CssPropertyWithConditions = CssPropertyWithConditions::on_focus(
    CssProperty::const_border_bottom_color(StyleBorderBottomColor { inner: FIELD_RING }),
);
/// See [`FOCUS_BORDER_TOP`].
pub const FOCUS_BORDER_LEFT: CssPropertyWithConditions = CssPropertyWithConditions::on_focus(
    CssProperty::const_border_left_color(StyleBorderLeftColor { inner: FIELD_RING }),
);
/// See [`FOCUS_BORDER_TOP`].
pub const FOCUS_BORDER_RIGHT: CssPropertyWithConditions = CssPropertyWithConditions::on_focus(
    CssProperty::const_border_right_color(StyleBorderRightColor { inner: FIELD_RING }),
);
/// The dark twin of [`FOCUS_BORDER_TOP`].
pub const FOCUS_BORDER_TOP_DARK: CssPropertyWithConditions =
    CssPropertyWithConditions::dark_on_focus(CssProperty::const_border_top_color(
        StyleBorderTopColor { inner: DARK_ACC },
    ));
/// The dark twin of [`FOCUS_BORDER_BOTTOM`].
pub const FOCUS_BORDER_BOTTOM_DARK: CssPropertyWithConditions =
    CssPropertyWithConditions::dark_on_focus(CssProperty::const_border_bottom_color(
        StyleBorderBottomColor { inner: DARK_ACC },
    ));
/// The dark twin of [`FOCUS_BORDER_LEFT`].
pub const FOCUS_BORDER_LEFT_DARK: CssPropertyWithConditions =
    CssPropertyWithConditions::dark_on_focus(CssProperty::const_border_left_color(
        StyleBorderLeftColor { inner: DARK_ACC },
    ));
/// The dark twin of [`FOCUS_BORDER_RIGHT`].
pub const FOCUS_BORDER_RIGHT_DARK: CssPropertyWithConditions =
    CssPropertyWithConditions::dark_on_focus(CssProperty::const_border_right_color(
        StyleBorderRightColor { inner: DARK_ACC },
    ));
/// Option-row hover for a drop-down list, light mode (#EAF4FC).
///
/// A shade lighter than [`LIGHT_ROW_HOVER`]: a menu that is already floating
/// over the page needs less contrast than a row inside a field.
pub const LIGHT_OPTION_HOVER: ColorU = ColorU {
    r: 234,
    g: 244,
    b: 252,
    a: 255,
};
/// The hover fill for one row of a drop-down list — light mode. Pair with
/// [`OPTION_HOVER_DARK`].
pub const OPTION_HOVER: CssPropertyWithConditions = CssPropertyWithConditions::on_hover(
    CssProperty::const_background_content(StyleBackgroundContentVec::from_const_slice(&[
        StyleBackgroundContent::Color(LIGHT_OPTION_HOVER),
    ])),
);
/// The dark twin of [`OPTION_HOVER`], reusing the row-hover dark fill: a menu
/// and a list row want the same treatment once the surface is dark.
pub const OPTION_HOVER_DARK: CssPropertyWithConditions = CssPropertyWithConditions::dark_on_hover(
    CssProperty::const_background_content(StyleBackgroundContentVec::from_const_slice(&[
        StyleBackgroundContent::Color(DARK_ROW_HOVER),
    ])),
);
/// A hover fill at a caller-supplied colour, paired with this theme's own dark
/// value.
///
/// For chrome whose LIGHT hover colour arrives from outside this palette — a
/// window-decoration colour the compositor reported, say. It still gets a twin:
/// azul's dark mode is its own CSS condition, not a reflection of the OS theme
/// (an app can force dark while the desktop is light), so a system colour
/// chosen for a light desktop is not automatically right here. The caller's
/// colour is used in light mode and [`DARK_HT`] in dark mode.
///
/// Returns both halves so a caller cannot take one without the other.
#[must_use]
3
pub fn hover_bg_pair(light: ColorU) -> [CssPropertyWithConditions; 2] {
3
    [
3
        CssPropertyWithConditions::on_hover(CssProperty::const_background_content(
3
            StyleBackgroundContentVec::from_vec(alloc::vec![StyleBackgroundContent::Color(light)]),
3
        )),
3
        CssPropertyWithConditions::dark_on_hover(CssProperty::const_background_content(
3
            StyleBackgroundContentVec::from_vec(alloc::vec![StyleBackgroundContent::Color(
3
                DARK_HT
3
            )]),
3
        )),
3
    ]
3
}
// ---------------------------------------------------------------------------
// INTERACTIVE STATES — text fields
// ---------------------------------------------------------------------------
/// Border colour on hover, one const per edge, light mode.
///
/// A border colour is four properties, so a state that sets only some edges
/// leaves the rest at their resting colour — which is why these travel as a set
/// rather than individually.
pub const HOVER_BORDER_TOP: CssPropertyWithConditions =
    CssPropertyWithConditions::on_hover(CssProperty::const_border_top_color(StyleBorderTopColor {
        inner: FIELD_RING,
    }));
/// See [`HOVER_BORDER_TOP`].
pub const HOVER_BORDER_BOTTOM: CssPropertyWithConditions = CssPropertyWithConditions::on_hover(
    CssProperty::const_border_bottom_color(StyleBorderBottomColor { inner: FIELD_RING }),
);
/// See [`HOVER_BORDER_TOP`].
pub const HOVER_BORDER_LEFT: CssPropertyWithConditions = CssPropertyWithConditions::on_hover(
    CssProperty::const_border_left_color(StyleBorderLeftColor { inner: FIELD_RING }),
);
/// See [`HOVER_BORDER_TOP`].
pub const HOVER_BORDER_RIGHT: CssPropertyWithConditions = CssPropertyWithConditions::on_hover(
    CssProperty::const_border_right_color(StyleBorderRightColor { inner: FIELD_RING }),
);
/// The dark twin of [`HOVER_BORDER_TOP`].
pub const HOVER_BORDER_TOP_DARK: CssPropertyWithConditions =
    CssPropertyWithConditions::dark_on_hover(CssProperty::const_border_top_color(
        StyleBorderTopColor { inner: DARK_ACC },
    ));
/// The dark twin of [`HOVER_BORDER_BOTTOM`].
pub const HOVER_BORDER_BOTTOM_DARK: CssPropertyWithConditions =
    CssPropertyWithConditions::dark_on_hover(CssProperty::const_border_bottom_color(
        StyleBorderBottomColor { inner: DARK_ACC },
    ));
/// The dark twin of [`HOVER_BORDER_LEFT`].
pub const HOVER_BORDER_LEFT_DARK: CssPropertyWithConditions =
    CssPropertyWithConditions::dark_on_hover(CssProperty::const_border_left_color(
        StyleBorderLeftColor { inner: DARK_ACC },
    ));
/// The dark twin of [`HOVER_BORDER_RIGHT`].
pub const HOVER_BORDER_RIGHT_DARK: CssPropertyWithConditions =
    CssPropertyWithConditions::dark_on_hover(CssProperty::const_border_right_color(
        StyleBorderRightColor { inner: DARK_ACC },
    ));
/// Every border state a text field takes: the accent on hover and on focus, each
/// edge, each with its dark twin.
///
/// One array so a theme function appends the whole set in a line and cannot ship
/// half of it. This is what a widget file used to declare itself, in light mode
/// only — the reason a hovered field kept its light-blue ring on a dark surface.
pub const FIELD_BORDER_STATES: [CssPropertyWithConditions; 16] = [
    HOVER_BORDER_TOP,
    HOVER_BORDER_BOTTOM,
    HOVER_BORDER_LEFT,
    HOVER_BORDER_RIGHT,
    HOVER_BORDER_TOP_DARK,
    HOVER_BORDER_BOTTOM_DARK,
    HOVER_BORDER_LEFT_DARK,
    HOVER_BORDER_RIGHT_DARK,
    FOCUS_BORDER_TOP,
    FOCUS_BORDER_BOTTOM,
    FOCUS_BORDER_LEFT,
    FOCUS_BORDER_RIGHT,
    FOCUS_BORDER_TOP_DARK,
    FOCUS_BORDER_BOTTOM_DARK,
    FOCUS_BORDER_LEFT_DARK,
    FOCUS_BORDER_RIGHT_DARK,
];
/// Every state a button of one semantic type takes: hover fill, pressed fill and
/// focus ring, each with its dark twin.
///
/// The light values come from [`crate::widgets::button::get_button_colors`], so
/// there is still one source of truth for them; the DARK halves are chosen here,
/// because this is the only place the palette is in scope.
///
/// The dark rule differs by type on purpose:
///
/// * `Default` is the neutral grey button, so its surface belongs to the PAGE and its dark states
///   come from the theme ([`DARK_HT`] / [`DARK_PT`]).
/// * Every other type carries its own semantic colour — a Primary button is blue whichever mode the
///   app is in — so the same hover and pressed colours apply in dark mode. A neutral grey hover on
///   a blue button would be wrong, and inventing a second blue would be a design decision this
///   refactor has no business making.
/// * `Link` has no surface at all: it underlines instead, in both modes.
#[must_use]
205
pub fn button_states(
205
    button_type: crate::widgets::button::ButtonType,
205
) -> Vec<CssPropertyWithConditions> {
    use crate::widgets::button::ButtonType;
788
    let bg = |c: ColorU| {
788
        CssProperty::BackgroundContent(
788
            StyleBackgroundContentVec::from_vec(alloc::vec![StyleBackgroundContent::Color(c)])
788
                .into(),
788
        )
788
    };
205
    if button_type == ButtonType::Link {
8
        return alloc::vec![
8
            CssPropertyWithConditions::on_hover(CssProperty::TextDecoration(
8
                StyleTextDecoration::Underline.into(),
8
            )),
8
            CssPropertyWithConditions::dark_on_hover(CssProperty::TextDecoration(
8
                StyleTextDecoration::Underline.into(),
8
            )),
        ];
197
    }
197
    let (_, bg_hover, bg_active) = crate::widgets::button::get_button_colors(button_type);
197
    let neutral = button_type.surface() == crate::widgets::button::ButtonSurface::Neutral;
197
    let (dark_hover, dark_active) = if neutral {
134
        (DARK_HT, DARK_PT)
    } else {
63
        (bg_hover, bg_active)
    };
197
    let mut out = alloc::vec![
197
        CssPropertyWithConditions::on_hover(bg(bg_hover)),
197
        CssPropertyWithConditions::dark_on_hover(bg(dark_hover)),
197
        CssPropertyWithConditions::on_active(bg(bg_active)),
197
        CssPropertyWithConditions::dark_on_active(bg(dark_active)),
    ];
    // The neutral button is the only one with a visible resting border, so it is
    // the only one whose border reacts to hover.
197
    if neutral {
134
        let light = ColorU::rgb(173, 181, 189);
536
        for (l, d) in [
134
            (
134
                CssProperty::const_border_top_color(StyleBorderTopColor { inner: light }),
134
                CssProperty::const_border_top_color(StyleBorderTopColor { inner: DARK_BD }),
134
            ),
134
            (
134
                CssProperty::const_border_bottom_color(StyleBorderBottomColor { inner: light }),
134
                CssProperty::const_border_bottom_color(StyleBorderBottomColor { inner: DARK_BD }),
134
            ),
134
            (
134
                CssProperty::const_border_left_color(StyleBorderLeftColor { inner: light }),
134
                CssProperty::const_border_left_color(StyleBorderLeftColor { inner: DARK_BD }),
134
            ),
134
            (
134
                CssProperty::const_border_right_color(StyleBorderRightColor { inner: light }),
134
                CssProperty::const_border_right_color(StyleBorderRightColor { inner: DARK_BD }),
134
            ),
536
        ] {
536
            out.push(CssPropertyWithConditions::on_hover(l));
536
            out.push(CssPropertyWithConditions::dark_on_hover(d));
536
        }
63
    }
    // The focus ring is the accent in both modes, and the consts already pair it.
197
    out.push(FOCUS_BORDER_TOP);
197
    out.push(FOCUS_BORDER_BOTTOM);
197
    out.push(FOCUS_BORDER_LEFT);
197
    out.push(FOCUS_BORDER_RIGHT);
197
    out.push(FOCUS_BORDER_TOP_DARK);
197
    out.push(FOCUS_BORDER_BOTTOM_DARK);
197
    out.push(FOCUS_BORDER_LEFT_DARK);
197
    out.push(FOCUS_BORDER_RIGHT_DARK);
197
    out
205
}
/// A hover fill with BOTH halves chosen by the caller.
///
/// For widgets that carry their own palette (`RibbonTheme`, `StatusBarTheme`,
/// ...). The rule for picking `dark`: a surface that is its own colour — a blue
/// nav, a coloured button — keeps its light hover colour, because the surface
/// does not change in dark mode either; a page-neutral surface takes this
/// theme's [`DARK_HT`]. See `button_states` for the same rule applied.
#[must_use]
54261
pub fn hover_bg_both(light: ColorU, dark: ColorU) -> [CssPropertyWithConditions; 2] {
108522
    let bg = |c: ColorU| {
108522
        CssProperty::const_background_content(StyleBackgroundContentVec::from_vec(alloc::vec![
108522
            StyleBackgroundContent::Color(c),
        ]))
108522
    };
54261
    [
54261
        CssPropertyWithConditions::on_hover(bg(light)),
54261
        CssPropertyWithConditions::dark_on_hover(bg(dark)),
54261
    ]
54261
}
/// A pressed fill with BOTH halves chosen by the caller — see [`hover_bg_both`].
#[must_use]
12116
pub fn active_bg_both(light: ColorU, dark: ColorU) -> [CssPropertyWithConditions; 2] {
24232
    let bg = |c: ColorU| {
24232
        CssProperty::const_background_content(StyleBackgroundContentVec::from_vec(alloc::vec![
24232
            StyleBackgroundContent::Color(c),
        ]))
24232
    };
12116
    [
12116
        CssPropertyWithConditions::on_active(bg(light)),
12116
        CssPropertyWithConditions::dark_on_active(bg(dark)),
12116
    ]
12116
}
// ===========================================================================
// PHASE 2 — per-widget state sections. Each widget's interactive-state consts
// live between its own pair of markers and nowhere else, so that the
// migrations can proceed in parallel without touching one another's lines.
// ===========================================================================
// == STATES: list_view ==
// ---------------------------------------------------------------------------
// INTERACTIVE STATES — list view
// ---------------------------------------------------------------------------
//
// The sixty hover / pressed / focus rules `list_view.rs` used to declare, as
// forty-five LIGHT+DARK pairs (the row's thirteen hover rules were spelled out
// twice over there). A column header hovers to a bluish-white face with a blue
// underline and presses to a bordered, inset-shadowed face; a row hovers to a
// blue-ringed tint and focuses to a stronger one. The light values are the
// widget's own, unchanged — this is a move, not a restyle. The dark halves are
// chosen here, because this is the only place the palette is in scope.
//
// Every surface the list styles is page-neutral — a white field, a
// white-to-grey header band — so each dark twin comes from the theme rather
// than from a colour invented for the occasion:
//
//   * fills: `DARK_HT` for the hovered header face and `DARK_PT` for the pressed one (a header is a
//     control face); `DARK_ROW_HOVER` for a hovered row, the fill a tree row already takes; and
//     `DARK_GLOW`, the translucent accent, for the focused row, so it stays distinguishable from a
//     merely hovered one the way #B8E0F3 is from #E5F3FB in light mode;
//   * borders: the light blues map onto the accent ramp — `DARK_SOFT`, the muted step, for the
//     hover underline and the hover ring, `DARK_ACC` for the focus ring — and the pressed header's
//     neutral grey-blue border takes `DARK_BD`;
//   * the pressed header's inset shadow shades its face toward the bottom stop, which is what
//     `DARK_PB` is.
//
// Border widths (1px) and styles (solid) do not change with the theme. Their
// dark twins restate the same value: the pairing is the invariant this
// section exists for, and keeping it uniform beats remembering which rules are
// exempt. They are shared between the header and the row, since a 1px solid
// edge is the same rule wherever it lands.
/// Column-header hover underline, light mode (#9ADFFE).
pub const LIGHT_LIST_HEADER_HOVER_LINE: ColorU = ColorU {
    r: 154,
    g: 223,
    b: 254,
    a: 255,
};
/// Column-header hover face, top stop, light mode (#F7FCFE).
///
/// The face is a vertical gradient with a hard step at the midpoint: this
/// colour held to 50%, then [`LIGHT_LIST_HEADER_HOVER_MID`] fading into
/// [`LIGHT_LIST_HEADER_HOVER_BOTTOM`].
pub const LIGHT_LIST_HEADER_HOVER_TOP: ColorU = ColorU {
    r: 247,
    g: 252,
    b: 254,
    a: 255,
};
/// Column-header hover face, just below the midpoint step (#E8F6FE).
pub const LIGHT_LIST_HEADER_HOVER_MID: ColorU = ColorU {
    r: 232,
    g: 246,
    b: 254,
    a: 255,
};
/// Column-header hover face, bottom stop (#CEE7F4).
pub const LIGHT_LIST_HEADER_HOVER_BOTTOM: ColorU = ColorU {
    r: 206,
    g: 231,
    b: 244,
    a: 255,
};
/// Column-header pressed face, light mode (#F9FAFB).
pub const LIGHT_LIST_HEADER_PRESSED: ColorU = ColorU {
    r: 249,
    g: 250,
    b: 251,
    a: 255,
};
/// Column-header pressed border, light mode (#C2CDDB): a neutral grey-blue,
/// not an accent.
pub const LIGHT_LIST_HEADER_PRESSED_BORDER: ColorU = ColorU {
    r: 194,
    g: 205,
    b: 219,
    a: 255,
};
/// Column-header pressed inset shadow, light mode (#CEE7F4) — the hover face's
/// bottom stop, so a pressed header reads as sunk into the hovered one.
pub const LIGHT_LIST_HEADER_PRESSED_SHADOW: ColorU = ColorU {
    r: 206,
    g: 231,
    b: 244,
    a: 255,
};
/// Row hover fill, light mode (#E5F3FB).
///
/// Not [`LIGHT_ROW_HOVER`] (#E5F3FF): the list's tint is four units short in
/// the blue channel. Kept as the widget had it — this is a move, not a restyle
/// — though nothing but the value separates the two, and a later pass may well
/// decide a list row and a tree row should hover alike.
pub const LIGHT_LIST_ROW_HOVER: ColorU = ColorU {
    r: 229,
    g: 243,
    b: 251,
    a: 255,
};
/// Row hover ring, light mode (#65B5DC).
pub const LIGHT_LIST_ROW_HOVER_BORDER: ColorU = ColorU {
    r: 101,
    g: 181,
    b: 220,
    a: 255,
};
/// Focused-row fill, light mode (#B8E0F3): a stronger tint than
/// [`LIGHT_LIST_ROW_HOVER`], so the keyboard cursor row stays distinct from a
/// row the pointer merely passes over.
pub const LIGHT_LIST_ROW_FOCUS: ColorU = ColorU {
    r: 184,
    g: 224,
    b: 243,
    a: 255,
};
/// Focused-row ring, light mode (#26A0DA).
pub const LIGHT_LIST_ROW_FOCUS_BORDER: ColorU = ColorU {
    r: 38,
    g: 160,
    b: 218,
    a: 255,
};
// The edge VALUES every list state shares — a 1px solid border, one const per
// edge and per property — so that each state rule below is a single line.
const LIST_EDGE_TOP_1PX: CssProperty = CssProperty::const_border_top_width(LayoutBorderTopWidth {
    inner: PixelValue::const_px(1),
});
const LIST_EDGE_BOTTOM_1PX: CssProperty =
    CssProperty::const_border_bottom_width(LayoutBorderBottomWidth {
        inner: PixelValue::const_px(1),
    });
const LIST_EDGE_LEFT_1PX: CssProperty =
    CssProperty::const_border_left_width(LayoutBorderLeftWidth {
        inner: PixelValue::const_px(1),
    });
const LIST_EDGE_RIGHT_1PX: CssProperty =
    CssProperty::const_border_right_width(LayoutBorderRightWidth {
        inner: PixelValue::const_px(1),
    });
const LIST_EDGE_TOP_SOLID: CssProperty = CssProperty::const_border_top_style(StyleBorderTopStyle {
    inner: BorderStyle::Solid,
});
const LIST_EDGE_BOTTOM_SOLID: CssProperty =
    CssProperty::const_border_bottom_style(StyleBorderBottomStyle {
        inner: BorderStyle::Solid,
    });
const LIST_EDGE_LEFT_SOLID: CssProperty =
    CssProperty::const_border_left_style(StyleBorderLeftStyle {
        inner: BorderStyle::Solid,
    });
const LIST_EDGE_RIGHT_SOLID: CssProperty =
    CssProperty::const_border_right_style(StyleBorderRightStyle {
        inner: BorderStyle::Solid,
    });
// -- hover: the 1px solid edge ---------------------------------------------
/// A 1px edge on hover, one const per edge and per property: the ring a
/// hovered row draws, and the underline a hovered column header draws (bottom
/// edge only). Each pairs with its `_DARK` twin.
pub const LIST_HOVER_BORDER_TOP_WIDTH: CssPropertyWithConditions =
    CssPropertyWithConditions::on_hover(LIST_EDGE_TOP_1PX);
/// See [`LIST_HOVER_BORDER_TOP_WIDTH`].
pub const LIST_HOVER_BORDER_BOTTOM_WIDTH: CssPropertyWithConditions =
    CssPropertyWithConditions::on_hover(LIST_EDGE_BOTTOM_1PX);
/// See [`LIST_HOVER_BORDER_TOP_WIDTH`].
pub const LIST_HOVER_BORDER_LEFT_WIDTH: CssPropertyWithConditions =
    CssPropertyWithConditions::on_hover(LIST_EDGE_LEFT_1PX);
/// See [`LIST_HOVER_BORDER_TOP_WIDTH`].
pub const LIST_HOVER_BORDER_RIGHT_WIDTH: CssPropertyWithConditions =
    CssPropertyWithConditions::on_hover(LIST_EDGE_RIGHT_1PX);
/// See [`LIST_HOVER_BORDER_TOP_WIDTH`].
pub const LIST_HOVER_BORDER_TOP_STYLE: CssPropertyWithConditions =
    CssPropertyWithConditions::on_hover(LIST_EDGE_TOP_SOLID);
/// See [`LIST_HOVER_BORDER_TOP_WIDTH`].
pub const LIST_HOVER_BORDER_BOTTOM_STYLE: CssPropertyWithConditions =
    CssPropertyWithConditions::on_hover(LIST_EDGE_BOTTOM_SOLID);
/// See [`LIST_HOVER_BORDER_TOP_WIDTH`].
pub const LIST_HOVER_BORDER_LEFT_STYLE: CssPropertyWithConditions =
    CssPropertyWithConditions::on_hover(LIST_EDGE_LEFT_SOLID);
/// See [`LIST_HOVER_BORDER_TOP_WIDTH`].
pub const LIST_HOVER_BORDER_RIGHT_STYLE: CssPropertyWithConditions =
    CssPropertyWithConditions::on_hover(LIST_EDGE_RIGHT_SOLID);
/// The dark twin of [`LIST_HOVER_BORDER_TOP_WIDTH`] — the same 1px, since a
/// border's weight does not change with the theme.
pub const LIST_HOVER_BORDER_TOP_WIDTH_DARK: CssPropertyWithConditions =
    CssPropertyWithConditions::dark_on_hover(LIST_EDGE_TOP_1PX);
/// The dark twin of [`LIST_HOVER_BORDER_BOTTOM_WIDTH`].
pub const LIST_HOVER_BORDER_BOTTOM_WIDTH_DARK: CssPropertyWithConditions =
    CssPropertyWithConditions::dark_on_hover(LIST_EDGE_BOTTOM_1PX);
/// The dark twin of [`LIST_HOVER_BORDER_LEFT_WIDTH`].
pub const LIST_HOVER_BORDER_LEFT_WIDTH_DARK: CssPropertyWithConditions =
    CssPropertyWithConditions::dark_on_hover(LIST_EDGE_LEFT_1PX);
/// The dark twin of [`LIST_HOVER_BORDER_RIGHT_WIDTH`].
pub const LIST_HOVER_BORDER_RIGHT_WIDTH_DARK: CssPropertyWithConditions =
    CssPropertyWithConditions::dark_on_hover(LIST_EDGE_RIGHT_1PX);
/// The dark twin of [`LIST_HOVER_BORDER_TOP_STYLE`].
pub const LIST_HOVER_BORDER_TOP_STYLE_DARK: CssPropertyWithConditions =
    CssPropertyWithConditions::dark_on_hover(LIST_EDGE_TOP_SOLID);
/// The dark twin of [`LIST_HOVER_BORDER_BOTTOM_STYLE`].
pub const LIST_HOVER_BORDER_BOTTOM_STYLE_DARK: CssPropertyWithConditions =
    CssPropertyWithConditions::dark_on_hover(LIST_EDGE_BOTTOM_SOLID);
/// The dark twin of [`LIST_HOVER_BORDER_LEFT_STYLE`].
pub const LIST_HOVER_BORDER_LEFT_STYLE_DARK: CssPropertyWithConditions =
    CssPropertyWithConditions::dark_on_hover(LIST_EDGE_LEFT_SOLID);
/// The dark twin of [`LIST_HOVER_BORDER_RIGHT_STYLE`].
pub const LIST_HOVER_BORDER_RIGHT_STYLE_DARK: CssPropertyWithConditions =
    CssPropertyWithConditions::dark_on_hover(LIST_EDGE_RIGHT_SOLID);
// -- pressed: the 1px solid edge -------------------------------------------
/// A 1px edge while pressed, one const per edge and per property: the border a
/// pressed column header draws all the way round. Each pairs with its `_DARK`
/// twin.
pub const LIST_ACTIVE_BORDER_TOP_WIDTH: CssPropertyWithConditions =
    CssPropertyWithConditions::on_active(LIST_EDGE_TOP_1PX);
/// See [`LIST_ACTIVE_BORDER_TOP_WIDTH`].
pub const LIST_ACTIVE_BORDER_BOTTOM_WIDTH: CssPropertyWithConditions =
    CssPropertyWithConditions::on_active(LIST_EDGE_BOTTOM_1PX);
/// See [`LIST_ACTIVE_BORDER_TOP_WIDTH`].
pub const LIST_ACTIVE_BORDER_LEFT_WIDTH: CssPropertyWithConditions =
    CssPropertyWithConditions::on_active(LIST_EDGE_LEFT_1PX);
/// See [`LIST_ACTIVE_BORDER_TOP_WIDTH`].
pub const LIST_ACTIVE_BORDER_RIGHT_WIDTH: CssPropertyWithConditions =
    CssPropertyWithConditions::on_active(LIST_EDGE_RIGHT_1PX);
/// See [`LIST_ACTIVE_BORDER_TOP_WIDTH`].
pub const LIST_ACTIVE_BORDER_TOP_STYLE: CssPropertyWithConditions =
    CssPropertyWithConditions::on_active(LIST_EDGE_TOP_SOLID);
/// See [`LIST_ACTIVE_BORDER_TOP_WIDTH`].
pub const LIST_ACTIVE_BORDER_BOTTOM_STYLE: CssPropertyWithConditions =
    CssPropertyWithConditions::on_active(LIST_EDGE_BOTTOM_SOLID);
/// See [`LIST_ACTIVE_BORDER_TOP_WIDTH`].
pub const LIST_ACTIVE_BORDER_LEFT_STYLE: CssPropertyWithConditions =
    CssPropertyWithConditions::on_active(LIST_EDGE_LEFT_SOLID);
/// See [`LIST_ACTIVE_BORDER_TOP_WIDTH`].
pub const LIST_ACTIVE_BORDER_RIGHT_STYLE: CssPropertyWithConditions =
    CssPropertyWithConditions::on_active(LIST_EDGE_RIGHT_SOLID);
/// The dark twin of [`LIST_ACTIVE_BORDER_TOP_WIDTH`] — the same 1px, since a
/// border's weight does not change with the theme.
pub const LIST_ACTIVE_BORDER_TOP_WIDTH_DARK: CssPropertyWithConditions =
    CssPropertyWithConditions::dark_on_active(LIST_EDGE_TOP_1PX);
/// The dark twin of [`LIST_ACTIVE_BORDER_BOTTOM_WIDTH`].
pub const LIST_ACTIVE_BORDER_BOTTOM_WIDTH_DARK: CssPropertyWithConditions =
    CssPropertyWithConditions::dark_on_active(LIST_EDGE_BOTTOM_1PX);
/// The dark twin of [`LIST_ACTIVE_BORDER_LEFT_WIDTH`].
pub const LIST_ACTIVE_BORDER_LEFT_WIDTH_DARK: CssPropertyWithConditions =
    CssPropertyWithConditions::dark_on_active(LIST_EDGE_LEFT_1PX);
/// The dark twin of [`LIST_ACTIVE_BORDER_RIGHT_WIDTH`].
pub const LIST_ACTIVE_BORDER_RIGHT_WIDTH_DARK: CssPropertyWithConditions =
    CssPropertyWithConditions::dark_on_active(LIST_EDGE_RIGHT_1PX);
/// The dark twin of [`LIST_ACTIVE_BORDER_TOP_STYLE`].
pub const LIST_ACTIVE_BORDER_TOP_STYLE_DARK: CssPropertyWithConditions =
    CssPropertyWithConditions::dark_on_active(LIST_EDGE_TOP_SOLID);
/// The dark twin of [`LIST_ACTIVE_BORDER_BOTTOM_STYLE`].
pub const LIST_ACTIVE_BORDER_BOTTOM_STYLE_DARK: CssPropertyWithConditions =
    CssPropertyWithConditions::dark_on_active(LIST_EDGE_BOTTOM_SOLID);
/// The dark twin of [`LIST_ACTIVE_BORDER_LEFT_STYLE`].
pub const LIST_ACTIVE_BORDER_LEFT_STYLE_DARK: CssPropertyWithConditions =
    CssPropertyWithConditions::dark_on_active(LIST_EDGE_LEFT_SOLID);
/// The dark twin of [`LIST_ACTIVE_BORDER_RIGHT_STYLE`].
pub const LIST_ACTIVE_BORDER_RIGHT_STYLE_DARK: CssPropertyWithConditions =
    CssPropertyWithConditions::dark_on_active(LIST_EDGE_RIGHT_SOLID);
// -- focus: the 1px solid edge ---------------------------------------------
/// A 1px edge on focus, one const per edge and per property: the ring the
/// focused row draws. Each pairs with its `_DARK` twin.
pub const LIST_FOCUS_BORDER_TOP_WIDTH: CssPropertyWithConditions =
    CssPropertyWithConditions::on_focus(LIST_EDGE_TOP_1PX);
/// See [`LIST_FOCUS_BORDER_TOP_WIDTH`].
pub const LIST_FOCUS_BORDER_BOTTOM_WIDTH: CssPropertyWithConditions =
    CssPropertyWithConditions::on_focus(LIST_EDGE_BOTTOM_1PX);
/// See [`LIST_FOCUS_BORDER_TOP_WIDTH`].
pub const LIST_FOCUS_BORDER_LEFT_WIDTH: CssPropertyWithConditions =
    CssPropertyWithConditions::on_focus(LIST_EDGE_LEFT_1PX);
/// See [`LIST_FOCUS_BORDER_TOP_WIDTH`].
pub const LIST_FOCUS_BORDER_RIGHT_WIDTH: CssPropertyWithConditions =
    CssPropertyWithConditions::on_focus(LIST_EDGE_RIGHT_1PX);
/// See [`LIST_FOCUS_BORDER_TOP_WIDTH`].
pub const LIST_FOCUS_BORDER_TOP_STYLE: CssPropertyWithConditions =
    CssPropertyWithConditions::on_focus(LIST_EDGE_TOP_SOLID);
/// See [`LIST_FOCUS_BORDER_TOP_WIDTH`].
pub const LIST_FOCUS_BORDER_BOTTOM_STYLE: CssPropertyWithConditions =
    CssPropertyWithConditions::on_focus(LIST_EDGE_BOTTOM_SOLID);
/// See [`LIST_FOCUS_BORDER_TOP_WIDTH`].
pub const LIST_FOCUS_BORDER_LEFT_STYLE: CssPropertyWithConditions =
    CssPropertyWithConditions::on_focus(LIST_EDGE_LEFT_SOLID);
/// See [`LIST_FOCUS_BORDER_TOP_WIDTH`].
pub const LIST_FOCUS_BORDER_RIGHT_STYLE: CssPropertyWithConditions =
    CssPropertyWithConditions::on_focus(LIST_EDGE_RIGHT_SOLID);
/// The dark twin of [`LIST_FOCUS_BORDER_TOP_WIDTH`] — the same 1px, since a
/// border's weight does not change with the theme.
pub const LIST_FOCUS_BORDER_TOP_WIDTH_DARK: CssPropertyWithConditions =
    CssPropertyWithConditions::dark_on_focus(LIST_EDGE_TOP_1PX);
/// The dark twin of [`LIST_FOCUS_BORDER_BOTTOM_WIDTH`].
pub const LIST_FOCUS_BORDER_BOTTOM_WIDTH_DARK: CssPropertyWithConditions =
    CssPropertyWithConditions::dark_on_focus(LIST_EDGE_BOTTOM_1PX);
/// The dark twin of [`LIST_FOCUS_BORDER_LEFT_WIDTH`].
pub const LIST_FOCUS_BORDER_LEFT_WIDTH_DARK: CssPropertyWithConditions =
    CssPropertyWithConditions::dark_on_focus(LIST_EDGE_LEFT_1PX);
/// The dark twin of [`LIST_FOCUS_BORDER_RIGHT_WIDTH`].
pub const LIST_FOCUS_BORDER_RIGHT_WIDTH_DARK: CssPropertyWithConditions =
    CssPropertyWithConditions::dark_on_focus(LIST_EDGE_RIGHT_1PX);
/// The dark twin of [`LIST_FOCUS_BORDER_TOP_STYLE`].
pub const LIST_FOCUS_BORDER_TOP_STYLE_DARK: CssPropertyWithConditions =
    CssPropertyWithConditions::dark_on_focus(LIST_EDGE_TOP_SOLID);
/// The dark twin of [`LIST_FOCUS_BORDER_BOTTOM_STYLE`].
pub const LIST_FOCUS_BORDER_BOTTOM_STYLE_DARK: CssPropertyWithConditions =
    CssPropertyWithConditions::dark_on_focus(LIST_EDGE_BOTTOM_SOLID);
/// The dark twin of [`LIST_FOCUS_BORDER_LEFT_STYLE`].
pub const LIST_FOCUS_BORDER_LEFT_STYLE_DARK: CssPropertyWithConditions =
    CssPropertyWithConditions::dark_on_focus(LIST_EDGE_LEFT_SOLID);
/// The dark twin of [`LIST_FOCUS_BORDER_RIGHT_STYLE`].
pub const LIST_FOCUS_BORDER_RIGHT_STYLE_DARK: CssPropertyWithConditions =
    CssPropertyWithConditions::dark_on_focus(LIST_EDGE_RIGHT_SOLID);
// -- the column header ------------------------------------------------------
/// Column-header hover underline — light: [`LIGHT_LIST_HEADER_HOVER_LINE`] on
/// the bottom edge. Pair with [`LIST_HEADER_HOVER_LINE_COLOR_DARK`].
pub const LIST_HEADER_HOVER_LINE_COLOR: CssPropertyWithConditions =
    CssPropertyWithConditions::on_hover(CssProperty::const_border_bottom_color(
        StyleBorderBottomColor {
            inner: LIGHT_LIST_HEADER_HOVER_LINE,
        },
    ));
/// The dark twin of [`LIST_HEADER_HOVER_LINE_COLOR`]: [`DARK_SOFT`], the muted
/// accent. The light value is a pale blue, and the ramp's muted step is the
/// nearest thing the palette has to it.
pub const LIST_HEADER_HOVER_LINE_COLOR_DARK: CssPropertyWithConditions =
    CssPropertyWithConditions::dark_on_hover(CssProperty::const_border_bottom_color(
        StyleBorderBottomColor { inner: DARK_SOFT },
    ));
// The stops of the hovered column header's face: `LIGHT_LIST_HEADER_HOVER_TOP`
// held to the midpoint, then a hard step to `LIGHT_LIST_HEADER_HOVER_MID`
// fading into `LIGHT_LIST_HEADER_HOVER_BOTTOM`.
const LIST_HEADER_HOVER_STOPS: &[NormalizedLinearColorStop] = &[
    NormalizedLinearColorStop {
        offset: PercentageValue::const_new(0),
        color: ColorOrSystem::color(LIGHT_LIST_HEADER_HOVER_TOP),
    },
    NormalizedLinearColorStop {
        offset: PercentageValue::const_new(50),
        color: ColorOrSystem::color(LIGHT_LIST_HEADER_HOVER_TOP),
    },
    NormalizedLinearColorStop {
        offset: PercentageValue::const_new(51),
        color: ColorOrSystem::color(LIGHT_LIST_HEADER_HOVER_MID),
    },
    NormalizedLinearColorStop {
        offset: PercentageValue::const_new(100),
        color: ColorOrSystem::color(LIGHT_LIST_HEADER_HOVER_BOTTOM),
    },
];
// The hovered column header's face: a top-to-bottom gradient over
// `LIST_HEADER_HOVER_STOPS`.
const LIST_HEADER_HOVER_FACE: &[StyleBackgroundContent] =
    &[StyleBackgroundContent::LinearGradient(LinearGradient {
        direction: Direction::FromTo(DirectionCorners {
            dir_from: DirectionCorner::Top,
            dir_to: DirectionCorner::Bottom,
        }),
        extend_mode: ExtendMode::Clamp,
        stops: NormalizedLinearColorStopVec::from_const_slice(LIST_HEADER_HOVER_STOPS),
    })];
/// Column-header hover face — light. Pair with [`LIST_HEADER_HOVER_BG_DARK`].
///
/// The white-to-blue-grey gradient built from [`LIGHT_LIST_HEADER_HOVER_TOP`],
/// [`LIGHT_LIST_HEADER_HOVER_MID`] and [`LIGHT_LIST_HEADER_HOVER_BOTTOM`].
pub const LIST_HEADER_HOVER_BG: CssPropertyWithConditions =
    CssPropertyWithConditions::on_hover(CssProperty::const_background_content(
        StyleBackgroundContentVec::from_const_slice(LIST_HEADER_HOVER_FACE),
    ));
/// The dark twin of [`LIST_HEADER_HOVER_BG`]: [`DARK_HT`], the hovered control
/// face. A flat fill rather than a gradient, because in this theme the two
/// stops of a face are the same colour anyway.
pub const LIST_HEADER_HOVER_BG_DARK: CssPropertyWithConditions =
    CssPropertyWithConditions::dark_on_hover(CssProperty::const_background_content(
        StyleBackgroundContentVec::from_const_slice(&[StyleBackgroundContent::Color(DARK_HT)]),
    ));
// The inset glow a pressed column header draws inside each edge, light mode: a
// 5px blur of `LIGHT_LIST_HEADER_PRESSED_SHADOW`, no offset, no spread.
const LIST_HEADER_PRESSED_INSET: StyleBoxShadow = StyleBoxShadow {
    offset_x: PixelValueNoPercent {
        inner: PixelValue::const_px(0),
    },
    offset_y: PixelValueNoPercent {
        inner: PixelValue::const_px(0),
    },
    color: LIGHT_LIST_HEADER_PRESSED_SHADOW,
    blur_radius: PixelValueNoPercent {
        inner: PixelValue::const_px(5),
    },
    spread_radius: PixelValueNoPercent {
        inner: PixelValue::const_px(0),
    },
    clip_mode: BoxShadowClipMode::Inset,
};
// `LIST_HEADER_PRESSED_INSET` in dark mode: the same glow in `DARK_PB`, the
// pressed face's bottom stop, which is what an inset shadow shades a face
// toward.
const LIST_HEADER_PRESSED_INSET_DARK: StyleBoxShadow = StyleBoxShadow {
    offset_x: PixelValueNoPercent {
        inner: PixelValue::const_px(0),
    },
    offset_y: PixelValueNoPercent {
        inner: PixelValue::const_px(0),
    },
    color: DARK_PB,
    blur_radius: PixelValueNoPercent {
        inner: PixelValue::const_px(5),
    },
    spread_radius: PixelValueNoPercent {
        inner: PixelValue::const_px(0),
    },
    clip_mode: BoxShadowClipMode::Inset,
};
/// Column-header pressed inset shadow, bottom edge — light: a 5px inset blur
/// of [`LIGHT_LIST_HEADER_PRESSED_SHADOW`].
///
/// Four edges, one const each, so the glow runs all the way round; pair each
/// with its `_DARK` twin.
pub const LIST_HEADER_ACTIVE_SHADOW_BOTTOM: CssPropertyWithConditions =
    CssPropertyWithConditions::on_active(CssProperty::BoxShadowBottom(StyleBoxShadowValue::Exact(
        BoxOrStatic::Static(&LIST_HEADER_PRESSED_INSET),
    )));
/// See [`LIST_HEADER_ACTIVE_SHADOW_BOTTOM`].
pub const LIST_HEADER_ACTIVE_SHADOW_TOP: CssPropertyWithConditions =
    CssPropertyWithConditions::on_active(CssProperty::BoxShadowTop(StyleBoxShadowValue::Exact(
        BoxOrStatic::Static(&LIST_HEADER_PRESSED_INSET),
    )));
/// See [`LIST_HEADER_ACTIVE_SHADOW_BOTTOM`].
pub const LIST_HEADER_ACTIVE_SHADOW_RIGHT: CssPropertyWithConditions =
    CssPropertyWithConditions::on_active(CssProperty::BoxShadowRight(StyleBoxShadowValue::Exact(
        BoxOrStatic::Static(&LIST_HEADER_PRESSED_INSET),
    )));
/// See [`LIST_HEADER_ACTIVE_SHADOW_BOTTOM`].
pub const LIST_HEADER_ACTIVE_SHADOW_LEFT: CssPropertyWithConditions =
    CssPropertyWithConditions::on_active(CssProperty::BoxShadowLeft(StyleBoxShadowValue::Exact(
        BoxOrStatic::Static(&LIST_HEADER_PRESSED_INSET),
    )));
/// The dark twin of [`LIST_HEADER_ACTIVE_SHADOW_BOTTOM`]: the same glow in
/// [`DARK_PB`], the pressed face's bottom stop — what an inset shadow shades a
/// face toward.
pub const LIST_HEADER_ACTIVE_SHADOW_BOTTOM_DARK: CssPropertyWithConditions =
    CssPropertyWithConditions::dark_on_active(CssProperty::BoxShadowBottom(
        StyleBoxShadowValue::Exact(BoxOrStatic::Static(&LIST_HEADER_PRESSED_INSET_DARK)),
    ));
/// The dark twin of [`LIST_HEADER_ACTIVE_SHADOW_TOP`].
pub const LIST_HEADER_ACTIVE_SHADOW_TOP_DARK: CssPropertyWithConditions =
    CssPropertyWithConditions::dark_on_active(CssProperty::BoxShadowTop(
        StyleBoxShadowValue::Exact(BoxOrStatic::Static(&LIST_HEADER_PRESSED_INSET_DARK)),
    ));
/// The dark twin of [`LIST_HEADER_ACTIVE_SHADOW_RIGHT`].
pub const LIST_HEADER_ACTIVE_SHADOW_RIGHT_DARK: CssPropertyWithConditions =
    CssPropertyWithConditions::dark_on_active(CssProperty::BoxShadowRight(
        StyleBoxShadowValue::Exact(BoxOrStatic::Static(&LIST_HEADER_PRESSED_INSET_DARK)),
    ));
/// The dark twin of [`LIST_HEADER_ACTIVE_SHADOW_LEFT`].
pub const LIST_HEADER_ACTIVE_SHADOW_LEFT_DARK: CssPropertyWithConditions =
    CssPropertyWithConditions::dark_on_active(CssProperty::BoxShadowLeft(
        StyleBoxShadowValue::Exact(BoxOrStatic::Static(&LIST_HEADER_PRESSED_INSET_DARK)),
    ));
/// Column-header pressed border, bottom edge — light:
/// [`LIGHT_LIST_HEADER_PRESSED_BORDER`]. Four edges; pair each with its `_DARK`
/// twin.
pub const LIST_HEADER_ACTIVE_BORDER_BOTTOM_COLOR: CssPropertyWithConditions =
    CssPropertyWithConditions::on_active(CssProperty::const_border_bottom_color(
        StyleBorderBottomColor {
            inner: LIGHT_LIST_HEADER_PRESSED_BORDER,
        },
    ));
/// See [`LIST_HEADER_ACTIVE_BORDER_BOTTOM_COLOR`].
pub const LIST_HEADER_ACTIVE_BORDER_LEFT_COLOR: CssPropertyWithConditions =
    CssPropertyWithConditions::on_active(CssProperty::const_border_left_color(
        StyleBorderLeftColor {
            inner: LIGHT_LIST_HEADER_PRESSED_BORDER,
        },
    ));
/// See [`LIST_HEADER_ACTIVE_BORDER_BOTTOM_COLOR`].
pub const LIST_HEADER_ACTIVE_BORDER_RIGHT_COLOR: CssPropertyWithConditions =
    CssPropertyWithConditions::on_active(CssProperty::const_border_right_color(
        StyleBorderRightColor {
            inner: LIGHT_LIST_HEADER_PRESSED_BORDER,
        },
    ));
/// See [`LIST_HEADER_ACTIVE_BORDER_BOTTOM_COLOR`].
pub const LIST_HEADER_ACTIVE_BORDER_TOP_COLOR: CssPropertyWithConditions =
    CssPropertyWithConditions::on_active(CssProperty::const_border_top_color(
        StyleBorderTopColor {
            inner: LIGHT_LIST_HEADER_PRESSED_BORDER,
        },
    ));
/// The dark twin of [`LIST_HEADER_ACTIVE_BORDER_BOTTOM_COLOR`]: [`DARK_BD`],
/// the default border — the light value is a neutral grey-blue, not an accent.
pub const LIST_HEADER_ACTIVE_BORDER_BOTTOM_COLOR_DARK: CssPropertyWithConditions =
    CssPropertyWithConditions::dark_on_active(CssProperty::const_border_bottom_color(
        StyleBorderBottomColor { inner: DARK_BD },
    ));
/// The dark twin of [`LIST_HEADER_ACTIVE_BORDER_LEFT_COLOR`].
pub const LIST_HEADER_ACTIVE_BORDER_LEFT_COLOR_DARK: CssPropertyWithConditions =
    CssPropertyWithConditions::dark_on_active(CssProperty::const_border_left_color(
        StyleBorderLeftColor { inner: DARK_BD },
    ));
/// The dark twin of [`LIST_HEADER_ACTIVE_BORDER_RIGHT_COLOR`].
pub const LIST_HEADER_ACTIVE_BORDER_RIGHT_COLOR_DARK: CssPropertyWithConditions =
    CssPropertyWithConditions::dark_on_active(CssProperty::const_border_right_color(
        StyleBorderRightColor { inner: DARK_BD },
    ));
/// The dark twin of [`LIST_HEADER_ACTIVE_BORDER_TOP_COLOR`].
pub const LIST_HEADER_ACTIVE_BORDER_TOP_COLOR_DARK: CssPropertyWithConditions =
    CssPropertyWithConditions::dark_on_active(CssProperty::const_border_top_color(
        StyleBorderTopColor { inner: DARK_BD },
    ));
/// Column-header pressed face — light: [`LIGHT_LIST_HEADER_PRESSED`]. Pair
/// with [`LIST_HEADER_ACTIVE_BG_DARK`].
pub const LIST_HEADER_ACTIVE_BG: CssPropertyWithConditions = CssPropertyWithConditions::on_active(
    CssProperty::const_background_content(StyleBackgroundContentVec::from_const_slice(&[
        StyleBackgroundContent::Color(LIGHT_LIST_HEADER_PRESSED),
    ])),
);
/// The dark twin of [`LIST_HEADER_ACTIVE_BG`]: [`DARK_PT`], the pressed
/// control face.
pub const LIST_HEADER_ACTIVE_BG_DARK: CssPropertyWithConditions =
    CssPropertyWithConditions::dark_on_active(CssProperty::const_background_content(
        StyleBackgroundContentVec::from_const_slice(&[StyleBackgroundContent::Color(DARK_PT)]),
    ));
// -- the row ----------------------------------------------------------------
/// Row hover ring, bottom edge — light: [`LIGHT_LIST_ROW_HOVER_BORDER`]. Four
/// edges; pair each with its `_DARK` twin.
pub const LIST_ROW_HOVER_BORDER_BOTTOM_COLOR: CssPropertyWithConditions =
    CssPropertyWithConditions::on_hover(CssProperty::const_border_bottom_color(
        StyleBorderBottomColor {
            inner: LIGHT_LIST_ROW_HOVER_BORDER,
        },
    ));
/// See [`LIST_ROW_HOVER_BORDER_BOTTOM_COLOR`].
pub const LIST_ROW_HOVER_BORDER_LEFT_COLOR: CssPropertyWithConditions =
    CssPropertyWithConditions::on_hover(CssProperty::const_border_left_color(
        StyleBorderLeftColor {
            inner: LIGHT_LIST_ROW_HOVER_BORDER,
        },
    ));
/// See [`LIST_ROW_HOVER_BORDER_BOTTOM_COLOR`].
pub const LIST_ROW_HOVER_BORDER_RIGHT_COLOR: CssPropertyWithConditions =
    CssPropertyWithConditions::on_hover(CssProperty::const_border_right_color(
        StyleBorderRightColor {
            inner: LIGHT_LIST_ROW_HOVER_BORDER,
        },
    ));
/// See [`LIST_ROW_HOVER_BORDER_BOTTOM_COLOR`].
pub const LIST_ROW_HOVER_BORDER_TOP_COLOR: CssPropertyWithConditions =
    CssPropertyWithConditions::on_hover(CssProperty::const_border_top_color(StyleBorderTopColor {
        inner: LIGHT_LIST_ROW_HOVER_BORDER,
    }));
/// The dark twin of [`LIST_ROW_HOVER_BORDER_BOTTOM_COLOR`]: [`DARK_SOFT`], the
/// muted accent — one step below the focus ring's [`DARK_ACC`], as #65B5DC is
/// one step below #26A0DA in light mode.
pub const LIST_ROW_HOVER_BORDER_BOTTOM_COLOR_DARK: CssPropertyWithConditions =
    CssPropertyWithConditions::dark_on_hover(CssProperty::const_border_bottom_color(
        StyleBorderBottomColor { inner: DARK_SOFT },
    ));
/// The dark twin of [`LIST_ROW_HOVER_BORDER_LEFT_COLOR`].
pub const LIST_ROW_HOVER_BORDER_LEFT_COLOR_DARK: CssPropertyWithConditions =
    CssPropertyWithConditions::dark_on_hover(CssProperty::const_border_left_color(
        StyleBorderLeftColor { inner: DARK_SOFT },
    ));
/// The dark twin of [`LIST_ROW_HOVER_BORDER_RIGHT_COLOR`].
pub const LIST_ROW_HOVER_BORDER_RIGHT_COLOR_DARK: CssPropertyWithConditions =
    CssPropertyWithConditions::dark_on_hover(CssProperty::const_border_right_color(
        StyleBorderRightColor { inner: DARK_SOFT },
    ));
/// The dark twin of [`LIST_ROW_HOVER_BORDER_TOP_COLOR`].
pub const LIST_ROW_HOVER_BORDER_TOP_COLOR_DARK: CssPropertyWithConditions =
    CssPropertyWithConditions::dark_on_hover(CssProperty::const_border_top_color(
        StyleBorderTopColor { inner: DARK_SOFT },
    ));
/// Row hover fill — light: [`LIGHT_LIST_ROW_HOVER`]. Pair with
/// [`LIST_ROW_HOVER_BG_DARK`].
pub const LIST_ROW_HOVER_BG: CssPropertyWithConditions = CssPropertyWithConditions::on_hover(
    CssProperty::const_background_content(StyleBackgroundContentVec::from_const_slice(&[
        StyleBackgroundContent::Color(LIGHT_LIST_ROW_HOVER),
    ])),
);
/// The dark twin of [`LIST_ROW_HOVER_BG`]: [`DARK_ROW_HOVER`], the fill a
/// hovered tree row already takes.
pub const LIST_ROW_HOVER_BG_DARK: CssPropertyWithConditions =
    CssPropertyWithConditions::dark_on_hover(CssProperty::const_background_content(
        StyleBackgroundContentVec::from_const_slice(&[StyleBackgroundContent::Color(
            DARK_ROW_HOVER,
        )]),
    ));
/// Focused-row ring, bottom edge — light: [`LIGHT_LIST_ROW_FOCUS_BORDER`].
/// Four edges; pair each with its `_DARK` twin.
pub const LIST_ROW_FOCUS_BORDER_BOTTOM_COLOR: CssPropertyWithConditions =
    CssPropertyWithConditions::on_focus(CssProperty::const_border_bottom_color(
        StyleBorderBottomColor {
            inner: LIGHT_LIST_ROW_FOCUS_BORDER,
        },
    ));
/// See [`LIST_ROW_FOCUS_BORDER_BOTTOM_COLOR`].
pub const LIST_ROW_FOCUS_BORDER_LEFT_COLOR: CssPropertyWithConditions =
    CssPropertyWithConditions::on_focus(CssProperty::const_border_left_color(
        StyleBorderLeftColor {
            inner: LIGHT_LIST_ROW_FOCUS_BORDER,
        },
    ));
/// See [`LIST_ROW_FOCUS_BORDER_BOTTOM_COLOR`].
pub const LIST_ROW_FOCUS_BORDER_RIGHT_COLOR: CssPropertyWithConditions =
    CssPropertyWithConditions::on_focus(CssProperty::const_border_right_color(
        StyleBorderRightColor {
            inner: LIGHT_LIST_ROW_FOCUS_BORDER,
        },
    ));
/// See [`LIST_ROW_FOCUS_BORDER_BOTTOM_COLOR`].
pub const LIST_ROW_FOCUS_BORDER_TOP_COLOR: CssPropertyWithConditions =
    CssPropertyWithConditions::on_focus(CssProperty::const_border_top_color(StyleBorderTopColor {
        inner: LIGHT_LIST_ROW_FOCUS_BORDER,
    }));
/// The dark twin of [`LIST_ROW_FOCUS_BORDER_BOTTOM_COLOR`]: [`DARK_ACC`] — a
/// focus ring is the accent, as it is for every other focused control in this
/// theme.
pub const LIST_ROW_FOCUS_BORDER_BOTTOM_COLOR_DARK: CssPropertyWithConditions =
    CssPropertyWithConditions::dark_on_focus(CssProperty::const_border_bottom_color(
        StyleBorderBottomColor { inner: DARK_ACC },
    ));
/// The dark twin of [`LIST_ROW_FOCUS_BORDER_LEFT_COLOR`].
pub const LIST_ROW_FOCUS_BORDER_LEFT_COLOR_DARK: CssPropertyWithConditions =
    CssPropertyWithConditions::dark_on_focus(CssProperty::const_border_left_color(
        StyleBorderLeftColor { inner: DARK_ACC },
    ));
/// The dark twin of [`LIST_ROW_FOCUS_BORDER_RIGHT_COLOR`].
pub const LIST_ROW_FOCUS_BORDER_RIGHT_COLOR_DARK: CssPropertyWithConditions =
    CssPropertyWithConditions::dark_on_focus(CssProperty::const_border_right_color(
        StyleBorderRightColor { inner: DARK_ACC },
    ));
/// The dark twin of [`LIST_ROW_FOCUS_BORDER_TOP_COLOR`].
pub const LIST_ROW_FOCUS_BORDER_TOP_COLOR_DARK: CssPropertyWithConditions =
    CssPropertyWithConditions::dark_on_focus(CssProperty::const_border_top_color(
        StyleBorderTopColor { inner: DARK_ACC },
    ));
/// Focused-row fill — light: [`LIGHT_LIST_ROW_FOCUS`]. Pair with
/// [`LIST_ROW_FOCUS_BG_DARK`].
pub const LIST_ROW_FOCUS_BG: CssPropertyWithConditions = CssPropertyWithConditions::on_focus(
    CssProperty::const_background_content(StyleBackgroundContentVec::from_const_slice(&[
        StyleBackgroundContent::Color(LIGHT_LIST_ROW_FOCUS),
    ])),
);
/// The dark twin of [`LIST_ROW_FOCUS_BG`]: [`DARK_GLOW`], the translucent
/// accent.
///
/// Over the dark field it tints the row toward the accent the way #B8E0F3 does
/// over white, and stays distinguishable from a hovered row's
/// [`DARK_ROW_HOVER`].
pub const LIST_ROW_FOCUS_BG_DARK: CssPropertyWithConditions =
    CssPropertyWithConditions::dark_on_focus(CssProperty::const_background_content(
        StyleBackgroundContentVec::from_const_slice(&[StyleBackgroundContent::Color(DARK_GLOW)]),
    ));
// == /STATES: list_view ==
//
//
//
// == STATES: tabs ==
// ---------------------------------------------------------------------------
// INTERACTIVE STATES — tabs
// ---------------------------------------------------------------------------
//
// The hover of an inactive tab, in the Windows-classic idiom `tabs.rs` draws:
//
//     .__azul-native-tabs-tab-not-active:hover {
//         border: 1px solid #7EB4EA;
//         background: linear-gradient(#ECF4FC, #DDEDFC);
//     }
//
// Thirteen declarations once the `border` shorthand is expanded — four widths,
// four styles, four colours — plus the fill, and they are one set. The widths
// and styles are not decoration: a `-noleftborder` / `-norightborder` tab has
// nulled the edge it shares with the active tab, and these are what draw it
// back while the pointer is over the tab, so the hover ring is whole. Take the
// colours without the widths and the two tabs beside the active one hover with
// a side missing.
//
// A width or a style has no per-mode value, so its dark twin repeats it. That
// is deliberate: every rule in this section is a pair, with no exceptions a
// reader would have to know about, and "dark >= light" is the invariant the
// check script holds the themes to.
/// Hover border of an inactive tab, light mode (#7EB4EA).
///
/// The Windows "hot" tab edge: a SOFT accent blue, neither the accent itself
/// nor the neutral #ACACAC the tab rests at. On this palette's accent ramp it
/// is the `SOFT` rung — [`LIGHT_SOFT`] is #6EA8FE — and that is what picks its
/// dark twin.
pub const LIGHT_TAB_HOVER_BORDER: ColorU = ColorU {
    r: 126,
    g: 180,
    b: 234,
    a: 255,
};
/// Hover border of an inactive tab, dark mode: [`DARK_SOFT`], the same rung of
/// the ramp in the other mode.
///
/// Not [`DARK_ACC`], which is the full accent and would ring a hovered tab
/// harder in dark mode than in light; not [`DARK_BD`], which is the neutral
/// border and would lose the tint altogether.
pub const DARK_TAB_HOVER_BORDER: ColorU = DARK_SOFT;
/// Top stop of an inactive tab's hover fill, light mode (#ECF4FC).
pub const LIGHT_TAB_HOVER_TOP: ColorU = ColorU {
    r: 236,
    g: 244,
    b: 252,
    a: 255,
};
/// Bottom stop of an inactive tab's hover fill, light mode (#DDEDFC).
pub const LIGHT_TAB_HOVER_BOTTOM: ColorU = ColorU {
    r: 221,
    g: 237,
    b: 252,
    a: 255,
};
// The light fill is a top-to-bottom gradient, so the dark one is too, and its
// stops are this theme's own hover pair: a tab strip is a page-neutral surface,
// and `HT` -> `HB` is what the plan names for a hovered control face.
const TAB_HOVER_STOPS: &[NormalizedLinearColorStop] = &[
    NormalizedLinearColorStop {
        offset: PercentageValue::const_new(0),
        color: ColorOrSystem::color(LIGHT_TAB_HOVER_TOP),
    },
    NormalizedLinearColorStop {
        offset: PercentageValue::const_new(100),
        color: ColorOrSystem::color(LIGHT_TAB_HOVER_BOTTOM),
    },
];
const TAB_HOVER_STOPS_DARK: &[NormalizedLinearColorStop] = &[
    NormalizedLinearColorStop {
        offset: PercentageValue::const_new(0),
        color: ColorOrSystem::color(DARK_HT),
    },
    NormalizedLinearColorStop {
        offset: PercentageValue::const_new(100),
        color: ColorOrSystem::color(DARK_HB),
    },
];
const TAB_HOVER_FILL: &[StyleBackgroundContent] =
    &[StyleBackgroundContent::LinearGradient(LinearGradient {
        direction: Direction::FromTo(DirectionCorners {
            dir_from: DirectionCorner::Top,
            dir_to: DirectionCorner::Bottom,
        }),
        extend_mode: ExtendMode::Clamp,
        stops: NormalizedLinearColorStopVec::from_const_slice(TAB_HOVER_STOPS),
    })];
const TAB_HOVER_FILL_DARK: &[StyleBackgroundContent] =
    &[StyleBackgroundContent::LinearGradient(LinearGradient {
        direction: Direction::FromTo(DirectionCorners {
            dir_from: DirectionCorner::Top,
            dir_to: DirectionCorner::Bottom,
        }),
        extend_mode: ExtendMode::Clamp,
        stops: NormalizedLinearColorStopVec::from_const_slice(TAB_HOVER_STOPS_DARK),
    })];
/// A hovered inactive tab's border: 1px on every edge — light mode.
///
/// The first of the thirteen rules in [`TAB_HOVER_STATES`]; use the set, not
/// the rule. A border width has no per-mode value, so the dark twin repeats it.
pub const TAB_HOVER_BORDER_BOTTOM_WIDTH: CssPropertyWithConditions =
    CssPropertyWithConditions::on_hover(CssProperty::const_border_bottom_width(
        LayoutBorderBottomWidth {
            inner: PixelValue::const_px(1),
        },
    ));
/// See [`TAB_HOVER_STATES`].
pub const TAB_HOVER_BORDER_LEFT_WIDTH: CssPropertyWithConditions =
    CssPropertyWithConditions::on_hover(CssProperty::const_border_left_width(
        LayoutBorderLeftWidth {
            inner: PixelValue::const_px(1),
        },
    ));
/// See [`TAB_HOVER_STATES`].
pub const TAB_HOVER_BORDER_RIGHT_WIDTH: CssPropertyWithConditions =
    CssPropertyWithConditions::on_hover(CssProperty::const_border_right_width(
        LayoutBorderRightWidth {
            inner: PixelValue::const_px(1),
        },
    ));
/// See [`TAB_HOVER_STATES`].
pub const TAB_HOVER_BORDER_TOP_WIDTH: CssPropertyWithConditions =
    CssPropertyWithConditions::on_hover(CssProperty::const_border_top_width(
        LayoutBorderTopWidth {
            inner: PixelValue::const_px(1),
        },
    ));
/// See [`TAB_HOVER_STATES`].
pub const TAB_HOVER_BORDER_BOTTOM_STYLE: CssPropertyWithConditions =
    CssPropertyWithConditions::on_hover(CssProperty::const_border_bottom_style(
        StyleBorderBottomStyle {
            inner: BorderStyle::Solid,
        },
    ));
/// See [`TAB_HOVER_STATES`].
pub const TAB_HOVER_BORDER_LEFT_STYLE: CssPropertyWithConditions =
    CssPropertyWithConditions::on_hover(CssProperty::const_border_left_style(
        StyleBorderLeftStyle {
            inner: BorderStyle::Solid,
        },
    ));
/// See [`TAB_HOVER_STATES`].
pub const TAB_HOVER_BORDER_RIGHT_STYLE: CssPropertyWithConditions =
    CssPropertyWithConditions::on_hover(CssProperty::const_border_right_style(
        StyleBorderRightStyle {
            inner: BorderStyle::Solid,
        },
    ));
/// See [`TAB_HOVER_STATES`].
pub const TAB_HOVER_BORDER_TOP_STYLE: CssPropertyWithConditions =
    CssPropertyWithConditions::on_hover(CssProperty::const_border_top_style(StyleBorderTopStyle {
        inner: BorderStyle::Solid,
    }));
/// See [`TAB_HOVER_STATES`]. The colour is [`LIGHT_TAB_HOVER_BORDER`].
pub const TAB_HOVER_BORDER_BOTTOM_COLOR: CssPropertyWithConditions =
    CssPropertyWithConditions::on_hover(CssProperty::const_border_bottom_color(
        StyleBorderBottomColor {
            inner: LIGHT_TAB_HOVER_BORDER,
        },
    ));
/// See [`TAB_HOVER_STATES`].
pub const TAB_HOVER_BORDER_LEFT_COLOR: CssPropertyWithConditions =
    CssPropertyWithConditions::on_hover(CssProperty::const_border_left_color(
        StyleBorderLeftColor {
            inner: LIGHT_TAB_HOVER_BORDER,
        },
    ));
/// See [`TAB_HOVER_STATES`].
pub const TAB_HOVER_BORDER_RIGHT_COLOR: CssPropertyWithConditions =
    CssPropertyWithConditions::on_hover(CssProperty::const_border_right_color(
        StyleBorderRightColor {
            inner: LIGHT_TAB_HOVER_BORDER,
        },
    ));
/// See [`TAB_HOVER_STATES`].
pub const TAB_HOVER_BORDER_TOP_COLOR: CssPropertyWithConditions =
    CssPropertyWithConditions::on_hover(CssProperty::const_border_top_color(StyleBorderTopColor {
        inner: LIGHT_TAB_HOVER_BORDER,
    }));
/// A hovered inactive tab's fill — light mode: the [`LIGHT_TAB_HOVER_TOP`] to
/// [`LIGHT_TAB_HOVER_BOTTOM`] gradient. See [`TAB_HOVER_STATES`].
pub const TAB_HOVER_BG: CssPropertyWithConditions =
    CssPropertyWithConditions::on_hover(CssProperty::const_background_content(
        StyleBackgroundContentVec::from_const_slice(TAB_HOVER_FILL),
    ));
/// The dark twin of [`TAB_HOVER_BORDER_BOTTOM_WIDTH`] — the same width; see
/// the section comment for why a twin exists at all.
pub const TAB_HOVER_BORDER_BOTTOM_WIDTH_DARK: CssPropertyWithConditions =
    CssPropertyWithConditions::dark_on_hover(CssProperty::const_border_bottom_width(
        LayoutBorderBottomWidth {
            inner: PixelValue::const_px(1),
        },
    ));
/// The dark twin of [`TAB_HOVER_BORDER_LEFT_WIDTH`].
pub const TAB_HOVER_BORDER_LEFT_WIDTH_DARK: CssPropertyWithConditions =
    CssPropertyWithConditions::dark_on_hover(CssProperty::const_border_left_width(
        LayoutBorderLeftWidth {
            inner: PixelValue::const_px(1),
        },
    ));
/// The dark twin of [`TAB_HOVER_BORDER_RIGHT_WIDTH`].
pub const TAB_HOVER_BORDER_RIGHT_WIDTH_DARK: CssPropertyWithConditions =
    CssPropertyWithConditions::dark_on_hover(CssProperty::const_border_right_width(
        LayoutBorderRightWidth {
            inner: PixelValue::const_px(1),
        },
    ));
/// The dark twin of [`TAB_HOVER_BORDER_TOP_WIDTH`].
pub const TAB_HOVER_BORDER_TOP_WIDTH_DARK: CssPropertyWithConditions =
    CssPropertyWithConditions::dark_on_hover(CssProperty::const_border_top_width(
        LayoutBorderTopWidth {
            inner: PixelValue::const_px(1),
        },
    ));
/// The dark twin of [`TAB_HOVER_BORDER_BOTTOM_STYLE`].
pub const TAB_HOVER_BORDER_BOTTOM_STYLE_DARK: CssPropertyWithConditions =
    CssPropertyWithConditions::dark_on_hover(CssProperty::const_border_bottom_style(
        StyleBorderBottomStyle {
            inner: BorderStyle::Solid,
        },
    ));
/// The dark twin of [`TAB_HOVER_BORDER_LEFT_STYLE`].
pub const TAB_HOVER_BORDER_LEFT_STYLE_DARK: CssPropertyWithConditions =
    CssPropertyWithConditions::dark_on_hover(CssProperty::const_border_left_style(
        StyleBorderLeftStyle {
            inner: BorderStyle::Solid,
        },
    ));
/// The dark twin of [`TAB_HOVER_BORDER_RIGHT_STYLE`].
pub const TAB_HOVER_BORDER_RIGHT_STYLE_DARK: CssPropertyWithConditions =
    CssPropertyWithConditions::dark_on_hover(CssProperty::const_border_right_style(
        StyleBorderRightStyle {
            inner: BorderStyle::Solid,
        },
    ));
/// The dark twin of [`TAB_HOVER_BORDER_TOP_STYLE`].
pub const TAB_HOVER_BORDER_TOP_STYLE_DARK: CssPropertyWithConditions =
    CssPropertyWithConditions::dark_on_hover(CssProperty::const_border_top_style(
        StyleBorderTopStyle {
            inner: BorderStyle::Solid,
        },
    ));
/// The dark twin of [`TAB_HOVER_BORDER_BOTTOM_COLOR`]: [`DARK_TAB_HOVER_BORDER`].
pub const TAB_HOVER_BORDER_BOTTOM_COLOR_DARK: CssPropertyWithConditions =
    CssPropertyWithConditions::dark_on_hover(CssProperty::const_border_bottom_color(
        StyleBorderBottomColor {
            inner: DARK_TAB_HOVER_BORDER,
        },
    ));
/// The dark twin of [`TAB_HOVER_BORDER_LEFT_COLOR`].
pub const TAB_HOVER_BORDER_LEFT_COLOR_DARK: CssPropertyWithConditions =
    CssPropertyWithConditions::dark_on_hover(CssProperty::const_border_left_color(
        StyleBorderLeftColor {
            inner: DARK_TAB_HOVER_BORDER,
        },
    ));
/// The dark twin of [`TAB_HOVER_BORDER_RIGHT_COLOR`].
pub const TAB_HOVER_BORDER_RIGHT_COLOR_DARK: CssPropertyWithConditions =
    CssPropertyWithConditions::dark_on_hover(CssProperty::const_border_right_color(
        StyleBorderRightColor {
            inner: DARK_TAB_HOVER_BORDER,
        },
    ));
/// The dark twin of [`TAB_HOVER_BORDER_TOP_COLOR`].
pub const TAB_HOVER_BORDER_TOP_COLOR_DARK: CssPropertyWithConditions =
    CssPropertyWithConditions::dark_on_hover(CssProperty::const_border_top_color(
        StyleBorderTopColor {
            inner: DARK_TAB_HOVER_BORDER,
        },
    ));
/// The dark twin of [`TAB_HOVER_BG`]: the [`DARK_HT`] to [`DARK_HB`] gradient.
pub const TAB_HOVER_BG_DARK: CssPropertyWithConditions =
    CssPropertyWithConditions::dark_on_hover(CssProperty::const_background_content(
        StyleBackgroundContentVec::from_const_slice(TAB_HOVER_FILL_DARK),
    ));
/// Every state an inactive tab takes: the hover ring (four widths, four
/// styles, four colours) and the hover fill, each with its dark twin.
///
/// One array so a theme function can append the whole set in a line and cannot
/// ship half of it. `tabs.rs` holds its styles in const slices, which cannot
/// splice an array, so it names these twenty-six one by one — in this order.
pub const TAB_HOVER_STATES: [CssPropertyWithConditions; 26] = [
    TAB_HOVER_BORDER_BOTTOM_WIDTH,
    TAB_HOVER_BORDER_LEFT_WIDTH,
    TAB_HOVER_BORDER_RIGHT_WIDTH,
    TAB_HOVER_BORDER_TOP_WIDTH,
    TAB_HOVER_BORDER_BOTTOM_STYLE,
    TAB_HOVER_BORDER_LEFT_STYLE,
    TAB_HOVER_BORDER_RIGHT_STYLE,
    TAB_HOVER_BORDER_TOP_STYLE,
    TAB_HOVER_BORDER_BOTTOM_COLOR,
    TAB_HOVER_BORDER_LEFT_COLOR,
    TAB_HOVER_BORDER_RIGHT_COLOR,
    TAB_HOVER_BORDER_TOP_COLOR,
    TAB_HOVER_BG,
    TAB_HOVER_BORDER_BOTTOM_WIDTH_DARK,
    TAB_HOVER_BORDER_LEFT_WIDTH_DARK,
    TAB_HOVER_BORDER_RIGHT_WIDTH_DARK,
    TAB_HOVER_BORDER_TOP_WIDTH_DARK,
    TAB_HOVER_BORDER_BOTTOM_STYLE_DARK,
    TAB_HOVER_BORDER_LEFT_STYLE_DARK,
    TAB_HOVER_BORDER_RIGHT_STYLE_DARK,
    TAB_HOVER_BORDER_TOP_STYLE_DARK,
    TAB_HOVER_BORDER_BOTTOM_COLOR_DARK,
    TAB_HOVER_BORDER_LEFT_COLOR_DARK,
    TAB_HOVER_BORDER_RIGHT_COLOR_DARK,
    TAB_HOVER_BORDER_TOP_COLOR_DARK,
    TAB_HOVER_BG_DARK,
];
// == /STATES: tabs ==
//
//
//
// == STATES: text_input ==
//
// text_input declares no states of its own: it takes `FIELD_BORDER_STATES`
// (the text-fields section above), because its eight rules were the same
// accent ring on hover and on focus that text_area had, and `text_input()`
// appends that array rather than a second copy of it. The one difference the
// widget file used to make — a grey hover ring on Windows only — was a
// platform distinction neither theme draws, so it went with the move.
//
// == /STATES: text_input ==
//
//
//
// == STATES: chrome ==
// ---------------------------------------------------------------------------
// INTERACTIVE STATES — chrome (ribbon, statusbar, quick_access, backstage)
// ---------------------------------------------------------------------------
//
// The four chrome widgets carry their own palettes (`RibbonTheme`,
// `StatusBarTheme`, `QuickAccessTheme`, `BackstageTheme`) whose colours are
// RUNTIME values — an Office preset, or whatever `from_system` read off the
// desktop — so their states cannot be consts. They are builders instead, like
// [`hover_bg_both`] and [`active_bg_both`] above, and every builder returns
// BOTH halves of a rule in one array, so a caller cannot take the light half
// without the dark one.
//
// The dark half is the caller's to choose, and the rule is the one
// `button_states` applies: a surface that is its own colour in both modes (the
// backstage's blue nav column, the ribbon's accent-filled application button,
// the status bar's accent strip) keeps its light state colour, because the
// surface does not change in dark mode either; a page-neutral surface (the
// ribbon chrome, the quick-access band) takes this theme's tokens —
// [`DARK_HT`] / [`DARK_PT`] for the fills, [`DARK_BD`] for a hover border,
// [`DARK_ACC`] for hovered accent text and focus rings.
/// Border colour on hover, all four edges, with BOTH halves chosen by the
/// caller.
///
/// Eight rules, because a border colour is four properties and a hover that
/// sets only some edges leaves the rest at their resting colour. The four dark
/// rules follow the four light ones so that they win in dark mode (inline
/// declarations resolve last-match-wins).
#[must_use]
34017
pub const fn hover_border_both(light: ColorU, dark: ColorU) -> [CssPropertyWithConditions; 8] {
34017
    [
34017
        CssPropertyWithConditions::on_hover(CssProperty::const_border_top_color(
34017
            StyleBorderTopColor { inner: light },
34017
        )),
34017
        CssPropertyWithConditions::on_hover(CssProperty::const_border_left_color(
34017
            StyleBorderLeftColor { inner: light },
34017
        )),
34017
        CssPropertyWithConditions::on_hover(CssProperty::const_border_right_color(
34017
            StyleBorderRightColor { inner: light },
34017
        )),
34017
        CssPropertyWithConditions::on_hover(CssProperty::const_border_bottom_color(
34017
            StyleBorderBottomColor { inner: light },
34017
        )),
34017
        CssPropertyWithConditions::dark_on_hover(CssProperty::const_border_top_color(
34017
            StyleBorderTopColor { inner: dark },
34017
        )),
34017
        CssPropertyWithConditions::dark_on_hover(CssProperty::const_border_left_color(
34017
            StyleBorderLeftColor { inner: dark },
34017
        )),
34017
        CssPropertyWithConditions::dark_on_hover(CssProperty::const_border_right_color(
34017
            StyleBorderRightColor { inner: dark },
34017
        )),
34017
        CssPropertyWithConditions::dark_on_hover(CssProperty::const_border_bottom_color(
34017
            StyleBorderBottomColor { inner: dark },
34017
        )),
34017
    ]
34017
}
/// Border colour on focus, all four edges, with BOTH halves chosen by the
/// caller: the focus ring of a field whose accent is a runtime colour.
///
/// The const [`FOCUS_BORDER_TOP`] family is the same ring at this theme's own
/// accent; this is for a palette that brings its own. Same shape as
/// [`hover_border_both`], for the same reason.
#[must_use]
4
pub const fn focus_border_both(light: ColorU, dark: ColorU) -> [CssPropertyWithConditions; 8] {
4
    [
4
        CssPropertyWithConditions::on_focus(CssProperty::const_border_top_color(
4
            StyleBorderTopColor { inner: light },
4
        )),
4
        CssPropertyWithConditions::on_focus(CssProperty::const_border_left_color(
4
            StyleBorderLeftColor { inner: light },
4
        )),
4
        CssPropertyWithConditions::on_focus(CssProperty::const_border_right_color(
4
            StyleBorderRightColor { inner: light },
4
        )),
4
        CssPropertyWithConditions::on_focus(CssProperty::const_border_bottom_color(
4
            StyleBorderBottomColor { inner: light },
4
        )),
4
        CssPropertyWithConditions::dark_on_focus(CssProperty::const_border_top_color(
4
            StyleBorderTopColor { inner: dark },
4
        )),
4
        CssPropertyWithConditions::dark_on_focus(CssProperty::const_border_left_color(
4
            StyleBorderLeftColor { inner: dark },
4
        )),
4
        CssPropertyWithConditions::dark_on_focus(CssProperty::const_border_right_color(
4
            StyleBorderRightColor { inner: dark },
4
        )),
4
        CssPropertyWithConditions::dark_on_focus(CssProperty::const_border_bottom_color(
4
            StyleBorderBottomColor { inner: dark },
4
        )),
4
    ]
4
}
/// Text colour on hover, with BOTH halves chosen by the caller — a tab header
/// that takes the accent when hovered, say. Pass [`DARK_ACC`] as `dark` when
/// the light colour is the palette's accent.
#[must_use]
8237
pub const fn hover_text_color_both(light: ColorU, dark: ColorU) -> [CssPropertyWithConditions; 2] {
8237
    [
8237
        CssPropertyWithConditions::on_hover(CssProperty::const_text_color(StyleTextColor {
8237
            inner: light,
8237
        })),
8237
        CssPropertyWithConditions::dark_on_hover(CssProperty::const_text_color(StyleTextColor {
8237
            inner: dark,
8237
        })),
8237
    ]
8237
}
/// Corner radius on hover, all four corners, paired with an identical dark
/// twin.
///
/// A radius has no colour, so the twin repeats the value. It is emitted anyway
/// so that the invariant this section exists for — every state rule has a dark
/// twin — holds without an exception to remember, the same way `button_states`
/// pairs the link button's underline.
#[must_use]
34
pub const fn hover_radius_pair(radius: PixelValue) -> [CssPropertyWithConditions; 8] {
34
    [
34
        CssPropertyWithConditions::on_hover(CssProperty::const_border_top_left_radius(
34
            StyleBorderTopLeftRadius { inner: radius },
34
        )),
34
        CssPropertyWithConditions::on_hover(CssProperty::const_border_top_right_radius(
34
            StyleBorderTopRightRadius { inner: radius },
34
        )),
34
        CssPropertyWithConditions::on_hover(CssProperty::const_border_bottom_left_radius(
34
            StyleBorderBottomLeftRadius { inner: radius },
34
        )),
34
        CssPropertyWithConditions::on_hover(CssProperty::const_border_bottom_right_radius(
34
            StyleBorderBottomRightRadius { inner: radius },
34
        )),
34
        CssPropertyWithConditions::dark_on_hover(CssProperty::const_border_top_left_radius(
34
            StyleBorderTopLeftRadius { inner: radius },
34
        )),
34
        CssPropertyWithConditions::dark_on_hover(CssProperty::const_border_top_right_radius(
34
            StyleBorderTopRightRadius { inner: radius },
34
        )),
34
        CssPropertyWithConditions::dark_on_hover(CssProperty::const_border_bottom_left_radius(
34
            StyleBorderBottomLeftRadius { inner: radius },
34
        )),
34
        CssPropertyWithConditions::dark_on_hover(CssProperty::const_border_bottom_right_radius(
34
            StyleBorderBottomRightRadius { inner: radius },
34
        )),
34
    ]
34
}
// == /STATES: chrome ==
//
//
//