| 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 | |
| 13 | 1. **Уйти от зашитых сплитов** в XAML (`Grid`, `GridSplitter`, фиксированные колонки под каждый новый инструмент) — они быстро становятся продуктовым контрактом: каждая новая сущность тянет ещё одну «полку» в главном окне. |
| 14 | 2. **Отдать композицию и отрисовку** слою, где раскладку проще менять (режимы, пресеты, второй монитор, кокпит) без разрастания дерева панелей. |
| 15 | 3. **Перестать опираться на оверлеи** как на основной способ показать постоянный 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 | |
| 43 | 1. **Презентация задаёт топологию**, а не набор сплитов вручную: строка `presentation` / `zone_screen_layout` и грамматика якорей ([0017](../adr/0017-multi-window-workspace-and-agent-surfaces.md)). На главном окне при схемах вроде `(P+F)(M)` в фокусе **P и лобовое**; содержимое **M** (включая вторичный контур) не дублируется отдельной «полкой» только ради одного инструмента. |
| 44 | |
| 45 | 2. **Обозреватель решения** — **страница** вторичного контура (`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 | |
| 47 | 3. **Оверлей — не зло сам по себе; зло — постоянный UI «налепкой».** Оверлеи в прототипе **доказали работоспособность**; то, что теперь **путает и сбивает** (постоянные панели, карты, инструменты), **убираем** в пользу **полноценных поверхностей в зоне** (в т.ч. Skia). **Где оверлей по смыслу уместен — оставляем:** тосты, подсказки, превью перетаскивания, модалка на весь экран, иной **временный и контекстный** слой — иначе пришлось бы дублировать ту же семантику в дереве ради запрета на «поверх». Итог: **не «ноль оверлеев»**, а **не подменять ими зоны презентации**. |
| 48 | |
| 49 | 4. **Skia** — способ **отрисовки и гибкой композиции** внутри выделенных регионов (в т.ч. кокпит, отдельные инструменты), а не оправдание очередного глобального слоя поверх всего окна. |
| 50 | |
| 51 | 5. **Слабая связность и роль Avalonia.** Рефакторинг и вынос логики из «прилипшего к вью» кода нацелены на то, чтобы **оркестрация, правила презентации, команды, состояние workspace** жили во **view model и сервисах** с явными границами. **Avalonia** остаётся слоем **хоста и тяжёлых контролов**: окна, ввод и фокус, жизненный цикл визуала, встраивание дорогих виджетов (редактор, докинг, списки и т.п.) — всё, ради чего не имеет смысла писать свой UI-стек с нуля. Это **не** утверждение «половина экрана не на Avalonia»: типично весь chrome всё ещё дерево Avalonia; речь о **узкой ответственности** — не размазывать семантику кокпита по XAML/code-behind. **Skia-поверхности** при этом живут **внутри** хоста (измерение, клип, pointer — через тот же слой, пока мы так решили). |
| 52 | |
| 53 | 6. **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 | |