Skip to content

Commit

Permalink
Merge pull request #336 from dme-compunet/native-aot-gallery
Browse files Browse the repository at this point in the history
Update Gallery for NativeAOT Compatibility
  • Loading branch information
kikipoulet authored Nov 27, 2024
2 parents c6ce6b9 + b3d2d07 commit cfd6486
Show file tree
Hide file tree
Showing 19 changed files with 340 additions and 186 deletions.
23 changes: 23 additions & 0 deletions .github/workflows/build-gallery-windows.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
name: Build Gallery (Native)

on:
push:
branches: [ "main" ]
pull_request:
branches: [ "main" ]

jobs:
build:
runs-on: windows-latest
steps:
- name: Checkout
uses: actions/checkout@v4

- name: Build Native
run: dotnet publish SukiUI.Demo/SukiUI.Demo.csproj -c Release -r win-x64 -o bin/

- name: Upload
uses: actions/upload-artifact@v4
with:
name: gallery-native
path: bin
3 changes: 0 additions & 3 deletions SukiUI.Demo/App.axaml
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,6 @@
xmlns:common="clr-namespace:SukiUI.Demo.Common"
xmlns:suki="https://github.com/kikipoulet/SukiUI"
RequestedThemeVariant="Default">
<Application.DataTemplates>
<common:ViewLocator />
</Application.DataTemplates>

<TrayIcon.Icons>
<TrayIcons>
Expand Down
92 changes: 62 additions & 30 deletions SukiUI.Demo/App.axaml.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,64 +5,96 @@
using Avalonia.Markup.Xaml;
using Microsoft.Extensions.DependencyInjection;
using SukiUI.Demo.Common;
using SukiUI.Demo.Features;
using SukiUI.Demo.Features.ControlsLibrary;
using SukiUI.Demo.Features.ControlsLibrary.Colors;
using SukiUI.Demo.Features.ControlsLibrary.Dialogs;
using SukiUI.Demo.Features.ControlsLibrary.StackPage;
using SukiUI.Demo.Features.ControlsLibrary.TabControl;
using SukiUI.Demo.Features.ControlsLibrary.Toasts;
using SukiUI.Demo.Features.CustomTheme;
using SukiUI.Demo.Features.Dashboard;
using SukiUI.Demo.Features.Effects;
using SukiUI.Demo.Features.Playground;
using SukiUI.Demo.Features.Splash;
using SukiUI.Demo.Features.Theming;
using SukiUI.Demo.Services;
using System;
using System.Linq;
using SukiUI.Controls;
using SukiUI.Dialogs;
using SukiUI.Toasts;

namespace SukiUI.Demo;

public class App : Application
{
private IServiceProvider? _provider;

public override void Initialize()
{
AvaloniaXamlLoader.Load(this);
_provider = ConfigureServices();
}

public override void OnFrameworkInitializationCompleted()
{
if (ApplicationLifetime is IClassicDesktopStyleApplicationLifetime desktop)
{
var viewLocator = _provider?.GetRequiredService<IDataTemplate>();
var mainViewModel = _provider?.GetRequiredService<SukiUIDemoViewModel>();
var mainView = _provider?.GetRequiredService<SukiUIDemoView>();
mainView.DataContext = mainViewModel;
desktop.MainWindow = mainView;
var services = new ServiceCollection();

services.AddSingleton(desktop);

var views = ConfigureViews(services);
var provider = ConfigureServices(services);

DataTemplates.Add(new ViewLocator(views));

desktop.MainWindow = views.CreateView<SukiUIDemoViewModel>(provider) as Window;
}

base.OnFrameworkInitializationCompleted();
}

private static ServiceProvider ConfigureServices()
private static SukiViews ConfigureViews(ServiceCollection services)
{
var viewLocator = Current?.DataTemplates.First(x => x is ViewLocator);
var services = new ServiceCollection();
return new SukiViews()

// Views
services.AddSingleton<SukiUIDemoView>();

// Services
if (viewLocator is not null)
services.AddSingleton(viewLocator);
services.AddSingleton<PageNavigationService>();
// Add main view
.AddView<SukiUIDemoView, SukiUIDemoViewModel>(services)

// Add pages
.AddView<SplashView, SplashViewModel>(services)
.AddView<ThemingView, ThemingViewModel>(services)
.AddView<PlaygroundView, PlaygroundViewModel>(services)
.AddView<EffectsView, EffectsViewModel>(services)
.AddView<DashboardView, DashboardViewModel>(services)
.AddView<ButtonsView, ButtonsViewModel>(services)
.AddView<CardsView, CardsViewModel>(services)
.AddView<CollectionsView, CollectionsViewModel>(services)
.AddView<ContextMenusView, ContextMenusViewModel>(services)
.AddView<DockView, DockViewModel>(services)
.AddView<ExpanderView, ExpanderViewModel>(services)
.AddView<IconsView, IconsViewModel>(services)
.AddView<InfoBarView, InfoBarViewModel>(services)
.AddView<MiscView, MiscViewModel>(services)
.AddView<ProgressView, ProgressViewModel>(services)
.AddView<PropertyGridView, PropertyGridViewModel>(services)
.AddView<TextView, TextViewModel>(services)
.AddView<TogglesView, TogglesViewModel>(services)
.AddView<ToastsView, ToastsViewModel>(services)
.AddView<TabControlView, TabControlViewModel>(services)
.AddView<StackPageView, StackPageViewModel>(services)
.AddView<DialogsView, DialogsViewModel>(services)
.AddView<ColorsView, ColorsViewModel>(services)

// Add additional views
.AddView<DialogView, DialogViewModel>(services)
.AddView<VmDialogView, VmDialogViewModel>(services)
.AddView<RecursiveView, RecursiveViewModel>(services)
.AddView<CustomThemeDialogView, CustomThemeDialogViewModel>(services);
}

private static ServiceProvider ConfigureServices(ServiceCollection services)
{
services.AddSingleton<ClipboardService>();
services.AddSingleton<PageNavigationService>();
services.AddSingleton<ISukiToastManager, SukiToastManager>();
services.AddSingleton<ISukiDialogManager, SukiDialogManager>();

// ViewModels
services.AddSingleton<SukiUIDemoViewModel>();
var types = AppDomain.CurrentDomain.GetAssemblies()
.SelectMany(s => s.GetTypes())
.Where(p => !p.IsAbstract && typeof(DemoPageBase).IsAssignableFrom(p));
foreach (var type in types)
services.AddSingleton(typeof(DemoPageBase), type);

return services.BuildServiceProvider();
}
}
81 changes: 81 additions & 0 deletions SukiUI.Demo/Common/SukiViews.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
using Avalonia.Controls;
using Avalonia.Markup.Xaml;
using CommunityToolkit.Mvvm.ComponentModel;
using Microsoft.Extensions.DependencyInjection;
using SukiUI.Demo.Features;
using System;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;

namespace SukiUI.Demo.Common;

public class SukiViews
{
private readonly Dictionary<Type, Type> _vmToViewMap = [];

public SukiViews AddView<
[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors)] TView,
[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors)] TViewModel>(ServiceCollection services)
where TView : ContentControl
where TViewModel : ObservableObject
{
var viewType = typeof(TView);
var viewModelType = typeof(TViewModel);

_vmToViewMap.Add(viewModelType, viewType);

if (viewModelType.IsAssignableTo(typeof(DemoPageBase)))
{
services.AddSingleton(typeof(DemoPageBase), viewModelType);
}
else
{
services.AddSingleton(viewModelType);
}

return this;
}

public bool TryCreateView(IServiceProvider provider, Type viewModelType, [NotNullWhen(true)] out Control? view)
{
var viewModel = provider.GetRequiredService(viewModelType);

return TryCreateView(viewModel, out view);
}

public bool TryCreateView(object? viewModel, [NotNullWhen(true)] out Control? view)
{
view = null;

if (viewModel == null)
{
return false;
}

var viewModelType = viewModel.GetType();

if (_vmToViewMap.TryGetValue(viewModelType, out var viewType))
{
view = Activator.CreateInstance(viewType) as Control;

if (view != null)
{
view.DataContext = viewModel;
}
}

return view != null;
}

public Control CreateView<TViewModel>(IServiceProvider provider) where TViewModel : ObservableObject
{
var viewModelType = typeof(TViewModel);

if (TryCreateView(provider, viewModelType, out var view))
{
return view;
}

throw new InvalidOperationException();
}
}
46 changes: 22 additions & 24 deletions SukiUI.Demo/Common/ViewLocator.cs
Original file line number Diff line number Diff line change
@@ -1,39 +1,37 @@
using Avalonia.Controls;
using Avalonia.Controls.Templates;
using System;
using CommunityToolkit.Mvvm.ComponentModel;
using System.Collections.Generic;
using System.ComponentModel;

namespace SukiUI.Demo.Common;

public class ViewLocator : IDataTemplate
public class ViewLocator(SukiViews views) : IDataTemplate
{
private readonly Dictionary<object, Control> _controlCache = new();
private readonly Dictionary<object, Control> _controlCache = [];

public Control Build(object? data)
public Control Build(object? param)
{
if(data is null)
return new TextBlock { Text = "Data is null." };

var fullName = data.GetType().FullName;

if (string.IsNullOrWhiteSpace(fullName))
return new TextBlock { Text = "Type has no name, or name is empty." };

var name = fullName.Replace("ViewModel", "View");
var type = Type.GetType(name);
if (type is null)
return new TextBlock { Text = $"No View For {name}." };

if (!_controlCache.TryGetValue(data, out var res))
if (param is null)
{
res ??= (Control)Activator.CreateInstance(type)!;
_controlCache[data] = res;
return CreateText("Data is null.");
}

res.DataContext = data;
return res;
if (_controlCache.TryGetValue(param, out var control))
{
return control;
}

if (views.TryCreateView(param, out var view))
{
_controlCache.Add(param, view);

return view;
}

return CreateText($"No View For {param.GetType().Name}.");
}

public bool Match(object? data) => data is INotifyPropertyChanged;
public bool Match(object? data) => data is ObservableObject;

private static TextBlock CreateText(string text) => new TextBlock { Text = text };
}
10 changes: 9 additions & 1 deletion SukiUI.Demo/Converters/StringToControlConverter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,15 @@ public sealed class StringToControlConverter : IValueConverter
if (string.IsNullOrWhiteSpace(xamlCode)) return null;

var previewCode = XamlData.InsertIntoGridControl(xamlCode);
return AvaloniaRuntimeXamlLoader.Parse<Grid>(previewCode);

try
{
return AvaloniaRuntimeXamlLoader.Parse<Grid>(previewCode);
}
catch
{
return null;
}
}

public object ConvertBack(object? value, Type targetType, object? parameter, CultureInfo culture)
Expand Down
26 changes: 26 additions & 0 deletions SukiUI.Demo/Features/ControlsLibrary/IconViewItem.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
using Avalonia.Media;
using CommunityToolkit.Mvvm.ComponentModel;
using CommunityToolkit.Mvvm.Input;
using SukiUI.Demo.Services;
using SukiUI.Toasts;

namespace SukiUI.Demo.Features.ControlsLibrary;

public partial class IconItemViewModel(ClipboardService clipboard, ISukiToastManager toastManager) : ObservableObject
{
public required string Name { get; init; }

public required Geometry Geometry { get; init; }

[RelayCommand]
public void OnClick()
{
clipboard.CopyToClipboard($"<PathIcon Data=\"{{x:Static content:Icons.{Name}}}\" />");

toastManager
.CreateSimpleInfoToast()
.WithTitle("Copied To Clipboard")
.WithContent($"Copied the XAML for {Name} to your clipboard.")
.Queue();
}
}
11 changes: 5 additions & 6 deletions SukiUI.Demo/Features/ControlsLibrary/IconsView.axaml
Original file line number Diff line number Diff line change
Expand Up @@ -38,18 +38,17 @@
</suki:GroupBox>
</suki:GlassCard>
<ScrollViewer Grid.Row="1">
<ItemsControl suki:ItemsControlExtensions.AnimatedScroll="True" ItemsSource="{Binding AllIcons}">
<ItemsControl suki:ItemsControlExtensions.AnimatedScroll="True" ItemsSource="{Binding Icons}">
<ItemsControl.ItemTemplate>
<DataTemplate x:CompileBindings="False">
<DataTemplate x:DataType="controlsLibrary:IconItemViewModel">
<suki:GlassCard Margin="15,15,15,15"
IsInteractive="True"
Command="{Binding $parent[controlsLibrary:IconsView].((controlsLibrary:IconsViewModel)DataContext).IconClickedCommand}"
CommandParameter="{Binding Key}">
<suki:GroupBox Header="{Binding Key}">
Command="{Binding ClickCommand}">
<suki:GroupBox Header="{Binding Name}">
<PathIcon Width="25"
Height="25"
Margin="0,8,0,0"
Data="{Binding Value}" />
Data="{Binding Geometry}" />
</suki:GroupBox>
</suki:GlassCard>
</DataTemplate>
Expand Down
Loading

0 comments on commit cfd6486

Please sign in to comment.