docs: reorganize into en/ru and add English translation
This commit is contained in:
182
docs/ru/modules/01-types.md
Normal file
182
docs/ru/modules/01-types.md
Normal file
@@ -0,0 +1,182 @@
|
||||
# Модуль типов: `src/interpreter/types.rs`
|
||||
|
||||
Базовые типы данных рантайма: интернирование строк, Value, DOM-элементы, плоский VDOM и Document.
|
||||
|
||||
---
|
||||
|
||||
## `InternedStr(u32)`
|
||||
|
||||
Новыйтип-обёртка над `u32`. Компактный идентификатор строки для быстрых сравнений.
|
||||
|
||||
```rust
|
||||
pub struct InternedStr(pub u32);
|
||||
```
|
||||
|
||||
**Методы:**
|
||||
- `from_raw(id: u32) -> Self` — константный конструктор
|
||||
- `raw(&self) -> u32` — сырое значение
|
||||
- `eq_str(&self, other: &str) -> bool` — сравнение со строкой через `Interner::lookup`
|
||||
|
||||
**Используется:** определён, но в горячих путях (Element, style matching, properties) не применяется. Фаза 2 не завершена.
|
||||
|
||||
---
|
||||
|
||||
## `Interner`
|
||||
|
||||
Пул строк с выделением уникальных ID. Каждая строка интернируется один раз.
|
||||
|
||||
```rust
|
||||
pub struct Interner {
|
||||
strings: Vec<String>,
|
||||
map: HashMap<String, u32>,
|
||||
next_id: u32,
|
||||
}
|
||||
```
|
||||
|
||||
- `new()` — пустой интернер
|
||||
- `intern(&mut self, s: &str) -> InternedStr` — получить/создать ID
|
||||
- `lookup(&self, id: InternedStr) -> &str` — получить строку по ID
|
||||
- `intern_or_none(&mut self, s: Option<&str>) -> Option<InternedStr>` — опциональное интернирование
|
||||
|
||||
Хранится в `Document::interner`. Доступен через `with_interner()` (thread_local).
|
||||
|
||||
---
|
||||
|
||||
## `Value`
|
||||
|
||||
Типизированное значение переменной. Замена сырым строкам для устранения parse/format round-trip.
|
||||
|
||||
```rust
|
||||
pub enum Value {
|
||||
Str(CompactString),
|
||||
Int(i64),
|
||||
Float(f64),
|
||||
Bool(bool),
|
||||
Array(Vec<Value>),
|
||||
None,
|
||||
}
|
||||
```
|
||||
|
||||
**Методы:**
|
||||
- `as_str(&self) -> Option<&str>` — заимствование строки
|
||||
- `to_owned_string(&self) -> CompactString` — форматирование в строку (используется в renderer)
|
||||
|
||||
**Реализовано:** `From<&str>`, `From<String>`, `From<i64>`, `From<f64>`, `From<bool>`, `From<Vec<T>>`.
|
||||
`PartialEq` — Float сравнивается с `f64::EPSILON`.
|
||||
|
||||
**Используется:** `Document::variables`, конверсии `Value↔Dynamic` в rhei.rs.
|
||||
**НЕ используется:** в стилях (`ComputedStyle::compute` всё ещё принимает `&str`, matched_sheets — `HashMap<String, String>`).
|
||||
|
||||
---
|
||||
|
||||
## `Element<'a>`
|
||||
|
||||
Узел VDOM-дерева. Центральный тип — из него строится UI.
|
||||
|
||||
```rust
|
||||
pub struct Element<'a> {
|
||||
pub type_name: &'a str, // "Button", "Panel", "Text", etc.
|
||||
pub properties: Vec<(Cow<'a, str>, Cow<'a, str>)>, // (key, value)
|
||||
pub children: Vec<Element<'a>>,
|
||||
pub computed_style: ComputedStyle,
|
||||
pub element_id: ElementId,
|
||||
pub content_hash: u64, // Phase 9.1: хэш для кэша виджетов
|
||||
}
|
||||
```
|
||||
|
||||
**Методы (в types.rs):**
|
||||
- `id(&self) -> Option<&str>` — значение HTML-атрибута `id`
|
||||
- `new(type_name)` — свежий элемент с `element_id: u32::MAX`
|
||||
- `new_with_id(type_name, id)` — с заданным ElementId
|
||||
|
||||
**Методы (в renderer.rs):**
|
||||
- `get_prop(&self, key: &str) -> Option<&str>` — значение свойства по ключу
|
||||
- `push_prop(key, val)` — добавить свойство
|
||||
- `set_prop(key, val)` — установить/перезаписать свойство
|
||||
|
||||
---
|
||||
|
||||
## `ComponentDef<'a>`
|
||||
|
||||
Определение компонента из шаблона.
|
||||
|
||||
```rust
|
||||
pub struct ComponentDef<'a> {
|
||||
pub name: String,
|
||||
pub params: Vec<(String, String)>,
|
||||
pub children: Vec<Element<'a>>,
|
||||
}
|
||||
```
|
||||
|
||||
Хранится в `Document::components`. Используется в `evaluate_vdom` при разворачивании
|
||||
компонентов: параметры передаются через переменные, тело компонента вставляется как children.
|
||||
|
||||
---
|
||||
|
||||
## `Document<'a>`
|
||||
|
||||
Полное состояние приложения после загрузки байткода.
|
||||
|
||||
```rust
|
||||
pub struct Document<'a> {
|
||||
pub roots: Vec<Element<'a>>,
|
||||
pub components: HashMap<String, ComponentDef<'a>>,
|
||||
pub variables: HashMap<String, Value>,
|
||||
pub rhei_scripts: Vec<String>,
|
||||
pub stylesheet: StyleSheet,
|
||||
pub interner: Interner,
|
||||
pub tracker: ReactiveTracker,
|
||||
}
|
||||
```
|
||||
|
||||
Создаётся в `Interpreter::run()`. Клонируется при старте Iced-приложения (boot function).
|
||||
Содержит всё необходимое для интерпретации: дерево, стили, переменные, реактивность.
|
||||
|
||||
---
|
||||
|
||||
## `VNode<'a>` и `FlatVDom<'a>`
|
||||
|
||||
Плоское представление VDOM для эффективной сериализации/десериализации.
|
||||
|
||||
```rust
|
||||
pub struct VNode<'a> {
|
||||
pub id: NodeId,
|
||||
pub type_name: &'a str,
|
||||
pub properties: Range<PropertyIdx>,
|
||||
pub children_range: Range<NodeIdx>,
|
||||
pub computed_style: ComputedStyle,
|
||||
pub element_id: ElementId,
|
||||
}
|
||||
|
||||
pub struct FlatVDom<'a> {
|
||||
pub nodes: Vec<VNode<'a>>,
|
||||
pub properties: Vec<(String, String)>,
|
||||
root_indices: Vec<NodeIdx>,
|
||||
}
|
||||
```
|
||||
|
||||
**Методы:**
|
||||
- `new()` — пустой
|
||||
- `from_elements(elements)` — рекурсивно уплощает дерево Element'ов в VNode'ы
|
||||
- `into_elements(self) -> Vec<Element>` — обратная сборка
|
||||
- `get_node(idx)`, `node_count()`, `root_count()`, `root_indices()`
|
||||
|
||||
**Используется:** в тестах (`test_flat_vdom_roundtrip`, `test_flat_vdom_nested`, `test_flat_vdom_empty`).
|
||||
В горячем пути не участвует — VDOM передаётся как `Vec<Element>`.
|
||||
|
||||
---
|
||||
|
||||
## `InterpError`
|
||||
|
||||
Ошибки загрузки байткода.
|
||||
|
||||
```rust
|
||||
pub enum InterpError {
|
||||
BadMagic,
|
||||
UnexpectedEof,
|
||||
InvalidUtf8,
|
||||
UnexpectedPop,
|
||||
}
|
||||
```
|
||||
|
||||
Реализует `Display` и `std::error::Error`. Возвращается из `Interpreter::run()`.
|
||||
482
docs/ru/modules/02-style.md
Normal file
482
docs/ru/modules/02-style.md
Normal 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.0–1.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.
|
||||
92
docs/ru/modules/03-reactive.md
Normal file
92
docs/ru/modules/03-reactive.md
Normal file
@@ -0,0 +1,92 @@
|
||||
# Модуль реактивности: `src/interpreter/reactive.rs`
|
||||
|
||||
Система отслеживания зависимостей между переменными и элементами VDOM.
|
||||
Позволяет пересчитывать только изменившиеся элементы (Phase 3).
|
||||
|
||||
---
|
||||
|
||||
## `ElementId(u32)`
|
||||
|
||||
Уникальный идентификатор элемента в VDOM-дереве. Раздаётся `ReactiveTracker::alloc_id()`
|
||||
во время загрузки шаблона.
|
||||
|
||||
```rust
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
|
||||
pub struct ElementId(pub u32);
|
||||
```
|
||||
|
||||
**Используется как:**
|
||||
- Ключ в `dirty_set: HashSet<ElementId>`
|
||||
- Ключ в `dependencies: HashMap<ElementId, HashSet<String>>`
|
||||
- Поле в `Element::element_id` (связь VDOM → tracker)
|
||||
- Источник для `iced::widget::Id` (Phase 9.2: `format!("ti:{}", id.0)`)
|
||||
- Источник для scrollable_id (`el.element_id.0 as u64`)
|
||||
|
||||
---
|
||||
|
||||
## `ReactiveTracker`
|
||||
|
||||
Граф зависимостей: переменная → список элементов, которые её используют.
|
||||
|
||||
```rust
|
||||
pub struct ReactiveTracker {
|
||||
subscribers: HashMap<String, HashSet<ElementId>>,
|
||||
dependencies: HashMap<ElementId, HashSet<String>>,
|
||||
dirty_set: HashSet<ElementId>,
|
||||
next_id: u32,
|
||||
}
|
||||
```
|
||||
|
||||
**Поля:**
|
||||
- `subscribers` — для каждой переменной: какие ElementId от неё зависят
|
||||
- `dependencies` — для каждого элемента: от каких переменных он зависит (обратная связь)
|
||||
- `dirty_set` — элементы, которые нужно пересчитать в следующем фрейме
|
||||
- `next_id` — счётчик для `alloc_id()`
|
||||
|
||||
### Методы
|
||||
|
||||
| Метод | Описание |
|
||||
|-------|----------|
|
||||
| `new()` | Пустой tracker |
|
||||
| `alloc_id() -> ElementId` | Выделить новый ID, увеличить счётчик |
|
||||
| `add_dependency(element, var_name)` | Зарегистрировать зависимость |
|
||||
| `scan_value(element, value)` | Сканировать строку на `$var` и добавить зависимости |
|
||||
| `on_variable_changed(name)` | Пометить все зависимые элементы как dirty |
|
||||
| `take_dirty_set() -> HashSet<ElementId>` | Забрать dirty_set и очистить |
|
||||
| `is_dirty(id) -> bool` | Проверить, помечен ли элемент |
|
||||
| `reset()` | Очистить все данные |
|
||||
|
||||
### scan_value()
|
||||
|
||||
Парсит строку в поиске `$var_name` паттернов:
|
||||
|
||||
```rust
|
||||
"Hello $name, you are $age years old"
|
||||
// → add_dependency(element, "name")
|
||||
// → add_dependency(element, "age")
|
||||
```
|
||||
|
||||
Используется во время загрузки шаблона для каждой properties строки,
|
||||
содержащей `$`. В результате каждый элемент знает, от каких переменных зависит.
|
||||
|
||||
### Цикл обновления
|
||||
|
||||
```
|
||||
Событие → update() → on_variable_changed("var")
|
||||
→ dirty_set = {element_A, element_B, ...}
|
||||
→ take_dirty_set() → evaluate_vdom_incr(roots, &dirty_set)
|
||||
→ для элементов в dirty_set: пересчитать
|
||||
→ для остальных: вернуть как есть
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Тесты
|
||||
|
||||
| Тест | Что проверяет |
|
||||
|------|--------------|
|
||||
| `test_basic_dependency_tracking` | Два элемента, две переменные, правильность dirty_set |
|
||||
| `test_scan_value` | Парсинг `$var` из строки |
|
||||
| `test_scan_no_vars` | Строка без `$` не создаёт зависимостей |
|
||||
| `test_take_dirty_set` | `take_dirty_set()` очищает внутренний set |
|
||||
| `test_reset` | `reset()` очищает всё |
|
||||
200
docs/ru/modules/04-rhei.md
Normal file
200
docs/ru/modules/04-rhei.md
Normal file
@@ -0,0 +1,200 @@
|
||||
# Модуль Rhai: `src/interpreter/rhei.rs`
|
||||
|
||||
Интеграция скриптового движка [Rhai](https://rhai.rs/) — компиляция, кеширование AST и выполнение выражений/скриптов.
|
||||
|
||||
---
|
||||
|
||||
## `RHEI_PREFIX` — префикс Rhai-выражений
|
||||
|
||||
```rust
|
||||
pub const RHEI_PREFIX: &str = "__rhei:";
|
||||
```
|
||||
|
||||
Константа-маркер для свойств элементов, содержимое которых должно интерпретироваться как Rhai-выражение. Используется в `collect_and_precompile()` для выборки свойств вида `!rhei:...`.
|
||||
|
||||
---
|
||||
|
||||
## `RheiContext` — контекст выполнения Rhai
|
||||
|
||||
```rust
|
||||
pub struct RheiContext {
|
||||
engine: Engine,
|
||||
init_ast: AST,
|
||||
scope: RefCell<Scope<'static>>,
|
||||
action_cache: RefCell<HashMap<String, AST>>,
|
||||
expr_cache: RefCell<HashMap<String, AST>>,
|
||||
}
|
||||
```
|
||||
|
||||
| Поле | Назначение |
|
||||
|---|---|
|
||||
| `engine` | Настроенный экземпляр `rhai::Engine` |
|
||||
| `init_ast` | Объединённое AST всех скриптов инициализации (включая определения функций) |
|
||||
| `scope` | Общая область видимости (`Scope`), разделяемая между вызовами; обёрнута в `RefCell` для interior mutability |
|
||||
| `action_cache` | Кеш скомпилированных скриптов (действий), ключ — исходный код |
|
||||
| `expr_cache` | Кеш скомпилированных выражений, ключ — исходный код |
|
||||
|
||||
---
|
||||
|
||||
## Конструкторы
|
||||
|
||||
### `new(scripts)`
|
||||
|
||||
```rust
|
||||
pub fn new(scripts: &[String]) -> Self
|
||||
```
|
||||
|
||||
Создаёт контекст через `new_empty()` и сразу прекомпилирует все переданные скрипты вызовом `precompile_scripts()`.
|
||||
|
||||
### `new_empty(scripts)`
|
||||
|
||||
```rust
|
||||
fn new_empty(scripts: &[String]) -> Self
|
||||
```
|
||||
|
||||
1. Создаёт `Engine::new()`.
|
||||
2. Настраивает обработчики `on_print` (вывод в stdout с префиксом `[rhei]`) и `on_debug` (вывод в stderr).
|
||||
3. Компилирует все скрипты и сливает их AST в единое дерево через `merge()`. Ошибки компиляции отдельных блоков логируются, но не прерывают процесс.
|
||||
4. Из объединённого AST создаёт модуль (`Module::eval_ast_as_new`) с пустым скопом — в нём регистрируются глобальные функции, определённые в скриптах. Модуль регистрируется в движке как глобальный (`register_global_module`).
|
||||
5. Инициализирует пустой `Scope`, пустые кеши `action_cache` и `expr_cache`.
|
||||
|
||||
---
|
||||
|
||||
## `sync_scope()` — синхронизация переменных
|
||||
|
||||
```rust
|
||||
pub fn sync_scope(&self, variables: &HashMap<String, Value>)
|
||||
```
|
||||
|
||||
Синхронизирует значения из внешнего `HashMap` в Rhai `Scope`:
|
||||
- Если переменная уже есть в скопе и её значение не изменилось — пропускает.
|
||||
- Если переменная есть — обновляет через `set_value()`.
|
||||
- Если переменной нет — добавляет через `push_dynamic()`.
|
||||
|
||||
Преобразование `Value → Dynamic` выполняется через `value_to_dynamic()`.
|
||||
|
||||
---
|
||||
|
||||
## `initialize()` — инициализация
|
||||
|
||||
```rust
|
||||
pub fn initialize(&self, variables: &mut HashMap<String, Value>)
|
||||
```
|
||||
|
||||
1. Синхронизирует переменные через `sync_scope()`.
|
||||
2. Запускает `init_ast` (объединённое AST всех скриптов) через `run_ast_with_scope()`.
|
||||
3. Обходит все переменные скопа через `iter_raw()` и записывает обратно в `HashMap` те, чьи значения изменились.
|
||||
|
||||
---
|
||||
|
||||
## `eval_expr()` — вычисление выражения
|
||||
|
||||
```rust
|
||||
pub fn eval_expr(&self, expr: &str, variables: &HashMap<String, Value>) -> Value
|
||||
```
|
||||
|
||||
1. Синхронизирует переменные.
|
||||
2. Получает (компилирует или берёт из кеша) AST выражения через `get_or_compile_expr()`.
|
||||
3. Выполняет через `eval_ast_with_scope::<Dynamic>()`.
|
||||
4. Преобразует результат `Dynamic → Value` через `dynamic_to_value()`.
|
||||
5. При ошибке возвращает `Value::None`.
|
||||
|
||||
---
|
||||
|
||||
## `eval_condition()` — вычисление условия
|
||||
|
||||
```rust
|
||||
pub fn eval_condition(&self, expr: &str, variables: &HashMap<String, Value>) -> bool
|
||||
```
|
||||
|
||||
Аналогичен `eval_expr()`, но типизирован как `bool`. При ошибке возвращает `false`.
|
||||
|
||||
---
|
||||
|
||||
## `execute_action()` — выполнение скрипта
|
||||
|
||||
```rust
|
||||
pub fn execute_action(&self, script: &str, variables: &mut HashMap<String, Value>)
|
||||
```
|
||||
|
||||
1. Синхронизирует переменные.
|
||||
2. Получает AST скрипта через `get_or_compile_action()`.
|
||||
3. Выполняет через `run_ast_with_scope()`.
|
||||
4. После выполнения обходит скоп и записывает обратно в `HashMap` изменившиеся переменные.
|
||||
|
||||
---
|
||||
|
||||
## Кеширование AST
|
||||
|
||||
### `get_or_compile_action(script)`
|
||||
|
||||
```rust
|
||||
fn get_or_compile_action(&self, script: &str) -> Option<AST>
|
||||
```
|
||||
|
||||
Проверяет `action_cache`. При промахе компилирует через `engine.compile()`, сохраняет в кеш.
|
||||
|
||||
### `get_or_compile_expr(expr)`
|
||||
|
||||
```rust
|
||||
fn get_or_compile_expr(&self, expr: &str) -> Option<AST>
|
||||
```
|
||||
|
||||
Проверяет `expr_cache`. При промахе компилирует через `engine.compile_expression()`, сохраняет в кеш.
|
||||
|
||||
Оба метода при ошибке компиляции логируют её и возвращают `None`.
|
||||
|
||||
---
|
||||
|
||||
## Пакетная прекомпиляция
|
||||
|
||||
```rust
|
||||
pub fn precompile_scripts(&self, scripts: &[String])
|
||||
pub fn precompile_actions(&self, actions: &[String])
|
||||
pub fn precompile_exprs(&self, exprs: &[String])
|
||||
pub fn precompile_all_from_doc(&self, doc: &super::Document)
|
||||
```
|
||||
|
||||
| Метод | Действие |
|
||||
|---|---|
|
||||
| `precompile_scripts` | Компилирует каждый скрипт как действие |
|
||||
| `precompile_actions` | То же, что `precompile_scripts` (алиас) |
|
||||
| `precompile_exprs` | Компилирует каждое выражение |
|
||||
| `precompile_all_from_doc` | Компилирует все `doc.rhei_scripts` и рекурсивно обходит дерево элементов |
|
||||
|
||||
### `collect_and_precompile()`
|
||||
|
||||
```rust
|
||||
fn collect_and_precompile(el: &super::Element, ctx: &RheiContext)
|
||||
```
|
||||
|
||||
Рекурсивно обходит дерево `Element`:
|
||||
- Для свойств, начинающихся с `__on:*` и непустых — компилирует как действие.
|
||||
- Для свойств, начинающихся с `RHEI_PREFIX` (`!rhei:`) — компилирует оставшуюся часть как выражение.
|
||||
|
||||
---
|
||||
|
||||
## Конвертация типов
|
||||
|
||||
### `value_to_dynamic(v: &Value) -> Dynamic`
|
||||
|
||||
```rust
|
||||
Value::Int(i) → Dynamic::from(*i)
|
||||
Value::Float(f) → Dynamic::from(*f)
|
||||
Value::Bool(b) → Dynamic::from(*b)
|
||||
Value::Str(s) → str_to_dynamic(s)
|
||||
Value::None → Dynamic::UNIT
|
||||
Value::Array(a) → Dynamic::from_iter(value_to_dynamic каждого элемента)
|
||||
```
|
||||
|
||||
### `dynamic_to_value(d: &Dynamic) -> Value`
|
||||
|
||||
Проверяет тип через `is_string()`, `is_int()`, `is_float()`, `is_bool()`, `is_array()` в порядке приоритета. Если тип не распознан — возвращает `Value::None`.
|
||||
|
||||
### `str_to_dynamic(s: &str) -> Dynamic`
|
||||
|
||||
Эвристический парсер строки, пробует последовательно:
|
||||
1. `s.parse::<i64>()` — целое число
|
||||
2. `s.parse::<f64>()` — дробное число
|
||||
3. `s.parse::<bool>()` — булево значение
|
||||
4. Иначе — `Dynamic::from(s)` как строка
|
||||
537
docs/ru/modules/05-renderer.md
Normal file
537
docs/ru/modules/05-renderer.md
Normal file
@@ -0,0 +1,537 @@
|
||||
# Модуль рендерера: `src/renderer.rs`
|
||||
|
||||
Преобразует дерево `Element` в виджеты Iced. Отвечает за построение иерархии виджетов, применение боксовой модели, обработку псевдоклассов `:hover`/`:active`, позиционирование (fixed, absolute, sticky) и рендеринг всех встроенных типов элементов.
|
||||
|
||||
---
|
||||
|
||||
## Импорты
|
||||
|
||||
```rust
|
||||
use crate::Message;
|
||||
use crate::interpreter::Element;
|
||||
use crate::interpreter::style::{ComputedStyle, ContentAlign, Display, LayoutDirection,
|
||||
Position, Overflow, StructuralContext, StyleSheet, TextAlign, resolve_size};
|
||||
use iced::widget::container::Style as ContainerStyle;
|
||||
use iced::widget::{
|
||||
button, checkbox, column, container, image, progress_bar, row, scrollable, slider, svg, text,
|
||||
text::Wrapping, text_input,
|
||||
};
|
||||
use iced::{Alignment, Background, Border, Length, Theme};
|
||||
use iced::font::Weight;
|
||||
use std::borrow::Cow;
|
||||
use std::cell::RefCell;
|
||||
use std::collections::HashMap;
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## `WIDGET_ID_CACHE` и `get_or_create_widget_id()`
|
||||
|
||||
```rust
|
||||
thread_local! {
|
||||
static WIDGET_ID_CACHE: RefCell<HashMap<(u32, &'static str), iced::widget::Id>> = ...;
|
||||
}
|
||||
|
||||
fn get_or_create_widget_id(key: u32, prefix: &'static str) -> iced::widget::Id
|
||||
```
|
||||
|
||||
`thread_local`-кеш для `iced::widget::Id`. Ключ — кортеж `(element_id, префикс)`. Строка формируется как `"{prefix}:{key}"` и «протекает» через `Box::leak`, чтобы получить `&'static str`. Используется для `scrollable::id` (префикс `"sc"`) и `text_input::id` (префикс `"ti"`).
|
||||
|
||||
---
|
||||
|
||||
## Методы `Element`
|
||||
|
||||
### `get_prop()`
|
||||
```rust
|
||||
impl<'a> Element<'a> {
|
||||
#[inline]
|
||||
pub fn get_prop(&self, key: &str) -> Option<&str>
|
||||
}
|
||||
```
|
||||
Ищет свойство по ключу в `self.properties`. Возвращает значение или `None`.
|
||||
|
||||
### `push_prop()`
|
||||
```rust
|
||||
pub fn push_prop<K: Into<Cow<'a, str>>, V: Into<Cow<'a, str>>>(&mut self, key: K, val: V)
|
||||
```
|
||||
Добавляет пару `(key, val)` в `self.properties`.
|
||||
|
||||
### `set_prop()`
|
||||
```rust
|
||||
pub fn set_prop<K: Into<Cow<'a, str>>, V: Into<Cow<'a, str>>>(&mut self, key: K, val: V)
|
||||
```
|
||||
Устанавливает свойство: если ключ уже существует — заменяет значение, иначе — добавляет новую пару.
|
||||
|
||||
---
|
||||
|
||||
## `extract_var_binding()`
|
||||
|
||||
```rust
|
||||
fn extract_var_binding(el: &Element, prop: &str) -> Option<String>
|
||||
```
|
||||
|
||||
Ищет свойство вида `__bind:<prop>` и возвращает его значение. Используется для реактивной привязки переменных: `__bind:value` для `Input`, `Toggle`, `Slider`.
|
||||
|
||||
---
|
||||
|
||||
## `collect_hover_active()`
|
||||
|
||||
```rust
|
||||
pub fn collect_hover_active<'a>(
|
||||
el: &'a Element,
|
||||
stylesheet: &StyleSheet,
|
||||
) -> (HashMap<String, String>, HashMap<String, String>)
|
||||
```
|
||||
|
||||
Собирает CSS-свойства для псевдоклассов `:hover` и `:active` для элемента. Вызывает `stylesheet.matching_pseudo_rules()` дважды — для `"hover"` и `"active"`. Возвращает кортеж `(hover_props, active_props)`. Используется в `render_element()` и `make_hoverable()`.
|
||||
|
||||
---
|
||||
|
||||
## `make_hoverable()`
|
||||
|
||||
```rust
|
||||
fn make_hoverable<'a>(
|
||||
widget: iced::Element<'a, crate::Message, Theme, iced::Renderer>,
|
||||
el: &Element,
|
||||
hover_props: &HashMap<String, String>,
|
||||
active_props: &HashMap<String, String>,
|
||||
base_cs: &ComputedStyle,
|
||||
) -> iced::Element<'a, crate::Message, Theme, iced::Renderer>
|
||||
```
|
||||
|
||||
Оборачивает произвольный виджет в `button` для поддержки `:hover`/`:active` стилей. Условия срабатывания:
|
||||
- Есть хотя бы один `hover` или `active` стиль;
|
||||
- У элемента есть обработчик `__on:click`.
|
||||
|
||||
Кнопке назначается `on_press(Message::EventTriggered(...))`. В замыкании `style()` подставляются `apply_overrides` в зависимости от `button::Status`:
|
||||
- `Hovered` → `hover_props`;
|
||||
- `Pressed` → `active_props`, если их нет — `hover_props`.
|
||||
|
||||
Применяется **только для не-Button и не-Input** элементов (строка 815).
|
||||
|
||||
---
|
||||
|
||||
## `render_element()`
|
||||
|
||||
```rust
|
||||
pub fn render_element<'a>(
|
||||
el: &'a Element,
|
||||
parent_color: Option<iced::Color>,
|
||||
parent_font_size: Option<f32>,
|
||||
parent_direction: Option<LayoutDirection>,
|
||||
fixed_layers: &mut Vec<iced::Element<'a, Message, Theme, iced::Renderer>>,
|
||||
abs_layers: &mut Vec<iced::Element<'a, Message, Theme, iced::Renderer>>,
|
||||
sticky_layers: &mut Vec<iced::Element<'a, Message, Theme, iced::Renderer>>,
|
||||
scroll_positions: &HashMap<u64, f32>,
|
||||
container_id: u64,
|
||||
stylesheet: &StyleSheet,
|
||||
) -> Option<iced::Element<'a, Message, Theme, iced::Renderer>>
|
||||
```
|
||||
|
||||
**Главная публичная функция рендеринга.** Возвращает `None` если `display: none`.
|
||||
|
||||
### Общая логика для всех элементов
|
||||
|
||||
1. Собираются `hover_props`, `active_props` через `collect_hover_active()`.
|
||||
2. Определяется тип позиционирования: `is_fixed`, `is_absolute`, `is_sticky`.
|
||||
3. Если заданы `left` + `right` без `width` — `width = Fill`. Если `top` + `bottom` без `height` — `height = Fill`.
|
||||
4. `flex-grow` преобразуется в `FillPortion(grow_value)` по оси родителя.
|
||||
5. Наследуются `current_color` и `current_font_size`.
|
||||
|
||||
### Ветка `"Window"`
|
||||
|
||||
```rust
|
||||
if el.type_name == "Window"
|
||||
```
|
||||
|
||||
- Создаётся `column` со `spacing` (по умолчанию 12px).
|
||||
- Рендерятся дети через `render_children()`.
|
||||
- Применяется `apply_universal_box_model(is_window = true, scrollable_id = window_id)` — окно всегда скроллируемое.
|
||||
- Формируется `iced::widget::stack`:
|
||||
1. Основной поток (main_flow)
|
||||
2. `abs_layers`
|
||||
3. `sticky_layers`
|
||||
4. `fixed_layers`
|
||||
|
||||
Итоговая структура:
|
||||
```
|
||||
stack[
|
||||
container[ scrollable[ container[ column[...] ] ] ]
|
||||
...abs-слои
|
||||
...sticky-слои
|
||||
...fixed-слои
|
||||
]
|
||||
```
|
||||
|
||||
### Ветка `"Panel"`
|
||||
|
||||
Делегирует `render_panel()`. Если есть `abs_layers` — оборачивает в `stack`.
|
||||
|
||||
### Ветка `"Button"`
|
||||
|
||||
Делегирует `render_button()`. Если есть `abs_layers` или `sticky_layers` — оборачивает в `stack`.
|
||||
|
||||
### Ветка `"Input"`
|
||||
|
||||
Делегирует `render_input()` с передачей `hover_props` и `active_props`.
|
||||
|
||||
### Текстовые виджеты: `"Title"`, `"Header"`, `"Text"`, `"Label"`, `"#text"`
|
||||
|
||||
```rust
|
||||
"Title" | "Header" => ...
|
||||
"Text" | "Label" | "#text" => ...
|
||||
```
|
||||
|
||||
- Читают свойство `text` (или пустая строка).
|
||||
- Создают `iced::widget::text` с размером шрифта (24 для Title/Header, 16 для Text).
|
||||
- Применяют `color`, `font_weight` (Light ≤399, Normal 400–599, Bold 600–799, ExtraBold ≥800), `text_align`, `line_height` (с `Wrapping::Word`).
|
||||
- Для Title/Header размер по умолчанию 24px, для Text — 16px.
|
||||
|
||||
### `"Image"`
|
||||
|
||||
- Читает `src`. Если путь начинается с `fs:` — обрезает префикс.
|
||||
- `.svg` → `svg::Handle`, иначе `image::Viewer`.
|
||||
- Размер изображения вычисляется с вычетом padding и border-width.
|
||||
- Для растровых изображений применяется `border_radius`.
|
||||
|
||||
### `"Icon"`
|
||||
|
||||
Выводит символ `🔹` как текст размера 18px (или `current_font_size`). Заглушка.
|
||||
|
||||
### `"Toggle"`
|
||||
|
||||
Делегирует `render_toggle()`.
|
||||
|
||||
### `"Slider"`
|
||||
|
||||
Делегирует `render_slider()`.
|
||||
|
||||
### `"ProgressBar"`
|
||||
|
||||
```rust
|
||||
progress_bar(0.0..=100.0, value)
|
||||
```
|
||||
Свойство `value` парсится как `f32`.
|
||||
|
||||
### `"Divider"`, `"Separator"`
|
||||
|
||||
```rust
|
||||
iced::widget::rule::horizontal(1)
|
||||
```
|
||||
Горизонтальная линия толщиной 1px.
|
||||
|
||||
### Ветка по умолчанию (неизвестный тип)
|
||||
|
||||
- Создаётся `column` со `spacing` (10px).
|
||||
- Рендерятся дети.
|
||||
- Если есть `abs_layers` — оборачиваются в `stack`.
|
||||
- Применяется `apply_universal_box_model()`.
|
||||
- Если есть `sticky_layers` — накладываются поверх через `stack`.
|
||||
- Возвращается `None` (элемент уже записан в `final_widget_opt`).
|
||||
|
||||
### Постобработка для не-Button и не-Input
|
||||
|
||||
```rust
|
||||
if el.type_name != "Button" && el.type_name != "Input" {
|
||||
final_widget_opt = make_hoverable(...);
|
||||
}
|
||||
```
|
||||
|
||||
### Обработка позиционирования
|
||||
|
||||
После получения `final_widget`:
|
||||
|
||||
- **Fixed** → `wrap_fixed_position()` → кладётся в `fixed_layers`, возвращается `None`.
|
||||
- **Absolute** → `wrap_fixed_position()` → кладётся в `abs_layers`, возвращается `None`.
|
||||
- **Sticky** → если `scroll_y > threshold`, элемент перекладывается в `sticky_layers`, а на его место вставляется пустой `spacer` высотой `estimate_element_height()`. Иначе элемент остаётся на месте.
|
||||
|
||||
---
|
||||
|
||||
## `render_children()`
|
||||
|
||||
```rust
|
||||
fn render_children<'a>(
|
||||
children: &'a [Element],
|
||||
parent_color: Option<iced::Color>,
|
||||
parent_font_size: Option<f32>,
|
||||
parent_direction: Option<LayoutDirection>,
|
||||
fixed_layers: &mut Vec<iced::Element<'a, Message, Theme, iced::Renderer>>,
|
||||
abs_layers: &mut Vec<iced::Element<'a, Message, Theme, iced::Renderer>>,
|
||||
sticky_layers: &mut Vec<iced::Element<'a, Message, Theme, iced::Renderer>>,
|
||||
scroll_positions: &HashMap<u64, f32>,
|
||||
container_id: u64,
|
||||
stylesheet: &StyleSheet,
|
||||
) -> Vec<iced::Element<'a, Message, Theme, iced::Renderer>>
|
||||
```
|
||||
|
||||
Рекурсивно вызывает `render_element()` для каждого ребёнка. Фильтрует `None` (display: none). Возвращает вектор отрендеренных элементов.
|
||||
|
||||
---
|
||||
|
||||
## `render_panel()`
|
||||
|
||||
```rust
|
||||
fn render_panel<'a>(
|
||||
el: &'a Element,
|
||||
cs: ComputedStyle,
|
||||
parent_color: Option<iced::Color>,
|
||||
parent_font_size: Option<f32>,
|
||||
fixed_layers: &mut Vec<iced::Element<'a, Message, Theme, iced::Renderer>>,
|
||||
abs_layers: &mut Vec<iced::Element<'a, Message, Theme, iced::Renderer>>,
|
||||
sticky_layers: &mut Vec<iced::Element<'a, Message, Theme, iced::Renderer>>,
|
||||
scroll_positions: &HashMap<u64, f32>,
|
||||
container_id: u64,
|
||||
stylesheet: &StyleSheet,
|
||||
) -> iced::Element<'a, Message, Theme, iced::Renderer>
|
||||
```
|
||||
|
||||
Рендерит `Panel` — контейнер с тремя режимами раскладки:
|
||||
|
||||
### Row
|
||||
|
||||
```rust
|
||||
LayoutDirection::Row
|
||||
```
|
||||
`iced::widget::row` с `spacing` (10px). `align_y` из `cs.align_items` или `Alignment::Center` по умолчанию.
|
||||
|
||||
### Column
|
||||
|
||||
```rust
|
||||
LayoutDirection::Column
|
||||
```
|
||||
`iced::widget::column` с `spacing`. `align_x` из `cs.align_items`.
|
||||
|
||||
### Grid
|
||||
|
||||
```rust
|
||||
LayoutDirection::Grid
|
||||
```
|
||||
Столбцы (`column`), внутри каждого — строка (`row`). Количество колонок из свойства `columns` (по умолчанию 3). Каждый ряд — чанк по `cols` детей.
|
||||
|
||||
Во всех режимах:
|
||||
- Если есть `abs_layers` — они оборачиваются в `stack` внутри контента.
|
||||
- После контента применяется `apply_universal_box_model()`.
|
||||
- `sticky_layers` накладываются поверх через `stack`.
|
||||
|
||||
---
|
||||
|
||||
## `render_button()`
|
||||
|
||||
```rust
|
||||
fn render_button<'a>(
|
||||
el: &'a Element,
|
||||
cs: ComputedStyle,
|
||||
parent_color: Option<iced::Color>,
|
||||
parent_font_size: Option<f32>,
|
||||
fixed_layers: &mut Vec<iced::Element<'a, Message, Theme, iced::Renderer>>,
|
||||
abs_layers: &mut Vec<iced::Element<'a, Message, Theme, iced::Renderer>>,
|
||||
sticky_layers: &mut Vec<iced::Element<'a, Message, Theme, iced::Renderer>>,
|
||||
scroll_positions: &HashMap<u64, f32>,
|
||||
container_id: u64,
|
||||
stylesheet: &StyleSheet,
|
||||
) -> iced::Element<'a, Message, Theme, iced::Renderer>
|
||||
```
|
||||
|
||||
- Если нет детей — читает `label` (по умолчанию `"Button"`) и создаёт `text`.
|
||||
- Если есть дети — рендерит их в `row` со `spacing = 8`.
|
||||
- Padding: по умолчанию 8px по вертикали, 16px по горизонтали. С авто-сжатием если `padding + border > width/height`.
|
||||
- `on_press` из `__on:click`.
|
||||
- Стиль: через `get_button_style()` с динамическими `hover`/`active` переопределениями из `stylesheet.matching_rules()`.
|
||||
|
||||
---
|
||||
|
||||
## `render_toggle()`
|
||||
|
||||
```rust
|
||||
fn render_toggle<'a>(
|
||||
el: &'a Element,
|
||||
parent_color: Option<iced::Color>,
|
||||
parent_font_size: Option<f32>,
|
||||
) -> iced::Element<'a, Message, Theme, iced::Renderer>
|
||||
```
|
||||
|
||||
- Читает `label` (опционально) и `value` (парсится как `bool`, по умолчанию `false`).
|
||||
- Извлекает `__bind:value` для реактивного биндинга.
|
||||
- Создаёт `checkbox`, на `on_toggle` отправляет `Message::ToggleChanged`.
|
||||
- Если есть label — оборачивает `row![checkbox, label]` с `spacing=8` и `align_y=Center`.
|
||||
|
||||
---
|
||||
|
||||
## `render_input()`
|
||||
|
||||
```rust
|
||||
fn render_input<'a>(
|
||||
el: &'a Element,
|
||||
cs: ComputedStyle,
|
||||
parent_color: Option<iced::Color>,
|
||||
_parent_font_size: Option<f32>,
|
||||
hover_props: &HashMap<String, String>,
|
||||
active_props: &HashMap<String, String>,
|
||||
) -> iced::Element<'a, Message, Theme, iced::Renderer>
|
||||
```
|
||||
|
||||
- Читает `placeholder` (по умолчанию `"Type here…"`) и `value`.
|
||||
- Извлекает `__bind:value`.
|
||||
- Создаёт `text_input` с `padding` из `get_padding(cs, 10.0)`.
|
||||
- Назначает `id` через `get_or_create_widget_id(el.element_id.0, "ti")`.
|
||||
- Если есть hover/active стили — применяет динамический `style()` с `apply_overrides`.
|
||||
- Иначе — статический стиль через `get_text_input_style()`.
|
||||
|
||||
---
|
||||
|
||||
## `render_slider()`
|
||||
|
||||
```rust
|
||||
fn render_slider<'a>(
|
||||
el: &'a Element,
|
||||
) -> iced::Element<'a, Message, Theme, iced::Renderer>
|
||||
```
|
||||
|
||||
- Читает `value` (парсится как `f32`, по умолчанию 0.0).
|
||||
- Извлекает `__bind:value`.
|
||||
- Создаёт `slider` в диапазоне `0.0..=100.0`.
|
||||
- На изменение отправляет `Message::SliderChanged`.
|
||||
|
||||
---
|
||||
|
||||
## Вспомогательные функции
|
||||
|
||||
### `get_padding()`
|
||||
|
||||
```rust
|
||||
fn get_padding(cs: &ComputedStyle, default_pad: f32) -> iced::Padding
|
||||
```
|
||||
|
||||
Собирает `padding` из `ComputedStyle` с учётом `padding-top/right/bottom/left`. Если `padding + border * 2` превышает фиксированные `width`/`height` — масштабирует padding пропорционально (авто-сжатие).
|
||||
|
||||
### `get_margin()`
|
||||
|
||||
```rust
|
||||
fn get_margin(cs: &ComputedStyle) -> iced::Padding
|
||||
```
|
||||
|
||||
Собирает `margin` из `ComputedStyle` с учётом `margin-top/right/bottom/left`. База — `cs.margin` (по умолчанию 0).
|
||||
|
||||
### `get_button_style()`
|
||||
|
||||
```rust
|
||||
fn get_button_style(cs: &ComputedStyle, status: button::Status) -> button::Style
|
||||
```
|
||||
|
||||
Формирует `button::Style` из `ComputedStyle`. В зависимости от `status`:
|
||||
- `Hovered` — фон светлее на 15% (`* 1.15`);
|
||||
- `Pressed` — фон темнее на 15% (`* 0.85`);
|
||||
- `Active` — без изменений.
|
||||
|
||||
### `get_text_input_style()`
|
||||
|
||||
```rust
|
||||
fn get_text_input_style(cs: &ComputedStyle) -> text_input::Style
|
||||
```
|
||||
|
||||
Формирует `text_input::Style` из `ComputedStyle`: `background`, `border`, `icon`, `placeholder`, `value`, `selection`. Значения по умолчанию — тёмная тема.
|
||||
|
||||
### `estimate_element_height()`
|
||||
|
||||
```rust
|
||||
fn estimate_element_height(cs: &ComputedStyle) -> f32
|
||||
```
|
||||
|
||||
Приблизительно вычисляет высоту элемента для sticky-спейсера: `padding_top + padding_bottom + border_width * 2 + font_size * line_height`.
|
||||
|
||||
### `wrap_sticky_position()`
|
||||
|
||||
```rust
|
||||
fn wrap_sticky_position<'a>(
|
||||
widget: iced::Element<'a, Message, Theme, iced::Renderer>,
|
||||
cs: &ComputedStyle,
|
||||
) -> iced::Element<'a, Message, Theme, iced::Renderer>
|
||||
```
|
||||
|
||||
Оборачивает виджет в `container` с `padding-top` из `cs.top`. Ширина `Fill`. Используется для sticky-элементов, когда `scroll_y > threshold`.
|
||||
|
||||
### `wrap_fixed_position()`
|
||||
|
||||
```rust
|
||||
fn wrap_fixed_position<'a>(
|
||||
widget: iced::Element<'a, Message, Theme, iced::Renderer>,
|
||||
cs: &ComputedStyle,
|
||||
) -> iced::Element<'a, Message, Theme, iced::Renderer>
|
||||
```
|
||||
|
||||
Оборачивает виджет в `container` с `width = Fill`, `height = Fill` и выравниванием (`align_x`, `align_y`) на основе установленных `top`/`bottom`/`left`/`right`. Padding выставляется соответственно. Используется для `fixed` и `absolute` позиционирования.
|
||||
|
||||
### `apply_universal_box_model()`
|
||||
|
||||
```rust
|
||||
fn apply_universal_box_model<'a>(
|
||||
widget: impl Into<iced::Element<'a, Message, Theme, iced::Renderer>>,
|
||||
cs: &ComputedStyle,
|
||||
is_window: bool,
|
||||
default_padding: f32,
|
||||
scrollable_id: Option<u64>,
|
||||
) -> iced::Element<'a, Message, Theme, iced::Renderer>
|
||||
```
|
||||
|
||||
Применяет боксовую модель к любому виджету. **Порядок обёртки:**
|
||||
|
||||
```
|
||||
outer_container (margin) ← если есть margin и !is_window
|
||||
inner_container (bg, border, clip)
|
||||
scrollable ← если overflow_x/overflow_y = Scroll/Auto
|
||||
container (padding)
|
||||
widget
|
||||
```
|
||||
|
||||
#### Этапы
|
||||
|
||||
1. **Padding**: `container(widget).padding(get_padding(cs, default_padding))`.
|
||||
2. **Overflow**: если `overflow-y` = Scroll/Auto — добавляется `scrollable` с направлением `Vertical` (или `Both` если `overflow-x` тоже Scroll/Auto). Для Window `overflow-y` по умолчанию `Auto`. Скроллу назначается `id` и `on_scroll`.
|
||||
3. **Clip**: если `overflow = Hidden` — `container.clip(true)`.
|
||||
4. **Фон, рамка, скругление**: `container.style(...)` с `Background`, `Border`.
|
||||
5. **Ширина/высота**: для Window — `Fill`/`Fill`; иначе — из `cs.width`, `cs.height`, `cs.max_width`, `cs.max_height`.
|
||||
6. **Выравнивание контента**: `align_x` из `cs.content_align`.
|
||||
7. **Margin**: если есть margin — внешний `container` с `padding = margin`.
|
||||
|
||||
---
|
||||
|
||||
## Структура дерева виджетов (Widget Tree)
|
||||
|
||||
Для типичного окна (`Window`) дерево выглядит так:
|
||||
|
||||
```
|
||||
stack[
|
||||
container (Window) [Fill, Fill]
|
||||
scrollable [id="sc:window_id"]
|
||||
container [bg, border, padding]
|
||||
column [spacing]
|
||||
...дочерние элементы...
|
||||
container (fixed) ← слой fixed
|
||||
container (absolute) ← слой absolute
|
||||
container (sticky) ← слой sticky
|
||||
]
|
||||
```
|
||||
|
||||
Для `Panel`:
|
||||
|
||||
```
|
||||
stack[
|
||||
container [bg, border, margin]
|
||||
scrollable (если overflow)
|
||||
container [padding]
|
||||
row | column | grid
|
||||
...дочерние элементы...
|
||||
container (sticky) ← слой sticky, поверх boxed-контента
|
||||
]
|
||||
```
|
||||
|
||||
Для неизвестных элементов — аналогично Panel, но abs-слои внутри скролла, sticky — поверх.
|
||||
|
||||
Для простых элементов (Text, Image, Toggle, Slider, ProgressBar, Divider):
|
||||
|
||||
```
|
||||
container [bg, border, margin, padding]
|
||||
scrollable (если overflow)
|
||||
container [padding]
|
||||
text | image | checkbox | slider | progress_bar | rule
|
||||
```
|
||||
305
docs/ru/modules/06-mod.md
Normal file
305
docs/ru/modules/06-mod.md
Normal file
@@ -0,0 +1,305 @@
|
||||
# Модуль `interpreter` — ядро интерпретатора байткода
|
||||
|
||||
## Структура модуля
|
||||
|
||||
```
|
||||
interpreter/
|
||||
├── mod.rs — Интерпретатор: парсинг байткода, VDOM, стили
|
||||
├── opcodes.rs — Определения opcode (OP_ELEM_PUSH, OP_IF, OP_EACH, …)
|
||||
├── reactive.rs — ReactiveTracker, ElementId — отслеживание грязных узлов
|
||||
├── reader.rs — Reader — чтение байткода (str_ref, i64, f64, value, …)
|
||||
├── rhei.rs — RheiContext — выполнение Rhai-скриптов и выражений
|
||||
├── style.rs — StyleSheet, AncestorInfo, ComputedStyle, StructuralContext
|
||||
└── types.rs — Element, Document, ComponentDef, Value, FlatVDom, InterpError
|
||||
```
|
||||
|
||||
## `Interpreter` (пустая структура)
|
||||
|
||||
```rust
|
||||
pub struct Interpreter;
|
||||
```
|
||||
|
||||
Структура не содержит полей — все методы статические. Служит пространством имён для функций интерпретации.
|
||||
|
||||
## `Interpreter::run()` — первичный парсинг байткода
|
||||
|
||||
```rust
|
||||
pub fn run<'a>(bytecode: &'a [u8]) -> Result<Document<'a>, InterpError>
|
||||
```
|
||||
|
||||
1. Проверяет магическое число (`bytecode[..4] == MAGIC`).
|
||||
2. Создаёт `Reader`, `HashMap` для `variables` и `components`, `Vec` для `rhei_scripts`, пустой `StyleSheet` и `ReactiveTracker`.
|
||||
3. Вызывает `parse_block_elements()` для корневого уровня, который читает поток опкодов и строит дерево `Element`.
|
||||
4. После парсинга вызывает `stylesheet.build_index()`.
|
||||
5. Возвращает `Document { roots, components, variables, rhei_scripts, stylesheet, interner, tracker }`.
|
||||
|
||||
## `parse_block_elements()` — рекурсивный парсинг блока
|
||||
|
||||
```rust
|
||||
fn parse_block_elements<'a>(
|
||||
r: &mut Reader<'a>,
|
||||
variables: &mut HashMap<String, Value>,
|
||||
components: &mut HashMap<String, ComponentDef<'a>>,
|
||||
rhei_scripts: &mut Vec<String>,
|
||||
stylesheet: &mut SS,
|
||||
tracker: &mut ReactiveTracker,
|
||||
is_root: bool,
|
||||
) -> Result<Vec<Element<'a>>, InterpError>
|
||||
```
|
||||
|
||||
Читает опкоды в цикле, используя стек для построения вложенности (`stack: Vec<Element>`). На `OP_END_BLOCK` завершает текущий блок (если `!is_root`).
|
||||
|
||||
### Обрабатываемые опкоды
|
||||
|
||||
| Опкод | Действие |
|
||||
|---|---|
|
||||
| `OP_ELEM_PUSH` | Создаёт `Element` с `tracker.alloc_id()`, кладёт на стек |
|
||||
| `OP_ELEM_POP` | Снимает элемент со стека, вызывает `attach()` |
|
||||
| `OP_GLOBAL` / `OP_LET` | Читает имя и значение, вставляет в `variables` |
|
||||
| `OP_SINGLETON` | Пропускает данные синглтона |
|
||||
| `OP_CONTENT` | Текстовое содержимое — свойство `text`. Если `OP_PROP_RHEI` — префикс `!rhei:` |
|
||||
| `OP_PROP_STR`, `OP_PROP_VAR`, `OP_PROP_INT`, `OP_PROP_FLOAT`, `OP_PROP_BOOL`, `OP_PROP_RHEI`, `OP_PROP_UNIT`, `OP_PROP_CALL`, `OP_PROP_IDENT`, `OP_PROP_FSPATH`, `OP_PROP_COLOR` | Читает ключ и значение, вызывает `el.push_prop()` |
|
||||
| `OP_RHEI_BLK` | На корневом уровне без родителя — скрипт; иначе — элемент `#text` с `!rhei:` |
|
||||
| `OP_COMPONENT` | Читает имя, параметры, рекурсивно парсит дочерний блок; сохраняет `ComponentDef` |
|
||||
| `OP_IF` | Читает условие, парсит true-блок, проверяет `has_else`, парсит false-блок. Создаёт `@if` с `@else` как последним child |
|
||||
| `OP_EACH` | Читает имя переменной, источник (массив или `$var` или Rhai), парсит блок-шаблон. Создаёт `@each` |
|
||||
| `OP_ON` | Читает имя события, аргументы, ищет `OP_RHEI_BLK` — скрипт обработчика; устанавливает `__on:{event}` |
|
||||
| `OP_STYLE_RULE` | Читает селектор и свойства, вызывает `stylesheet.add_rule()` |
|
||||
|
||||
### `attach()`
|
||||
|
||||
```rust
|
||||
fn attach<'a>(stack: &mut Vec<Element<'a>>, roots: &mut Vec<Element<'a>>, el: Element<'a>)
|
||||
```
|
||||
|
||||
Если стек не пуст — добавляет в `parent.children`, иначе в `roots`.
|
||||
|
||||
---
|
||||
|
||||
## `evaluate_vdom()` — сборка VDOM (точка входа)
|
||||
|
||||
```rust
|
||||
pub fn evaluate_vdom<'a>(
|
||||
templates: &[Element<'a>],
|
||||
variables: &mut HashMap<String, Value>,
|
||||
components: &HashMap<String, ComponentDef<'a>>,
|
||||
rhei: &RheiContext,
|
||||
stylesheet: &SS,
|
||||
ancestors: &[AncestorInfo],
|
||||
) -> Vec<Element<'a>>
|
||||
```
|
||||
|
||||
Делегирует `evaluate_vdom_incr()` с пустым `dirty_set` — полный пересчёт.
|
||||
|
||||
### `evaluate_vdom_flat()`
|
||||
|
||||
```rust
|
||||
pub fn evaluate_vdom_flat<'a>(...) -> FlatVDom<'a>
|
||||
```
|
||||
|
||||
Оборачивает `evaluate_vdom()` и конвертирует результат через `FlatVDom::from_elements()`.
|
||||
|
||||
---
|
||||
|
||||
## `evaluate_vdom_incr()` — инкрементальная сборка VDOM
|
||||
|
||||
```rust
|
||||
pub fn evaluate_vdom_incr<'a>(
|
||||
templates: &[&Element<'a>],
|
||||
variables: &mut HashMap<String, Value>,
|
||||
components: &HashMap<String, ComponentDef<'a>>,
|
||||
rhei: &RheiContext,
|
||||
stylesheet: &SS,
|
||||
ancestors: &[AncestorInfo],
|
||||
dirty_set: &HashSet<ElementId>,
|
||||
) -> Vec<Element<'a>>
|
||||
```
|
||||
|
||||
### Предварительная обработка
|
||||
|
||||
1. **`sibling_infos`** — для каждого элемента из `templates` создаётся `AncestorInfo` (type_name, id, classes). Нужен для структурных псевдоклассов (CSS `:nth-child`, `:first-of-type` и т.д.).
|
||||
|
||||
2. **`type_counts`** / **`type_seen`** — подсчёт общего числа элементов каждого типа и счётчик для `StructuralContext`.
|
||||
|
||||
### Основной цикл по `templates`
|
||||
|
||||
Для каждого элемента вычисляется `StructuralContext`:
|
||||
|
||||
```rust
|
||||
let structural = StructuralContext {
|
||||
sibling_index: i,
|
||||
sibling_total: templates.len(),
|
||||
type_index: *type_idx,
|
||||
type_total,
|
||||
has_children: ...,
|
||||
is_root: ancestors.is_empty(),
|
||||
};
|
||||
```
|
||||
|
||||
#### `@if`
|
||||
|
||||
- Читает `condition`, вызывает `evaluate_condition()`.
|
||||
- Проходит по `child` элементам: если `@else` — активна когда условие ложно; иначе активна когда истинно.
|
||||
- Рекурсивно вызывает `evaluate_vdom_incr()` для активной ветки.
|
||||
|
||||
#### `@each`
|
||||
|
||||
- Получает `var_name` и `source`.
|
||||
- Разрешает источник: если с префиксом `!rhei:` — вызывает `normalize_rhai_array()`, иначе — `resolve_string()`.
|
||||
- Разбивает результат по `,`, для каждого элемента:
|
||||
- Вставляет `var_name` в `variables`, рекурсивно обходит шаблон, восстанавливает предыдущее значение переменной.
|
||||
|
||||
#### Обычный элемент / Компонент
|
||||
|
||||
- Если `el.type_name` найден в `components`:
|
||||
1. Собирает аргументы из параметров компонента через `resolve_prop()`.
|
||||
2. Сохраняет старые значения переменных, вставляет новые.
|
||||
3. Создаёт `vcomp`, копирует свойства, разрешая их через `resolve_prop()` и `resolve_string()`.
|
||||
4. Вычисляет стили: `collect_matching_styles()` → `stylesheet.compute_cached()`.
|
||||
5. Строит chain предков: `build_ancestor_chain()`.
|
||||
6. Рекурсивно обходит `comp.children`.
|
||||
7. Вычисляет `content_hash`.
|
||||
8. Восстанавливает переменные.
|
||||
- Иначе (обычный элемент):
|
||||
1. Создаёт `vnode`.
|
||||
2. Разрешает свойства: `__on:` → `resolve_string()`, `$var` → `__bind:{key}`, остальные → `resolve_prop()`.
|
||||
3. Вычисляет стили через `collect_matching_styles()` + `compute_cached()`.
|
||||
4. Строит chain предков, рекурсивно обходит `el.children`.
|
||||
5. Вычисляет `content_hash`.
|
||||
|
||||
---
|
||||
|
||||
## `compute_content_hash()`
|
||||
|
||||
```rust
|
||||
fn compute_content_hash(el: &Element) -> u64
|
||||
```
|
||||
|
||||
Хеш элемента для кэширования. Учитывает:
|
||||
- `type_name`
|
||||
- Все пары ключ-значение из `properties`
|
||||
- Рекурсивно `content_hash` дочерних элементов (`children`)
|
||||
|
||||
Использует `DefaultHasher`.
|
||||
|
||||
---
|
||||
|
||||
## `build_ancestor_chain()`
|
||||
|
||||
```rust
|
||||
fn build_ancestor_chain<'a>(ancestors: &[AncestorInfo], el: &Element) -> Vec<AncestorInfo>
|
||||
```
|
||||
|
||||
Копирует текущий `ancestors`, добавляет `AncestorInfo` для текущего элемента (type_name, id, classes). Возвращает расширенную цепочку для передачи при рекурсивном обходе детей.
|
||||
|
||||
---
|
||||
|
||||
## `collect_matching_styles()`
|
||||
|
||||
```rust
|
||||
fn collect_matching_styles<'a>(
|
||||
el: &Element,
|
||||
stylesheet: &'a SS,
|
||||
active_pseudo: &[&str],
|
||||
structural: &StructuralContext,
|
||||
ancestors: &[AncestorInfo],
|
||||
preceding_siblings: &[AncestorInfo],
|
||||
) -> Vec<&'a HashMap<String, String>>
|
||||
```
|
||||
|
||||
Извлекает `id`, `classes`, все атрибуты элемента, делегирует `stylesheet.matching_rules()` с полным контекстом для CSS-селекторов.
|
||||
|
||||
---
|
||||
|
||||
## `resolve_string()` — подстановка `$var`
|
||||
|
||||
```rust
|
||||
pub fn resolve_string<'a>(val: &'a str, scope: &HashMap<String, Value>) -> Cow<'a, str>
|
||||
```
|
||||
|
||||
- Если в строке нет `$` — возвращает `Cow::Borrowed(val)` (без аллокаций).
|
||||
- Иначе обходит строку посимвольно. После `$` собирает имя переменной (буквы, цифры, `_`), ищет в `scope`, подставляет значение. Если переменная не найдена — оставляет `$var` как есть.
|
||||
|
||||
## `resolve_prop()` — разрешение значения свойства
|
||||
|
||||
```rust
|
||||
fn resolve_prop(v: &str, variables: &HashMap<String, Value>, rhei: &RheiContext) -> String
|
||||
```
|
||||
|
||||
- Если начинается с `!rhei:` — вызывает `rhei.eval_expr()`.
|
||||
- Иначе — `resolve_string()`.
|
||||
|
||||
## `evaluate_condition()` — вычисление условия `@if`
|
||||
|
||||
```rust
|
||||
fn evaluate_condition(cond: &str, variables: &HashMap<String, Value>, rhei: &RheiContext) -> bool
|
||||
```
|
||||
|
||||
- Если `!rhei:` — `rhei.eval_condition()`.
|
||||
- Иначе:
|
||||
1. Удаляет фигурные скобки `{...}`.
|
||||
2. Выполняет `resolve_string()`.
|
||||
3. Проверяет `is_truthy_str()`, затем `false`/`0`/пусто.
|
||||
4. Пытается разобрать числовые операторы (`>=`, `<=`, `>`, `<`, `==`, `!=`).
|
||||
|
||||
## `is_truthy()` / `is_truthy_str()` — приведение к bool
|
||||
|
||||
```rust
|
||||
fn is_truthy(v: &Value) -> bool
|
||||
fn is_truthy_str(s: &str) -> bool
|
||||
```
|
||||
|
||||
- `Value::Bool` — по значению.
|
||||
- `Value::Int` — ненулевой.
|
||||
- `Value::Float` — ненулевой.
|
||||
- `Value::Str` — делегирует `is_truthy_str()`.
|
||||
- `Value::None` — `false`.
|
||||
- `Value::Array` — не пустой.
|
||||
- Строка: `""`, `"false"`, `"0"`, `"null"` → `false`; `"true"`, `"1"` → `true`; иначе парсит как `f64`.
|
||||
|
||||
## `normalize_rhai_array()` — нормализация Rhai-массива
|
||||
|
||||
```rust
|
||||
fn normalize_rhai_array(s: &str) -> String
|
||||
```
|
||||
|
||||
Обрезает `[...]`, разбивает по `,`, обрезает пробелы, соединяет через `,`. Используется в `@each` для приведения Rhai-массива к формату, ожидаемому циклом.
|
||||
|
||||
---
|
||||
|
||||
## `Document`
|
||||
|
||||
```rust
|
||||
pub struct Document<'a> {
|
||||
pub roots: Vec<Element<'a>>,
|
||||
pub components: HashMap<String, ComponentDef<'a>>,
|
||||
pub variables: HashMap<String, Value>,
|
||||
pub rhei_scripts: Vec<String>,
|
||||
pub stylesheet: StyleSheet,
|
||||
pub interner: Interner,
|
||||
pub tracker: ReactiveTracker,
|
||||
}
|
||||
```
|
||||
|
||||
## `Element`
|
||||
|
||||
```rust
|
||||
pub struct Element<'a> {
|
||||
pub type_name: &'a str,
|
||||
pub properties: Vec<(String, String)>,
|
||||
pub children: Vec<Element<'a>>,
|
||||
pub element_id: ElementId,
|
||||
pub computed_style: Option<ComputedStyle>,
|
||||
pub content_hash: u64,
|
||||
}
|
||||
```
|
||||
|
||||
## `ComponentDef`
|
||||
|
||||
```rust
|
||||
pub struct ComponentDef<'a> {
|
||||
pub name: String,
|
||||
pub params: Vec<(String, String)>,
|
||||
pub children: Vec<Element<'a>>,
|
||||
}
|
||||
```
|
||||
109
docs/ru/modules/07-app-perf.md
Normal file
109
docs/ru/modules/07-app-perf.md
Normal file
@@ -0,0 +1,109 @@
|
||||
# App и Perf модули
|
||||
|
||||
## `src/app.rs` — GlintApp
|
||||
|
||||
Точка входа Iced-приложения. Держит состояние и реализует `update`/`view`.
|
||||
|
||||
### `GlintApp`
|
||||
|
||||
```rust
|
||||
pub struct GlintApp {
|
||||
pub doc: Document<'static>,
|
||||
pub vdom_roots: Vec<Element<'static>>,
|
||||
pub rhei: RheiContext,
|
||||
}
|
||||
```
|
||||
|
||||
- `doc` — оригинальный Document (стили, переменные, tracker)
|
||||
- `vdom_roots` — результат последнего evaluate_vdom (_incr)
|
||||
- `rhei` — Rhai-движок с кэшированными AST
|
||||
|
||||
### `update(&mut self, message: Message) -> iced::Task<Message>`
|
||||
|
||||
Обработка сообщений от Iced:
|
||||
|
||||
| Message | Действие |
|
||||
|---------|----------|
|
||||
| `WindowScrolled(y)` | Установить `__scroll_y`, вызвать `on_variable_changed` |
|
||||
| `ScrollableScrolled(id, y)` | Установить `__scroll_{id}`, вызвать `on_variable_changed` |
|
||||
| `EventTriggered(script)` | `execute_action()`, затем diff переменных для поиска изменившихся |
|
||||
| `InputChanged(var, val)` | Установить переменную, вызвать `on_variable_changed` |
|
||||
| `ToggleChanged(var, val)` | Установить `Bool`, вызвать `on_variable_changed` |
|
||||
| `SliderChanged(var, val)` | Установить `Float`, вызвать `on_variable_changed` |
|
||||
|
||||
После обработки сообщения:
|
||||
1. `tracker.take_dirty_set()` — получить dirty элементы
|
||||
2. `evaluate_vdom_incr()` — пересчитать VDOM
|
||||
3. Сохранить в `self.vdom_roots`
|
||||
|
||||
**Perf:** VDOM фаза замеряется `PerfScope::new("vdom")`.
|
||||
|
||||
### `view(&self) -> iced::Element`
|
||||
|
||||
Построить Iced-виджеты из `self.vdom_roots`:
|
||||
|
||||
1. Собрать scroll_positions из `__scroll_*` переменных
|
||||
2. Для каждого root: `render_element()` → push в column
|
||||
3. Наложить global fixed/absolute/sticky слои
|
||||
4. Вернуть `container(layout).into()`
|
||||
|
||||
**Perf:** Render фаза замеряется `PerfScope::new("render")`.
|
||||
В конце `perf::print_frame()` — вывод в stderr.
|
||||
|
||||
---
|
||||
|
||||
## `src/perf.rs` — Performance Monitoring
|
||||
|
||||
Система замера времени по фазам (VDOM, Style, Render). Включение: флаг `--perf`.
|
||||
|
||||
### `PerfReport`
|
||||
|
||||
```rust
|
||||
pub struct PerfReport {
|
||||
pub vdom_eval: f64, // ms
|
||||
pub style: f64, // ms
|
||||
pub render: f64, // ms
|
||||
pub total: f64, // ms
|
||||
}
|
||||
```
|
||||
|
||||
Хранится в `thread_local!` `RefCell<PerfReport>`.
|
||||
|
||||
### `PerfScope`
|
||||
|
||||
Drop-guard: замеряет время от создания до разрушения.
|
||||
|
||||
```rust
|
||||
pub struct PerfScope {
|
||||
name: &'static str,
|
||||
start: Instant,
|
||||
}
|
||||
```
|
||||
|
||||
При `drop()`: добавляет elapsed ms к соответствующему полю `PerfReport`.
|
||||
Имена: `"vdom"`, `"style"`, `"render"`.
|
||||
|
||||
### Функции
|
||||
|
||||
| Функция | Описание |
|
||||
|---------|----------|
|
||||
| `set_enabled(bool)` | Включить/выключить замеры |
|
||||
| `is_enabled() -> bool` | Проверить состояние |
|
||||
| `report_and_reset() -> Option<String>` | Собрать отчёт, проверить >16ms threshold |
|
||||
| `reset()` | Обнулить PerfReport |
|
||||
| `print_frame()` | Вызвать report_and_reset + сброс, напечатать в stderr |
|
||||
|
||||
### Пример вывода
|
||||
|
||||
```
|
||||
[perf] VDOM: 6.63ms | Style: 0.26ms | Render: 7.19ms | Total: 14.08ms
|
||||
[perf] VDOM: 13.26ms | Style: 0.52ms | Render: 7.18ms | Total: 20.95ms ⚠️ Frame budget exceeded! 20.95ms > 16ms
|
||||
```
|
||||
|
||||
### Точки инструментирования
|
||||
|
||||
| Файл | Фаза | Место |
|
||||
|------|------|-------|
|
||||
| `app.rs:update` | vdom | Вокруг `evaluate_vdom_incr()` |
|
||||
| `app.rs:view` | render | Вся функция `view()` |
|
||||
| `style.rs:compute_cached` | style | Внутри `StyleSheet::compute_cached()` |
|
||||
Reference in New Issue
Block a user