# 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 } ``` `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 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` 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, pub id: Option, pub classes: Vec, pub pseudo_classes: Vec, pub attributes: Vec, } ``` **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, pub combinators: Vec, } ``` **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, pub classes: Vec, } ``` 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, } impl StyleRule { pub fn build(selector_str: String, properties: HashMap) -> Self } ``` --- ## `StyleIndex` ```rust pub type RuleId = usize; #[derive(Debug, Clone)] pub struct StyleIndex { pub by_tag: HashMap>, pub by_class: HashMap>, pub by_id: HashMap>, pub by_tag_class: HashMap<(String, String), Vec>, pub by_tag_id: HashMap<(String, String), Vec>, pub complex_rules: Vec<(RuleId, RuleId)>, pub universal_rules: Vec, pub rule_specificities: Vec<(u32, u32, u32)>, pub rules: Vec, 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, 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], ) -> 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, index: Option, epoch: u64, cache: Mutex, } ``` 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)` — 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>>` — 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>` — finds matching rules. Attempts `query_index`; if no index — linear scan of all `self.rules` with sorting. - `pub fn matching_pseudo_rules(pseudo, ...) -> HashMap` — 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>>` — parallel batch search via `rayon::par_iter`. --- ## `ComputedStyle` ```rust #[derive(Debug, Clone, Default)] pub struct ComputedStyle { pub font_size: Option, pub color: Option, pub padding: Option, pub padding_top: Option, pub padding_right: Option, pub padding_bottom: Option, pub padding_left: Option, pub margin: Option, pub margin_top: Option, pub margin_right: Option, pub margin_bottom: Option, pub margin_left: Option, pub background: Option, pub spacing: Option, pub border_radius: Option, pub border_width: Option, pub border_color: Option, pub width: Option, pub height: Option, pub min_width: Option, pub max_width: Option, pub min_height: Option, pub max_height: Option, pub direction: Option, pub align_items: Option, pub content_align: Option, pub flex_grow: Option, pub position: Option, pub top: Option, pub right: Option, pub bottom: Option, pub left: Option, pub overflow_x: Option, pub overflow_y: Option, pub display: Option, pub opacity: Option, pub font_weight: Option, pub line_height: Option, pub text_align: Option, } ``` 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], ) -> 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])], ) -> Vec ``` Parallel batch variant via `rayon::par_iter`. ### `ComputedStyle::apply_overrides()` ```rust pub fn apply_overrides(&mut self, sheet: &HashMap) ``` 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], ) -> 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` | Parses a size: `"10"` → `Px(10)`, `"50%"` → `Percent(50)`. `auto`, `fill`, `stretch` → `None` | | `parse_color` | `(s: &str) -> Option` | Parses a color: `#rgb`, `#rrggbb`, `#rrggbbaa`, names (`white`, `black`, `transparent`) | | `parse_length` | `(s: &str) -> Option` | Parses an Iced length: `"fill"`/`"100%"`, `"shrink"`/`"auto"`, `"50"` → `Fixed(50)` | | `parse_overflow` | `(s: &str) -> Option` | `visible`, `hidden`, `scroll`, `auto` | | `parse_position` | `(s: &str) -> Option` | `static`, `relative`, `absolute`, `sticky`, `fixed` | | `parse_display` | `(s: &str) -> Option` | `none`, `block`, `flex`, `grid`, `inline` | | `parse_direction` | `(s: &str) -> Option` | `row`/`horizontal`, `column`/`vertical`, `grid` | | `parse_alignment` | `(s: &str) -> Option` | `start`, `center`, `end` | | `parse_content_align` | `(s: &str) -> Option` | `start`/`left`/`top`, `center`, `end`/`right`/`bottom` | | `parse_opacity` | `(s: &str) -> Option` | Number 0.0–1.0, clamped | | `parse_font_weight` | `(s: &str) -> Option` | Names: `normal`→400, `bold`→700, `lighter`→300, `bolder`→900; numeric values | | `parse_text_align` | `(s: &str) -> Option` | `left`, `center`, `right` | ### `resolve_size` ```rust pub fn resolve_size(v: Option, relative_to: Option) -> Option ``` A convenience wrapper around `SizeValue::resolve`, returns `Option`. --- ## 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()`. 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.