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

View File

@@ -0,0 +1,537 @@
# Renderer Module: `src/renderer.rs`
Transforms the `Element` tree into Iced widgets. Responsible for building the widget hierarchy, applying the box model, handling `:hover`/`:active` pseudo-classes, positioning (fixed, absolute, sticky), and rendering all built-in element types.
---
## Imports
```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` and `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
```
A `thread_local` cache for `iced::widget::Id`. The key is a tuple `(element_id, prefix)`. The string is formatted as `"{prefix}:{key}"` and "leaked" via `Box::leak` to obtain a `&'static str`. Used for `scrollable::id` (prefix `"sc"`) and `text_input::id` (prefix `"ti"`).
---
## `Element` Methods
### `get_prop()`
```rust
impl<'a> Element<'a> {
#[inline]
pub fn get_prop(&self, key: &str) -> Option<&str>
}
```
Looks up a property by key in `self.properties`. Returns the value or `None`.
### `push_prop()`
```rust
pub fn push_prop<K: Into<Cow<'a, str>>, V: Into<Cow<'a, str>>>(&mut self, key: K, val: V)
```
Adds a `(key, val)` pair to `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)
```
Sets a property: if the key already exists — replaces the value, otherwise — adds a new pair.
---
## `extract_var_binding()`
```rust
fn extract_var_binding(el: &Element, prop: &str) -> Option<String>
```
Looks for a property of the form `__bind:<prop>` and returns its value. Used for reactive variable binding: `__bind:value` for `Input`, `Toggle`, `Slider`.
---
## `collect_hover_active()`
```rust
pub fn collect_hover_active<'a>(
el: &'a Element,
stylesheet: &StyleSheet,
) -> (HashMap<String, String>, HashMap<String, String>)
```
Collects CSS properties for the `:hover` and `:active` pseudo-classes for an element. Calls `stylesheet.matching_pseudo_rules()` twice — for `"hover"` and `"active"`. Returns a tuple `(hover_props, active_props)`. Used in `render_element()` and `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>
```
Wraps an arbitrary widget in a `button` to support `:hover`/`:active` styles. Trigger conditions:
- At least one `hover` or `active` style exists;
- The element has an `__on:click` handler.
The button is assigned `on_press(Message::EventTriggered(...))`. In the `style()` closure, `apply_overrides` are substituted depending on `button::Status`:
- `Hovered``hover_props`;
- `Pressed``active_props`, or `hover_props` if none exist.
Applied **only to non-Button and non-Input** elements (line 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>>
```
**Main public rendering function.** Returns `None` if `display: none`.
### Common logic for all elements
1. `hover_props`, `active_props` are collected via `collect_hover_active()`.
2. Positioning type is determined: `is_fixed`, `is_absolute`, `is_sticky`.
3. If `left` + `right` are set without `width``width = Fill`. If `top` + `bottom` without `height``height = Fill`.
4. `flex-grow` is converted to `FillPortion(grow_value)` along the parent axis.
5. `current_color` and `current_font_size` are inherited.
### `"Window"` branch
```rust
if el.type_name == "Window"
```
- Creates a `column` with `spacing` (default 12px).
- Renders children via `render_children()`.
- Applies `apply_universal_box_model(is_window = true, scrollable_id = window_id)` — the window is always scrollable.
- Builds an `iced::widget::stack`:
1. Main flow (main_flow)
2. `abs_layers`
3. `sticky_layers`
4. `fixed_layers`
Final structure:
```
stack[
container[ scrollable[ container[ column[...] ] ] ]
...abs layers
...sticky layers
...fixed layers
]
```
### `"Panel"` branch
Delegates to `render_panel()`. If there are `abs_layers` — wraps in a `stack`.
### `"Button"` branch
Delegates to `render_button()`. If there are `abs_layers` or `sticky_layers` — wraps in a `stack`.
### `"Input"` branch
Delegates to `render_input()` passing `hover_props` and `active_props`.
### Text widgets: `"Title"`, `"Header"`, `"Text"`, `"Label"`, `"#text"`
```rust
"Title" | "Header" => ...
"Text" | "Label" | "#text" => ...
```
- Read the `text` property (or empty string).
- Create `iced::widget::text` with font size (24 for Title/Header, 16 for Text).
- Apply `color`, `font_weight` (Light ≤399, Normal 400599, Bold 600799, ExtraBold ≥800), `text_align`, `line_height` (with `Wrapping::Word`).
- For Title/Header the default size is 24px, for Text — 16px.
### `"Image"`
- Reads `src`. If the path starts with `fs:` — strips the prefix.
- `.svg``svg::Handle`, otherwise `image::Viewer`.
- Image size is calculated subtracting padding and border-width.
- For raster images `border_radius` is applied.
### `"Icon"`
Renders the character `🔹` as text of size 18px (or `current_font_size`). Placeholder.
### `"Toggle"`
Delegates to `render_toggle()`.
### `"Slider"`
Delegates to `render_slider()`.
### `"ProgressBar"`
```rust
progress_bar(0.0..=100.0, value)
```
The `value` property is parsed as `f32`.
### `"Divider"`, `"Separator"`
```rust
iced::widget::rule::horizontal(1)
```
Horizontal line with thickness 1px.
### Default branch (unknown type)
- Creates a `column` with `spacing` (10px).
- Renders children.
- If there are `abs_layers` — wraps in a `stack`.
- Applies `apply_universal_box_model()`.
- If there are `sticky_layers` — overlays them via `stack`.
- Returns `None` (the element is already written into `final_widget_opt`).
### Post-processing for non-Button and non-Input
```rust
if el.type_name != "Button" && el.type_name != "Input" {
final_widget_opt = make_hoverable(...);
}
```
### Positioning handling
After obtaining `final_widget`:
- **Fixed** → `wrap_fixed_position()` → placed into `fixed_layers`, returns `None`.
- **Absolute** → `wrap_fixed_position()` → placed into `abs_layers`, returns `None`.
- **Sticky** → if `scroll_y > threshold`, the element is moved to `sticky_layers`, and an empty `spacer` with height `estimate_element_height()` is inserted in its place. Otherwise the element stays in place.
---
## `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>>
```
Recursively calls `render_element()` for each child. Filters out `None` (display: none). Returns a vector of rendered elements.
---
## `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>
```
Renders `Panel` — a container with three layout modes:
### Row
```rust
LayoutDirection::Row
```
`iced::widget::row` with `spacing` (10px). `align_y` from `cs.align_items` or `Alignment::Center` by default.
### Column
```rust
LayoutDirection::Column
```
`iced::widget::column` with `spacing`. `align_x` from `cs.align_items`.
### Grid
```rust
LayoutDirection::Grid
```
Columns (`column`), inside each — a row (`row`). Number of columns from the `columns` property (default 3). Each row is a chunk of `cols` children.
In all modes:
- If there are `abs_layers` — they are wrapped in a `stack` inside the content.
- After content, `apply_universal_box_model()` is applied.
- `sticky_layers` are overlaid on top via `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>
```
- If there are no children — reads `label` (default `"Button"`) and creates `text`.
- If there are children — renders them in a `row` with `spacing = 8`.
- Padding: default 8px vertical, 16px horizontal. With auto-shrink if `padding + border > width/height`.
- `on_press` from `__on:click`.
- Style: via `get_button_style()` with dynamic `hover`/`active` overrides from `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>
```
- Reads `label` (optional) and `value` (parsed as `bool`, default `false`).
- Extracts `__bind:value` for reactive binding.
- Creates a `checkbox`, on `on_toggle` sends `Message::ToggleChanged`.
- If there is a label — wraps in `row![checkbox, label]` with `spacing=8` and `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>
```
- Reads `placeholder` (default `"Type here…"`) and `value`.
- Extracts `__bind:value`.
- Creates `text_input` with `padding` from `get_padding(cs, 10.0)`.
- Assigns `id` via `get_or_create_widget_id(el.element_id.0, "ti")`.
- If there are hover/active styles — applies dynamic `style()` with `apply_overrides`.
- Otherwise — static style via `get_text_input_style()`.
---
## `render_slider()`
```rust
fn render_slider<'a>(
el: &'a Element,
) -> iced::Element<'a, Message, Theme, iced::Renderer>
```
- Reads `value` (parsed as `f32`, default 0.0).
- Extracts `__bind:value`.
- Creates a `slider` in the range `0.0..=100.0`.
- On change sends `Message::SliderChanged`.
---
## Helper Functions
### `get_padding()`
```rust
fn get_padding(cs: &ComputedStyle, default_pad: f32) -> iced::Padding
```
Gathers `padding` from `ComputedStyle` considering `padding-top/right/bottom/left`. If `padding + border * 2` exceeds fixed `width`/`height` — scales padding proportionally (auto-shrink).
### `get_margin()`
```rust
fn get_margin(cs: &ComputedStyle) -> iced::Padding
```
Gathers `margin` from `ComputedStyle` considering `margin-top/right/bottom/left`. Base is `cs.margin` (default 0).
### `get_button_style()`
```rust
fn get_button_style(cs: &ComputedStyle, status: button::Status) -> button::Style
```
Builds `button::Style` from `ComputedStyle`. Depending on `status`:
- `Hovered` — background 15% lighter (`* 1.15`);
- `Pressed` — background 15% darker (`* 0.85`);
- `Active` — unchanged.
### `get_text_input_style()`
```rust
fn get_text_input_style(cs: &ComputedStyle) -> text_input::Style
```
Builds `text_input::Style` from `ComputedStyle`: `background`, `border`, `icon`, `placeholder`, `value`, `selection`. Default values use a dark theme.
### `estimate_element_height()`
```rust
fn estimate_element_height(cs: &ComputedStyle) -> f32
```
Approximately calculates element height for sticky spacer: `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>
```
Wraps a widget in a `container` with `padding-top` from `cs.top`. Width is `Fill`. Used for sticky elements when `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>
```
Wraps a widget in a `container` with `width = Fill`, `height = Fill` and alignment (`align_x`, `align_y`) based on set `top`/`bottom`/`left`/`right`. Padding is set accordingly. Used for `fixed` and `absolute` positioning.
### `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>
```
Applies the box model to any widget. **Wrapping order:**
```
outer_container (margin) ← if margin exists and !is_window
inner_container (bg, border, clip)
scrollable ← if overflow_x/overflow_y = Scroll/Auto
container (padding)
widget
```
#### Stages
1. **Padding**: `container(widget).padding(get_padding(cs, default_padding))`.
2. **Overflow**: if `overflow-y` = Scroll/Auto — adds `scrollable` with direction `Vertical` (or `Both` if `overflow-x` is also Scroll/Auto). For Window `overflow-y` defaults to `Auto`. Scroll gets an `id` and `on_scroll`.
3. **Clip**: if `overflow = Hidden``container.clip(true)`.
4. **Background, border, rounding**: `container.style(...)` with `Background`, `Border`.
5. **Width/height**: for Window — `Fill`/`Fill`; otherwise — from `cs.width`, `cs.height`, `cs.max_width`, `cs.max_height`.
6. **Content alignment**: `align_x` from `cs.content_align`.
7. **Margin**: if margin exists — outer `container` with `padding = margin`.
---
## Widget Tree Structure
For a typical window (`Window`) the tree looks like:
```
stack[
container (Window) [Fill, Fill]
scrollable [id="sc:window_id"]
container [bg, border, padding]
column [spacing]
...child elements...
container (fixed) ← fixed layer
container (absolute) ← absolute layer
container (sticky) ← sticky layer
]
```
For `Panel`:
```
stack[
container [bg, border, margin]
scrollable (if overflow)
container [padding]
row | column | grid
...child elements...
container (sticky) ← sticky layer, on top of boxed content
]
```
For unknown elements — similar to Panel, but abs-layers inside scroll, sticky on top.
For simple elements (Text, Image, Toggle, Slider, ProgressBar, Divider):
```
container [bg, border, margin, padding]
scrollable (if overflow)
container [padding]
text | image | checkbox | slider | progress_bar | rule
```