docs: reorganize into en/ru and add English translation
This commit is contained in:
482
docs/en/modules/02-style.md
Normal file
482
docs/en/modules/02-style.md
Normal file
@@ -0,0 +1,482 @@
|
||||
# Style Module: `src/interpreter/style.rs`
|
||||
|
||||
A CSS-like style system for Glint: selector parsing, index building, cascading property resolution, and computed style caching.
|
||||
|
||||
---
|
||||
|
||||
## Helper Enums
|
||||
|
||||
### `SizeValue`
|
||||
```rust
|
||||
pub enum SizeValue {
|
||||
Px(f32),
|
||||
Percent(f32),
|
||||
}
|
||||
```
|
||||
An absolute (`Px`) or relative (`Percent`) size value.
|
||||
|
||||
```rust
|
||||
impl SizeValue {
|
||||
pub fn resolve(self, relative_to: Option<f32>) -> f32
|
||||
}
|
||||
```
|
||||
`Percent` resolves relative to `relative_to`; `Px` returns as-is. When `Percent` and `relative_to = None`, the percentage is returned as a number.
|
||||
|
||||
### `Overflow`
|
||||
```rust
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
|
||||
pub enum Overflow {
|
||||
#[default] Visible,
|
||||
Hidden,
|
||||
Scroll,
|
||||
Auto,
|
||||
}
|
||||
```
|
||||
Used for `overflow-x`, `overflow-y`.
|
||||
|
||||
### `Position`
|
||||
```rust
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
|
||||
pub enum Position {
|
||||
#[default] Static,
|
||||
Relative,
|
||||
Absolute,
|
||||
Sticky,
|
||||
Fixed,
|
||||
}
|
||||
```
|
||||
Defines the element positioning scheme.
|
||||
|
||||
### `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
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Selector Parsing
|
||||
|
||||
### `AttributeSelector`
|
||||
```rust
|
||||
pub enum AttributeSelector {
|
||||
Exists(String),
|
||||
Equals(String, String),
|
||||
}
|
||||
```
|
||||
Attribute selector: `[attr]` or `[attr=value]`.
|
||||
|
||||
### `Combinator`
|
||||
```rust
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub enum Combinator {
|
||||
Descendant, // space
|
||||
Child, // >
|
||||
NextSibling, // +
|
||||
Subsequent, // ~
|
||||
}
|
||||
```
|
||||
|
||||
### `split_selectors(input: &str) -> Vec<String>`
|
||||
Splits a selector group by comma, accounting for nested brackets. For example, `"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>,
|
||||
}
|
||||
```
|
||||
|
||||
**Methods:**
|
||||
|
||||
- `CompoundSelector::parse(input: &str) -> Self` — parses a simple selector like `Button#id.primary:hover[named=val]`. Processes character by character, grouping parts by the first symbol (`#`, `.`, `:`, `[`).
|
||||
|
||||
- `fn specificity(&self) -> (u32, u32, u32)` — returns specificity per CSS rule: (id, class+attr+pseudo, tag).
|
||||
|
||||
- `pub fn matches_element(type_name, el_id, el_classes, active_pseudo, structural, el_attributes) -> bool` — checks whether an element matches this simple selector. Considers:
|
||||
- `tag` match (or `*`)
|
||||
- `id` match
|
||||
- Presence of all `classes`
|
||||
- Presence of all `attributes` (Exists / Equals)
|
||||
- Pseudo-classes: `first-child`, `last-child`, `first-of-type`, `empty`, `root`, `nth-child(...)`; others are compared against `active_pseudo`.
|
||||
|
||||
### `ComplexSelector`
|
||||
```rust
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct ComplexSelector {
|
||||
pub compounds: Vec<CompoundSelector>,
|
||||
pub combinators: Vec<Combinator>,
|
||||
}
|
||||
```
|
||||
|
||||
**Methods:**
|
||||
|
||||
- `ComplexSelector::parse(input: &str) -> Self` — parses a complex selector (e.g. `Panel > Button.primary`). Splits into parts by combinators (`>`, `+`, `~`, space), parses each as a `CompoundSelector`.
|
||||
|
||||
- `fn check_compound_against(&self, i, info: &AncestorInfo) -> bool` — checks whether the `i`-th compound matches the provided ancestor info.
|
||||
|
||||
- `pub fn matches(type_name, el_id, el_classes, active_pseudo, structural, ancestors, preceding_siblings, el_attributes) -> bool` — full complex selector check: the last compound is the target element, the rest are ancestors/siblings according to combinators.
|
||||
|
||||
- `pub fn specificity(&self) -> (u32, u32, u32)` — sum of all compounds' specificities.
|
||||
|
||||
- `pub fn as_simple(&self) -> Option<&CompoundSelector>` — if compounds contains exactly one element, returns it; otherwise `None`.
|
||||
|
||||
- `pub fn has_pseudo_class(&self, pc: &str) -> bool` — whether any compound has the given pseudo-class.
|
||||
|
||||
---
|
||||
|
||||
## Helper Structs
|
||||
|
||||
### `AncestorInfo`
|
||||
```rust
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct AncestorInfo {
|
||||
pub type_name: String,
|
||||
pub id: Option<String>,
|
||||
pub classes: Vec<String>,
|
||||
}
|
||||
```
|
||||
|
||||
Methods:
|
||||
- `AncestorInfo::new(type_name, classes) -> Self`
|
||||
- `AncestorInfo::new_with_id(type_name, id, classes) -> Self`
|
||||
|
||||
Used when checking complex selectors — describes an ancestor or a sibling element.
|
||||
|
||||
### `StructuralContext`
|
||||
```rust
|
||||
#[derive(Debug, Clone, Default)]
|
||||
pub struct StructuralContext {
|
||||
pub sibling_index: usize, // 0-based
|
||||
pub sibling_total: usize,
|
||||
pub type_index: usize, // among elements of the same type
|
||||
pub type_total: usize,
|
||||
pub has_children: bool,
|
||||
pub is_root: bool,
|
||||
}
|
||||
```
|
||||
|
||||
Used for resolving structural pseudo-classes (`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
|
||||
}
|
||||
```
|
||||
|
||||
An index for fast rule lookup. Built in `StyleSheet::build_index`:
|
||||
- `by_tag` / `by_class` / `by_id` / `by_tag_class` / `by_tag_id` — indexes for simple selectors
|
||||
- `complex_rules` — rules with complex selectors (always checked by brute force)
|
||||
- `universal_rules` — rules with `*`
|
||||
- `rule_specificities` — specificity cache
|
||||
- `epoch` — monotonically increasing counter for cache invalidation
|
||||
|
||||
---
|
||||
|
||||
## `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)
|
||||
}
|
||||
```
|
||||
|
||||
Computed style cache. Key is a hash of `type_name`, inline properties, and `epoch`. When `max_entries` is exceeded, the cache is fully cleared.
|
||||
|
||||
---
|
||||
|
||||
## `StyleSheet`
|
||||
```rust
|
||||
#[derive(Debug)]
|
||||
pub struct StyleSheet {
|
||||
rules: Vec<StyleRule>,
|
||||
index: Option<StyleIndex>,
|
||||
epoch: u64,
|
||||
cache: Mutex<StyleCache>,
|
||||
}
|
||||
```
|
||||
|
||||
The main type of the module. Contains a list of rules, an optional index, and a cache. Implements `Clone` (with a new empty cache) and `Default`.
|
||||
|
||||
**Methods:**
|
||||
|
||||
- `StyleSheet::new() -> Self` — creates an empty sheet.
|
||||
- `pub fn add_rule(&mut self, selector: String, properties: HashMap<String, String>)` — adds a rule. Passes the selector through `split_selectors` (supports comma-separated groups). Resets `index = None`.
|
||||
- `pub fn build_index(&mut self)` — rebuilds the `StyleIndex`. Increments `epoch`, clears the cache. For each rule:
|
||||
- Computes specificity
|
||||
- If the selector is simple (1 compound) — indexes by tag/class/id/attributes
|
||||
- If complex — marks as `complex_rules`
|
||||
- Universal (`*`) go into `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>>>` — uses the index for fast lookup: collects candidates from `universal_rules`, `by_tag`, `by_class`, `by_id`, `complex_rules`; filters via `ComplexSelector::matches`; sorts by specificity.
|
||||
- `pub fn matching_rules(...) -> Vec<&HashMap<String, String>>` — finds matching rules. Attempts `query_index`; if no index — linear scan of all `self.rules` with sorting.
|
||||
- `pub fn matching_pseudo_rules(pseudo, ...) -> HashMap<String, String>` — finds rules containing the specified pseudo-class (e.g. `:hover`). Uses `query_index_for_pseudo` or linear scan.
|
||||
- `pub fn compute_cached(type_name, props, matched_sheets) -> ComputedStyle` — computes the final style through the cache (wrapper around `StyleCache::get_or_compute`). Includes `PerfScope::new("style")`.
|
||||
- `pub fn clear_cache(&self)` — clears the cache.
|
||||
- `pub fn is_empty(&self) -> bool` — `self.rules.is_empty()`.
|
||||
|
||||
**Methods with `#[cfg(feature = "parallel")]`:**
|
||||
|
||||
- `matching_rules_batch(type_names, el_ids, el_classes_list, ...) -> Vec<Vec<&HashMap<String, String>>>` — parallel batch search via `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>,
|
||||
}
|
||||
```
|
||||
|
||||
The final computed style of an element. All fields are `Option`; a missing property means "not set / inherited from parent".
|
||||
|
||||
### `ComputedStyle::compute()`
|
||||
```rust
|
||||
pub fn compute(
|
||||
inline: &[(Cow<'_, str>, Cow<'_, str>)],
|
||||
matched_sheets: &[&HashMap<String, String>],
|
||||
) -> Self
|
||||
```
|
||||
|
||||
Assembles the style via `lookup()`: for each field, `lookup(key, inline, matched_sheets)` is called, then parsed by the corresponding function. Notable details:
|
||||
- `padding`/`margin` — individual properties (`-top`, `-right`, etc.) are checked first, then the shorthand.
|
||||
- `spacing` — alternate name for `gap`.
|
||||
- `background` — first `background`, then `background-color`.
|
||||
- `overflow-x`/`overflow-y` — if the individual property is not found, the general `overflow` is applied.
|
||||
- `flex_grow` — parsed as `f32`, cast to `u16`.
|
||||
|
||||
### `ComputedStyle::compute_batch()`
|
||||
```rust
|
||||
#[cfg(feature = "parallel")]
|
||||
pub fn compute_batch<'a>(
|
||||
pairs: &[(&[(Cow<'_, str>, Cow<'_, str>)], &[&'a HashMap<String, String>])],
|
||||
) -> Vec<ComputedStyle>
|
||||
```
|
||||
Parallel batch variant via `rayon::par_iter`.
|
||||
|
||||
### `ComputedStyle::apply_overrides()`
|
||||
```rust
|
||||
pub fn apply_overrides(&mut self, sheet: &HashMap<String, String>)
|
||||
```
|
||||
Applies (overwrites) a given set of properties on top of the existing style. Used for dynamic changes (e.g. `:hover` rules, inline overrides).
|
||||
|
||||
### How `lookup()` works
|
||||
```rust
|
||||
fn lookup<'a>(
|
||||
key: &str,
|
||||
inline: &'a [(Cow<'_, str>, Cow<'_, str>)],
|
||||
matched_sheets: &[&'a HashMap<String, String>],
|
||||
) -> Option<&'a str>
|
||||
```
|
||||
|
||||
Property resolution order:
|
||||
1. **Inline properties** — iterates over `(key, value)` pairs. Supports the `style:` prefix (i.e. `style:color` is equivalent to `color`).
|
||||
2. **matched_sheets** — a list of dictionaries from matching CSS rules, sorted by specificity. Iterated from the end (last is most specific).
|
||||
3. Returns the first found value.
|
||||
|
||||
---
|
||||
|
||||
## Parsing Functions
|
||||
|
||||
| Function | Signature | Description |
|
||||
|---|---|---|
|
||||
| `parse_size` | `(s: &str) -> Option<SizeValue>` | Parses a size: `"10"` → `Px(10)`, `"50%"` → `Percent(50)`. `auto`, `fill`, `stretch` → `None` |
|
||||
| `parse_color` | `(s: &str) -> Option<iced::Color>` | Parses a color: `#rgb`, `#rrggbb`, `#rrggbbaa`, names (`white`, `black`, `transparent`) |
|
||||
| `parse_length` | `(s: &str) -> Option<iced::Length>` | Parses an Iced length: `"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>` | Number 0.0–1.0, clamped |
|
||||
| `parse_font_weight` | `(s: &str) -> Option<u16>` | Names: `normal`→400, `bold`→700, `lighter`→300, `bolder`→900; numeric values |
|
||||
| `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>
|
||||
```
|
||||
A convenience wrapper around `SizeValue::resolve`, returns `Option<f32>`.
|
||||
|
||||
---
|
||||
|
||||
## Usage in `renderer.rs`
|
||||
|
||||
`ComputedStyle` fields are actively used in `/home/faynot/software/glint-runtime/src/renderer.rs`:
|
||||
|
||||
| Field | Where used |
|
||||
|---|---|
|
||||
| `.color` | Text color in buttons, text fields, Label |
|
||||
| `.background` | Background of containers, buttons, text fields |
|
||||
| `.padding*` | `iced::Padding` for buttons, input fields, containers |
|
||||
| `.margin*` | Spacing around elements |
|
||||
| `.border_radius`, `.border_width`, `.border_color` | Borders of buttons, input fields, containers |
|
||||
| `.width`, `.height` | Sizes of Scrollable, Column, Row, Image |
|
||||
| `.min_width`, `.max_width`, `.min_height`, `.max_height` | Size constraints |
|
||||
| `.direction` | Flex direction (Row / Column) |
|
||||
| `.align_items` | Child element alignment |
|
||||
| `.content_align` | Content alignment |
|
||||
| `.flex_grow` | Flex-grow with `FillPortion` |
|
||||
| `.spacing` | `iced::container::Style` spacing, gap in Row / Column |
|
||||
| `.position` | Static / Fixed / Absolute / Sticky |
|
||||
| `.top`, `.right`, `.bottom`, `.left` | Positioning |
|
||||
| `.overflow_x`, `.overflow_y` | Scrolling (`Scrollable`) |
|
||||
| `.display` | `Display::None` — element hiding |
|
||||
| `.opacity` | Transparency |
|
||||
| `.font_weight` | Font weight in Text |
|
||||
| `.line_height` | Line spacing |
|
||||
| `.text_align` | Horizontal text alignment |
|
||||
| `.font_size` | Font size (inherited from parent) |
|
||||
|
||||
---
|
||||
|
||||
## `nth_matches(expr: &str, n: usize) -> bool`
|
||||
|
||||
Internal function for resolving `:nth-child(an+b)`, `:nth-child(odd)`, `:nth-child(even)`, and `:nth-child(<number>)`. Supports negative `a` and `b`.
|
||||
|
||||
---
|
||||
|
||||
## Relationships with other modules
|
||||
|
||||
- `types.rs` — each `DomNode` (both element and text node) contains `computed_style: ComputedStyle`.
|
||||
- `renderer.rs` — imports `{ComputedStyle, ContentAlign, Display, LayoutDirection, Position, Overflow, StructuralContext, StyleSheet, TextAlign, resolve_size}`.
|
||||
- `mod.rs` — uses `AncestorInfo`, `ComputedStyle`, `StructuralContext` when traversing the DOM.
|
||||
Reference in New Issue
Block a user