docs: reorganize into en/ru and add English translation
This commit is contained in:
537
docs/en/modules/05-renderer.md
Normal file
537
docs/en/modules/05-renderer.md
Normal 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 400–599, Bold 600–799, 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
|
||||
```
|
||||
Reference in New Issue
Block a user