# Модуль стилей: `src/interpreter/style.rs` Система CSS-подобных стилей для Glint: парсинг селекторов, построение индекса, каскадное разрешение свойств и кеширование вычисленных стилей. --- ## Перечисления-помощники ### `SizeValue` ```rust pub enum SizeValue { Px(f32), Percent(f32), } ``` Абсолютное (`Px`) или относительное (`Percent`) значение размера. ```rust impl SizeValue { pub fn resolve(self, relative_to: Option) -> f32 } ``` `Percent` разрешается относительно `relative_to`; `Px` возвращается как есть. При `Percent` и `relative_to = None` возвращается процент как число. ### `Overflow` ```rust #[derive(Debug, Clone, Copy, PartialEq, Eq, Default)] pub enum Overflow { #[default] Visible, Hidden, Scroll, Auto, } ``` Используется для `overflow-x`, `overflow-y`. ### `Position` ```rust #[derive(Debug, Clone, Copy, PartialEq, Eq, Default)] pub enum Position { #[default] Static, Relative, Absolute, Sticky, Fixed, } ``` Определяет схему позиционирования элемента. ### `Display` ```rust #[derive(Debug, Clone, Copy, PartialEq, Eq, Default)] pub enum Display { #[default] Block, Flex, Grid, Inline, None, } ``` ### `LayoutDirection` ```rust #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub enum LayoutDirection { Column, Row, Grid, } ``` ### `ContentAlign` ```rust #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub enum ContentAlign { Start, Center, End, } ``` ### `TextAlign` ```rust #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub enum TextAlign { Left, Center, Right, } impl From for iced::alignment::Horizontal ``` --- ## Парсинг селекторов ### `AttributeSelector` ```rust pub enum AttributeSelector { Exists(String), Equals(String, String), } ``` Селектор атрибута: `[attr]` или `[attr=value]`. ### `Combinator` ```rust #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub enum Combinator { Descendant, // пробел Child, // > NextSibling, // + Subsequent, // ~ } ``` ### `split_selectors(input: &str) -> Vec` Разделяет группу селекторов по запятой с учётом вложенности скобок. Например, `"Button, Label:hover"` → `["Button", "Label:hover"]`. ### `CompoundSelector` ```rust #[derive(Debug, Clone)] pub struct CompoundSelector { pub tag: Option, pub id: Option, pub classes: Vec, pub pseudo_classes: Vec, pub attributes: Vec, } ``` **Методы:** - `CompoundSelector::parse(input: &str) -> Self` — парсит простой селектор вида `Button#id.primary:hover[named=val]`. Разбирает посимвольно, группируя части по первому символу (`#`, `.`, `:`, `[`). - `fn specificity(&self) -> (u32, u32, u32)` — возвращает специфичность по правилу CSS: (id, class+attr+pseudo, tag). - `pub fn matches_element(type_name, el_id, el_classes, active_pseudo, structural, el_attributes) -> bool` — проверяет, соответствует ли элемент данному простому селектору. Учитывает: - Совпадение `tag` (или `*`) - Совпадение `id` - Наличие всех `classes` - Наличие всех `attributes` (Exists / Equals) - Псевдоклассы: `first-child`, `last-child`, `first-of-type`, `empty`, `root`, `nth-child(...)`; остальные сравниваются с `active_pseudo`. ### `ComplexSelector` ```rust #[derive(Debug, Clone)] pub struct ComplexSelector { pub compounds: Vec, pub combinators: Vec, } ``` **Методы:** - `ComplexSelector::parse(input: &str) -> Self` — парсит сложный селектор (например `Panel > Button.primary`). Разбивает на части по комбинаторам (`>`, `+`, `~`, пробел), парсит каждую как `CompoundSelector`. - `fn check_compound_against(&self, i, info: &AncestorInfo) -> bool` — проверяет, соответствует ли `i`-й compound переданной информации о предке. - `pub fn matches(type_name, el_id, el_classes, active_pseudo, structural, ancestors, preceding_siblings, el_attributes) -> bool` — полная проверка сложного селектора: последний compound — целевой элемент, остальные — предки/соседи в соответствии с комбинаторами. - `pub fn specificity(&self) -> (u32, u32, u32)` — сумма специфичностей всех compounds. - `pub fn as_simple(&self) -> Option<&CompoundSelector>` — если compounds содержит ровно один элемент, возвращает его; иначе `None`. - `pub fn has_pseudo_class(&self, pc: &str) -> bool` — есть ли среди compounds указанный псевдокласс. --- ## Вспомогательные структуры ### `AncestorInfo` ```rust #[derive(Debug, Clone)] pub struct AncestorInfo { pub type_name: String, pub id: Option, pub classes: Vec, } ``` Методы: - `AncestorInfo::new(type_name, classes) -> Self` - `AncestorInfo::new_with_id(type_name, id, classes) -> Self` Используется при проверке сложных селекторов — описывает предка или соседний элемент. ### `StructuralContext` ```rust #[derive(Debug, Clone, Default)] pub struct StructuralContext { pub sibling_index: usize, // 0-based pub sibling_total: usize, pub type_index: usize, // среди элементов того же типа pub type_total: usize, pub has_children: bool, pub is_root: bool, } ``` Используется для разрешения структурных псевдоклассов (`first-child`, `nth-child`, `empty`, `root`). --- ## `StyleRule` ```rust #[derive(Debug, Clone)] pub struct StyleRule { pub selector: ComplexSelector, pub properties: HashMap, } impl StyleRule { pub fn build(selector_str: String, properties: HashMap) -> Self } ``` --- ## `StyleIndex` ```rust pub type RuleId = usize; #[derive(Debug, Clone)] pub struct StyleIndex { pub by_tag: HashMap>, pub by_class: HashMap>, pub by_id: HashMap>, pub by_tag_class: HashMap<(String, String), Vec>, pub by_tag_id: HashMap<(String, String), Vec>, pub complex_rules: Vec<(RuleId, RuleId)>, pub universal_rules: Vec, pub rule_specificities: Vec<(u32, u32, u32)>, pub rules: Vec, pub epoch: u64, } impl StyleIndex { pub fn new() -> Self } ``` Индекс для быстрого поиска правил. Строится в `StyleSheet::build_index`: - `by_tag` / `by_class` / `by_id` / `by_tag_class` / `by_tag_id` — индексы для простых селекторов - `complex_rules` — правила со сложными селекторами (всегда проверяются в лоб) - `universal_rules` — правила с `*` - `rule_specificities` — кеш специфичностей - `epoch` — монотонно возрастающий счётчик для инвалидации кеша --- ## `StyleCache` ```rust #[derive(Debug, Clone)] pub struct StyleCache { entries: HashMap, max_entries: usize, } impl StyleCache { pub fn new(max_entries: usize) -> Self pub fn get_or_compute( &mut self, type_name: &str, props: &[(Cow<'_, str>, Cow<'_, str>)], epoch: u64, matched_sheets: &[&HashMap], ) -> ComputedStyle pub fn clear(&mut self) } ``` Кеш вычисленных стилей. Ключ — хеш от `type_name`, inline-свойств и `epoch`. При превышении `max_entries` кеш полностью очищается. --- ## `StyleSheet` ```rust #[derive(Debug)] pub struct StyleSheet { rules: Vec, index: Option, epoch: u64, cache: Mutex, } ``` Главный тип модуля. Содержит список правил, опциональный индекс и кеш. Реализует `Clone` (с новым пустым кешем) и `Default`. **Методы:** - `StyleSheet::new() -> Self` — создаёт пустой лист. - `pub fn add_rule(&mut self, selector: String, properties: HashMap)` — добавляет правило. Пропускает селектор через `split_selectors` (поддержка групп через запятую). Сбрасывает `index = None`. - `pub fn build_index(&mut self)` — перестраивает `StyleIndex`. Увеличивает `epoch`, очищает кеш. Для каждого правила: - Вычисляет специфичность - Если селектор простой (1 compound) — индексирует по tag/class/id/атрибутам - Если сложный — помечает как `complex_rules` - Универсальные (`*`) попадают в `universal_rules` - `pub fn has_index(&self) -> bool` - `pub fn query_index(type_name, el_id, el_classes, active_pseudo, structural, ancestors, preceding_siblings, el_attributes) -> Option>>` — использует индекс для быстрого поиска: собирает кандидатов из `universal_rules`, `by_tag`, `by_class`, `by_id`, `complex_rules`; фильтрует через `ComplexSelector::matches`; сортирует по специфичности. - `pub fn matching_rules(...) -> Vec<&HashMap>` — поиск подходящих правил. Пытается `query_index`; если индекса нет — линейный перебор всех `self.rules` с сортировкой. - `pub fn matching_pseudo_rules(pseudo, ...) -> HashMap` — ищет правила, содержащие указанный псевдокласс (например `:hover`). Использует `query_index_for_pseudo` или линейный перебор. - `pub fn compute_cached(type_name, props, matched_sheets) -> ComputedStyle` — вычисляет итоговый стиль через кеш (обёртка над `StyleCache::get_or_compute`). Включает `PerfScope::new("style")`. - `pub fn clear_cache(&self)` — очищает кеш. - `pub fn is_empty(&self) -> bool` — `self.rules.is_empty()`. **Методы с `#[cfg(feature = "parallel")]`:** - `matching_rules_batch(type_names, el_ids, el_classes_list, ...) -> Vec>>` — параллельный batch-поиск через `rayon::par_iter`. --- ## `ComputedStyle` ```rust #[derive(Debug, Clone, Default)] pub struct ComputedStyle { pub font_size: Option, pub color: Option, pub padding: Option, pub padding_top: Option, pub padding_right: Option, pub padding_bottom: Option, pub padding_left: Option, pub margin: Option, pub margin_top: Option, pub margin_right: Option, pub margin_bottom: Option, pub margin_left: Option, pub background: Option, pub spacing: Option, pub border_radius: Option, pub border_width: Option, pub border_color: Option, pub width: Option, pub height: Option, pub min_width: Option, pub max_width: Option, pub min_height: Option, pub max_height: Option, pub direction: Option, pub align_items: Option, pub content_align: Option, pub flex_grow: Option, pub position: Option, pub top: Option, pub right: Option, pub bottom: Option, pub left: Option, pub overflow_x: Option, pub overflow_y: Option, pub display: Option, pub opacity: Option, pub font_weight: Option, pub line_height: Option, pub text_align: Option, } ``` Итоговый вычисленный стиль элемента. Все поля — `Option`; отсутствующее свойство означает «не задано / наследуется от родителя». ### `ComputedStyle::compute()` ```rust pub fn compute( inline: &[(Cow<'_, str>, Cow<'_, str>)], matched_sheets: &[&HashMap], ) -> Self ``` Собирает стиль через `lookup()`: для каждого поля вызывается `lookup(key, inline, matched_sheets)`, затем парсится соответствующей функцией. Особенности: - `padding`/`margin` — сначала ищутся индивидуальные (`-top`, `-right`, и т.д.), потом общие. - `spacing` — альтернативное имя `gap`. - `background` — сначала `background`, затем `background-color`. - `overflow-x`/`overflow-y` — если индивидуальный не найден, применяется общий `overflow`. - `flex_grow` — парсится как `f32`, кастуется в `u16`. ### `ComputedStyle::compute_batch()` ```rust #[cfg(feature = "parallel")] pub fn compute_batch<'a>( pairs: &[(&[(Cow<'_, str>, Cow<'_, str>)], &[&'a HashMap])], ) -> Vec ``` Параллельный batch-вариант через `rayon::par_iter`. ### `ComputedStyle::apply_overrides()` ```rust pub fn apply_overrides(&mut self, sheet: &HashMap) ``` Применяет (перезаписывает) заданный набор свойств поверх существующего стиля. Используется для динамических изменений (например `:hover`-правила, inline-переопределения). ### Как работает `lookup()` ```rust fn lookup<'a>( key: &str, inline: &'a [(Cow<'_, str>, Cow<'_, str>)], matched_sheets: &[&'a HashMap], ) -> Option<&'a str> ``` Порядок разрешения свойства: 1. **Inline-свойства** — перебор пар `(key, value)`. Поддерживает префикс `style:` (т.е. `style:color` эквивалентен `color`). 2. **matched_sheets** — список словарей от подходящих CSS-правил, отсортированный по специфичности. Перебирается с конца (последний — самый специфичный). 3. Возвращается первое найденное значение. --- ## Функции парсинга | Функция | Сигнатура | Описание | |---|---|---| | `parse_size` | `(s: &str) -> Option` | Парсит размер: `"10"` → `Px(10)`, `"50%"` → `Percent(50)`. `auto`, `fill`, `stretch` → `None` | | `parse_color` | `(s: &str) -> Option` | Парсит цвет: `#rgb`, `#rrggbb`, `#rrggbbaa`, имена (`white`, `black`, `transparent`) | | `parse_length` | `(s: &str) -> Option` | Парсит длину Iced: `"fill"`/`"100%"`, `"shrink"`/`"auto"`, `"50"` → `Fixed(50)` | | `parse_overflow` | `(s: &str) -> Option` | `visible`, `hidden`, `scroll`, `auto` | | `parse_position` | `(s: &str) -> Option` | `static`, `relative`, `absolute`, `sticky`, `fixed` | | `parse_display` | `(s: &str) -> Option` | `none`, `block`, `flex`, `grid`, `inline` | | `parse_direction` | `(s: &str) -> Option` | `row`/`horizontal`, `column`/`vertical`, `grid` | | `parse_alignment` | `(s: &str) -> Option` | `start`, `center`, `end` | | `parse_content_align` | `(s: &str) -> Option` | `start`/`left`/`top`, `center`, `end`/`right`/`bottom` | | `parse_opacity` | `(s: &str) -> Option` | Число 0.0–1.0, clamp | | `parse_font_weight` | `(s: &str) -> Option` | Имена: `normal`→400, `bold`→700, `lighter`→300, `bolder`→900; числовые значения | | `parse_text_align` | `(s: &str) -> Option` | `left`, `center`, `right` | ### `resolve_size` ```rust pub fn resolve_size(v: Option, relative_to: Option) -> Option ``` Удобная обёртка над `SizeValue::resolve`, возвращает `Option`. --- ## Использование в `renderer.rs` Поля `ComputedStyle` активно используются в `/home/faynot/software/glint-runtime/src/renderer.rs`: | Поле | Где используется | |---|---| | `.color` | Цвет текста в кнопках, текстовых полях, Label | | `.background` | Фон контейнеров, кнопок, текстовых полей | | `.padding*` | `iced::Padding` для кнопок, полей ввода, контейнеров | | `.margin*` | Отступы вокруг элементов | | `.border_radius`, `.border_width`, `.border_color` | Рамки кнопок, полей ввода, контейнеров | | `.width`, `.height` | Размеры Scrollable, Column, Row, Image | | `.min_width`, `.max_width`, `.min_height`, `.max_height` | Ограничения размеров | | `.direction` | Направление флекса (Row/Column) | | `.align_items` | Выравнивание дочерних элементов | | `.content_align` | Выравнивание контента | | `.flex_grow` | Flex-grow с `FillPortion` | | `.spacing` | `iced::container::Style` spacing, gap в Row/Column | | `.position` | Static / Fixed / Absolute / Sticky | | `.top`, `.right`, `.bottom`, `.left` | Позиционирование | | `.overflow_x`, `.overflow_y` | Скроллинг (`Scrollable`) | | `.display` | `Display::None` — скрытие элемента | | `.opacity` | Прозрачность | | `.font_weight` | Вес шрифта в Text | | `.line_height` | Межстрочный интервал | | `.text_align` | Горизонтальное выравнивание текста | | `.font_size` | Размер шрифта (передаётся от родителя) | --- ## `nth_matches(expr: &str, n: usize) -> bool` Внутренняя функция для разрешения `:nth-child(an+b)`, `:nth-child(odd)`, `:nth-child(even)` и `:nth-child(<число>)`. Поддерживает отрицательные `a` и `b`. --- ## Связи с другими модулями - `types.rs` — каждый `DomNode` (как элемент, так и текстовый узел) содержит `computed_style: ComputedStyle`. - `renderer.rs` — импортирует `{ComputedStyle, ContentAlign, Display, LayoutDirection, Position, Overflow, StructuralContext, StyleSheet, TextAlign, resolve_size}`. - `mod.rs` — использует `AncestorInfo`, `ComputedStyle`, `StructuralContext` при обходе DOM.