layoutcss_parser/components/
area.rs

1use crate::harmonic::get_harmonic;
2use indoc::formatdoc;
3use std::collections::HashSet;
4
5pub const AREA_STYLE: &str = r#"
6area-l{
7        display: grid;
8    }
9"#;
10
11fn area_gap_style(value: &str, harmonic: String) -> String {
12    formatdoc!(
13        r#"
14        area-l[layout~="gap:{value}"]{{
15            gap: {harmonic};
16        }}
17        "#,
18    )
19}
20
21fn area_gap_x_style(value: &str, harmonic: String) -> String {
22    formatdoc!(
23        r#"
24        area-l[layout~="gap-x:{value}"]{{
25            column-gap: {harmonic};
26        }}
27        "#,
28    )
29}
30
31fn area_gap_y_style(value: &str, harmonic: String) -> String {
32    formatdoc!(
33        r#"
34        area-l[layout~="gap-y:{value}"]{{
35            row-gap: {harmonic};
36        }}
37        "#,
38    )
39}
40
41fn area_grid_template_areas_style(value: &str, template: String) -> String {
42    formatdoc!(
43        r#"
44        area-l[layout~="template:{value}"] {{
45                grid-template-areas: {template};
46            }}
47        "#,
48    )
49}
50
51fn area_grid_area_unit_style(value: &str, unit: char, index: usize) -> String {
52    formatdoc!(
53        r#"
54        area-l[layout~="template:{value}"] > :nth-child({index}) {{
55            grid-area: {unit};
56        }}
57        "#,
58    )
59}
60
61fn area_rows_style(selector: &str, value: &str, template_selector:&str) -> String {
62    formatdoc!(
63        r#"
64        area-l[layout~="template:{template_selector}"]{selector}{{
65            grid-template-rows: {value};
66        }}
67        "#,
68    )
69}
70
71fn area_cols_style(selector: &str, value: &str, template_selector:&str  ) -> String {
72    formatdoc!(
73        r#"
74        area-l[layout~="template:{template_selector}"]{selector}{{
75            grid-template-columns: {value};
76        }}
77        "#,
78    )
79}
80
81/// return the number of rows and cols from a template layout class in a tuple like this (rows,cols)
82/// for example "(a-a-b|a-a-b)" will returns (2,3) because their is 2 rows and 3 columns
83fn count_rows_and_cols(text: &str) -> (usize, usize) {
84    let rows = text.chars().filter(|c| *c == '|').count() + 1;
85    let cols = text
86        .chars()
87        .take_while(|c| *c != '|')
88        .filter(|c| *c == '-')
89        .count()
90        + 1;
91    (rows, cols)
92}
93
94/// return the grid-template-areas value for
95/// a specific template value, so "(a-a-b|a-a-b)" return "\"a a b\" \"a a b\""
96fn grid_template_areas_value(text: &str) -> String {
97    let mut areas = Vec::new();
98    let text_without_parentheses = text.replace("(", "").replace(")", "");
99    for part in text_without_parentheses.split('|') {
100        let area = part
101            .chars()
102            .filter(|c| *c != '-')
103            .map(|c| c.to_string())
104            .collect::<Vec<String>>()
105            .join(" ");
106        areas.push(formatdoc!("\"{}\"", area));
107    }
108    areas.join(" ")
109}
110
111/// return the grid-template-columns value (if pattern is "col-") or grid-template-rows value (if pattern is "row-")
112/// items is the list of col or row classes, pattern is "col-" or "row-" and number is the number of row or col.
113fn grid_template_rows_or_cols_rule(items: &Vec<&str>, pattern: &str, number: usize) -> String {
114    let mut rules: Vec<String> = vec![];
115    // we iterate on 1 to number and for each value
116    // we will check if there is col-x:... or row-x:... associated
117    // if it's the case it will take the value after the ':'
118    // else it will use 1fr
119    for i in 1..=number {
120        let pattern = formatdoc!("{}{}", pattern, i.to_string());
121        if let Some(item) = items.iter().find(|&s| s.starts_with(&pattern)) {
122            let parts: Vec<&str> = item.split(':').collect();
123            if let Some(value) = parts.get(1) {
124                rules.push(value.to_string());
125            }
126        } else {
127            rules.push("1fr".to_string());
128        }
129    }
130    rules.join(" ")
131}
132
133fn grid_template_rows_or_cols_selector(items: &Vec<&str>) -> String {
134    let formatted_items: Vec<String> = items
135        .iter()
136        .map(|item| {
137            formatdoc!(
138                r#"[layout~="{}"]"#,
139                item
140            )
141        })
142        .collect();
143    formatted_items.join("")
144}
145
146fn unique_letters(input: &str) -> Vec<char> {
147    let mut unique_chars = Vec::new();
148    for c in input.chars() {
149        // Exclude '.' and check for uniqueness
150        if c.is_alphabetic() && !unique_chars.contains(&c) {
151            unique_chars.push(c);
152        }
153    }
154    unique_chars.sort();
155    unique_chars
156}
157
158pub fn area_css(
159    template: Option<&str>,
160    rows: Vec<&str>,
161    cols: Vec<&str>,
162    gap: Option<&str>,
163    gap_x: Option<&str>,
164    gap_y: Option<&str>,
165    harmonic_ratio: f64,
166    set: &mut HashSet<String>,
167) {
168    set.insert(AREA_STYLE.to_string());
169    if let Some(template) = template {
170        let template_areas = grid_template_areas_value(template);
171        set.insert(area_grid_template_areas_style(template, template_areas));
172        for (index, letter) in unique_letters(template).into_iter().enumerate() {
173            set.insert(area_grid_area_unit_style(template, letter, index + 1));
174        }
175
176        let (rows_nb, cols_nb) = count_rows_and_cols(template);
177        if !rows.is_empty() {
178            let selector = grid_template_rows_or_cols_selector(&rows);
179            let value = grid_template_rows_or_cols_rule(&rows, "row-", rows_nb);
180            set.insert(area_rows_style(&selector, &value, template));
181        }
182        if !cols.is_empty() {
183            let selector = grid_template_rows_or_cols_selector(&cols );
184            let value = grid_template_rows_or_cols_rule(&cols, "col-", cols_nb);
185            set.insert(area_cols_style(&selector, &value,template));
186        }
187    }
188
189    if let Some(value) = gap {
190        let harmonic_value = get_harmonic(value, harmonic_ratio);
191        set.insert(area_gap_style(value, harmonic_value));
192    }
193    if let Some(value) = gap_x {
194        let harmonic_value = get_harmonic(value, harmonic_ratio);
195        set.insert(area_gap_x_style(value, harmonic_value));
196    }
197    if let Some(value) = gap_y {
198        let harmonic_value = get_harmonic(value, harmonic_ratio);
199        set.insert(area_gap_y_style(value, harmonic_value));
200    }
201}
202
203#[cfg(test)]
204mod tests {
205    use super::*;
206
207    #[test]
208    fn demo_css_area() {
209        let mut css_set: HashSet<String> = HashSet::new();
210        area_css(
211            Some("(a-a-b-b-b|a-a-b-b-b)"),
212            vec!["row-1:800px"],
213            vec!["col-2:300px", "col-4:80px"],
214            Some("1"),
215            None,
216            None,
217            1.618,
218            &mut css_set,
219        );
220        println!("{:?}", css_set);
221    }
222}