docs: reorganize into en/ru and add English translation

This commit is contained in:
Glint Dev
2026-07-24 22:04:29 +03:00
parent 938f05f59d
commit 35f92435a3
20 changed files with 4651 additions and 0 deletions

482
docs/ru/modules/02-style.md Normal file
View File

@@ -0,0 +1,482 @@
# Модуль стилей: `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.