Scaffold AvParser: Avalonia 12 shell with adaptive layout
Greenfield skeleton for a parser desktop app. The domain is deliberately a placeholder — IParser<TIn,TOut> plus two sample parsers — so the shell is runnable and verifiable end to end before real logic lands. Layers run one way: Core (no Avalonia, no IO) <- Infrastructure <- UI <- Desktop. UI is a class library rather than the exe so headless tests build real views without dragging in Program.cs, Serilog or the container. Adaptive layout is built from what Avalonia actually offers, since it has no AdaptiveTrigger or media queries: ResponsiveLayout observes Visual.Bounds and projects a breakpoint onto both an attached property and :compact/:medium/ :expanded pseudoclasses, with 24px hysteresis so dragging a window edge cannot make the layout flap. Pane state lives in the view model because a style setter loses to a local value permanently; styles own only the visual variance. Stack notes worth remembering: Avalonia.ReactiveUI is deprecated in favour of ReactiveUI.Avalonia, and ReactiveUI 24 runs on the Primitives engine (RxVoid, ISequencer, Signal<T>) and no longer self-initialises. Avalonia.Headless.XUnit 12.x requires xUnit v3. InvariantGlobalization must stay false or Semi.Avalonia throws in its static constructor. 102 tests across three projects, including headless guards for the two failures that are otherwise completely silent: a stylesheet whose selectors match nothing, and a light palette too low-contrast for cards to read. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,123 @@
|
||||
# AvParser
|
||||
|
||||
Каркас desktop-приложения на **Avalonia 12** с ReactiveUI-MVVM, адаптивным layout поверх
|
||||
Semi.Avalonia, единым DI-контейнером и тремя уровнями тестов.
|
||||
|
||||
Доменная часть пока намеренно абстрактная: ядро — это pluggable-контракт
|
||||
`IParser<TInput, TOutput>` и два демо-парсера, чтобы каркас был запускаемым и проверяемым
|
||||
end-to-end до появления настоящей логики.
|
||||
|
||||
---
|
||||
|
||||
## Быстрый старт
|
||||
|
||||
Нужен .NET SDK **10.0.100** (закреплён в `global.json`).
|
||||
|
||||
```bash
|
||||
dotnet restore AvParser.slnx
|
||||
```
|
||||
```bash
|
||||
dotnet build AvParser.slnx -c Release
|
||||
```
|
||||
```bash
|
||||
dotnet test AvParser.slnx -c Release
|
||||
```
|
||||
```bash
|
||||
dotnet run --project src/AvParser.Desktop
|
||||
```
|
||||
|
||||
Форматирование (csharpier — единственный владелец форматирования, включая `.axaml` и `.csproj`):
|
||||
|
||||
```bash
|
||||
dotnet tool restore && dotnet csharpier check .
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Структура
|
||||
|
||||
```
|
||||
src/
|
||||
AvParser.Core домен: IParser, IParserCatalog, модели, демо-парсеры
|
||||
ноль зависимостей кроме DI.Abstractions — ни Avalonia, ни IO
|
||||
AvParser.Infrastructure AppPaths, JSON-настройки с debounce, Serilog
|
||||
AvParser.UI Avalonia class library: App-независимые View, ViewModel,
|
||||
ResponsiveLayout, дизайн-токены, навигация
|
||||
AvParser.Desktop WinExe-хост: Program.cs, App.axaml, composition root
|
||||
tests/
|
||||
AvParser.Core.Tests парсеры, реестр, отмена, прогресс
|
||||
AvParser.UI.Tests ViewModel'и без Avalonia
|
||||
AvParser.UI.HeadlessTests реальное дерево контролов через [AvaloniaFact]
|
||||
```
|
||||
|
||||
Ссылки идут строго в одну сторону: `Core ← Infrastructure ← UI ← Desktop`.
|
||||
`UI` — библиотека, а не exe, именно чтобы headless-тесты собирали настоящие View, не подтягивая
|
||||
`Program.cs`, Serilog и контейнер.
|
||||
|
||||
---
|
||||
|
||||
## Адаптивный layout
|
||||
|
||||
В Avalonia нет `AdaptiveTrigger`, `VisualStateManager` и media-queries. Есть три примитива:
|
||||
наблюдаемый `Visual.Bounds`, псевдоклассы и `SplitView`. `ResponsiveLayout` связывает первое со
|
||||
вторым — получается CSS-подобная реакция на ширину.
|
||||
|
||||
| Брейкпоинт | Ширина окна | Навигация |
|
||||
|---|---|---|
|
||||
| Compact | < 720 px | выезжающий drawer поверх контента |
|
||||
| Medium | 720 – 1100 px | рельс из одних иконок (56 px) |
|
||||
| Expanded | ≥ 1100 px | полный сайдбар с подписями (248 px) |
|
||||
|
||||
Переключение с гистерезисом в 24 px: без неё перетаскивание края окна заставляет layout
|
||||
мигать между двумя состояниями на каждом пикселе дрожания.
|
||||
|
||||
Разделение обязанностей, которое важно не сломать:
|
||||
|
||||
- `SplitView.DisplayMode` и `IsPaneOpen` **биндятся во ViewModel**. Style-сеттер навсегда
|
||||
проигрывает локальному значению, поэтому первый же клик по гамбургеру заморозил бы любой
|
||||
стиль, который тоже пишет в эти свойства.
|
||||
- Всё чисто визуальное — ширины панели, видимость подписей, паддинги — живёт в
|
||||
`Styles/Shell.axaml`.
|
||||
|
||||
Селекторы там написаны как `:is(UserControl).shell`, а не `UserControl.shell`: селектор типа в
|
||||
Avalonia матчит **точный** тип, а `ShellView` наследуется от `ReactiveUserControl<T>` — обычная
|
||||
форма молча не сматчилась бы ни с чем. На это есть тест
|
||||
(`ShellViewTests.The_shell_stylesheet_is_actually_applied`).
|
||||
|
||||
---
|
||||
|
||||
## Дизайн-токены
|
||||
|
||||
Все цвета, отступы, радиусы и типографика — в `Styles/Tokens.axaml`, с отдельными словарями
|
||||
для Light и Dark. В остальном XAML нет ни одного литерального цвета и ни одного «магического»
|
||||
отступа, так что перекрасить тему или уплотнить интерфейс — это правка одного файла.
|
||||
|
||||
Semi.Avalonia даёт темы контролов; токены — это семантический слой приложения поверх них.
|
||||
Кнопки `.primary` / `.destructive` описаны своими стилями, а не классами Semi, чтобы акцентный
|
||||
цвет не разъезжался между двумя палитрами.
|
||||
|
||||
---
|
||||
|
||||
## Стек
|
||||
|
||||
| Пакет | Версия | Заметка |
|
||||
|---|---|---|
|
||||
| Avalonia | 12.1.1 | compiled bindings по умолчанию → `x:DataType` обязателен |
|
||||
| ReactiveUI.Avalonia | 12.1.1 | `Avalonia.ReactiveUI` — deprecated, это его преемник |
|
||||
| ReactiveUI | 24.1.0 | дистрибутив Primitives: `RxVoid` вместо `Unit`, `ISequencer` вместо `IScheduler` |
|
||||
| Semi.Avalonia | 12.1.0.1 | темы контролов |
|
||||
| xUnit | v3 (3.2.2) | `Avalonia.Headless.XUnit` 12.x требует именно v3 |
|
||||
|
||||
---
|
||||
|
||||
## Что проверить руками
|
||||
|
||||
1. Потянуть окно по ширине — сайдбар проходит путь
|
||||
`полный → только иконки → выезжающий drawer`, без мигания на границах.
|
||||
2. Переключить тему кнопкой в заголовке и в Settings; перезапустить — выбор сохранился.
|
||||
3. На странице Parse нажать **50k rows**, затем **Parse** — виден прогресс; **Cancel**
|
||||
останавливает на середине и пишет, сколько успело разобраться.
|
||||
4. `F12` в Debug-сборке открывает Avalonia DevTools — там видно, как переключаются
|
||||
`:compact` / `:medium` / `:expanded`.
|
||||
|
||||
Настройки и логи лежат в `%APPDATA%/AvParser` (Windows) или `~/.config/AvParser` (Linux/macOS).
|
||||
Reference in New Issue
Block a user