1use alloc::string::{String, ToString};
4
5use crate::props::{
6 basic::{
7 color::{parse_css_color, ColorU, CssColorParseError, CssColorParseErrorOwned},
8 pixel::PixelValue,
9 },
10 formatter::PrintAsCssValue,
11 layout::{
12 dimensions::LayoutWidth,
13 spacing::{LayoutPaddingLeft, LayoutPaddingRight},
14 },
15 style::background::StyleBackgroundContent,
16};
17
18#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Default)]
25#[repr(C)]
26pub enum ScrollBehavior {
27 #[default]
29 Auto,
30 Smooth,
32}
33
34impl 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#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Default)]
46#[repr(C)]
47pub enum OverscrollBehavior {
48 #[default]
50 Auto,
51 Contain,
53 None,
55}
56
57impl 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#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Default)]
69#[repr(C)]
70pub struct OverscrollBehaviorXY {
71 pub x: OverscrollBehavior,
72 pub y: OverscrollBehavior,
73}
74
75#[derive(Debug, Clone, PartialEq, PartialOrd)]
84#[repr(C)]
85pub struct ScrollPhysics {
86 pub smooth_scroll_duration_ms: u32,
89
90 pub deceleration_rate: f32,
94
95 pub min_velocity_threshold: f32,
98
99 pub max_velocity: f32,
101
102 pub wheel_multiplier: f32,
105
106 pub invert_direction: bool,
108
109 pub overscroll_elasticity: f32,
112
113 pub max_overscroll_distance: f32,
116
117 pub bounce_back_duration_ms: u32,
120}
121
122impl Default for ScrollPhysics {
123 fn default() -> Self {
124 Self {
125 smooth_scroll_duration_ms: 300,
126 deceleration_rate: 0.95,
127 min_velocity_threshold: 50.0,
128 max_velocity: 8000.0,
129 wheel_multiplier: 1.0,
130 invert_direction: false,
131 overscroll_elasticity: 0.0, max_overscroll_distance: 100.0,
133 bounce_back_duration_ms: 400,
134 }
135 }
136}
137
138impl ScrollPhysics {
139 pub const fn ios() -> Self {
141 Self {
142 smooth_scroll_duration_ms: 300,
143 deceleration_rate: 0.998,
144 min_velocity_threshold: 20.0,
145 max_velocity: 8000.0,
146 wheel_multiplier: 1.0,
147 invert_direction: true, overscroll_elasticity: 0.5,
149 max_overscroll_distance: 120.0,
150 bounce_back_duration_ms: 500,
151 }
152 }
153
154 pub const fn macos() -> Self {
156 Self {
157 smooth_scroll_duration_ms: 250,
158 deceleration_rate: 0.997,
159 min_velocity_threshold: 30.0,
160 max_velocity: 6000.0,
161 wheel_multiplier: 1.0,
162 invert_direction: true, overscroll_elasticity: 0.3,
164 max_overscroll_distance: 80.0,
165 bounce_back_duration_ms: 400,
166 }
167 }
168
169 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 }
182 }
183
184 pub const fn android() -> Self {
186 Self {
187 smooth_scroll_duration_ms: 250,
188 deceleration_rate: 0.996,
189 min_velocity_threshold: 40.0,
190 max_velocity: 8000.0,
191 wheel_multiplier: 1.0,
192 invert_direction: false,
193 overscroll_elasticity: 0.2, max_overscroll_distance: 60.0,
195 bounce_back_duration_ms: 300,
196 }
197 }
198}
199
200#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
206#[repr(C)]
207pub enum LayoutScrollbarWidth {
208 Auto,
209 Thin,
210 None,
211}
212
213impl Default for LayoutScrollbarWidth {
214 fn default() -> Self {
215 Self::Auto
216 }
217}
218
219impl PrintAsCssValue for LayoutScrollbarWidth {
220 fn print_as_css_value(&self) -> String {
221 match self {
222 Self::Auto => "auto".to_string(),
223 Self::Thin => "thin".to_string(),
224 Self::None => "none".to_string(),
225 }
226 }
227}
228
229#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
231#[repr(C)]
232pub struct ScrollbarColorCustom {
233 pub thumb: ColorU,
234 pub track: ColorU,
235}
236
237#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
239#[repr(C, u8)]
240pub enum StyleScrollbarColor {
241 Auto,
242 Custom(ScrollbarColorCustom),
243}
244
245impl Default for StyleScrollbarColor {
246 fn default() -> Self {
247 Self::Auto
248 }
249}
250
251impl PrintAsCssValue for StyleScrollbarColor {
252 fn print_as_css_value(&self) -> String {
253 match self {
254 Self::Auto => "auto".to_string(),
255 Self::Custom(c) => format!("{} {}", c.thumb.to_hash(), c.track.to_hash()),
256 }
257 }
258}
259
260#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
264#[repr(C)]
265pub struct ScrollbarInfo {
266 pub width: LayoutWidth,
268 pub padding_left: LayoutPaddingLeft,
271 pub padding_right: LayoutPaddingRight,
273 pub track: StyleBackgroundContent,
277 pub thumb: StyleBackgroundContent,
279 pub button: StyleBackgroundContent,
281 pub corner: StyleBackgroundContent,
284 pub resizer: StyleBackgroundContent,
287 pub clip_to_container_border: bool,
292 pub scroll_behavior: ScrollBehavior,
294 pub overscroll_behavior_x: OverscrollBehavior,
296 pub overscroll_behavior_y: OverscrollBehavior,
298}
299
300impl Default for ScrollbarInfo {
301 fn default() -> Self {
302 SCROLLBAR_CLASSIC_LIGHT
303 }
304}
305
306impl PrintAsCssValue for ScrollbarInfo {
307 fn print_as_css_value(&self) -> String {
308 format!(
310 "width: {}; padding-left: {}; padding-right: {}; track: {}; thumb: {}; button: {}; \
311 corner: {}; resizer: {}",
312 self.width.print_as_css_value(),
313 self.padding_left.print_as_css_value(),
314 self.padding_right.print_as_css_value(),
315 self.track.print_as_css_value(),
316 self.thumb.print_as_css_value(),
317 self.button.print_as_css_value(),
318 self.corner.print_as_css_value(),
319 self.resizer.print_as_css_value(),
320 )
321 }
322}
323
324#[derive(Debug, Default, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
326#[repr(C)]
327pub struct ScrollbarStyle {
328 pub horizontal: ScrollbarInfo,
330 pub vertical: ScrollbarInfo,
332}
333
334impl PrintAsCssValue for ScrollbarStyle {
335 fn print_as_css_value(&self) -> String {
336 format!(
338 "horz({}), vert({})",
339 self.horizontal.print_as_css_value(),
340 self.vertical.print_as_css_value()
341 )
342 }
343}
344
345impl crate::format_rust_code::FormatAsRustCode for ScrollbarStyle {
347 fn format_as_rust_code(&self, tabs: usize) -> String {
348 let t = String::from(" ").repeat(tabs);
349 let t1 = String::from(" ").repeat(tabs + 1);
350 format!(
351 "ScrollbarStyle {{\r\n{}horizontal: {},\r\n{}vertical: {},\r\n{}}}",
352 t1,
353 crate::format_rust_code::format_scrollbar_info(&self.horizontal, tabs + 1),
354 t1,
355 crate::format_rust_code::format_scrollbar_info(&self.vertical, tabs + 1),
356 t,
357 )
358 }
359}
360
361impl crate::format_rust_code::FormatAsRustCode for LayoutScrollbarWidth {
362 fn format_as_rust_code(&self, _tabs: usize) -> String {
363 match self {
364 LayoutScrollbarWidth::Auto => String::from("LayoutScrollbarWidth::Auto"),
365 LayoutScrollbarWidth::Thin => String::from("LayoutScrollbarWidth::Thin"),
366 LayoutScrollbarWidth::None => String::from("LayoutScrollbarWidth::None"),
367 }
368 }
369}
370
371impl crate::format_rust_code::FormatAsRustCode for StyleScrollbarColor {
372 fn format_as_rust_code(&self, _tabs: usize) -> String {
373 match self {
374 StyleScrollbarColor::Auto => String::from("StyleScrollbarColor::Auto"),
375 StyleScrollbarColor::Custom(c) => format!(
376 "StyleScrollbarColor::Custom(ScrollbarColorCustom {{ thumb: {}, track: {} }})",
377 crate::format_rust_code::format_color_value(&c.thumb),
378 crate::format_rust_code::format_color_value(&c.track)
379 ),
380 }
381 }
382}
383
384#[derive(Debug, Clone, PartialEq)]
389pub struct ComputedScrollbarStyle {
390 pub width: Option<LayoutWidth>,
392 pub thumb_color: Option<ColorU>,
394 pub track_color: Option<ColorU>,
396}
397
398impl Default for ComputedScrollbarStyle {
399 fn default() -> Self {
400 let default_info = ScrollbarInfo::default();
401 Self {
402 width: Some(default_info.width), thumb_color: match default_info.thumb {
404 StyleBackgroundContent::Color(c) => Some(c),
405 _ => None,
406 },
407 track_color: match default_info.track {
408 StyleBackgroundContent::Color(c) => Some(c),
409 _ => None,
410 },
411 }
412 }
413}
414
415pub const SCROLLBAR_CLASSIC_LIGHT: ScrollbarInfo = ScrollbarInfo {
419 width: LayoutWidth::Px(crate::props::basic::pixel::PixelValue::const_px(17)),
420 padding_left: LayoutPaddingLeft {
421 inner: crate::props::basic::pixel::PixelValue::const_px(2),
422 },
423 padding_right: LayoutPaddingRight {
424 inner: crate::props::basic::pixel::PixelValue::const_px(2),
425 },
426 track: StyleBackgroundContent::Color(ColorU {
427 r: 241,
428 g: 241,
429 b: 241,
430 a: 255,
431 }),
432 thumb: StyleBackgroundContent::Color(ColorU {
433 r: 193,
434 g: 193,
435 b: 193,
436 a: 255,
437 }),
438 button: StyleBackgroundContent::Color(ColorU {
439 r: 163,
440 g: 163,
441 b: 163,
442 a: 255,
443 }),
444 corner: StyleBackgroundContent::Color(ColorU {
445 r: 241,
446 g: 241,
447 b: 241,
448 a: 255,
449 }),
450 resizer: StyleBackgroundContent::Color(ColorU {
451 r: 241,
452 g: 241,
453 b: 241,
454 a: 255,
455 }),
456 clip_to_container_border: false,
457 scroll_behavior: ScrollBehavior::Auto,
458 overscroll_behavior_x: OverscrollBehavior::Auto,
459 overscroll_behavior_y: OverscrollBehavior::Auto,
460};
461
462pub const SCROLLBAR_CLASSIC_DARK: ScrollbarInfo = ScrollbarInfo {
464 width: LayoutWidth::Px(crate::props::basic::pixel::PixelValue::const_px(17)),
465 padding_left: LayoutPaddingLeft {
466 inner: crate::props::basic::pixel::PixelValue::const_px(2),
467 },
468 padding_right: LayoutPaddingRight {
469 inner: crate::props::basic::pixel::PixelValue::const_px(2),
470 },
471 track: StyleBackgroundContent::Color(ColorU {
472 r: 45,
473 g: 45,
474 b: 45,
475 a: 255,
476 }),
477 thumb: StyleBackgroundContent::Color(ColorU {
478 r: 100,
479 g: 100,
480 b: 100,
481 a: 255,
482 }),
483 button: StyleBackgroundContent::Color(ColorU {
484 r: 120,
485 g: 120,
486 b: 120,
487 a: 255,
488 }),
489 corner: StyleBackgroundContent::Color(ColorU {
490 r: 45,
491 g: 45,
492 b: 45,
493 a: 255,
494 }),
495 resizer: StyleBackgroundContent::Color(ColorU {
496 r: 45,
497 g: 45,
498 b: 45,
499 a: 255,
500 }),
501 clip_to_container_border: false,
502 scroll_behavior: ScrollBehavior::Auto,
503 overscroll_behavior_x: OverscrollBehavior::Auto,
504 overscroll_behavior_y: OverscrollBehavior::Auto,
505};
506
507pub const SCROLLBAR_MACOS_LIGHT: ScrollbarInfo = ScrollbarInfo {
509 width: LayoutWidth::Px(crate::props::basic::pixel::PixelValue::const_px(8)),
510 padding_left: LayoutPaddingLeft {
511 inner: crate::props::basic::pixel::PixelValue::const_px(0),
512 },
513 padding_right: LayoutPaddingRight {
514 inner: crate::props::basic::pixel::PixelValue::const_px(0),
515 },
516 track: StyleBackgroundContent::Color(ColorU::TRANSPARENT),
517 thumb: StyleBackgroundContent::Color(ColorU {
518 r: 0,
519 g: 0,
520 b: 0,
521 a: 100,
522 }), button: StyleBackgroundContent::Color(ColorU::TRANSPARENT),
524 corner: StyleBackgroundContent::Color(ColorU::TRANSPARENT),
525 resizer: StyleBackgroundContent::Color(ColorU::TRANSPARENT),
526 clip_to_container_border: true, scroll_behavior: ScrollBehavior::Smooth,
528 overscroll_behavior_x: OverscrollBehavior::Auto,
529 overscroll_behavior_y: OverscrollBehavior::Auto,
530};
531
532pub const SCROLLBAR_MACOS_DARK: ScrollbarInfo = ScrollbarInfo {
534 width: LayoutWidth::Px(crate::props::basic::pixel::PixelValue::const_px(8)),
535 padding_left: LayoutPaddingLeft {
536 inner: crate::props::basic::pixel::PixelValue::const_px(0),
537 },
538 padding_right: LayoutPaddingRight {
539 inner: crate::props::basic::pixel::PixelValue::const_px(0),
540 },
541 track: StyleBackgroundContent::Color(ColorU::TRANSPARENT),
542 thumb: StyleBackgroundContent::Color(ColorU {
543 r: 255,
544 g: 255,
545 b: 255,
546 a: 100,
547 }), button: StyleBackgroundContent::Color(ColorU::TRANSPARENT),
549 corner: StyleBackgroundContent::Color(ColorU::TRANSPARENT),
550 resizer: StyleBackgroundContent::Color(ColorU::TRANSPARENT),
551 clip_to_container_border: true, scroll_behavior: ScrollBehavior::Smooth,
553 overscroll_behavior_x: OverscrollBehavior::Auto,
554 overscroll_behavior_y: OverscrollBehavior::Auto,
555};
556
557pub const SCROLLBAR_WINDOWS_LIGHT: ScrollbarInfo = ScrollbarInfo {
559 width: LayoutWidth::Px(crate::props::basic::pixel::PixelValue::const_px(12)),
560 padding_left: LayoutPaddingLeft {
561 inner: crate::props::basic::pixel::PixelValue::const_px(0),
562 },
563 padding_right: LayoutPaddingRight {
564 inner: crate::props::basic::pixel::PixelValue::const_px(0),
565 },
566 track: StyleBackgroundContent::Color(ColorU {
567 r: 241,
568 g: 241,
569 b: 241,
570 a: 255,
571 }),
572 thumb: StyleBackgroundContent::Color(ColorU {
573 r: 130,
574 g: 130,
575 b: 130,
576 a: 255,
577 }),
578 button: StyleBackgroundContent::Color(ColorU::TRANSPARENT),
579 corner: StyleBackgroundContent::Color(ColorU::TRANSPARENT),
580 resizer: StyleBackgroundContent::Color(ColorU::TRANSPARENT),
581 clip_to_container_border: false,
582 scroll_behavior: ScrollBehavior::Auto,
583 overscroll_behavior_x: OverscrollBehavior::None,
584 overscroll_behavior_y: OverscrollBehavior::None,
585};
586
587pub const SCROLLBAR_WINDOWS_DARK: ScrollbarInfo = ScrollbarInfo {
589 width: LayoutWidth::Px(crate::props::basic::pixel::PixelValue::const_px(12)),
590 padding_left: LayoutPaddingLeft {
591 inner: crate::props::basic::pixel::PixelValue::const_px(0),
592 },
593 padding_right: LayoutPaddingRight {
594 inner: crate::props::basic::pixel::PixelValue::const_px(0),
595 },
596 track: StyleBackgroundContent::Color(ColorU {
597 r: 32,
598 g: 32,
599 b: 32,
600 a: 255,
601 }),
602 thumb: StyleBackgroundContent::Color(ColorU {
603 r: 110,
604 g: 110,
605 b: 110,
606 a: 255,
607 }),
608 button: StyleBackgroundContent::Color(ColorU::TRANSPARENT),
609 corner: StyleBackgroundContent::Color(ColorU::TRANSPARENT),
610 resizer: StyleBackgroundContent::Color(ColorU::TRANSPARENT),
611 clip_to_container_border: false,
612 scroll_behavior: ScrollBehavior::Auto,
613 overscroll_behavior_x: OverscrollBehavior::None,
614 overscroll_behavior_y: OverscrollBehavior::None,
615};
616
617pub const SCROLLBAR_IOS_LIGHT: ScrollbarInfo = ScrollbarInfo {
619 width: LayoutWidth::Px(crate::props::basic::pixel::PixelValue::const_px(7)),
620 padding_left: LayoutPaddingLeft {
621 inner: crate::props::basic::pixel::PixelValue::const_px(0),
622 },
623 padding_right: LayoutPaddingRight {
624 inner: crate::props::basic::pixel::PixelValue::const_px(0),
625 },
626 track: StyleBackgroundContent::Color(ColorU::TRANSPARENT),
627 thumb: StyleBackgroundContent::Color(ColorU {
628 r: 0,
629 g: 0,
630 b: 0,
631 a: 102,
632 }), button: StyleBackgroundContent::Color(ColorU::TRANSPARENT),
634 corner: StyleBackgroundContent::Color(ColorU::TRANSPARENT),
635 resizer: StyleBackgroundContent::Color(ColorU::TRANSPARENT),
636 clip_to_container_border: true, scroll_behavior: ScrollBehavior::Smooth,
638 overscroll_behavior_x: OverscrollBehavior::Auto,
639 overscroll_behavior_y: OverscrollBehavior::Auto,
640};
641
642pub const SCROLLBAR_IOS_DARK: ScrollbarInfo = ScrollbarInfo {
644 width: LayoutWidth::Px(crate::props::basic::pixel::PixelValue::const_px(7)),
645 padding_left: LayoutPaddingLeft {
646 inner: crate::props::basic::pixel::PixelValue::const_px(0),
647 },
648 padding_right: LayoutPaddingRight {
649 inner: crate::props::basic::pixel::PixelValue::const_px(0),
650 },
651 track: StyleBackgroundContent::Color(ColorU::TRANSPARENT),
652 thumb: StyleBackgroundContent::Color(ColorU {
653 r: 255,
654 g: 255,
655 b: 255,
656 a: 102,
657 }), button: StyleBackgroundContent::Color(ColorU::TRANSPARENT),
659 corner: StyleBackgroundContent::Color(ColorU::TRANSPARENT),
660 resizer: StyleBackgroundContent::Color(ColorU::TRANSPARENT),
661 clip_to_container_border: true, scroll_behavior: ScrollBehavior::Smooth,
663 overscroll_behavior_x: OverscrollBehavior::Auto,
664 overscroll_behavior_y: OverscrollBehavior::Auto,
665};
666
667pub const SCROLLBAR_ANDROID_LIGHT: ScrollbarInfo = ScrollbarInfo {
669 width: LayoutWidth::Px(crate::props::basic::pixel::PixelValue::const_px(6)),
670 padding_left: LayoutPaddingLeft {
671 inner: crate::props::basic::pixel::PixelValue::const_px(0),
672 },
673 padding_right: LayoutPaddingRight {
674 inner: crate::props::basic::pixel::PixelValue::const_px(0),
675 },
676 track: StyleBackgroundContent::Color(ColorU::TRANSPARENT),
677 thumb: StyleBackgroundContent::Color(ColorU {
678 r: 0,
679 g: 0,
680 b: 0,
681 a: 102,
682 }), button: StyleBackgroundContent::Color(ColorU::TRANSPARENT),
684 corner: StyleBackgroundContent::Color(ColorU::TRANSPARENT),
685 resizer: StyleBackgroundContent::Color(ColorU::TRANSPARENT),
686 clip_to_container_border: true, scroll_behavior: ScrollBehavior::Smooth,
688 overscroll_behavior_x: OverscrollBehavior::Contain,
689 overscroll_behavior_y: OverscrollBehavior::Auto,
690};
691
692pub const SCROLLBAR_ANDROID_DARK: ScrollbarInfo = ScrollbarInfo {
694 width: LayoutWidth::Px(crate::props::basic::pixel::PixelValue::const_px(6)),
695 padding_left: LayoutPaddingLeft {
696 inner: crate::props::basic::pixel::PixelValue::const_px(0),
697 },
698 padding_right: LayoutPaddingRight {
699 inner: crate::props::basic::pixel::PixelValue::const_px(0),
700 },
701 track: StyleBackgroundContent::Color(ColorU::TRANSPARENT),
702 thumb: StyleBackgroundContent::Color(ColorU {
703 r: 255,
704 g: 255,
705 b: 255,
706 a: 102,
707 }), button: StyleBackgroundContent::Color(ColorU::TRANSPARENT),
709 corner: StyleBackgroundContent::Color(ColorU::TRANSPARENT),
710 resizer: StyleBackgroundContent::Color(ColorU::TRANSPARENT),
711 clip_to_container_border: true, scroll_behavior: ScrollBehavior::Smooth,
713 overscroll_behavior_x: OverscrollBehavior::Contain,
714 overscroll_behavior_y: OverscrollBehavior::Auto,
715};
716
717#[derive(Clone, PartialEq)]
720pub enum LayoutScrollbarWidthParseError<'a> {
721 InvalidValue(&'a str),
722}
723impl_debug_as_display!(LayoutScrollbarWidthParseError<'a>);
724impl_display! { LayoutScrollbarWidthParseError<'a>, {
725 InvalidValue(v) => format!("Invalid scrollbar-width value: \"{}\"", v),
726}}
727
728#[derive(Debug, Clone, PartialEq)]
729pub enum LayoutScrollbarWidthParseErrorOwned {
730 InvalidValue(String),
731}
732impl<'a> LayoutScrollbarWidthParseError<'a> {
733 pub fn to_contained(&self) -> LayoutScrollbarWidthParseErrorOwned {
734 match self {
735 Self::InvalidValue(s) => {
736 LayoutScrollbarWidthParseErrorOwned::InvalidValue(s.to_string())
737 }
738 }
739 }
740}
741impl LayoutScrollbarWidthParseErrorOwned {
742 pub fn to_shared<'a>(&'a self) -> LayoutScrollbarWidthParseError<'a> {
743 match self {
744 Self::InvalidValue(s) => LayoutScrollbarWidthParseError::InvalidValue(s.as_str()),
745 }
746 }
747}
748
749#[cfg(feature = "parser")]
750pub fn parse_layout_scrollbar_width<'a>(
751 input: &'a str,
752) -> Result<LayoutScrollbarWidth, LayoutScrollbarWidthParseError<'a>> {
753 match input.trim() {
754 "auto" => Ok(LayoutScrollbarWidth::Auto),
755 "thin" => Ok(LayoutScrollbarWidth::Thin),
756 "none" => Ok(LayoutScrollbarWidth::None),
757 _ => Err(LayoutScrollbarWidthParseError::InvalidValue(input)),
758 }
759}
760
761#[derive(Clone, PartialEq)]
762pub enum StyleScrollbarColorParseError<'a> {
763 InvalidValue(&'a str),
764 Color(CssColorParseError<'a>),
765}
766impl_debug_as_display!(StyleScrollbarColorParseError<'a>);
767impl_display! { StyleScrollbarColorParseError<'a>, {
768 InvalidValue(v) => format!("Invalid scrollbar-color value: \"{}\"", v),
769 Color(e) => format!("Invalid color in scrollbar-color: {}", e),
770}}
771impl_from!(CssColorParseError<'a>, StyleScrollbarColorParseError::Color);
772
773#[derive(Debug, Clone, PartialEq)]
774pub enum StyleScrollbarColorParseErrorOwned {
775 InvalidValue(String),
776 Color(CssColorParseErrorOwned),
777}
778impl<'a> StyleScrollbarColorParseError<'a> {
779 pub fn to_contained(&self) -> StyleScrollbarColorParseErrorOwned {
780 match self {
781 Self::InvalidValue(s) => {
782 StyleScrollbarColorParseErrorOwned::InvalidValue(s.to_string())
783 }
784 Self::Color(e) => StyleScrollbarColorParseErrorOwned::Color(e.to_contained()),
785 }
786 }
787}
788impl StyleScrollbarColorParseErrorOwned {
789 pub fn to_shared<'a>(&'a self) -> StyleScrollbarColorParseError<'a> {
790 match self {
791 Self::InvalidValue(s) => StyleScrollbarColorParseError::InvalidValue(s.as_str()),
792 Self::Color(e) => StyleScrollbarColorParseError::Color(e.to_shared()),
793 }
794 }
795}
796
797#[cfg(feature = "parser")]
798pub fn parse_style_scrollbar_color<'a>(
799 input: &'a str,
800) -> Result<StyleScrollbarColor, StyleScrollbarColorParseError<'a>> {
801 let input = input.trim();
802 if input == "auto" {
803 return Ok(StyleScrollbarColor::Auto);
804 }
805
806 let mut parts = input.split_whitespace();
807 let thumb_str = parts
808 .next()
809 .ok_or(StyleScrollbarColorParseError::InvalidValue(input))?;
810 let track_str = parts
811 .next()
812 .ok_or(StyleScrollbarColorParseError::InvalidValue(input))?;
813
814 if parts.next().is_some() {
815 return Err(StyleScrollbarColorParseError::InvalidValue(input));
816 }
817
818 let thumb = parse_css_color(thumb_str)?;
819 let track = parse_css_color(track_str)?;
820
821 Ok(StyleScrollbarColor::Custom(ScrollbarColorCustom {
822 thumb,
823 track,
824 }))
825}
826
827#[derive(Clone, PartialEq)]
828pub enum CssScrollbarStyleParseError<'a> {
829 Invalid(&'a str),
830}
831
832impl_debug_as_display!(CssScrollbarStyleParseError<'a>);
833impl_display! { CssScrollbarStyleParseError<'a>, {
834 Invalid(e) => format!("Invalid scrollbar style: \"{}\"", e),
835}}
836
837#[derive(Debug, Clone, PartialEq)]
838pub enum CssScrollbarStyleParseErrorOwned {
839 Invalid(String),
840}
841
842impl<'a> CssScrollbarStyleParseError<'a> {
843 pub fn to_contained(&self) -> CssScrollbarStyleParseErrorOwned {
844 match self {
845 CssScrollbarStyleParseError::Invalid(s) => {
846 CssScrollbarStyleParseErrorOwned::Invalid(s.to_string())
847 }
848 }
849 }
850}
851
852impl CssScrollbarStyleParseErrorOwned {
853 pub fn to_shared<'a>(&'a self) -> CssScrollbarStyleParseError<'a> {
854 match self {
855 CssScrollbarStyleParseErrorOwned::Invalid(s) => {
856 CssScrollbarStyleParseError::Invalid(s.as_str())
857 }
858 }
859 }
860}
861
862#[cfg(feature = "parser")]
863pub fn parse_scrollbar_style<'a>(
864 _input: &'a str,
865) -> Result<ScrollbarStyle, CssScrollbarStyleParseError<'a>> {
866 Ok(ScrollbarStyle::default())
869}
870
871pub fn resolve_scrollbar_style(
880 scrollbar_width: Option<&LayoutScrollbarWidth>,
881 scrollbar_color: Option<&StyleScrollbarColor>,
882 webkit_scrollbar_style: Option<&ScrollbarStyle>,
883) -> ComputedScrollbarStyle {
884 let final_width = scrollbar_width
885 .cloned()
886 .unwrap_or(LayoutScrollbarWidth::Auto);
887 let final_color = scrollbar_color
888 .cloned()
889 .unwrap_or(StyleScrollbarColor::Auto);
890
891 if final_width != LayoutScrollbarWidth::Auto || final_color != StyleScrollbarColor::Auto {
893 let width = match final_width {
894 LayoutScrollbarWidth::None => None,
895 LayoutScrollbarWidth::Thin => Some(LayoutWidth::Px(PixelValue::px(8.0))),
897 LayoutScrollbarWidth::Auto => Some(
899 webkit_scrollbar_style
900 .map_or_else(|| ScrollbarInfo::default().width, |s| s.vertical.width),
901 ),
902 };
903
904 let (thumb_color, track_color) = match final_color {
905 StyleScrollbarColor::Custom(c) => (Some(c.thumb), Some(c.track)),
906 StyleScrollbarColor::Auto => (None, None), };
908
909 return ComputedScrollbarStyle {
910 width,
911 thumb_color,
912 track_color,
913 };
914 }
915
916 if let Some(webkit_style) = webkit_scrollbar_style {
918 let info = &webkit_style.vertical;
920
921 let width_pixels = match info.width {
923 LayoutWidth::Px(px) => {
924 use crate::props::basic::pixel::DEFAULT_FONT_SIZE;
925 px.to_pixels_internal(0.0, DEFAULT_FONT_SIZE)
926 }
927 _ => 8.0, };
929 if width_pixels <= 0.0 {
930 return ComputedScrollbarStyle {
931 width: None,
932 thumb_color: None,
933 track_color: None,
934 };
935 }
936
937 let thumb = match &info.thumb {
938 StyleBackgroundContent::Color(c) => Some(*c),
939 _ => None, };
941
942 let track = match &info.track {
943 StyleBackgroundContent::Color(c) => Some(*c),
944 _ => None,
945 };
946
947 return ComputedScrollbarStyle {
948 width: Some(info.width),
949 thumb_color: thumb,
950 track_color: track,
951 };
952 }
953
954 ComputedScrollbarStyle::default()
956}
957
958#[cfg(all(test, feature = "parser"))]
959mod tests {
960 use super::*;
961 use crate::props::{basic::color::ColorU, layout::dimensions::LayoutWidth};
962
963 #[test]
966 fn test_parse_scrollbar_width() {
967 assert_eq!(
968 parse_layout_scrollbar_width("auto").unwrap(),
969 LayoutScrollbarWidth::Auto
970 );
971 assert_eq!(
972 parse_layout_scrollbar_width("thin").unwrap(),
973 LayoutScrollbarWidth::Thin
974 );
975 assert_eq!(
976 parse_layout_scrollbar_width("none").unwrap(),
977 LayoutScrollbarWidth::None
978 );
979 assert!(parse_layout_scrollbar_width("thick").is_err());
980 }
981
982 #[test]
983 fn test_parse_scrollbar_color() {
984 assert_eq!(
985 parse_style_scrollbar_color("auto").unwrap(),
986 StyleScrollbarColor::Auto
987 );
988
989 let custom = parse_style_scrollbar_color("red blue").unwrap();
990 assert_eq!(
991 custom,
992 StyleScrollbarColor::Custom(ScrollbarColorCustom {
993 thumb: ColorU::RED,
994 track: ColorU::BLUE
995 })
996 );
997
998 let custom_hex = parse_style_scrollbar_color("#ff0000 #0000ff").unwrap();
999 assert_eq!(
1000 custom_hex,
1001 StyleScrollbarColor::Custom(ScrollbarColorCustom {
1002 thumb: ColorU::RED,
1003 track: ColorU::BLUE
1004 })
1005 );
1006
1007 assert!(parse_style_scrollbar_color("red").is_err());
1008 assert!(parse_style_scrollbar_color("red blue green").is_err());
1009 }
1010
1011 fn get_webkit_style() -> ScrollbarStyle {
1015 let mut info = ScrollbarInfo::default();
1016 info.width = LayoutWidth::px(15.0);
1017 info.thumb = StyleBackgroundContent::Color(ColorU::GREEN);
1018 info.track = StyleBackgroundContent::Color(ColorU::new_rgb(100, 100, 100));
1019 ScrollbarStyle {
1020 horizontal: info.clone(),
1021 vertical: info,
1022 }
1023 }
1024
1025 #[test]
1026 fn test_resolve_standard_overrides_webkit() {
1027 let width = LayoutScrollbarWidth::Thin;
1028 let color = StyleScrollbarColor::Custom(ScrollbarColorCustom {
1029 thumb: ColorU::RED,
1030 track: ColorU::BLUE,
1031 });
1032 let webkit_style = get_webkit_style();
1033
1034 let resolved = resolve_scrollbar_style(Some(&width), Some(&color), Some(&webkit_style));
1035
1036 assert_eq!(resolved.width, Some(LayoutWidth::px(8.0)));
1038 assert_eq!(resolved.thumb_color, Some(ColorU::RED));
1039 assert_eq!(resolved.track_color, Some(ColorU::BLUE));
1040 }
1041
1042 #[test]
1043 fn test_resolve_standard_auto_falls_back_to_webkit() {
1044 let width = LayoutScrollbarWidth::Auto;
1045 let color = StyleScrollbarColor::Auto;
1046 let webkit_style = get_webkit_style();
1047
1048 let resolved = resolve_scrollbar_style(Some(&width), Some(&color), Some(&webkit_style));
1049
1050 assert_eq!(resolved.width, Some(LayoutWidth::px(15.0)));
1051 assert_eq!(resolved.thumb_color, Some(ColorU::GREEN));
1052 assert_eq!(resolved.track_color, Some(ColorU::new_rgb(100, 100, 100)));
1053 }
1054
1055 #[test]
1056 fn test_resolve_no_styles_uses_default() {
1057 let resolved = resolve_scrollbar_style(None, None, None);
1058 assert_eq!(resolved, ComputedScrollbarStyle::default());
1059 }
1060
1061 #[test]
1062 fn test_resolve_scrollbar_width_none() {
1063 let width = LayoutScrollbarWidth::None;
1064 let webkit_style = get_webkit_style();
1065
1066 let resolved = resolve_scrollbar_style(Some(&width), None, Some(&webkit_style));
1067 assert_eq!(resolved.width, None);
1068 }
1069
1070 #[test]
1071 fn test_resolve_webkit_display_none_equivalent() {
1072 let mut webkit_style = get_webkit_style();
1073 webkit_style.vertical.width = LayoutWidth::px(0.0);
1074
1075 let resolved = resolve_scrollbar_style(None, None, Some(&webkit_style));
1076 assert_eq!(resolved.width, None);
1077 }
1078
1079 #[test]
1080 fn test_resolve_only_color_is_set() {
1081 let color = StyleScrollbarColor::Custom(ScrollbarColorCustom {
1082 thumb: ColorU::RED,
1083 track: ColorU::BLUE,
1084 });
1085 let webkit_style = get_webkit_style();
1086
1087 let resolved = resolve_scrollbar_style(None, Some(&color), Some(&webkit_style));
1088
1089 assert_eq!(resolved.width, Some(LayoutWidth::px(15.0)));
1091 assert_eq!(resolved.thumb_color, Some(ColorU::RED));
1092 assert_eq!(resolved.track_color, Some(ColorU::BLUE));
1093 }
1094
1095 #[test]
1096 fn test_resolve_only_width_is_set() {
1097 let width = LayoutScrollbarWidth::Thin;
1098 let webkit_style = get_webkit_style();
1099
1100 let resolved = resolve_scrollbar_style(Some(&width), None, Some(&webkit_style));
1101
1102 assert_eq!(resolved.width, Some(LayoutWidth::px(8.0)));
1104 assert_eq!(resolved.thumb_color, None);
1105 assert_eq!(resolved.track_color, None);
1106 }
1107}