# План завершения стилевой системы 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 по точному совпадению. **Нужно:** Разобрать селектор в структуру: ```rust enum Selector { Simple(SimpleSelector), Compound(CompoundSelector), // без комбинатора Complex { left: Box, combinator: Combinator, right: Box, }, List(Vec), // запятые: `Button, .btn, #id` } struct SimpleSelector { tag: Option, // `Button` | `*` id: Option, // `#myid` classes: Vec, // `.foo.bar` pseudo_classes: Vec, // `:hover` pseudo_element: Option, // `::before` attribute: Vec, // `[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), // ... } struct AttributeSelector { name: String, op: AttrOp, value: Option, } 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 Парсер ```glts @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` пропускается. **Нужно:** 1. Хранить `KeyframeAnimation` в `StyleSheet` (не только в байткоде, но и в runtime-структуре). 2. В `Element` поле `animation_state: HashMap`. 3. Система тиков: - Iced `subscription` на каждый кадр (`on_every_event` → `Event::Window(RedrawRequested)`). - При каждом тике обновлять `elapsed` для активных анимаций. - Интерполировать между keyframes. 4. Свойства `animation-name`, `animation-duration`, `animation-timing-function`, `animation-delay`, `animation-iteration-count`, `animation-fill-mode`. 5. Поддержка `@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`. - Каждый кадр обновлять transitioning-свойства. --- ## 6. Новые CSS-свойства в ComputedStyle ### 6.1 Типографика | Свойство | Тип | Парсер | |----------|-----|--------| | `font-family` | `Option` | список шрифтов | | `font-weight` | `Option` | `normal(400)`, `bold(700)`, числовое | | `font-style` | `Option` | `normal`, `italic`, `oblique` | | `line-height` | `Option` | числовое или `normal` | | `letter-spacing` | `Option` | px | | `text-align` | `Option` | `left`, `center`, `right`, `justify` | | `text-decoration` | `Option` | `none`, `underline`, `line-through` | | `text-transform` | `Option` | `none`, `uppercase`, `lowercase`, `capitalize` | | `white-space` | `Option` | `normal`, `nowrap`, `pre` | | `word-break` | `Option` | `normal`, `break-all`, `keep-all` | | `text-overflow` | `Option` | `clip`, `ellipsis` | ### 6.2 Фон и границы | Свойство | Тип | Парсер | |----------|-----|--------| | `opacity` | `Option` | 0.0–1.0 | | `background-image` | `Option` | `url(...)` | | `background-repeat` | `Option` | `repeat`, `no-repeat` | | `background-size` | `Option` | `cover`, `contain`, px, % | | `background-position` | `Option` | `center`, `top left`, px | | `linear-gradient(...)` | `Option` | парсить в `Value::Call` и обрабатывать | | `box-shadow` | `Option>` | `offset-x offset-y blur spread color` | | `text-shadow` | `Option>` | то же | | `outline` | `Option` | width, style, color | | `border-style` | `Option` | `solid`, `dashed`, `dotted` | | `border` (шорткат) | — | разворачивать в width/style/color | ### 6.3 Flexbox (расширение) | Свойство | Тип | |----------|-----| | `justify-content` | `Option` | `start`, `center`, `end`, `space-between`, `space-around`, `space-evenly` | | `flex-wrap` | `Option` | `nowrap`, `wrap`, `wrap-reverse` | | `flex-direction` | `Option` | (расширение `LayoutDirection`) | | `align-self` | `Option` | переопределяет `align-items` для конкретного элемента | | `align-content` | `Option` | multi-line выравнивание | | `flex` (шорткат) | — | grow, shrink, basis | | `flex-shrink` | `Option` | | | `flex-basis` | `Option` | | ### 6.4 Grid Layout | Свойство | Тип | |----------|-----| | `grid-template-columns` | `Option>` | `1fr 1fr`, `repeat(3, 1fr)`, `auto` | | `grid-template-rows` | `Option>` | | | `grid-gap` / `gap` | уже есть как `spacing` | | | `grid-column` | `Option<(u32, u32)>` | start / end | | `grid-row` | `Option<(u32, u32)>` | | | `grid-auto-flow` | `Option` | | ### 6.5 Позиционирование (расширение) | Свойство | Тип | Статус | |----------|-----|--------| | `position: absolute` | — | сейчас мэппится в Static | | `position: relative` | — | сейчас мэппится в Static | | `position: sticky` | — | сейчас мэппится в Static | | `z-index` | `Option` | | | `display` | `Option` | `block`, `flex`, `grid`, `none`, `inline` | ### 6.6 Прочее | Свойство | Тип | |----------|-----| | `visibility` | `Option` | `visible`, `hidden` | | `cursor` | `Option` | `pointer`, `default`, `text` | | `pointer-events` | `Option` | `auto`, `none` | | `transform` | `Option>` | `translate(x,y)`, `scale(s)`, `rotate(a)` | | `transform-origin` | `Option` | | | `backdrop-filter` | `Option>` | `blur(10px)`, `brightness(1.2)` | | `filter` | `Option>` | то же | | `list-style` | `Option` | | | `isolation` | `Option` | `auto`, `isolate` | | `mix-blend-mode` | `Option` | `multiply`, `screen` | ### 6.7 Итого: ~40–60 новых полей в ComputedStyle Каждое поле: тип `Option`, значение по умолчанию `None`. Парсинг: новая функция `parse_(s: &str) -> Option` для каждого. 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()` ```rust enum CalcValue { Number(f32), Percentage(f32), Add(Box, Box), Sub(Box, Box), Mul(Box, Box), Div(Box, Box), 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`. - Наследуются по умолчанию. ### 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` ```glts @import "theme.glts" ``` - В парсере: обработать `@import`, загрузить файл, влить переменные и миксины. - Может быть сложным (циклы, кэширование). Опционально на раннем этапе. ### 9.2 `@font-face` ```glts @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`: Iced `Row`/`Column` не имеют нативного justifyContent; эмулировать через наполнители (`Length::Fill`). - `flex-wrap`: нужен `Flow` widget или эмуляция строками. ### 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+) 1. Структурированные селекторы + matching engine со специфичностью 2. Комбинаторы (особенно descendant) 3. Псевдоклассы `:hover`, `:active`, `:focus` 4. Селектор по ID (`#id`) 5. Множественные селекторы через запятую 6. Псевдоклассы `:first-child`, `:nth-child`, `:last-child`, `:empty` ### Фаза 2: Визуальное обогащение 7. Новые свойства: opacity, font-weight, text-align, justify-content, line-height 8. fill-шорткаты для flex (flex-grow/shrink/basis) 9. `position: absolute` / `relative` / `sticky` 10. `z-index` 11. `display: none` 12. `calc()`, `min()`, `max()`, `clamp()` 13. `rgb()`, `rgba()`, `hsl()` + расширенный словарь named colors 14. `!important` ### Фаза 3: Продвинутое 15. `@media` queries 16. Animations (playback) 17. Transitions 18. `box-shadow`, `text-shadow` (эмуляция) 19. CSS Grid 20. `flex-wrap` + `justify-content` полноценно 21. `var()` CSS custom properties 22. `@import` 23. `@font-face` ### Фаза 4: Экспертное 24. `transform`, `transform-origin` 25. `backdrop-filter`, `filter` 26. `gradient` (linear, radial) 27. `color-mix()`, `oklch()` 28. Hot-reload 29. DevTools инспектор 30. `@scope` --- ## 13. Архитектурные изменения ### 13.1 `ComputedStyle` → разбить на подс-труктуры Вместо 40+ плоских полей: ```rust 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, pub display: Option, pub visibility: Option, } ``` ### 13.2 Selector engine — отдельный модуль ```rust mod selector { pub struct Selector { ... } pub fn parse(input: &str) -> Result; pub fn specificity(&self) -> (u32, u32, u32); pub fn matches(&self, el: &Element, ctx: &MatchContext) -> bool; } ``` ### 13.3 StyleSheet — расширение ```rust pub struct StyleRule { pub selector: Selector, pub properties: HashMap, pub source_order: u32, pub media: Option, pub important: HashSet, } ``` ### 13.4 Система тиков для анимаций/транзишенов - Новый модуль `animation` в runtime. - `AnimationEngine` с `HashMap`. - 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 строк нового кода.