Кроссплатформенная новогодняя демка на .NET Core и Avalonia

в 7:44, , рубрики: .net, .net core, avalonia, C#, GUI, linux, MacOS, UI, unsafe, wpf, XAML, ненормальное программирование, Программирование, Разработка под Linux

"ААА! Пришло время переписывать на .NET Coreǃ", говорили они, WPF в комментариях обсуждали. Так давайте же проверим, можно ли написать кросс-платформенное GUI приложение на .NET / C#.

Кроссплатформенная новогодняя демка на .NET Core и Avalonia - 1

Новогоднее настроение навеяло идею сделать анимацию падающего снега. Были такие демки под DOS, горящий огонь, фракталы, снежок, падающий на ёлочку, и так далее.

Как увидим ниже, это не только весело, но и позволит испытать ключевой функционал UI фреймворка. Поехали!

Создание проекта и UI

Для Avalonia есть Visual Studio Extension с шаблоном проекта. Устанавливаем, создаём Avalonia .NET Core Application. Видим привычные по WPF App.xaml и MainWindow.xaml. Однако, проект содержит <TargetFrameworks>netcoreapp1.1;net461</TargetFrameworks>, меняем на <TargetFramework>netcoreapp2.0</TargetFramework>, мы же не в каменном веке.

Расширение Avalonia для студии содержит XAML Designer, но у меня он не заработал. Решарпер немного сходит с ума в редакторе разметки, хочет везде вставить явные неймспейсы, так что и без него тоже обойдёмся.

В остальном у нас в руках привычный XAML с привычными контролами и пропертями. Обо всех отличиях можно почитать в документации.

Для произвольного рисования существует одноимённый аналог WriteableBitmap из WPF. Огромный плюс в том, что нет проблем рисовать в нём из любого потока, выглядит это так:

<Image Source="{Binding Bitmap}" Stretch="Fill" />

using (ILockedFramebuffer buf = writeableBitmap.Lock())
{
    uint* ptr = (uint*) buf.Address;

   // Рисуем
    *ptr = uint.MaxValue;
}

Однако Image, который привязан к нашему writeableBitmap, не обновится сам по себе, ему необходимо сказать InvalidateVisual().

Таким образом, мы можем рисовать анимацию в фоновом потоке, не нагружая UI thread. Помимо Image добавим пару слайдеров для управления скоростью падения снега и количеством снежинок, здесь всё стандартно, {Binding Mode=TwoWay}. Плюс кнопка "начать заново", тоже стандартная привязка к ICommand. Замечу, что использованы векторные иконки на XAML, скопипащенные из гугла, <Path> фунциклирует как положено.

Разметка целиком: MainWindow.xaml

Снежный алгоритм

"Физика"

Двигаем снежинку вниз на один пиксель. Если пиксель уже занят "лежащей" снежинкой, проверяем точки слева и справа, и двигаем туда, где свободно. Всё занято — помечаем точку как "лежащую". Таким образом достигается скатывание снежинок с наклонных поверхностей.

Параллакс

Для достижения объёмного эффекта зададим каждой снежинке рандомную скорость. Чем скорость ниже, тем более тёмный оттенок используем для рисования.

Чтобы наша "физика" работала корректно, необходимо перемещать снежинки не более, чем на 1 пиксель за кадр. То есть самые быстрые снежинки двигаются каждый кадр на пиксель, остальные — пропускают некоторые кадры. Для этого можно применить float координаты и просто перерисовывать каждую снежинку на каждый кадр. Вместо этого я использую два целочисленных short поля и перерисовываю снежинку только если она реально сдвинулась.

Рендеринг

Основная идея — избежать полной перерисовки кадра. Нам надо как-то хранить "лежащий" снег, нарисованные пользователем точки, загруженные изображения (да, можно рисовать мышью и грузить пикчи правым кликом — снежок будет прилипать к ёлочкам и надписям).

Простое и эффективное решение — использовать сам WriteableBitmap. "Перманентные" пиксели пусть будут полностью непрозрачными (A = 255), а для движущихся снежинок A = 254.

Падающих снежинок всегда фиксированное количество, позицию и скорость каждой храним в массиве. В итоге, если снежинка сдвинулась — стираем точку на старой позиции и рисуем на новой. Если превратилась в "лежачую" — выставляем альфа-канал точки в 255, перемещаем живую снижинку обратно наверх.

Как это запустить?

Благодаря возможности рисования прямо "по живому" получилась доволно залипательная штука, попробуйте :)

Для всех ОС инструкция одинаковая:

  • Установить .NET Core SDK
  • git clone https://github.com/ptupitsyn/let-it-snow.git
  • cd let-it-snow/AvaloniaCoreSnow
  • dotnet run

Заключение

.NET Core молод, Avalonia ещё в альфе, но уже сейчас эти инструменты решают поставленную задачу! Код простой и понятный, никаких хаков и лишних приседаний, прекрасно работает на Windows, macOS, Linux.

Альтернативы?

  • Qt (посложнее будет в использовании)
  • Java (нет нормального unsafe)
  • Electron (JavaScript + HTML — нет уж, спасибо)

UI в данной демке очень прост, но он использует несколько наиболее важных фич:

  • Layout (Grid, StackPanel, выравнивание) — основа вёрстки
  • Binding (привязка контролов к свойствам модели)
  • ItemsControl, ItemsPanel, DataTemplate — работа с коллекциями данных
  • WriteableBitmap — прямая работа с изображениями
  • OpenFileDialog, нативный на каждой платформе

Этого уже достаточно, чтобы построить UI любой сложности. Так что можем сказать, что закрыт последний пробел в экосистеме .NET: есть возможность создавать веб (ASP.NET Core), мобильные (Xamarin) и десктопные (Avalonia) приложения, при этом переиспользовать код, располагая его в библиотеках .NET Standard.

Ссылки

Автор: Павел Тупицын

Источник

* - обязательные к заполнению поля


https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js