Skip to main content

atmos/os_lib/web_engine/
scrollbar.rs

1//! W3C CSS Scrollbars Module Level 1
2//! - `scrollbar-width: auto | thin | none`
3//! - `scrollbar-color: auto | <color> <color>` (<thumb-color> <track-color>)
4
5use alloc::string::{String, ToString};
6use alloc::vec::Vec;
7use crate::os_lib::css::parse_color;
8
9/// CSS `scrollbar-width` の設定値
10#[derive(Clone, Copy, Debug, PartialEq, Eq, Default)]
11pub enum ScrollbarWidth {
12    #[default]
13    Auto,
14    Thin,
15    None,
16}
17
18impl ScrollbarWidth {
19    pub fn parse(s: &str) -> Option<Self> {
20        match s.trim().to_ascii_lowercase().as_str() {
21            "auto" => Some(ScrollbarWidth::Auto),
22            "thin" => Some(ScrollbarWidth::Thin),
23            "none" => Some(ScrollbarWidth::None),
24            _ => None,
25        }
26    }
27
28    pub fn resolve(s: &str) -> Self {
29        Self::parse(s).unwrap_or(ScrollbarWidth::Auto)
30    }
31
32    /// つまみ(thumb)の描画幅 (px)
33    pub fn thumb_width_px(&self) -> i32 {
34        match self {
35            ScrollbarWidth::Auto => 6,
36            ScrollbarWidth::Thin => 3,
37            ScrollbarWidth::None => 0,
38        }
39    }
40
41    /// トラック(背景・判定領域)の幅 (px)
42    pub fn track_width_px(&self) -> i32 {
43        match self {
44            ScrollbarWidth::Auto => 10,
45            ScrollbarWidth::Thin => 5,
46            ScrollbarWidth::None => 0,
47        }
48    }
49
50    pub fn is_none(&self) -> bool {
51        matches!(self, ScrollbarWidth::None)
52    }
53}
54
55/// CSS `scrollbar-color` の色ペア (thumb, track)
56#[derive(Clone, Copy, Debug, PartialEq, Eq)]
57pub struct ScrollbarColor {
58    pub thumb: u32,
59    pub track: u32,
60}
61
62#[derive(Clone, Copy, Debug, PartialEq, Eq, Default)]
63pub enum ScrollbarColorMode {
64    #[default]
65    Auto,
66    Custom(ScrollbarColor),
67}
68
69/// 括弧ネスト(`rgb(...)`, `rgba(...)`, `hsl(...)` 等)を考慮して空白区切りで2つのカラートークンに分割する。
70/// 仕様上 `<thumb-color> <track-color>` の2トークンちょうどでなければならない。
71pub fn split_scrollbar_color_tokens(val: &str) -> Option<(String, String)> {
72    let s = val.trim();
73    if s.is_empty() {
74        return None;
75    }
76    let mut tokens = Vec::new();
77    let mut current = String::new();
78    let mut depth = 0;
79
80    for ch in s.chars() {
81        if ch == '(' {
82            depth += 1;
83            current.push(ch);
84        } else if ch == ')' {
85            if depth > 0 {
86                depth -= 1;
87            }
88            current.push(ch);
89        } else if ch.is_whitespace() && depth == 0 {
90            if !current.trim().is_empty() {
91                tokens.push(current.trim().to_string());
92                current.clear();
93            }
94        } else {
95            current.push(ch);
96        }
97    }
98    if !current.trim().is_empty() {
99        tokens.push(current.trim().to_string());
100    }
101
102    if tokens.len() == 2 {
103        Some((tokens.remove(0), tokens.remove(0)))
104    } else {
105        None
106    }
107}
108
109/// CSS `scrollbar-color` プロパティをパースする。
110/// `auto` または `<thumb-color> <track-color>`。不正値は None を返す。
111pub fn parse_scrollbar_color(val: &str) -> Option<ScrollbarColorMode> {
112    let s = val.trim();
113    if s.is_empty() {
114        return None;
115    }
116    if s.eq_ignore_ascii_case("auto") {
117        return Some(ScrollbarColorMode::Auto);
118    }
119
120    let (thumb_str, track_str) = split_scrollbar_color_tokens(s)?;
121    let thumb = parse_color(&thumb_str)?;
122    let track = parse_color(&track_str)?;
123    Some(ScrollbarColorMode::Custom(ScrollbarColor { thumb, track }))
124}