Files
Glint-Runtime/STYLE-SYSTEM-COMPLETION-PLAN.md
2026-07-08 23:45:37 +03:00

571 lines
22 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# План завершения стилевой системы 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<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`:
- Записать дерево селектора в байткод.
- На каждый тип узла — свой подопкод (0x300x3F).
- Компилятор: сериализация структуры селектора.
- Читатель (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<String, AnimationInstance>`.
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<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.01.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>` | `start`, `center`, `end`, `space-between`, `space-around`, `space-evenly` |
| `flex-wrap` | `Option<FlexWrap>` | `nowrap`, `wrap`, `wrap-reverse` |
| `flex-direction` | `Option<FlexDirection>` | (расширение `LayoutDirection`) |
| `align-self` | `Option<Alignment>` | переопределяет `align-items` для конкретного элемента |
| `align-content` | `Option<AlignContent>` | multi-line выравнивание |
| `flex` (шорткат) | — | grow, shrink, basis |
| `flex-shrink` | `Option<f32>` | |
| `flex-basis` | `Option<Length>` | |
### 6.4 Grid Layout
| Свойство | Тип |
|----------|-----|
| `grid-template-columns` | `Option<Vec<GridTrack>>` | `1fr 1fr`, `repeat(3, 1fr)`, `auto` |
| `grid-template-rows` | `Option<Vec<GridTrack>>` | |
| `grid-gap` / `gap` | уже есть как `spacing` | |
| `grid-column` | `Option<(u32, u32)>` | start / end |
| `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>` | `visible`, `hidden` |
| `cursor` | `Option<Cursor>` | `pointer`, `default`, `text` |
| `pointer-events` | `Option<PointerEvents>` | `auto`, `none` |
| `transform` | `Option<Vec<Transform>>` | `translate(x,y)`, `scale(s)`, `rotate(a)` |
| `transform-origin` | `Option<String>` | |
| `backdrop-filter` | `Option<Vec<Filter>>` | `blur(10px)`, `brightness(1.2)` |
| `filter` | `Option<Vec<Filter>>` | то же |
| `list-style` | `Option<ListStyle>` | |
| `isolation` | `Option<Isolation>` | `auto`, `isolate` |
| `mix-blend-mode` | `Option<BlendMode>` | `multiply`, `screen` |
### 6.7 Итого: ~4060 новых полей в 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()`
```rust
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`
```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<String, String>,
pub display: Option<Display>,
pub visibility: Option<Visibility>,
}
```
### 13.2 Selector engine — отдельный модуль
```rust
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 — расширение
```rust
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 |
**Итого:** ~57 новых файлов, ~15 изменённых, ~50006000 строк нового кода.