Procházet zdrojové kódy

Palettes api and initialization changes

Krzysztof Krysiński před 2 roky
rodič
revize
bd1247f05c
24 změnil soubory, kde provedl 239 přidání a 54 odebrání
  1. 2 0
      src/PixiEditor.Extensions/ExtensionServices.cs
  2. 0 0
      src/PixiEditor.Extensions/Palettes/ColorsNumberMode.cs
  3. 14 0
      src/PixiEditor.Extensions/Palettes/ExtensionPalette.cs
  4. 3 3
      src/PixiEditor.Extensions/Palettes/FilteringSettings.cs
  5. 8 0
      src/PixiEditor.Extensions/Palettes/IPalette.cs
  6. 21 0
      src/PixiEditor.Extensions/Palettes/IPaletteProvider.cs
  7. 22 0
      src/PixiEditor.Extensions/Palettes/PaletteColor.cs
  8. 0 1
      src/PixiEditor.Extensions/PixiEditor.Extensions.csproj
  9. 0 6
      src/PixiEditor/App.xaml.cs
  10. 5 2
      src/PixiEditor/Helpers/Extensions/ServiceCollectionHelpers.cs
  11. 58 0
      src/PixiEditor/Models/AppExtensions/Services/PaletteProvider.cs
  12. 4 3
      src/PixiEditor/Models/DataHolders/Palettes/Palette.cs
  13. 25 0
      src/PixiEditor/Models/DataHolders/Palettes/PaletteObject.cs
  14. 15 18
      src/PixiEditor/Models/DataProviders/LocalPalettesFetcher.cs
  15. 2 1
      src/PixiEditor/Models/DataProviders/PaletteListDataSource.cs
  16. 4 3
      src/PixiEditor/Models/ExternalServices/LospecPaletteFetcher.cs
  17. 6 3
      src/PixiEditor/Models/IO/PaletteFileData.cs
  18. 5 2
      src/PixiEditor/Models/IO/Paths.cs
  19. 7 6
      src/PixiEditor/ViewModels/SubViewModels/Main/ColorsViewModel.cs
  20. 24 0
      src/PixiEditor/ViewModels/SubViewModels/Main/ExtensionsViewModel.cs
  21. 4 0
      src/PixiEditor/ViewModels/ViewModelMain.cs
  22. 6 5
      src/PixiEditor/Views/Dialogs/PalettesBrowser.xaml.cs
  23. 1 1
      src/PixiEditor/Views/UserControls/Palettes/PaletteItem.xaml
  24. 3 0
      src/SampleExtension/SampleExtension.cs

+ 2 - 0
src/PixiEditor.Extensions/ExtensionServices.cs

@@ -1,4 +1,5 @@
 using Microsoft.Extensions.DependencyInjection;
+using PixiEditor.Extensions.Palettes;
 using PixiEditor.Extensions.Windowing;
 
 namespace PixiEditor.Extensions;
@@ -8,6 +9,7 @@ public class ExtensionServices
     public ServiceProvider Services { get; private set; }
 
     public IWindowProvider WindowProvider => Services.GetRequiredService<IWindowProvider>();
+    public IPaletteProvider PaletteProvider => Services.GetRequiredService<IPaletteProvider>();
 
     public ExtensionServices(ServiceProvider services)
     {

+ 0 - 0
src/PixiEditor/Models/Enums/ColorsNumberMode.cs → src/PixiEditor.Extensions/Palettes/ColorsNumberMode.cs


+ 14 - 0
src/PixiEditor.Extensions/Palettes/ExtensionPalette.cs

@@ -0,0 +1,14 @@
+namespace PixiEditor.Extensions.Palettes;
+
+public class ExtensionPalette : IPalette
+{
+    public string Name { get; }
+    public List<PaletteColor> Colors { get; }
+    public bool IsFavourite { get; set; }
+
+    public ExtensionPalette(string name, List<PaletteColor> colors)
+    {
+        Name = name;
+        Colors = colors;
+    }
+}

+ 3 - 3
src/PixiEditor/Models/DataHolders/Palettes/FilteringSettings.cs → src/PixiEditor.Extensions/Palettes/FilteringSettings.cs

@@ -1,8 +1,8 @@
 using PixiEditor.Models.Enums;
 
-namespace PixiEditor.Models.DataHolders.Palettes;
+namespace PixiEditor.Extensions.Palettes;
 
-internal class FilteringSettings
+public sealed class FilteringSettings
 {
     public ColorsNumberMode ColorsNumberMode { get; set; }
     public int ColorsCount { get; set; }
@@ -18,7 +18,7 @@ internal class FilteringSettings
         ShowOnlyFavourites = showOnlyFavourites;
     }
 
-    public bool Filter(Palette palette)
+    public bool Filter(IPalette palette)
     {
         // Lexical comparison
         bool result = string.IsNullOrWhiteSpace(Name) || palette.Name.Contains(Name, StringComparison.OrdinalIgnoreCase);

+ 8 - 0
src/PixiEditor.Extensions/Palettes/IPalette.cs

@@ -0,0 +1,8 @@
+namespace PixiEditor.Extensions.Palettes;
+
+public interface IPalette
+{
+    public string Name { get; }
+    public List<PaletteColor> Colors { get; }
+    public bool IsFavourite { get; set; }
+}

+ 21 - 0
src/PixiEditor.Extensions/Palettes/IPaletteProvider.cs

@@ -0,0 +1,21 @@
+namespace PixiEditor.Extensions.Palettes;
+
+public interface IPaletteProvider
+{
+    /// <summary>
+    ///     Fetches palettes from the provider.
+    /// </summary>
+    /// <param name="startIndex">Starting fetch index. Palettes before said index won't be fetched.</param>
+    /// <param name="items">Max amount of palettes to fetch.</param>
+    /// <param name="filtering">Filtering settings for fetching.</param>
+    /// <returns>List of palettes.</returns>
+    public Task<List<IPalette>> FetchPalettes(int startIndex, int items, FilteringSettings filtering);
+
+    /// <summary>
+    ///     Adds a palette to the provider. This means that the palette will be saved in local storage.
+    /// </summary>
+    /// <param name="palette">Palette to save.</param>
+    /// <param name="overwrite">If true and palette with the same name exists, it will be overwritten. If false and palette with the same name exists, it will not be added.</param>
+    /// <returns>True if adding palette was successful.</returns>
+    public Task<bool> AddPalette(IPalette palette, bool overwrite = false);
+}

+ 22 - 0
src/PixiEditor.Extensions/Palettes/PaletteColor.cs

@@ -0,0 +1,22 @@
+namespace PixiEditor.Extensions.Palettes;
+
+public struct PaletteColor
+{
+    public byte R { get; set; }
+    public byte G { get; set; }
+    public byte B { get; set; }
+
+    public string Hex => $"#{R:X2}{G:X2}{B:X2}";
+
+    public PaletteColor(byte r, byte g, byte b)
+    {
+        R = r;
+        G = g;
+        B = b;
+    }
+
+    public override string ToString()
+    {
+        return Hex;
+    }
+}

+ 0 - 1
src/PixiEditor.Extensions/PixiEditor.Extensions.csproj

@@ -11,5 +11,4 @@
       <PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="7.0.0" />
       <PackageReference Include="Microsoft.Extensions.DependencyInjection.Abstractions" Version="7.0.0" />
     </ItemGroup>
-
 </Project>

+ 0 - 6
src/PixiEditor/App.xaml.cs

@@ -51,14 +51,8 @@ internal partial class App : Application
 
         AddNativeAssets();
 
-        var services = new ServiceCollection().AddExtensionServices().BuildServiceProvider();
-        ExtensionLoader loader = new ExtensionLoader(new ExtensionServices(services));
-        loader.LoadExtensions();
-
         MainWindow = new MainWindow();
         MainWindow.Show();
-
-        loader.InitializeExtensions();
     }
 
     private void AddNativeAssets()

+ 5 - 2
src/PixiEditor/Helpers/Extensions/ServiceCollectionHelpers.cs

@@ -1,5 +1,6 @@
 using Microsoft.Extensions.DependencyInjection;
 using PixiEditor.Extensions;
+using PixiEditor.Extensions.Palettes;
 using PixiEditor.Extensions.Windowing;
 using PixiEditor.Models.AppExtensions.Services;
 using PixiEditor.Models.Commands;
@@ -46,6 +47,7 @@ internal static class ServiceCollectionHelpers
         .AddSingleton<DebugViewModel>()
         .AddSingleton<SearchViewModel>()
         .AddSingleton<AdditionalContentViewModel>()
+        .AddSingleton<ExtensionsViewModel>()
         // Controllers
         .AddSingleton<ShortcutController>()
         .AddSingleton<CommandController>()
@@ -77,6 +79,7 @@ internal static class ServiceCollectionHelpers
         // Palette data sources
         .AddSingleton<PaletteListDataSource, LocalPalettesFetcher>();
 
-    public static IServiceCollection AddExtensionServices(this IServiceCollection collection) =>
-        collection.AddSingleton<IWindowProvider, WindowProvider>();
+    public static IServiceCollection AddExtensionServices(this IServiceCollection collection, List<PaletteListDataSource> paletteDataSources) =>
+        collection.AddSingleton<IWindowProvider, WindowProvider>()
+            .AddSingleton<IPaletteProvider, PaletteProvider>(provider => new PaletteProvider(paletteDataSources));
 }

+ 58 - 0
src/PixiEditor/Models/AppExtensions/Services/PaletteProvider.cs

@@ -0,0 +1,58 @@
+using PixiEditor.DrawingApi.Core.ColorsImpl;
+using PixiEditor.Extensions.Palettes;
+using PixiEditor.Models.DataHolders.Palettes;
+using PixiEditor.Models.DataProviders;
+
+namespace PixiEditor.Models.AppExtensions.Services;
+
+internal sealed class PaletteProvider : IPaletteProvider
+{
+    private List<PaletteListDataSource> dataSources;
+
+    public PaletteProvider(List<PaletteListDataSource> dataSources)
+    {
+        this.dataSources = dataSources;
+    }
+
+    public async Task<List<IPalette>> FetchPalettes(int startIndex, int items, FilteringSettings filtering)
+    {
+        List<IPalette> allPalettes = new();
+        foreach (PaletteListDataSource dataSource in dataSources)
+        {
+            var palettes = await dataSource.FetchPaletteList(startIndex, items, filtering);
+            allPalettes.AddRange(palettes.Palettes);
+        }
+
+        return allPalettes;
+    }
+
+    public async Task<bool> AddPalette(IPalette palette, bool overwrite = false)
+    {
+        LocalPalettesFetcher localPalettesFetcher = dataSources.OfType<LocalPalettesFetcher>().FirstOrDefault();
+        if(localPalettesFetcher == null)
+        {
+            return false;
+        }
+
+        if (LocalPalettesFetcher.PaletteExists(palette.Name))
+        {
+            if (overwrite)
+            {
+                await localPalettesFetcher.DeletePalette(palette.Name);
+            }
+            else
+            {
+                return false;
+            }
+        }
+
+
+        string finalName = LocalPalettesFetcher.GetNonExistingName(palette.Name, true);
+
+        await localPalettesFetcher.SavePalette(
+            finalName,
+            palette.Colors.Select(x => new Color(x.R, x.G, x.B)).ToArray());
+
+        return true;
+    }
+}

+ 4 - 3
src/PixiEditor/Models/DataHolders/Palettes/Palette.cs

@@ -1,9 +1,10 @@
 #nullable enable
 using System.IO;
+using PixiEditor.Extensions.Palettes;
 using PixiEditor.Helpers;
 namespace PixiEditor.Models.DataHolders.Palettes;
 
-internal class Palette : NotifyableObject
+internal class Palette : NotifyableObject, IPalette
 {
     private string _name = "";
 
@@ -12,7 +13,7 @@ internal class Palette : NotifyableObject
         get => _name;
         set => SetProperty(ref _name, value);
     }
-    public List<string> Colors { get; set; }
+    public List<PaletteColor> Colors { get; set; }
 
     private string? fileName;
 
@@ -28,7 +29,7 @@ internal class Palette : NotifyableObject
 
     public bool IsFavourite { get; set; }
 
-    public Palette(string name, List<string> colors, string fileName)
+    public Palette(string name, List<PaletteColor> colors, string? fileName)
     {
         Name = name;
         Colors = colors;

+ 25 - 0
src/PixiEditor/Models/DataHolders/Palettes/PaletteObject.cs

@@ -0,0 +1,25 @@
+using PixiEditor.DrawingApi.Core.ColorsImpl;
+using PixiEditor.Extensions.Palettes;
+
+namespace PixiEditor.Models.DataHolders.Palettes;
+
+/// <summary>
+///     Class used to deserialize palette file from Lospec.
+/// </summary>
+internal class PaletteObject
+{
+    public string Name { get; set; }
+    public List<string> Colors { get; set; }
+
+    public Palette ToPalette()
+    {
+        List<PaletteColor> colors = new();
+        foreach (string color in Colors)
+        {
+            Color parsedColor = Color.Parse(color);
+            colors.Add(new PaletteColor(parsedColor.R, parsedColor.G, parsedColor.B));
+        }
+
+        return new(Name, colors, null);
+    }
+}

+ 15 - 18
src/PixiEditor/Models/DataProviders/LocalPalettesFetcher.cs

@@ -1,5 +1,6 @@
 using System.IO;
 using PixiEditor.DrawingApi.Core.ColorsImpl;
+using PixiEditor.Extensions.Palettes;
 using PixiEditor.Models.DataHolders;
 using PixiEditor.Models.DataHolders.Palettes;
 using PixiEditor.Models.IO;
@@ -12,10 +13,6 @@ internal delegate void CacheUpdate(RefreshType refreshType, Palette itemAffected
 
 internal class LocalPalettesFetcher : PaletteListDataSource
 {
-    public static string PathToPalettesFolder { get; } = Path.Join(
-        Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData),
-        "PixiEditor", "Palettes");
-
     private List<Palette> cachedPalettes;
 
     public event CacheUpdate CacheUpdated;
@@ -27,7 +24,7 @@ internal class LocalPalettesFetcher : PaletteListDataSource
     public override void Initialize()
     {
         InitDir();
-        watcher = new FileSystemWatcher(PathToPalettesFolder);
+        watcher = new FileSystemWatcher(Paths.PathToPalettesFolder);
         watcher.Filter = "*.pal";
         watcher.Changed += FileSystemChanged;
         watcher.Deleted += FileSystemChanged;
@@ -78,21 +75,21 @@ internal class LocalPalettesFetcher : PaletteListDataSource
             finalFileName += ".pal";
         }
 
-        return File.Exists(Path.Join(PathToPalettesFolder, finalFileName));
+        return File.Exists(Path.Join(Paths.PathToPalettesFolder, finalFileName));
     }
 
     public static string GetNonExistingName(string currentName, bool appendExtension = false)
     {
         string newName = Path.GetFileNameWithoutExtension(currentName);
 
-        if (File.Exists(Path.Join(PathToPalettesFolder, newName + ".pal")))
+        if (File.Exists(Path.Join(Paths.PathToPalettesFolder, newName + ".pal")))
         {
             int number = 1;
             while (true)
             {
                 string potentialName = $"{newName} ({number})";
                 number++;
-                if (File.Exists(Path.Join(PathToPalettesFolder, potentialName + ".pal")))
+                if (File.Exists(Path.Join(Paths.PathToPalettesFolder, potentialName + ".pal")))
                     continue;
                 newName = potentialName;
                 break;
@@ -108,7 +105,7 @@ internal class LocalPalettesFetcher : PaletteListDataSource
     public async Task SavePalette(string fileName, Color[] colors)
     {
         watcher.EnableRaisingEvents = false;
-        string path = Path.Join(PathToPalettesFolder, fileName);
+        string path = Path.Join(Paths.PathToPalettesFolder, fileName);
         InitDir();
         await JascFileParser.SaveFile(path, new PaletteFileData(colors));
         watcher.EnableRaisingEvents = true;
@@ -118,8 +115,8 @@ internal class LocalPalettesFetcher : PaletteListDataSource
 
     public async Task DeletePalette(string name)
     {
-        if (!Directory.Exists(PathToPalettesFolder)) return;
-        string path = Path.Join(PathToPalettesFolder, name);
+        if (!Directory.Exists(Paths.PathToPalettesFolder)) return;
+        string path = Path.Join(Paths.PathToPalettesFolder, name);
         if (!File.Exists(path)) return;
 
         watcher.EnableRaisingEvents = false;
@@ -131,11 +128,11 @@ internal class LocalPalettesFetcher : PaletteListDataSource
 
     public void RenamePalette(string oldFileName, string newFileName)
     {
-        if (!Directory.Exists(PathToPalettesFolder))
+        if (!Directory.Exists(Paths.PathToPalettesFolder))
             return;
 
-        string oldPath = Path.Join(PathToPalettesFolder, oldFileName);
-        string newPath = Path.Join(PathToPalettesFolder, newFileName);
+        string oldPath = Path.Join(Paths.PathToPalettesFolder, oldFileName);
+        string newPath = Path.Join(Paths.PathToPalettesFolder, newFileName);
         if (!File.Exists(oldPath) || File.Exists(newPath))
             return;
 
@@ -149,7 +146,7 @@ internal class LocalPalettesFetcher : PaletteListDataSource
     public async Task RefreshCacheAll()
     {
         string[] files = DirectoryExtensions.GetFiles(
-            PathToPalettesFolder,
+            Paths.PathToPalettesFolder,
             string.Join("|", AvailableParsers.SelectMany(x => x.SupportedFileExtensions)),
             SearchOption.TopDirectoryOnly);
         cachedPalettes = await ParseAll(files);
@@ -321,7 +318,7 @@ internal class LocalPalettesFetcher : PaletteListDataSource
     {
         var palette = new Palette(
             fileData.Title,
-            new List<string>(fileData.GetHexColors()),
+            new List<PaletteColor>(fileData.GetPaletteColors()),
             Path.GetFileName(file))
         {
             IsFavourite = isFavourite
@@ -337,9 +334,9 @@ internal class LocalPalettesFetcher : PaletteListDataSource
 
     private static void InitDir()
     {
-        if (!Directory.Exists(PathToPalettesFolder))
+        if (!Directory.Exists(Paths.PathToPalettesFolder))
         {
-            Directory.CreateDirectory(PathToPalettesFolder);
+            Directory.CreateDirectory(Paths.PathToPalettesFolder);
         }
     }
 }

+ 2 - 1
src/PixiEditor/Models/DataProviders/PaletteListDataSource.cs

@@ -1,4 +1,5 @@
-using PixiEditor.Models.DataHolders.Palettes;
+using PixiEditor.Extensions.Palettes;
+using PixiEditor.Models.DataHolders.Palettes;
 using PixiEditor.Models.IO;
 
 namespace PixiEditor.Models.DataProviders;

+ 4 - 3
src/PixiEditor/Models/ExternalServices/LospecPaletteFetcher.cs

@@ -1,6 +1,7 @@
 using System.Net;
 using System.Net.Http;
 using Newtonsoft.Json;
+using PixiEditor.Extensions.Palettes;
 using PixiEditor.Models.DataHolders.Palettes;
 using PixiEditor.Models.Dialogs;
 
@@ -21,14 +22,14 @@ internal static class LospecPaletteFetcher
             if (response.StatusCode == HttpStatusCode.OK)
             {
                 string content = await response.Content.ReadAsStringAsync();
-                var obj = JsonConvert.DeserializeObject<Palette>(content);
+                var obj = JsonConvert.DeserializeObject<PaletteObject>(content);
 
-                if (obj is { Colors: { } })
+                if (obj is { Colors: not null })
                 {
                     ReadjustColors(obj.Colors);
                 }
 
-                return obj;
+                return obj.ToPalette();
             }
         }
         catch (HttpRequestException)

+ 6 - 3
src/PixiEditor/Models/IO/PaletteFileData.cs

@@ -1,4 +1,5 @@
 using PixiEditor.DrawingApi.Core.ColorsImpl;
+using PixiEditor.Extensions.Palettes;
 
 namespace PixiEditor.Models.IO;
 
@@ -32,13 +33,15 @@ internal class PaletteFileData
         Colors = colors;
     }
 
-    public string[] GetHexColors()
+    public PaletteColor[] GetPaletteColors()
     {
-        string[] colors = new string[Colors.Length];
+        PaletteColor[] colors = new PaletteColor[Colors.Length];
         for (int i = 0; i < Colors.Length; i++)
         {
-            colors[i] = Colors[i].ToString();
+            Color color = Colors[i];
+            colors[i] = new PaletteColor(color.R, color.G, color.B);
         }
+
         return colors;
     }
 }

+ 5 - 2
src/PixiEditor/Models/IO/Paths.cs

@@ -4,6 +4,9 @@ using System.Reflection;
 namespace PixiEditor.Models.IO;
 public static class Paths
 {
-    public static string DataFullPath = Path.Combine(Path.GetDirectoryName(Assembly.GetEntryAssembly().Location), "Data");
-    public static string ExtensionsFullPath = Path.Combine(Path.GetDirectoryName(Assembly.GetEntryAssembly().Location), "Extensions");
+    public static string DataFullPath { get; } = Path.Combine(Path.GetDirectoryName(Assembly.GetEntryAssembly().Location), "Data");
+    public static string ExtensionsFullPath { get; } = Path.Combine(Path.GetDirectoryName(Assembly.GetEntryAssembly().Location), "Extensions");
+    public static string PathToPalettesFolder { get; } = Path.Join(
+        Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData),
+        "PixiEditor", "Palettes");
 }

+ 7 - 6
src/PixiEditor/ViewModels/SubViewModels/Main/ColorsViewModel.cs

@@ -2,6 +2,7 @@
 using System.Windows.Input;
 using System.Windows.Media;
 using Microsoft.Extensions.DependencyInjection;
+using PixiEditor.Extensions.Palettes;
 using PixiEditor.Helpers;
 using PixiEditor.Models.Commands.XAML;
 using PixiEditor.Models.Controllers;
@@ -23,7 +24,7 @@ namespace PixiEditor.ViewModels.SubViewModels.Main;
 [Command.Group("PixiEditor.Colors", "PALETTE_COLORS")]
 internal class ColorsViewModel : SubViewModel<ViewModelMain>
 {
-    public RelayCommand<List<string>> ImportPaletteCommand { get; set; }
+    public RelayCommand<List<PaletteColor>> ImportPaletteCommand { get; set; }
 
     public WpfObservableRangeCollection<PaletteFileParser> PaletteParsers { get; private set; }
     public WpfObservableRangeCollection<PaletteListDataSource> PaletteDataSources { get; private set; }
@@ -65,7 +66,7 @@ internal class ColorsViewModel : SubViewModel<ViewModelMain>
     public ColorsViewModel(ViewModelMain owner)
         : base(owner)
     {
-        ImportPaletteCommand = new RelayCommand<List<string>>(ImportPalette, CanImportPalette);
+        ImportPaletteCommand = new RelayCommand<List<PaletteColor>>(ImportPalette, CanImportPalette);
         Owner.OnStartupEvent += OwnerOnStartupEvent;
     }
 
@@ -185,7 +186,7 @@ internal class ColorsViewModel : SubViewModel<ViewModelMain>
 
         await LocalPaletteFetcher.SavePalette(
             palette.FileName,
-            palette.Colors.Select(Color.Parse).ToArray());
+            palette.Colors.Select(x => new Color(x.R, x.G, x.B)).ToArray());
 
         await browser.UpdatePaletteList();
         if (browser.SortedResults.Any(x => x.FileName == palette.FileName))
@@ -201,13 +202,13 @@ internal class ColorsViewModel : SubViewModel<ViewModelMain>
     }
 
     [Evaluator.CanExecute("PixiEditor.Colors.CanImportPalette")]
-    public bool CanImportPalette(List<string> paletteColors)
+    public bool CanImportPalette(List<PaletteColor> paletteColors)
     {
         return paletteColors is not null && Owner.DocumentIsNotNull(paletteColors) && paletteColors.Count > 0;
     }
 
     [Command.Internal("PixiEditor.Colors.ImportPalette", CanExecute = "PixiEditor.Colors.CanImportPalette")]
-    public void ImportPalette(List<string> palette)
+    public void ImportPalette(List<PaletteColor> palette)
     {
         var doc = Owner.DocumentManagerSubViewModel.ActiveDocument;
         if (doc is null)
@@ -220,7 +221,7 @@ internal class ColorsViewModel : SubViewModel<ViewModelMain>
                 doc.Palette = new WpfObservableRangeCollection<DrawingApi.Core.ColorsImpl.Color>();
             }
 
-            doc.Palette.ReplaceRange(palette.Select(Color.Parse));
+            doc.Palette.ReplaceRange(palette.Select(x => new Color(x.R, x.G, x.B)));
         }
     }
 

+ 24 - 0
src/PixiEditor/ViewModels/SubViewModels/Main/ExtensionsViewModel.cs

@@ -0,0 +1,24 @@
+using Microsoft.Extensions.DependencyInjection;
+using PixiEditor.Extensions;
+using PixiEditor.Models.AppExtensions;
+
+namespace PixiEditor.ViewModels.SubViewModels.Main;
+
+internal class ExtensionsViewModel : SubViewModel<ViewModelMain>
+{
+    public ExtensionLoader ExtensionLoader { get; }
+    public ExtensionsViewModel(ViewModelMain owner) : base(owner)
+    {
+        var services = new ServiceCollection().AddExtensionServices(owner.ColorsSubViewModel.PaletteDataSources.ToList()).BuildServiceProvider();
+        ExtensionLoader loader = new ExtensionLoader(new ExtensionServices(services));
+        loader.LoadExtensions();
+
+        ExtensionLoader = loader;
+        Owner.OnStartupEvent += Owner_OnStartupEvent;
+    }
+
+    private void Owner_OnStartupEvent(object sender, EventArgs e)
+    {
+        ExtensionLoader.InitializeExtensions();
+    }
+}

+ 4 - 0
src/PixiEditor/ViewModels/ViewModelMain.cs

@@ -75,6 +75,8 @@ internal class ViewModelMain : ViewModelBase
 
     public AdditionalContentViewModel AdditionalContentSubViewModel { get; set; }
 
+    public ExtensionsViewModel ExtensionsSubViewModel { get; set; }
+
     public IPreferences Preferences { get; set; }
     public ILocalizationProvider LocalizationProvider { get; set; }
 
@@ -155,6 +157,8 @@ internal class ViewModelMain : ViewModelBase
 
         SearchSubViewModel = services.GetService<SearchViewModel>();
 
+        ExtensionsSubViewModel = services.GetService<ExtensionsViewModel>(); // Must be last
+
         DocumentManagerSubViewModel.ActiveDocumentChanged += OnActiveDocumentChanged;
     }
 

+ 6 - 5
src/PixiEditor/Views/Dialogs/PalettesBrowser.xaml.cs

@@ -6,6 +6,7 @@ using System.Windows.Input;
 using System.Windows.Navigation;
 using Microsoft.Win32;
 using PixiEditor.DrawingApi.Core.ColorsImpl;
+using PixiEditor.Extensions.Palettes;
 using PixiEditor.Helpers;
 using PixiEditor.Models.DataHolders;
 using PixiEditor.Models.DataHolders.Palettes;
@@ -279,7 +280,7 @@ internal partial class PalettesBrowser : Window
     {
         if (palette == null) return;
 
-        string filePath = Path.Join(LocalPalettesFetcher.PathToPalettesFolder, palette.FileName);
+        string filePath = Path.Join(Paths.PathToPalettesFolder, palette.FileName);
         if (File.Exists(filePath))
         {
             if (ConfirmationDialog.Show("DELETE_PALETTE_CONFIRMATION", "WARNING") == ConfirmationType.Yes)
@@ -416,7 +417,7 @@ internal partial class PalettesBrowser : Window
 
     private bool CanToggleFavourite(Palette palette)
     {
-        return palette != null && palette.Colors.Count > 0;
+        return palette is { Colors.Count: > 0 };
     }
 
     private void Grid_MouseDown(object sender, MouseButtonEventArgs e)
@@ -473,11 +474,11 @@ internal partial class PalettesBrowser : Window
 
     private void OpenFolder_OnClick(object sender, RoutedEventArgs e)
     {
-        if (Directory.Exists(LocalPalettesFetcher.PathToPalettesFolder))
+        if (Directory.Exists(Paths.PathToPalettesFolder))
         {
             Process.Start(new ProcessStartInfo
             {
-                FileName = LocalPalettesFetcher.PathToPalettesFolder,
+                FileName = Paths.PathToPalettesFolder,
                 UseShellExecute = true,
                 Verb = "open"
             });
@@ -504,7 +505,7 @@ internal partial class PalettesBrowser : Window
         string oldFileName = $"{e.OldText}.pal";
 
         string finalNewName = LocalPalettesFetcher.GetNonExistingName($"{Palette.ReplaceInvalidChars(e.NewText)}.pal", true);
-        string newPath = Path.Join(LocalPalettesFetcher.PathToPalettesFolder, finalNewName);
+        string newPath = Path.Join(Paths.PathToPalettesFolder, finalNewName);
 
         if (newPath.Length > 250)
         {

+ 1 - 1
src/PixiEditor/Views/UserControls/Palettes/PaletteItem.xaml

@@ -46,7 +46,7 @@
             </ItemsControl.ItemsPanel>
             <ItemsControl.ItemTemplate>
                 <DataTemplate>
-                    <Rectangle Fill="{Binding}" ToolTip="{Binding}" Width="30" Height="30"/>
+                    <Rectangle Fill="{Binding Hex}" ToolTip="{Binding}" Width="30" Height="30"/>
                 </DataTemplate>
             </ItemsControl.ItemTemplate>
         </ItemsControl>

+ 3 - 0
src/SampleExtension/SampleExtension.cs

@@ -1,5 +1,6 @@
 using System.Windows.Controls;
 using PixiEditor.Extensions;
+using PixiEditor.Extensions.Palettes;
 
 namespace SampleExtension;
 
@@ -13,5 +14,7 @@ public class SampleExtension : Extension
     {
         var popup = Api.WindowProvider.CreatePopupWindow("Hello World!", new TextBlock { Text = "Hello World!" });
         popup.ShowDialog();
+
+        Api.PaletteProvider.AddPalette(new ExtensionPalette("Sample Palette", new List<PaletteColor>(){new PaletteColor(255, 125, 75)}));
     }
 }