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
// Colors extracted from flora.css
27
// Light mode colors
28
pub const LIGHT_PG: ColorU = ColorU {
29
    r: 230,
30
    g: 228,
31
    b: 223,
32
    a: 255,
33
};
34
pub const LIGHT_SUR: ColorU = ColorU {
35
    r: 242,
36
    g: 241,
37
    b: 237,
38
    a: 255,
39
};
40
pub const LIGHT_DESK: ColorU = ColorU {
41
    r: 221,
42
    g: 219,
43
    b: 213,
44
    a: 255,
45
};
46
pub const LIGHT_STRIP: ColorU = ColorU {
47
    r: 233,
48
    g: 231,
49
    b: 226,
50
    a: 255,
51
};
52
pub const LIGHT_TRACK: ColorU = ColorU {
53
    r: 231,
54
    g: 229,
55
    b: 224,
56
    a: 255,
57
};
58
pub const LIGHT_BD: ColorU = ColorU {
59
    r: 198,
60
    g: 195,
61
    b: 187,
62
    a: 255,
63
};
64
pub const LIGHT_BD2: ColorU = ColorU {
65
    r: 180,
66
    g: 177,
67
    b: 169,
68
    a: 255,
69
};
70
pub const LIGHT_BD3: ColorU = ColorU {
71
    r: 156,
72
    g: 152,
73
    b: 144,
74
    a: 255,
75
};
76
pub const LIGHT_BD4: ColorU = ColorU {
77
    r: 210,
78
    g: 207,
79
    b: 200,
80
    a: 255,
81
};
82
pub const LIGHT_BD5: ColorU = ColorU {
83
    r: 165,
84
    g: 161,
85
    b: 153,
86
    a: 255,
87
};
88
pub const LIGHT_SEP: ColorU = ColorU {
89
    r: 216,
90
    g: 213,
91
    b: 206,
92
    a: 255,
93
};
94
pub const LIGHT_SEP2: ColorU = ColorU {
95
    r: 224,
96
    g: 221,
97
    b: 215,
98
    a: 255,
99
};
100
pub const LIGHT_INK: ColorU = ColorU {
101
    r: 38,
102
    g: 37,
103
    b: 33,
104
    a: 255,
105
};
106
pub const LIGHT_INK2: ColorU = ColorU {
107
    r: 46,
108
    g: 44,
109
    b: 38,
110
    a: 255,
111
};
112
pub const LIGHT_INTRO: ColorU = ColorU {
113
    r: 78,
114
    g: 76,
115
    b: 69,
116
    a: 255,
117
};
118
pub const LIGHT_SOFT1: ColorU = ColorU {
119
    r: 102,
120
    g: 100,
121
    b: 92,
122
    a: 255,
123
};
124
pub const LIGHT_SOFT2: ColorU = ColorU {
125
    r: 130,
126
    g: 127,
127
    b: 118,
128
    a: 255,
129
};
130
pub const LIGHT_SOFT3: ColorU = ColorU {
131
    r: 116,
132
    g: 113,
133
    b: 106,
134
    a: 255,
135
};
136
pub const LIGHT_ICON: ColorU = ColorU {
137
    r: 86,
138
    g: 84,
139
    b: 76,
140
    a: 255,
141
};
142
pub const LIGHT_RT: ColorU = ColorU {
143
    r: 250,
144
    g: 249,
145
    b: 245,
146
    a: 255,
147
};
148
pub const LIGHT_RB: ColorU = ColorU {
149
    r: 236,
150
    g: 234,
151
    b: 228,
152
    a: 255,
153
};
154
pub const LIGHT_HT: ColorU = ColorU {
155
    r: 254,
156
    g: 253,
157
    b: 250,
158
    a: 255,
159
};
160
pub const LIGHT_HB: ColorU = ColorU {
161
    r: 241,
162
    g: 239,
163
    b: 233,
164
    a: 255,
165
};
166
pub const LIGHT_PT: ColorU = ColorU {
167
    r: 228,
168
    g: 225,
169
    b: 218,
170
    a: 255,
171
};
172
pub const LIGHT_PB: ColorU = ColorU {
173
    r: 238,
174
    g: 236,
175
    b: 230,
176
    a: 255,
177
};
178
pub const LIGHT_FLD: ColorU = ColorU {
179
    r: 251,
180
    g: 250,
181
    b: 246,
182
    a: 255,
183
};
184
pub const LIGHT_FLD2: ColorU = ColorU {
185
    r: 239,
186
    g: 237,
187
    b: 231,
188
    a: 255,
189
};
190
pub const LIGHT_DISBG: ColorU = ColorU {
191
    r: 235,
192
    g: 233,
193
    b: 227,
194
    a: 255,
195
};
196
pub const LIGHT_DISTX: ColorU = ColorU {
197
    r: 163,
198
    g: 160,
199
    b: 153,
200
    a: 255,
201
};
202
pub const LIGHT_QT: ColorU = ColorU {
203
    r: 110,
204
    g: 99,
205
    b: 73,
206
    a: 255,
207
};
208
pub const LIGHT_QT2: ColorU = ColorU {
209
    r: 79,
210
    g: 70,
211
    b: 51,
212
    a: 255,
213
};
214
pub const LIGHT_ACC: ColorU = ColorU {
215
    r: 47,
216
    g: 74,
217
    b: 133,
218
    a: 255,
219
};
220
pub const LIGHT_DEEP: ColorU = ColorU {
221
    r: 30,
222
    g: 50,
223
    b: 96,
224
    a: 255,
225
};
226
pub const LIGHT_SOFT: ColorU = ColorU {
227
    r: 224,
228
    g: 228,
229
    b: 238,
230
    a: 255,
231
};
232
pub const LIGHT_GLOW: ColorU = ColorU {
233
    r: 122,
234
    g: 147,
235
    b: 198,
236
    a: 255,
237
};
238
pub const LIGHT_ON_ACC: ColorU = ColorU {
239
    r: 244,
240
    g: 242,
241
    b: 234,
242
    a: 255,
243
};
244

            
245
// Dark mode colors
246
pub const DARK_PG: ColorU = ColorU {
247
    r: 26,
248
    g: 26,
249
    b: 26,
250
    a: 255,
251
};
252
pub const DARK_SUR: ColorU = ColorU {
253
    r: 35,
254
    g: 35,
255
    b: 35,
256
    a: 255,
257
};
258
pub const DARK_DESK: ColorU = ColorU {
259
    r: 18,
260
    g: 18,
261
    b: 18,
262
    a: 255,
263
};
264
pub const DARK_STRIP: ColorU = ColorU {
265
    r: 31,
266
    g: 31,
267
    b: 31,
268
    a: 255,
269
};
270
pub const DARK_TRACK: ColorU = ColorU {
271
    r: 21,
272
    g: 21,
273
    b: 21,
274
    a: 255,
275
};
276
pub const DARK_BD: ColorU = ColorU {
277
    r: 63,
278
    g: 63,
279
    b: 63,
280
    a: 255,
281
};
282
pub const DARK_BD2: ColorU = ColorU {
283
    r: 74,
284
    g: 74,
285
    b: 74,
286
    a: 255,
287
};
288
pub const DARK_BD3: ColorU = ColorU {
289
    r: 97,
290
    g: 97,
291
    b: 97,
292
    a: 255,
293
};
294
pub const DARK_BD4: ColorU = ColorU {
295
    r: 52,
296
    g: 52,
297
    b: 52,
298
    a: 255,
299
};
300
pub const DARK_BD5: ColorU = ColorU {
301
    r: 16,
302
    g: 16,
303
    b: 16,
304
    a: 255,
305
};
306
pub const DARK_SEP: ColorU = ColorU {
307
    r: 56,
308
    g: 56,
309
    b: 56,
310
    a: 255,
311
};
312
pub const DARK_SEP2: ColorU = ColorU {
313
    r: 46,
314
    g: 46,
315
    b: 46,
316
    a: 255,
317
};
318
pub const DARK_INK: ColorU = ColorU {
319
    r: 231,
320
    g: 231,
321
    b: 231,
322
    a: 255,
323
};
324
pub const DARK_INK2: ColorU = ColorU {
325
    r: 220,
326
    g: 220,
327
    b: 220,
328
    a: 255,
329
};
330
pub const DARK_INTRO: ColorU = ColorU {
331
    r: 188,
332
    g: 188,
333
    b: 188,
334
    a: 255,
335
};
336
pub const DARK_SOFT1: ColorU = ColorU {
337
    r: 168,
338
    g: 168,
339
    b: 168,
340
    a: 255,
341
};
342
pub const DARK_SOFT2: ColorU = ColorU {
343
    r: 140,
344
    g: 140,
345
    b: 140,
346
    a: 255,
347
};
348
pub const DARK_SOFT3: ColorU = ColorU {
349
    r: 154,
350
    g: 154,
351
    b: 154,
352
    a: 255,
353
};
354
pub const DARK_ICON: ColorU = ColorU {
355
    r: 190,
356
    g: 190,
357
    b: 190,
358
    a: 255,
359
};
360
pub const DARK_RT: ColorU = ColorU {
361
    r: 51,
362
    g: 51,
363
    b: 51,
364
    a: 255,
365
};
366
pub const DARK_RB: ColorU = ColorU {
367
    r: 41,
368
    g: 41,
369
    b: 41,
370
    a: 255,
371
};
372
pub const DARK_HT: ColorU = ColorU {
373
    r: 63,
374
    g: 63,
375
    b: 63,
376
    a: 255,
377
};
378
pub const DARK_HB: ColorU = ColorU {
379
    r: 51,
380
    g: 51,
381
    b: 51,
382
    a: 255,
383
};
384
pub const DARK_PT: ColorU = ColorU {
385
    r: 31,
386
    g: 31,
387
    b: 31,
388
    a: 255,
389
};
390
pub const DARK_PB: ColorU = ColorU {
391
    r: 38,
392
    g: 38,
393
    b: 38,
394
    a: 255,
395
};
396
pub const DARK_FLD: ColorU = ColorU {
397
    r: 29,
398
    g: 29,
399
    b: 29,
400
    a: 255,
401
};
402
pub const DARK_FLD2: ColorU = ColorU {
403
    r: 36,
404
    g: 36,
405
    b: 36,
406
    a: 255,
407
};
408
pub const DARK_DISBG: ColorU = ColorU {
409
    r: 36,
410
    g: 36,
411
    b: 36,
412
    a: 255,
413
};
414
pub const DARK_DISTX: ColorU = ColorU {
415
    r: 102,
416
    g: 102,
417
    b: 102,
418
    a: 255,
419
};
420
pub const DARK_QT: ColorU = ColorU {
421
    r: 196,
422
    g: 181,
423
    b: 142,
424
    a: 255,
425
};
426
pub const DARK_QT2: ColorU = ColorU {
427
    r: 222,
428
    g: 211,
429
    b: 180,
430
    a: 255,
431
};
432
pub const DARK_ACC: ColorU = ColorU {
433
    r: 47,
434
    g: 74,
435
    b: 133,
436
    a: 255,
437
};
438
pub const DARK_DEEP: ColorU = ColorU {
439
    r: 30,
440
    g: 50,
441
    b: 96,
442
    a: 255,
443
};
444
pub const DARK_SOFT: ColorU = ColorU {
445
    r: 224,
446
    g: 228,
447
    b: 238,
448
    a: 255,
449
};
450
pub const DARK_GLOW: ColorU = ColorU {
451
    r: 122,
452
    g: 147,
453
    b: 198,
454
    a: 255,
455
};
456
pub const DARK_ON_ACC: ColorU = ColorU {
457
    r: 244,
458
    g: 242,
459
    b: 234,
460
    a: 255,
461
};
462

            
463
// ---------------------------------------------------------------------------
464
// GRADIENTS
465
// ---------------------------------------------------------------------------
466
//
467
// flora.css's `linear-gradient(..)` declarations, transcribed with the angles
468
// and stops the CSS has; an `rgba(..)` alpha is rounded the way the CSS parser
469
// rounds it, `(a * 255).round()`. Everything here is `const`: `LinearGradient`
470
// and its stop vec have const constructors, so a gradient can sit in a `const`
471
// style slice as well as in a runtime `vec!`. The stop slices are named consts
472
// rather than inline borrows because a gradient value has drop glue (its stop
473
// vec implements `Drop`), and a borrowed temporary of such a value is not
474
// promotable — the shape `tabs.rs` uses for the same reason.
475
//
476
// The `RT`/`RB`, `HT`/`HB` and `PT`/`PB` tokens above are the two stops of the
477
// raised, hovered and pressed control faces — that is what they exist for —
478
// and the six `*_FACE_*` gradients are those pairs. `flat.rs` keeps flat fills:
479
// its stop pairs are equal values, which is what lets the same widget code read
480
// correctly under both themes.
481
//
482
// Layer order: a `StyleBackgroundContentVec` is painted first-to-last, so a base
483
// colour goes FIRST and a translucent overlay after it — the reverse of a CSS
484
// comma list, whose first layer is on top.
485
//
486
// Not transcribed, and why:
487
// * `--fl-grain` / `--fl-fibre`: `repeating-linear-gradient` with PIXEL stops (`0 1px, transparent
488
//   1px 3px`); a stop here is a percentage.
489
// * `--fl-rolled-tab`: a `calc()` stop.
490
// * The `mask-image` gradients (not backgrounds) and the `.docs-card::after` sheen (a keyframe
491
//   animation).
492
// * `--fl-band`, `--fl-rolled`, `--fl-rule-metal-bg`, `--fl-gem-sunken`, the `.fl-tab-*` pieces and
493
//   the scrollbar thumb: tabs, ribbon rules and scrollbars have no theme function in this module
494
//   yet. They belong with the widget that gets one, not as consts nothing declares.
495

            
496
/// The CSS default direction: `linear-gradient(a, b)` with no angle runs top to
497
/// bottom.
498
const TO_BOTTOM: Direction = Direction::FromTo(DirectionCorners {
499
    dir_from: DirectionCorner::Top,
500
    dir_to: DirectionCorner::Bottom,
501
});
502

            
503
/// `<n>deg`, as the CSS writes an explicit angle.
504
const fn deg(degrees: isize) -> Direction {
505
    Direction::Angle(AngleValue::const_deg(degrees))
506
}
507

            
508
/// A colour stop at `offset` percent.
509
const fn stop(offset: isize, color: ColorU) -> NormalizedLinearColorStop {
510
    NormalizedLinearColorStop::new(PercentageValue::const_new(offset), color)
511
}
512

            
513
/// A `background` of these layers, painted first to last.
514
26
fn layers(list: Vec<StyleBackgroundContent>) -> CssProperty {
515
26
    CssProperty::BackgroundContent(StyleBackgroundContentVec::from_vec(list).into())
516
26
}
517

            
518
// -- the raised face --------------------------------------------------------
519

            
520
const RAISED_FACE_LIGHT_STOPS: &[NormalizedLinearColorStop] =
521
    &[stop(0, LIGHT_RT), stop(100, LIGHT_RB)];
522

            
523
/// `.btn-secondary`: raised paper, the standard command at rest.
524
///
525
/// `linear-gradient(var(--fl-rT), var(--fl-rB))` — [`LIGHT_RT`] over
526
/// [`LIGHT_RB`]. Also `.navbar`, `.btn-hero-secondary`, `.pill`, and the
527
/// `padding-box` layer of `.btn-hero-primary`, `.hero-badge` and `.pill-soon`.
528
pub const RAISED_FACE_LIGHT: StyleBackgroundContent =
529
    StyleBackgroundContent::LinearGradient(LinearGradient {
530
        direction: TO_BOTTOM,
531
        extend_mode: ExtendMode::Clamp,
532
        stops: NormalizedLinearColorStopVec::from_const_slice(RAISED_FACE_LIGHT_STOPS),
533
    });
534

            
535
const RAISED_FACE_DARK_STOPS: &[NormalizedLinearColorStop] =
536
    &[stop(0, DARK_RT), stop(100, DARK_RB)];
537

            
538
/// [`RAISED_FACE_LIGHT`] under `:root[data-theme="dark"]`, where the tokens
539
/// take their dark values. [`DARK_RT`] over [`DARK_RB`].
540
pub const RAISED_FACE_DARK: StyleBackgroundContent =
541
    StyleBackgroundContent::LinearGradient(LinearGradient {
542
        direction: TO_BOTTOM,
543
        extend_mode: ExtendMode::Clamp,
544
        stops: NormalizedLinearColorStopVec::from_const_slice(RAISED_FACE_DARK_STOPS),
545
    });
546

            
547
/// [`RAISED_FACE_LIGHT`] as a one-layer background, for `const` style slices.
548
const RAISED_FACE_LIGHT_LAYER: &[StyleBackgroundContent] = &[RAISED_FACE_LIGHT];
549

            
550
/// [`RAISED_FACE_DARK`] as a one-layer background, for `const` style slices.
551
const RAISED_FACE_DARK_LAYER: &[StyleBackgroundContent] = &[RAISED_FACE_DARK];
552

            
553
// -- the hovered face -------------------------------------------------------
554

            
555
const HOVER_FACE_LIGHT_STOPS: &[NormalizedLinearColorStop] =
556
    &[stop(0, LIGHT_HT), stop(100, LIGHT_HB)];
557

            
558
/// `.btn-secondary:hover`: the raised face lifted toward the light.
559
///
560
/// `linear-gradient(var(--fl-hT), var(--fl-hB))` — [`LIGHT_HT`] over
561
/// [`LIGHT_HB`]. Also `.nav-links a:hover`, `.btn-hero-secondary:hover` and
562
/// `.mobile-menu a:focus`.
563
pub const HOVER_FACE_LIGHT: StyleBackgroundContent =
564
    StyleBackgroundContent::LinearGradient(LinearGradient {
565
        direction: TO_BOTTOM,
566
        extend_mode: ExtendMode::Clamp,
567
        stops: NormalizedLinearColorStopVec::from_const_slice(HOVER_FACE_LIGHT_STOPS),
568
    });
569

            
570
const HOVER_FACE_DARK_STOPS: &[NormalizedLinearColorStop] = &[stop(0, DARK_HT), stop(100, DARK_HB)];
571

            
572
/// [`HOVER_FACE_LIGHT`] in dark mode. [`DARK_HT`] over [`DARK_HB`].
573
pub const HOVER_FACE_DARK: StyleBackgroundContent =
574
    StyleBackgroundContent::LinearGradient(LinearGradient {
575
        direction: TO_BOTTOM,
576
        extend_mode: ExtendMode::Clamp,
577
        stops: NormalizedLinearColorStopVec::from_const_slice(HOVER_FACE_DARK_STOPS),
578
    });
579

            
580
// -- the pressed face -------------------------------------------------------
581

            
582
const PRESSED_FACE_LIGHT_STOPS: &[NormalizedLinearColorStop] =
583
    &[stop(0, LIGHT_PT), stop(100, LIGHT_PB)];
584

            
585
/// `.btn-secondary:active`: the face pushed in.
586
///
587
/// `linear-gradient(var(--fl-pT), var(--fl-pB))` — [`LIGHT_PT`] over
588
/// [`LIGHT_PB`]: darker at the top, where the near lip shades it. Also
589
/// `.nav-links a:active`.
590
pub const PRESSED_FACE_LIGHT: StyleBackgroundContent =
591
    StyleBackgroundContent::LinearGradient(LinearGradient {
592
        direction: TO_BOTTOM,
593
        extend_mode: ExtendMode::Clamp,
594
        stops: NormalizedLinearColorStopVec::from_const_slice(PRESSED_FACE_LIGHT_STOPS),
595
    });
596

            
597
const PRESSED_FACE_DARK_STOPS: &[NormalizedLinearColorStop] =
598
    &[stop(0, DARK_PT), stop(100, DARK_PB)];
599

            
600
/// [`PRESSED_FACE_LIGHT`] in dark mode. [`DARK_PT`] over [`DARK_PB`].
601
pub const PRESSED_FACE_DARK: StyleBackgroundContent =
602
    StyleBackgroundContent::LinearGradient(LinearGradient {
603
        direction: TO_BOTTOM,
604
        extend_mode: ExtendMode::Clamp,
605
        stops: NormalizedLinearColorStopVec::from_const_slice(PRESSED_FACE_DARK_STOPS),
606
    });
607

            
608
// -- the stone: a coloured command's depth -----------------------------------
609
//
610
// flora.css draws its accent command as a stone and lays a "depth rig" over it
611
// in two pseudo-elements: `::after` shades the edges the light does not reach
612
// and `::before` rakes a specular streak across the face. Their fills are
613
// translucent, so they go OVER the command's own colour and work for any
614
// colour — which is how a Success or Danger button, a stone in its own colour,
615
// gets the same depth as the Primary one. The pseudo-elements' `opacity`
616
// (0.85 at rest, 1 on hover) is not a background property and is not
617
// reproduced; the stops are the CSS's own.
618

            
619
const STONE_RIG_TOP_STOPS: &[NormalizedLinearColorStop] = &[
620
    stop(0, ColorU::new(12, 10, 4, 87)),
621
    stop(32, ColorU::new(12, 10, 4, 0)),
622
];
623

            
624
/// `.btn-primary::after`, second layer: the shadow the top edge throws down
625
/// the face.
626
///
627
/// `linear-gradient(180deg, rgba(12, 10, 4, 0.34) 0%, rgba(12, 10, 4, 0) 32%)`.
628
pub const STONE_RIG_TOP: StyleBackgroundContent =
629
    StyleBackgroundContent::LinearGradient(LinearGradient {
630
        direction: deg(180),
631
        extend_mode: ExtendMode::Clamp,
632
        stops: NormalizedLinearColorStopVec::from_const_slice(STONE_RIG_TOP_STOPS),
633
    });
634

            
635
const STONE_RIG_LEFT_STOPS: &[NormalizedLinearColorStop] = &[
636
    stop(0, ColorU::new(12, 10, 4, 56)),
637
    stop(15, ColorU::new(12, 10, 4, 0)),
638
];
639

            
640
/// `.btn-primary::after`, third layer: the same shadow, off the left edge.
641
///
642
/// `linear-gradient(90deg, rgba(12, 10, 4, 0.22) 0%, rgba(12, 10, 4, 0) 15%)`.
643
pub const STONE_RIG_LEFT: StyleBackgroundContent =
644
    StyleBackgroundContent::LinearGradient(LinearGradient {
645
        direction: deg(90),
646
        extend_mode: ExtendMode::Clamp,
647
        stops: NormalizedLinearColorStopVec::from_const_slice(STONE_RIG_LEFT_STOPS),
648
    });
649

            
650
const STONE_STREAK_STOPS: &[NormalizedLinearColorStop] = &[
651
    stop(6, ColorU::new(255, 248, 215, 0)),
652
    stop(15, ColorU::new(255, 248, 215, 51)),
653
    stop(22, ColorU::new(255, 248, 215, 15)),
654
    stop(32, ColorU::new(255, 248, 215, 0)),
655
];
656

            
657
/// `.btn-primary::before`: the specular streak raked across the face at rest.
658
///
659
/// `linear-gradient(115deg, rgba(255, 248, 215, 0) 6%, rgba(255, 248, 215,
660
/// 0.20) 15%, rgba(255, 248, 215, 0.06) 22%, rgba(255, 248, 215, 0) 32%)`.
661
pub const STONE_STREAK: StyleBackgroundContent =
662
    StyleBackgroundContent::LinearGradient(LinearGradient {
663
        direction: deg(115),
664
        extend_mode: ExtendMode::Clamp,
665
        stops: NormalizedLinearColorStopVec::from_const_slice(STONE_STREAK_STOPS),
666
    });
667

            
668
const STONE_STREAK_HOVER_STOPS: &[NormalizedLinearColorStop] = &[
669
    stop(4, ColorU::new(255, 248, 215, 0)),
670
    stop(14, ColorU::new(255, 248, 215, 87)),
671
    stop(23, ColorU::new(255, 248, 215, 26)),
672
    stop(34, ColorU::new(255, 248, 215, 0)),
673
];
674

            
675
/// `.btn-primary:hover::before`: the streak, brighter and wider, as the face
676
/// turns toward the light.
677
///
678
/// `linear-gradient(115deg, rgba(255, 248, 215, 0) 4%, rgba(255, 248, 215,
679
/// 0.34) 14%, rgba(255, 248, 215, 0.10) 23%, rgba(255, 248, 215, 0) 34%)`.
680
pub const STONE_STREAK_HOVER: StyleBackgroundContent =
681
    StyleBackgroundContent::LinearGradient(LinearGradient {
682
        direction: deg(115),
683
        extend_mode: ExtendMode::Clamp,
684
        stops: NormalizedLinearColorStopVec::from_const_slice(STONE_STREAK_HOVER_STOPS),
685
    });
686

            
687
// The stone pressed. flora.css's sunken stone (`.nav-links a.active::after`,
688
// `.lang-grid button.active::after`) is "lit from below the near edge instead
689
// of above it: the light falls INTO the well, so the top edge darkens and the
690
// bottom lifts. That inversion is the whole difference between pressed and
691
// raised" — so it is what a coloured command shows while it is held down.
692

            
693
const SUNKEN_RIG_TOP_STOPS: &[NormalizedLinearColorStop] = &[
694
    stop(0, ColorU::new(8, 6, 2, 122)),
695
    stop(42, ColorU::new(8, 6, 2, 0)),
696
];
697

            
698
/// `.nav-links a.active::after`, second layer: the near lip's shadow, deeper
699
/// than the raised rig's.
700
///
701
/// `linear-gradient(180deg, rgba(8, 6, 2, 0.48) 0%, rgba(8, 6, 2, 0) 42%)`.
702
pub const SUNKEN_RIG_TOP: StyleBackgroundContent =
703
    StyleBackgroundContent::LinearGradient(LinearGradient {
704
        direction: deg(180),
705
        extend_mode: ExtendMode::Clamp,
706
        stops: NormalizedLinearColorStopVec::from_const_slice(SUNKEN_RIG_TOP_STOPS),
707
    });
708

            
709
const SUNKEN_RIG_LEFT_STOPS: &[NormalizedLinearColorStop] = &[
710
    stop(0, ColorU::new(8, 6, 2, 77)),
711
    stop(18, ColorU::new(8, 6, 2, 0)),
712
];
713

            
714
/// `.nav-links a.active::after`, third layer: the same shadow, off the left
715
/// edge.
716
///
717
/// `linear-gradient(90deg, rgba(8, 6, 2, 0.30) 0%, rgba(8, 6, 2, 0) 18%)`.
718
pub const SUNKEN_RIG_LEFT: StyleBackgroundContent =
719
    StyleBackgroundContent::LinearGradient(LinearGradient {
720
        direction: deg(90),
721
        extend_mode: ExtendMode::Clamp,
722
        stops: NormalizedLinearColorStopVec::from_const_slice(SUNKEN_RIG_LEFT_STOPS),
723
    });
724

            
725
const SUNKEN_RIG_BOTTOM_STOPS: &[NormalizedLinearColorStop] = &[
726
    stop(0, ColorU::new(255, 253, 238, 26)),
727
    stop(14, ColorU::new(255, 253, 238, 0)),
728
];
729

            
730
/// `.nav-links a.active::after`, fourth layer: the far wall's light lifting
731
/// the bottom edge.
732
///
733
/// `linear-gradient(0deg, rgba(255, 253, 238, 0.10) 0%, rgba(255, 253, 238, 0)
734
/// 14%)`.
735
pub const SUNKEN_RIG_BOTTOM: StyleBackgroundContent =
736
    StyleBackgroundContent::LinearGradient(LinearGradient {
737
        direction: deg(0),
738
        extend_mode: ExtendMode::Clamp,
739
        stops: NormalizedLinearColorStopVec::from_const_slice(SUNKEN_RIG_BOTTOM_STOPS),
740
    });
741

            
742
/// A coloured command is a stone: its own colour, then the rig
743
/// `.btn-primary::after` lays on it, then the streak of `::before`.
744
///
745
/// The colour is the base layer because layers paint first-to-last; the rig is
746
/// translucent, so the colour shows through it.
747
6
fn stone_face(color: ColorU, streak: StyleBackgroundContent) -> Vec<StyleBackgroundContent> {
748
6
    vec![
749
6
        StyleBackgroundContent::Color(color),
750
6
        STONE_RIG_TOP,
751
6
        STONE_RIG_LEFT,
752
6
        streak,
753
    ]
754
6
}
755

            
756
/// The stone held down: its pressed colour under the sunken rig.
757
4
fn sunken_stone_face(color: ColorU) -> Vec<StyleBackgroundContent> {
758
4
    vec![
759
4
        StyleBackgroundContent::Color(color),
760
4
        SUNKEN_RIG_TOP,
761
4
        SUNKEN_RIG_LEFT,
762
4
        SUNKEN_RIG_BOTTOM,
763
    ]
764
4
}
765

            
766
// -- the orb's gloss --------------------------------------------------------
767

            
768
const ORB_GLOSS_STOPS: &[NormalizedLinearColorStop] = &[
769
    stop(0, ColorU::new(255, 255, 255, 158)),
770
    stop(52, ColorU::new(255, 255, 255, 56)),
771
    stop(100, ColorU::new(255, 255, 255, 8)),
772
];
773

            
774
/// `.fl-orb-gloss`: "the specular cap. Hard along the top, dissolving at the
775
/// equator."
776
///
777
/// `linear-gradient(180deg, rgba(255, 255, 255, 0.62) 0%, rgba(255, 255, 255,
778
/// 0.22) 52%, rgba(255, 255, 255, 0.03) 100%)`. The CSS puts it on a separate
779
/// element covering the dome's upper half; here it spans the whole box it is
780
/// laid over, so it reads as a top-lit sheen on a domed knob.
781
pub const ORB_GLOSS: StyleBackgroundContent =
782
    StyleBackgroundContent::LinearGradient(LinearGradient {
783
        direction: deg(180),
784
        extend_mode: ExtendMode::Clamp,
785
        stops: NormalizedLinearColorStopVec::from_const_slice(ORB_GLOSS_STOPS),
786
    });
787

            
788
#[must_use]
789
6
pub fn button(btn: Button) -> Dom {
790
6
    let callbacks = match btn.on_click.into_option() {
791
        Some(ButtonOnClick {
792
            refany: data,
793
            callback,
794
        }) => vec![CoreCallbackData {
795
            event: EventFilter::Hover(HoverEventFilter::Click),
796
            callback: azul_core::callbacks::CoreCallback {
797
                cb: callback.cb as *const () as usize,
798
                ctx: callback.ctx,
799
            },
800
            refany: data,
801
        }],
802
6
        None => Vec::new(),
803
    };
804

            
805
6
    let btn_type = btn.button_type;
806
6
    let type_class = btn.button_type.class_name();
807
6
    let classes: Vec<IdOrClass> = vec![
808
6
        Class(AzString::from("__azul-native-button")),
809
6
        Class(AzString::from(type_class)),
810
6
        Class(AzString::from("__azul-theme-flora")),
811
    ];
812

            
813
6
    let mut button = Dom::create_node(NodeType::Button);
814

            
815
6
    let has_icon = !btn.icon.as_str().is_empty() || btn.icon_dom.is_some();
816
6
    let has_image = btn.image.is_some();
817
6
    let has_trailing_icon = !btn.trailing_icon.as_str().is_empty();
818

            
819
    // Resolved before `btn`'s fields are moved into the tree below.
820
6
    let btn_container_style = btn.resolved_container_style();
821
    // A caller who injected a container style (`Some`) chose every property in
822
    // it — resting face, dark colours and states included. The chrome widgets
823
    // hand in part styles complete with their own hover/pressed pairs, and
824
    // anything this theme appended after them would win the cascade (inline
825
    // resolution is last-match) and paint the theme's greys over the ribbon's
826
    // blue. So the theme adds to its OWN default only.
827
6
    let btn_owns_style = btn.container_style.as_ref().is_none();
828
6
    let btn_label_style = btn.resolved_label_style();
829
6
    let btn_image_style = btn.resolved_image_style();
830
6
    let btn_icon_style = btn.resolved_icon_style();
831
6
    let btn_trailing_icon_style = btn.resolved_trailing_icon_style();
832

            
833
6
    let a11y_name_src: String = if btn.label.as_str().is_empty() {
834
        if has_icon {
835
            btn.icon.as_str().to_string()
836
        } else {
837
            String::new()
838
        }
839
    } else {
840
6
        btn.label.as_str().to_string()
841
    };
842

            
843
6
    if has_icon {
844
        button = button.with_child(match btn.icon_dom.into_option() {
845
            Some(dom) => dom,
846
            None => Dom::create_icon(btn.icon).with_css_props(btn_icon_style),
847
        });
848
6
    }
849

            
850
6
    if let Some(image) = btn.image.into_option() {
851
        button = button.with_child(Dom::create_image(image).with_css_props(btn_image_style));
852
6
    }
853

            
854
6
    let skip_label = btn.label.as_str().is_empty() && (has_icon || has_image || has_trailing_icon);
855
6
    if !skip_label {
856
6
        button = button.with_child(
857
6
            crate::widgets::widget_p()
858
6
                .with_css_props(btn_label_style)
859
6
                .with_children(azul_core::dom::DomVec::from_vec(vec![
860
6
                    Dom::create_text_do_not_use_without_block_level_wrapper(btn.label),
861
6
                ])),
862
6
        );
863
6
    }
864

            
865
6
    if has_trailing_icon {
866
        button = button.with_child(
867
            Dom::create_icon(btn.trailing_icon).with_css_props(btn_trailing_icon_style),
868
        );
869
6
    }
870

            
871
6
    let a11y_name = a11y_name_src;
872
6
    let mut a11y = AccessibilityInfo {
873
6
        role: AccessibilityRole::PushButton,
874
6
        ..AccessibilityInfo::default()
875
6
    };
876
6
    if !a11y_name.is_empty() {
877
6
        a11y.accessibility_name = Some(AzString::from(a11y_name)).into();
878
6
    }
879

            
880
    // Add dark mode colors to container style
881
6
    let mut container_style: Vec<CssPropertyWithConditions> =
882
6
        btn_container_style.as_slice().to_vec();
883

            
884
6
    if btn_owns_style {
885
        // The resting face, in flora.css's terms. The standard command is raised
886
        // paper (`.btn-secondary`: `linear-gradient(var(--fl-rT), var(--fl-rB))`),
887
        // with its dark twin. A coloured command is a stone: its own colour in
888
        // both modes, under the depth rig and the streak the CSS lays on its
889
        // accent stone. The Link button has no surface and keeps what it had, the
890
        // dark surface included. It is part of the BASE: after the widget's flat
891
        // fill, which it wins over, and before the states, which win over it.
892
        {
893
            use crate::widgets::button::ButtonType;
894
6
            match btn_type {
895
2
                ButtonType::Default => {
896
2
                    container_style.push(CssPropertyWithConditions::simple(layers(vec![
897
2
                        RAISED_FACE_LIGHT,
898
2
                    ])));
899
2
                    container_style.push(CssPropertyWithConditions::dark_theme(layers(vec![
900
2
                        RAISED_FACE_DARK,
901
2
                    ])));
902
2
                }
903
2
                ButtonType::Link => {
904
2
                    container_style.push(CssPropertyWithConditions::dark_theme(layers(vec![
905
2
                        StyleBackgroundContent::Color(DARK_SUR),
906
2
                    ])));
907
2
                }
908
2
                _ => {
909
2
                    let (bg, _, _) = crate::widgets::button::get_button_colors(btn_type);
910
2
                    container_style.push(CssPropertyWithConditions::simple(layers(stone_face(
911
2
                        bg,
912
2
                        STONE_STREAK,
913
2
                    ))));
914
2
                }
915
            }
916
        }
917

            
918
        // Dark ink and dark borders belong to the NEUTRAL surface (raised
919
        // paper); a coloured stone keeps its own text and edge colours in
920
        // both modes, and the link has no face to border. Same rule as the
921
        // resting face above, from the one place it lives:
922
        // `ButtonType::surface`.
923
6
        if btn_type.surface() == crate::widgets::button::ButtonSurface::Neutral {
924
2
            container_style.push(CssPropertyWithConditions::dark_theme(
925
2
                CssProperty::TextColor(StyleTextColor { inner: DARK_INK }.into()),
926
2
            ));
927
2
            container_style.push(CssPropertyWithConditions::dark_theme(
928
2
                CssProperty::BorderTopColor(StyleBorderTopColor { inner: DARK_BD }.into()),
929
2
            ));
930
2
            container_style.push(CssPropertyWithConditions::dark_theme(
931
2
                CssProperty::BorderBottomColor(StyleBorderBottomColor { inner: DARK_BD }.into()),
932
2
            ));
933
2
            container_style.push(CssPropertyWithConditions::dark_theme(
934
2
                CssProperty::BorderLeftColor(StyleBorderLeftColor { inner: DARK_BD }.into()),
935
2
            ));
936
2
            container_style.push(CssPropertyWithConditions::dark_theme(
937
2
                CssProperty::BorderRightColor(StyleBorderRightColor { inner: DARK_BD }.into()),
938
2
            ));
939
4
        }
940

            
941
        // Here we could wrap the button in decorative DOM nodes for the skeumorphic flora look.
942
        // For now, we apply basic properties to test the theming engine.
943

            
944
        // The interactive states go LAST. Inline declarations resolve last-match
945
        // wins and a `dark_theme(..)` rule matches in every pseudo-state, so any
946
        // dark resting colour pushed after a `dark_on_hover` / `dark_on_focus` twin
947
        // would shadow it — no ring, no hover face, in dark mode.
948
6
        container_style.extend(button_states(btn_type));
949
    }
950

            
951
6
    button
952
6
        .with_css_props(CssPropertyWithConditionsVec::from_vec(container_style))
953
6
        .with_ids_and_classes(IdOrClassVec::from_vec(classes))
954
6
        .with_callbacks(callbacks.into())
955
6
        .with_tab_index(TabIndex::Auto)
956
6
        .with_accessibility_info(a11y)
957
6
}
958

            
959
use crate::widgets::check_box::CheckBox;
960

            
961
#[must_use]
962
pub fn check_box(cb: CheckBox) -> Dom {
963
    let cb_name = cb.accessibility_name.clone();
964
    crate::widgets::warn_widget_needs_a_name("check_box", cb_name.is_some());
965

            
966
    let checked_now = cb.check_box_state.inner.checked;
967

            
968
    use azul_core::{
969
        callbacks::{CoreCallback, CoreCallbackData},
970
        dom::{EventFilter, HoverEventFilter},
971
    };
972

            
973
    let mut container_style: Vec<CssPropertyWithConditions> =
974
        cb.resolved_container_style().as_slice().to_vec();
975
    container_style.push(CssPropertyWithConditions::dark_theme(
976
        CssProperty::BackgroundContent(
977
            StyleBackgroundContentVec::from_vec(vec![StyleBackgroundContent::Color(DARK_SUR)])
978
                .into(),
979
        ),
980
    ));
981
    let is_checked = cb.check_box_state.inner.checked;
982
    let mut content_style: Vec<CssPropertyWithConditions> =
983
        cb.resolved_content_style().as_slice().to_vec();
984
    if checked_now {
985
        content_style.push(CssPropertyWithConditions::dark_theme(
986
            CssProperty::BackgroundContent(
987
                StyleBackgroundContentVec::from_vec(vec![StyleBackgroundContent::Color(DARK_INK)])
988
                    .into(),
989
            ),
990
        ));
991
    }
992

            
993
    Dom::create_div()
994
        .with_ids_and_classes(IdOrClassVec::from(
995
            crate::widgets::check_box::CHECKBOX_CONTAINER_CLASS,
996
        ))
997
        .with_css_props(CssPropertyWithConditionsVec::from_vec(container_style))
998
        .with_callbacks(
999
            vec![CoreCallbackData {
                event: EventFilter::Hover(HoverEventFilter::Click),
                callback: CoreCallback {
                    cb: crate::widgets::check_box::input::default_on_checkbox_clicked as usize,
                    ctx: azul_core::refany::OptionRefAny::None,
                },
                refany: RefAny::new(cb.check_box_state),
            }]
            .into(),
        )
        .with_tab_index(TabIndex::Auto)
        .with_accessibility_info(AccessibilityInfo {
            role: AccessibilityRole::CheckButton,
            accessibility_name: cb_name,
            states: azul_core::a11y::AccessibilityStateVec::from_const_slice(if checked_now {
                &[azul_core::a11y::AccessibilityState::CheckedTrue]
            } else {
                &[azul_core::a11y::AccessibilityState::CheckedFalse]
            }),
            ..Default::default()
        })
        .with_children(
            vec![Dom::create_div()
                .with_ids_and_classes(IdOrClassVec::from(
                    crate::widgets::check_box::CHECKBOX_CONTENT_CLASS,
                ))
                .with_css_props(CssPropertyWithConditionsVec::from_vec(content_style))]
            .into(),
        )
}
use crate::widgets::text_input::{
    default_on_focus_lost, default_on_focus_received, default_on_mouse_hover,
    default_on_text_input, default_on_virtual_key_down, TextInput, TEXT_INPUT_CONTAINER_CLASS,
    TEXT_INPUT_LABEL_CLASS,
};
#[must_use]
1
pub fn text_input(mut ti: TextInput) -> Dom {
1
    let a11y_name: Option<AzString> = ti.text_input_state.inner.placeholder.as_ref().cloned();
1
    let a11y_value: String = ti
1
        .text_input_state
1
        .inner
1
        .text
1
        .as_ref()
1
        .iter()
1
        .filter_map(|c| core::char::from_u32(*c))
1
        .collect();
    use azul_core::{
        callbacks::{CoreCallback, CoreCallbackData},
        dom::{
            AttributeType, DomVec, EventFilter, FocusEventFilter, HoverEventFilter,
            IdOrClass::Class, TabIndex,
        },
    };
1
    ti.text_input_state.inner.cursor_pos = ti.text_input_state.inner.text.len();
1
    let label_text: String = ti
1
        .text_input_state
1
        .inner
1
        .text
1
        .iter()
1
        .filter_map(|s| core::char::from_u32(*s))
1
        .collect();
1
    let placeholder = ti
1
        .text_input_state
1
        .inner
1
        .placeholder
1
        .as_ref()
1
        .map(|s| s.as_str().to_string())
1
        .unwrap_or_default();
    // Resolved before `ti.text_input_state` is moved out below, and through the
    // widget's own resolver rather than a second copy of its default — the point
    // of the resolver is that flat and flora cannot drift on this answer.
1
    let resolved_container_style = ti.resolved_container_style();
1
    let resolved_label_style = ti.resolved_label_style();
1
    let state_ref = RefAny::new(ti.text_input_state);
1
    let mut container_style: Vec<CssPropertyWithConditions> =
1
        resolved_container_style.as_slice().to_vec();
1
    container_style.push(CssPropertyWithConditions::dark_theme(
1
        CssProperty::BackgroundContent(
1
            StyleBackgroundContentVec::from_vec(vec![StyleBackgroundContent::Color(DARK_SUR)])
1
                .into(),
1
        ),
    ));
1
    container_style.push(CssPropertyWithConditions::dark_theme(
1
        CssProperty::TextColor(StyleTextColor { inner: DARK_INK }.into()),
    ));
1
    container_style.push(CssPropertyWithConditions::dark_theme(
1
        CssProperty::BorderTopColor(StyleBorderTopColor { inner: DARK_BD }.into()),
    ));
1
    container_style.push(CssPropertyWithConditions::dark_theme(
1
        CssProperty::BorderBottomColor(StyleBorderBottomColor { inner: DARK_BD }.into()),
    ));
1
    container_style.push(CssPropertyWithConditions::dark_theme(
1
        CssProperty::BorderLeftColor(StyleBorderLeftColor { inner: DARK_BD }.into()),
    ));
1
    container_style.push(CssPropertyWithConditions::dark_theme(
1
        CssProperty::BorderRightColor(StyleBorderRightColor { inner: DARK_BD }.into()),
    ));
    // The interactive states the widget no longer declares. Appended LAST —
    // after the base style and after the theme's own dark resting colours —
    // because the last matching inline declaration wins: a `dark_theme` border
    // pushed after these would beat the dark hover/focus ring. One array so
    // half of them cannot ship.
1
    container_style.extend_from_slice(&FIELD_BORDER_STATES);
1
    let mut label_style: Vec<CssPropertyWithConditions> = resolved_label_style.as_slice().to_vec();
1
    label_style.push(CssPropertyWithConditions::dark_theme(
1
        CssProperty::TextColor(StyleTextColor { inner: DARK_INK }.into()),
    ));
1
    Dom::create_div()
1
        .with_ids_and_classes(vec![Class(TEXT_INPUT_CONTAINER_CLASS.into())].into())
1
        .with_css_props(CssPropertyWithConditionsVec::from_vec(container_style))
1
        .with_tab_index(TabIndex::Auto)
1
        .with_accessibility_info(AccessibilityInfo {
1
            role: AccessibilityRole::Text,
1
            accessibility_name: a11y_name.into(),
1
            accessibility_value: Some(AzString::from(a11y_value)).into(),
1
            ..Default::default()
1
        })
1
        .with_contenteditable(true)
1
        .with_dataset(Some(state_ref.clone()).into())
1
        .with_callbacks(
1
            vec![
1
                CoreCallbackData {
1
                    event: EventFilter::Focus(FocusEventFilter::FocusReceived),
1
                    refany: state_ref.clone(),
1
                    callback: CoreCallback {
1
                        cb: default_on_focus_received as usize,
1
                        ctx: azul_core::refany::OptionRefAny::None,
1
                    },
1
                },
1
                CoreCallbackData {
1
                    event: EventFilter::Focus(FocusEventFilter::FocusLost),
1
                    refany: state_ref.clone(),
1
                    callback: CoreCallback {
1
                        cb: default_on_focus_lost as usize,
1
                        ctx: azul_core::refany::OptionRefAny::None,
1
                    },
1
                },
1
                CoreCallbackData {
1
                    event: EventFilter::Focus(FocusEventFilter::TextInput),
1
                    refany: state_ref.clone(),
1
                    callback: CoreCallback {
1
                        cb: default_on_text_input as usize,
1
                        ctx: azul_core::refany::OptionRefAny::None,
1
                    },
1
                },
1
                CoreCallbackData {
1
                    event: EventFilter::Focus(FocusEventFilter::VirtualKeyDown),
1
                    refany: state_ref.clone(),
1
                    callback: CoreCallback {
1
                        cb: default_on_virtual_key_down as usize,
1
                        ctx: azul_core::refany::OptionRefAny::None,
1
                    },
1
                },
1
                CoreCallbackData {
1
                    event: EventFilter::Hover(HoverEventFilter::MouseOver),
1
                    refany: state_ref,
1
                    callback: CoreCallback {
1
                        cb: default_on_mouse_hover as usize,
1
                        ctx: azul_core::refany::OptionRefAny::None,
1
                    },
1
                },
            ]
1
            .into(),
        )
1
        .with_children(
1
            vec![crate::widgets::widget_p()
1
                .with_ids_and_classes(vec![Class(TEXT_INPUT_LABEL_CLASS.into())].into())
1
                .with_css_props(CssPropertyWithConditionsVec::from_vec(label_style))
1
                .with_attribute(AttributeType::Placeholder(placeholder.into()))
1
                .with_children(DomVec::from_vec(vec![
1
                    Dom::create_text_do_not_use_without_block_level_wrapper(label_text),
                ]))]
1
            .into(),
        )
1
}
#[must_use]
pub fn label(l: crate::widgets::label::Label) -> Dom {
    use azul_core::dom::{IdOrClass::Class, IdOrClassVec};
    use AzString;
    static LABEL_CLASS: &[IdOrClass] = &[Class(AzString::from_const_str("__azul-native-label"))];
    // Resolved before `l.string` is moved out below.
    let label_style = l.resolved_label_style();
    crate::widgets::widget_p_with_text(l.string)
        .with_ids_and_classes(IdOrClassVec::from_const_slice(LABEL_CLASS))
        .with_css_props(label_style)
}
#[must_use]
pub fn switch(s: crate::widgets::switch::Switch) -> Dom {
    let is_checked = s.switch_state.inner.checked;
    // Resolved up front: the knob's Dom is built after `s.switch_state` has
    // been moved into the callback's RefAny, and the resolver needs the whole
    // widget.
    let resolved_track_style = s.resolved_track_style();
    let resolved_knob_style = s.resolved_knob_style();
    use azul_core::{
        callbacks::{CoreCallback, CoreCallbackData},
        dom::{Dom, EventFilter, HoverEventFilter, IdOrClassVec, TabIndex},
    };
    let sw_name = s.accessibility_name.clone();
    crate::widgets::warn_widget_needs_a_name("switch", sw_name.is_some());
    let switch_checked = s.switch_state.inner.checked;
    Dom::create_div()
        .with_ids_and_classes(IdOrClassVec::from(
            crate::widgets::switch::SWITCH_TRACK_CLASS,
        ))
        .with_css_props(resolved_track_style.as_slice().to_vec().into())
        .with_callbacks(
            alloc::vec![CoreCallbackData {
                event: EventFilter::Hover(HoverEventFilter::Click),
                callback: CoreCallback {
                    cb: crate::widgets::switch::input::default_on_switch_clicked as usize,
                    ctx: azul_core::refany::OptionRefAny::None,
                },
                refany: RefAny::new(s.switch_state),
            }]
            .into(),
        )
        .with_tab_index(TabIndex::Auto)
        .with_accessibility_info(AccessibilityInfo {
            role: AccessibilityRole::CheckButton,
            accessibility_name: sw_name,
            states: azul_core::a11y::AccessibilityStateVec::from_vec(alloc::vec![
                if switch_checked {
                    azul_core::a11y::AccessibilityState::CheckedTrue
                } else {
                    azul_core::a11y::AccessibilityState::CheckedFalse
                },
            ]),
            ..Default::default()
        })
        .with_children(
            alloc::vec![Dom::create_div()
                .with_ids_and_classes(IdOrClassVec::from(
                    crate::widgets::switch::SWITCH_KNOB_CLASS
                ))
                .with_css_props(resolved_knob_style.as_slice().to_vec().into())]
            .into(),
        )
}
// -----------------------------------------------------------------------------
// PROGRESSBAR
// -----------------------------------------------------------------------------
#[must_use]
pub fn progressbar(bar: crate::widgets::progressbar::ProgressBar) -> Dom {
    let height = bar.height;
    let dataset = RefAny::new(crate::widgets::progressbar::ProgressBarLocalDataset { bar });
    Dom::create_virtual_view(
        dataset.clone(),
        azul_core::callbacks::VirtualViewCallback::create(progressbar_render_virtual_view),
    )
    .with_dataset(Some(dataset).into())
    .with_css_props(CssPropertyWithConditionsVec::from_vec(vec![
        CssPropertyWithConditions::simple(CssProperty::Height(LayoutHeightValue::Exact(
            LayoutHeight::Px(height),
        ))),
        CssPropertyWithConditions::simple(CssProperty::Width(LayoutWidthValue::Exact(
            LayoutWidth::Px(PixelValue::percent(100.0)),
        ))),
        CssPropertyWithConditions::simple(CssProperty::OverflowX(LayoutOverflowValue::Exact(
            LayoutOverflow::Hidden,
        ))),
        CssPropertyWithConditions::simple(CssProperty::OverflowY(LayoutOverflowValue::Exact(
            LayoutOverflow::Hidden,
        ))),
    ]))
}
/// 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]
pub fn progressbar_render_bar_impl(
    bar: crate::widgets::progressbar::ProgressBar,
    bounds_px: Option<(f32, f32)>,
) -> Dom {
    {
        use azul_core::dom::DomVec;
        let this = bar;
        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.
        let (bar_width, remaining_width) = match bounds_px {
            Some((w, _)) => {
                let inner = (w - 2.0).max(0.0);
                let filled = inner * percent_done / 100.0;
                (PixelValue::px(filled), PixelValue::px(inner - filled))
            }
            None => (
                PixelValue::percent(percent_done),
                PixelValue::percent(100.0 - percent_done),
            ),
        };
        let container_height = match bounds_px {
            Some((_, h)) => PixelValue::px((h - 2.0).max(0.0)),
            None => this.height,
        };
        let mut container_props = vec![
            // .__azul-native-progress-bar-container
            CssPropertyWithConditions::simple(CssProperty::Height(LayoutHeightValue::Exact(
                LayoutHeight::Px(container_height),
            ))),
            // `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).
            CssPropertyWithConditions::simple(CssProperty::Display(LayoutDisplayValue::Exact(
                LayoutDisplay::Flex,
            ))),
            CssPropertyWithConditions::simple(CssProperty::FlexDirection(
                LayoutFlexDirectionValue::Exact(LayoutFlexDirection::Row),
            )),
            CssPropertyWithConditions::simple(CssProperty::BoxShadowBottom(
                StyleBoxShadowValue::Exact(BoxOrStatic::heap(StyleBoxShadow {
                    offset_x: PixelValueNoPercent {
                        inner: PixelValue::const_px(0),
                    },
                    offset_y: PixelValueNoPercent {
                        inner: PixelValue::const_px(0),
                    },
                    color: ColorU {
                        r: 0,
                        g: 0,
                        b: 0,
                        a: 9,
                    },
                    blur_radius: PixelValueNoPercent {
                        inner: PixelValue::const_px(15),
                    },
                    spread_radius: PixelValueNoPercent {
                        inner: PixelValue::const_px(2),
                    },
                    clip_mode: BoxShadowClipMode::Inset,
                })),
            )),
            CssPropertyWithConditions::simple(CssProperty::BoxShadowTop(
                StyleBoxShadowValue::Exact(BoxOrStatic::heap(StyleBoxShadow {
                    offset_x: PixelValueNoPercent {
                        inner: PixelValue::const_px(0),
                    },
                    offset_y: PixelValueNoPercent {
                        inner: PixelValue::const_px(0),
                    },
                    color: ColorU {
                        r: 0,
                        g: 0,
                        b: 0,
                        a: 9,
                    },
                    blur_radius: PixelValueNoPercent {
                        inner: PixelValue::const_px(15),
                    },
                    spread_radius: PixelValueNoPercent {
                        inner: PixelValue::const_px(2),
                    },
                    clip_mode: BoxShadowClipMode::Inset,
                })),
            )),
            CssPropertyWithConditions::simple(CssProperty::BoxShadowRight(
                StyleBoxShadowValue::Exact(BoxOrStatic::heap(StyleBoxShadow {
                    offset_x: PixelValueNoPercent {
                        inner: PixelValue::const_px(0),
                    },
                    offset_y: PixelValueNoPercent {
                        inner: PixelValue::const_px(0),
                    },
                    color: ColorU {
                        r: 0,
                        g: 0,
                        b: 0,
                        a: 9,
                    },
                    blur_radius: PixelValueNoPercent {
                        inner: PixelValue::const_px(15),
                    },
                    spread_radius: PixelValueNoPercent {
                        inner: PixelValue::const_px(2),
                    },
                    clip_mode: BoxShadowClipMode::Inset,
                })),
            )),
            CssPropertyWithConditions::simple(CssProperty::BoxShadowLeft(
                StyleBoxShadowValue::Exact(BoxOrStatic::heap(StyleBoxShadow {
                    offset_x: PixelValueNoPercent {
                        inner: PixelValue::const_px(0),
                    },
                    offset_y: PixelValueNoPercent {
                        inner: PixelValue::const_px(0),
                    },
                    color: ColorU {
                        r: 0,
                        g: 0,
                        b: 0,
                        a: 9,
                    },
                    blur_radius: PixelValueNoPercent {
                        inner: PixelValue::const_px(15),
                    },
                    spread_radius: PixelValueNoPercent {
                        inner: PixelValue::const_px(2),
                    },
                    clip_mode: BoxShadowClipMode::Inset,
                })),
            )),
            CssPropertyWithConditions::simple(CssProperty::BorderBottomRightRadius(
                StyleBorderBottomRightRadiusValue::Exact(StyleBorderBottomRightRadius {
                    inner: PixelValue::const_px(3),
                }),
            )),
            CssPropertyWithConditions::simple(CssProperty::BorderBottomLeftRadius(
                StyleBorderBottomLeftRadiusValue::Exact(StyleBorderBottomLeftRadius {
                    inner: PixelValue::const_px(3),
                }),
            )),
            CssPropertyWithConditions::simple(CssProperty::BorderTopRightRadius(
                StyleBorderTopRightRadiusValue::Exact(StyleBorderTopRightRadius {
                    inner: PixelValue::const_px(3),
                }),
            )),
            CssPropertyWithConditions::simple(CssProperty::BorderTopLeftRadius(
                StyleBorderTopLeftRadiusValue::Exact(StyleBorderTopLeftRadius {
                    inner: PixelValue::const_px(3),
                }),
            )),
            CssPropertyWithConditions::simple(CssProperty::BorderBottomWidth(
                LayoutBorderBottomWidthValue::Exact(LayoutBorderBottomWidth {
                    inner: PixelValue::const_px(1),
                }),
            )),
            CssPropertyWithConditions::simple(CssProperty::BorderLeftWidth(
                LayoutBorderLeftWidthValue::Exact(LayoutBorderLeftWidth {
                    inner: PixelValue::const_px(1),
                }),
            )),
            CssPropertyWithConditions::simple(CssProperty::BorderRightWidth(
                LayoutBorderRightWidthValue::Exact(LayoutBorderRightWidth {
                    inner: PixelValue::const_px(1),
                }),
            )),
            CssPropertyWithConditions::simple(CssProperty::BorderTopWidth(
                LayoutBorderTopWidthValue::Exact(LayoutBorderTopWidth {
                    inner: PixelValue::const_px(1),
                }),
            )),
            CssPropertyWithConditions::simple(CssProperty::BorderBottomStyle(
                StyleBorderBottomStyleValue::Exact(StyleBorderBottomStyle {
                    inner: BorderStyle::Solid,
                }),
            )),
            CssPropertyWithConditions::simple(CssProperty::BorderLeftStyle(
                StyleBorderLeftStyleValue::Exact(StyleBorderLeftStyle {
                    inner: BorderStyle::Solid,
                }),
            )),
            CssPropertyWithConditions::simple(CssProperty::BorderRightStyle(
                StyleBorderRightStyleValue::Exact(StyleBorderRightStyle {
                    inner: BorderStyle::Solid,
                }),
            )),
            CssPropertyWithConditions::simple(CssProperty::BorderTopStyle(
                StyleBorderTopStyleValue::Exact(StyleBorderTopStyle {
                    inner: BorderStyle::Solid,
                }),
            )),
            CssPropertyWithConditions::simple(CssProperty::BorderBottomColor(
                StyleBorderBottomColorValue::Exact(StyleBorderBottomColor {
                    inner: ColorU {
                        r: 178,
                        g: 178,
                        b: 178,
                        a: 255,
                    },
                }),
            )),
            CssPropertyWithConditions::simple(CssProperty::BorderLeftColor(
                StyleBorderLeftColorValue::Exact(StyleBorderLeftColor {
                    inner: ColorU {
                        r: 178,
                        g: 178,
                        b: 178,
                        a: 255,
                    },
                }),
            )),
            CssPropertyWithConditions::simple(CssProperty::BorderRightColor(
                StyleBorderRightColorValue::Exact(StyleBorderRightColor {
                    inner: ColorU {
                        r: 178,
                        g: 178,
                        b: 178,
                        a: 255,
                    },
                }),
            )),
            CssPropertyWithConditions::simple(CssProperty::BorderTopColor(
                StyleBorderTopColorValue::Exact(StyleBorderTopColor {
                    inner: ColorU {
                        r: 178,
                        g: 178,
                        b: 178,
                        a: 255,
                    },
                }),
            )),
            CssPropertyWithConditions::simple(CssProperty::BackgroundContent(
                StyleBackgroundContentVecValue::Exact(this.container_background.clone()),
            )),
        ];
        if let Some((w, _)) = bounds_px {
            container_props.push(CssPropertyWithConditions::simple(CssProperty::Width(
                LayoutWidthValue::Exact(LayoutWidth::Px(PixelValue::px((w - 2.0).max(0.0)))),
            )));
        }
        Dom::create_div()
            .with_css_props(CssPropertyWithConditionsVec::from_vec(container_props))
            .with_ids_and_classes({
                const IDS_AND_CLASSES_10874511710181900075: &[IdOrClass] = &[Class(
                    AzString::from_const_str("__azul-native-progress-bar-container"),
                )];
                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.
            .with_accessibility_info(AccessibilityInfo {
                role: AccessibilityRole::ProgressBar,
                accessibility_value: Some(AzString::from(alloc::format!(
                    "{:.0}%",
                    // NaN clamps to NaN and would read "NaN%"; an unknown
                    // value announces as empty, like the bar it draws.
                    if percent_done.is_finite() { percent_done } else { 0.0 }
                )))
                .into(),
                ..Default::default()
            })
            .with_children(DomVec::from_vec(vec![
                Dom::create_div()
                    .with_css_props(CssPropertyWithConditionsVec::from_vec(vec![
                        // .__azul-native-progress-bar-bar
                        // Use percentage width instead of flex-grow hack
                        CssPropertyWithConditions::simple(CssProperty::Width(
                            LayoutWidthValue::Exact(LayoutWidth::Px(bar_width)),
                        )),
                        CssPropertyWithConditions::simple(CssProperty::BoxShadowBottom(
                            StyleBoxShadowValue::Exact(BoxOrStatic::heap(StyleBoxShadow {
                                offset_x: PixelValueNoPercent {
                                    inner: PixelValue::const_px(0),
                                },
                                offset_y: PixelValueNoPercent {
                                    inner: PixelValue::const_px(0),
                                },
                                color: ColorU {
                                    r: 0,
                                    g: 51,
                                    b: 0,
                                    a: 51,
                                },
                                blur_radius: PixelValueNoPercent {
                                    inner: PixelValue::const_px(15),
                                },
                                spread_radius: PixelValueNoPercent {
                                    inner: PixelValue::const_px(12),
                                },
                                clip_mode: BoxShadowClipMode::Inset,
                            })),
                        )),
                        CssPropertyWithConditions::simple(CssProperty::BoxShadowTop(
                            StyleBoxShadowValue::Exact(BoxOrStatic::heap(StyleBoxShadow {
                                offset_x: PixelValueNoPercent {
                                    inner: PixelValue::const_px(0),
                                },
                                offset_y: PixelValueNoPercent {
                                    inner: PixelValue::const_px(0),
                                },
                                color: ColorU {
                                    r: 0,
                                    g: 51,
                                    b: 0,
                                    a: 51,
                                },
                                blur_radius: PixelValueNoPercent {
                                    inner: PixelValue::const_px(15),
                                },
                                spread_radius: PixelValueNoPercent {
                                    inner: PixelValue::const_px(12),
                                },
                                clip_mode: BoxShadowClipMode::Inset,
                            })),
                        )),
                        CssPropertyWithConditions::simple(CssProperty::BoxShadowRight(
                            StyleBoxShadowValue::Exact(BoxOrStatic::heap(StyleBoxShadow {
                                offset_x: PixelValueNoPercent {
                                    inner: PixelValue::const_px(0),
                                },
                                offset_y: PixelValueNoPercent {
                                    inner: PixelValue::const_px(0),
                                },
                                color: ColorU {
                                    r: 0,
                                    g: 51,
                                    b: 0,
                                    a: 51,
                                },
                                blur_radius: PixelValueNoPercent {
                                    inner: PixelValue::const_px(15),
                                },
                                spread_radius: PixelValueNoPercent {
                                    inner: PixelValue::const_px(12),
                                },
                                clip_mode: BoxShadowClipMode::Inset,
                            })),
                        )),
                        CssPropertyWithConditions::simple(CssProperty::BoxShadowLeft(
                            StyleBoxShadowValue::Exact(BoxOrStatic::heap(StyleBoxShadow {
                                offset_x: PixelValueNoPercent {
                                    inner: PixelValue::const_px(0),
                                },
                                offset_y: PixelValueNoPercent {
                                    inner: PixelValue::const_px(0),
                                },
                                color: ColorU {
                                    r: 0,
                                    g: 51,
                                    b: 0,
                                    a: 51,
                                },
                                blur_radius: PixelValueNoPercent {
                                    inner: PixelValue::const_px(15),
                                },
                                spread_radius: PixelValueNoPercent {
                                    inner: PixelValue::const_px(12),
                                },
                                clip_mode: BoxShadowClipMode::Inset,
                            })),
                        )),
                        CssPropertyWithConditions::simple(CssProperty::BorderBottomRightRadius(
                            StyleBorderBottomRightRadiusValue::Exact(
                                StyleBorderBottomRightRadius {
                                    inner: PixelValue::const_px(1),
                                },
                            ),
                        )),
                        CssPropertyWithConditions::simple(CssProperty::BorderBottomLeftRadius(
                            StyleBorderBottomLeftRadiusValue::Exact(StyleBorderBottomLeftRadius {
                                inner: PixelValue::const_px(1),
                            }),
                        )),
                        CssPropertyWithConditions::simple(CssProperty::BorderTopRightRadius(
                            StyleBorderTopRightRadiusValue::Exact(StyleBorderTopRightRadius {
                                inner: PixelValue::const_px(1),
                            }),
                        )),
                        CssPropertyWithConditions::simple(CssProperty::BorderTopLeftRadius(
                            StyleBorderTopLeftRadiusValue::Exact(StyleBorderTopLeftRadius {
                                inner: PixelValue::const_px(1),
                            }),
                        )),
                        CssPropertyWithConditions::simple(CssProperty::BackgroundContent(
                            StyleBackgroundContentVecValue::Exact(this.bar_background),
                        )),
                    ]))
                    .with_ids_and_classes({
                        const IDS_AND_CLASSES_16512648314570682783: &[IdOrClass] = &[Class(
                            AzString::from_const_str("__azul-native-progress-bar-bar"),
                        )];
                        IdOrClassVec::from_const_slice(IDS_AND_CLASSES_16512648314570682783)
                    }),
                Dom::create_div()
                    .with_css_props(CssPropertyWithConditionsVec::from_vec(vec![
                        // .__azul-native-progress-bar-remaining
                        // Use percentage width for the remaining space
                        CssPropertyWithConditions::simple(CssProperty::Width(
                            LayoutWidthValue::Exact(LayoutWidth::Px(remaining_width)),
                        )),
                    ]))
                    .with_ids_and_classes({
                        const IDS_AND_CLASSES_2492405364126620395: &[IdOrClass] = &[Class(
                            AzString::from_const_str("__azul-native-progress-bar-remaining"),
                        )];
                        IdOrClassVec::from_const_slice(IDS_AND_CLASSES_2492405364126620395)
                    }),
            ]))
    }
}
#[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.
pub extern "C" fn progressbar_render_virtual_view(
    mut data: RefAny,
    info: VirtualViewCallbackInfo,
) -> VirtualViewReturn {
    let Some(state) = data.downcast_ref::<crate::widgets::progressbar::ProgressBarLocalDataset>()
    else {
        // Foreign payload: render nothing rather than lying about bounds.
        return VirtualViewReturn::default();
    };
    let size = info.bounds.get_logical_size();
    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`).
    VirtualViewReturn::with_dom(
        progressbar_render_bar_impl(state.bar.clone(), Some((size.width, size.height))),
        rect,
        rect,
    )
}
1
pub fn slider(slider: crate::widgets::slider::Slider) -> Dom {
1
    let value_now = slider.slider_state.inner.value;
1
    let a11y_name = slider.accessibility_name.clone();
1
    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.
1
    let resolved_track_style = slider.resolved_track_style();
1
    let resolved_thumb_style = slider.resolved_thumb_style();
1
    let state = RefAny::new(slider.slider_state);
1
    let mk = |event: EventFilter, cb: usize| CoreCallbackData {
8
        event,
8
        callback: CoreCallback {
8
            cb,
8
            ctx: OptionRefAny::None,
8
        },
8
        refany: state.clone(),
8
    };
1
    let callbacks = vec![
1
        mk(
1
            EventFilter::Hover(HoverEventFilter::MouseDown),
1
            crate::widgets::slider::on_slider_pointer_down as usize,
1
        ),
1
        mk(
1
            EventFilter::Hover(HoverEventFilter::MouseMove),
1
            crate::widgets::slider::on_slider_pointer_move as usize,
1
        ),
1
        mk(
1
            EventFilter::Hover(HoverEventFilter::MouseUp),
1
            crate::widgets::slider::on_slider_pointer_up as usize,
1
        ),
1
        mk(
1
            EventFilter::Focus(azul_core::events::FocusEventFilter::VirtualKeyDown),
1
            crate::widgets::slider::on_slider_key as usize,
1
        ),
1
        mk(
1
            EventFilter::Hover(HoverEventFilter::MouseLeave),
1
            crate::widgets::slider::on_slider_pointer_leave as usize,
1
        ),
1
        mk(
1
            EventFilter::Hover(HoverEventFilter::TouchStart),
1
            crate::widgets::slider::on_slider_pointer_down as usize,
1
        ),
1
        mk(
1
            EventFilter::Hover(HoverEventFilter::TouchMove),
1
            crate::widgets::slider::on_slider_pointer_move as usize,
1
        ),
1
        mk(
1
            EventFilter::Hover(HoverEventFilter::TouchEnd),
1
            crate::widgets::slider::on_slider_pointer_up as usize,
1
        ),
    ];
1
    let mut track_style = resolved_track_style.as_slice().to_vec();
1
    let mut thumb_style = resolved_thumb_style.as_slice().to_vec();
    // Flora specific. The rail stays a flat track: flora.css's `--fl-track` is
    // a flat token, and the rail has no border to hold a paler well against the
    // page. The thumb is the one domed control here, and the CSS caps its dome
    // with `.fl-orb-gloss`: laid OVER whatever colour the widget resolved for
    // the thumb — read back rather than restated, so it cannot drift from
    // slider.rs — and over the theme's accent in dark mode.
1
    track_style.push(CssPropertyWithConditions::dark_theme(
1
        CssProperty::BackgroundContent(
1
            StyleBackgroundContentVec::from_vec(vec![StyleBackgroundContent::Color(DARK_TRACK)])
1
                .into(),
1
        ),
    ));
1
    let mut thumb_layers: Vec<StyleBackgroundContent> = thumb_style
1
        .iter()
1
        .rev()
1
        .find_map(|p| match &p.property {
1
            CssProperty::BackgroundContent(b) if p.apply_if.as_ref().is_empty() => {
1
                b.get_property().map(|b| b.as_ref().to_vec())
            }
1
            _ => None,
2
        })
1
        .unwrap_or_default();
1
    thumb_layers.push(ORB_GLOSS);
1
    thumb_style.push(CssPropertyWithConditions::simple(layers(thumb_layers)));
1
    thumb_style.push(CssPropertyWithConditions::dark_theme(layers(vec![
1
        StyleBackgroundContent::Color(DARK_ACC),
1
        ORB_GLOSS,
    ])));
1
    Dom::create_div()
1
        .with_ids_and_classes(IdOrClassVec::from_vec(vec![Class(
1
            AzString::from_const_str("__azul-native-slider"),
1
        )]))
1
        .with_css_props(CssPropertyWithConditionsVec::from_vec(track_style))
1
        .with_callbacks(callbacks.into())
1
        .with_dataset(OptionRefAny::Some(state))
1
        .with_merge_callback(azul_core::dom::DatasetMergeCallback::from_ptr(
1
            crate::widgets::slider::merge_slider_state,
        ))
1
        .with_tab_index(TabIndex::Auto)
1
        .with_accessibility_info(AccessibilityInfo {
1
            role: AccessibilityRole::Slider,
1
            accessibility_name: a11y_name,
1
            accessibility_value: Some(AzString::from(alloc::format!("{value_now}"))).into(),
1
            ..Default::default()
1
        })
1
        .with_children(
1
            vec![Dom::create_div()
1
                .with_ids_and_classes(IdOrClassVec::from_vec(vec![Class(
1
                    AzString::from_const_str("__azul-native-slider-thumb"),
1
                )]))
1
                .with_css_props(CssPropertyWithConditionsVec::from_vec(thumb_style))]
1
            .into(),
        )
1
}
#[must_use]
pub fn text_area(mut ta: crate::widgets::text_area::TextArea) -> Dom {
    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,
    };
    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.
    let resolved_container_style = ta.resolved_container_style();
    let label_text: String = ta
        .text_area_state
        .inner
        .text
        .iter()
        .filter_map(|s| core::char::from_u32(*s))
        .collect();
    let placeholder = ta
        .text_area_state
        .inner
        .placeholder
        .as_ref()
        .map(|s| s.as_str().to_string())
        .unwrap_or_default();
    let state_ref = RefAny::new(ta.text_area_state);
    let mut container_style: Vec<CssPropertyWithConditions> =
        resolved_container_style.as_slice().to_vec();
    container_style.push(CssPropertyWithConditions::dark_theme(
        CssProperty::BackgroundContent(
            StyleBackgroundContentVec::from_vec(vec![StyleBackgroundContent::Color(DARK_SUR)])
                .into(),
        ),
    ));
    container_style.push(CssPropertyWithConditions::dark_theme(
        CssProperty::TextColor(StyleTextColor { inner: DARK_INK }.into()),
    ));
    container_style.push(CssPropertyWithConditions::dark_theme(
        CssProperty::BorderTopColor(StyleBorderTopColor { inner: DARK_BD }.into()),
    ));
    container_style.push(CssPropertyWithConditions::dark_theme(
        CssProperty::BorderBottomColor(StyleBorderBottomColor { inner: DARK_BD }.into()),
    ));
    container_style.push(CssPropertyWithConditions::dark_theme(
        CssProperty::BorderLeftColor(StyleBorderLeftColor { inner: DARK_BD }.into()),
    ));
    container_style.push(CssPropertyWithConditions::dark_theme(
        CssProperty::BorderRightColor(StyleBorderRightColor { inner: DARK_BD }.into()),
    ));
    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 => {
            crate::widgets::text_area::TEXT_AREA_LABEL_PROPS.to_vec()
        }
    };
    label_style.push(CssPropertyWithConditions::dark_theme(
        CssProperty::TextColor(StyleTextColor { inner: DARK_INK }.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.
    container_style.extend_from_slice(&FIELD_BORDER_STATES);
    Dom::create_div()
        .with_ids_and_classes(vec![Class("__azul-native-text-area-container".into())].into())
        .with_css_props(CssPropertyWithConditionsVec::from_vec(container_style))
        .with_tab_index(TabIndex::Auto)
        .with_accessibility_info(AccessibilityInfo {
            role: AccessibilityRole::Text,
            accessibility_name: ta_name.into(),
            ..Default::default()
        })
        .with_contenteditable(true)
        .with_dataset(Some(state_ref.clone()).into())
        .with_callbacks(
            vec![
                CoreCallbackData {
                    event: EventFilter::Focus(FocusEventFilter::FocusReceived),
                    refany: state_ref.clone(),
                    callback: azul_core::callbacks::CoreCallback {
                        cb: crate::widgets::text_area::default_on_focus_received as usize,
                        ctx: azul_core::refany::OptionRefAny::None,
                    },
                },
                CoreCallbackData {
                    event: EventFilter::Focus(FocusEventFilter::FocusLost),
                    refany: state_ref.clone(),
                    callback: azul_core::callbacks::CoreCallback {
                        cb: crate::widgets::text_area::default_on_focus_lost as usize,
                        ctx: azul_core::refany::OptionRefAny::None,
                    },
                },
                CoreCallbackData {
                    event: EventFilter::Focus(FocusEventFilter::TextInput),
                    refany: state_ref.clone(),
                    callback: azul_core::callbacks::CoreCallback {
                        cb: crate::widgets::text_area::default_on_text_input as usize,
                        ctx: azul_core::refany::OptionRefAny::None,
                    },
                },
                CoreCallbackData {
                    event: EventFilter::Focus(FocusEventFilter::VirtualKeyDown),
                    refany: state_ref,
                    callback: azul_core::callbacks::CoreCallback {
                        cb: crate::widgets::text_area::default_on_virtual_key_down as usize,
                        ctx: azul_core::refany::OptionRefAny::None,
                    },
                },
            ]
            .into(),
        )
        .with_children(
            vec![crate::widgets::widget_p()
                .with_ids_and_classes(vec![Class("__azul-native-text-area-label".into())].into())
                .with_css_props(CssPropertyWithConditionsVec::from_vec(label_style))
                .with_attribute(AttributeType::Placeholder(placeholder.into()))
                .with_children(DomVec::from_vec(vec![
                    Dom::create_text_do_not_use_without_block_level_wrapper(label_text),
                ]))]
            .into(),
        )
}
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);
const FLORA_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(6),
    )),
    CssPropertyWithConditions::simple(CssProperty::const_padding_right(
        LayoutPaddingRight::const_px(6),
    )),
    CssPropertyWithConditions::simple(CssProperty::const_padding_top(LayoutPaddingTop::const_px(
        4,
    ))),
    CssPropertyWithConditions::simple(CssProperty::const_padding_bottom(
        LayoutPaddingBottom::const_px(4),
    )),
    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_border_top_left_radius(
        StyleBorderTopLeftRadius::const_px(4),
    )),
    CssPropertyWithConditions::simple(CssProperty::const_border_top_right_radius(
        StyleBorderTopRightRadius::const_px(4),
    )),
    CssPropertyWithConditions::simple(CssProperty::const_border_bottom_left_radius(
        StyleBorderBottomLeftRadius::const_px(4),
    )),
    CssPropertyWithConditions::simple(CssProperty::const_border_bottom_right_radius(
        StyleBorderBottomRightRadius::const_px(4),
    )),
    CssPropertyWithConditions::simple(CssProperty::const_background_content(
        StyleBackgroundContentVec::from_const_slice(RAISED_FACE_LIGHT_LAYER),
    )),
    CssPropertyWithConditions::simple(CssProperty::const_text_color(StyleTextColor {
        inner: LIGHT_INK,
    })),
    CssPropertyWithConditions::simple(CssProperty::const_border_top_color(StyleBorderTopColor {
        inner: LIGHT_BD,
    })),
    CssPropertyWithConditions::simple(CssProperty::const_border_bottom_color(
        StyleBorderBottomColor { inner: LIGHT_BD },
    )),
    CssPropertyWithConditions::simple(CssProperty::const_border_left_color(StyleBorderLeftColor {
        inner: LIGHT_BD,
    })),
    CssPropertyWithConditions::simple(CssProperty::const_border_right_color(
        StyleBorderRightColor { inner: LIGHT_BD },
    )),
    CssPropertyWithConditions::dark_theme(CssProperty::const_background_content(
        StyleBackgroundContentVec::from_const_slice(RAISED_FACE_DARK_LAYER),
    )),
    CssPropertyWithConditions::dark_theme(CssProperty::const_text_color(StyleTextColor {
        inner: DARK_INK,
    })),
    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 },
    )),
];
const FLORA_DROPDOWN_LABEL_STYLE: &[CssPropertyWithConditions] = &[
    CssPropertyWithConditions::simple(CssProperty::const_flex_grow(LayoutFlexGrow::const_new(1))),
    CssPropertyWithConditions::simple(CssProperty::const_padding_right(
        LayoutPaddingRight::const_px(10),
    )),
    CssPropertyWithConditions::simple(CssProperty::const_text_color(StyleTextColor {
        inner: LIGHT_INK,
    })),
    CssPropertyWithConditions::dark_theme(CssProperty::const_text_color(StyleTextColor {
        inner: DARK_INK,
    })),
];
const FLORA_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_INK,
    })),
    CssPropertyWithConditions::dark_theme(CssProperty::const_text_color(StyleTextColor {
        inner: DARK_INK,
    })),
];
#[must_use]
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;
    let selected_label: Option<AzString> = dd
        .choices
        .as_ref()
        .get(dd.selected)
        .map(|o| AzString::from(o.as_str().to_string()));
    const DROPDOWN_CLASS: &[IdOrClass] =
        &[Class(AzString::from_const_str("__azul-native-dropdown"))];
    let selected_text = dd
        .choices
        .as_slice()
        .get(dd.selected)
        .cloned()
        .unwrap_or_else(|| AzString::from_const_str(""));
    let refany = RefAny::new(dd);
    Dom::create_div()
        .with_css_props(CssPropertyWithConditionsVec::from_const_slice(
            FLORA_DROPDOWN_WRAPPER_STYLE,
        ))
        .with_ids_and_classes(IdOrClassVec::from_const_slice(DROPDOWN_CLASS))
        .with_tab_index(TabIndex::Auto)
        .with_accessibility_info(AccessibilityInfo {
            role: AccessibilityRole::ComboBox,
            accessibility_value: selected_label.into(),
            ..Default::default()
        })
        .with_callbacks(
            vec![CoreCallbackData {
                event: EventFilter::Focus(FocusEventFilter::FocusReceived),
                refany,
                callback: CoreCallback {
                    cb: crate::widgets::drop_down::on_dropdown_click as usize,
                    ctx: azul_core::refany::OptionRefAny::None,
                },
            }]
            .into(),
        )
        .with_children(DomVec::from_vec(vec![
            crate::widgets::widget_p()
                .with_css_props(CssPropertyWithConditionsVec::from_const_slice(
                    FLORA_DROPDOWN_LABEL_STYLE,
                ))
                .with_children(DomVec::from_vec(vec![
                    Dom::create_text_do_not_use_without_block_level_wrapper(selected_text),
                ])),
            Dom::create_icon(AzString::from_const_str("arrow_drop_down")).with_css_props(
                CssPropertyWithConditionsVec::from_const_slice(FLORA_DROPDOWN_ARROW_STYLE),
            ),
        ]))
}
#[must_use]
pub fn avatar(a: crate::widgets::avatar::Avatar) -> Dom {
    use azul_core::dom::{Dom, IdOrClassVec};
    let size = a.size;
    let child = match a.image.into_option() {
        Some(image) => Dom::create_image(image)
            .with_ids_and_classes(IdOrClassVec::from_const_slice(
                crate::widgets::avatar::AVATAR_IMAGE_CLASS,
            ))
            .with_css_props(crate::widgets::avatar::build_image_style(size)),
        None => crate::widgets::widget_p_with_text(a.initials).with_ids_and_classes(
            IdOrClassVec::from_const_slice(crate::widgets::avatar::AVATAR_INITIALS_CLASS),
        ),
    };
    Dom::create_div()
        .with_ids_and_classes(IdOrClassVec::from_const_slice(
            crate::widgets::avatar::AVATAR_CLASS,
        ))
        .with_css_props(
            match a.avatar_style {
                azul_css::dynamic_selector::OptionCssPropertyWithConditionsVec::Some(style) => {
                    style.as_slice().to_vec()
                }
                azul_css::dynamic_selector::OptionCssPropertyWithConditionsVec::None => {
                    crate::widgets::avatar::build_avatar_style(size)
                        .as_slice()
                        .to_vec()
                }
            }
            .into(),
        )
        .with_children(alloc::vec![child].into())
}
// ---------------------------------------------------------------------------
// INTERACTIVE STATES
// ---------------------------------------------------------------------------
//
// The same vocabulary flat.rs declares, in flora's palette. Both themes expose
// these names so a widget's theme function reads identically in either.
/// The focus ring: the focused control's border takes the accent colour.
///
/// One const per edge — a border colour is four properties, and a 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 with a genuine
/// 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: LIGHT_ACC,
    }));
/// See [`FOCUS_BORDER_TOP`].
pub const FOCUS_BORDER_BOTTOM: CssPropertyWithConditions = CssPropertyWithConditions::on_focus(
    CssProperty::const_border_bottom_color(StyleBorderBottomColor { inner: LIGHT_ACC }),
);
/// See [`FOCUS_BORDER_TOP`].
pub const FOCUS_BORDER_LEFT: CssPropertyWithConditions = CssPropertyWithConditions::on_focus(
    CssProperty::const_border_left_color(StyleBorderLeftColor { inner: LIGHT_ACC }),
);
/// See [`FOCUS_BORDER_TOP`].
pub const FOCUS_BORDER_RIGHT: CssPropertyWithConditions = CssPropertyWithConditions::on_focus(
    CssProperty::const_border_right_color(StyleBorderRightColor { inner: LIGHT_ACC }),
);
/// 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 },
    ));
// ---------------------------------------------------------------------------
// 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: LIGHT_ACC,
    }));
/// See [`HOVER_BORDER_TOP`].
pub const HOVER_BORDER_BOTTOM: CssPropertyWithConditions = CssPropertyWithConditions::on_hover(
    CssProperty::const_border_bottom_color(StyleBorderBottomColor { inner: LIGHT_ACC }),
);
/// See [`HOVER_BORDER_TOP`].
pub const HOVER_BORDER_LEFT: CssPropertyWithConditions = CssPropertyWithConditions::on_hover(
    CssProperty::const_border_left_color(StyleBorderLeftColor { inner: LIGHT_ACC }),
);
/// See [`HOVER_BORDER_TOP`].
pub const HOVER_BORDER_RIGHT: CssPropertyWithConditions = CssPropertyWithConditions::on_hover(
    CssProperty::const_border_right_color(StyleBorderRightColor { inner: LIGHT_ACC }),
);
/// 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 coloured types' values come from [`crate::widgets::button::get_button_colors`],
/// so there is still one source of truth for them; the neutral type's faces and
/// every DARK half are chosen here, because this is the only place the palette
/// is in scope.
///
/// The rule differs by type on purpose:
///
/// * `Default` is the neutral paper button, so its surface belongs to the PAGE: it hovers and
///   presses to the theme's faces — [`HOVER_FACE_LIGHT`] / [`HOVER_FACE_DARK`] and
///   [`PRESSED_FACE_LIGHT`] / [`PRESSED_FACE_DARK`], the gradients flora.css draws for
///   `.btn-secondary:hover` and `:active`.
/// * 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. That colour is the base layer; over it goes the depth rig
///   flora.css lays on its accent stone (`.btn-primary::after` and `::before`), sunken while
///   pressed.
/// * `Link` has no surface at all: it underlines instead, in both modes.
#[must_use]
6
pub fn button_states(
6
    button_type: crate::widgets::button::ButtonType,
6
) -> Vec<CssPropertyWithConditions> {
    use crate::widgets::button::ButtonType;
6
    if button_type == ButtonType::Link {
2
        return alloc::vec![
2
            CssPropertyWithConditions::on_hover(CssProperty::TextDecoration(
2
                StyleTextDecoration::Underline.into(),
2
            )),
2
            CssPropertyWithConditions::dark_on_hover(CssProperty::TextDecoration(
2
                StyleTextDecoration::Underline.into(),
2
            )),
        ];
4
    }
4
    let (_, bg_hover, bg_active) = crate::widgets::button::get_button_colors(button_type);
4
    let neutral = button_type.surface() == crate::widgets::button::ButtonSurface::Neutral;
    // The neutral button is paper: it hovers and presses to the theme's faces,
    // each with its dark twin. A coloured button is a stone: the same colour in
    // both modes (see above), under the rig flora.css lays on a stone.
4
    let (hover, dark_hover, active, dark_active) = if neutral {
2
        (
2
            vec![HOVER_FACE_LIGHT],
2
            vec![HOVER_FACE_DARK],
2
            vec![PRESSED_FACE_LIGHT],
2
            vec![PRESSED_FACE_DARK],
2
        )
    } else {
2
        (
2
            stone_face(bg_hover, STONE_STREAK_HOVER),
2
            stone_face(bg_hover, STONE_STREAK_HOVER),
2
            sunken_stone_face(bg_active),
2
            sunken_stone_face(bg_active),
2
        )
    };
4
    let mut out = alloc::vec![
4
        CssPropertyWithConditions::on_hover(layers(hover)),
4
        CssPropertyWithConditions::dark_on_hover(layers(dark_hover)),
4
        CssPropertyWithConditions::on_active(layers(active)),
4
        CssPropertyWithConditions::dark_on_active(layers(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.
4
    if neutral {
2
        let light = ColorU::rgb(173, 181, 189);
8
        for (l, d) in [
2
            (
2
                CssProperty::const_border_top_color(StyleBorderTopColor { inner: light }),
2
                CssProperty::const_border_top_color(StyleBorderTopColor { inner: DARK_BD }),
2
            ),
2
            (
2
                CssProperty::const_border_bottom_color(StyleBorderBottomColor { inner: light }),
2
                CssProperty::const_border_bottom_color(StyleBorderBottomColor { inner: DARK_BD }),
2
            ),
2
            (
2
                CssProperty::const_border_left_color(StyleBorderLeftColor { inner: light }),
2
                CssProperty::const_border_left_color(StyleBorderLeftColor { inner: DARK_BD }),
2
            ),
2
            (
2
                CssProperty::const_border_right_color(StyleBorderRightColor { inner: light }),
2
                CssProperty::const_border_right_color(StyleBorderRightColor { inner: DARK_BD }),
2
            ),
8
        ] {
8
            out.push(CssPropertyWithConditions::on_hover(l));
8
            out.push(CssPropertyWithConditions::dark_on_hover(d));
8
        }
2
    }
    // The focus ring is the accent in both modes, and the consts already pair it.
4
    out.push(FOCUS_BORDER_TOP);
4
    out.push(FOCUS_BORDER_BOTTOM);
4
    out.push(FOCUS_BORDER_LEFT);
4
    out.push(FOCUS_BORDER_RIGHT);
4
    out.push(FOCUS_BORDER_TOP_DARK);
4
    out.push(FOCUS_BORDER_BOTTOM_DARK);
4
    out.push(FOCUS_BORDER_LEFT_DARK);
4
    out.push(FOCUS_BORDER_RIGHT_DARK);
4
    out
6
}
/// 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]
pub fn hover_bg_both(light: ColorU, dark: ColorU) -> [CssPropertyWithConditions; 2] {
    let bg = |c: ColorU| {
        CssProperty::const_background_content(StyleBackgroundContentVec::from_vec(alloc::vec![
            StyleBackgroundContent::Color(c),
        ]))
    };
    [
        CssPropertyWithConditions::on_hover(bg(light)),
        CssPropertyWithConditions::dark_on_hover(bg(dark)),
    ]
}
/// A pressed fill with BOTH halves chosen by the caller — see [`hover_bg_both`].
#[must_use]
pub fn active_bg_both(light: ColorU, dark: ColorU) -> [CssPropertyWithConditions; 2] {
    let bg = |c: ColorU| {
        CssProperty::const_background_content(StyleBackgroundContentVec::from_vec(alloc::vec![
            StyleBackgroundContent::Color(c),
        ]))
    };
    [
        CssPropertyWithConditions::on_active(bg(light)),
        CssPropertyWithConditions::dark_on_active(bg(dark)),
    ]
}
// The PHASE 2 anchor block below stays LAST (the plan's parallel-work
// contract), and the consts the other migrations place in it are items after
// this module — which is exactly what `items_after_test_module` objects to.
#[cfg(test)]
#[allow(clippy::items_after_test_module)]
mod gradient_tests {
    //! Phase 3 of `doc/WIDGET_THEME_MIGRATION.md`: the raised / hover / pressed
    //! faces are gradients built from the stop tokens, and the controls this
    //! module renders carry them — a gradient defined and never declared on a
    //! node would be the same smell as a token referenced only by itself.
    use azul_css::dynamic_selector::{DynamicSelector, PseudoStateType, ThemeCondition};
    use super::*;
    use crate::widgets::{
        button::{get_button_colors, Button, ButtonType},
        slider::Slider,
        themes::{OptionUiTheme, UiTheme},
    };
    /// `(offset %, colour)` per stop, in order. Panics on anything but a linear
    /// gradient of concrete colours, which every gradient here is.
14
    fn stops_of(bg: &StyleBackgroundContent) -> Vec<(PercentageValue, ColorU)> {
14
        let StyleBackgroundContent::LinearGradient(g) = bg else {
            panic!("not a linear gradient: {bg:?}");
        };
14
        g.stops
14
            .as_ref()
14
            .iter()
33
            .map(|s| match s.color {
33
                ColorOrSystem::Color(c) => (s.offset, c),
                ColorOrSystem::System(_) => panic!("a system colour in a transcribed stop"),
33
            })
14
            .collect()
14
    }
    /// The layer lists of every `background` declared on the root whose
    /// conditions satisfy `pred`, in declaration order — the last one wins.
16
    fn backgrounds_where(
16
        dom: &Dom,
16
        pred: impl Fn(&[DynamicSelector]) -> bool,
16
    ) -> Vec<Vec<StyleBackgroundContent>> {
16
        dom.root
16
            .style
16
            .iter_inline_properties()
650
            .filter(|(_, c)| pred(c.as_ref()))
111
            .filter_map(|(p, _)| match p {
17
                CssProperty::BackgroundContent(b) => b.get_property().map(|b| b.as_ref().to_vec()),
94
                _ => None,
111
            })
16
            .collect()
16
    }
    /// The resting background: the last unconditional declaration.
4
    fn resting_background(dom: &Dom) -> Option<Vec<StyleBackgroundContent>> {
4
        backgrounds_where(dom, <[DynamicSelector]>::is_empty).pop()
4
    }
    /// The dark-mode resting background: gated on the dark theme and nothing
    /// else — a `dark_on_hover` rule is not it.
3
    fn dark_resting_background(dom: &Dom) -> Option<Vec<StyleBackgroundContent>> {
109
        backgrounds_where(dom, |c| {
109
            matches!(c, [DynamicSelector::Theme(ThemeCondition::Dark)])
109
        })
3
        .pop()
3
    }
    /// The background for `state`, in light mode (`dark == false`: no theme
    /// condition) or dark mode (`dark == true`: the dark condition as well).
9
    fn state_background(
9
        dom: &Dom,
9
        state: PseudoStateType,
9
        dark: bool,
9
    ) -> Option<Vec<StyleBackgroundContent>> {
412
        backgrounds_where(dom, |c| {
412
            c.iter()
412
                .any(|s| matches!(s, DynamicSelector::PseudoState(st) if *st == state))
34
                && c.iter()
34
                    .any(|s| matches!(s, DynamicSelector::Theme(ThemeCondition::Dark)))
34
                    == dark
412
        })
9
        .pop()
9
    }
3
    fn flora_button(label: &'static str, ty: ButtonType) -> Button {
3
        let mut b = Button::with_type(AzString::from_const_str(label), ty);
3
        b.theme = OptionUiTheme::Some(UiTheme::Flora);
3
        b
3
    }
    #[test]
1
    fn each_face_is_a_two_stop_vertical_gradient_from_its_top_token_to_its_bottom_token() {
1
        let faces = [
1
            ("RAISED_FACE_LIGHT", RAISED_FACE_LIGHT, LIGHT_RT, LIGHT_RB),
1
            ("RAISED_FACE_DARK", RAISED_FACE_DARK, DARK_RT, DARK_RB),
1
            ("HOVER_FACE_LIGHT", HOVER_FACE_LIGHT, LIGHT_HT, LIGHT_HB),
1
            ("HOVER_FACE_DARK", HOVER_FACE_DARK, DARK_HT, DARK_HB),
1
            ("PRESSED_FACE_LIGHT", PRESSED_FACE_LIGHT, LIGHT_PT, LIGHT_PB),
1
            ("PRESSED_FACE_DARK", PRESSED_FACE_DARK, DARK_PT, DARK_PB),
1
        ];
7
        for (name, face, top, bottom) in faces {
6
            let StyleBackgroundContent::LinearGradient(g) = &face else {
                panic!("{name} is not a linear gradient: {face:?}");
            };
6
            assert_eq!(
                g.direction, TO_BOTTOM,
                "{name}: `linear-gradient(a, b)` has no angle, so it runs top to bottom"
            );
6
            assert_eq!(g.extend_mode, ExtendMode::Clamp, "{name}");
6
            assert_eq!(
6
                stops_of(&face),
6
                vec![
6
                    (PercentageValue::const_new(0), top),
6
                    (PercentageValue::const_new(100), bottom),
                ],
                "{name}: two stops, the top token then the bottom token"
            );
            // flat.rs's pairs are equal values; flora's are not, or the
            // "gradient" would be a flat fill with extra steps.
6
            assert_ne!(top, bottom, "{name}: a flora face has two different stops");
        }
1
    }
    #[test]
1
    fn the_overlays_never_hide_the_colour_beneath_them() {
        // The rig and the gloss are laid OVER a command's own colour, so every
        // stop must be translucent and each one must fade to nothing somewhere.
8
        for (name, overlay) in [
1
            ("STONE_RIG_TOP", STONE_RIG_TOP),
1
            ("STONE_RIG_LEFT", STONE_RIG_LEFT),
1
            ("STONE_STREAK", STONE_STREAK),
1
            ("STONE_STREAK_HOVER", STONE_STREAK_HOVER),
1
            ("SUNKEN_RIG_TOP", SUNKEN_RIG_TOP),
1
            ("SUNKEN_RIG_LEFT", SUNKEN_RIG_LEFT),
1
            ("SUNKEN_RIG_BOTTOM", SUNKEN_RIG_BOTTOM),
1
            ("ORB_GLOSS", ORB_GLOSS),
        ] {
8
            let stops = stops_of(&overlay);
8
            assert!(stops.len() >= 2, "{name}: fewer than two stops");
8
            assert!(
21
                stops.iter().all(|(_, c)| c.a < 255),
                "{name}: an opaque stop would hide the colour beneath: {stops:?}"
            );
8
            assert!(
14
                stops.iter().any(|(_, c)| c.a < 32),
                "{name}: an overlay fades out somewhere: {stops:?}"
            );
        }
1
    }
    #[test]
1
    fn a_flora_default_button_rests_on_the_raised_paper_face_in_both_modes() {
1
        let dom = button(Button::with_type(
1
            AzString::from_const_str("OK"),
1
            ButtonType::Default,
        ));
1
        let light = resting_background(&dom).expect("the button declares a resting background");
1
        assert!(
            matches!(
1
                light.first(),
                Some(StyleBackgroundContent::LinearGradient(_))
            ),
            "the resting face is a flat Color, not flora's raised paper: {light:?}"
        );
1
        assert_eq!(light, vec![RAISED_FACE_LIGHT]);
1
        assert_eq!(dark_resting_background(&dom), Some(vec![RAISED_FACE_DARK]));
1
    }
    #[test]
1
    fn the_rendered_flora_default_button_hovers_and_presses_to_the_face_gradients() {
        // `Button::dom` is the path that renders in the product: it builds its
        // own tree and appends `button_states` for the theme the button carries.
1
        let dom = flora_button("OK", ButtonType::Default).dom();
1
        assert_eq!(
1
            state_background(&dom, PseudoStateType::Hover, false),
1
            Some(vec![HOVER_FACE_LIGHT])
        );
1
        assert_eq!(
1
            state_background(&dom, PseudoStateType::Hover, true),
1
            Some(vec![HOVER_FACE_DARK])
        );
1
        assert_eq!(
1
            state_background(&dom, PseudoStateType::Active, false),
1
            Some(vec![PRESSED_FACE_LIGHT])
        );
1
        assert_eq!(
1
            state_background(&dom, PseudoStateType::Active, true),
1
            Some(vec![PRESSED_FACE_DARK])
        );
1
    }
    #[test]
1
    fn a_coloured_button_keeps_its_own_colour_as_the_base_layer_under_the_rig() {
1
        let (bg, bg_hover, bg_active) = get_button_colors(ButtonType::Primary);
1
        let dom = button(Button::with_type(
1
            AzString::from_const_str("Go"),
1
            ButtonType::Primary,
        ));
1
        assert_eq!(
1
            resting_background(&dom),
1
            Some(vec![
1
                StyleBackgroundContent::Color(bg),
1
                STONE_RIG_TOP,
1
                STONE_RIG_LEFT,
1
                STONE_STREAK,
1
            ]),
            "the stone's colour paints first; the rig and streak go over it"
        );
1
        assert_eq!(
1
            dark_resting_background(&dom),
            None,
            "a stone keeps its colour in dark mode, so it needs no dark override"
        );
1
        let dom = flora_button("Go", ButtonType::Primary).dom();
1
        let hovered = vec![
1
            StyleBackgroundContent::Color(bg_hover),
1
            STONE_RIG_TOP,
1
            STONE_RIG_LEFT,
1
            STONE_STREAK_HOVER,
        ];
1
        assert_eq!(
1
            state_background(&dom, PseudoStateType::Hover, false),
1
            Some(hovered.clone())
        );
1
        assert_eq!(
1
            state_background(&dom, PseudoStateType::Hover, true),
1
            Some(hovered)
        );
1
        let pressed = vec![
1
            StyleBackgroundContent::Color(bg_active),
1
            SUNKEN_RIG_TOP,
1
            SUNKEN_RIG_LEFT,
1
            SUNKEN_RIG_BOTTOM,
        ];
1
        assert_eq!(
1
            state_background(&dom, PseudoStateType::Active, false),
1
            Some(pressed.clone())
        );
1
        assert_eq!(
1
            state_background(&dom, PseudoStateType::Active, true),
1
            Some(pressed)
        );
1
    }
    #[test]
1
    fn the_link_button_has_no_surface_and_grows_no_face() {
1
        let dom = button(Button::with_type(
1
            AzString::from_const_str("more"),
1
            ButtonType::Link,
        ));
1
        assert_eq!(
1
            resting_background(&dom),
1
            Some(vec![StyleBackgroundContent::Color(ColorU::TRANSPARENT)]),
            "the widget's transparent fill is untouched"
        );
1
        let dom = flora_button("more", ButtonType::Link).dom();
1
        assert_eq!(
1
            state_background(&dom, PseudoStateType::Hover, false),
            None,
            "a link underlines on hover; it does not take a face"
        );
1
    }
    #[test]
1
    fn the_dropdown_wrapper_rests_on_the_raised_paper_face_in_both_modes() {
1
        let backgrounds: Vec<(bool, Vec<StyleBackgroundContent>)> = FLORA_DROPDOWN_WRAPPER_STYLE
1
            .iter()
35
            .filter_map(|p| match &p.property {
2
                CssProperty::BackgroundContent(b) => Some((
1
                    matches!(
2
                        p.apply_if.as_ref(),
2
                        [DynamicSelector::Theme(ThemeCondition::Dark)]
                    ),
2
                    b.get_property()?.as_ref().to_vec(),
                )),
33
                _ => None,
35
            })
1
            .collect();
1
        assert_eq!(
            backgrounds,
1
            vec![
1
                (false, vec![RAISED_FACE_LIGHT]),
1
                (true, vec![RAISED_FACE_DARK]),
            ]
        );
1
    }
    #[test]
1
    fn the_slider_thumb_wears_the_orb_gloss_over_its_own_colour() {
1
        let dom = slider(Slider::create(50.0, 0.0, 100.0));
1
        let thumb = &dom.children.as_ref()[0];
1
        let light = resting_background(thumb).expect("the thumb declares a background");
1
        assert!(
1
            matches!(light.first(), Some(StyleBackgroundContent::Color(_))),
            "the widget's own thumb colour is the base layer, read back rather than restated: \
             {light:?}"
        );
1
        assert_eq!(light.last(), Some(&ORB_GLOSS), "the gloss is the top layer");
1
        assert_eq!(light.len(), 2);
1
        assert_eq!(
1
            dark_resting_background(thumb),
1
            Some(vec![StyleBackgroundContent::Color(DARK_ACC), ORB_GLOSS]),
            "dark mode: the same cap over the theme's accent"
        );
1
    }
}
// ===========================================================================
// 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 ==
// == /STATES: list_view ==
//
//
//
// == STATES: tabs ==
// == /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 ==
// == /STATES: chrome ==
//
//
//