diff --git a/.github/workflows/build-gallery-windows.yml b/.github/workflows/build-gallery-windows.yml
new file mode 100644
index 000000000..821c2fd3a
--- /dev/null
+++ b/.github/workflows/build-gallery-windows.yml
@@ -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
diff --git a/SukiUI.Demo/App.axaml b/SukiUI.Demo/App.axaml
index 11485037b..ceca5e713 100644
--- a/SukiUI.Demo/App.axaml
+++ b/SukiUI.Demo/App.axaml
@@ -6,9 +6,6 @@
xmlns:common="clr-namespace:SukiUI.Demo.Common"
xmlns:suki="https://github.com/kikipoulet/SukiUI"
RequestedThemeVariant="Default">
-
-
-
diff --git a/SukiUI.Demo/App.axaml.cs b/SukiUI.Demo/App.axaml.cs
index 810a6a6ce..d0f257f6c 100644
--- a/SukiUI.Demo/App.axaml.cs
+++ b/SukiUI.Demo/App.axaml.cs
@@ -5,11 +5,19 @@
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;
@@ -17,52 +25,76 @@ 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();
- var mainViewModel = _provider?.GetRequiredService();
- var mainView = _provider?.GetRequiredService();
- 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(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();
-
- // Services
- if (viewLocator is not null)
- services.AddSingleton(viewLocator);
- services.AddSingleton();
+ // Add main view
+ .AddView(services)
+
+ // Add pages
+ .AddView(services)
+ .AddView(services)
+ .AddView(services)
+ .AddView(services)
+ .AddView(services)
+ .AddView(services)
+ .AddView(services)
+ .AddView(services)
+ .AddView(services)
+ .AddView(services)
+ .AddView(services)
+ .AddView(services)
+ .AddView(services)
+ .AddView(services)
+ .AddView(services)
+ .AddView(services)
+ .AddView(services)
+ .AddView(services)
+ .AddView(services)
+ .AddView(services)
+ .AddView(services)
+ .AddView(services)
+ .AddView(services)
+
+ // Add additional views
+ .AddView(services)
+ .AddView(services)
+ .AddView(services)
+ .AddView(services);
+ }
+
+ private static ServiceProvider ConfigureServices(ServiceCollection services)
+ {
services.AddSingleton();
+ services.AddSingleton();
services.AddSingleton();
services.AddSingleton();
- // ViewModels
- services.AddSingleton();
- 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();
}
}
\ No newline at end of file
diff --git a/SukiUI.Demo/Common/SukiViews.cs b/SukiUI.Demo/Common/SukiViews.cs
new file mode 100644
index 000000000..b56bbaf8d
--- /dev/null
+++ b/SukiUI.Demo/Common/SukiViews.cs
@@ -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 _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(IServiceProvider provider) where TViewModel : ObservableObject
+ {
+ var viewModelType = typeof(TViewModel);
+
+ if (TryCreateView(provider, viewModelType, out var view))
+ {
+ return view;
+ }
+
+ throw new InvalidOperationException();
+ }
+}
\ No newline at end of file
diff --git a/SukiUI.Demo/Common/ViewLocator.cs b/SukiUI.Demo/Common/ViewLocator.cs
index 4f12def8b..47adb52d7 100644
--- a/SukiUI.Demo/Common/ViewLocator.cs
+++ b/SukiUI.Demo/Common/ViewLocator.cs
@@ -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