16 KiB
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 likeButton#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:tagmatch (or*)idmatch- 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 againstactive_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 aCompoundSelector. -
fn check_compound_against(&self, i, info: &AncestorInfo) -> bool— checks whether thei-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; otherwiseNone. -
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) -> SelfAncestorInfo::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 selectorscomplex_rules— rules with complex selectors (always checked by brute force)universal_rules— rules with*rule_specificities— specificity cacheepoch— 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 throughsplit_selectors(supports comma-separated groups). Resetsindex = None. -
pub fn build_index(&mut self)— rebuilds theStyleIndex. Incrementsepoch, 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 intouniversal_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 fromuniversal_rules,by_tag,by_class,by_id,complex_rules; filters viaComplexSelector::matches; sorts by specificity. -
pub fn matching_rules(...) -> Vec<&HashMap<String, String>>— finds matching rules. Attemptsquery_index; if no index — linear scan of allself.ruleswith sorting. -
pub fn matching_pseudo_rules(pseudo, ...) -> HashMap<String, String>— finds rules containing the specified pseudo-class (e.g.:hover). Usesquery_index_for_pseudoor linear scan. -
pub fn compute_cached(type_name, props, matched_sheets) -> ComputedStyle— computes the final style through the cache (wrapper aroundStyleCache::get_or_compute). IncludesPerfScope::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 viarayon::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 forgap.background— firstbackground, thenbackground-color.overflow-x/overflow-y— if the individual property is not found, the generaloverflowis applied.flex_grow— parsed asf32, cast tou16.
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:
- Inline properties — iterates over
(key, value)pairs. Supports thestyle:prefix (i.e.style:coloris equivalent tocolor). - matched_sheets — a list of dictionaries from matching CSS rules, sorted by specificity. Iterated from the end (last is most specific).
- 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
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— eachDomNode(both element and text node) containscomputed_style: ComputedStyle.renderer.rs— imports{ComputedStyle, ContentAlign, Display, LayoutDirection, Position, Overflow, StructuralContext, StyleSheet, TextAlign, resolve_size}.mod.rs— usesAncestorInfo,ComputedStyle,StructuralContextwhen traversing the DOM.