- BrainTools - https://www.braintools.ru -
Привет! Меня зовут Егор, я мобильный разработчик. Пару лет назад я уже писал на Хабре о своём переходе с Xamarin Native на Flutter [1] — и с тех пор моя любовь к декларативному UI только окрепла. Но за это время накопилась и заноза: Flutter великолепен на мобилке и ощутимо буксует на десктопе, а Avalonia — ровно наоборот.
Так родился Plumix [2] — фреймворк с открытым исходным кодом, который переносит архитектуру Flutter (Widget → Element → RenderObject) в мир .NET, используя Avalonia как платформенную инфраструктуру. В этой статье расскажу, зачем я вообще в это ввязался, как значительную часть работы делают ИИ-агенты, что фреймворк умеет сейчас, и почему недавняя новость о партнёрстве Avalonia с командой Flutter — это лучшее, что могло случиться с проектом.
Начну с главного тейка, из которого и вырос весь проект.
Мобильную разработку на Flutter я обожаю. Hot reload, декларативная вёрстка, вылизанные Material- и Cupertino-контролы, жесты и анимации, которые из коробки ощущаются нативно, правильная физика скролла на каждой платформе — про всё это я подробно писал в прошлой статье, и с тех пор мнение не изменилось.
А вот десктоп на Flutter — это боль [3]. Формально он поддерживается: окно открывается, кнопки нажимаются. Но контролы под десктоп попросту не заточены. Попробуйте собрать типичное «взрослое» десктопное приложение: таблицу на десятки тысяч строк с виртуализацией, сортировкой, ресайзом и закреплением колонок; развесистые контекстные меню; drag-and-drop; плотные формы, рассчитанные на мышь и клавиатуру, а не на палец. Флаттеровский DataTable даже виртуализацию из коробки не умеет — на серьёзных объёмах данных он просто ложится. Всё это либо пишется руками, либо собирается из сторонних пакетов разной степени заброшенности. В итоге десктопное приложение на Flutter слишком часто ощущается как мобилка, растянутая на весь монитор.
С Avalonia у меня ровно обратная история. Десктопы на ней я пишу с искренним кайфом: DataGrid, меню, окна и диалоги, хоткеи, честная работа с мышью и клавиатурой — всё родное, всё сделано для десктопа людьми, которые десктопом пользуются. Но стоит собрать проект под мобилку — и выясняется, что мобильных контролов нет. Нет привычной физики скролла (особенно фирменного bounce-эффекта в iOS), нет жестов и свайпов, нет pull-to-refresh, нет нативно ощущающихся переходов между экранами и bottom sheet’ов. Технически приложение запускается на Android и iOS, но пользователь мгновенно чувствует: что-то не так.
И вот на этой развилке я и застрял: для мобилки хочется Flutter, для десктопа — Avalonia, а приложение у меня одно. Так появилась идея Plumix: перенести флаттеровскую виджетную модель поверх Avalonia. От Flutter — архитектура, контролы, жесты, анимации и поведение [4] скролла, отточенные годами на миллиардах мобильных устройств (и портируемые 1:1, а не «по мотивам»). От Avalonia — зрелая платформенная инфраструктура и та самая десктопная среда, в которой мне комфортно: окна, жизненный цикл, ввод, графический бэкенд. Бонусом весь стек остаётся в родной для меня экосистеме .NET.
Кстати говоря, в тот момент я думал, что это заведомо неподъёмная задача для одного человека. Переписать рендер-пайплайн Flutter? Систему жестов с ареной распознавателей? Sliver-протокол для скролла? В одиночку — да, неподъёмная. Но на дворе 2026 год, и работаю я давно не в одиночку.
Это, пожалуй, самая интересная часть проекта, и о ней я хочу рассказать подробнее.
Plumix с самого начала строился как репозиторий, оптимизированный под ИИ-агентов. Не «я иногда прошу чат-бота написать функцию», а полноценный конвейер, где агент получает задачу вида «портируй Material-контрол X», сам находит нужный контекст, пишет код, тесты и документацию — а я выступаю в роли архитектора, ревьюера и ОТК.
Чтобы это работало, пришлось выстроить целую систему документов:
AGENTS.md — правила игры: архитектурные границы, что можно, что нельзя, какие команды запускать для проверки.
docs/FRAMEWORK_PLAN.md — единый источник правды о статусе проекта. Начинается с YAML-блока «AI Semantic Snapshot»: машиночитаемая сводка, по которой агент за секунды понимает, что сделано, что в работе и куда двигаться.
docs/ai/PORTING_MODE.md — обязательный воркфлоу портирования. Ключевое правило: строгий порт 1:1, а не «примерно похоже». Источник правды — Dart-исходники Flutter, которые лежат локально рядом с репозиторием.
docs/ai/DIVERGENCES.md — реестр осознанных отклонений от Flutter. Каждое расхождение фиксируется с причиной и условием закрытия.
docs/ai/MODULE_INDEX.md и TEST_MATRIX.md — карта модулей и тестов, чтобы агент не читал весь репозиторий, а заходил в подсистему через её тесты.
Отдельно расскажу про следующий приём: эталонное приложение на настоящем Flutter. В репозитории живут два одинаковых сэмпла — src/Sample/Plumix.Sample на C# и dart_sample на Dart. Любое изменение демо-галереи обязано зеркалиться в обоих. Запускаю их рядом — и глазами (а иногда и попиксельно) сравниваю: так же ли ведёт себя ripple-эффект на кнопке, так ли раскрывается Drawer, там ли стоит заголовок в AppBar. Это дешёвый и безжалостный детектор халтуры: если агент «примерно нарисовал» вместо честного порта, расхождение видно сразу.
Ещё одно важное правило — единица поставки: один контрол за одну итерацию, целиком. API, значения по умолчанию, порядок композиции, состояния взаимодействия, layout, отрисовка, тесты — всё в одном заходе. Опытным путём выяснилось, что если разрешить агенту дробить работу («сначала геометрия, потом цвета, потом оверлеи»), получается вечный недострой. А требование закрыть контрол end-to-end дисциплинирует не хуже, чем дедлайн в пятницу вечером.
Что в итоге? Портирование контрола из Flutter в C# перестало быть исследовательской задачей и стало конвейерной. Агент читает Dart-исходник, переносит структуру один к одному, пишет фокусные тесты на дефолты/состояния/layout, обновляет оба сэмпла и changelog. Я проверяю, гоняю демо, придираюсь к деталям. Если во время порта выясняется, что в ядре не хватает примитива (например, IntrinsicWidth или таблично-строчного layout для DataTable) — сначала добавляется примитив, потом закрывается контрол.
Забавный побочный эффект: changelog разросся до такой степени, что пришлось ввести правило ротации — когда файл переваливает за 100 КБ, старая половина уезжает в архив. Хорошая иллюстрация темпа, который дают агенты: такой объём изменений руками я бы генерировал годы.
Теперь к делу. Вот как выглядит приложение на Plumix:
using Plumix.Widgets;
public sealed class MyApp : StatelessWidget
{
public override Widget Build(BuildContext context)
{
return new Scaffold(
body: new Center(
child: new Column(
mainAxisAlignment: MainAxisAlignment.Center,
children:
[
new Text(
"Hello, Plumix!",
style: new TextStyle(fontSize: 32, fontWeight: FontWeight.Bold)
),
new SizedBox(height: 16),
new Text("Flutter-like widgets, powered by .NET and Avalonia."),
new SizedBox(height: 24),
new ElevatedButton(
onPressed: () => { /* handle tap */ },
child: new Text("Get Started")
)
]
)
)
);
}
}
Если вы писали на Flutter — вы уже умеете писать на Plumix. Это не совпадение, это цель проекта.
Под капотом — не обёртки над Avalonia-контролами, а честный порт архитектуры Flutter. Ядро уже закрыто полностью:
Виджетное дерево и жизненный цикл: StatelessWidget/StatefulWidget, State, SetState, реконсиляция, InheritedWidget;
Рендер-пайплайн: RenderObject/RenderBox, фазы layout/paint/compositing/semantics, дерево слоёв;
Система жестов с ареной распознавателей (tap, drag, long-press), hover-события;
Навигация: Navigator, именованные маршруты, обсерверы, диалоговые route’ы и даже Hero-анимации между экранами;
Скролл на sliver-протоколе: ListView, GridView, вьюпорты, keep-alive, notification’ы;
Фокус, клавиатура, IME (включая composition/preedit), буфер обмена, семантика для accessibility;
MediaQuery/SafeArea, пайплайн изображений с кэшем и DPR-выбором ассетов;
Hot reload — да, работает: .NET Hot Reload плюс флаттеровский flow с Reassemble, состояние State переживает правку кода.
Поверх ядра — Material-библиотека, и это сейчас основной фронт работ. Портировано уже несколько десятков контролов: вся линейка кнопок (TextButton/ElevatedButton/OutlinedButton/FilledButton с ButtonStyle и state-слоями), Scaffold с drawer’ами и жестами открытия, AppBar со всеми precedence-правилами тем, SnackBar со ScaffoldMessenger, диалоги, попап-меню, DropdownButton, чипы, NavigationBar/NavigationRail/NavigationDrawer, DataTable с пагинацией, слайдеры, прогресс-индикаторы, Stepper, Card, ListTile со всем семейством, скроллбары и много чего ещё. Плюс начало Cupertino-пакета: адаптивные контролы (Switch.Adaptive, Radio.Adaptive и т.д.) на iOS/macOS честно переключаются на купертиновскую ветку, как и во Flutter.
Темизация портирована со всей флаттеровской дотошностью: ThemeData, Material 2/Material 3 режимы, локальные inherited-темы для каждого контрола, правильный порядок разрешения (widget → theme → defaults). Это та часть, где «примерно похоже» не работает: либо дефолты совпадают с Flutter до пикселя и альфа-канала, либо это уже не порт.
Всё это лежит на NuGet тремя пакетами — Plumix, Plumix.Material, Plumix.Cupertino — и покрыто тестами: жизненный цикл, рендеринг, жесты, навигация, скролл и парити-тесты почти на каждый контрол.
В ноябре 2025 команда Avalonia объявила о партнёрстве с командой Flutter из Google [5]: в .NET приходит Impeller — GPU-first рендер нового поколения, который Google написала для Flutter взамен Skia. Инициатива, что показательно, исходила от самого создателя Impeller — Chinmay Garde, который связался с командой Avalonia после их публикации о поиске альтернатив SkiaSharp.
Если коротко, чем хорош Impeller: он был создан, чтобы победить главную боль Skia — «джанк» при компиляции шейдеров. Все пайплайны состояний собираются заранее, поэтому кадры стабильные, средний FPS выше, память [6] и энергопотребление ниже (в ранних замерах Avalonia — 0.48 Вт против 4.93 Вт на альтернативном стеке). Особенно заметен выигрыш на мобильных и embedded-устройствах с тайловыми GPU. Работа ведётся полностью в open source, ранний Impeller-бэкенд для Avalonia уже существует, хотя до продакшена ещё далеко.
Что это значит для Plumix? Тут нужно вспомнить архитектуру: Plumix владеет layout’ом и логикой [7] отрисовки, но сами пиксели на экран выводит графический бэкенд Avalonia. Сейчас это Skia. Когда Avalonia доведёт Impeller-бэкенд до стабильности, Plumix получит его автоматически, без единой строчки изменений.
Оцените красоту момента: фреймворк, который копирует архитектуру Flutter, будет рисоваться рендером, написанным для Flutter, — но при этом весь код останется на C#. Круг замкнулся. Когда я начинал проект, «Flutter для .NET» был амбициозной метафорой. Теперь это почти буквальное техническое описание стека.
Есть и более прагматичный вывод: ставка на Avalonia как на инфраструктуру оправдывает себя. Я сознательно не писал свой оконный менеджер, свой input-стек и свой графический бэкенд — и теперь каждое улучшение Avalonia (включая такие подарки, как Impeller) Plumix получает бесплатно.
Дорожная карта у проекта простая и публичная (лежит прямо в репозитории). Ближайшие цели:
Дожать Material-библиотеку. Конвейер портирования работает, список оставшихся контролов конечен и уменьшается с каждой неделей. Цель — чтобы типовое Flutter-приложение переносилось на C# заменой синтаксиса, а не переизобретением UI.
Кроссплатформенные хосты. Ядро изначально не привязано к десктопу, и заготовки хостов под Browser (WebAssembly), Android и iOS уже в репозитории. Следующий большой этап — довести сэмплы на всех платформах до идентичного поведения [8].
Глубокая текстовая подсистема. Базовый EditableText уже есть, но до полноценного TextField со всеми его нюансами ещё есть путь.
Оверлеи и продвинутые переходы маршрутов, инструменты производительности — фреймовая диагностика, профилирование рендер-пайплайна.
Рецепты миграции — документация в духе «как перенести виджет с Dart на C#», чтобы порог входа для Flutter-разработчиков был минимальным.
И, конечно, продолжу развивать сам подход «фреймворк как конвейер для ИИ-агентов». Честно говоря, эта мета-задача увлекает меня не меньше самого фреймворка: Plumix для меня ещё и полигон, на котором проверяется, насколько далеко можно уехать на связке «человек-архитектор + ИИ-исполнители» в по-настоящему сложном системном проекте. Пока ответ: значительно дальше, чем я думал.
Какой бы крутой вам ни казалась технология, не нужно зажимать себя в её рамках — эту мысль из прошлой статьи я могу повторить и здесь, только с неожиданным продолжением. Иногда вместо выбора между «кайфовой мобилкой на Flutter» и «кайфовым десктопом на Avalonia» можно просто взять и совместить их. Ещё три года назад такой проект в одиночку был бы авантюрой. Сегодня, с ИИ-агентами и правильно выстроенным процессом, — это рабочие будни.
Если вам близка идея писать декларативный UI в стиле Flutter, не покидая экосистему .NET, — буду рад вашим звёздочкам, issue и pull request’ам:
Сайт: plumix.net [2]
GitHub: github.com/Plumix-Net/Plumix [9]
NuGet: Plumix [10], Plumix.Material [11], Plumix.Cupertino [12]
А в комментариях предлагаю обсудить: как вы решаете дилемму «одно приложение и на мобилку, и на десктоп» — терпите десктоп на Flutter, дотачиваете мобилку на Avalonia или разводите по разным стекам? И используете ли вы ИИ-агентов в пет-проектах так же плотно — или пока присматриваетесь?
Автор: egorozh
Источник [13]
Сайт-источник BrainTools: https://www.braintools.ru
Путь до страницы источника: https://www.braintools.ru/article/32758
URLs in this post:
[1] переходе с Xamarin Native на Flutter: https://habr.com/ru/articles/796147/
[2] Plumix: https://plumix.net/
[3] боль: http://www.braintools.ru/article/9901
[4] поведение: http://www.braintools.ru/article/9372
[5] объявила о партнёрстве с командой Flutter из Google: https://avaloniaui.net/blog/avalonia-partners-with-google-s-flutter-t-eam-to-bring-impeller-rendering-to-net
[6] память: http://www.braintools.ru/article/4140
[7] логикой: http://www.braintools.ru/article/7640
[8] поведения: http://www.braintools.ru/article/5593
[9] github.com/Plumix-Net/Plumix: https://github.com/Plumix-Net/Plumix
[10] Plumix: https://www.nuget.org/packages/Plumix/
[11] Plumix.Material: https://www.nuget.org/packages/Plumix.Material/
[12] Plumix.Cupertino: https://www.nuget.org/packages/Plumix.Cupertino/
[13] Источник: https://habr.com/ru/articles/1056568/?utm_source=habrahabr&utm_medium=rss&utm_campaign=1056568
Нажмите здесь для печати.