Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Update Gallery for NativeAOT Compatibility #336

Merged
merged 8 commits into from
Nov 27, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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 @@ -5,9 +5,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