Просмотр исходного кода

Switched from ReactiveUI to MVVMToolkit

Krzysztof Krysiński 2 лет назад
Родитель
Сommit
e4ac762ef4
23 измененных файлов с 106 добавлено и 160 удалено
  1. 0 2
      src/PixiEditor.Avalonia/PixiEditor.Avalonia.Browser/Program.cs
  2. 1 3
      src/PixiEditor.Avalonia/PixiEditor.Avalonia.Desktop/Program.cs
  3. 2 0
      src/PixiEditor.Avalonia/PixiEditor.Avalonia/App.axaml
  4. 0 4
      src/PixiEditor.Avalonia/PixiEditor.Avalonia/App.axaml.cs
  5. 1 1
      src/PixiEditor.Avalonia/PixiEditor.Avalonia/Initialization/ClassicDesktopEntry.cs
  6. 3 3
      src/PixiEditor.Avalonia/PixiEditor.Avalonia/Models/Commands/CommandGroup.cs
  7. 5 9
      src/PixiEditor.Avalonia/PixiEditor.Avalonia/Models/Commands/Commands/Command.cs
  8. 1 2
      src/PixiEditor.Avalonia/PixiEditor.Avalonia/Models/Commands/Search/ColorSearchResult.cs
  9. 1 2
      src/PixiEditor.Avalonia/PixiEditor.Avalonia/Models/Commands/Search/CommandSearchResult.cs
  10. 1 3
      src/PixiEditor.Avalonia/PixiEditor.Avalonia/Models/Commands/Search/FileSearchResult.cs
  11. 9 9
      src/PixiEditor.Avalonia/PixiEditor.Avalonia/Models/Commands/Search/SearchResult.cs
  12. 10 50
      src/PixiEditor.Avalonia/PixiEditor.Avalonia/Models/Commands/XAML/Command.cs
  13. 7 7
      src/PixiEditor.Avalonia/PixiEditor.Avalonia/Models/Commands/XAML/Menu.cs
  14. 7 10
      src/PixiEditor.Avalonia/PixiEditor.Avalonia/Models/UserData/RecentlyOpenedDocument.cs
  15. 1 1
      src/PixiEditor.Avalonia/PixiEditor.Avalonia/PixiEditor.Avalonia.csproj
  16. 9 0
      src/PixiEditor.Avalonia/PixiEditor.Avalonia/Styles/CommandsMenu.axaml
  17. 11 0
      src/PixiEditor.Avalonia/PixiEditor.Avalonia/Styles/PixiEditor.Controls.axaml
  18. 9 26
      src/PixiEditor.Avalonia/PixiEditor.Avalonia/ViewModels/CrashReportViewModel.cs
  19. 4 6
      src/PixiEditor.Avalonia/PixiEditor.Avalonia/ViewModels/MainViewModel.cs
  20. 15 19
      src/PixiEditor.Avalonia/PixiEditor.Avalonia/ViewModels/SystemCommands.cs
  21. 2 2
      src/PixiEditor.Avalonia/PixiEditor.Avalonia/ViewModels/ViewModelBase.cs
  22. 2 1
      src/PixiEditor.Avalonia/PixiEditor.Avalonia/Views/Main/MainTitleBar.axaml
  23. 5 0
      src/PixiEditor.Avalonia/PixiEditor.Avalonia/Views/MainView.axaml

+ 0 - 2
src/PixiEditor.Avalonia/PixiEditor.Avalonia.Browser/Program.cs

@@ -2,7 +2,6 @@
 using System.Threading.Tasks;
 using Avalonia;
 using Avalonia.Browser;
-using Avalonia.ReactiveUI;
 using PixiEditor.Avalonia;
 
 [assembly: SupportedOSPlatform("browser")]
@@ -11,7 +10,6 @@ internal partial class Program
 {
     private static async Task Main(string[] args) => await BuildAvaloniaApp()
         .WithInterFont()
-        .UseReactiveUI()
         .StartBrowserAppAsync("out");
 
     public static AppBuilder BuildAvaloniaApp()

+ 1 - 3
src/PixiEditor.Avalonia/PixiEditor.Avalonia.Desktop/Program.cs

@@ -1,6 +1,5 @@
 using System;
 using Avalonia;
-using Avalonia.ReactiveUI;
 
 namespace PixiEditor.Avalonia.Desktop;
 
@@ -18,6 +17,5 @@ class Program
         => AppBuilder.Configure<App>()
             .UsePlatformDetect()
             .WithInterFont()
-            .LogToTrace()
-            .UseReactiveUI();
+            .LogToTrace();
 }

+ 2 - 0
src/PixiEditor.Avalonia/PixiEditor.Avalonia/App.axaml

@@ -7,5 +7,7 @@
 
     <Application.Styles>
         <themes:PixiEditorTheme />
+        <StyleInclude Source="/Styles/PixiEditor.Controls.axaml"/>
     </Application.Styles>
+
 </Application>

+ 0 - 4
src/PixiEditor.Avalonia/PixiEditor.Avalonia/App.axaml.cs

@@ -1,11 +1,7 @@
 using Avalonia;
 using Avalonia.Controls.ApplicationLifetimes;
 using Avalonia.Markup.Xaml;
-using PixiEditor.Avalonia.ViewModels;
-using PixiEditor.Avalonia.Views;
 using PixiEditor.Initialization;
-using PixiEditor.UI.Common.Themes;
-using Splat;
 
 namespace PixiEditor.Avalonia;
 

+ 1 - 1
src/PixiEditor.Avalonia/PixiEditor.Avalonia/Initialization/ClassicDesktopEntry.cs

@@ -123,7 +123,7 @@ internal class ClassicDesktopEntry
                                     StartupArgs.Args = args;
                                     StartupArgs.Args.Add("--openedInExisting");
                                     MainViewModel viewModel = (MainViewModel)mainWindow.DataContext;
-                                    viewModel.OnStartupCommand.Execute();
+                                    viewModel.StartupCommand.Execute(null);
                                 }
                             }));
                     }

+ 3 - 3
src/PixiEditor.Avalonia/PixiEditor.Avalonia/Models/Commands/CommandGroup.cs

@@ -1,14 +1,14 @@
 using System.Collections.Generic;
 using System.Linq;
 using Avalonia.Input;
+using CommunityToolkit.Mvvm.ComponentModel;
 using PixiEditor.Extensions.Common.Localization;
 using PixiEditor.Models.Commands.Commands;
 using PixiEditor.Models.DataHolders;
-using ReactiveUI;
 
 namespace PixiEditor.Models.Commands;
 
-internal class CommandGroup : ReactiveObject
+internal class CommandGroup : ObservableObject
 {
     private readonly Command[] commands;
     private readonly Command[] visibleCommands;
@@ -18,7 +18,7 @@ internal class CommandGroup : ReactiveObject
     public LocalizedString DisplayName
     {
         get => displayName;
-        set => this.RaiseAndSetIfChanged(ref displayName, value);
+        set => SetProperty(ref displayName, value);
     }
 
     public bool HasAssignedShortcuts { get; set; }

+ 5 - 9
src/PixiEditor.Avalonia/PixiEditor.Avalonia/Models/Commands/Commands/Command.cs

@@ -1,17 +1,14 @@
 using System.Diagnostics;
-using System.Windows.Input;
-using System.Windows.Media;
 using Avalonia.Media;
+using CommunityToolkit.Mvvm.ComponentModel;
 using PixiEditor.Extensions.Common.Localization;
 using PixiEditor.Models.Commands.Evaluators;
 using PixiEditor.Models.DataHolders;
-using PixiEditor.Models.Localization;
-using ReactiveUI;
 
 namespace PixiEditor.Models.Commands.Commands;
 
 [DebuggerDisplay("{InternalName,nq} ('{DisplayName,nq}')")]
-internal abstract partial class Command : ReactiveObject
+internal abstract partial class Command : ObservableObject
 {
     private KeyCombination _shortcut;
 
@@ -37,8 +34,7 @@ internal abstract partial class Command : ReactiveObject
         set
         {
             var oldValue = _shortcut;
-            var combination = this.RaiseAndSetIfChanged(ref _shortcut, value);
-            if (combination != oldValue)
+            if (SetProperty(ref _shortcut, value))
             {
                 ShortcutChanged?.Invoke(this, new(oldValue, value));
             }
@@ -61,8 +57,8 @@ internal abstract partial class Command : ReactiveObject
         DisplayName = new LocalizedString(DisplayName.Key, DisplayName.Parameters);
         Description = new LocalizedString(Description.Key, Description.Parameters);
 
-        this.RaisePropertyChanged(nameof(DisplayName));
-        this.RaisePropertyChanged(nameof(Description));
+        OnPropertyChanged(nameof(DisplayName));
+        OnPropertyChanged(nameof(Description));
     }
 
     public void Execute() => Methods.Execute(GetParameter());

+ 1 - 2
src/PixiEditor.Avalonia/PixiEditor.Avalonia/Models/Commands/Search/ColorSearchResult.cs

@@ -31,10 +31,9 @@ internal class ColorSearchResult : SearchResult
 
     public override IImage Icon => icon;
 
-    public override Task Execute()
+    public override void Execute()
     {
         target(color);
-        return Task.CompletedTask;
     }
 
     private ColorSearchResult(DrawingApi.Core.ColorsImpl.Color color, Action<DrawingApi.Core.ColorsImpl.Color> target)

+ 1 - 2
src/PixiEditor.Avalonia/PixiEditor.Avalonia/Models/Commands/Search/CommandSearchResult.cs

@@ -20,9 +20,8 @@ internal class CommandSearchResult : SearchResult
 
     public CommandSearchResult(Command command) => Command = command;
 
-    public override Task Execute()
+    public override void Execute()
     {
         Command.Execute();
-        return Task.CompletedTask;
     }
 }

+ 1 - 3
src/PixiEditor.Avalonia/PixiEditor.Avalonia/Models/Commands/Search/FileSearchResult.cs

@@ -33,7 +33,7 @@ internal class FileSearchResult : SearchResult
         this.asReferenceLayer = asReferenceLayer;
     }
 
-    public override Task Execute()
+    public override void Execute()
     {
         if (!asReferenceLayer)
         {
@@ -48,7 +48,5 @@ internal class FileSearchResult : SearchResult
                     .Execute(FilePath);
             }
         }
-
-        return Task.CompletedTask;
     }
 }

+ 9 - 9
src/PixiEditor.Avalonia/PixiEditor.Avalonia/Models/Commands/Search/SearchResult.cs

@@ -1,17 +1,17 @@
 using System.Collections.Generic;
 using System.Linq;
 using System.Text.RegularExpressions;
-using System.Threading.Tasks;
+using System.Windows.Input;
 using Avalonia;
 using Avalonia.Controls.Documents;
 using Avalonia.Media;
-using Avalonia.Media.Imaging;
+using CommunityToolkit.Mvvm.ComponentModel;
+using CommunityToolkit.Mvvm.Input;
 using PixiEditor.Models.DataHolders;
-using ReactiveUI;
 
 namespace PixiEditor.Models.Commands.Search;
 
-internal abstract class SearchResult : ReactiveObject
+internal abstract class SearchResult : ObservableObject
 {
     private bool isSelected;
     private bool isMouseSelected;
@@ -33,25 +33,25 @@ internal abstract class SearchResult : ReactiveObject
     public bool IsSelected
     {
         get => isSelected;
-        set => this.RaiseAndSetIfChanged(ref isSelected, value);
+        set => SetProperty(ref isSelected, value);
     }
 
     public bool IsMouseSelected
     {
         get => isMouseSelected;
-        set => this.RaiseAndSetIfChanged(ref isMouseSelected, value);
+        set => SetProperty(ref isMouseSelected, value);
     }
 
 
-    public abstract Task Execute();
+    public abstract void Execute();
 
     public virtual KeyCombination Shortcut { get; }
 
-    public IReactiveCommand ExecuteCommand { get; }
+    public ICommand ExecuteCommand { get; }
 
     public SearchResult()
     {
-        ExecuteCommand = ReactiveCommand.CreateFromTask(_ => Execute(), this.WhenAnyValue(x => CanExecute));
+        ExecuteCommand = new RelayCommand(Execute, () => CanExecute);
     }
 
     private IEnumerable<Inline> GetInlines()

+ 10 - 50
src/PixiEditor.Avalonia/PixiEditor.Avalonia/Models/Commands/XAML/Command.cs

@@ -1,11 +1,7 @@
-using System.Reactive;
-using System.Reactive.Disposables;
-using System.Reactive.Linq;
+using System.Windows.Input;
 using Avalonia.Controls;
 using Avalonia.Markup.Xaml;
-using Microsoft.Extensions.DependencyInjection;
 using PixiEditor.Helpers;
-using ReactiveUI;
 
 namespace PixiEditor.Models.Commands.XAML;
 
@@ -48,57 +44,28 @@ internal class Command : MarkupExtension
         return GetPixiCommand ? command : GetICommand(command, UseProvided);
     }
 
-    public static IReactiveCommand GetICommand(Commands.Command command, bool useProvidedParameter) => new ProvidedICommand()
+    public static ICommand GetICommand(Commands.Command command, bool useProvidedParameter) => new ProvidedICommand()
     {
         Command = command,
         UseProvidedParameter = useProvidedParameter,
     };
 
-    class ProvidedICommand : IReactiveCommand
+    class ProvidedICommand : ICommand
     {
         //TODO: Not found in Avalonia
-
-        /*public event EventHandler CanExecuteChanged
-        {
-            add => CommandManager.RequerySuggested += value;
-            remove => CommandManager.RequerySuggested -= value;
-        }*/
+        public event EventHandler? CanExecuteChanged;
+        /* {
+             add => CommandManager.RequerySuggested += value;
+             remove => CommandManager.RequerySuggested -= value;
+         }*/
 
         public Commands.Command Command { get; init; }
 
         public bool UseProvidedParameter { get; init; }
 
-        public IObservable<Exception> ThrownExceptions { get; }
-
-        public IObservable<bool> IsExecuting { get; }
-
-        public IObservable<bool> CanExecute { get; }
-
-        public ProvidedICommand()
-        {
-            ReactiveCommand<object, Unit> reactiveCommand = ReactiveCommand.Create<object, Unit>(Execute, CanExecuteCommand());
-        }
-
+        public bool CanExecute(object parameter) => UseProvidedParameter ? Command.Methods.CanExecute(parameter) : Command.CanExecute();
 
-        public IObservable<bool> CanExecuteCommand()
-        {
-            return this.WhenAnyValue(x => x.Command, x => x.UseProvidedParameter, (command, useProvidedParameter) =>
-            {
-                return true;
-                /*if (useProvidedParameter)
-                {
-                    return command.CanExecute();
-                    //TODO: return command.CanExecute(parameter); // Should be this, but idk how to make it properly, I think whole logic should be changed so it fits
-                    // reactiveUI
-                }
-                else
-                {
-                    return command.CanExecute();
-                }*/
-            });
-        }
-
-        public Unit Execute(object parameter)
+        public void Execute(object parameter)
         {
             if (UseProvidedParameter)
             {
@@ -108,13 +75,6 @@ internal class Command : MarkupExtension
             {
                 Command.Execute();
             }
-
-            return Unit.Default;
-        }
-
-        public void Dispose()
-        {
-
         }
     }
 }

+ 7 - 7
src/PixiEditor.Avalonia/PixiEditor.Avalonia/Models/Commands/XAML/Menu.cs

@@ -2,7 +2,6 @@
 using Avalonia.Controls;
 using PixiEditor.Helpers;
 using PixiEditor.Models.DataHolders;
-using ReactiveUI;
 
 namespace PixiEditor.Models.Commands.XAML;
 
@@ -42,14 +41,15 @@ internal class Menu : global::Avalonia.Controls.Menu
             Opacity = command.CanExecute() ? 1 : 0.75
         };
 
-        icon.IsVisible.WhenAnyValue(v => v).Subscribe(newValue =>
+        icon.PropertyChanged += (sender, args) =>
         {
-            icon.Opacity = command.CanExecute() ? 1 : 0.75;
-
-        });
+            if (args.Property.Name == nameof(icon.IsVisible))
+            {
+                icon.Opacity = command.CanExecute() ? 1 : 0.75;
+            }
+        };
 
-        //TODO: This, some ReactiveUI shit should be here, https://docs.avaloniaui.net/docs/next/concepts/reactiveui/reactive-command
-        //item.Command = Command.GetICommand(command, false);
+        item.Command = Command.GetICommand(command, false);
         item.Icon = icon;
         item.Bind(MenuItem.InputGestureProperty, ShortcutBinding.GetBinding(command, null));
     }

+ 7 - 10
src/PixiEditor.Avalonia/PixiEditor.Avalonia/Models/UserData/RecentlyOpenedDocument.cs

@@ -1,18 +1,15 @@
 using System.Diagnostics;
 using System.IO;
-using System.Linq;
-using ChunkyImageLib;
+using CommunityToolkit.Mvvm.ComponentModel;
 using PixiEditor.Avalonia.Exceptions.Exceptions;
 using PixiEditor.Parser.Deprecated;
-using PixiEditor.DrawingApi.Core.Numerics;
 using PixiEditor.Helpers;
 using PixiEditor.Parser;
-using ReactiveUI;
 
 namespace PixiEditor.Models.DataHolders;
 
 [DebuggerDisplay("{FilePath}")]
-internal class RecentlyOpenedDocument : ReactiveObject
+internal class RecentlyOpenedDocument : ObservableObject
 {
     private bool corrupt;
 
@@ -25,9 +22,9 @@ internal class RecentlyOpenedDocument : ReactiveObject
         get => filePath;
         set
         {
-            this.RaiseAndSetIfChanged(ref filePath, value);
-            this.RaisePropertyChanged(nameof(FileName));
-            this.RaisePropertyChanged(nameof(FileExtension));
+            SetProperty(ref filePath, value);
+            this.OnPropertyChanged(nameof(FileName));
+            this.OnPropertyChanged(nameof(FileExtension));
             PreviewBitmap = null;
         }
     }
@@ -35,7 +32,7 @@ internal class RecentlyOpenedDocument : ReactiveObject
     public bool Corrupt
     {
         get => corrupt;
-        set => this.RaiseAndSetIfChanged(ref corrupt, value);
+        set => SetProperty(ref corrupt, value);
     }
 
     public string FileName => Path.GetFileNameWithoutExtension(filePath);
@@ -70,7 +67,7 @@ internal class RecentlyOpenedDocument : ReactiveObject
 
             return previewBitmap;
         }
-        private set => this.RaiseAndSetIfChanged(ref previewBitmap, value);
+        private set => SetProperty(ref previewBitmap, value);
     }
 
     public RecentlyOpenedDocument(string path)

+ 1 - 1
src/PixiEditor.Avalonia/PixiEditor.Avalonia/PixiEditor.Avalonia.csproj

@@ -16,10 +16,10 @@
         <PackageReference Include="Avalonia" Version="$(AvaloniaVersion)" />
         <PackageReference Include="Avalonia.Themes.Fluent" Version="$(AvaloniaVersion)" />
         <PackageReference Include="Avalonia.Fonts.Inter" Version="$(AvaloniaVersion)" />
-        <PackageReference Include="Avalonia.ReactiveUI" Version="$(AvaloniaVersion)" />
         <!--Condition below is needed to remove Avalonia.Diagnostics package from build output in Release configuration.-->
         <PackageReference Condition="'$(Configuration)' == 'Debug'" Include="Avalonia.Diagnostics" Version="$(AvaloniaVersion)" />
         <PackageReference Include="ByteSize" Version="2.1.1" />
+        <PackageReference Include="CommunityToolkit.Mvvm" Version="8.2.1" />
         <PackageReference Include="Dock.Avalonia" Version="11.0.0" />
         <PackageReference Include="Dock.Model.Avalonia" Version="11.0.0" />
         <PackageReference Include="Hardware.Info" Version="11.0.0" />

+ 9 - 0
src/PixiEditor.Avalonia/PixiEditor.Avalonia/Styles/CommandsMenu.axaml

@@ -0,0 +1,9 @@
+<ResourceDictionary xmlns="https://github.com/avaloniaui"
+                    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
+                    xmlns:sys="using:System"
+                    xmlns:xaml="clr-namespace:PixiEditor.Models.Commands.XAML">
+
+    <ControlTheme x:Key="{x:Type xaml:Menu}"
+                  TargetType="xaml:Menu" BasedOn="{StaticResource {x:Type Menu}}"/>
+
+</ResourceDictionary>

+ 11 - 0
src/PixiEditor.Avalonia/PixiEditor.Avalonia/Styles/PixiEditor.Controls.axaml

@@ -0,0 +1,11 @@
+<Styles xmlns="https://github.com/avaloniaui"
+        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
+
+    <Styles.Resources>
+        <ResourceDictionary>
+            <ResourceDictionary.MergedDictionaries>
+                <MergeResourceInclude Source="avares://PixiEditor.Avalonia/Styles/CommandsMenu.axaml"/>
+            </ResourceDictionary.MergedDictionaries>
+        </ResourceDictionary>
+    </Styles.Resources>
+</Styles>

+ 9 - 26
src/PixiEditor.Avalonia/PixiEditor.Avalonia/ViewModels/CrashReportViewModel.cs

@@ -1,25 +1,15 @@
 using System.Diagnostics;
-using System.IO;
-using System.Net.Http;
-using System.Reactive;
-using System.Reactive.Disposables;
-using System.Reactive.Linq;
-using System.Text;
-using System.Windows;
-using System.Windows.Media;
 using Avalonia;
 using Avalonia.Controls;
+using CommunityToolkit.Mvvm.Input;
 using PixiEditor.Avalonia.ViewModels;
 using PixiEditor.Avalonia.Views;
 using PixiEditor.Helpers;
 using PixiEditor.Models.DataHolders;
-using PixiEditor.Views;
-using PixiEditor.Views.Dialogs;
-using ReactiveUI;
 
 namespace PixiEditor.ViewModels;
 
-internal class CrashReportViewModel : ViewModelBase
+internal partial class CrashReportViewModel : ViewModelBase
 {
     private bool hasRecoveredDocuments = true;
 
@@ -29,12 +19,6 @@ internal class CrashReportViewModel : ViewModelBase
 
     public int DocumentCount { get; }
 
-    public ReactiveCommand<Unit, Unit> OpenSendCrashReportCommand { get; }
-
-    public ReactiveCommand<Unit, Unit> RecoverDocumentsCommand { get; }
-
-    public ReactiveCommand<Unit, Unit> AttachDebuggerCommand { get; }
-
     public bool IsDebugBuild { get; set; }
 
     public CrashReportViewModel(CrashReport report)
@@ -46,19 +30,12 @@ internal class CrashReportViewModel : ViewModelBase
         DocumentCount = report.GetDocumentCount();
         //TODO: Implement
         //OpenSendCrashReportCommand = ReactiveCommand.Create(() => new SendCrashReportWindow(CrashReport).Show());
-        RecoverDocumentsCommand = ReactiveCommand.Create(RecoverDocuments, Observable.Create((IObserver<bool> observer) =>
-        {
-            observer.OnNext(hasRecoveredDocuments);
-            observer.OnCompleted();
-            return Disposable.Empty;
-        }));
-
-        AttachDebuggerCommand = ReactiveCommand.Create(AttachDebugger);
 
         if (!IsDebugBuild)
             _ = CrashHelper.SendReportTextToWebhook(report);
     }
 
+    [RelayCommand(CanExecute = nameof(CanRecoverDocuments))]
     public void RecoverDocuments()
     {
         MainWindow window = MainWindow.CreateWithDocuments(CrashReport.RecoverDocuments());
@@ -68,12 +45,18 @@ internal class CrashReportViewModel : ViewModelBase
         hasRecoveredDocuments = false;
     }
 
+    public bool CanRecoverDocuments()
+    {
+        return hasRecoveredDocuments;
+    }
+
     [Conditional("DEBUG")]
     private void SetIsDebug()
     {
         IsDebugBuild = true;
     }
 
+    [RelayCommand]
     private void AttachDebugger()
     {
         if (!Debugger.Launch())

+ 4 - 6
src/PixiEditor.Avalonia/PixiEditor.Avalonia/ViewModels/MainViewModel.cs

@@ -1,23 +1,20 @@
 using System.Reactive;
+using CommunityToolkit.Mvvm.Input;
 using Microsoft.Extensions.DependencyInjection;
 using PixiEditor.Extensions.Common.Localization;
 using PixiEditor.Extensions.Common.UserPreferences;
 using PixiEditor.Helpers.Extensions;
 using PixiEditor.Models.Commands;
-using PixiEditor.Models.Localization;
-using PixiEditor.Platform;
 using PixiEditor.ViewModels.SubViewModels;
-using ReactiveUI;
 
 namespace PixiEditor.Avalonia.ViewModels;
 
-internal class MainViewModel : ViewModelBase
+internal partial class MainViewModel : ViewModelBase
 {
     public event Action OnStartupEvent;
 
     public IServiceProvider Services { get; set; }
     public CommandController CommandController { get; set; }
-    public ReactiveCommand<Unit, Unit> OnStartupCommand { get; }
     public ToolsViewModel ToolsViewModel { get; set; }
 
     public IPreferences Preferences { get; set; }
@@ -25,7 +22,7 @@ internal class MainViewModel : ViewModelBase
 
     public MainViewModel()
     {
-        OnStartupCommand = ReactiveCommand.Create(OnStartup);
+
     }
 
     public void Setup(IServiceProvider services)
@@ -44,6 +41,7 @@ internal class MainViewModel : ViewModelBase
         CommandController.Init(services);
     }
 
+    [RelayCommand]
     private void OnStartup()
     {
         OnStartupEvent?.Invoke();

+ 15 - 19
src/PixiEditor.Avalonia/PixiEditor.Avalonia/ViewModels/SystemCommands.cs

@@ -1,33 +1,29 @@
-using System.Reactive;
-using Avalonia.Controls;
-using ReactiveUI;
+using Avalonia.Controls;
+using Avalonia.Interactivity;
+using CommunityToolkit.Mvvm.Input;
 
 namespace PixiEditor.Avalonia.ViewModels;
 
 public static class SystemCommands
 {
-    public static ReactiveCommand<Window, Unit> CloseWindowCommand { get; } = ReactiveCommand.Create<Window>(CloseWindow);
-    public static ReactiveCommand<Window, Unit> MaximizeWindowCommand { get; } = ReactiveCommand.Create<Window>(MaximizeWindow);
-    public static ReactiveCommand<Window, Unit> MinimizeWindowCommand { get; } = ReactiveCommand.Create<Window>(MinimizeWindow);
-    public static ReactiveCommand<Window, Unit> RestoreWindowCommand { get; } = ReactiveCommand.Create<Window>(RestoreWindow);
+    public static RoutedEvent<RoutedEventArgs> CloseWindowEvent { get; }
+        = RoutedEvent.Register<Window, RoutedEventArgs>(nameof(CloseWindowEvent), RoutingStrategies.Bubble);
+    public static RoutedEvent<RoutedEventArgs> MaximizeWindowEvent { get; }
+        = RoutedEvent.Register<Window, RoutedEventArgs>(nameof(MaximizeWindowEvent), RoutingStrategies.Bubble);
+    public static RoutedEvent<RoutedEventArgs> MinimizeWindowEvent { get; }
+        = RoutedEvent.Register<Window, RoutedEventArgs>(nameof(MinimizeWindowEvent), RoutingStrategies.Bubble);
+    public static RoutedEvent<RoutedEventArgs> RestoreWindowEvent { get; }
+        = RoutedEvent.Register<Window, RoutedEventArgs>(nameof(RestoreWindowEvent), RoutingStrategies.Bubble);
 
-    public static void CloseWindow(Window window)
-    {
-        window.Close();
-    }
+    public static RelayCommand<Window> CloseWindowCommand { get; }
 
-    public static void MaximizeWindow(Window window)
+    static SystemCommands()
     {
-        window.WindowState = WindowState.Maximized;
+        CloseWindowCommand = new RelayCommand<Window>(CloseWindow);
     }
 
-    public static void MinimizeWindow(Window window)
+    private static void CloseWindow(Window? obj)
     {
-        window.WindowState = WindowState.Minimized;
-    }
 
-    public static void RestoreWindow(Window window)
-    {
-        window.WindowState = WindowState.Normal;
     }
 }

+ 2 - 2
src/PixiEditor.Avalonia/PixiEditor.Avalonia/ViewModels/ViewModelBase.cs

@@ -1,7 +1,7 @@
-using ReactiveUI;
+using CommunityToolkit.Mvvm.ComponentModel;
 
 namespace PixiEditor.Avalonia.ViewModels;
 
-public class ViewModelBase : ReactiveObject
+public class ViewModelBase : ObservableObject
 {
 }

+ 2 - 1
src/PixiEditor.Avalonia/PixiEditor.Avalonia/Views/Main/MainTitleBar.axaml

@@ -70,7 +70,8 @@
                         <Separator />
                         <MenuItem
                             ui:Translator.Key="EXIT"
-                            Command="{x:Static viewModels:SystemCommands.CloseWindowCommand}">
+                            Command="{x:Static viewModels:SystemCommands.CloseWindowCommand}"
+                            >
                             <MenuItem.Icon>
                                 <TextBlock Text="&#xE106;" FontFamily="{DynamicResource NativeIconFont}" FontSize="20"/>
                             </MenuItem.Icon>

+ 5 - 0
src/PixiEditor.Avalonia/PixiEditor.Avalonia/Views/MainView.axaml

@@ -4,10 +4,15 @@
              xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
              xmlns:vm="clr-namespace:PixiEditor.Avalonia.ViewModels"
              xmlns:main="clr-namespace:PixiEditor.Avalonia.Views.Main"
+             xmlns:xaml="clr-namespace:PixiEditor.Models.Commands.XAML"
              mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
              x:Class="PixiEditor.Avalonia.Views.MainView"
              x:DataType="vm:MainViewModel" Background="{DynamicResource ThemeBackgroundBrush}">
     <Grid>
+        <Grid.RowDefinitions>
+            <RowDefinition Height="25"/>
+            <RowDefinition Height="*"/>
+        </Grid.RowDefinitions>
         <main:MainTitleBar/>
       <DockControl x:Name="Dock" Grid.Row="1" InitializeLayout="True" InitializeFactory="True">
       <DockControl.Factory>