22 KiB
План завершения стилевой системы Glint
Цель
Достичь полной мощности HTML+CSS в языке Glint. Система разделена на 4 уровня:
- Парсер (glt/src/style_parser.rs) — чтение
.glts - AST + Компилятор (glt/src/ast.rs, compiler.rs) — представление и байткод
- Интерпретатор (runtime/src/interpreter/) — VDOM + вычисление стилей
- Рендерер (runtime/src/renderer.rs) — отрисовка через Iced
1. Селекторы (критично)
1.1 Структурированное представление селектора
Сейчас: Селектор — плоская строка "Panel.desktop", matching по точному совпадению.
Нужно: Разобрать селектор в структуру:
enum Selector {
Simple(SimpleSelector),
Compound(CompoundSelector), // без комбинатора
Complex {
left: Box<Selector>,
combinator: Combinator,
right: Box<Selector>,
},
List(Vec<Selector>), // запятые: `Button, .btn, #id`
}
struct SimpleSelector {
tag: Option<String>, // `Button` | `*`
id: Option<String>, // `#myid`
classes: Vec<String>, // `.foo.bar`
pseudo_classes: Vec<PseudoClass>, // `:hover`
pseudo_element: Option<PseudoElement>, // `::before`
attribute: Vec<AttributeSelector>, // `[disabled]`, `[type="text"]`
}
enum Combinator {
Descendant, // пробел
Child, // >
NextSibling, // +
Subsequent, // ~
}
enum PseudoClass {
Hover, Active, Focus,
FirstChild, LastChild,
NthChild(i32, i32), // An+B
FirstOfType, LastOfType,
Disabled, Enabled, Checked,
Root, Empty,
Not(Box<Selector>),
// ...
}
struct AttributeSelector {
name: String,
op: AttrOp,
value: Option<String>,
}
enum AttrOp { Set, Eq, Contain, StartsWith, EndsWith, InList }
1.2 Парсинг селекторов (style_parser.rs)
- Заменить
parse_selector()на полноценный парсер селекторов. - Поддержка:
Button.active:hover,Panel > .btn,#header,[type="text"],*,:nth-child(2n+1), запятые. - Валидация и сообщения об ошибках.
1.3 Байткод для селекторов
- Новый опкод
OP_SELECTORили расширениеOP_STYLE_RULE:- Записать дерево селектора в байткод.
- На каждый тип узла — свой подопкод (0x30–0x3F).
- Компилятор: сериализация структуры селектора.
- Читатель (reader.rs): десериализация обратно в структуру.
1.4 Runtime matching (interpreter/mod.rs)
- Заменить
collect_matching_styles()на алгоритм обхода дерева селекторов. - Специфичность: inline > id > class/attr/pseudo > tag.
- При одинаковой специфичности — порядок объявления (последний побеждает).
- Комбинаторы: при обходе VDOM подниматься вверх по предкам/соседям.
- Кэширование результатов matching для производительности.
2. Псевдоклассы (высокий приоритет)
2.1 Интерактивные (:hover, :active, :focus)
Сейчас: Только у Button ховер/нажатие — хардкод в renderer.
Нужно:
- Хранить состояние интерактивности в VDOM/Element (
ElementState). - В renderer передавать статус (hovered/pressed/focused) в
collect_matching_styles. :hover-стили переопределяют базовые.- Плавный переход между состояниями (см. transitions).
Изменения:
types.rs:Elementможет получить полеstate: ElementState.renderer.rs: уrender_elementпоявляется доступ к состоянию мыши.style.rs:ComputedStyle::compute()принимает псевдоклассы.
2.2 Структурные (:first-child, :nth-child, :last-child, :first-of-type, :last-of-type, :empty, :root)
Сейчас: Не поддерживаются.
Нужно:
- При matching вычислять позицию элемента среди siblings.
- Для
:nth-child(An+B)— парсить выражение и проверять(index - B) % A == 0. :empty—el.children.is_empty().:root— элемент верхнего уровня в VDOM.
2.3 Состояния (:disabled, :enabled, :checked)
:disabled/:enabled— по свойствуdisabled.:checked— по свойствуchecked.
3. Медиа-запросы (@media) (средний приоритет)
3.1 Парсер
@media (max-width: 600px) {
Panel { direction: vertical }
}
- Парсить
@mediaвDirective::MediaQuery { query, child_span }. - Поддержка:
width,height,min-width,max-width,orientation,prefers-color-scheme. - Логические операторы:
and,or,not,,(or).
3.2 Runtime
- Собрать информацию об окне (размер, тема ОС).
- При изменении размера окна переоценивать media queries.
- Хранить в
Document::media_state: MediaState. collect_matching_stylesпроверяет media query перед добавлением правил.
4. Анимации (@anim) (средний приоритет)
4.1 Парсинг — уже есть
@anim name { from { ... } to { ... } } — парсится и компилируется.
4.2 Runtime — НЕ реализован
Сейчас: OP_STYLE_ANIM пропускается.
Нужно:
- Хранить
KeyframeAnimationвStyleSheet(не только в байткоде, но и в runtime-структуре). - В
Elementполеanimation_state: HashMap<String, AnimationInstance>. - Система тиков:
- Iced
subscriptionна каждый кадр (on_every_event→Event::Window(RedrawRequested)). - При каждом тике обновлять
elapsedдля активных анимаций. - Интерполировать между keyframes.
- Iced
- Свойства
animation-name,animation-duration,animation-timing-function,animation-delay,animation-iteration-count,animation-fill-mode. - Поддержка
@keyframesс процентами:0% { ... } 50% { ... } 100% { ... }.
4.3 Timing functions
linear,ease,ease-in,ease-out,ease-in-out.cubic-bezier(p1x, p1y, p2x, p2y).steps(n, direction).
5. Транзишены (transition) (средний приоритет)
5.1 Свойства
transition-property,transition-duration,transition-timing-function,transition-delay.- Шорткат
transition: all 0.3s ease.
5.2 Runtime
- При изменении
ComputedStyle(например, при ховере) не применять мгновенно, а запускать интерполяцию. - Хранить
TransitionStateна элемент:HashMap<String, (start_value, end_value, elapsed, duration, easing)>. - Каждый кадр обновлять transitioning-свойства.
6. Новые CSS-свойства в ComputedStyle
6.1 Типографика
| Свойство | Тип | Парсер |
|---|---|---|
font-family |
Option<String> |
список шрифтов |
font-weight |
Option<u16> |
normal(400), bold(700), числовое |
font-style |
Option<FontStyle> |
normal, italic, oblique |
line-height |
Option<f32> |
числовое или normal |
letter-spacing |
Option<f32> |
px |
text-align |
Option<TextAlign> |
left, center, right, justify |
text-decoration |
Option<TextDecoration> |
none, underline, line-through |
text-transform |
Option<TextTransform> |
none, uppercase, lowercase, capitalize |
white-space |
Option<WhiteSpace> |
normal, nowrap, pre |
word-break |
Option<WordBreak> |
normal, break-all, keep-all |
text-overflow |
Option<TextOverflow> |
clip, ellipsis |
6.2 Фон и границы
| Свойство | Тип | Парсер |
|---|---|---|
opacity |
Option<f32> |
0.0–1.0 |
background-image |
Option<String> |
url(...) |
background-repeat |
Option<BgRepeat> |
repeat, no-repeat |
background-size |
Option<BgSize> |
cover, contain, px, % |
background-position |
Option<BgPos> |
center, top left, px |
linear-gradient(...) |
Option<Gradient> |
парсить в Value::Call и обрабатывать |
box-shadow |
Option<Vec<Shadow>> |
offset-x offset-y blur spread color |
text-shadow |
Option<Vec<Shadow>> |
то же |
outline |
Option<Outline> |
width, style, color |
border-style |
Option<BorderStyle> |
solid, dashed, dotted |
border (шорткат) |
— | разворачивать в width/style/color |
6.3 Flexbox (расширение)
| Свойство | Тип |
|---|---|
justify-content |
Option<JustifyContent> |
flex-wrap |
Option<FlexWrap> |
flex-direction |
Option<FlexDirection> |
align-self |
Option<Alignment> |
align-content |
Option<AlignContent> |
flex (шорткат) |
— |
flex-shrink |
Option<f32> |
flex-basis |
Option<Length> |
6.4 Grid Layout
| Свойство | Тип |
|---|---|
grid-template-columns |
Option<Vec<GridTrack>> |
grid-template-rows |
Option<Vec<GridTrack>> |
grid-gap / gap |
уже есть как spacing |
grid-column |
Option<(u32, u32)> |
grid-row |
Option<(u32, u32)> |
grid-auto-flow |
Option<GridFlow> |
6.5 Позиционирование (расширение)
| Свойство | Тип | Статус |
|---|---|---|
position: absolute |
— | сейчас мэппится в Static |
position: relative |
— | сейчас мэппится в Static |
position: sticky |
— | сейчас мэппится в Static |
z-index |
Option<i32> |
|
display |
Option<Display> |
block, flex, grid, none, inline |
6.6 Прочее
| Свойство | Тип |
|---|---|
visibility |
Option<Visibility> |
cursor |
Option<Cursor> |
pointer-events |
Option<PointerEvents> |
transform |
Option<Vec<Transform>> |
transform-origin |
Option<String> |
backdrop-filter |
Option<Vec<Filter>> |
filter |
Option<Vec<Filter>> |
list-style |
Option<ListStyle> |
isolation |
Option<Isolation> |
mix-blend-mode |
Option<BlendMode> |
6.7 Итого: ~40–60 новых полей в ComputedStyle
Каждое поле: тип Option<T>, значение по умолчанию None.
Парсинг: новая функция parse_<property>(s: &str) -> Option<T> для каждого.
Lookup в ComputedStyle::compute().
Рендеринг в Iced:
- Iced 0.14 поддерживает Background::Gradient.
- Box-shadow пока не поддерживается, но можно эмулировать через container style с offset-тенью.
- Transforms и filters — Iced пока не нативно; нужен кастомный widget или пропуск.
7. Система каскада и наследования
7.1 Наследование (сейчас: только color, font-size)
Добавить наследование для:
font-family,font-weight,font-style,line-height,letter-spacing,text-align,white-space,word-break,text-transform,visibility,cursor,pointer-events.
Механизм: в render_element() передавать не только parent_color и parent_font_size, а &ComputedStyle родителя или отдельный InheritedStyle.
7.2 inherit / initial / unset
- Специальные значения для любого свойства.
7.3 !important
- Флаг важности в правиле. Переопределяет специфичность.
- Парсить
value!important.
8. CSS-функции и значения
8.1 calc()
enum CalcValue {
Number(f32),
Percentage(f32),
Add(Box<CalcValue>, Box<CalcValue>),
Sub(Box<CalcValue>, Box<CalcValue>),
Mul(Box<CalcValue>, Box<CalcValue>),
Div(Box<CalcValue>, Box<CalcValue>),
Var(String),
}
- Парсить
calc(100% - 20px). - Вычислять в runtime с учётом контекста (размер родителя).
8.2 var() — CSS custom properties
--my-var: value;в правилах.var(--my-var, fallback)при использовании.- Хранить custom properties в
ComputedStyle.custom_props: HashMap<String, String>. - Наследуются по умолчанию.
8.3 min(), max(), clamp()
min(100%, 500px),max(200px, 50%),clamp(200px, 50%, 500px).
8.4 rgb(), rgba(), hsl(), hsla(), hwb(), oklch(), color-mix()
- Сейчас только
#hexи 3 named colors. - Парсить
rgb(255, 0, 0),rgba(255, 0, 0, 0.5). - Расширить
parse_color().
8.5 Named colors
- Расширить словарь до 140+ CSS named colors (AliceBlue, ...).
9. @-правила
9.1 @import
@import "theme.glts"
- В парсере: обработать
@import, загрузить файл, влить переменные и миксины. - Может быть сложным (циклы, кэширование). Опционально на раннем этапе.
9.2 @font-face
@font-face {
font-family: "MyFont"
src: fs:/fonts/myfont.ttf
}
- Регистрировать в Iced через
font::Family. - Потребует
iced::font::load().
9.3 @scope (CSS Cascading Layers)
@scope (.card) { ... }— ограничение области действия правил.
10. Рендеринг (renderer.rs)
10.1 Псевдоклассы в рендере
- Для интерактивных элементов передавать
button::Status. :hover/:active-стили должны пересчитываться при изменении статуса.
10.2 Flexbox (полноценный)
justify-content: IcedRow/Columnне имеют нативного justifyContent; эмулировать через наполнители (Length::Fill).flex-wrap: нуженFlowwidget или эмуляция строками.
10.3 Grid
- Полноценный CSS Grid: имплементировать кастомный Iced widget или разбивать на строки/колонки вручную.
grid-template-columns: 1fr 1fr 1fr→ разбивка children на ряды.grid-column/grid-row: позиционирование ячеек.
10.4 display: none
- Пропускать элемент при рендеринге (уже частично —
Noneвозвращается).
10.5 z-index
- Сортировать fixed-слой по z-index внутри stack.
10.6 position: absolute / relative
relative: смещение через padding контейнера.absolute: позиционирование относительно предка сposition: relative(или окна). Эмулировать через stack слои, какfixed.
10.7 position: sticky
- Эмулировать через Iced scrollable + перехват событий скролла.
- Пока не поддерживается Iced нативно. Можно через subscription на scroll position + ручное позиционирование.
10.8 opacity
- Iced:
widget.opacity(f32)(iced 0.14+).
10.9 box-shadow, text-shadow
- Эмуляция: container с подложкой (offset background) или кастомный шейдер.
- Iced 0.14 имеет
Borderтолько сcolor,width,radius. Shadow — нет.
10.10 outline
- Через border с
outline-offsetэмуляцией.
10.11 Трансформы (transform)
- Iced не поддерживает transforms нативно.
- Потребуется кастомный widget с
lyonилиvelloдля path-трансформаций. - На раннем этапе — документировать как unsupported.
11. Инструментарий разработчика
11.1 Вывод вычисленных стилей
- Флаг
--debug-stylesв CLI: печататьComputedStyleкаждого элемента.
11.2 Инспектор элементов
- Iced debug overlay:
iced::widget::pane_gridс инспектором. - Показывать matched selectors, specificity, computed properties.
11.3 Hot-reload стилей
- Перекомпиляция
.gltsбез перезапуска приложения. - Обновление bytecode в runtime.
12. Порядок реализации (приоритеты)
Фаза 1: Критическое (MVP+)
- Структурированные селекторы + matching engine со специфичностью
- Комбинаторы (особенно descendant)
- Псевдоклассы
:hover,:active,:focus - Селектор по ID (
#id) - Множественные селекторы через запятую
- Псевдоклассы
:first-child,:nth-child,:last-child,:empty
Фаза 2: Визуальное обогащение
- Новые свойства: opacity, font-weight, text-align, justify-content, line-height
- fill-шорткаты для flex (flex-grow/shrink/basis)
position: absolute/relative/stickyz-indexdisplay: nonecalc(),min(),max(),clamp()rgb(),rgba(),hsl()+ расширенный словарь named colors!important
Фаза 3: Продвинутое
@mediaqueries- Animations (playback)
- Transitions
box-shadow,text-shadow(эмуляция)- CSS Grid
flex-wrap+justify-contentполноценноvar()CSS custom properties@import@font-face
Фаза 4: Экспертное
transform,transform-originbackdrop-filter,filtergradient(linear, radial)color-mix(),oklch()- Hot-reload
- DevTools инспектор
@scope
13. Архитектурные изменения
13.1 ComputedStyle → разбить на подс-труктуры
Вместо 40+ плоских полей:
pub struct ComputedStyle {
pub box_model: BoxModel,
pub typography: Typography,
pub background: BackgroundStyle,
pub border: BorderStyle,
pub flex: FlexStyle,
pub grid: GridStyle,
pub position: Positioning,
pub effects: Effects,
pub overflow: OverflowStyle,
pub animation: AnimationStyle,
pub custom: HashMap<String, String>,
pub display: Option<Display>,
pub visibility: Option<Visibility>,
}
13.2 Selector engine — отдельный модуль
mod selector {
pub struct Selector { ... }
pub fn parse(input: &str) -> Result<Selector, String>;
pub fn specificity(&self) -> (u32, u32, u32);
pub fn matches(&self, el: &Element, ctx: &MatchContext) -> bool;
}
13.3 StyleSheet — расширение
pub struct StyleRule {
pub selector: Selector,
pub properties: HashMap<String, String>,
pub source_order: u32,
pub media: Option<MediaQuery>,
pub important: HashSet<String>,
}
13.4 Система тиков для анимаций/транзишенов
- Новый модуль
animationв runtime. AnimationEngineсHashMap<AnimationId, AnimationInstance>.- Iced subscription:
time::every(Duration::from_millis(16))(60fps).
14. Оценка сложности
| Компонент | Новые файлы | Изменённые файлы | Пример LOC |
|---|---|---|---|
| Selector parser | glt/src/selector.rs |
style_parser.rs |
~500 |
| Selector matching | runtime/src/selector.rs |
mod.rs |
~400 |
| ComputedStyle расширение | — | style.rs |
+400 |
| Медиа-запросы | — | style_parser.rs, mod.rs |
~300 |
| Анимации | runtime/src/animation.rs |
style.rs, mod.rs, app.rs |
~500 |
| Транзишены | runtime/src/transition.rs |
~400 | |
| Псевдоклассы | — | mod.rs, renderer.rs, types.rs |
~300 |
| Новые парсеры свойств | — | style.rs |
+600 |
| Рендеринг | — | renderer.rs |
+500 |
| Flex/Grid | runtime/src/layout.rs |
renderer.rs |
~400 |
| calc/var | runtime/src/values.rs |
style.rs |
~300 |
| @import/@font-face | — | style_parser.rs, renderer.rs |
~200 |
| Инструменты | runtime/src/inspector.rs |
cli.rs, app.rs |
~300 |
Итого: ~5–7 новых файлов, ~15 изменённых, ~5000–6000 строк нового кода.