Files
Glint-Runtime/docs/ru/modules/05-renderer.md

538 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/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 400599, Bold 600799, 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
```