Files
Glint-Runtime/docs/en/modules/02-style.md

16 KiB
Raw Permalink Blame History

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

pub enum SizeValue {
    Px(f32),
    Percent(f32),
}

An absolute (Px) or relative (Percent) size value.

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

#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub enum Overflow {
    #[default] Visible,
    Hidden,
    Scroll,
    Auto,
}

Used for overflow-x, overflow-y.

Position

#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub enum Position {
    #[default] Static,
    Relative,
    Absolute,
    Sticky,
    Fixed,
}

Defines the element positioning scheme.

Display

#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub enum Display {
    #[default] Block,
    Flex,
    Grid,
    Inline,
    None,
}

LayoutDirection

#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum LayoutDirection {
    Column,
    Row,
    Grid,
}

ContentAlign

#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum ContentAlign {
    Start,
    Center,
    End,
}

TextAlign

#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum TextAlign {
    Left,
    Center,
    Right,
}

impl From<TextAlign> for iced::alignment::Horizontal

Selector Parsing

AttributeSelector

pub enum AttributeSelector {
    Exists(String),
    Equals(String, String),
}

Attribute selector: [attr] or [attr=value].

Combinator

#[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

#[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

#[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

#[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

#[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

#[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

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

#[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

#[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) -> boolself.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

#[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()

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()

#[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()

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

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, stretchNone
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.01.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

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.