Forge
markdown6dd9109c
1# Skia-поверхности вместо зашитых сплитов и оверлеев (черновик для обсуждения)
2
3**Статус:** Draft — зафиксировать общее понимание; при стабилизации можно сжать в ADR или раздел в [0017](../adr/0017-multi-window-workspace-and-agent-surfaces.md).
4**Дата:** 2026-04-16
5**Связь:** [ADR 0017](../adr/0017-multi-window-workspace-and-agent-surfaces.md) (мультиоконность, `MfdHostWindow`, полный `MfdShellView`), [0021](../adr/0021-pfd-mfd-cockpit-attention-model.md) (зоны внимания), [0022](../adr/0022-mfd-visual-design-surface-axaml-blazor.md) (визуальная поверхность в MFD — ортогонально теме Skia, но та же идея «инструмент в контуре», не отдельный хак).
6
7---
8
9## Зачем этот документ
10
11Переход на **Skia** в стеке Avalonia задумывался не только как смена бэкенда отрисовки, а как способ:
12
131. **Уйти от зашитых сплитов** в XAML (`Grid`, `GridSplitter`, фиксированные колонки под каждый новый инструмент) — они быстро становятся продуктовым контрактом: каждая новая сущность тянет ещё одну «полку» в главном окне.
142. **Отдать композицию и отрисовку** слою, где раскладку проще менять (режимы, пресеты, второй монитор, кокпит) без разрастания дерева панелей.
153. **Перестать опираться на оверлеи** как на основной способ показать постоянный UI — и перейти к **нормальным Skia-поверхностям**: узлы в дереве композиции с предсказуемым измерением, вводом, фокусом и клипом.
16
17Цель документа — **выровняться по терминам** и зафиксировать направление до того, как оно разъедется по разным ADR и коду.
18
19---
20
21## Термины
22
23| Термин | Что имеем в виду |
24|--------|------------------|
25| **Зашитый сплит** | Раскладка в XAML, где новый инструмент = новая колонка/строка или обязательный `GridSplitter` в главном хроме, без участия модели презентации / зон внимания. |
26| **Оверлей** | Слой «поверх» основного дерева (popup, полупрозрачная панель, отдельный `OverlayLayer`, плавающий прямоугольник), удобный для прототипа, но с отдельными правилами z-order, hit-testing и фокуса. |
27| **Skia-поверхность (первая класса)** | Регион, который живёт в **явном месте** композиции: участвует в layout, получает pointer/keyboard как соседние зоны, рисуется через Skia в своих границах. Не замена `Window`, а **нормальный** участок UI, а не «налепка». Типичная реализация в дереве Avalonia — **`SkiaHost`**. |
28| **SkiaHost** | **Контрол Avalonia** (`Control`), который **внутри своих границ хостит отрисовку Skia** (measure/arrange, клип, ввод — по правилам дерева). Ставится в слот кабины / разметку как обычный узел. **Не путать** с [`MfdHostWindow`](../../Views/MfdHostWindow.axaml) — это отдельное **окно** вторичного контура; `SkiaHost` — **обёртка-контрол** под канву внутри окна. |
29| **Вторичный контур** | `MfdShellView`: переключаемые **страницы** `SecondaryShellPage` (чат, терминал, сборка, обозреватель решения, …). Зона внимания **Mfd** — якорь презентации; **страница** — не то же самое, что зона ([0021](../adr/0021-pfd-mfd-cockpit-attention-model.md)). |
30
31### SkiaHost в коде (когда вводить тип)
32
33**Имя фиксируем в репозитории** не только «чтобы не путаться», хотя дизамбиг важен: в проекте уже много `*Host*` (`MfdHostWindow`, host surface, каналы) — отдельный тип **`SkiaHost`** делает явным **контрол Avalonia с канвой Skia внутри окна**, а не второе `TopLevel` и не абстрактный «хост поверхности» в смысле CDS.
34
35**Инженерно** тип имеет смысл как **единая точка** для общего поведения: инвалидация перерисовки, DPI / layout rounding, при необходимости маршрут ввода — чтобы не плодить копипасту «ещё один кастомный `Control` со Skia» в каждом слоте; удобный **`grep SkiaHost`** для ревью.
36
37**Когда заводить в коде:** когда начинаете **стабильно** монтировать Skia в слотах кабины (не обязательно отдельный PR «только имя» до первого реального использования). Пока Skia только в превью/экспериментах и размазан по контролам — документ термина достаточен; при консолидации — **имя типа в коде совпадает с этим документом**.
38
39---
40
41## Принятые ориентиры
42
431. **Презентация задаёт топологию**, а не набор сплитов вручную: строка `presentation` / `zone_screen_layout` и грамматика якорей ([0017](../adr/0017-multi-window-workspace-and-agent-surfaces.md)). На главном окне при схемах вроде `(P+F)(M)` в фокусе **P и лобовое**; содержимое **M** (включая вторичный контур) не дублируется отдельной «полкой» только ради одного инструмента.
44
452. **Обозреватель решения** — **страница** вторичного контура (`SecondaryShellPage.SolutionExplorer`), а не отдельная колонка со сплитом «дерево + shell» в `MainWindow`. То же дерево доступно в колонке Mfd и в `MfdHostWindow`, пока открыта эта страница — без второго независимого дубля MFD-контента в main ([0017 п. 8](../adr/0017-multi-window-workspace-and-agent-surfaces.md#adr0017-p8-mfd-host-wide)).
46
473. **Оверлей — не зло сам по себе; зло — постоянный UI «налепкой».** Оверлеи в прототипе **доказали работоспособность**; то, что теперь **путает и сбивает** (постоянные панели, карты, инструменты), **убираем** в пользу **полноценных поверхностей в зоне** (в т.ч. Skia). **Где оверлей по смыслу уместен — оставляем:** тосты, подсказки, превью перетаскивания, модалка на весь экран, иной **временный и контекстный** слой — иначе пришлось бы дублировать ту же семантику в дереве ради запрета на «поверх». Итог: **не «ноль оверлеев»**, а **не подменять ими зоны презентации**.
48
494. **Skia** — способ **отрисовки и гибкой композиции** внутри выделенных регионов (в т.ч. кокпит, отдельные инструменты), а не оправдание очередного глобального слоя поверх всего окна.
50
515. **Слабая связность и роль Avalonia.** Рефакторинг и вынос логики из «прилипшего к вью» кода нацелены на то, чтобы **оркестрация, правила презентации, команды, состояние workspace** жили во **view model и сервисах** с явными границами. **Avalonia** остаётся слоем **хоста и тяжёлых контролов**: окна, ввод и фокус, жизненный цикл визуала, встраивание дорогих виджетов (редактор, докинг, списки и т.п.) — всё, ради чего не имеет смысла писать свой UI-стек с нуля. Это **не** утверждение «половина экрана не на Avalonia»: типично весь chrome всё ещё дерево Avalonia; речь о **узкой ответственности** — не размазывать семантику кокпита по XAML/code-behind. **Skia-поверхности** при этом живут **внутри** хоста (измерение, клип, pointer — через тот же слой, пока мы так решили).
52
536. **Avalonia XPF — не «платная сетка для 50/50».** Коммерческий **[Avalonia XPF](https://docs.avaloniaui.net/xpf/)** — это продукт **совместимости с WPF** (перенос существующих WPF-приложений на движок Avalonia), а не замена stock-layout для зелёного приложения на Avalonia. Мотивация «пропорции и сплиты боль без отдельного продукта» ведёт к **Skia / своему композитору / модели зон**, а не к покупке XPF как способа упростить `Grid`.
54
55### Зафиксированное разделение: Skia, Avalonia, view model
56
57Продуктовая позиция команды (не перечитывать как «весь UI не Avalonia» — речь об **ответственности**):
58
59| Слой | Роль |
60|------|------|
61| **Skia** | **Границы** регионов, **отрисовка**, **зоны** (доли, кокпит, кастомная графика) — там, где источник правды не зашитый XAML-сплит. На уровне дерева — обычно через **`SkiaHost`**. |
62| **Avalonia** | **Редактор**, **дерево**, терминал и прочие **тяжёлые контролы**. Это **виджеты**, которые монтируются во вторичный контур как **страница** (`SecondaryShellPage` и аналоги в `MfdShellView`) и **показываются там, куда укажет** оркестрация (в колонке Mfd главного окна или в `MfdHostWindow` — тот же набор страниц, тот же хост). Они **не** являются «логикой внутри `MainWindowViewModel`» в смысле реализации редактирования текста/дерева в VM. |
63| **View model** (в т.ч. `MainWindowViewModel`) | **Навигация и хром**: какая страница вторичного контура активна, видимость зон Mfd/Pfd, команды, согласованность с мультиоконностью — **не** внутренности редактора или дерева как предметная логика в VM. |
64
65Семантика кабины для агента/тестов агрегируется в **CDS** (`CockpitSurfaceSnapshotBuilder` → [`cds-contract-v0.md`](cds-contract-v0.md)); сейчас снимок строится из VM, **целевой язык** — CDS. Как сблизить и не плодить второй источник правды — см. подраздел **«Источник правды сегодня и как сблизить с CDS»** там же.
66
67---
68
69## Зафиксировано здесь (не ждём отдельного ADR без новой развилки)
70
71- **Граница оверлей vs поверхность в зоне** — см. п. 3: постоянный инструментарий → **в зоне** (Skia / слот кабины); временное и контекстное → **оверлей, где уместен**.
72- **Рендер-пайплайн (`SkiaHost` vs отдельные surface hosts / прочие оптимизации):** **жёсткой взаимоисключающей развилки на весь продукт сейчас нет.** Дефолт: **`SkiaHost` в слоте** в дереве Avalonia; цепочка смысла — [ADR 0036](../adr/0036-cds-channel-compositor-surface-pipeline.md) (канал → CDS → композитор → **поверхность**). Отдельные surface hosts и иные оптимизации — **последующая эволюция по метрикам/требованиям**, не вторая параллельная «архитектура», пока не появится **несовместимое** требование для класса сценариев. **Отдельный ADR** под пайплайн — только тогда; до тех пор достаточно **`architecture-migration.md`** + эта заметка (при необходимости — дополнение к 0036, а не дублирующий ADR).
73
74## Остаётся открытым (вне этого документа или по фичам)
75
76- **Пофичево** (semantic map, подтверждения агента, палитра): оверлей vs зона в конкретном UX — по миграции и [0017 п. 6](../adr/0017-multi-window-workspace-and-agent-surfaces.md#adr0017-p6), не по принципу «только оверлей / только Skia».
77- Кокпит и host-surface: сопоставить **правила размещения инструментов** ([`CockpitInstrumentPlacementRules`](../../Cockpit/Composition/HostSurface/CockpitInstrumentPlacementRules.cs)) с принципом «не размножать сплиты в main» после эволюции shell/страниц.
78
79---
80
81## История правок
82
83| Дата | Изменение |
84|------|-----------|
85| 2026-04-16 | Первый черновик: сплиты, оверлеи, Skia-поверхности, связь с вторичным контуром и 0017. |
86| 2026-04-16 | П. 5–6: граница Avalonia (хост + тяжёлые контролы), слабая связность; уточнение про XPF vs Skia. |
87| 2026-04-16 | Подраздел «Зафиксированное разделение»: Skia (границы/зоны/отрисовка), Avalonia (редактор/дерево как страницы shell), VM — навигация, не логика виджетов. |
88| 2026-04-16 | Связка VM ↔ CDS: ссылка на `cds-contract-v0.md` (источник правды / улучшение). |
89| 2026-04-16 | П. 3 и открытые вопросы: постоянный UI из оверлеев → поверхности в зоне; оверлей оставляем где уместен (тосты, DnD, модалки). |
90| 2026-04-16 | Термин **SkiaHost**: контрол Avalonia, хостящий Skia; дизамбиг с `MfdHostWindow`. |
91| 2026-04-16 | Подраздел **«SkiaHost в коде»**: зачем тип в репозитории (дизамбиг + общая логика + grep), когда вводить. |
92| 2026-04-16 | Раздел «открытые вопросы» перестроен: зафиксировано (оверлеи, рендер-пайплайн без отдельного ADR до жёсткой развилки); открыто — пофичево + CockpitInstrumentPlacementRules. |
93
View only · write via MCP/CIDE