1
//! CSS properties for styling scrollbars.
2

            
3
use alloc::string::{String, ToString};
4
use crate::corety::AzString;
5

            
6
use crate::props::{
7
    basic::{
8
        color::{parse_css_color, ColorU, CssColorParseError, CssColorParseErrorOwned},
9
    },
10
    formatter::PrintAsCssValue,
11
    layout::{
12
        dimensions::LayoutWidth,
13
        spacing::{LayoutPaddingLeft, LayoutPaddingRight},
14
    },
15
    style::background::StyleBackgroundContent,
16
};
17

            
18
// ============================================================================
19
// CSS Standard Scroll Behavior Properties
20
// ============================================================================
21

            
22
/// CSS `scroll-behavior` property - controls smooth scrolling
23
/// https://developer.mozilla.org/en-US/docs/Web/CSS/scroll-behavior
24
#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Default)]
25
#[repr(C)]
26
pub enum ScrollBehavior {
27
    /// Scrolling jumps instantly to the final position
28
    #[default]
29
    Auto,
30
    /// Scrolling animates smoothly to the final position
31
    Smooth,
32
}
33

            
34
impl PrintAsCssValue for ScrollBehavior {
35
    fn print_as_css_value(&self) -> String {
36
        match self {
37
            Self::Auto => "auto".to_string(),
38
            Self::Smooth => "smooth".to_string(),
39
        }
40
    }
41
}
42

            
43
/// CSS `overscroll-behavior` property - controls overscroll effects
44
/// https://developer.mozilla.org/en-US/docs/Web/CSS/overscroll-behavior
45
#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Default)]
46
#[repr(C)]
47
pub enum OverscrollBehavior {
48
    /// Default scroll overflow behavior (bounce/glow effects, scroll chaining)
49
    #[default]
50
    Auto,
51
    /// Prevents scroll chaining to parent elements, but allows local overscroll effects
52
    Contain,
53
    /// No scroll chaining and no overscroll effects (hard stop at boundaries)
54
    None,
55
}
56

            
57
impl PrintAsCssValue for OverscrollBehavior {
58
    fn print_as_css_value(&self) -> String {
59
        match self {
60
            Self::Auto => "auto".to_string(),
61
            Self::Contain => "contain".to_string(),
62
            Self::None => "none".to_string(),
63
        }
64
    }
65
}
66

            
67
// ============================================================================
68
// Extended Scroll Configuration (Azul-specific)
69
// ============================================================================
70

            
71
/// Scroll physics configuration for momentum scrolling
72
///
73
/// This controls how scrolling feels - the "weight" and "friction" of the scroll.
74
/// Different platforms have different scroll physics (iOS vs Android vs Windows).
75
#[derive(Debug, Clone, PartialEq, PartialOrd)]
76
#[repr(C)]
77
pub struct ScrollPhysics {
78
    /// Smooth scroll animation duration in milliseconds (default: 300ms)
79
    /// Only used when scroll-behavior: smooth
80
    pub smooth_scroll_duration_ms: u32,
81

            
82
    /// Deceleration rate for momentum scrolling (0.0 = instant stop, 1.0 = never stops)
83
    /// Typical values: 0.95 (fast deceleration) to 0.998 (slow, iOS-like)
84
    /// Default: 0.95
85
    pub deceleration_rate: f32,
86

            
87
    /// Minimum velocity threshold to start momentum scrolling (pixels/second)
88
    /// Below this, scrolling stops immediately. Default: 50.0
89
    pub min_velocity_threshold: f32,
90

            
91
    /// Maximum scroll velocity (pixels/second). Default: 8000.0
92
    pub max_velocity: f32,
93

            
94
    /// Scroll wheel multiplier. Default: 1.0
95
    /// Values > 1.0 make scrolling faster, < 1.0 slower
96
    pub wheel_multiplier: f32,
97

            
98
    /// Whether to invert scroll direction (natural scrolling). Default: false
99
    pub invert_direction: bool,
100

            
101
    /// Overscroll elasticity (0.0 = no bounce, 1.0 = full bounce like iOS)
102
    /// Only applies when overscroll-behavior: auto. Default: 0.0 (no bounce)
103
    pub overscroll_elasticity: f32,
104

            
105
    /// Maximum overscroll distance in pixels before rubber-banding stops
106
    /// Default: 100.0
107
    pub max_overscroll_distance: f32,
108

            
109
    /// Bounce-back duration when releasing overscroll (milliseconds)
110
    /// Default: 400
111
    pub bounce_back_duration_ms: u32,
112

            
113
    /// Timer tick interval in milliseconds for the scroll physics timer.
114
    /// Should match the monitor refresh rate (e.g. 16ms for 60Hz, 8ms for 120Hz).
115
    /// Default: 16 (60 Hz)
116
    pub timer_interval_ms: u32,
117
}
118

            
119
impl Default for ScrollPhysics {
120
42
    fn default() -> Self {
121
42
        Self {
122
42
            smooth_scroll_duration_ms: 300,
123
42
            deceleration_rate: 0.95,
124
42
            min_velocity_threshold: 50.0,
125
42
            max_velocity: 8000.0,
126
42
            wheel_multiplier: 1.0,
127
42
            invert_direction: false,
128
42
            overscroll_elasticity: 0.0, // No bounce by default (Windows-like)
129
42
            max_overscroll_distance: 100.0,
130
42
            bounce_back_duration_ms: 400,
131
42
            timer_interval_ms: 16,
132
42
        }
133
42
    }
134
}
135

            
136
impl ScrollPhysics {
137
    /// iOS-like scroll physics with momentum and bounce
138
    pub const fn ios() -> Self {
139
        Self {
140
            smooth_scroll_duration_ms: 300,
141
            deceleration_rate: 0.998,
142
            min_velocity_threshold: 20.0,
143
            max_velocity: 8000.0,
144
            wheel_multiplier: 1.0,
145
            invert_direction: true, // Natural scrolling
146
            overscroll_elasticity: 0.5,
147
            max_overscroll_distance: 120.0,
148
            bounce_back_duration_ms: 500,
149
            timer_interval_ms: 16,
150
        }
151
    }
152

            
153
    /// macOS-like scroll physics
154
    pub const fn macos() -> Self {
155
        Self {
156
            smooth_scroll_duration_ms: 250,
157
            deceleration_rate: 0.997,
158
            min_velocity_threshold: 30.0,
159
            max_velocity: 6000.0,
160
            wheel_multiplier: 1.0,
161
            invert_direction: true, // Natural scrolling by default
162
            overscroll_elasticity: 0.3,
163
            max_overscroll_distance: 80.0,
164
            bounce_back_duration_ms: 400,
165
            timer_interval_ms: 16,
166
        }
167
    }
168

            
169
    /// Windows-like scroll physics (no momentum, no bounce)
170
    pub const fn windows() -> Self {
171
        Self {
172
            smooth_scroll_duration_ms: 200,
173
            deceleration_rate: 0.9,
174
            min_velocity_threshold: 100.0,
175
            max_velocity: 4000.0,
176
            wheel_multiplier: 1.0,
177
            invert_direction: false,
178
            overscroll_elasticity: 0.0,
179
            max_overscroll_distance: 0.0,
180
            bounce_back_duration_ms: 200,
181
            timer_interval_ms: 16,
182
        }
183
    }
184

            
185
    /// Android-like scroll physics
186
    pub const fn android() -> Self {
187
        Self {
188
            smooth_scroll_duration_ms: 250,
189
            deceleration_rate: 0.996,
190
            min_velocity_threshold: 40.0,
191
            max_velocity: 8000.0,
192
            wheel_multiplier: 1.0,
193
            invert_direction: false,
194
            overscroll_elasticity: 0.2, // Subtle glow effect
195
            max_overscroll_distance: 60.0,
196
            bounce_back_duration_ms: 300,
197
            timer_interval_ms: 16,
198
        }
199
    }
200
}
201

            
202
// ============================================================================
203
// Scrollbar Visibility Mode (CSS: -azul-scrollbar-visibility)
204
// ============================================================================
205

            
206
/// Controls when the scrollbar is displayed.
207
///
208
/// This is a per-element CSS property (`-azul-scrollbar-visibility`) that
209
/// determines the scrollbar presentation style. It interacts with the
210
/// OS-level `ScrollbarPreferences.visibility` (from System Preferences)
211
/// when set to `Auto`.
212
///
213
/// - `Always`: Classic, always-visible scrollbar (Chrome/Windows/Linux default).
214
///   Scrollbar reserves layout space.
215
/// - `WhenScrolling`: Overlay scrollbar that fades in on scroll activity
216
///   and fades out after a delay. Does not reserve layout space.
217
/// - `Auto`: Use the OS preference. On macOS this typically means `WhenScrolling`,
218
///   on Windows/Linux this typically means `Always`.
219
#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Default)]
220
#[repr(C)]
221
pub enum ScrollbarVisibilityMode {
222
    /// Scrollbar is always visible (Chrome/Windows/Linux default).
223
    /// Reserves layout space.
224
    #[default]
225
    Always,
226
    /// Scrollbar appears on scroll and fades out after inactivity.
227
    /// Does not reserve layout space (overlay).
228
    WhenScrolling,
229
    /// Use the OS-level scrollbar preference.
230
    Auto,
231
}
232

            
233
impl PrintAsCssValue for ScrollbarVisibilityMode {
234
    fn print_as_css_value(&self) -> String {
235
        match self {
236
            Self::Always => "always".to_string(),
237
            Self::WhenScrolling => "when-scrolling".to_string(),
238
            Self::Auto => "auto".to_string(),
239
        }
240
    }
241
}
242

            
243
// ============================================================================
244
// Scrollbar Fade Delay (CSS: -azul-scrollbar-fade-delay)
245
// ============================================================================
246

            
247
/// Time in milliseconds before the overlay scrollbar starts fading out.
248
///
249
/// A value of 0 means the scrollbar never fades (always visible).
250
/// Typical values: 500ms (macOS), 0ms (Windows).
251
///
252
/// CSS syntax: `-azul-scrollbar-fade-delay: 500ms;` or `-azul-scrollbar-fade-delay: 0;`
253
#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Default)]
254
#[repr(C)]
255
pub struct ScrollbarFadeDelay {
256
    /// Delay in milliseconds
257
    pub ms: u32,
258
}
259

            
260
impl ScrollbarFadeDelay {
261
    pub const fn new(ms: u32) -> Self { Self { ms } }
262
    pub const ZERO: Self = Self { ms: 0 };
263
}
264

            
265
impl PrintAsCssValue for ScrollbarFadeDelay {
266
    fn print_as_css_value(&self) -> String {
267
        if self.ms == 0 { "0".to_string() } else { format!("{}ms", self.ms) }
268
    }
269
}
270

            
271
// ============================================================================
272
// Scrollbar Fade Duration (CSS: -azul-scrollbar-fade-duration)
273
// ============================================================================
274

            
275
/// Duration in milliseconds of the scrollbar fade-out animation.
276
///
277
/// A value of 0 means instant disappearance (no animation).
278
/// Typical values: 200ms (macOS), 0ms (Windows).
279
///
280
/// CSS syntax: `-azul-scrollbar-fade-duration: 200ms;` or `-azul-scrollbar-fade-duration: 0;`
281
#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Default)]
282
#[repr(C)]
283
pub struct ScrollbarFadeDuration {
284
    /// Duration in milliseconds
285
    pub ms: u32,
286
}
287

            
288
impl ScrollbarFadeDuration {
289
    pub const fn new(ms: u32) -> Self { Self { ms } }
290
    pub const ZERO: Self = Self { ms: 0 };
291
}
292

            
293
impl PrintAsCssValue for ScrollbarFadeDuration {
294
    fn print_as_css_value(&self) -> String {
295
        if self.ms == 0 { "0".to_string() } else { format!("{}ms", self.ms) }
296
    }
297
}
298

            
299
// ============================================================================
300
// Per-node Overflow Scrolling Mode (CSS: -azul-overflow-scrolling)
301
// ============================================================================
302

            
303
/// Controls per-node rubber-banding / momentum scrolling behavior.
304
///
305
/// Analogous to `-webkit-overflow-scrolling` on iOS Safari.
306
///
307
/// - `Auto`: Use the global `ScrollPhysics` from `SystemStyle`. On platforms
308
///   with `overscroll_elasticity == 0.0` (e.g. Windows), this means no rubber-banding.
309
/// - `Touch`: Force momentum scrolling with rubber-banding on this node,
310
///   regardless of the global `ScrollPhysics` setting. Uses iOS-like elasticity
311
///   if the global elasticity is zero.
312
#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Default)]
313
#[repr(C)]
314
pub enum OverflowScrolling {
315
    /// Use the global scroll physics (platform default). No rubber-banding on Windows.
316
    #[default]
317
    Auto,
318
    /// Force rubber-banding / momentum scrolling on this node (like iOS/macOS).
319
    Touch,
320
}
321

            
322
impl PrintAsCssValue for OverflowScrolling {
323
    fn print_as_css_value(&self) -> String {
324
        match self {
325
            Self::Auto => "auto".to_string(),
326
            Self::Touch => "touch".to_string(),
327
        }
328
    }
329
}
330

            
331
// ============================================================================
332
// Standard Properties
333
// ============================================================================
334

            
335
/// Represents the standard `scrollbar-width` property.
336
#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
337
#[repr(C)]
338
#[derive(Default)]
339
pub enum LayoutScrollbarWidth {
340
    #[default]
341
    Auto,
342
    Thin,
343
    None,
344
}
345

            
346

            
347
impl PrintAsCssValue for LayoutScrollbarWidth {
348
    fn print_as_css_value(&self) -> String {
349
        match self {
350
            Self::Auto => "auto".to_string(),
351
            Self::Thin => "thin".to_string(),
352
            Self::None => "none".to_string(),
353
        }
354
    }
355
}
356

            
357
/// Wrapper struct for custom scrollbar colors (thumb and track)
358
#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
359
#[repr(C)]
360
pub struct ScrollbarColorCustom {
361
    pub thumb: ColorU,
362
    pub track: ColorU,
363
}
364

            
365
/// Represents the standard `scrollbar-color` property.
366
#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
367
#[repr(C, u8)]
368
#[derive(Default)]
369
pub enum StyleScrollbarColor {
370
    #[default]
371
    Auto,
372
    Custom(ScrollbarColorCustom),
373
}
374

            
375

            
376
impl PrintAsCssValue for StyleScrollbarColor {
377
    fn print_as_css_value(&self) -> String {
378
        match self {
379
            Self::Auto => "auto".to_string(),
380
            Self::Custom(c) => format!("{} {}", c.thumb.to_hash(), c.track.to_hash()),
381
        }
382
    }
383
}
384

            
385
// -- -webkit-prefixed Properties --
386

            
387
/// Holds info necessary for layouting / styling -webkit-scrollbar properties.
388
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
389
#[repr(C)]
390
pub struct ScrollbarInfo {
391
    /// Total width (or height for vertical scrollbars) of the scrollbar in pixels
392
    pub width: LayoutWidth,
393
    /// Padding of the scrollbar tracker, in pixels. The inner bar is `width - padding` pixels
394
    /// wide.
395
    pub padding_left: LayoutPaddingLeft,
396
    /// Padding of the scrollbar (right)
397
    pub padding_right: LayoutPaddingRight,
398
    /// Style of the scrollbar background
399
    /// (`-webkit-scrollbar` / `-webkit-scrollbar-track` / `-webkit-scrollbar-track-piece`
400
    /// combined)
401
    pub track: StyleBackgroundContent,
402
    /// Style of the scrollbar thumbs (the "up" / "down" arrows), (`-webkit-scrollbar-thumb`)
403
    pub thumb: StyleBackgroundContent,
404
    /// Styles the directional buttons on the scrollbar (`-webkit-scrollbar-button`)
405
    pub button: StyleBackgroundContent,
406
    /// If two scrollbars are present, addresses the (usually) bottom corner
407
    /// of the scrollable element, where two scrollbars might meet (`-webkit-scrollbar-corner`)
408
    pub corner: StyleBackgroundContent,
409
    /// Addresses the draggable resizing handle that appears above the
410
    /// `corner` at the bottom corner of some elements (`-webkit-resizer`)
411
    pub resizer: StyleBackgroundContent,
412
    /// Whether to clip the scrollbar to the container's border-radius.
413
    /// When true, if the container has rounded corners, the scrollbar will be
414
    /// clipped to those rounded corners instead of having rectangular edges.
415
    /// Default is false for classic scrollbars, true for overlay scrollbars.
416
    pub clip_to_container_border: bool,
417
    /// Scroll behavior for this scrollbar's container (auto or smooth)
418
    pub scroll_behavior: ScrollBehavior,
419
    /// Overscroll behavior for the X axis
420
    pub overscroll_behavior_x: OverscrollBehavior,
421
    /// Overscroll behavior for the Y axis  
422
    pub overscroll_behavior_y: OverscrollBehavior,
423
    /// Per-node overflow scrolling mode (`-azul-overflow-scrolling: auto | touch`)
424
    /// `Touch` forces rubber-banding on this node even when the global physics has no bounce.
425
    pub overflow_scrolling: OverflowScrolling,
426
}
427

            
428
impl Default for ScrollbarInfo {
429
    fn default() -> Self {
430
        SCROLLBAR_CLASSIC_LIGHT
431
    }
432
}
433

            
434
impl PrintAsCssValue for ScrollbarInfo {
435
    fn print_as_css_value(&self) -> String {
436
        // This is a custom format, not standard CSS
437
        format!(
438
            "width: {}; padding-left: {}; padding-right: {}; track: {}; thumb: {}; button: {}; \
439
             corner: {}; resizer: {}",
440
            self.width.print_as_css_value(),
441
            self.padding_left.print_as_css_value(),
442
            self.padding_right.print_as_css_value(),
443
            self.track.print_as_css_value(),
444
            self.thumb.print_as_css_value(),
445
            self.button.print_as_css_value(),
446
            self.corner.print_as_css_value(),
447
            self.resizer.print_as_css_value(),
448
        )
449
    }
450
}
451

            
452
/// Scrollbar style for both horizontal and vertical scrollbars.
453
#[derive(Debug, Default, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
454
#[repr(C)]
455
pub struct ScrollbarStyle {
456
    /// Horizontal scrollbar style, if any
457
    pub horizontal: ScrollbarInfo,
458
    /// Vertical scrollbar style, if any
459
    pub vertical: ScrollbarInfo,
460
}
461

            
462
impl PrintAsCssValue for ScrollbarStyle {
463
    fn print_as_css_value(&self) -> String {
464
        // This is a custom format, not standard CSS
465
        format!(
466
            "horz({}), vert({})",
467
            self.horizontal.print_as_css_value(),
468
            self.vertical.print_as_css_value()
469
        )
470
    }
471
}
472

            
473
// Formatting to Rust code
474
impl crate::format_rust_code::FormatAsRustCode for ScrollbarStyle {
475
    fn format_as_rust_code(&self, tabs: usize) -> String {
476
        let t = String::from("    ").repeat(tabs);
477
        let t1 = String::from("    ").repeat(tabs + 1);
478
        format!(
479
            "ScrollbarStyle {{\r\n{}horizontal: {},\r\n{}vertical: {},\r\n{}}}",
480
            t1,
481
            crate::format_rust_code::format_scrollbar_info(&self.horizontal, tabs + 1),
482
            t1,
483
            crate::format_rust_code::format_scrollbar_info(&self.vertical, tabs + 1),
484
            t,
485
        )
486
    }
487
}
488

            
489
impl crate::format_rust_code::FormatAsRustCode for LayoutScrollbarWidth {
490
    fn format_as_rust_code(&self, _tabs: usize) -> String {
491
        match self {
492
            LayoutScrollbarWidth::Auto => String::from("LayoutScrollbarWidth::Auto"),
493
            LayoutScrollbarWidth::Thin => String::from("LayoutScrollbarWidth::Thin"),
494
            LayoutScrollbarWidth::None => String::from("LayoutScrollbarWidth::None"),
495
        }
496
    }
497
}
498

            
499
impl crate::format_rust_code::FormatAsRustCode for StyleScrollbarColor {
500
    fn format_as_rust_code(&self, _tabs: usize) -> String {
501
        match self {
502
            StyleScrollbarColor::Auto => String::from("StyleScrollbarColor::Auto"),
503
            StyleScrollbarColor::Custom(c) => format!(
504
                "StyleScrollbarColor::Custom(ScrollbarColorCustom {{ thumb: {}, track: {} }})",
505
                crate::format_rust_code::format_color_value(&c.thumb),
506
                crate::format_rust_code::format_color_value(&c.track)
507
            ),
508
        }
509
    }
510
}
511

            
512
impl crate::format_rust_code::FormatAsRustCode for ScrollbarVisibilityMode {
513
    fn format_as_rust_code(&self, _tabs: usize) -> String {
514
        match self {
515
            ScrollbarVisibilityMode::Always => String::from("ScrollbarVisibilityMode::Always"),
516
            ScrollbarVisibilityMode::WhenScrolling => String::from("ScrollbarVisibilityMode::WhenScrolling"),
517
            ScrollbarVisibilityMode::Auto => String::from("ScrollbarVisibilityMode::Auto"),
518
        }
519
    }
520
}
521

            
522
impl crate::format_rust_code::FormatAsRustCode for ScrollbarFadeDelay {
523
    fn format_as_rust_code(&self, _tabs: usize) -> String {
524
        format!("ScrollbarFadeDelay::new({})", self.ms)
525
    }
526
}
527

            
528
impl crate::format_rust_code::FormatAsRustCode for ScrollbarFadeDuration {
529
    fn format_as_rust_code(&self, _tabs: usize) -> String {
530
        format!("ScrollbarFadeDuration::new({})", self.ms)
531
    }
532
}
533

            
534
// --- Final Computed Style ---
535

            
536
/// The final, resolved style for a scrollbar, after considering both
537
/// standard and -webkit- properties. This struct is intended for use by the layout engine.
538
#[derive(Debug, Clone, PartialEq)]
539
pub struct ComputedScrollbarStyle {
540
    /// The width of the scrollbar. `None` signifies `scrollbar-width: none`.
541
    pub width: Option<LayoutWidth>,
542
    /// The color of the scrollbar thumb. `None` means use UA default.
543
    pub thumb_color: Option<ColorU>,
544
    /// The color of the scrollbar track. `None` means use UA default.
545
    pub track_color: Option<ColorU>,
546
}
547

            
548
impl Default for ComputedScrollbarStyle {
549
    fn default() -> Self {
550
        let default_info = ScrollbarInfo::default();
551
        Self {
552
            width: Some(default_info.width), // Default width from UA/platform
553
            thumb_color: match default_info.thumb {
554
                StyleBackgroundContent::Color(c) => Some(c),
555
                _ => None,
556
            },
557
            track_color: match default_info.track {
558
                StyleBackgroundContent::Color(c) => Some(c),
559
                _ => None,
560
            },
561
        }
562
    }
563
}
564

            
565
// --- Default Style Constants ---
566

            
567
/// A classic light-themed scrollbar, similar to older Windows versions.
568
pub const SCROLLBAR_CLASSIC_LIGHT: ScrollbarInfo = ScrollbarInfo {
569
    width: LayoutWidth::Px(crate::props::basic::pixel::PixelValue::const_px(17)),
570
    padding_left: LayoutPaddingLeft {
571
        inner: crate::props::basic::pixel::PixelValue::const_px(2),
572
    },
573
    padding_right: LayoutPaddingRight {
574
        inner: crate::props::basic::pixel::PixelValue::const_px(2),
575
    },
576
    track: StyleBackgroundContent::Color(ColorU {
577
        r: 241,
578
        g: 241,
579
        b: 241,
580
        a: 255,
581
    }),
582
    thumb: StyleBackgroundContent::Color(ColorU {
583
        r: 193,
584
        g: 193,
585
        b: 193,
586
        a: 255,
587
    }),
588
    button: StyleBackgroundContent::Color(ColorU {
589
        r: 163,
590
        g: 163,
591
        b: 163,
592
        a: 255,
593
    }),
594
    corner: StyleBackgroundContent::Color(ColorU {
595
        r: 241,
596
        g: 241,
597
        b: 241,
598
        a: 255,
599
    }),
600
    resizer: StyleBackgroundContent::Color(ColorU {
601
        r: 241,
602
        g: 241,
603
        b: 241,
604
        a: 255,
605
    }),
606
    clip_to_container_border: false,
607
    scroll_behavior: ScrollBehavior::Auto,
608
    overscroll_behavior_x: OverscrollBehavior::Auto,
609
    overscroll_behavior_y: OverscrollBehavior::Auto,
610
    overflow_scrolling: OverflowScrolling::Auto,
611
};
612

            
613
/// A classic dark-themed scrollbar.
614
pub const SCROLLBAR_CLASSIC_DARK: ScrollbarInfo = ScrollbarInfo {
615
    width: LayoutWidth::Px(crate::props::basic::pixel::PixelValue::const_px(17)),
616
    padding_left: LayoutPaddingLeft {
617
        inner: crate::props::basic::pixel::PixelValue::const_px(2),
618
    },
619
    padding_right: LayoutPaddingRight {
620
        inner: crate::props::basic::pixel::PixelValue::const_px(2),
621
    },
622
    track: StyleBackgroundContent::Color(ColorU {
623
        r: 45,
624
        g: 45,
625
        b: 45,
626
        a: 255,
627
    }),
628
    thumb: StyleBackgroundContent::Color(ColorU {
629
        r: 100,
630
        g: 100,
631
        b: 100,
632
        a: 255,
633
    }),
634
    button: StyleBackgroundContent::Color(ColorU {
635
        r: 120,
636
        g: 120,
637
        b: 120,
638
        a: 255,
639
    }),
640
    corner: StyleBackgroundContent::Color(ColorU {
641
        r: 45,
642
        g: 45,
643
        b: 45,
644
        a: 255,
645
    }),
646
    resizer: StyleBackgroundContent::Color(ColorU {
647
        r: 45,
648
        g: 45,
649
        b: 45,
650
        a: 255,
651
    }),
652
    clip_to_container_border: false,
653
    scroll_behavior: ScrollBehavior::Auto,
654
    overscroll_behavior_x: OverscrollBehavior::Auto,
655
    overscroll_behavior_y: OverscrollBehavior::Auto,
656
    overflow_scrolling: OverflowScrolling::Auto,
657
};
658

            
659
/// A modern, thin, overlay scrollbar inspired by macOS (Light Theme).
660
pub const SCROLLBAR_MACOS_LIGHT: ScrollbarInfo = ScrollbarInfo {
661
    width: LayoutWidth::Px(crate::props::basic::pixel::PixelValue::const_px(8)),
662
    padding_left: LayoutPaddingLeft {
663
        inner: crate::props::basic::pixel::PixelValue::const_px(0),
664
    },
665
    padding_right: LayoutPaddingRight {
666
        inner: crate::props::basic::pixel::PixelValue::const_px(0),
667
    },
668
    track: StyleBackgroundContent::Color(ColorU::TRANSPARENT),
669
    thumb: StyleBackgroundContent::Color(ColorU {
670
        r: 0,
671
        g: 0,
672
        b: 0,
673
        a: 100,
674
    }), // semi-transparent black
675
    button: StyleBackgroundContent::Color(ColorU::TRANSPARENT),
676
    corner: StyleBackgroundContent::Color(ColorU::TRANSPARENT),
677
    resizer: StyleBackgroundContent::Color(ColorU::TRANSPARENT),
678
    clip_to_container_border: true, // Overlay scrollbars should clip to rounded borders
679
    scroll_behavior: ScrollBehavior::Smooth,
680
    overscroll_behavior_x: OverscrollBehavior::Auto,
681
    overscroll_behavior_y: OverscrollBehavior::Auto,
682
    overflow_scrolling: OverflowScrolling::Auto,
683
};
684

            
685
/// A modern, thin, overlay scrollbar inspired by macOS (Dark Theme).
686
pub const SCROLLBAR_MACOS_DARK: ScrollbarInfo = ScrollbarInfo {
687
    width: LayoutWidth::Px(crate::props::basic::pixel::PixelValue::const_px(8)),
688
    padding_left: LayoutPaddingLeft {
689
        inner: crate::props::basic::pixel::PixelValue::const_px(0),
690
    },
691
    padding_right: LayoutPaddingRight {
692
        inner: crate::props::basic::pixel::PixelValue::const_px(0),
693
    },
694
    track: StyleBackgroundContent::Color(ColorU::TRANSPARENT),
695
    thumb: StyleBackgroundContent::Color(ColorU {
696
        r: 255,
697
        g: 255,
698
        b: 255,
699
        a: 100,
700
    }), // semi-transparent white
701
    button: StyleBackgroundContent::Color(ColorU::TRANSPARENT),
702
    corner: StyleBackgroundContent::Color(ColorU::TRANSPARENT),
703
    resizer: StyleBackgroundContent::Color(ColorU::TRANSPARENT),
704
    clip_to_container_border: true, // Overlay scrollbars should clip to rounded borders
705
    scroll_behavior: ScrollBehavior::Smooth,
706
    overscroll_behavior_x: OverscrollBehavior::Auto,
707
    overscroll_behavior_y: OverscrollBehavior::Auto,
708
    overflow_scrolling: OverflowScrolling::Auto,
709
};
710

            
711
/// A modern scrollbar inspired by Windows 11 (Light Theme).
712
pub const SCROLLBAR_WINDOWS_LIGHT: ScrollbarInfo = ScrollbarInfo {
713
    width: LayoutWidth::Px(crate::props::basic::pixel::PixelValue::const_px(12)),
714
    padding_left: LayoutPaddingLeft {
715
        inner: crate::props::basic::pixel::PixelValue::const_px(0),
716
    },
717
    padding_right: LayoutPaddingRight {
718
        inner: crate::props::basic::pixel::PixelValue::const_px(0),
719
    },
720
    track: StyleBackgroundContent::Color(ColorU {
721
        r: 241,
722
        g: 241,
723
        b: 241,
724
        a: 255,
725
    }),
726
    thumb: StyleBackgroundContent::Color(ColorU {
727
        r: 130,
728
        g: 130,
729
        b: 130,
730
        a: 255,
731
    }),
732
    button: StyleBackgroundContent::Color(ColorU::TRANSPARENT),
733
    corner: StyleBackgroundContent::Color(ColorU::TRANSPARENT),
734
    resizer: StyleBackgroundContent::Color(ColorU::TRANSPARENT),
735
    clip_to_container_border: false,
736
    scroll_behavior: ScrollBehavior::Auto,
737
    overscroll_behavior_x: OverscrollBehavior::None,
738
    overscroll_behavior_y: OverscrollBehavior::None,
739
    overflow_scrolling: OverflowScrolling::Auto,
740
};
741

            
742
/// A modern scrollbar inspired by Windows 11 (Dark Theme).
743
pub const SCROLLBAR_WINDOWS_DARK: ScrollbarInfo = ScrollbarInfo {
744
    width: LayoutWidth::Px(crate::props::basic::pixel::PixelValue::const_px(12)),
745
    padding_left: LayoutPaddingLeft {
746
        inner: crate::props::basic::pixel::PixelValue::const_px(0),
747
    },
748
    padding_right: LayoutPaddingRight {
749
        inner: crate::props::basic::pixel::PixelValue::const_px(0),
750
    },
751
    track: StyleBackgroundContent::Color(ColorU {
752
        r: 32,
753
        g: 32,
754
        b: 32,
755
        a: 255,
756
    }),
757
    thumb: StyleBackgroundContent::Color(ColorU {
758
        r: 110,
759
        g: 110,
760
        b: 110,
761
        a: 255,
762
    }),
763
    button: StyleBackgroundContent::Color(ColorU::TRANSPARENT),
764
    corner: StyleBackgroundContent::Color(ColorU::TRANSPARENT),
765
    resizer: StyleBackgroundContent::Color(ColorU::TRANSPARENT),
766
    clip_to_container_border: false,
767
    scroll_behavior: ScrollBehavior::Auto,
768
    overscroll_behavior_x: OverscrollBehavior::None,
769
    overscroll_behavior_y: OverscrollBehavior::None,
770
    overflow_scrolling: OverflowScrolling::Auto,
771
};
772

            
773
/// A modern, thin, overlay scrollbar inspired by iOS (Light Theme).
774
pub const SCROLLBAR_IOS_LIGHT: ScrollbarInfo = ScrollbarInfo {
775
    width: LayoutWidth::Px(crate::props::basic::pixel::PixelValue::const_px(7)),
776
    padding_left: LayoutPaddingLeft {
777
        inner: crate::props::basic::pixel::PixelValue::const_px(0),
778
    },
779
    padding_right: LayoutPaddingRight {
780
        inner: crate::props::basic::pixel::PixelValue::const_px(0),
781
    },
782
    track: StyleBackgroundContent::Color(ColorU::TRANSPARENT),
783
    thumb: StyleBackgroundContent::Color(ColorU {
784
        r: 0,
785
        g: 0,
786
        b: 0,
787
        a: 102,
788
    }), // rgba(0,0,0,0.4)
789
    button: StyleBackgroundContent::Color(ColorU::TRANSPARENT),
790
    corner: StyleBackgroundContent::Color(ColorU::TRANSPARENT),
791
    resizer: StyleBackgroundContent::Color(ColorU::TRANSPARENT),
792
    clip_to_container_border: true, // Overlay scrollbars should clip to rounded borders
793
    scroll_behavior: ScrollBehavior::Smooth,
794
    overscroll_behavior_x: OverscrollBehavior::Auto,
795
    overscroll_behavior_y: OverscrollBehavior::Auto,
796
    overflow_scrolling: OverflowScrolling::Auto,
797
};
798

            
799
/// A modern, thin, overlay scrollbar inspired by iOS (Dark Theme).
800
pub const SCROLLBAR_IOS_DARK: ScrollbarInfo = ScrollbarInfo {
801
    width: LayoutWidth::Px(crate::props::basic::pixel::PixelValue::const_px(7)),
802
    padding_left: LayoutPaddingLeft {
803
        inner: crate::props::basic::pixel::PixelValue::const_px(0),
804
    },
805
    padding_right: LayoutPaddingRight {
806
        inner: crate::props::basic::pixel::PixelValue::const_px(0),
807
    },
808
    track: StyleBackgroundContent::Color(ColorU::TRANSPARENT),
809
    thumb: StyleBackgroundContent::Color(ColorU {
810
        r: 255,
811
        g: 255,
812
        b: 255,
813
        a: 102,
814
    }), // rgba(255,255,255,0.4)
815
    button: StyleBackgroundContent::Color(ColorU::TRANSPARENT),
816
    corner: StyleBackgroundContent::Color(ColorU::TRANSPARENT),
817
    resizer: StyleBackgroundContent::Color(ColorU::TRANSPARENT),
818
    clip_to_container_border: true, // Overlay scrollbars should clip to rounded borders
819
    scroll_behavior: ScrollBehavior::Smooth,
820
    overscroll_behavior_x: OverscrollBehavior::Auto,
821
    overscroll_behavior_y: OverscrollBehavior::Auto,
822
    overflow_scrolling: OverflowScrolling::Auto,
823
};
824

            
825
/// A modern, thin, overlay scrollbar inspired by Android (Light Theme).
826
pub const SCROLLBAR_ANDROID_LIGHT: ScrollbarInfo = ScrollbarInfo {
827
    width: LayoutWidth::Px(crate::props::basic::pixel::PixelValue::const_px(6)),
828
    padding_left: LayoutPaddingLeft {
829
        inner: crate::props::basic::pixel::PixelValue::const_px(0),
830
    },
831
    padding_right: LayoutPaddingRight {
832
        inner: crate::props::basic::pixel::PixelValue::const_px(0),
833
    },
834
    track: StyleBackgroundContent::Color(ColorU::TRANSPARENT),
835
    thumb: StyleBackgroundContent::Color(ColorU {
836
        r: 0,
837
        g: 0,
838
        b: 0,
839
        a: 102,
840
    }), // rgba(0,0,0,0.4)
841
    button: StyleBackgroundContent::Color(ColorU::TRANSPARENT),
842
    corner: StyleBackgroundContent::Color(ColorU::TRANSPARENT),
843
    resizer: StyleBackgroundContent::Color(ColorU::TRANSPARENT),
844
    clip_to_container_border: true, // Overlay scrollbars should clip to rounded borders
845
    scroll_behavior: ScrollBehavior::Smooth,
846
    overscroll_behavior_x: OverscrollBehavior::Contain,
847
    overscroll_behavior_y: OverscrollBehavior::Auto,
848
    overflow_scrolling: OverflowScrolling::Auto,
849
};
850

            
851
/// A modern, thin, overlay scrollbar inspired by Android (Dark Theme).
852
pub const SCROLLBAR_ANDROID_DARK: ScrollbarInfo = ScrollbarInfo {
853
    width: LayoutWidth::Px(crate::props::basic::pixel::PixelValue::const_px(6)),
854
    padding_left: LayoutPaddingLeft {
855
        inner: crate::props::basic::pixel::PixelValue::const_px(0),
856
    },
857
    padding_right: LayoutPaddingRight {
858
        inner: crate::props::basic::pixel::PixelValue::const_px(0),
859
    },
860
    track: StyleBackgroundContent::Color(ColorU::TRANSPARENT),
861
    thumb: StyleBackgroundContent::Color(ColorU {
862
        r: 255,
863
        g: 255,
864
        b: 255,
865
        a: 102,
866
    }), // rgba(255,255,255,0.4)
867
    button: StyleBackgroundContent::Color(ColorU::TRANSPARENT),
868
    corner: StyleBackgroundContent::Color(ColorU::TRANSPARENT),
869
    resizer: StyleBackgroundContent::Color(ColorU::TRANSPARENT),
870
    clip_to_container_border: true, // Overlay scrollbars should clip to rounded borders
871
    scroll_behavior: ScrollBehavior::Smooth,
872
    overscroll_behavior_x: OverscrollBehavior::Contain,
873
    overscroll_behavior_y: OverscrollBehavior::Auto,
874
    overflow_scrolling: OverflowScrolling::Auto,
875
};
876

            
877
// --- PARSERS ---
878

            
879
#[derive(Clone, PartialEq)]
880
pub enum LayoutScrollbarWidthParseError<'a> {
881
    InvalidValue(&'a str),
882
}
883
impl_debug_as_display!(LayoutScrollbarWidthParseError<'a>);
884
impl_display! { LayoutScrollbarWidthParseError<'a>, {
885
    InvalidValue(v) => format!("Invalid scrollbar-width value: \"{}\"", v),
886
}}
887

            
888
#[derive(Debug, Clone, PartialEq)]
889
#[repr(C, u8)]
890
pub enum LayoutScrollbarWidthParseErrorOwned {
891
    InvalidValue(AzString),
892
}
893
impl<'a> LayoutScrollbarWidthParseError<'a> {
894
    pub fn to_contained(&self) -> LayoutScrollbarWidthParseErrorOwned {
895
        match self {
896
            Self::InvalidValue(s) => {
897
                LayoutScrollbarWidthParseErrorOwned::InvalidValue(s.to_string().into())
898
            }
899
        }
900
    }
901
}
902
impl LayoutScrollbarWidthParseErrorOwned {
903
    pub fn to_shared<'a>(&'a self) -> LayoutScrollbarWidthParseError<'a> {
904
        match self {
905
            Self::InvalidValue(s) => LayoutScrollbarWidthParseError::InvalidValue(s.as_str()),
906
        }
907
    }
908
}
909

            
910
#[cfg(feature = "parser")]
911
4
pub fn parse_layout_scrollbar_width<'a>(
912
4
    input: &'a str,
913
4
) -> Result<LayoutScrollbarWidth, LayoutScrollbarWidthParseError<'a>> {
914
4
    match input.trim() {
915
4
        "auto" => Ok(LayoutScrollbarWidth::Auto),
916
3
        "thin" => Ok(LayoutScrollbarWidth::Thin),
917
2
        "none" => Ok(LayoutScrollbarWidth::None),
918
1
        _ => Err(LayoutScrollbarWidthParseError::InvalidValue(input)),
919
    }
920
4
}
921

            
922
#[derive(Clone, PartialEq)]
923
pub enum StyleScrollbarColorParseError<'a> {
924
    InvalidValue(&'a str),
925
    Color(CssColorParseError<'a>),
926
}
927
impl_debug_as_display!(StyleScrollbarColorParseError<'a>);
928
impl_display! { StyleScrollbarColorParseError<'a>, {
929
    InvalidValue(v) => format!("Invalid scrollbar-color value: \"{}\"", v),
930
    Color(e) => format!("Invalid color in scrollbar-color: {}", e),
931
}}
932
impl_from!(CssColorParseError<'a>, StyleScrollbarColorParseError::Color);
933

            
934
#[derive(Debug, Clone, PartialEq)]
935
#[repr(C, u8)]
936
pub enum StyleScrollbarColorParseErrorOwned {
937
    InvalidValue(AzString),
938
    Color(CssColorParseErrorOwned),
939
}
940
impl<'a> StyleScrollbarColorParseError<'a> {
941
    pub fn to_contained(&self) -> StyleScrollbarColorParseErrorOwned {
942
        match self {
943
            Self::InvalidValue(s) => {
944
                StyleScrollbarColorParseErrorOwned::InvalidValue(s.to_string().into())
945
            }
946
            Self::Color(e) => StyleScrollbarColorParseErrorOwned::Color(e.to_contained()),
947
        }
948
    }
949
}
950
impl StyleScrollbarColorParseErrorOwned {
951
    pub fn to_shared<'a>(&'a self) -> StyleScrollbarColorParseError<'a> {
952
        match self {
953
            Self::InvalidValue(s) => StyleScrollbarColorParseError::InvalidValue(s.as_str()),
954
            Self::Color(e) => StyleScrollbarColorParseError::Color(e.to_shared()),
955
        }
956
    }
957
}
958

            
959
#[cfg(feature = "parser")]
960
5
pub fn parse_style_scrollbar_color<'a>(
961
5
    input: &'a str,
962
5
) -> Result<StyleScrollbarColor, StyleScrollbarColorParseError<'a>> {
963
5
    let input = input.trim();
964
5
    if input == "auto" {
965
1
        return Ok(StyleScrollbarColor::Auto);
966
4
    }
967

            
968
4
    let mut parts = input.split_whitespace();
969
4
    let thumb_str = parts
970
4
        .next()
971
4
        .ok_or(StyleScrollbarColorParseError::InvalidValue(input))?;
972
4
    let track_str = parts
973
4
        .next()
974
4
        .ok_or(StyleScrollbarColorParseError::InvalidValue(input))?;
975

            
976
3
    if parts.next().is_some() {
977
1
        return Err(StyleScrollbarColorParseError::InvalidValue(input));
978
2
    }
979

            
980
2
    let thumb = parse_css_color(thumb_str)?;
981
2
    let track = parse_css_color(track_str)?;
982

            
983
2
    Ok(StyleScrollbarColor::Custom(ScrollbarColorCustom {
984
2
        thumb,
985
2
        track,
986
2
    }))
987
5
}
988

            
989
// --- Scrollbar Visibility Mode Parser ---
990

            
991
#[derive(Clone, PartialEq)]
992
pub enum ScrollbarVisibilityModeParseError<'a> {
993
    InvalidValue(&'a str),
994
}
995
impl_debug_as_display!(ScrollbarVisibilityModeParseError<'a>);
996
impl_display! { ScrollbarVisibilityModeParseError<'a>, {
997
    InvalidValue(v) => format!("Invalid scrollbar-visibility value: \"{}\"", v),
998
}}
999

            
#[derive(Debug, Clone, PartialEq)]
#[repr(C, u8)]
pub enum ScrollbarVisibilityModeParseErrorOwned {
    InvalidValue(AzString),
}
impl<'a> ScrollbarVisibilityModeParseError<'a> {
    pub fn to_contained(&self) -> ScrollbarVisibilityModeParseErrorOwned {
        match self {
            Self::InvalidValue(s) => ScrollbarVisibilityModeParseErrorOwned::InvalidValue(s.to_string().into()),
        }
    }
}
impl ScrollbarVisibilityModeParseErrorOwned {
    pub fn to_shared<'a>(&'a self) -> ScrollbarVisibilityModeParseError<'a> {
        match self {
            Self::InvalidValue(s) => ScrollbarVisibilityModeParseError::InvalidValue(s.as_str()),
        }
    }
}
#[cfg(feature = "parser")]
pub fn parse_scrollbar_visibility_mode<'a>(
    input: &'a str,
) -> Result<ScrollbarVisibilityMode, ScrollbarVisibilityModeParseError<'a>> {
    match input.trim() {
        "always" => Ok(ScrollbarVisibilityMode::Always),
        "when-scrolling" => Ok(ScrollbarVisibilityMode::WhenScrolling),
        "auto" => Ok(ScrollbarVisibilityMode::Auto),
        _ => Err(ScrollbarVisibilityModeParseError::InvalidValue(input)),
    }
}
// --- Scrollbar Fade Delay Parser ---
#[derive(Clone, PartialEq)]
pub enum ScrollbarFadeDelayParseError<'a> {
    InvalidValue(&'a str),
}
impl_debug_as_display!(ScrollbarFadeDelayParseError<'a>);
impl_display! { ScrollbarFadeDelayParseError<'a>, {
    InvalidValue(v) => format!("Invalid scrollbar-fade-delay value: \"{}\"", v),
}}
#[derive(Debug, Clone, PartialEq)]
#[repr(C, u8)]
pub enum ScrollbarFadeDelayParseErrorOwned {
    InvalidValue(AzString),
}
impl<'a> ScrollbarFadeDelayParseError<'a> {
    pub fn to_contained(&self) -> ScrollbarFadeDelayParseErrorOwned {
        match self {
            Self::InvalidValue(s) => ScrollbarFadeDelayParseErrorOwned::InvalidValue(s.to_string().into()),
        }
    }
}
impl ScrollbarFadeDelayParseErrorOwned {
    pub fn to_shared<'a>(&'a self) -> ScrollbarFadeDelayParseError<'a> {
        match self {
            Self::InvalidValue(s) => ScrollbarFadeDelayParseError::InvalidValue(s.as_str()),
        }
    }
}
#[cfg(feature = "parser")]
fn parse_time_ms(input: &str) -> Option<u32> {
    crate::props::basic::time::parse_duration(input).ok().map(|d| d.inner)
}
#[cfg(feature = "parser")]
pub fn parse_scrollbar_fade_delay<'a>(
    input: &'a str,
) -> Result<ScrollbarFadeDelay, ScrollbarFadeDelayParseError<'a>> {
    parse_time_ms(input)
        .map(ScrollbarFadeDelay::new)
        .ok_or(ScrollbarFadeDelayParseError::InvalidValue(input))
}
// --- Scrollbar Fade Duration Parser ---
#[derive(Clone, PartialEq)]
pub enum ScrollbarFadeDurationParseError<'a> {
    InvalidValue(&'a str),
}
impl_debug_as_display!(ScrollbarFadeDurationParseError<'a>);
impl_display! { ScrollbarFadeDurationParseError<'a>, {
    InvalidValue(v) => format!("Invalid scrollbar-fade-duration value: \"{}\"", v),
}}
#[derive(Debug, Clone, PartialEq)]
#[repr(C, u8)]
pub enum ScrollbarFadeDurationParseErrorOwned {
    InvalidValue(AzString),
}
impl<'a> ScrollbarFadeDurationParseError<'a> {
    pub fn to_contained(&self) -> ScrollbarFadeDurationParseErrorOwned {
        match self {
            Self::InvalidValue(s) => ScrollbarFadeDurationParseErrorOwned::InvalidValue(s.to_string().into()),
        }
    }
}
impl ScrollbarFadeDurationParseErrorOwned {
    pub fn to_shared<'a>(&'a self) -> ScrollbarFadeDurationParseError<'a> {
        match self {
            Self::InvalidValue(s) => ScrollbarFadeDurationParseError::InvalidValue(s.as_str()),
        }
    }
}
#[cfg(feature = "parser")]
pub fn parse_scrollbar_fade_duration<'a>(
    input: &'a str,
) -> Result<ScrollbarFadeDuration, ScrollbarFadeDurationParseError<'a>> {
    parse_time_ms(input)
        .map(ScrollbarFadeDuration::new)
        .ok_or(ScrollbarFadeDurationParseError::InvalidValue(input))
}
#[cfg(all(test, feature = "parser"))]
mod tests {
    use super::*;
    use crate::props::basic::color::ColorU;
    #[test]
1
    fn test_parse_scrollbar_width() {
1
        assert_eq!(
1
            parse_layout_scrollbar_width("auto").unwrap(),
            LayoutScrollbarWidth::Auto
        );
1
        assert_eq!(
1
            parse_layout_scrollbar_width("thin").unwrap(),
            LayoutScrollbarWidth::Thin
        );
1
        assert_eq!(
1
            parse_layout_scrollbar_width("none").unwrap(),
            LayoutScrollbarWidth::None
        );
1
        assert!(parse_layout_scrollbar_width("thick").is_err());
1
    }
    #[test]
1
    fn test_parse_scrollbar_color() {
1
        assert_eq!(
1
            parse_style_scrollbar_color("auto").unwrap(),
            StyleScrollbarColor::Auto
        );
1
        let custom = parse_style_scrollbar_color("red blue").unwrap();
1
        assert_eq!(
            custom,
            StyleScrollbarColor::Custom(ScrollbarColorCustom {
                thumb: ColorU::RED,
                track: ColorU::BLUE
            })
        );
1
        let custom_hex = parse_style_scrollbar_color("#ff0000 #0000ff").unwrap();
1
        assert_eq!(
            custom_hex,
            StyleScrollbarColor::Custom(ScrollbarColorCustom {
                thumb: ColorU::RED,
                track: ColorU::BLUE
            })
        );
1
        assert!(parse_style_scrollbar_color("red").is_err());
1
        assert!(parse_style_scrollbar_color("red blue green").is_err());
1
    }
}