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 pub timer_interval_ms: u32,
125}
126
127impl Default for ScrollPhysics {
128 fn default() -> Self {
129 Self {
130 smooth_scroll_duration_ms: 300,
131 deceleration_rate: 0.95,
132 min_velocity_threshold: 50.0,
133 max_velocity: 8000.0,
134 wheel_multiplier: 1.0,
135 invert_direction: false,
136 overscroll_elasticity: 0.0, max_overscroll_distance: 100.0,
138 bounce_back_duration_ms: 400,
139 timer_interval_ms: 16,
140 }
141 }
142}
143
144impl ScrollPhysics {
145 pub const fn ios() -> Self {
147 Self {
148 smooth_scroll_duration_ms: 300,
149 deceleration_rate: 0.998,
150 min_velocity_threshold: 20.0,
151 max_velocity: 8000.0,
152 wheel_multiplier: 1.0,
153 invert_direction: true, overscroll_elasticity: 0.5,
155 max_overscroll_distance: 120.0,
156 bounce_back_duration_ms: 500,
157 timer_interval_ms: 16,
158 }
159 }
160
161 pub const fn macos() -> Self {
163 Self {
164 smooth_scroll_duration_ms: 250,
165 deceleration_rate: 0.997,
166 min_velocity_threshold: 30.0,
167 max_velocity: 6000.0,
168 wheel_multiplier: 1.0,
169 invert_direction: true, overscroll_elasticity: 0.3,
171 max_overscroll_distance: 80.0,
172 bounce_back_duration_ms: 400,
173 timer_interval_ms: 16,
174 }
175 }
176
177 pub const fn windows() -> Self {
179 Self {
180 smooth_scroll_duration_ms: 200,
181 deceleration_rate: 0.9,
182 min_velocity_threshold: 100.0,
183 max_velocity: 4000.0,
184 wheel_multiplier: 1.0,
185 invert_direction: false,
186 overscroll_elasticity: 0.0,
187 max_overscroll_distance: 0.0,
188 bounce_back_duration_ms: 200,
189 timer_interval_ms: 16,
190 }
191 }
192
193 pub const fn android() -> Self {
195 Self {
196 smooth_scroll_duration_ms: 250,
197 deceleration_rate: 0.996,
198 min_velocity_threshold: 40.0,
199 max_velocity: 8000.0,
200 wheel_multiplier: 1.0,
201 invert_direction: false,
202 overscroll_elasticity: 0.2, max_overscroll_distance: 60.0,
204 bounce_back_duration_ms: 300,
205 timer_interval_ms: 16,
206 }
207 }
208}
209
210#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Default)]
224#[repr(C)]
225pub enum OverflowScrolling {
226 #[default]
228 Auto,
229 Touch,
231}
232
233impl PrintAsCssValue for OverflowScrolling {
234 fn print_as_css_value(&self) -> String {
235 match self {
236 Self::Auto => "auto".to_string(),
237 Self::Touch => "touch".to_string(),
238 }
239 }
240}
241
242#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
248#[repr(C)]
249pub enum LayoutScrollbarWidth {
250 Auto,
251 Thin,
252 None,
253}
254
255impl Default for LayoutScrollbarWidth {
256 fn default() -> Self {
257 Self::Auto
258 }
259}
260
261impl PrintAsCssValue for LayoutScrollbarWidth {
262 fn print_as_css_value(&self) -> String {
263 match self {
264 Self::Auto => "auto".to_string(),
265 Self::Thin => "thin".to_string(),
266 Self::None => "none".to_string(),
267 }
268 }
269}
270
271#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
273#[repr(C)]
274pub struct ScrollbarColorCustom {
275 pub thumb: ColorU,
276 pub track: ColorU,
277}
278
279#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
281#[repr(C, u8)]
282pub enum StyleScrollbarColor {
283 Auto,
284 Custom(ScrollbarColorCustom),
285}
286
287impl Default for StyleScrollbarColor {
288 fn default() -> Self {
289 Self::Auto
290 }
291}
292
293impl PrintAsCssValue for StyleScrollbarColor {
294 fn print_as_css_value(&self) -> String {
295 match self {
296 Self::Auto => "auto".to_string(),
297 Self::Custom(c) => format!("{} {}", c.thumb.to_hash(), c.track.to_hash()),
298 }
299 }
300}
301
302#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
306#[repr(C)]
307pub struct ScrollbarInfo {
308 pub width: LayoutWidth,
310 pub padding_left: LayoutPaddingLeft,
313 pub padding_right: LayoutPaddingRight,
315 pub track: StyleBackgroundContent,
319 pub thumb: StyleBackgroundContent,
321 pub button: StyleBackgroundContent,
323 pub corner: StyleBackgroundContent,
326 pub resizer: StyleBackgroundContent,
329 pub clip_to_container_border: bool,
334 pub scroll_behavior: ScrollBehavior,
336 pub overscroll_behavior_x: OverscrollBehavior,
338 pub overscroll_behavior_y: OverscrollBehavior,
340 pub overflow_scrolling: OverflowScrolling,
343}
344
345impl Default for ScrollbarInfo {
346 fn default() -> Self {
347 SCROLLBAR_CLASSIC_LIGHT
348 }
349}
350
351impl PrintAsCssValue for ScrollbarInfo {
352 fn print_as_css_value(&self) -> String {
353 format!(
355 "width: {}; padding-left: {}; padding-right: {}; track: {}; thumb: {}; button: {}; \
356 corner: {}; resizer: {}",
357 self.width.print_as_css_value(),
358 self.padding_left.print_as_css_value(),
359 self.padding_right.print_as_css_value(),
360 self.track.print_as_css_value(),
361 self.thumb.print_as_css_value(),
362 self.button.print_as_css_value(),
363 self.corner.print_as_css_value(),
364 self.resizer.print_as_css_value(),
365 )
366 }
367}
368
369#[derive(Debug, Default, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
371#[repr(C)]
372pub struct ScrollbarStyle {
373 pub horizontal: ScrollbarInfo,
375 pub vertical: ScrollbarInfo,
377}
378
379impl PrintAsCssValue for ScrollbarStyle {
380 fn print_as_css_value(&self) -> String {
381 format!(
383 "horz({}), vert({})",
384 self.horizontal.print_as_css_value(),
385 self.vertical.print_as_css_value()
386 )
387 }
388}
389
390impl crate::format_rust_code::FormatAsRustCode for ScrollbarStyle {
392 fn format_as_rust_code(&self, tabs: usize) -> String {
393 let t = String::from(" ").repeat(tabs);
394 let t1 = String::from(" ").repeat(tabs + 1);
395 format!(
396 "ScrollbarStyle {{\r\n{}horizontal: {},\r\n{}vertical: {},\r\n{}}}",
397 t1,
398 crate::format_rust_code::format_scrollbar_info(&self.horizontal, tabs + 1),
399 t1,
400 crate::format_rust_code::format_scrollbar_info(&self.vertical, tabs + 1),
401 t,
402 )
403 }
404}
405
406impl crate::format_rust_code::FormatAsRustCode for LayoutScrollbarWidth {
407 fn format_as_rust_code(&self, _tabs: usize) -> String {
408 match self {
409 LayoutScrollbarWidth::Auto => String::from("LayoutScrollbarWidth::Auto"),
410 LayoutScrollbarWidth::Thin => String::from("LayoutScrollbarWidth::Thin"),
411 LayoutScrollbarWidth::None => String::from("LayoutScrollbarWidth::None"),
412 }
413 }
414}
415
416impl crate::format_rust_code::FormatAsRustCode for StyleScrollbarColor {
417 fn format_as_rust_code(&self, _tabs: usize) -> String {
418 match self {
419 StyleScrollbarColor::Auto => String::from("StyleScrollbarColor::Auto"),
420 StyleScrollbarColor::Custom(c) => format!(
421 "StyleScrollbarColor::Custom(ScrollbarColorCustom {{ thumb: {}, track: {} }})",
422 crate::format_rust_code::format_color_value(&c.thumb),
423 crate::format_rust_code::format_color_value(&c.track)
424 ),
425 }
426 }
427}
428
429#[derive(Debug, Clone, PartialEq)]
434pub struct ComputedScrollbarStyle {
435 pub width: Option<LayoutWidth>,
437 pub thumb_color: Option<ColorU>,
439 pub track_color: Option<ColorU>,
441}
442
443impl Default for ComputedScrollbarStyle {
444 fn default() -> Self {
445 let default_info = ScrollbarInfo::default();
446 Self {
447 width: Some(default_info.width), thumb_color: match default_info.thumb {
449 StyleBackgroundContent::Color(c) => Some(c),
450 _ => None,
451 },
452 track_color: match default_info.track {
453 StyleBackgroundContent::Color(c) => Some(c),
454 _ => None,
455 },
456 }
457 }
458}
459
460pub const SCROLLBAR_CLASSIC_LIGHT: 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: 241,
473 g: 241,
474 b: 241,
475 a: 255,
476 }),
477 thumb: StyleBackgroundContent::Color(ColorU {
478 r: 193,
479 g: 193,
480 b: 193,
481 a: 255,
482 }),
483 button: StyleBackgroundContent::Color(ColorU {
484 r: 163,
485 g: 163,
486 b: 163,
487 a: 255,
488 }),
489 corner: StyleBackgroundContent::Color(ColorU {
490 r: 241,
491 g: 241,
492 b: 241,
493 a: 255,
494 }),
495 resizer: StyleBackgroundContent::Color(ColorU {
496 r: 241,
497 g: 241,
498 b: 241,
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 overflow_scrolling: OverflowScrolling::Auto,
506};
507
508pub const SCROLLBAR_CLASSIC_DARK: ScrollbarInfo = ScrollbarInfo {
510 width: LayoutWidth::Px(crate::props::basic::pixel::PixelValue::const_px(17)),
511 padding_left: LayoutPaddingLeft {
512 inner: crate::props::basic::pixel::PixelValue::const_px(2),
513 },
514 padding_right: LayoutPaddingRight {
515 inner: crate::props::basic::pixel::PixelValue::const_px(2),
516 },
517 track: StyleBackgroundContent::Color(ColorU {
518 r: 45,
519 g: 45,
520 b: 45,
521 a: 255,
522 }),
523 thumb: StyleBackgroundContent::Color(ColorU {
524 r: 100,
525 g: 100,
526 b: 100,
527 a: 255,
528 }),
529 button: StyleBackgroundContent::Color(ColorU {
530 r: 120,
531 g: 120,
532 b: 120,
533 a: 255,
534 }),
535 corner: StyleBackgroundContent::Color(ColorU {
536 r: 45,
537 g: 45,
538 b: 45,
539 a: 255,
540 }),
541 resizer: StyleBackgroundContent::Color(ColorU {
542 r: 45,
543 g: 45,
544 b: 45,
545 a: 255,
546 }),
547 clip_to_container_border: false,
548 scroll_behavior: ScrollBehavior::Auto,
549 overscroll_behavior_x: OverscrollBehavior::Auto,
550 overscroll_behavior_y: OverscrollBehavior::Auto,
551 overflow_scrolling: OverflowScrolling::Auto,
552};
553
554pub const SCROLLBAR_MACOS_LIGHT: ScrollbarInfo = ScrollbarInfo {
556 width: LayoutWidth::Px(crate::props::basic::pixel::PixelValue::const_px(8)),
557 padding_left: LayoutPaddingLeft {
558 inner: crate::props::basic::pixel::PixelValue::const_px(0),
559 },
560 padding_right: LayoutPaddingRight {
561 inner: crate::props::basic::pixel::PixelValue::const_px(0),
562 },
563 track: StyleBackgroundContent::Color(ColorU::TRANSPARENT),
564 thumb: StyleBackgroundContent::Color(ColorU {
565 r: 0,
566 g: 0,
567 b: 0,
568 a: 100,
569 }), button: StyleBackgroundContent::Color(ColorU::TRANSPARENT),
571 corner: StyleBackgroundContent::Color(ColorU::TRANSPARENT),
572 resizer: StyleBackgroundContent::Color(ColorU::TRANSPARENT),
573 clip_to_container_border: true, scroll_behavior: ScrollBehavior::Smooth,
575 overscroll_behavior_x: OverscrollBehavior::Auto,
576 overscroll_behavior_y: OverscrollBehavior::Auto,
577 overflow_scrolling: OverflowScrolling::Auto,
578};
579
580pub const SCROLLBAR_MACOS_DARK: ScrollbarInfo = ScrollbarInfo {
582 width: LayoutWidth::Px(crate::props::basic::pixel::PixelValue::const_px(8)),
583 padding_left: LayoutPaddingLeft {
584 inner: crate::props::basic::pixel::PixelValue::const_px(0),
585 },
586 padding_right: LayoutPaddingRight {
587 inner: crate::props::basic::pixel::PixelValue::const_px(0),
588 },
589 track: StyleBackgroundContent::Color(ColorU::TRANSPARENT),
590 thumb: StyleBackgroundContent::Color(ColorU {
591 r: 255,
592 g: 255,
593 b: 255,
594 a: 100,
595 }), button: StyleBackgroundContent::Color(ColorU::TRANSPARENT),
597 corner: StyleBackgroundContent::Color(ColorU::TRANSPARENT),
598 resizer: StyleBackgroundContent::Color(ColorU::TRANSPARENT),
599 clip_to_container_border: true, scroll_behavior: ScrollBehavior::Smooth,
601 overscroll_behavior_x: OverscrollBehavior::Auto,
602 overscroll_behavior_y: OverscrollBehavior::Auto,
603 overflow_scrolling: OverflowScrolling::Auto,
604};
605
606pub const SCROLLBAR_WINDOWS_LIGHT: ScrollbarInfo = ScrollbarInfo {
608 width: LayoutWidth::Px(crate::props::basic::pixel::PixelValue::const_px(12)),
609 padding_left: LayoutPaddingLeft {
610 inner: crate::props::basic::pixel::PixelValue::const_px(0),
611 },
612 padding_right: LayoutPaddingRight {
613 inner: crate::props::basic::pixel::PixelValue::const_px(0),
614 },
615 track: StyleBackgroundContent::Color(ColorU {
616 r: 241,
617 g: 241,
618 b: 241,
619 a: 255,
620 }),
621 thumb: StyleBackgroundContent::Color(ColorU {
622 r: 130,
623 g: 130,
624 b: 130,
625 a: 255,
626 }),
627 button: StyleBackgroundContent::Color(ColorU::TRANSPARENT),
628 corner: StyleBackgroundContent::Color(ColorU::TRANSPARENT),
629 resizer: StyleBackgroundContent::Color(ColorU::TRANSPARENT),
630 clip_to_container_border: false,
631 scroll_behavior: ScrollBehavior::Auto,
632 overscroll_behavior_x: OverscrollBehavior::None,
633 overscroll_behavior_y: OverscrollBehavior::None,
634 overflow_scrolling: OverflowScrolling::Auto,
635};
636
637pub const SCROLLBAR_WINDOWS_DARK: ScrollbarInfo = ScrollbarInfo {
639 width: LayoutWidth::Px(crate::props::basic::pixel::PixelValue::const_px(12)),
640 padding_left: LayoutPaddingLeft {
641 inner: crate::props::basic::pixel::PixelValue::const_px(0),
642 },
643 padding_right: LayoutPaddingRight {
644 inner: crate::props::basic::pixel::PixelValue::const_px(0),
645 },
646 track: StyleBackgroundContent::Color(ColorU {
647 r: 32,
648 g: 32,
649 b: 32,
650 a: 255,
651 }),
652 thumb: StyleBackgroundContent::Color(ColorU {
653 r: 110,
654 g: 110,
655 b: 110,
656 a: 255,
657 }),
658 button: StyleBackgroundContent::Color(ColorU::TRANSPARENT),
659 corner: StyleBackgroundContent::Color(ColorU::TRANSPARENT),
660 resizer: StyleBackgroundContent::Color(ColorU::TRANSPARENT),
661 clip_to_container_border: false,
662 scroll_behavior: ScrollBehavior::Auto,
663 overscroll_behavior_x: OverscrollBehavior::None,
664 overscroll_behavior_y: OverscrollBehavior::None,
665 overflow_scrolling: OverflowScrolling::Auto,
666};
667
668pub const SCROLLBAR_IOS_LIGHT: ScrollbarInfo = ScrollbarInfo {
670 width: LayoutWidth::Px(crate::props::basic::pixel::PixelValue::const_px(7)),
671 padding_left: LayoutPaddingLeft {
672 inner: crate::props::basic::pixel::PixelValue::const_px(0),
673 },
674 padding_right: LayoutPaddingRight {
675 inner: crate::props::basic::pixel::PixelValue::const_px(0),
676 },
677 track: StyleBackgroundContent::Color(ColorU::TRANSPARENT),
678 thumb: StyleBackgroundContent::Color(ColorU {
679 r: 0,
680 g: 0,
681 b: 0,
682 a: 102,
683 }), button: StyleBackgroundContent::Color(ColorU::TRANSPARENT),
685 corner: StyleBackgroundContent::Color(ColorU::TRANSPARENT),
686 resizer: StyleBackgroundContent::Color(ColorU::TRANSPARENT),
687 clip_to_container_border: true, scroll_behavior: ScrollBehavior::Smooth,
689 overscroll_behavior_x: OverscrollBehavior::Auto,
690 overscroll_behavior_y: OverscrollBehavior::Auto,
691 overflow_scrolling: OverflowScrolling::Auto,
692};
693
694pub const SCROLLBAR_IOS_DARK: ScrollbarInfo = ScrollbarInfo {
696 width: LayoutWidth::Px(crate::props::basic::pixel::PixelValue::const_px(7)),
697 padding_left: LayoutPaddingLeft {
698 inner: crate::props::basic::pixel::PixelValue::const_px(0),
699 },
700 padding_right: LayoutPaddingRight {
701 inner: crate::props::basic::pixel::PixelValue::const_px(0),
702 },
703 track: StyleBackgroundContent::Color(ColorU::TRANSPARENT),
704 thumb: StyleBackgroundContent::Color(ColorU {
705 r: 255,
706 g: 255,
707 b: 255,
708 a: 102,
709 }), button: StyleBackgroundContent::Color(ColorU::TRANSPARENT),
711 corner: StyleBackgroundContent::Color(ColorU::TRANSPARENT),
712 resizer: StyleBackgroundContent::Color(ColorU::TRANSPARENT),
713 clip_to_container_border: true, scroll_behavior: ScrollBehavior::Smooth,
715 overscroll_behavior_x: OverscrollBehavior::Auto,
716 overscroll_behavior_y: OverscrollBehavior::Auto,
717 overflow_scrolling: OverflowScrolling::Auto,
718};
719
720pub const SCROLLBAR_ANDROID_LIGHT: ScrollbarInfo = ScrollbarInfo {
722 width: LayoutWidth::Px(crate::props::basic::pixel::PixelValue::const_px(6)),
723 padding_left: LayoutPaddingLeft {
724 inner: crate::props::basic::pixel::PixelValue::const_px(0),
725 },
726 padding_right: LayoutPaddingRight {
727 inner: crate::props::basic::pixel::PixelValue::const_px(0),
728 },
729 track: StyleBackgroundContent::Color(ColorU::TRANSPARENT),
730 thumb: StyleBackgroundContent::Color(ColorU {
731 r: 0,
732 g: 0,
733 b: 0,
734 a: 102,
735 }), button: StyleBackgroundContent::Color(ColorU::TRANSPARENT),
737 corner: StyleBackgroundContent::Color(ColorU::TRANSPARENT),
738 resizer: StyleBackgroundContent::Color(ColorU::TRANSPARENT),
739 clip_to_container_border: true, scroll_behavior: ScrollBehavior::Smooth,
741 overscroll_behavior_x: OverscrollBehavior::Contain,
742 overscroll_behavior_y: OverscrollBehavior::Auto,
743 overflow_scrolling: OverflowScrolling::Auto,
744};
745
746pub const SCROLLBAR_ANDROID_DARK: ScrollbarInfo = ScrollbarInfo {
748 width: LayoutWidth::Px(crate::props::basic::pixel::PixelValue::const_px(6)),
749 padding_left: LayoutPaddingLeft {
750 inner: crate::props::basic::pixel::PixelValue::const_px(0),
751 },
752 padding_right: LayoutPaddingRight {
753 inner: crate::props::basic::pixel::PixelValue::const_px(0),
754 },
755 track: StyleBackgroundContent::Color(ColorU::TRANSPARENT),
756 thumb: StyleBackgroundContent::Color(ColorU {
757 r: 255,
758 g: 255,
759 b: 255,
760 a: 102,
761 }), button: StyleBackgroundContent::Color(ColorU::TRANSPARENT),
763 corner: StyleBackgroundContent::Color(ColorU::TRANSPARENT),
764 resizer: StyleBackgroundContent::Color(ColorU::TRANSPARENT),
765 clip_to_container_border: true, scroll_behavior: ScrollBehavior::Smooth,
767 overscroll_behavior_x: OverscrollBehavior::Contain,
768 overscroll_behavior_y: OverscrollBehavior::Auto,
769 overflow_scrolling: OverflowScrolling::Auto,
770};
771
772#[derive(Clone, PartialEq)]
775pub enum LayoutScrollbarWidthParseError<'a> {
776 InvalidValue(&'a str),
777}
778impl_debug_as_display!(LayoutScrollbarWidthParseError<'a>);
779impl_display! { LayoutScrollbarWidthParseError<'a>, {
780 InvalidValue(v) => format!("Invalid scrollbar-width value: \"{}\"", v),
781}}
782
783#[derive(Debug, Clone, PartialEq)]
784pub enum LayoutScrollbarWidthParseErrorOwned {
785 InvalidValue(String),
786}
787impl<'a> LayoutScrollbarWidthParseError<'a> {
788 pub fn to_contained(&self) -> LayoutScrollbarWidthParseErrorOwned {
789 match self {
790 Self::InvalidValue(s) => {
791 LayoutScrollbarWidthParseErrorOwned::InvalidValue(s.to_string())
792 }
793 }
794 }
795}
796impl LayoutScrollbarWidthParseErrorOwned {
797 pub fn to_shared<'a>(&'a self) -> LayoutScrollbarWidthParseError<'a> {
798 match self {
799 Self::InvalidValue(s) => LayoutScrollbarWidthParseError::InvalidValue(s.as_str()),
800 }
801 }
802}
803
804#[cfg(feature = "parser")]
805pub fn parse_layout_scrollbar_width<'a>(
806 input: &'a str,
807) -> Result<LayoutScrollbarWidth, LayoutScrollbarWidthParseError<'a>> {
808 match input.trim() {
809 "auto" => Ok(LayoutScrollbarWidth::Auto),
810 "thin" => Ok(LayoutScrollbarWidth::Thin),
811 "none" => Ok(LayoutScrollbarWidth::None),
812 _ => Err(LayoutScrollbarWidthParseError::InvalidValue(input)),
813 }
814}
815
816#[derive(Clone, PartialEq)]
817pub enum StyleScrollbarColorParseError<'a> {
818 InvalidValue(&'a str),
819 Color(CssColorParseError<'a>),
820}
821impl_debug_as_display!(StyleScrollbarColorParseError<'a>);
822impl_display! { StyleScrollbarColorParseError<'a>, {
823 InvalidValue(v) => format!("Invalid scrollbar-color value: \"{}\"", v),
824 Color(e) => format!("Invalid color in scrollbar-color: {}", e),
825}}
826impl_from!(CssColorParseError<'a>, StyleScrollbarColorParseError::Color);
827
828#[derive(Debug, Clone, PartialEq)]
829pub enum StyleScrollbarColorParseErrorOwned {
830 InvalidValue(String),
831 Color(CssColorParseErrorOwned),
832}
833impl<'a> StyleScrollbarColorParseError<'a> {
834 pub fn to_contained(&self) -> StyleScrollbarColorParseErrorOwned {
835 match self {
836 Self::InvalidValue(s) => {
837 StyleScrollbarColorParseErrorOwned::InvalidValue(s.to_string())
838 }
839 Self::Color(e) => StyleScrollbarColorParseErrorOwned::Color(e.to_contained()),
840 }
841 }
842}
843impl StyleScrollbarColorParseErrorOwned {
844 pub fn to_shared<'a>(&'a self) -> StyleScrollbarColorParseError<'a> {
845 match self {
846 Self::InvalidValue(s) => StyleScrollbarColorParseError::InvalidValue(s.as_str()),
847 Self::Color(e) => StyleScrollbarColorParseError::Color(e.to_shared()),
848 }
849 }
850}
851
852#[cfg(feature = "parser")]
853pub fn parse_style_scrollbar_color<'a>(
854 input: &'a str,
855) -> Result<StyleScrollbarColor, StyleScrollbarColorParseError<'a>> {
856 let input = input.trim();
857 if input == "auto" {
858 return Ok(StyleScrollbarColor::Auto);
859 }
860
861 let mut parts = input.split_whitespace();
862 let thumb_str = parts
863 .next()
864 .ok_or(StyleScrollbarColorParseError::InvalidValue(input))?;
865 let track_str = parts
866 .next()
867 .ok_or(StyleScrollbarColorParseError::InvalidValue(input))?;
868
869 if parts.next().is_some() {
870 return Err(StyleScrollbarColorParseError::InvalidValue(input));
871 }
872
873 let thumb = parse_css_color(thumb_str)?;
874 let track = parse_css_color(track_str)?;
875
876 Ok(StyleScrollbarColor::Custom(ScrollbarColorCustom {
877 thumb,
878 track,
879 }))
880}
881
882#[derive(Clone, PartialEq)]
883pub enum CssScrollbarStyleParseError<'a> {
884 Invalid(&'a str),
885}
886
887impl_debug_as_display!(CssScrollbarStyleParseError<'a>);
888impl_display! { CssScrollbarStyleParseError<'a>, {
889 Invalid(e) => format!("Invalid scrollbar style: \"{}\"", e),
890}}
891
892#[derive(Debug, Clone, PartialEq)]
893pub enum CssScrollbarStyleParseErrorOwned {
894 Invalid(String),
895}
896
897impl<'a> CssScrollbarStyleParseError<'a> {
898 pub fn to_contained(&self) -> CssScrollbarStyleParseErrorOwned {
899 match self {
900 CssScrollbarStyleParseError::Invalid(s) => {
901 CssScrollbarStyleParseErrorOwned::Invalid(s.to_string())
902 }
903 }
904 }
905}
906
907impl CssScrollbarStyleParseErrorOwned {
908 pub fn to_shared<'a>(&'a self) -> CssScrollbarStyleParseError<'a> {
909 match self {
910 CssScrollbarStyleParseErrorOwned::Invalid(s) => {
911 CssScrollbarStyleParseError::Invalid(s.as_str())
912 }
913 }
914 }
915}
916
917#[cfg(feature = "parser")]
918pub fn parse_scrollbar_style<'a>(
919 _input: &'a str,
920) -> Result<ScrollbarStyle, CssScrollbarStyleParseError<'a>> {
921 Ok(ScrollbarStyle::default())
924}
925
926pub fn resolve_scrollbar_style(
935 scrollbar_width: Option<&LayoutScrollbarWidth>,
936 scrollbar_color: Option<&StyleScrollbarColor>,
937 webkit_scrollbar_style: Option<&ScrollbarStyle>,
938) -> ComputedScrollbarStyle {
939 let final_width = scrollbar_width
940 .cloned()
941 .unwrap_or(LayoutScrollbarWidth::Auto);
942 let final_color = scrollbar_color
943 .cloned()
944 .unwrap_or(StyleScrollbarColor::Auto);
945
946 if final_width != LayoutScrollbarWidth::Auto || final_color != StyleScrollbarColor::Auto {
948 let width = match final_width {
949 LayoutScrollbarWidth::None => None,
950 LayoutScrollbarWidth::Thin => Some(LayoutWidth::Px(PixelValue::px(8.0))),
952 LayoutScrollbarWidth::Auto => Some(
954 webkit_scrollbar_style
955 .map_or_else(|| ScrollbarInfo::default().width, |s| s.vertical.width.clone()),
956 ),
957 };
958
959 let (thumb_color, track_color) = match final_color {
960 StyleScrollbarColor::Custom(c) => (Some(c.thumb), Some(c.track)),
961 StyleScrollbarColor::Auto => (None, None), };
963
964 return ComputedScrollbarStyle {
965 width: width.clone(),
966 thumb_color,
967 track_color,
968 };
969 }
970
971 if let Some(webkit_style) = webkit_scrollbar_style {
973 let info = &webkit_style.vertical;
975
976 let width_pixels = match info.width {
978 LayoutWidth::Px(px) => {
979 use crate::props::basic::pixel::DEFAULT_FONT_SIZE;
980 px.to_pixels_internal(0.0, DEFAULT_FONT_SIZE)
981 }
982 _ => 8.0, };
984 if width_pixels <= 0.0 {
985 return ComputedScrollbarStyle {
986 width: None,
987 thumb_color: None,
988 track_color: None,
989 };
990 }
991
992 let thumb = match &info.thumb {
993 StyleBackgroundContent::Color(c) => Some(*c),
994 _ => None, };
996
997 let track = match &info.track {
998 StyleBackgroundContent::Color(c) => Some(*c),
999 _ => None,
1000 };
1001
1002 return ComputedScrollbarStyle {
1003 width: Some(info.width.clone()),
1004 thumb_color: thumb,
1005 track_color: track,
1006 };
1007 }
1008
1009 ComputedScrollbarStyle::default()
1011}
1012
1013#[cfg(all(test, feature = "parser"))]
1014mod tests {
1015 use super::*;
1016 use crate::props::{basic::color::ColorU, layout::dimensions::LayoutWidth};
1017
1018 #[test]
1021 fn test_parse_scrollbar_width() {
1022 assert_eq!(
1023 parse_layout_scrollbar_width("auto").unwrap(),
1024 LayoutScrollbarWidth::Auto
1025 );
1026 assert_eq!(
1027 parse_layout_scrollbar_width("thin").unwrap(),
1028 LayoutScrollbarWidth::Thin
1029 );
1030 assert_eq!(
1031 parse_layout_scrollbar_width("none").unwrap(),
1032 LayoutScrollbarWidth::None
1033 );
1034 assert!(parse_layout_scrollbar_width("thick").is_err());
1035 }
1036
1037 #[test]
1038 fn test_parse_scrollbar_color() {
1039 assert_eq!(
1040 parse_style_scrollbar_color("auto").unwrap(),
1041 StyleScrollbarColor::Auto
1042 );
1043
1044 let custom = parse_style_scrollbar_color("red blue").unwrap();
1045 assert_eq!(
1046 custom,
1047 StyleScrollbarColor::Custom(ScrollbarColorCustom {
1048 thumb: ColorU::RED,
1049 track: ColorU::BLUE
1050 })
1051 );
1052
1053 let custom_hex = parse_style_scrollbar_color("#ff0000 #0000ff").unwrap();
1054 assert_eq!(
1055 custom_hex,
1056 StyleScrollbarColor::Custom(ScrollbarColorCustom {
1057 thumb: ColorU::RED,
1058 track: ColorU::BLUE
1059 })
1060 );
1061
1062 assert!(parse_style_scrollbar_color("red").is_err());
1063 assert!(parse_style_scrollbar_color("red blue green").is_err());
1064 }
1065
1066 fn get_webkit_style() -> ScrollbarStyle {
1070 let mut info = ScrollbarInfo::default();
1071 info.width = LayoutWidth::px(15.0);
1072 info.thumb = StyleBackgroundContent::Color(ColorU::GREEN);
1073 info.track = StyleBackgroundContent::Color(ColorU::new_rgb(100, 100, 100));
1074 ScrollbarStyle {
1075 horizontal: info.clone(),
1076 vertical: info,
1077 }
1078 }
1079
1080 #[test]
1081 fn test_resolve_standard_overrides_webkit() {
1082 let width = LayoutScrollbarWidth::Thin;
1083 let color = StyleScrollbarColor::Custom(ScrollbarColorCustom {
1084 thumb: ColorU::RED,
1085 track: ColorU::BLUE,
1086 });
1087 let webkit_style = get_webkit_style();
1088
1089 let resolved = resolve_scrollbar_style(Some(&width), Some(&color), Some(&webkit_style));
1090
1091 assert_eq!(resolved.width, Some(LayoutWidth::px(8.0)));
1093 assert_eq!(resolved.thumb_color, Some(ColorU::RED));
1094 assert_eq!(resolved.track_color, Some(ColorU::BLUE));
1095 }
1096
1097 #[test]
1098 fn test_resolve_standard_auto_falls_back_to_webkit() {
1099 let width = LayoutScrollbarWidth::Auto;
1100 let color = StyleScrollbarColor::Auto;
1101 let webkit_style = get_webkit_style();
1102
1103 let resolved = resolve_scrollbar_style(Some(&width), Some(&color), Some(&webkit_style));
1104
1105 assert_eq!(resolved.width, Some(LayoutWidth::px(15.0)));
1106 assert_eq!(resolved.thumb_color, Some(ColorU::GREEN));
1107 assert_eq!(resolved.track_color, Some(ColorU::new_rgb(100, 100, 100)));
1108 }
1109
1110 #[test]
1111 fn test_resolve_no_styles_uses_default() {
1112 let resolved = resolve_scrollbar_style(None, None, None);
1113 assert_eq!(resolved, ComputedScrollbarStyle::default());
1114 }
1115
1116 #[test]
1117 fn test_resolve_scrollbar_width_none() {
1118 let width = LayoutScrollbarWidth::None;
1119 let webkit_style = get_webkit_style();
1120
1121 let resolved = resolve_scrollbar_style(Some(&width), None, Some(&webkit_style));
1122 assert_eq!(resolved.width, None);
1123 }
1124
1125 #[test]
1126 fn test_resolve_webkit_display_none_equivalent() {
1127 let mut webkit_style = get_webkit_style();
1128 webkit_style.vertical.width = LayoutWidth::px(0.0);
1129
1130 let resolved = resolve_scrollbar_style(None, None, Some(&webkit_style));
1131 assert_eq!(resolved.width, None);
1132 }
1133
1134 #[test]
1135 fn test_resolve_only_color_is_set() {
1136 let color = StyleScrollbarColor::Custom(ScrollbarColorCustom {
1137 thumb: ColorU::RED,
1138 track: ColorU::BLUE,
1139 });
1140 let webkit_style = get_webkit_style();
1141
1142 let resolved = resolve_scrollbar_style(None, Some(&color), Some(&webkit_style));
1143
1144 assert_eq!(resolved.width, Some(LayoutWidth::px(15.0)));
1146 assert_eq!(resolved.thumb_color, Some(ColorU::RED));
1147 assert_eq!(resolved.track_color, Some(ColorU::BLUE));
1148 }
1149
1150 #[test]
1151 fn test_resolve_only_width_is_set() {
1152 let width = LayoutScrollbarWidth::Thin;
1153 let webkit_style = get_webkit_style();
1154
1155 let resolved = resolve_scrollbar_style(Some(&width), None, Some(&webkit_style));
1156
1157 assert_eq!(resolved.width, Some(LayoutWidth::px(8.0)));
1159 assert_eq!(resolved.thumb_color, None);
1160 assert_eq!(resolved.track_color, None);
1161 }
1162}