feat: new styles

This commit is contained in:
faynot
2026-07-08 23:45:37 +03:00
parent 9c279015dc
commit 75af23930f
18 changed files with 3556 additions and 816 deletions

View File

@@ -0,0 +1,570 @@
# План завершения стилевой системы 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 строк нового кода.