Files
Glint-Runtime/docs/ru/modules/02-style.md

483 lines
20 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Модуль стилей: `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>) -> 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<TextAlign> 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<String>`
Разделяет группу селекторов по запятой с учётом вложенности скобок. Например, `"Button, Label:hover"``["Button", "Label:hover"]`.
### `CompoundSelector`
```rust
#[derive(Debug, Clone)]
pub struct CompoundSelector {
pub tag: Option<String>,
pub id: Option<String>,
pub classes: Vec<String>,
pub pseudo_classes: Vec<String>,
pub attributes: Vec<AttributeSelector>,
}
```
**Методы:**
- `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<CompoundSelector>,
pub combinators: Vec<Combinator>,
}
```
**Методы:**
- `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<String>,
pub classes: Vec<String>,
}
```
Методы:
- `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<String, String>,
}
impl StyleRule {
pub fn build(selector_str: String, properties: HashMap<String, String>) -> Self
}
```
---
## `StyleIndex`
```rust
pub type RuleId = usize;
#[derive(Debug, Clone)]
pub struct StyleIndex {
pub by_tag: HashMap<String, Vec<RuleId>>,
pub by_class: HashMap<String, Vec<RuleId>>,
pub by_id: HashMap<String, Vec<RuleId>>,
pub by_tag_class: HashMap<(String, String), Vec<RuleId>>,
pub by_tag_id: HashMap<(String, String), Vec<RuleId>>,
pub complex_rules: Vec<(RuleId, RuleId)>,
pub universal_rules: Vec<RuleId>,
pub rule_specificities: Vec<(u32, u32, u32)>,
pub rules: Vec<StyleRule>,
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<u64, ComputedStyle>,
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<String, String>],
) -> ComputedStyle
pub fn clear(&mut self)
}
```
Кеш вычисленных стилей. Ключ — хеш от `type_name`, inline-свойств и `epoch`. При превышении `max_entries` кеш полностью очищается.
---
## `StyleSheet`
```rust
#[derive(Debug)]
pub struct StyleSheet {
rules: Vec<StyleRule>,
index: Option<StyleIndex>,
epoch: u64,
cache: Mutex<StyleCache>,
}
```
Главный тип модуля. Содержит список правил, опциональный индекс и кеш. Реализует `Clone` (с новым пустым кешем) и `Default`.
**Методы:**
- `StyleSheet::new() -> Self` — создаёт пустой лист.
- `pub fn add_rule(&mut self, selector: String, properties: HashMap<String, String>)` — добавляет правило. Пропускает селектор через `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<Vec<&HashMap<String, String>>>` — использует индекс для быстрого поиска: собирает кандидатов из `universal_rules`, `by_tag`, `by_class`, `by_id`, `complex_rules`; фильтрует через `ComplexSelector::matches`; сортирует по специфичности.
- `pub fn matching_rules(...) -> Vec<&HashMap<String, String>>` — поиск подходящих правил. Пытается `query_index`; если индекса нет — линейный перебор всех `self.rules` с сортировкой.
- `pub fn matching_pseudo_rules(pseudo, ...) -> HashMap<String, String>` — ищет правила, содержащие указанный псевдокласс (например `: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<Vec<&HashMap<String, String>>>` — параллельный batch-поиск через `rayon::par_iter`.
---
## `ComputedStyle`
```rust
#[derive(Debug, Clone, Default)]
pub struct ComputedStyle {
pub font_size: Option<SizeValue>,
pub color: Option<iced::Color>,
pub padding: Option<SizeValue>,
pub padding_top: Option<SizeValue>,
pub padding_right: Option<SizeValue>,
pub padding_bottom: Option<SizeValue>,
pub padding_left: Option<SizeValue>,
pub margin: Option<SizeValue>,
pub margin_top: Option<SizeValue>,
pub margin_right: Option<SizeValue>,
pub margin_bottom: Option<SizeValue>,
pub margin_left: Option<SizeValue>,
pub background: Option<iced::Color>,
pub spacing: Option<SizeValue>,
pub border_radius: Option<SizeValue>,
pub border_width: Option<SizeValue>,
pub border_color: Option<iced::Color>,
pub width: Option<iced::Length>,
pub height: Option<iced::Length>,
pub min_width: Option<SizeValue>,
pub max_width: Option<SizeValue>,
pub min_height: Option<SizeValue>,
pub max_height: Option<SizeValue>,
pub direction: Option<LayoutDirection>,
pub align_items: Option<iced::Alignment>,
pub content_align: Option<ContentAlign>,
pub flex_grow: Option<u16>,
pub position: Option<Position>,
pub top: Option<SizeValue>,
pub right: Option<SizeValue>,
pub bottom: Option<SizeValue>,
pub left: Option<SizeValue>,
pub overflow_x: Option<Overflow>,
pub overflow_y: Option<Overflow>,
pub display: Option<Display>,
pub opacity: Option<f32>,
pub font_weight: Option<u16>,
pub line_height: Option<SizeValue>,
pub text_align: Option<TextAlign>,
}
```
Итоговый вычисленный стиль элемента. Все поля — `Option`; отсутствующее свойство означает «не задано / наследуется от родителя».
### `ComputedStyle::compute()`
```rust
pub fn compute(
inline: &[(Cow<'_, str>, Cow<'_, str>)],
matched_sheets: &[&HashMap<String, String>],
) -> 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<String, String>])],
) -> Vec<ComputedStyle>
```
Параллельный batch-вариант через `rayon::par_iter`.
### `ComputedStyle::apply_overrides()`
```rust
pub fn apply_overrides(&mut self, sheet: &HashMap<String, String>)
```
Применяет (перезаписывает) заданный набор свойств поверх существующего стиля. Используется для динамических изменений (например `:hover`-правила, inline-переопределения).
### Как работает `lookup()`
```rust
fn lookup<'a>(
key: &str,
inline: &'a [(Cow<'_, str>, Cow<'_, str>)],
matched_sheets: &[&'a HashMap<String, String>],
) -> Option<&'a str>
```
Порядок разрешения свойства:
1. **Inline-свойства** — перебор пар `(key, value)`. Поддерживает префикс `style:` (т.е. `style:color` эквивалентен `color`).
2. **matched_sheets** — список словарей от подходящих CSS-правил, отсортированный по специфичности. Перебирается с конца (последний — самый специфичный).
3. Возвращается первое найденное значение.
---
## Функции парсинга
| Функция | Сигнатура | Описание |
|---|---|---|
| `parse_size` | `(s: &str) -> Option<SizeValue>` | Парсит размер: `"10"``Px(10)`, `"50%"``Percent(50)`. `auto`, `fill`, `stretch``None` |
| `parse_color` | `(s: &str) -> Option<iced::Color>` | Парсит цвет: `#rgb`, `#rrggbb`, `#rrggbbaa`, имена (`white`, `black`, `transparent`) |
| `parse_length` | `(s: &str) -> Option<iced::Length>` | Парсит длину Iced: `"fill"`/`"100%"`, `"shrink"`/`"auto"`, `"50"``Fixed(50)` |
| `parse_overflow` | `(s: &str) -> Option<Overflow>` | `visible`, `hidden`, `scroll`, `auto` |
| `parse_position` | `(s: &str) -> Option<Position>` | `static`, `relative`, `absolute`, `sticky`, `fixed` |
| `parse_display` | `(s: &str) -> Option<Display>` | `none`, `block`, `flex`, `grid`, `inline` |
| `parse_direction` | `(s: &str) -> Option<LayoutDirection>` | `row`/`horizontal`, `column`/`vertical`, `grid` |
| `parse_alignment` | `(s: &str) -> Option<iced::Alignment>` | `start`, `center`, `end` |
| `parse_content_align` | `(s: &str) -> Option<ContentAlign>` | `start`/`left`/`top`, `center`, `end`/`right`/`bottom` |
| `parse_opacity` | `(s: &str) -> Option<f32>` | Число 0.01.0, clamp |
| `parse_font_weight` | `(s: &str) -> Option<u16>` | Имена: `normal`→400, `bold`→700, `lighter`→300, `bolder`→900; числовые значения |
| `parse_text_align` | `(s: &str) -> Option<TextAlign>` | `left`, `center`, `right` |
### `resolve_size`
```rust
pub fn resolve_size(v: Option<SizeValue>, relative_to: Option<f32>) -> Option<f32>
```
Удобная обёртка над `SizeValue::resolve`, возвращает `Option<f32>`.
---
## Использование в `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.