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

20 KiB
Raw Permalink Blame History

Модуль стилей: src/interpreter/style.rs

Система CSS-подобных стилей для Glint: парсинг селекторов, построение индекса, каскадное разрешение свойств и кеширование вычисленных стилей.


Перечисления-помощники

SizeValue

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

Абсолютное (Px) или относительное (Percent) значение размера.

impl SizeValue {
    pub fn resolve(self, relative_to: Option<f32>) -> f32
}

Percent разрешается относительно relative_to; Px возвращается как есть. При Percent и relative_to = None возвращается процент как число.

Overflow

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

Используется для overflow-x, overflow-y.

Position

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

Определяет схему позиционирования элемента.

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

Парсинг селекторов

AttributeSelector

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

Селектор атрибута: [attr] или [attr=value].

Combinator

#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Combinator {
    Descendant,   // пробел
    Child,        // >
    NextSibling,  // +
    Subsequent,   // ~
}

split_selectors(input: &str) -> Vec<String>

Разделяет группу селекторов по запятой с учётом вложенности скобок. Например, "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>,
}

Методы:

  • CompoundSelector::parse(input: &str) -> Self — парсит простой селектор вида Button#id.primary:hover[named=val]. Разбирает посимвольно, группируя части по первому символу (#, ., :, [).

  • fn specificity(&self) -> (u32, u32, u32) — возвращает специфичность по правилу CSS: (id, class+attr+pseudo, tag).

  • pub fn matches_element(type_name, el_id, el_classes, active_pseudo, structural, el_attributes) -> bool — проверяет, соответствует ли элемент данному простому селектору. Учитывает:

    • Совпадение tag (или *)
    • Совпадение id
    • Наличие всех classes
    • Наличие всех attributes (Exists / Equals)
    • Псевдоклассы: first-child, last-child, first-of-type, empty, root, nth-child(...); остальные сравниваются с active_pseudo.

ComplexSelector

#[derive(Debug, Clone)]
pub struct ComplexSelector {
    pub compounds:   Vec<CompoundSelector>,
    pub combinators: Vec<Combinator>,
}

Методы:

  • ComplexSelector::parse(input: &str) -> Self — парсит сложный селектор (например Panel > Button.primary). Разбивает на части по комбинаторам (>, +, ~, пробел), парсит каждую как CompoundSelector.

  • fn check_compound_against(&self, i, info: &AncestorInfo) -> bool — проверяет, соответствует ли i-й compound переданной информации о предке.

  • pub fn matches(type_name, el_id, el_classes, active_pseudo, structural, ancestors, preceding_siblings, el_attributes) -> bool — полная проверка сложного селектора: последний compound — целевой элемент, остальные — предки/соседи в соответствии с комбинаторами.

  • pub fn specificity(&self) -> (u32, u32, u32) — сумма специфичностей всех compounds.

  • pub fn as_simple(&self) -> Option<&CompoundSelector> — если compounds содержит ровно один элемент, возвращает его; иначе None.

  • pub fn has_pseudo_class(&self, pc: &str) -> bool — есть ли среди compounds указанный псевдокласс.


Вспомогательные структуры

AncestorInfo

#[derive(Debug, Clone)]
pub struct AncestorInfo {
    pub type_name: String,
    pub id:        Option<String>,
    pub classes:   Vec<String>,
}

Методы:

  • AncestorInfo::new(type_name, classes) -> Self
  • AncestorInfo::new_with_id(type_name, id, classes) -> Self

Используется при проверке сложных селекторов — описывает предка или соседний элемент.

StructuralContext

#[derive(Debug, Clone, Default)]
pub struct StructuralContext {
    pub sibling_index: usize,   // 0-based
    pub sibling_total: usize,
    pub type_index:    usize,   // среди элементов того же типа
    pub type_total:    usize,
    pub has_children:  bool,
    pub is_root:       bool,
}

Используется для разрешения структурных псевдоклассов (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
}

Индекс для быстрого поиска правил. Строится в StyleSheet::build_index:

  • by_tag / by_class / by_id / by_tag_class / by_tag_id — индексы для простых селекторов
  • complex_rules — правила со сложными селекторами (всегда проверяются в лоб)
  • universal_rules — правила с *
  • rule_specificities — кеш специфичностей
  • epoch — монотонно возрастающий счётчик для инвалидации кеша

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

Кеш вычисленных стилей. Ключ — хеш от type_name, inline-свойств и epoch. При превышении max_entries кеш полностью очищается.


StyleSheet

#[derive(Debug)]
pub struct StyleSheet {
    rules: Vec<StyleRule>,
    index: Option<StyleIndex>,
    epoch: u64,
    cache: Mutex<StyleCache>,
}

Главный тип модуля. Содержит список правил, опциональный индекс и кеш. Реализует Clone (с новым пустым кешем) и Default.

Методы:

  • StyleSheet::new() -> Self — создаёт пустой лист.

  • pub fn add_rule(&mut self, selector: String, properties: HashMap<String, String>) — добавляет правило. Пропускает селектор через split_selectors (поддержка групп через запятую). Сбрасывает index = None.

  • pub fn build_index(&mut self) — перестраивает StyleIndex. Увеличивает epoch, очищает кеш. Для каждого правила:

    • Вычисляет специфичность
    • Если селектор простой (1 compound) — индексирует по tag/class/id/атрибутам
    • Если сложный — помечает как complex_rules
    • Универсальные (*) попадают в 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>>> — использует индекс для быстрого поиска: собирает кандидатов из universal_rules, by_tag, by_class, by_id, complex_rules; фильтрует через ComplexSelector::matches; сортирует по специфичности.

  • pub fn matching_rules(...) -> Vec<&HashMap<String, String>> — поиск подходящих правил. Пытается query_index; если индекса нет — линейный перебор всех self.rules с сортировкой.

  • pub fn matching_pseudo_rules(pseudo, ...) -> HashMap<String, String> — ищет правила, содержащие указанный псевдокласс (например :hover). Использует query_index_for_pseudo или линейный перебор.

  • pub fn compute_cached(type_name, props, matched_sheets) -> ComputedStyle — вычисляет итоговый стиль через кеш (обёртка над StyleCache::get_or_compute). Включает PerfScope::new("style").

  • pub fn clear_cache(&self) — очищает кеш.

  • pub fn is_empty(&self) -> boolself.rules.is_empty().

Методы с #[cfg(feature = "parallel")]:

  • matching_rules_batch(type_names, el_ids, el_classes_list, ...) -> Vec<Vec<&HashMap<String, String>>> — параллельный batch-поиск через 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>,
}

Итоговый вычисленный стиль элемента. Все поля — Option; отсутствующее свойство означает «не задано / наследуется от родителя».

ComputedStyle::compute()

pub fn compute(
    inline: &[(Cow<'_, str>, Cow<'_, str>)],
    matched_sheets: &[&HashMap<String, String>],
) -> Self

Собирает стиль через lookup(): для каждого поля вызывается lookup(key, inline, matched_sheets), затем парсится соответствующей функцией. Особенности:

  • padding/margin — сначала ищутся индивидуальные (-top, -right, и т.д.), потом общие.
  • spacing — альтернативное имя gap.
  • background — сначала background, затем background-color.
  • overflow-x/overflow-y — если индивидуальный не найден, применяется общий overflow.
  • flex_grow — парсится как f32, кастуется в u16.

ComputedStyle::compute_batch()

#[cfg(feature = "parallel")]
pub fn compute_batch<'a>(
    pairs: &[(&[(Cow<'_, str>, Cow<'_, str>)], &[&'a HashMap<String, String>])],
) -> Vec<ComputedStyle>

Параллельный batch-вариант через rayon::par_iter.

ComputedStyle::apply_overrides()

pub fn apply_overrides(&mut self, sheet: &HashMap<String, String>)

Применяет (перезаписывает) заданный набор свойств поверх существующего стиля. Используется для динамических изменений (например :hover-правила, inline-переопределения).

Как работает lookup()

fn lookup<'a>(
    key: &str,
    inline: &'a [(Cow<'_, str>, Cow<'_, str>)],
    matched_sheets: &[&'a HashMap<String, String>],
) -> Option<&'a str>

Порядок разрешения свойства:

  1. Inline-свойства — перебор пар (key, value). Поддерживает префикс style: (т.е. style:color эквивалентен color).
  2. matched_sheets — список словарей от подходящих CSS-правил, отсортированный по специфичности. Перебирается с конца (последний — самый специфичный).
  3. Возвращается первое найденное значение.

Функции парсинга

Функция Сигнатура Описание
parse_size (s: &str) -> Option<SizeValue> Парсит размер: "10"Px(10), "50%"Percent(50). auto, fill, stretchNone
parse_color (s: &str) -> Option<iced::Color> Парсит цвет: #rgb, #rrggbb, #rrggbbaa, имена (white, black, transparent)
parse_length (s: &str) -> Option<iced::Length> Парсит длину Iced: "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> Число 0.01.0, clamp
parse_font_weight (s: &str) -> Option<u16> Имена: normal→400, bold→700, lighter→300, bolder→900; числовые значения
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>

Удобная обёртка над SizeValue::resolve, возвращает Option<f32>.


Использование в renderer.rs

Поля ComputedStyle активно используются в /home/faynot/software/glint-runtime/src/renderer.rs:

Поле Где используется
.color Цвет текста в кнопках, текстовых полях, Label
.background Фон контейнеров, кнопок, текстовых полей
.padding* iced::Padding для кнопок, полей ввода, контейнеров
.margin* Отступы вокруг элементов
.border_radius, .border_width, .border_color Рамки кнопок, полей ввода, контейнеров
.width, .height Размеры Scrollable, Column, Row, Image
.min_width, .max_width, .min_height, .max_height Ограничения размеров
.direction Направление флекса (Row/Column)
.align_items Выравнивание дочерних элементов
.content_align Выравнивание контента
.flex_grow Flex-grow с FillPortion
.spacing iced::container::Style spacing, gap в Row/Column
.position Static / Fixed / Absolute / Sticky
.top, .right, .bottom, .left Позиционирование
.overflow_x, .overflow_y Скроллинг (Scrollable)
.display Display::None — скрытие элемента
.opacity Прозрачность
.font_weight Вес шрифта в Text
.line_height Межстрочный интервал
.text_align Горизонтальное выравнивание текста
.font_size Размер шрифта (передаётся от родителя)

nth_matches(expr: &str, n: usize) -> bool

Внутренняя функция для разрешения :nth-child(an+b), :nth-child(odd), :nth-child(even) и :nth-child(<число>). Поддерживает отрицательные a и b.


Связи с другими модулями

  • types.rs — каждый DomNode (как элемент, так и текстовый узел) содержит computed_style: ComputedStyle.
  • renderer.rs — импортирует {ComputedStyle, ContentAlign, Display, LayoutDirection, Position, Overflow, StructuralContext, StyleSheet, TextAlign, resolve_size}.
  • mod.rs — использует AncestorInfo, ComputedStyle, StructuralContext при обходе DOM.