ソースを参照

Extension window register changes

Krzysztof Krysiński 1 年間 前
コミット
5ad7c171b7

+ 2 - 2
src/PixiEditor.AvaloniaUI/Helpers/ServiceCollectionHelpers.cs

@@ -107,7 +107,7 @@ internal static class ServiceCollectionHelpers
             .AddSingleton<PaletteListDataSource, LocalPalettesFetcher>();
     }
 
-    public static IServiceCollection AddExtensionServices(this IServiceCollection collection) =>
-        collection.AddSingleton<IWindowProvider, WindowProvider>()
+    public static IServiceCollection AddExtensionServices(this IServiceCollection collection, ExtensionLoader loader) =>
+        collection.AddSingleton<IWindowProvider, WindowProvider>(x => new WindowProvider(loader, x))
             .AddSingleton<IPaletteProvider, PaletteProvider>();
 }

+ 50 - 5
src/PixiEditor.AvaloniaUI/Models/AppExtensions/ExtensionLoader.cs

@@ -3,7 +3,6 @@ using System.IO;
 using System.Linq;
 using System.Reflection;
 using System.Runtime.InteropServices;
-using System.Threading.Tasks;
 using Newtonsoft.Json;
 using PixiEditor.AvaloniaUI.Helpers;
 using PixiEditor.AvaloniaUI.Models.IO;
@@ -38,6 +37,26 @@ internal class ExtensionLoader
         }
     }
 
+    // Uncomment when PixiEditor.Core extension concept is implemented
+    /*private void LoadCore()
+    {
+        Type entry = typeof(PixiEditorCoreExtension);
+        Assembly assembly = entry.Assembly;
+        var serializer = new JsonSerializer();
+
+        Uri uri = new Uri("avares://PixiEditor.Core/extension.json");
+
+        if (!AssetLoader.Exists(uri))
+        {
+            throw new FileNotFoundException("Core metadata not found", uri.ToString());
+        }
+
+        using var sr = new StreamReader(AssetLoader.Open(uri));
+        using var jsonTextReader = new JsonTextReader(sr);
+        ExtensionMetadata? metadata = serializer.Deserialize<ExtensionMetadata>(jsonTextReader);
+        LoadExtensionFrom(assembly, entry, metadata);
+    }*/
+
     public void InitializeExtensions(ExtensionServices pixiEditorApi)
     {
         try
@@ -49,7 +68,9 @@ internal class ExtensionLoader
         }
         catch (Exception ex)
         {
-            CrashHelper.SendExceptionInfoToWebhook(ex);
+            // TODO: Log exception
+            // Maybe it's not a good idea to send webhook exceptions in the extension loader
+            //CrashHelper.SendExceptionInfoToWebhook(ex);
         }
     }
 
@@ -71,9 +92,7 @@ internal class ExtensionLoader
                 return;
             }
 
-            var extension = LoadExtensionEntry(entry, extensionType, metadata);
-            extension.Load();
-            LoadedExtensions.Add(extension);
+            LoadExtensionFrom(entry, extensionType, metadata);
         }
         catch (JsonException)
         {
@@ -90,6 +109,13 @@ internal class ExtensionLoader
         }
     }
 
+    private void LoadExtensionFrom(Assembly entry, Type extensionType, ExtensionMetadata metadata)
+    {
+        var extension = LoadExtensionEntry(entry, extensionType, metadata);
+        extension.Load();
+        LoadedExtensions.Add(extension);
+    }
+
     private Assembly? GetEntryAssembly(string assemblyFolder, out Type extensionType)
     {
         string[] dlls = Directory.GetFiles(assemblyFolder, "*.dll");
@@ -216,6 +242,25 @@ internal class ExtensionLoader
             Directory.CreateDirectory(Paths.ExtensionsFullPath);
         }
     }
+
+    public string? GetTypeId(Type id)
+    {
+        if (id.Assembly == Assembly.GetExecutingAssembly())
+        {
+            return $"PixiEditor.{id.Name}";
+        }
+
+        foreach (var extension in LoadedExtensions)
+        {
+            Type? foundType = extension.Assembly.GetTypes().FirstOrDefault(x => x == id);
+            if (foundType != null)
+            {
+                return $"{extension.Metadata.UniqueName}:{foundType.Name}";
+            }
+        }
+
+        return null;
+    }
 }
 
 internal struct OfficialExtensionData

+ 47 - 15
src/PixiEditor.AvaloniaUI/Models/AppExtensions/Services/WindowProvider.cs

@@ -1,47 +1,79 @@
 using System.Collections.Generic;
 using System.Linq;
+using System.Reflection;
 using PixiEditor.AvaloniaUI.Views.Dialogs;
 using PixiEditor.Extensions;
+using PixiEditor.Extensions.Helpers;
 using PixiEditor.Extensions.Windowing;
 
 namespace PixiEditor.AvaloniaUI.Models.AppExtensions.Services;
 
 public class WindowProvider : IWindowProvider
 {
-    private Dictionary<string, Func<IPopupWindow>> _openHandlers = new();
+    private readonly Dictionary<string, Type> registeredWindows = new();
+    private ExtensionLoader extensionLoader;
+    private IServiceProvider services;
 
-    public WindowProvider RegisterHandler(string id, Func<IPopupWindow> handler)
+    internal WindowProvider(ExtensionLoader loader, IServiceProvider services)
     {
-        if (_openHandlers.ContainsKey(id))
+        this.extensionLoader = loader;
+        this.services = services;
+    }
+
+    public WindowProvider RegisterWindow<T>() where T : IPopupWindow
+    {
+        Type type = typeof(T);
+        string? id = extensionLoader.GetTypeId(type);
+        if (id is null)
         {
-            _openHandlers[id] = handler;
-            throw new ArgumentException($"Window with id {id} already has a handler");
+            throw new ArgumentException($"Window {type} doesn't seem to be part of an extension.");
+        }
+
+        if (!registeredWindows.TryAdd(id, type))
+        {
+            throw new ArgumentException($"Window with id {id} is already registered.");
         }
 
-        _openHandlers.Add(id, handler);
         return this;
     }
 
     public PopupWindow CreatePopupWindow(string title, object body)
     {
-        return new PopupWindow(new PixiEditorPopup() { Title = title, Content = body });
+        return new PopupWindow(new PixiEditorPopup { Title = title, Content = body });
+    }
+
+    public PopupWindow GetWindow(WindowType type)
+    {
+        string id = type.GetDescription();
+        return GetWindow($"PixiEditor.{id}");
     }
 
-    public PopupWindow OpenWindow(WindowType type)
+    public PopupWindow GetWindow(string windowId)
     {
-        return OpenWindow($"PixiEditor.{type}");
+        if (registeredWindows.TryGetValue(windowId, out Type? handler))
+        {
+            object[] args = TryGetConstructorArgs(handler);
+            return new PopupWindow((IPopupWindow)Activator.CreateInstance(handler, args));
+        }
+
+        throw new ArgumentException($"Window with id {windowId} does not exist");
     }
 
-    public PopupWindow OpenWindow(string windowId)
+    private object?[] TryGetConstructorArgs(Type handler)
     {
-        var handler = _openHandlers.FirstOrDefault(x => x.Key == windowId);
-        if (handler.Key != null)
+        ConstructorInfo[] constructors = handler.GetConstructors();
+        if (constructors.Length == 0)
         {
-            return new PopupWindow(handler.Value());
+            return Array.Empty<object>();
         }
-        else
+
+        ConstructorInfo constructor = constructors[0];
+        ParameterInfo[] parameters = constructor.GetParameters();
+        if (parameters.Length == 0)
         {
-            throw new ArgumentException($"Window with id {windowId} does not exist");
+            return Array.Empty<object>();
         }
+
+        return parameters.Select(x => services.GetService(x.ParameterType)).ToArray();
     }
 }

+ 4 - 0
src/PixiEditor.AvaloniaUI/PixiEditor.AvaloniaUI.csproj

@@ -82,6 +82,10 @@
         <DependentUpon>LocalizationDebugWindow.axaml</DependentUpon>
         <SubType>Code</SubType>
       </Compile>
+      <Compile Update="Views\Windows\PalettesBrowser.axaml.cs">
+        <DependentUpon>PalettesBrowser.axaml</DependentUpon>
+        <SubType>Code</SubType>
+      </Compile>
     </ItemGroup>
   
     <ItemGroup>

+ 2 - 3
src/PixiEditor.AvaloniaUI/ViewModels/SubViewModels/ColorsViewModel.cs

@@ -150,7 +150,7 @@ internal class ColorsViewModel : SubViewModel<ViewModelMain>, IColorsHandler
     {
         var doc = Owner.DocumentManagerSubViewModel.ActiveDocument;
         if (doc is not null)
-            PalettesBrowser.Open(PaletteProvider, ImportPaletteCommand, doc.Palette);
+            PalettesBrowser.Open();
     } 
 
     private async Task ImportLospecPalette()
@@ -160,8 +160,7 @@ internal class ColorsViewModel : SubViewModel<ViewModelMain>, IColorsHandler
 
         if (lospecPaletteArg != null)
         {
-            var browser = PalettesBrowser.Open(PaletteProvider, ImportPaletteCommand,
-                new ObservableRangeCollection<PaletteColor>());
+            var browser = PalettesBrowser.Open();
 
             browser.IsFetching = true;
             var palette = await LospecPaletteFetcher.FetchPalette(lospecPaletteArg.Split(@"://")[1].Replace("/", ""));

+ 8 - 7
src/PixiEditor.AvaloniaUI/ViewModels/SubViewModels/ExtensionsViewModel.cs

@@ -13,16 +13,17 @@ internal class ExtensionsViewModel : SubViewModel<ViewModelMain>
     public ExtensionsViewModel(ViewModelMain owner, ExtensionLoader loader) : base(owner)
     {
         ExtensionLoader = loader;
-        ((WindowProvider)Owner.Services.GetService<IWindowProvider>()).RegisterHandler(PalettesBrowser.UniqueId, () =>
-        {
-            return PalettesBrowser.Open(
-                Owner.ColorsSubViewModel.PaletteProvider,
-                Owner.ColorsSubViewModel.ImportPaletteCommand,
-                Owner.DocumentManagerSubViewModel.ActiveDocument?.Palette);
-        });
+        WindowProvider windowProvider = (WindowProvider)Owner.Services.GetService<IWindowProvider>();
+
+        RegisterCoreWindows(windowProvider);
         Owner.OnStartupEvent += Owner_OnStartupEvent;
     }
 
+    private void RegisterCoreWindows(WindowProvider? windowProvider)
+    {
+        windowProvider?.RegisterWindow<PalettesBrowser>();
+    }
+
     private void Owner_OnStartupEvent(object sender, EventArgs e)
     {
         ExtensionLoader.InitializeExtensions(new ExtensionServices(Owner.Services));

+ 1 - 2
src/PixiEditor.AvaloniaUI/ViewModels/SubViewModels/WindowViewModel.cs

@@ -171,8 +171,7 @@ internal class WindowViewModel : SubViewModel<ViewModelMain>
         IconPath = "Database.png")]
     public void ShowPalettesBrowserWindow()
     {
-        PalettesBrowser.Open(Owner.ColorsSubViewModel.PaletteProvider, Owner.ColorsSubViewModel.ImportPaletteCommand,
-            Owner.DocumentManagerSubViewModel.ActiveDocument?.Palette);
+        PalettesBrowser.Open();
     }
         
     [Command.Basic("PixiEditor.Window.OpenAboutWindow", "OPEN_ABOUT_WINDOW", "OPEN_ABOUT_WINDOW")]

+ 1 - 0
src/PixiEditor.AvaloniaUI/Views/Dialogs/PixiEditorPopup.cs

@@ -7,6 +7,7 @@ using Avalonia.Styling;
 using CommunityToolkit.Mvvm.Input;
 using PixiEditor.AvaloniaUI.Helpers.Extensions;
 using PixiEditor.Extensions;
+using PixiEditor.Extensions.Windowing;
 
 namespace PixiEditor.AvaloniaUI.Views.Dialogs;
 

+ 1 - 1
src/PixiEditor.AvaloniaUI/Views/MainWindow.axaml.cs

@@ -44,7 +44,7 @@ internal partial class MainWindow : Window
         services = new ServiceCollection()
             .AddPlatform()
             .AddPixiEditor(extensionLoader)
-            .AddExtensionServices()
+            .AddExtensionServices(extensionLoader)
             .BuildServiceProvider();
 
         SkiaDrawingBackend skiaDrawingBackend = new SkiaDrawingBackend();

+ 1 - 1
src/PixiEditor.AvaloniaUI/Views/Palettes/PaletteViewer.axaml.cs

@@ -279,7 +279,7 @@ internal partial class PaletteViewer : UserControl
 
     private async void BrowsePalettes_Click(object sender, RoutedEventArgs e)
     {
-        var browser = PalettesBrowser.Open(PaletteProvider, ImportPaletteCommand, Colors);
+        var browser = PalettesBrowser.Open();
         await browser.UpdatePaletteList();
     }
 

+ 8 - 9
src/PixiEditor.AvaloniaUI/Views/Windows/PalettesBrowser.axaml.cs

@@ -18,6 +18,7 @@ using PixiEditor.AvaloniaUI.Models.Dialogs;
 using PixiEditor.AvaloniaUI.Models.IO;
 using PixiEditor.AvaloniaUI.Models.Palettes;
 using PixiEditor.AvaloniaUI.Models.Structures;
+using PixiEditor.AvaloniaUI.ViewModels.SubViewModels;
 using PixiEditor.AvaloniaUI.Views.Dialogs;
 using PixiEditor.AvaloniaUI.Views.Input;
 using PixiEditor.AvaloniaUI.Views.Palettes;
@@ -26,6 +27,7 @@ using PixiEditor.Extensions.Common.Localization;
 using PixiEditor.Extensions.Common.UserPreferences;
 using PixiEditor.Extensions.Palettes;
 using PixiEditor.Extensions.Palettes.Parsers;
+using PixiEditor.Extensions.Windowing;
 using PixiEditor.Models.Enums;
 using PixiEditor.OperatingSystem;
 using PaletteColor = PixiEditor.Extensions.Palettes.PaletteColor;
@@ -35,9 +37,6 @@ namespace PixiEditor.AvaloniaUI.Views.Windows;
 
 internal partial class PalettesBrowser : PixiEditorPopup, IPopupWindow
 {
-    public static string UniqueId => "PixiEditor.BrowserPalette";
-    string IPopupWindow.UniqueId => UniqueId;
-
     private const int ItemsPerLoad = 25;
 
     private readonly LocalizedString[] stopItTexts = new[]
@@ -184,13 +183,15 @@ internal partial class PalettesBrowser : PixiEditorPopup, IPopupWindow
         ShowOnlyFavouritesProperty.Changed.Subscribe(OnShowOnlyFavouritesChanged);
     }
 
-    public PalettesBrowser(PaletteProvider provider)
+    public PalettesBrowser(ColorsViewModel vm)
     {
         localizationProvider = ViewModelMain.Current.LocalizationProvider;
         localizationProvider.OnLanguageChanged += LocalizationProviderOnOnLanguageChanged;
         MinWidth = DetermineWidth();
         
-        PaletteProvider = provider;
+        PaletteProvider = vm.PaletteProvider;
+        ImportPaletteCommand = vm.ImportPaletteCommand;
+        CurrentEditingPalette = vm.Owner.DocumentManagerSubViewModel.ActiveDocument?.Palette;
         InitializeComponent();
         Title = new LocalizedString("PALETTE_BROWSER");
         Instance = this;
@@ -253,7 +254,7 @@ internal partial class PalettesBrowser : PixiEditorPopup, IPopupWindow
             IPreferences.Current.GetLocalPreference<List<string>>(PreferencesConstants.FavouritePalettes);
     }
 
-    public static PalettesBrowser Open(PaletteProvider provider, ICommand importPaletteCommand, ObservableRangeCollection<PaletteColor> currentEditingPalette)
+    public static PalettesBrowser Open()
     {
         if (Instance != null) return Instance;
 
@@ -263,11 +264,9 @@ internal partial class PalettesBrowser : PixiEditorPopup, IPopupWindow
             owner = desktop.MainWindow;
         }
 
-        PalettesBrowser browser = new PalettesBrowser(provider)
+        PalettesBrowser browser = new PalettesBrowser(ViewModelMain.Current.ColorsSubViewModel)
         {
             Owner = owner,
-            ImportPaletteCommand = importPaletteCommand,
-            CurrentEditingPalette = currentEditingPalette
         };
 
         browser.Show();

+ 24 - 0
src/PixiEditor.Core/PixiEditor.Core.csproj

@@ -0,0 +1,24 @@
+<Project Sdk="Microsoft.NET.Sdk">
+
+    <PropertyGroup>
+        <TargetFramework>net8.0</TargetFramework>
+        <ImplicitUsings>enable</ImplicitUsings>
+        <Nullable>enable</Nullable>
+    </PropertyGroup>
+
+    <ItemGroup>
+      <ProjectReference Include="..\PixiEditor.Extensions\PixiEditor.Extensions.csproj" />
+    </ItemGroup>
+
+    <ItemGroup>
+      <None Remove="extension.json" />
+      <AvaloniaResource Include="extension.json">
+        <CopyToOutputDirectory>Never</CopyToOutputDirectory>
+      </AvaloniaResource>
+    </ItemGroup>
+
+    <ItemGroup>
+      <PackageReference Include="CommunityToolkit.Mvvm" Version="8.2.2" />
+    </ItemGroup>
+
+</Project>

+ 8 - 0
src/PixiEditor.Core/PixiEditorCoreExtension.cs

@@ -0,0 +1,8 @@
+using PixiEditor.Extensions;
+
+namespace PixiEditor.Core;
+
+public class PixiEditorCoreExtension : Extension
+{
+
+}

+ 2 - 0
src/PixiEditor.Core/extension.json

@@ -0,0 +1,2 @@
+{
+}

+ 3 - 2
src/PixiEditor.Extensions/Windowing/IPopupWindow.cs

@@ -1,12 +1,13 @@
-namespace PixiEditor.Extensions;
+namespace PixiEditor.Extensions.Windowing;
 
 public interface IPopupWindow
 {
-    public string UniqueId { get; }
     public string Title { get; set; }
     public void Show();
     public void Close();
     public Task<bool?> ShowDialog();
     public double Width { get; set; }
     public double Height { get; set; }
+    public bool CanResize { get; set; }
+    public bool CanMinimize { get; set; }
 }

+ 7 - 4
src/PixiEditor.Extensions/Windowing/IWindowProvider.cs

@@ -1,13 +1,16 @@
-namespace PixiEditor.Extensions.Windowing;
+using System.ComponentModel;
+
+namespace PixiEditor.Extensions.Windowing;
 
 public interface IWindowProvider
 {
     public PopupWindow CreatePopupWindow(string title, object body);
-    public PopupWindow OpenWindow(WindowType type);
-    public PopupWindow OpenWindow(string windowId);
+    public PopupWindow GetWindow(WindowType type);
+    public PopupWindow GetWindow(string windowId);
 }
 
 public enum WindowType
 {
-    BrowserPalette
+    [Description("PalettesBrowser")]
+    PalettesBrowser
 }

+ 12 - 2
src/PixiEditor.Extensions/Windowing/PopupWindow.cs

@@ -2,8 +2,6 @@
 
 public class PopupWindow : IPopupWindow
 {
-    public string UniqueId => _underlyingWindow.UniqueId;
-
     private IPopupWindow _underlyingWindow;
 
     public PopupWindow(IPopupWindow basicPopup)
@@ -31,4 +29,16 @@ public class PopupWindow : IPopupWindow
         get => _underlyingWindow.Height;
         set => _underlyingWindow.Height = value;
     }
+
+    public bool CanResize
+    {
+        get => _underlyingWindow.CanResize;
+        set => _underlyingWindow.CanResize = value;
+    }
+
+    public bool CanMinimize
+    {
+        get => _underlyingWindow.CanMinimize;
+        set => _underlyingWindow.CanMinimize = value;
+    }
 }

+ 7 - 3
src/PixiEditor/Models/AppExtensions/ExtensionLoader.cs

@@ -1,4 +1,5 @@
-using System.IO;
+using System.Diagnostics;
+using System.IO;
 using System.Reflection;
 using System.Runtime.InteropServices;
 using System.Windows;
@@ -49,7 +50,8 @@ internal class ExtensionLoader
         }
         catch (Exception ex)
         {
-            CrashHelper.SendExceptionInfoToWebhook(ex);
+            Debug.WriteLine(ex);
+            //CrashHelper.SendExceptionInfoToWebhook(ex);
         }
     }
 
@@ -82,11 +84,13 @@ internal class ExtensionLoader
         catch (ExtensionException ex)
         {
             //MessageBox.Show(ex.DisplayMessage, "ERROR");
+            Debug.WriteLine(ex);
         }
         catch (Exception ex)
         {
             //MessageBox.Show(new LocalizedString("ERROR_LOADING_PACKAGE", packageJsonPath), "ERROR");
-            CrashHelper.SendExceptionInfoToWebhook(ex);
+            //TODO: Maybe sending exception info to webhook is not a good idea?
+            //CrashHelper.SendExceptionInfoToWebhook(ex);
         }
     }
 

+ 3 - 3
src/PixiEditor/Models/AppExtensions/Services/WindowProvider.cs

@@ -26,12 +26,12 @@ public class WindowProvider : IWindowProvider
         return new PopupWindow(new BasicPopup { Title = title, Body = body });
     }
 
-    public PopupWindow OpenWindow(WindowType type)
+    public PopupWindow GetWindow(WindowType type)
     {
-        return OpenWindow($"PixiEditor.{type}");
+        return GetWindow($"PixiEditor.{type}");
     }
 
-    public PopupWindow OpenWindow(string windowId)
+    public PopupWindow GetWindow(string windowId)
     {
         var handler = _openHandlers.FirstOrDefault(x => x.Key == windowId);
         if (handler.Key != null)

+ 1 - 0
src/PixiEditor/Views/Dialogs/BasicPopup.xaml.cs

@@ -1,5 +1,6 @@
 using System.Windows;
 using PixiEditor.Extensions;
+using PixiEditor.Extensions.Windowing;
 using PixiEditor.Helpers;
 
 namespace PixiEditor.Views.Dialogs;

+ 1 - 0
src/PixiEditor/Views/Dialogs/PalettesBrowser.xaml.cs

@@ -11,6 +11,7 @@ using PixiEditor.Extensions.Common.Localization;
 using PixiEditor.Extensions.Common.UserPreferences;
 using PixiEditor.Extensions.Palettes;
 using PixiEditor.Extensions.Palettes.Parsers;
+using PixiEditor.Extensions.Windowing;
 using PixiEditor.Helpers;
 using PixiEditor.Models.AppExtensions.Services;
 using PixiEditor.Models.DataHolders;

+ 3 - 0
src/SampleExtension/SampleExtension.cs

@@ -2,6 +2,7 @@
 using Avalonia.Layout;
 using Avalonia.Media;
 using PixiEditor.Extensions;
+using PixiEditor.Extensions.Windowing;
 
 namespace SampleExtension;
 
@@ -20,5 +21,7 @@ public class SampleExtension : Extension
         });
         Api.PaletteProvider.RegisterDataSource(new TestPaletteDataSource());
         popup.ShowDialog();
+
+        Api.WindowProvider.GetWindow(WindowType.PalettesBrowser).Show();
     }
 }