Browse Source

Merge branch 'master' into development

flabbet 9 months ago
parent
commit
e076257c17
51 changed files with 437 additions and 213 deletions
  1. 59 19
      README.md
  2. 1 1
      src/Drawie
  3. 3 2
      src/PixiEditor.ChangeableDocument/Changeables/Graph/Nodes/Filters.cs
  4. 6 5
      src/PixiEditor.ChangeableDocument/Changes/Drawing/LineBasedPen_UpdateableChange.cs
  5. 1 1
      src/PixiEditor.ChangeableDocument/Changes/Drawing/TransformSelected_UpdateableChange.cs
  6. 28 3
      src/PixiEditor.ChangeableDocument/DocumentChangeTracker.cs
  7. 3 2
      src/PixiEditor.Zoombox/Zoombox.cs
  8. 10 1
      src/PixiEditor/Data/Localization/Languages/en.json
  9. 17 0
      src/PixiEditor/Helpers/Converters/EnumToLocalizedStringConverter.cs
  10. 1 1
      src/PixiEditor/Helpers/Converters/ZoomToViewportConverter.cs
  11. 1 1
      src/PixiEditor/Models/Controllers/InputDevice/SnappingController.cs
  12. 1 1
      src/PixiEditor/Models/DocumentModels/DocumentUpdater.cs
  13. 14 3
      src/PixiEditor/Models/DocumentModels/UpdateableChangeExecutors/DrawableShapeToolExecutor.cs
  14. 6 0
      src/PixiEditor/Models/DocumentModels/UpdateableChangeExecutors/Features/IDelayedColorSwapFeature.cs
  15. 12 1
      src/PixiEditor/Models/DocumentModels/UpdateableChangeExecutors/LineExecutor.cs
  16. 6 1
      src/PixiEditor/Models/DocumentModels/UpdateableChangeExecutors/SimpleShapeToolExecutor.cs
  17. 1 1
      src/PixiEditor/Models/DocumentModels/UpdateableChangeExecutors/TransformReferenceLayerExecutor.cs
  18. 10 0
      src/PixiEditor/Models/DocumentModels/UpdateableChangeExecutors/VectorPathToolExecutor.cs
  19. 4 0
      src/PixiEditor/Models/ExceptionHandling/CrashReport.cs
  20. 3 3
      src/PixiEditor/Models/Handlers/IAnimationHandler.cs
  21. 9 0
      src/PixiEditor/Models/Handlers/ICelGroupHandler.cs
  22. 1 1
      src/PixiEditor/Models/Handlers/ICelHandler.cs
  23. 1 0
      src/PixiEditor/Models/Handlers/IColorsHandler.cs
  24. 0 9
      src/PixiEditor/Models/Handlers/IKeyFrameGroupHandler.cs
  25. 6 0
      src/PixiEditor/Models/Handlers/IRasterCelHandler.cs
  26. 0 6
      src/PixiEditor/Models/Handlers/IRasterKeyFrameHandler.cs
  27. 9 9
      src/PixiEditor/Models/Rendering/MemberPreviewUpdater.cs
  28. 2 2
      src/PixiEditor/Properties/AssemblyInfo.cs
  29. 3 3
      src/PixiEditor/Styles/Templates/Timeline.axaml
  30. 39 39
      src/PixiEditor/ViewModels/Document/AnimationDataViewModel.cs
  31. 6 6
      src/PixiEditor/ViewModels/Document/CelGroupViewModel.cs
  32. 3 3
      src/PixiEditor/ViewModels/Document/CelViewModel.cs
  33. 14 0
      src/PixiEditor/ViewModels/Document/IRasterCelViewModel.cs
  34. 8 8
      src/PixiEditor/ViewModels/Document/KeyFrameCollection.cs
  35. 0 14
      src/PixiEditor/ViewModels/Document/RasterKeyFrameViewModel.cs
  36. 1 0
      src/PixiEditor/ViewModels/Document/TransformOverlays/LineToolOverlayViewModel.cs
  37. 11 11
      src/PixiEditor/ViewModels/SubViewModels/AnimationsViewModel.cs
  38. 12 0
      src/PixiEditor/ViewModels/SubViewModels/ColorsViewModel.cs
  39. 29 12
      src/PixiEditor/ViewModels/SubViewModels/IoViewModel.cs
  40. 2 0
      src/PixiEditor/ViewModels/Tools/Tools/VectorPathToolViewModel.cs
  41. 3 3
      src/PixiEditor/Views/Animations/KeyFrame.cs
  42. 23 23
      src/PixiEditor/Views/Animations/Timeline.cs
  43. 2 2
      src/PixiEditor/Views/Animations/TimelineGroupHeader.cs
  44. 1 1
      src/PixiEditor/Views/Dock/TimelineDockView.axaml
  45. 2 2
      src/PixiEditor/Views/Main/ViewportControls/Viewport.axaml
  46. 4 1
      src/PixiEditor/Views/Main/ViewportControls/ViewportOverlays.cs
  47. 11 6
      src/PixiEditor/Views/Overlays/LineToolOverlay/LineToolOverlay.cs
  48. 11 0
      src/PixiEditor/Views/Overlays/Overlay.cs
  49. 29 4
      src/PixiEditor/Views/Rendering/Scene.cs
  50. 1 1
      src/PixiEditor/Views/Shortcuts/KeyCombinationBox.axaml.cs
  51. 7 1
      src/PixiEditor/Views/Windows/Settings/SettingsWindow.axaml

+ 59 - 19
README.md

@@ -1,8 +1,8 @@
-<img src="https://user-images.githubusercontent.com/25402427/102633463-c1d3bb80-4150-11eb-8262-535e568fa781.png" width="700">
+<img src="https://github.com/user-attachments/assets/bd08c8bd-f610-449d-b1e2-6a990e562518">
 
 
----
 
 
-**PixiEditor** is a Pixel art editing software. Create beautiful sprites for your games, animations (coming soon!), and edit images. All packed in eye-friendly dark theme.
+**PixiEditor** is a universal 2D platform that aims to provide you with tools and features for all your 2D needs. Create beautiful sprites for your games, animations, edit images, create logos. All packed in an eye-friendly dark theme.     
+
 
 
 [![Release](https://img.shields.io/github/v/release/flabbet/PixiEditor)](https://github.com/flabbet/PixiEditor/releases) 
 [![Release](https://img.shields.io/github/v/release/flabbet/PixiEditor)](https://github.com/flabbet/PixiEditor/releases) 
 [![Downloads](https://img.shields.io/github/downloads/PixiEditor/PixiEditor/total)](https://github.com/flabbet/PixiEditor/releases)
 [![Downloads](https://img.shields.io/github/downloads/PixiEditor/PixiEditor/total)](https://github.com/flabbet/PixiEditor/releases)
@@ -17,24 +17,45 @@ PixiEditor is undergoing massive changes, master branch is unstable. We will not
 
 
 ## About PixiEditor
 ## About PixiEditor
 
 
-Want to create beautiful pixel art for your games? PixiEditor can help you! Our goal is to create a fully open-source, fast, and feature-rich pixel art creator. 
+PixiEditor aims to be all-in-one solution for 2D image editing, we aim to achieve this by building a solid foundation with basic functionalities, and exposing complex extension system, that would customize PixiEditor for all your needs.
+
+The project started as a pixel-art editor, but quickly evolved into something much more complex. Version 1.0 was downloaded over 100 000 times on all platforms and received 93% positive rating on Steam.
 
 
 ### Familiar interface
 ### Familiar interface
 
 
 Have you ever used Photoshop or Gimp? Reinventing the wheel is unnecessary, we wanted users to get familiar with the tool quickly and with ease. 
 Have you ever used Photoshop or Gimp? Reinventing the wheel is unnecessary, we wanted users to get familiar with the tool quickly and with ease. 
 
 
-![](https://user-images.githubusercontent.com/45312141/235351211-e00bcaea-9c63-4ecd-a2ee-e4fb2b2c9651.png)
+![](https://opencollective-production.s3.us-west-1.amazonaws.com/account-long-description/d2e269a7-8ded-4e0a-a723-c014730dba1c/PixiEditor_6OoxS5PGVD.png)
+
+### Toolsets for any scenario
+
+PixiEditor 2.0 comes by default with multiple toolsets: 
+- Pixel art - it contains tool suited for pixel-perfect scenarios
+- Painting - Basic painting tools, soft brushes, anti aliased shapes
+- Vector - Shapes and paths for creating vectors
+
+All toolsets can be used on one canvas, mix vector with raster. Export to png, jpg, svg, gif, mp4 and more!
+
+![](https://github.com/user-attachments/assets/605c901a-24aa-4c91-9ef9-0fa44878b614)
+
+### Animations
+
+Version 2.0 comes with Timeline and animation capabilities. You can create frame by frame animations or use nodes to animate your custom shaders.
+Key frame animations with vectors are planned.
 
 
-### Fast
+![PixiEditor_YdWFRnYxfb](https://github.com/user-attachments/assets/8fba0c6c-35c8-4ccb-9d69-d6beaff5d97f)
 
 
-PixiEditor is fast, drawing feels smooth on any canvas size, we've developed original chunk-based system and adaptive rendering to minimize pixel processing time.
+### Nodes
 
 
-### Active development
+Node render system is what powers such extensive capabilities. All layers, effects, layer structure are nodes or a result of node connections. PixiEditor exposes node graph for every document, so you are free to customize your image however you want and create procedural art/animations!
 
 
-PixiEditor started in 2018 and it's been actively developed since. We continuously improve code quality to ensure the best experience and performance.
+Here are some examples of what you can do with custom nodes https://pixieditor.net/blog/2024/08/16/devlog7#madeinpixieditor20
 
 
+## Installation - PixiEditor 2.0
 
 
-## Installation
+Currently version 2.0 is in open beta, follow this guide to install it https://pixieditor.net/docs/open-beta
+
+## Installation PixiEditor 1.0 - Pixel Art Editor
 
 
 <a href='//www.microsoft.com/store/apps/9NDDRHS8PBRN?cid=storebadge&ocid=badge'><img src='https://developer.microsoft.com/store/badges/images/English_get-it-from-MS.png' alt='Microsoft Store badge' width="184"/></a>
 <a href='//www.microsoft.com/store/apps/9NDDRHS8PBRN?cid=storebadge&ocid=badge'><img src='https://developer.microsoft.com/store/badges/images/English_get-it-from-MS.png' alt='Microsoft Store badge' width="184"/></a>
 
 
@@ -51,7 +72,6 @@ Follow these instructions to get PixiEditor working on your machine.
 3. Launch it
 3. Launch it
 4. Follow the steps in the installer to finish the installation
 4. Follow the steps in the installer to finish the installation
 
 
-
 ## Featured content
 ## Featured content
 
 
 ### PixiEditor 1.0 Trailer
 ### PixiEditor 1.0 Trailer
@@ -71,31 +91,51 @@ Check out some pixel arts made with PixiEditor [here](https://github.com/PixiEdi
 
 
 Struggling with something? You can find support in a few places:
 Struggling with something? You can find support in a few places:
 
 
-* Check out [documentation](https://github.com/flabbet/PixiEditor/wiki)
+* Check out [documentation](https://pixieditor.net/docs)
 
 
 * Ask on [Discord](https://discord.gg/qSRMYmq)
 * Ask on [Discord](https://discord.gg/qSRMYmq)
+* Check out [Forum](https://forum.pixieditor.net)
 * Open new [Issue](https://github.com/flabbet/PixiEditor/issues)
 * Open new [Issue](https://github.com/flabbet/PixiEditor/issues)
-* Check out the [FAQ](https://github.com/PixiEditor/PixiEditor/wiki/FAQ). 
-
+* [Get help](https://pixieditor.net/help)
 
 
 
 
 ## Building from source
 ## Building from source
 
 
 ### Software Requirements
 ### Software Requirements
 
 
-* .NET 7
+* .NET 8 SDK
+* [wasi-sdk](https://github.com/WebAssembly/wasi-sdk) - PixiEditor uses WASI modules for extensions
 
 
 ### Instructions
 ### Instructions
 
 
-1. Clone Repository
+1. Clone Repository with nested submodules
+
+`git clone --recurse-submodules -j8 https://github.com/PixiEditor/PixiEditor.git`
 
 
-2. Open PixiEditor/src/PixiEditor/PixiEditor.sln in Visual Studio
+or if cloned already, init submodules with
 
 
-3. Build solution
+```
+cd PixiEditor
+```
+```
+git submodule update --init --recursive
+```
+
+2. Download [Wasi-sdk](https://github.com/WebAssembly/wasi-sdk/releases) release for your system
+3. Extract downloaded sdk 
+4. Set `WASI_SDK_PATH` enviroment variable to extracted directory
+5. Run 
+```
+dotnet workload install wasi-experimental
+```
+
+7. Open PixiEditor/src/PixiEditor.sln in Visual Studio or other IDE of your choice
+
+8. Build solution and run PixiEditor.Desktop project
 
 
 ## Contributing 
 ## Contributing 
 
 
-Please read [CONTRIBUTING.md](https://github.com/flabbet/PixiEditor/blob/master/CONTRIBUTING.md) for details on our code of conduct, and the process for submitting pull requests to us.
+PixiEditor is undergoing massive changes, master branch is unstable. We will not accept any contributions at the moment, until version 2.0 comes out.
 
 
 ## License
 ## License
 
 

+ 1 - 1
src/Drawie

@@ -1 +1 @@
-Subproject commit c6372d7854028add4f17fe36fa98a1ab7dcd3951
+Subproject commit 3470bee10ed28168e63d19f03a1260f9a0aae2d3

+ 3 - 2
src/PixiEditor.ChangeableDocument/Changeables/Graph/Nodes/Filters.cs

@@ -39,7 +39,8 @@ public static class Filters
         ColorFilter.CreateColorMatrix(ColorMatrix.AverageGrayscale + ColorMatrix.OpaqueAlphaOffset);
         ColorFilter.CreateColorMatrix(ColorMatrix.AverageGrayscale + ColorMatrix.OpaqueAlphaOffset);
 
 
     /// <summary>
     /// <summary>
-    ///     R,G,B values are set to 0. Alpha is set to the average of R,G,B values.
+    ///     R,G,B values are set to 0. Alpha is set to the average of R,G,B values. Multiplied by alpha
     /// </summary>
     /// </summary>
-    public static readonly ColorFilter MaskFilter = ColorFilter.CreateColorMatrix(ColorMatrix.WeightedWavelengthAlphaGrayscale);
+    public static readonly ColorFilter MaskFilter = ColorFilter.CreateLumaColor();
+
 }
 }

+ 6 - 5
src/PixiEditor.ChangeableDocument/Changes/Drawing/LineBasedPen_UpdateableChange.cs

@@ -96,8 +96,8 @@ internal class LineBasedPen_UpdateableChange : UpdateableChange
             {
             {
                 ApplySoftnessGradient((VecD)point);
                 ApplySoftnessGradient((VecD)point);
             }
             }
-
-            image.EnqueueDrawEllipse(rect, color, color, 1, 0, antiAliasing, srcPaint);
+            
+            image.EnqueueDrawEllipse(rect, color, color, 0, 0, antiAliasing, srcPaint);
         }
         }
 
 
         var affChunks = image.FindAffectedArea(opCount);
         var affChunks = image.FindAffectedArea(opCount);
@@ -130,18 +130,19 @@ internal class LineBasedPen_UpdateableChange : UpdateableChange
                 ApplySoftnessGradient(points[i]);
                 ApplySoftnessGradient(points[i]);
             }
             }
 
 
-            targetImage.EnqueueDrawEllipse(rect, color, color, 1, 0, antiAliasing, srcPaint);
+            targetImage.EnqueueDrawEllipse(rect, color, color, 0, 0, antiAliasing, srcPaint);
         }
         }
     }
     }
 
 
     private void ApplySoftnessGradient(VecD pos)
     private void ApplySoftnessGradient(VecD pos)
     {
     {
+        if (hardness >= 1) return;
         srcPaint.Shader?.Dispose();
         srcPaint.Shader?.Dispose();
         float radius = strokeWidth / 2f;
         float radius = strokeWidth / 2f;
         radius = MathF.Max(1, radius);
         radius = MathF.Max(1, radius);
         srcPaint.Shader = Shader.CreateRadialGradient(
         srcPaint.Shader = Shader.CreateRadialGradient(
-            pos, radius, new Color[] { color, color.WithAlpha(0) }, 
-            new float[] { hardness, 1 }, ShaderTileMode.Clamp);
+            pos, radius, [color, color.WithAlpha(0)],
+            [hardness - 0.04f, 1f], ShaderTileMode.Clamp);
     }
     }
 
 
     public override OneOf<None, IChangeInfo, List<IChangeInfo>> Apply(Document target, bool firstApply,
     public override OneOf<None, IChangeInfo, List<IChangeInfo>> Apply(Document target, bool firstApply,

+ 1 - 1
src/PixiEditor.ChangeableDocument/Changes/Drawing/TransformSelected_UpdateableChange.cs

@@ -12,7 +12,7 @@ using Drawie.Numerics;
 
 
 namespace PixiEditor.ChangeableDocument.Changes.Drawing;
 namespace PixiEditor.ChangeableDocument.Changes.Drawing;
 
 
-internal class TransformSelected_UpdateableChange : UpdateableChange
+internal class TransformSelected_UpdateableChange : InterruptableUpdateableChange
 {
 {
     private readonly bool drawOnMask;
     private readonly bool drawOnMask;
     private bool keepOriginal;
     private bool keepOriginal;

+ 28 - 3
src/PixiEditor.ChangeableDocument/DocumentChangeTracker.cs

@@ -203,11 +203,30 @@ public class DocumentChangeTracker : IDisposable
     private OneOf<None, IChangeInfo, List<IChangeInfo>> ProcessMakeChangeAction(IMakeChangeAction act,
     private OneOf<None, IChangeInfo, List<IChangeInfo>> ProcessMakeChangeAction(IMakeChangeAction act,
         ActionSource source)
         ActionSource source)
     {
     {
-        if (activeUpdateableChange is not null)
+        if (activeUpdateableChange is not null && activeUpdateableChange is not InterruptableUpdateableChange)
         {
         {
             Trace.WriteLine($"Attempted to execute make change action {act} while {activeUpdateableChange} is active");
             Trace.WriteLine($"Attempted to execute make change action {act} while {activeUpdateableChange} is active");
             return new None();
             return new None();
         }
         }
+        
+        bool ignoreInUndo = false;
+        List<IChangeInfo> changeInfos = new();
+
+        if (activeUpdateableChange is InterruptableUpdateableChange interruptable)
+        {
+            var applyInfo = interruptable.Apply(document, false, out ignoreInUndo);
+            if (!ignoreInUndo)
+                AddToUndo(interruptable, source);
+            else
+                interruptable.Dispose();
+            
+            applyInfo.Switch(
+                static (None _) => { },
+                (IChangeInfo info) => changeInfos.Add(info),
+                (List<IChangeInfo> infos) => changeInfos.AddRange(infos));
+            
+            activeUpdateableChange = null;
+        }
 
 
         var change = act.CreateCorrespondingChange();
         var change = act.CreateCorrespondingChange();
         var validationResult = change.InitializeAndValidate(document);
         var validationResult = change.InitializeAndValidate(document);
@@ -218,12 +237,18 @@ public class DocumentChangeTracker : IDisposable
             return new None();
             return new None();
         }
         }
 
 
-        var info = change.Apply(document, true, out bool ignoreInUndo);
+        var info = change.Apply(document, true, out ignoreInUndo);
+        
+        info.Switch(
+            static (None _) => { },
+            (IChangeInfo changeInfo) => changeInfos.Add(changeInfo),
+            (List<IChangeInfo> infos) => changeInfos.AddRange(infos));
+        
         if (!ignoreInUndo)
         if (!ignoreInUndo)
             AddToUndo(change, source);
             AddToUndo(change, source);
         else
         else
             change.Dispose();
             change.Dispose();
-        return info;
+        return changeInfos;
     }
     }
 
 
     private OneOf<None, IChangeInfo, List<IChangeInfo>> ProcessStartOrUpdateChangeAction(IStartOrUpdateChangeAction act,
     private OneOf<None, IChangeInfo, List<IChangeInfo>> ProcessStartOrUpdateChangeAction(IStartOrUpdateChangeAction act,

+ 3 - 2
src/PixiEditor.Zoombox/Zoombox.cs

@@ -158,6 +158,7 @@ public partial class Zoombox : ContentControl, INotifyPropertyChanged
     }
     }
 
 
     internal const double ScaleFactor = 1.09050773267; //2^(1/8)
     internal const double ScaleFactor = 1.09050773267; //2^(1/8)
+    internal const double ScrollStep = 0.5;
 
 
     public VecD ToScreenSpace(VecD p)
     public VecD ToScreenSpace(VecD p)
     {
     {
@@ -429,10 +430,10 @@ public partial class Zoombox : ContentControl, INotifyPropertyChanged
 
 
     private void OnScroll(object? sender, PointerWheelEventArgs e)
     private void OnScroll(object? sender, PointerWheelEventArgs e)
     {
     {
-        double abs = Math.Abs(e.Delta.Y / 100.0);
+        double abs = Math.Abs(e.Delta.Y / ScrollStep);
         for (int i = 0; i < abs; i++)
         for (int i = 0; i < abs; i++)
         {
         {
-            ZoomInto(ToVecD(e.GetPosition(this)), e.Delta.Y / 100.0);
+            ZoomInto(ToVecD(e.GetPosition(this)), e.Delta.Y / ScrollStep);
         }
         }
     }
     }
 
 

+ 10 - 1
src/PixiEditor/Data/Localization/Languages/en.json

@@ -769,5 +769,14 @@
   "PATH_TOOL_TOOLTIP": "Create vector paths and curves ({0}).",
   "PATH_TOOL_TOOLTIP": "Create vector paths and curves ({0}).",
   "PATH_TOOL_ACTION_DISPLAY": "Click to add a point.",
   "PATH_TOOL_ACTION_DISPLAY": "Click to add a point.",
   "PATH_TOOL_ACTION_DISPLAY_CTRL": "Click on existing point and drag to make it a curve.", 
   "PATH_TOOL_ACTION_DISPLAY_CTRL": "Click on existing point and drag to make it a curve.", 
-  "PATH_TOOL_ACTION_DISPLAY_ALT": "Click on a control point and move to adjust only one side of the curve."
+  "PATH_TOOL_ACTION_DISPLAY_ALT": "Click on a control point and move to adjust only one side of the curve.",
+  "DEFAULT_PATH_LAYER_NAME": "Path",
+  "DELETE_NODES": "Delete nodes",
+  "DELETE_NODES_DESCRIPTIVE": "Delete selected nodes",
+  "DELETE_CELS": "Delete cels",
+  "DELETE_CELS_DESCRIPTIVE": "Delete selected cels",
+  "COPY_COLOR_TO_CLIPBOARD": "Copy color to clipboard",
+  "VIEWPORT_ROTATION": "Viewport rotation",
+  "NEXT_TOOL_SET": "Next tool set",
+  "PREVIOUS_TOOL_SET": "Previous tool set"
 }
 }

+ 17 - 0
src/PixiEditor/Helpers/Converters/EnumToLocalizedStringConverter.cs

@@ -0,0 +1,17 @@
+using System.Globalization;
+using PixiEditor.Extensions.Helpers;
+
+namespace PixiEditor.Helpers.Converters;
+
+internal class EnumToLocalizedStringConverter : SingleInstanceConverter<EnumToLocalizedStringConverter>
+{
+    public override object Convert(object value, Type targetType, object parameter, CultureInfo culture)
+    {
+        if (value is Enum enumValue)
+        {
+            return EnumHelpers.GetDescription(enumValue);
+        }
+
+        return value;
+    }
+}

+ 1 - 1
src/PixiEditor/Helpers/Converters/ZoomToViewportConverter.cs

@@ -19,7 +19,7 @@ internal class ZoomToViewportConverter
 
 
     public static double ZoomToViewport(double factor, double scale)
     public static double ZoomToViewport(double factor, double scale)
     {
     {
-        double newSize = Math.Clamp(factor / scale, 2, 9999);
+        double newSize = Math.Clamp(factor / scale, 1, 9999);
 
 
         double log = Math.Log(newSize, 2);
         double log = Math.Log(newSize, 2);
         //round to power of 2
         //round to power of 2

+ 1 - 1
src/PixiEditor/Models/Controllers/InputDevice/SnappingController.cs

@@ -277,7 +277,7 @@ public class SnappingController
             return pos;
             return pos;
         }
         }
 
 
-        if (direction == VecD.Zero)
+        if (direction.X == 0 || direction.Y == 0)
         {
         {
             return GetSnapPoint(pos, out xAxis, out yAxis);
             return GetSnapPoint(pos, out xAxis, out yAxis);
         }
         }

+ 1 - 1
src/PixiEditor/Models/DocumentModels/DocumentUpdater.cs

@@ -465,7 +465,7 @@ internal class DocumentUpdater
 
 
     private void ProcessCreateRasterKeyFrame(CreateRasterKeyFrame_ChangeInfo info)
     private void ProcessCreateRasterKeyFrame(CreateRasterKeyFrame_ChangeInfo info)
     {
     {
-        var vm = new RasterKeyFrameViewModel(info.TargetLayerGuid, info.Frame, 1,
+        var vm = new IRasterCelViewModel(info.TargetLayerGuid, info.Frame, 1,
             info.KeyFrameId,
             info.KeyFrameId,
             (DocumentViewModel)doc, helper);
             (DocumentViewModel)doc, helper);
         
         

+ 14 - 3
src/PixiEditor/Models/DocumentModels/UpdateableChangeExecutors/DrawableShapeToolExecutor.cs

@@ -10,6 +10,7 @@ using PixiEditor.Models.Handlers.Tools;
 using PixiEditor.Models.Tools;
 using PixiEditor.Models.Tools;
 using Drawie.Numerics;
 using Drawie.Numerics;
 using PixiEditor.ChangeableDocument.Changeables.Graph.Interfaces;
 using PixiEditor.ChangeableDocument.Changeables.Graph.Interfaces;
+using PixiEditor.Models.DocumentModels.UpdateableChangeExecutors.Features;
 
 
 namespace PixiEditor.Models.DocumentModels.UpdateableChangeExecutors;
 namespace PixiEditor.Models.DocumentModels.UpdateableChangeExecutors;
 
 
@@ -33,6 +34,7 @@ internal abstract class DrawableShapeToolExecutor<T> : SimpleShapeToolExecutor w
     private bool noMovement = true;
     private bool noMovement = true;
     protected IBasicShapeToolbar toolbar;
     protected IBasicShapeToolbar toolbar;
     private IColorsHandler? colorsVM;
     private IColorsHandler? colorsVM;
+    private bool ignoreNextColorChange = false;
 
 
     public override bool CanUndo => document.TransformHandler.HasUndo;
     public override bool CanUndo => document.TransformHandler.HasUndo;
     public override bool CanRedo => document.TransformHandler.HasRedo;
     public override bool CanRedo => document.TransformHandler.HasRedo;
@@ -60,6 +62,7 @@ internal abstract class DrawableShapeToolExecutor<T> : SimpleShapeToolExecutor w
             {
             {
                 toolbar.FillColor = colorsVM.PrimaryColor.ToColor();
                 toolbar.FillColor = colorsVM.PrimaryColor.ToColor();
                 toolbar.StrokeColor = colorsVM.PrimaryColor.ToColor();
                 toolbar.StrokeColor = colorsVM.PrimaryColor.ToColor();
+                ignoreNextColorChange = colorsVM.ColorsTempSwapped;
             }
             }
             
             
             lastRect = new RectD(startDrawingPos, VecD.Zero);
             lastRect = new RectD(startDrawingPos, VecD.Zero);
@@ -161,11 +164,19 @@ internal abstract class DrawableShapeToolExecutor<T> : SimpleShapeToolExecutor w
 
 
     public override void OnColorChanged(Color color, bool primary)
     public override void OnColorChanged(Color color, bool primary)
     {
     {
-        if (primary && toolbar.SyncWithPrimaryColor && ActiveMode == ShapeToolMode.Transform)
+        if (!primary || !toolbar.SyncWithPrimaryColor || ActiveMode == ShapeToolMode.Preview || ignoreNextColorChange)
         {
         {
-            toolbar.StrokeColor = color.ToColor();
-            toolbar.FillColor = color.ToColor();
+            if (primary)
+            {
+                ignoreNextColorChange = false;
+            }
+            return;
         }
         }
+        
+        ignoreNextColorChange = ActiveMode == ShapeToolMode.Drawing;
+
+        toolbar.StrokeColor = color.ToColor();
+        toolbar.FillColor = color.ToColor();
     }
     }
 
 
     public override void OnSelectedObjectNudged(VecI distance)
     public override void OnSelectedObjectNudged(VecI distance)

+ 6 - 0
src/PixiEditor/Models/DocumentModels/UpdateableChangeExecutors/Features/IDelayedColorSwapFeature.cs

@@ -0,0 +1,6 @@
+namespace PixiEditor.Models.DocumentModels.UpdateableChangeExecutors.Features;
+
+public interface IDelayedColorSwapFeature : IExecutorFeature
+{
+    
+}

+ 12 - 1
src/PixiEditor/Models/DocumentModels/UpdateableChangeExecutors/LineExecutor.cs

@@ -10,6 +10,7 @@ using PixiEditor.Models.Handlers.Tools;
 using PixiEditor.Models.Tools;
 using PixiEditor.Models.Tools;
 using Drawie.Numerics;
 using Drawie.Numerics;
 using PixiEditor.Helpers;
 using PixiEditor.Helpers;
+using PixiEditor.Models.Controllers.InputDevice;
 
 
 namespace PixiEditor.Models.DocumentModels.UpdateableChangeExecutors;
 namespace PixiEditor.Models.DocumentModels.UpdateableChangeExecutors;
 
 
@@ -27,6 +28,7 @@ internal abstract class LineExecutor<T> : SimpleShapeToolExecutor where T : ILin
     private T? toolViewModel;
     private T? toolViewModel;
     private IColorsHandler? colorsVM;
     private IColorsHandler? colorsVM;
     protected ILineToolbar? toolbar;
     protected ILineToolbar? toolbar;
+    private bool ignoreNextColorChange = false;
 
 
     public override bool CanUndo => document.LineToolOverlayHandler.HasUndo;
     public override bool CanUndo => document.LineToolOverlayHandler.HasUndo;
     public override bool CanRedo => document.LineToolOverlayHandler.HasRedo;
     public override bool CanRedo => document.LineToolOverlayHandler.HasRedo;
@@ -54,8 +56,10 @@ internal abstract class LineExecutor<T> : SimpleShapeToolExecutor where T : ILin
             if (toolbar.SyncWithPrimaryColor)
             if (toolbar.SyncWithPrimaryColor)
             {
             {
                 toolbar.StrokeColor = colorsVM.PrimaryColor.ToColor();
                 toolbar.StrokeColor = colorsVM.PrimaryColor.ToColor();
+                ignoreNextColorChange = colorsVM.ColorsTempSwapped;
             }
             }
 
 
+            document.LineToolOverlayHandler.Hide();
             document.LineToolOverlayHandler.Show(startDrawingPos, startDrawingPos, false);
             document.LineToolOverlayHandler.Show(startDrawingPos, startDrawingPos, false);
             document.LineToolOverlayHandler.ShowHandles = false;
             document.LineToolOverlayHandler.ShowHandles = false;
             document.LineToolOverlayHandler.IsSizeBoxEnabled = true;
             document.LineToolOverlayHandler.IsSizeBoxEnabled = true;
@@ -155,9 +159,16 @@ internal abstract class LineExecutor<T> : SimpleShapeToolExecutor where T : ILin
 
 
     public override void OnColorChanged(Color color, bool primary)
     public override void OnColorChanged(Color color, bool primary)
     {
     {
-        if (!primary || !toolbar!.SyncWithPrimaryColor || ActiveMode != ShapeToolMode.Transform)
+        if (!primary || !toolbar!.SyncWithPrimaryColor || ActiveMode == ShapeToolMode.Preview || ignoreNextColorChange)
+        {
+            if (primary)
+            {
+                ignoreNextColorChange = false;
+            }
             return;
             return;
+        }
 
 
+        ignoreNextColorChange = ActiveMode == ShapeToolMode.Drawing;
         toolbar!.StrokeColor = color.ToColor();
         toolbar!.StrokeColor = color.ToColor();
         var colorChangedAction = SettingsChange();
         var colorChangedAction = SettingsChange();
         internals!.ActionAccumulator.AddActions(colorChangedAction);
         internals!.ActionAccumulator.AddActions(colorChangedAction);

+ 6 - 1
src/PixiEditor/Models/DocumentModels/UpdateableChangeExecutors/SimpleShapeToolExecutor.cs

@@ -26,7 +26,7 @@ namespace PixiEditor.Models.DocumentModels.UpdateableChangeExecutors;
 ///         - Transform -> Drawing (when user clicks outside of shape transform bounds)
 ///         - Transform -> Drawing (when user clicks outside of shape transform bounds)
 /// </summary>
 /// </summary>
 internal abstract class SimpleShapeToolExecutor : UpdateableChangeExecutor, 
 internal abstract class SimpleShapeToolExecutor : UpdateableChangeExecutor, 
-    ITransformableExecutor, IMidChangeUndoableExecutor
+    ITransformableExecutor, IMidChangeUndoableExecutor, IDelayedColorSwapFeature
 {
 {
     private ShapeToolMode activeMode;
     private ShapeToolMode activeMode;
 
 
@@ -228,6 +228,11 @@ internal abstract class SimpleShapeToolExecutor : UpdateableChangeExecutor,
             return ActiveMode == ShapeToolMode.Transform;
             return ActiveMode == ShapeToolMode.Transform;
         }
         }
         
         
+        if (feature is IDelayedColorSwapFeature)
+        {
+            return true;
+        }
+        
         return false;
         return false;
     }
     }
 }
 }

+ 1 - 1
src/PixiEditor/Models/DocumentModels/UpdateableChangeExecutors/TransformReferenceLayerExecutor.cs

@@ -14,7 +14,7 @@ internal class TransformReferenceLayerExecutor : UpdateableChangeExecutor, ITran
             return ExecutionState.Error;
             return ExecutionState.Error;
 
 
         ShapeCorners corners = document.ReferenceLayerHandler.ReferenceShapeBindable;
         ShapeCorners corners = document.ReferenceLayerHandler.ReferenceShapeBindable;
-        document.TransformHandler.ShowTransform(DocumentTransformMode.Scale_Rotate_Shear_NoPerspective, true, corners, true);
+        document.TransformHandler.ShowTransform(DocumentTransformMode.Scale_Rotate_Shear_Perspective, true, corners, true);
         document.ReferenceLayerHandler.IsTransforming = true;
         document.ReferenceLayerHandler.IsTransforming = true;
         internals!.ActionAccumulator.AddActions(new TransformReferenceLayer_Action(corners));
         internals!.ActionAccumulator.AddActions(new TransformReferenceLayer_Action(corners));
         return ExecutionState.Success;
         return ExecutionState.Success;

+ 10 - 0
src/PixiEditor/Models/DocumentModels/UpdateableChangeExecutors/VectorPathToolExecutor.cs

@@ -196,6 +196,16 @@ internal class VectorPathToolExecutor : UpdateableChangeExecutor, IPathExecutorF
 
 
     private PathVectorData ConstructShapeData()
     private PathVectorData ConstructShapeData()
     {
     {
+        if(startingPath == null)
+        {
+            return new PathVectorData(new VectorPath())
+            {
+                StrokeWidth = toolbar.ToolSize,
+                StrokeColor = toolbar.StrokeColor.ToColor(),
+                FillColor = toolbar.Fill ? toolbar.FillColor.ToColor() : Colors.Transparent,
+            };
+        }
+        
         return new PathVectorData(new VectorPath(startingPath))
         return new PathVectorData(new VectorPath(startingPath))
         {
         {
             StrokeWidth = toolbar.ToolSize,
             StrokeWidth = toolbar.ToolSize,

+ 4 - 0
src/PixiEditor/Models/ExceptionHandling/CrashReport.cs

@@ -341,7 +341,11 @@ internal class CrashReport : IDisposable
         Process process = new();
         Process process = new();
 
 
         //TODO: Handle different name for the executable, .Desktop.exe
         //TODO: Handle different name for the executable, .Desktop.exe
+#if DEBUG
+        string fileName = Path.GetFileNameWithoutExtension(Assembly.GetExecutingAssembly().Location) + ".Desktop.exe";
+        #else
         string fileName = Path.GetFileNameWithoutExtension(Assembly.GetExecutingAssembly().Location) + ".exe";
         string fileName = Path.GetFileNameWithoutExtension(Assembly.GetExecutingAssembly().Location) + ".exe";
+#endif
 
 
         process.StartInfo = new()
         process.StartInfo = new()
         {
         {

+ 3 - 3
src/PixiEditor/Models/Handlers/IAnimationHandler.cs

@@ -4,7 +4,7 @@ namespace PixiEditor.Models.Handlers;
 
 
 internal interface IAnimationHandler
 internal interface IAnimationHandler
 {
 {
-    public IReadOnlyCollection<IKeyFrameHandler> KeyFrames { get; }
+    public IReadOnlyCollection<ICelHandler> KeyFrames { get; }
     public int ActiveFrameBindable { get; set; }
     public int ActiveFrameBindable { get; set; }
     public KeyFrameTime ActiveFrameTime { get; }
     public KeyFrameTime ActiveFrameTime { get; }
     public bool OnionSkinningEnabledBindable { get; set; }
     public bool OnionSkinningEnabledBindable { get; set; }
@@ -16,8 +16,8 @@ internal interface IAnimationHandler
     public void SetActiveFrame(int newFrame);
     public void SetActiveFrame(int newFrame);
     public void SetFrameLength(Guid keyFrameId, int newStartFrame, int newDuration);
     public void SetFrameLength(Guid keyFrameId, int newStartFrame, int newDuration);
     public void SetKeyFrameVisibility(Guid infoKeyFrameId, bool infoIsVisible);
     public void SetKeyFrameVisibility(Guid infoKeyFrameId, bool infoIsVisible);
-    public bool FindKeyFrame<T>(Guid guid, out T keyFrameHandler) where T : IKeyFrameHandler;
-    internal void AddKeyFrame(IKeyFrameHandler keyFrame);
+    public bool FindKeyFrame<T>(Guid guid, out T keyFrameHandler) where T : ICelHandler;
+    internal void AddKeyFrame(ICelHandler iCel);
     internal void RemoveKeyFrame(Guid keyFrameId);
     internal void RemoveKeyFrame(Guid keyFrameId);
     public void AddSelectedKeyFrame(Guid keyFrameId);
     public void AddSelectedKeyFrame(Guid keyFrameId);
     public void RemoveSelectedKeyFrame(Guid keyFrameId);
     public void RemoveSelectedKeyFrame(Guid keyFrameId);

+ 9 - 0
src/PixiEditor/Models/Handlers/ICelGroupHandler.cs

@@ -0,0 +1,9 @@
+using System.Collections.ObjectModel;
+using ChunkyImageLib;
+
+namespace PixiEditor.Models.Handlers;
+
+internal interface ICelGroupHandler : ICelHandler
+{
+    public ObservableCollection<ICelHandler> Children { get; }
+}

+ 1 - 1
src/PixiEditor/Models/Handlers/IKeyFrameHandler.cs → src/PixiEditor/Models/Handlers/ICelHandler.cs

@@ -4,7 +4,7 @@ using PixiEditor.Models.Rendering;
 
 
 namespace PixiEditor.Models.Handlers;
 namespace PixiEditor.Models.Handlers;
 
 
-internal interface IKeyFrameHandler
+internal interface ICelHandler
 {
 {
     public PreviewPainter? PreviewPainter { get; set; }
     public PreviewPainter? PreviewPainter { get; set; }
     public int StartFrameBindable { get; }
     public int StartFrameBindable { get; }

+ 1 - 0
src/PixiEditor/Models/Handlers/IColorsHandler.cs

@@ -8,5 +8,6 @@ internal interface IColorsHandler : IHandler
     public static IColorsHandler? Instance { get; }
     public static IColorsHandler? Instance { get; }
     public Color PrimaryColor { get; set; }
     public Color PrimaryColor { get; set; }
     public Color SecondaryColor { get; set; }
     public Color SecondaryColor { get; set; }
+    public bool ColorsTempSwapped { get; }
     public void AddSwatch(PaletteColor paletteColor);
     public void AddSwatch(PaletteColor paletteColor);
 }
 }

+ 0 - 9
src/PixiEditor/Models/Handlers/IKeyFrameGroupHandler.cs

@@ -1,9 +0,0 @@
-using System.Collections.ObjectModel;
-using ChunkyImageLib;
-
-namespace PixiEditor.Models.Handlers;
-
-internal interface IKeyFrameGroupHandler : IKeyFrameHandler
-{
-    public ObservableCollection<IKeyFrameHandler> Children { get; }
-}

+ 6 - 0
src/PixiEditor/Models/Handlers/IRasterCelHandler.cs

@@ -0,0 +1,6 @@
+namespace PixiEditor.Models.Handlers;
+
+internal interface IRasterCelHandler : ICelHandler
+{
+
+}

+ 0 - 6
src/PixiEditor/Models/Handlers/IRasterKeyFrameHandler.cs

@@ -1,6 +0,0 @@
-namespace PixiEditor.Models.Handlers;
-
-internal interface IRasterKeyFrameHandler : IKeyFrameHandler
-{
-
-}

+ 9 - 9
src/PixiEditor/Models/Rendering/MemberPreviewUpdater.cs

@@ -111,7 +111,7 @@ internal class MemberPreviewUpdater
     {
     {
         foreach (var keyFrame in doc.AnimationHandler.KeyFrames)
         foreach (var keyFrame in doc.AnimationHandler.KeyFrames)
         {
         {
-            if (keyFrame is IKeyFrameGroupHandler groupHandler)
+            if (keyFrame is ICelGroupHandler groupHandler)
             {
             {
                 foreach (var childFrame in groupHandler.Children)
                 foreach (var childFrame in groupHandler.Children)
                 {
                 {
@@ -132,22 +132,22 @@ internal class MemberPreviewUpdater
         }
         }
     }
     }
     
     
-    private bool IsInFrame(IKeyFrameHandler keyFrame)
+    private bool IsInFrame(ICelHandler iCel)
     {
     {
-        return keyFrame.StartFrameBindable <= doc.AnimationHandler.ActiveFrameBindable &&
-               keyFrame.StartFrameBindable + keyFrame.DurationBindable >= doc.AnimationHandler.ActiveFrameBindable;
+        return iCel.StartFrameBindable <= doc.AnimationHandler.ActiveFrameBindable &&
+               iCel.StartFrameBindable + iCel.DurationBindable >= doc.AnimationHandler.ActiveFrameBindable;
     }
     }
 
 
-    private void RenderFramePreview(IKeyFrameHandler keyFrame)
+    private void RenderFramePreview(ICelHandler iCel)
     {
     {
-        if (internals.Tracker.Document.AnimationData.TryFindKeyFrame(keyFrame.Id, out KeyFrame _))
+        if (internals.Tracker.Document.AnimationData.TryFindKeyFrame(iCel.Id, out KeyFrame _))
         {
         {
-            keyFrame.PreviewPainter ??= new PreviewPainter(AnimationKeyFramePreviewRenderer, keyFrame.Id.ToString());
-            keyFrame.PreviewPainter.Repaint();
+            iCel.PreviewPainter ??= new PreviewPainter(AnimationKeyFramePreviewRenderer, iCel.Id.ToString());
+            iCel.PreviewPainter.Repaint();
         }
         }
     }
     }
     
     
-    private void RenderGroupPreview(IKeyFrameGroupHandler groupHandler)
+    private void RenderGroupPreview(ICelGroupHandler groupHandler)
     {
     {
         var group = internals.Tracker.Document.AnimationData.KeyFrames.FirstOrDefault(x => x.Id == groupHandler.Id);
         var group = internals.Tracker.Document.AnimationData.KeyFrames.FirstOrDefault(x => x.Id == groupHandler.Id);
         if (group != null)
         if (group != null)

+ 2 - 2
src/PixiEditor/Properties/AssemblyInfo.cs

@@ -41,5 +41,5 @@ using System.Runtime.InteropServices;
 // You can specify all the values or you can default the Build and Revision Numbers
 // You can specify all the values or you can default the Build and Revision Numbers
 // by using the '*' as shown below:
 // by using the '*' as shown below:
 // [assembly: AssemblyVersion("1.0.*")]
 // [assembly: AssemblyVersion("1.0.*")]
-[assembly: AssemblyVersion("2.0.0.25")]
-[assembly: AssemblyFileVersion("2.0.0.25")]
+[assembly: AssemblyVersion("2.0.0.26")]
+[assembly: AssemblyFileVersion("2.0.0.26")]

+ 3 - 3
src/PixiEditor/Styles/Templates/Timeline.axaml

@@ -205,7 +205,7 @@
                                 <ItemsControl
                                 <ItemsControl
                                     ItemsSource="{Binding KeyFrames, RelativeSource={RelativeSource TemplatedParent}}">
                                     ItemsSource="{Binding KeyFrames, RelativeSource={RelativeSource TemplatedParent}}">
                                     <ItemsControl.DataTemplates>
                                     <ItemsControl.DataTemplates>
-                                        <DataTemplate DataType="document:KeyFrameGroupViewModel">
+                                        <DataTemplate DataType="document:CelGroupViewModel">
                                             <animations:TimelineGroupHeader Height="70"
                                             <animations:TimelineGroupHeader Height="70"
                                                                             Item="{Binding}" />
                                                                             Item="{Binding}" />
                                         </DataTemplate>
                                         </DataTemplate>
@@ -234,7 +234,7 @@
                                 <ItemsControl ClipToBounds="False" Name="PART_KeyFramesHost"
                                 <ItemsControl ClipToBounds="False" Name="PART_KeyFramesHost"
                                               ItemsSource="{Binding KeyFrames, RelativeSource={RelativeSource TemplatedParent}}">
                                               ItemsSource="{Binding KeyFrames, RelativeSource={RelativeSource TemplatedParent}}">
                                     <ItemsControl.DataTemplates>
                                     <ItemsControl.DataTemplates>
-                                        <DataTemplate DataType="document:KeyFrameGroupViewModel">
+                                        <DataTemplate DataType="document:CelGroupViewModel">
                                             <ItemsControl ClipToBounds="False"
                                             <ItemsControl ClipToBounds="False"
                                                           BorderThickness="0, 0, 0, 1"
                                                           BorderThickness="0, 0, 0, 1"
                                                           BorderBrush="{DynamicResource ThemeBorderMidBrush}"
                                                           BorderBrush="{DynamicResource ThemeBorderMidBrush}"
@@ -256,7 +256,7 @@
                                                 </ItemsControl.ItemsPanel>
                                                 </ItemsControl.ItemsPanel>
                                             </ItemsControl>
                                             </ItemsControl>
                                         </DataTemplate>
                                         </DataTemplate>
-                                        <DataTemplate DataType="document:RasterKeyFrameViewModel">
+                                        <DataTemplate DataType="document:IRasterCelViewModel">
                                             <animations:KeyFrame
                                             <animations:KeyFrame
                                                 Scale="{Binding Scale, RelativeSource={RelativeSource FindAncestor, AncestorType=animations:Timeline}}"
                                                 Scale="{Binding Scale, RelativeSource={RelativeSource FindAncestor, AncestorType=animations:Timeline}}"
                                                 IsEnabled="{Binding IsVisible}"
                                                 IsEnabled="{Binding IsVisible}"

+ 39 - 39
src/PixiEditor/ViewModels/Document/AnimationDataViewModel.cs

@@ -20,12 +20,12 @@ internal class AnimationDataViewModel : ObservableObject, IAnimationHandler
     
     
     public DocumentViewModel Document { get; }
     public DocumentViewModel Document { get; }
     protected DocumentInternalParts Internals { get; }
     protected DocumentInternalParts Internals { get; }
-    public IReadOnlyCollection<IKeyFrameHandler> KeyFrames => keyFrames;
+    public IReadOnlyCollection<ICelHandler> KeyFrames => keyFrames;
 
 
-    public IReadOnlyCollection<IKeyFrameHandler> AllKeyFrames => allKeyFrames;
+    public IReadOnlyCollection<ICelHandler> AllCels => allCels;
 
 
     private KeyFrameCollection keyFrames = new KeyFrameCollection();
     private KeyFrameCollection keyFrames = new KeyFrameCollection();
-    private List<IKeyFrameHandler> allKeyFrames = new List<IKeyFrameHandler>();
+    private List<ICelHandler> allCels = new List<ICelHandler>();
     private bool onionSkinningEnabled;
     private bool onionSkinningEnabled;
     private bool isPlayingBindable;
     private bool isPlayingBindable;
 
 
@@ -135,7 +135,7 @@ internal class AnimationDataViewModel : ObservableObject, IAnimationHandler
         }
         }
     }
     }
 
 
-    public void DeleteKeyFrames(List<Guid> keyFrameIds)
+    public void DeleteCels(List<Guid> keyFrameIds)
     {
     {
         if (!Document.BlockingUpdateableChangeActive)
         if (!Document.BlockingUpdateableChangeActive)
         {
         {
@@ -213,7 +213,7 @@ internal class AnimationDataViewModel : ObservableObject, IAnimationHandler
 
 
     public void SetFrameLength(Guid keyFrameId, int newStartFrame, int newDuration)
     public void SetFrameLength(Guid keyFrameId, int newStartFrame, int newDuration)
     {
     {
-        if (TryFindKeyFrame(keyFrameId, out KeyFrameViewModel keyFrame))
+        if (TryFindCels(keyFrameId, out CelViewModel keyFrame))
         {
         {
             keyFrame.SetStartFrame(newStartFrame);
             keyFrame.SetStartFrame(newStartFrame);
             keyFrame.SetDuration(newDuration);
             keyFrame.SetDuration(newDuration);
@@ -223,34 +223,34 @@ internal class AnimationDataViewModel : ObservableObject, IAnimationHandler
 
 
     public void SetKeyFrameVisibility(Guid keyFrameId, bool isVisible)
     public void SetKeyFrameVisibility(Guid keyFrameId, bool isVisible)
     {
     {
-        if (TryFindKeyFrame(keyFrameId, out KeyFrameViewModel keyFrame))
+        if (TryFindCels(keyFrameId, out CelViewModel keyFrame))
         {
         {
             keyFrame.SetVisibility(isVisible);
             keyFrame.SetVisibility(isVisible);
             keyFrames.NotifyCollectionChanged();
             keyFrames.NotifyCollectionChanged();
         }
         }
     }
     }
 
 
-    public void AddKeyFrame(IKeyFrameHandler keyFrame)
+    public void AddKeyFrame(ICelHandler iCel)
     {
     {
-        Guid id = keyFrame.LayerGuid;
-        if (TryFindKeyFrame(id, out KeyFrameGroupViewModel foundGroup))
+        Guid id = iCel.LayerGuid;
+        if (TryFindCels(id, out CelGroupViewModel foundGroup))
         {
         {
-            foundGroup.Children.Add((KeyFrameViewModel)keyFrame);
+            foundGroup.Children.Add((CelViewModel)iCel);
         }
         }
         else
         else
         {
         {
             var group =
             var group =
-                new KeyFrameGroupViewModel(keyFrame.StartFrameBindable, keyFrame.DurationBindable, id, id, Document,
+                new CelGroupViewModel(iCel.StartFrameBindable, iCel.DurationBindable, id, id, Document,
                     Internals);
                     Internals);
-            group.Children.Add((KeyFrameViewModel)keyFrame);
+            group.Children.Add((CelViewModel)iCel);
             keyFrames.Add(group);
             keyFrames.Add(group);
         }
         }
 
 
-        keyFrames.NotifyCollectionChanged(NotifyCollectionChangedAction.Add, (KeyFrameViewModel)keyFrame);
+        keyFrames.NotifyCollectionChanged(NotifyCollectionChangedAction.Add, (CelViewModel)iCel);
 
 
-        if (!allKeyFrames.Contains(keyFrame))
+        if (!allCels.Contains(iCel))
         {
         {
-            allKeyFrames.Add(keyFrame);
+            allCels.Add(iCel);
         }
         }
         
         
         SortByLayers();
         SortByLayers();
@@ -258,16 +258,16 @@ internal class AnimationDataViewModel : ObservableObject, IAnimationHandler
 
 
     public void RemoveKeyFrame(Guid keyFrameId)
     public void RemoveKeyFrame(Guid keyFrameId)
     {
     {
-        TryFindKeyFrame<KeyFrameViewModel>(keyFrameId, out _, (frame, parent) =>
+        TryFindCels<CelViewModel>(keyFrameId, out _, (frame, parent) =>
         {
         {
-            if (frame is not KeyFrameGroupViewModel group)
+            if (frame is not CelGroupViewModel group)
             {
             {
                 parent.Children.Remove(frame);
                 parent.Children.Remove(frame);
-                keyFrames.NotifyCollectionChanged(NotifyCollectionChangedAction.Remove, (KeyFrameViewModel)frame);
+                keyFrames.NotifyCollectionChanged(NotifyCollectionChangedAction.Remove, (CelViewModel)frame);
 
 
                 if (parent.Children.Count == 0)
                 if (parent.Children.Count == 0)
                 {
                 {
-                    keyFrames.Remove(parent as KeyFrameGroupViewModel);
+                    keyFrames.Remove(parent as CelGroupViewModel);
                 }
                 }
             }
             }
             else
             else
@@ -276,12 +276,12 @@ internal class AnimationDataViewModel : ObservableObject, IAnimationHandler
             }
             }
         });
         });
 
 
-        allKeyFrames.RemoveAll(x => x.Id == keyFrameId);
+        allCels.RemoveAll(x => x.Id == keyFrameId);
     }
     }
 
 
     public void AddSelectedKeyFrame(Guid keyFrameId)
     public void AddSelectedKeyFrame(Guid keyFrameId)
     {
     {
-        if (TryFindKeyFrame(keyFrameId, out KeyFrameViewModel keyFrame))
+        if (TryFindCels(keyFrameId, out CelViewModel keyFrame))
         {
         {
             keyFrame.IsSelected = true;
             keyFrame.IsSelected = true;
         }
         }
@@ -289,7 +289,7 @@ internal class AnimationDataViewModel : ObservableObject, IAnimationHandler
 
 
     public void RemoveSelectedKeyFrame(Guid keyFrameId)
     public void RemoveSelectedKeyFrame(Guid keyFrameId)
     {
     {
-        if (TryFindKeyFrame(keyFrameId, out KeyFrameViewModel keyFrame))
+        if (TryFindCels(keyFrameId, out CelViewModel keyFrame))
         {
         {
             keyFrame.IsSelected = false;
             keyFrame.IsSelected = false;
         }
         }
@@ -297,7 +297,7 @@ internal class AnimationDataViewModel : ObservableObject, IAnimationHandler
 
 
     public void ClearSelectedKeyFrames()
     public void ClearSelectedKeyFrames()
     {
     {
-        var selectedFrames = keyFrames.SelectChildrenBy<KeyFrameViewModel>(x => x.IsSelected);
+        var selectedFrames = keyFrames.SelectChildrenBy<CelViewModel>(x => x.IsSelected);
         foreach (var frame in selectedFrames)
         foreach (var frame in selectedFrames)
         {
         {
             frame.IsSelected = false;
             frame.IsSelected = false;
@@ -306,36 +306,36 @@ internal class AnimationDataViewModel : ObservableObject, IAnimationHandler
 
 
     public void RemoveKeyFrames(List<Guid> keyFrameIds)
     public void RemoveKeyFrames(List<Guid> keyFrameIds)
     {
     {
-        List<KeyFrameViewModel> framesToRemove = new List<KeyFrameViewModel>();
+        List<CelViewModel> framesToRemove = new List<CelViewModel>();
         foreach (var keyFrame in keyFrameIds)
         foreach (var keyFrame in keyFrameIds)
         {
         {
-            TryFindKeyFrame<KeyFrameViewModel>(keyFrame, out _, (frame, parent) =>
+            TryFindCels<CelViewModel>(keyFrame, out _, (frame, parent) =>
             {
             {
                 parent.Children.Remove(frame);
                 parent.Children.Remove(frame);
-                framesToRemove.Add((KeyFrameViewModel)frame);
+                framesToRemove.Add((CelViewModel)frame);
             });
             });
 
 
-            allKeyFrames.RemoveAll(x => x.Id == keyFrame);
+            allCels.RemoveAll(x => x.Id == keyFrame);
         }
         }
 
 
         keyFrames.NotifyCollectionChanged(NotifyCollectionChangedAction.Remove, framesToRemove);
         keyFrames.NotifyCollectionChanged(NotifyCollectionChangedAction.Remove, framesToRemove);
     }
     }
 
 
-    public bool FindKeyFrame<T>(Guid guid, out T keyFrameHandler) where T : IKeyFrameHandler
+    public bool FindKeyFrame<T>(Guid guid, out T keyFrameHandler) where T : ICelHandler
     {
     {
-        return TryFindKeyFrame<T>(keyFrames, null, guid, out keyFrameHandler, null);
+        return TryFindCels<T>(keyFrames, null, guid, out keyFrameHandler, null);
     }
     }
 
 
     // TODO: Use the same structure functions as layers
     // TODO: Use the same structure functions as layers
-    public bool TryFindKeyFrame<T>(Guid id, out T? foundKeyFrame,
-        Action<IKeyFrameHandler, IKeyFrameGroupHandler?> onFound = null) where T : IKeyFrameHandler
+    public bool TryFindCels<T>(Guid id, out T? foundKeyFrame,
+        Action<ICelHandler, ICelGroupHandler?> onFound = null) where T : ICelHandler
     {
     {
-        return TryFindKeyFrame(keyFrames, null, id, out foundKeyFrame, onFound);
+        return TryFindCels(keyFrames, null, id, out foundKeyFrame, onFound);
     }
     }
 
 
-    private bool TryFindKeyFrame<T>(IReadOnlyCollection<IKeyFrameHandler> root, IKeyFrameGroupHandler parent, Guid id,
+    private bool TryFindCels<T>(IReadOnlyCollection<ICelHandler> root, ICelGroupHandler parent, Guid id,
         out T? result,
         out T? result,
-        Action<IKeyFrameHandler, IKeyFrameGroupHandler?> onFound) where T : IKeyFrameHandler
+        Action<ICelHandler, ICelGroupHandler?> onFound) where T : ICelHandler
     {
     {
         for (var i = 0; i < root.Count; i++)
         for (var i = 0; i < root.Count; i++)
         {
         {
@@ -347,9 +347,9 @@ internal class AnimationDataViewModel : ObservableObject, IAnimationHandler
                 return true;
                 return true;
             }
             }
 
 
-            if (frame is IKeyFrameGroupHandler { Children.Count: > 0 } group)
+            if (frame is ICelGroupHandler { Children.Count: > 0 } group)
             {
             {
-                bool found = TryFindKeyFrame(group.Children, group, id, out result, onFound);
+                bool found = TryFindCels(group.Children, group, id, out result, onFound);
                 if (found)
                 if (found)
                 {
                 {
                     return true;
                     return true;
@@ -365,10 +365,10 @@ internal class AnimationDataViewModel : ObservableObject, IAnimationHandler
     {
     {
         var allLayers = Document.StructureHelper.GetAllLayers();
         var allLayers = Document.StructureHelper.GetAllLayers();
         var unsortedKeyFrames = keyFrames.ToList();
         var unsortedKeyFrames = keyFrames.ToList();
-        var layerKeyFrames = new List<KeyFrameGroupViewModel>();
+        var layerKeyFrames = new List<CelGroupViewModel>();
         foreach (var layer in allLayers)
         foreach (var layer in allLayers)
         {
         {
-            var group = unsortedKeyFrames.FirstOrDefault(x => x is KeyFrameGroupViewModel group && group.LayerGuid == layer.Id) as KeyFrameGroupViewModel; 
+            var group = unsortedKeyFrames.FirstOrDefault(x => x is CelGroupViewModel group && group.LayerGuid == layer.Id) as CelGroupViewModel; 
             if(group != null)
             if(group != null)
             {
             {
                 layerKeyFrames.Insert(0, group);
                 layerKeyFrames.Insert(0, group);
@@ -377,7 +377,7 @@ internal class AnimationDataViewModel : ObservableObject, IAnimationHandler
 
 
         foreach (var remaining in unsortedKeyFrames)
         foreach (var remaining in unsortedKeyFrames)
         {
         {
-            if (remaining is KeyFrameGroupViewModel group && !layerKeyFrames.Contains(group))
+            if (remaining is CelGroupViewModel group && !layerKeyFrames.Contains(group))
             {
             {
                 layerKeyFrames.Add(group);
                 layerKeyFrames.Add(group);
             }
             }

+ 6 - 6
src/PixiEditor/ViewModels/Document/KeyFrameGroupViewModel.cs → src/PixiEditor/ViewModels/Document/CelGroupViewModel.cs

@@ -6,9 +6,9 @@ using PixiEditor.Models.Handlers;
 
 
 namespace PixiEditor.ViewModels.Document;
 namespace PixiEditor.ViewModels.Document;
 
 
-internal class KeyFrameGroupViewModel : KeyFrameViewModel, IKeyFrameGroupHandler
+internal class CelGroupViewModel : CelViewModel, ICelGroupHandler
 {
 {
-    public ObservableCollection<IKeyFrameHandler> Children { get; } = new ObservableCollection<IKeyFrameHandler>();
+    public ObservableCollection<ICelHandler> Children { get; } = new ObservableCollection<ICelHandler>();
 
 
     public override int StartFrameBindable => Children.Count > 0 ? Children.Min(x => x.StartFrameBindable) : 0;
     public override int StartFrameBindable => Children.Count > 0 ? Children.Min(x => x.StartFrameBindable) : 0;
     public override int DurationBindable => Children.Count > 0 ? Children.Max(x => x.StartFrameBindable + x.DurationBindable) - StartFrameBindable : 0;
     public override int DurationBindable => Children.Count > 0 ? Children.Max(x => x.StartFrameBindable + x.DurationBindable) - StartFrameBindable : 0;
@@ -23,7 +23,7 @@ internal class KeyFrameGroupViewModel : KeyFrameViewModel, IKeyFrameGroupHandler
             SetProperty(ref _isCollapsed, value);
             SetProperty(ref _isCollapsed, value);
             foreach (var child in Children)
             foreach (var child in Children)
             {
             {
-                if (child is KeyFrameViewModel keyFrame)
+                if (child is CelViewModel keyFrame)
                 {
                 {
                     keyFrame.IsCollapsed = value;
                     keyFrame.IsCollapsed = value;
                 }
                 }
@@ -37,7 +37,7 @@ internal class KeyFrameGroupViewModel : KeyFrameViewModel, IKeyFrameGroupHandler
     {
     {
         foreach (var child in Children)
         foreach (var child in Children)
         {
         {
-            if(child is KeyFrameViewModel keyFrame)
+            if(child is CelViewModel keyFrame)
             {
             {
                 keyFrame.SetVisibility(isVisible);
                 keyFrame.SetVisibility(isVisible);
             }
             }
@@ -46,7 +46,7 @@ internal class KeyFrameGroupViewModel : KeyFrameViewModel, IKeyFrameGroupHandler
         base.SetVisibility(isVisible);
         base.SetVisibility(isVisible);
     }
     }
 
 
-    public KeyFrameGroupViewModel(int startFrame, int duration, Guid layerGuid, Guid id, DocumentViewModel doc, DocumentInternalParts internalParts) 
+    public CelGroupViewModel(int startFrame, int duration, Guid layerGuid, Guid id, DocumentViewModel doc, DocumentInternalParts internalParts) 
         : base(startFrame, duration, layerGuid, id, doc, internalParts)
         : base(startFrame, duration, layerGuid, id, doc, internalParts)
     {
     {
         Children.CollectionChanged += ChildrenOnCollectionChanged;
         Children.CollectionChanged += ChildrenOnCollectionChanged;
@@ -68,7 +68,7 @@ internal class KeyFrameGroupViewModel : KeyFrameViewModel, IKeyFrameGroupHandler
         {
         {
             foreach (var item in e.NewItems)
             foreach (var item in e.NewItems)
             {
             {
-                if (item is KeyFrameViewModel keyFrame)
+                if (item is CelViewModel keyFrame)
                 {
                 {
                     keyFrame.IsCollapsed = IsCollapsed;
                     keyFrame.IsCollapsed = IsCollapsed;
                     keyFrame.SetVisibility(IsVisible);
                     keyFrame.SetVisibility(IsVisible);

+ 3 - 3
src/PixiEditor/ViewModels/Document/KeyFrameViewModel.cs → src/PixiEditor/ViewModels/Document/CelViewModel.cs

@@ -8,7 +8,7 @@ using PixiEditor.Models.Rendering;
 
 
 namespace PixiEditor.ViewModels.Document;
 namespace PixiEditor.ViewModels.Document;
 
 
-internal abstract class KeyFrameViewModel : ObservableObject, IKeyFrameHandler
+internal abstract class CelViewModel : ObservableObject, ICelHandler
 {
 {
     private PreviewPainter? previewPainter;
     private PreviewPainter? previewPainter;
     private int startFrameBindable;
     private int startFrameBindable;
@@ -26,7 +26,7 @@ internal abstract class KeyFrameViewModel : ObservableObject, IKeyFrameHandler
     public DocumentViewModel Document { get; }
     public DocumentViewModel Document { get; }
     protected DocumentInternalParts Internals { get; }
     protected DocumentInternalParts Internals { get; }
 
 
-    IDocument IKeyFrameHandler.Document => Document;
+    IDocument ICelHandler.Document => Document;
 
 
     public PreviewPainter? PreviewPainter
     public PreviewPainter? PreviewPainter
     {
     {
@@ -93,7 +93,7 @@ internal abstract class KeyFrameViewModel : ObservableObject, IKeyFrameHandler
         set => SetProperty(ref isSelected, value);
         set => SetProperty(ref isSelected, value);
     }
     }
 
 
-    protected KeyFrameViewModel(int startFrame, int duration, Guid layerGuid, Guid id,
+    protected CelViewModel(int startFrame, int duration, Guid layerGuid, Guid id,
         DocumentViewModel document, DocumentInternalParts internalParts)
         DocumentViewModel document, DocumentInternalParts internalParts)
     {
     {
         startFrameBindable = startFrame;
         startFrameBindable = startFrame;

+ 14 - 0
src/PixiEditor/ViewModels/Document/IRasterCelViewModel.cs

@@ -0,0 +1,14 @@
+using PixiEditor.Models.DocumentModels;
+using PixiEditor.Models.Handlers;
+
+namespace PixiEditor.ViewModels.Document;
+
+internal class IRasterCelViewModel : CelViewModel, IRasterCelHandler
+{
+    public IRasterCelViewModel(Guid targetLayerGuid, int startFrame, int duration, Guid id, DocumentViewModel doc, DocumentInternalParts internalParts) 
+        : base(startFrame, duration, targetLayerGuid, id, doc, internalParts)
+    {
+        
+    }
+
+}

+ 8 - 8
src/PixiEditor/ViewModels/Document/KeyFrameCollection.cs

@@ -6,14 +6,14 @@ using PixiEditor.Views.Animations;
 
 
 namespace PixiEditor.ViewModels.Document;
 namespace PixiEditor.ViewModels.Document;
 
 
-internal class KeyFrameCollection : ObservableCollection<KeyFrameGroupViewModel>
+internal class KeyFrameCollection : ObservableCollection<CelGroupViewModel>
 {
 {
     public KeyFrameCollection()
     public KeyFrameCollection()
     {
     {
         
         
     }
     }
 
 
-    public KeyFrameCollection(IEnumerable<KeyFrameGroupViewModel> source)
+    public KeyFrameCollection(IEnumerable<CelGroupViewModel> source)
     {
     {
         foreach (var handler in source)
         foreach (var handler in source)
         {
         {
@@ -21,28 +21,28 @@ internal class KeyFrameCollection : ObservableCollection<KeyFrameGroupViewModel>
         }
         }
     }
     }
 
 
-    public event Action<KeyFrameViewModel> KeyFrameAdded; 
-    public event Action<KeyFrameViewModel> KeyFrameRemoved; 
+    public event Action<CelViewModel> KeyFrameAdded; 
+    public event Action<CelViewModel> KeyFrameRemoved; 
     
     
     public void NotifyCollectionChanged()
     public void NotifyCollectionChanged()
     {
     {
         OnPropertyChanged(new PropertyChangedEventArgs(nameof(FrameCount)));
         OnPropertyChanged(new PropertyChangedEventArgs(nameof(FrameCount)));
     }
     }
 
 
-    public void NotifyCollectionChanged(NotifyCollectionChangedAction action, KeyFrameViewModel keyFrame)
+    public void NotifyCollectionChanged(NotifyCollectionChangedAction action, CelViewModel cel)
     {
     {
         NotifyCollectionChanged();
         NotifyCollectionChanged();
         if (action == NotifyCollectionChangedAction.Add)
         if (action == NotifyCollectionChangedAction.Add)
         {
         {
-            KeyFrameAdded?.Invoke(keyFrame);
+            KeyFrameAdded?.Invoke(cel);
         }
         }
         else if (action == NotifyCollectionChangedAction.Remove)
         else if (action == NotifyCollectionChangedAction.Remove)
         {
         {
-            KeyFrameRemoved?.Invoke(keyFrame);
+            KeyFrameRemoved?.Invoke(cel);
         }
         }
     }
     }
     
     
-    public void NotifyCollectionChanged(NotifyCollectionChangedAction action, List<KeyFrameViewModel> fames)
+    public void NotifyCollectionChanged(NotifyCollectionChangedAction action, List<CelViewModel> fames)
     {
     {
         foreach (var frame in fames)
         foreach (var frame in fames)
         {
         {

+ 0 - 14
src/PixiEditor/ViewModels/Document/RasterKeyFrameViewModel.cs

@@ -1,14 +0,0 @@
-using PixiEditor.Models.DocumentModels;
-using PixiEditor.Models.Handlers;
-
-namespace PixiEditor.ViewModels.Document;
-
-internal class RasterKeyFrameViewModel : KeyFrameViewModel, IRasterKeyFrameHandler
-{
-    public RasterKeyFrameViewModel(Guid targetLayerGuid, int startFrame, int duration, Guid id, DocumentViewModel doc, DocumentInternalParts internalParts) 
-        : base(startFrame, duration, targetLayerGuid, id, doc, internalParts)
-    {
-        
-    }
-
-}

+ 1 - 0
src/PixiEditor/ViewModels/Document/TransformOverlays/LineToolOverlayViewModel.cs

@@ -106,6 +106,7 @@ internal class LineToolOverlayViewModel : ObservableObject, ILineOverlayHandler
         undoStack = null;
         undoStack = null;
         IsEnabled = false;
         IsEnabled = false;
         ShowApplyButton = false;
         ShowApplyButton = false;
+        IsSizeBoxEnabled = false;
     }
     }
 
 
     public bool Nudge(VecD distance)
     public bool Nudge(VecD distance)

+ 11 - 11
src/PixiEditor/ViewModels/SubViewModels/AnimationsViewModel.cs

@@ -75,7 +75,7 @@ internal class AnimationsViewModel : SubViewModel<ViewModelMain>
             "Raster",
             "Raster",
             activeDocument.AnimationDataViewModel.FrameRateBindable,
             activeDocument.AnimationDataViewModel.FrameRateBindable,
             activeDocument.AnimationDataViewModel.FramesCount,
             activeDocument.AnimationDataViewModel.FramesCount,
-            activeDocument.AnimationDataViewModel.AllKeyFrames.Count);
+            activeDocument.AnimationDataViewModel.AllCels.Count);
     }
     }
 
 
     [Command.Basic("PixiEditor.Animation.ToggleOnionSkinning", "TOGGLE_ONION_SKINNING",
     [Command.Basic("PixiEditor.Animation.ToggleOnionSkinning", "TOGGLE_ONION_SKINNING",
@@ -89,28 +89,28 @@ internal class AnimationsViewModel : SubViewModel<ViewModelMain>
         Owner.DocumentManagerSubViewModel.ActiveDocument?.AnimationDataViewModel.ToggleOnionSkinning(value);
         Owner.DocumentManagerSubViewModel.ActiveDocument?.AnimationDataViewModel.ToggleOnionSkinning(value);
     }
     }
 
 
-    [Command.Basic("PixiEditor.Animation.DeleteKeyFrames", "DELETE_KEY_FRAMES", "DELETE_KEY_FRAMES_DESCRIPTIVE",
+    [Command.Basic("PixiEditor.Animation.DeleteCels", "DELETE_CELS", "DELETE_CELS_DESCRIPTIVE",
         ShortcutContext = typeof(TimelineDockViewModel), Key = Key.Delete, AnalyticsTrack = true)]
         ShortcutContext = typeof(TimelineDockViewModel), Key = Key.Delete, AnalyticsTrack = true)]
-    public void DeleteKeyFrames()
+    public void DeleteCels()
     {
     {
         var activeDocument = Owner.DocumentManagerSubViewModel.ActiveDocument;
         var activeDocument = Owner.DocumentManagerSubViewModel.ActiveDocument;
-        var selected = activeDocument.AnimationDataViewModel.AllKeyFrames.Where(x => x.IsSelected).ToArray();
+        var selected = activeDocument.AnimationDataViewModel.AllCels.Where(x => x.IsSelected).ToArray();
 
 
         if (activeDocument is null || selected.Length == 0)
         if (activeDocument is null || selected.Length == 0)
             return;
             return;
 
 
-        List<Guid> keyFrameIds = selected.Select(x => x.Id).ToList();
+        List<Guid> celIds = selected.Select(x => x.Id).ToList();
 
 
-        for (int i = 0; i < keyFrameIds.Count; i++)
+        for (int i = 0; i < celIds.Count; i++)
         {
         {
-            if (!activeDocument.AnimationDataViewModel.TryFindKeyFrame<KeyFrameViewModel>(keyFrameIds[i], out _))
+            if (!activeDocument.AnimationDataViewModel.TryFindCels<CelViewModel>(celIds[i], out _))
             {
             {
-                keyFrameIds.RemoveAt(i);
+                celIds.RemoveAt(i);
                 i--;
                 i--;
             }
             }
         }
         }
 
 
-        activeDocument.AnimationDataViewModel.DeleteKeyFrames(keyFrameIds);
+        activeDocument.AnimationDataViewModel.DeleteCels(celIds);
     }
     }
 
 
     [Command.Internal("PixiEditor.Animation.ChangeKeyFramesStartPos")]
     [Command.Internal("PixiEditor.Animation.ChangeKeyFramesStartPos")]
@@ -135,8 +135,8 @@ internal class AnimationsViewModel : SubViewModel<ViewModelMain>
     private static int GetActiveFrame(DocumentViewModel activeDocument, Guid targetLayer)
     private static int GetActiveFrame(DocumentViewModel activeDocument, Guid targetLayer)
     {
     {
         int active = activeDocument.AnimationDataViewModel.ActiveFrameBindable;
         int active = activeDocument.AnimationDataViewModel.ActiveFrameBindable;
-        if (activeDocument.AnimationDataViewModel.TryFindKeyFrame<KeyFrameGroupViewModel>(targetLayer,
-                out KeyFrameGroupViewModel groupViewModel))
+        if (activeDocument.AnimationDataViewModel.TryFindCels<CelGroupViewModel>(targetLayer,
+                out CelGroupViewModel groupViewModel))
         {
         {
             if (active == groupViewModel.StartFrameBindable + groupViewModel.DurationBindable - 1)
             if (active == groupViewModel.StartFrameBindable + groupViewModel.DurationBindable - 1)
             {
             {

+ 12 - 0
src/PixiEditor/ViewModels/SubViewModels/ColorsViewModel.cs

@@ -50,6 +50,8 @@ internal class ColorsViewModel : SubViewModel<ViewModelMain>, IColorsHandler
     public LocalPalettesFetcher LocalPaletteFetcher => _localPaletteFetcher ??=
     public LocalPalettesFetcher LocalPaletteFetcher => _localPaletteFetcher ??=
         (LocalPalettesFetcher)PaletteProvider.DataSources.FirstOrDefault(x => x is LocalPalettesFetcher)!;
         (LocalPalettesFetcher)PaletteProvider.DataSources.FirstOrDefault(x => x is LocalPalettesFetcher)!;
 
 
+    public bool ColorsTempSwapped { get; private set; }
+    
     private Color primaryColor = Colors.Black;
     private Color primaryColor = Colors.Black;
     private Color secondaryColor = Colors.White;
     private Color secondaryColor = Colors.White;
     private ColorState primaryColorState;
     private ColorState primaryColorState;
@@ -364,9 +366,19 @@ internal class ColorsViewModel : SubViewModel<ViewModelMain>, IColorsHandler
         Icon = PixiPerfectIcons.ColorsSwap, AnalyticsTrack = true)]
         Icon = PixiPerfectIcons.ColorsSwap, AnalyticsTrack = true)]
     public void SwapColors(object parameter)
     public void SwapColors(object parameter)
     {
     {
+        if (parameter is true)
+        {
+            ColorsTempSwapped = !ColorsTempSwapped;
+        }
+        else
+        {
+            ColorsTempSwapped = false;    
+        }
+        
         (PrimaryColor, SecondaryColor) = (SecondaryColor, PrimaryColor);
         (PrimaryColor, SecondaryColor) = (SecondaryColor, PrimaryColor);
     }
     }
 
 
+
     public void AddSwatch(PaletteColor color)
     public void AddSwatch(PaletteColor color)
     {
     {
         var doc = Owner.DocumentManagerSubViewModel.ActiveDocument;
         var doc = Owner.DocumentManagerSubViewModel.ActiveDocument;

+ 29 - 12
src/PixiEditor/ViewModels/SubViewModels/IoViewModel.cs

@@ -18,6 +18,7 @@ using PixiEditor.Models.Events;
 using PixiEditor.Models.Handlers;
 using PixiEditor.Models.Handlers;
 using PixiEditor.Models.Input;
 using PixiEditor.Models.Input;
 using Drawie.Numerics;
 using Drawie.Numerics;
+using PixiEditor.Models.DocumentModels.UpdateableChangeExecutors.Features;
 using PixiEditor.ViewModels.Document;
 using PixiEditor.ViewModels.Document;
 using PixiEditor.ViewModels.Tools.Tools;
 using PixiEditor.ViewModels.Tools.Tools;
 using PixiEditor.Views;
 using PixiEditor.Views;
@@ -26,7 +27,6 @@ namespace PixiEditor.ViewModels.SubViewModels;
 #nullable enable
 #nullable enable
 internal class IoViewModel : SubViewModel<ViewModelMain>
 internal class IoViewModel : SubViewModel<ViewModelMain>
 {
 {
-    private bool hadSwapped;
     private int? previousEraseSize;
     private int? previousEraseSize;
     private bool hadSharedToolbar;
     private bool hadSharedToolbar;
     private bool? drawingWithRight;
     private bool? drawingWithRight;
@@ -196,6 +196,11 @@ internal class IoViewModel : SubViewModel<ViewModelMain>
         activeDocument.EventInlet.OnCanvasLeftMouseButtonDown(args);
         activeDocument.EventInlet.OnCanvasLeftMouseButtonDown(args);
         Owner.ToolsSubViewModel.UseToolEventInlet(args.PositionOnCanvas, args.Button);
         Owner.ToolsSubViewModel.UseToolEventInlet(args.PositionOnCanvas, args.Button);
 
 
+        if (args.Button == MouseButton.Right)
+        {
+            HandleRightSwapColor();
+        }
+
         Analytics.SendUseTool(Owner.ToolsSubViewModel.ActiveTool, args.PositionOnCanvas, activeDocument.SizeBindable);
         Analytics.SendUseTool(Owner.ToolsSubViewModel.ActiveTool, args.PositionOnCanvas, activeDocument.SizeBindable);
     }
     }
 
 
@@ -208,17 +213,15 @@ internal class IoViewModel : SubViewModel<ViewModelMain>
         switch (tools.RightClickMode)
         switch (tools.RightClickMode)
         {
         {
             case RightClickMode.SecondaryColor when tools.ActiveTool.UsesColor:
             case RightClickMode.SecondaryColor when tools.ActiveTool.UsesColor:
-            case RightClickMode.Erase when tools.ActiveTool is ColorPickerToolViewModel:
-                if (!Owner.DocumentManagerSubViewModel.ActiveDocument.BlockingUpdateableChangeActive)
+                if (Owner.DocumentManagerSubViewModel.ActiveDocument.IsChangeFeatureActive<IDelayedColorSwapFeature>())
                 {
                 {
-                    Owner.ColorsSubViewModel.SwapColors(null);
-                }
-                else
-                {
-                    Owner.DocumentManagerSubViewModel.ActiveDocument.ToolSessionFinished += ToolSessionFinished;
+                    return true;
                 }
                 }
 
 
-                hadSwapped = true;
+                Owner.ColorsSubViewModel.SwapColors(true);
+                return true;
+            case RightClickMode.Erase when tools.ActiveTool is ColorPickerToolViewModel:
+                Owner.ColorsSubViewModel.SwapColors(true);
                 return true;
                 return true;
             case RightClickMode.Erase when tools.ActiveTool.IsErasable:
             case RightClickMode.Erase when tools.ActiveTool.IsErasable:
             {
             {
@@ -233,6 +236,22 @@ internal class IoViewModel : SubViewModel<ViewModelMain>
         }
         }
     }
     }
 
 
+    private void HandleRightSwapColor()
+    {
+        if (Owner.DocumentManagerSubViewModel.ActiveDocument is null)
+            return;
+        
+        if(Owner.ColorsSubViewModel.ColorsTempSwapped)
+            return;
+
+        var tools = Owner.ToolsSubViewModel;
+        
+        if (tools is { RightClickMode: RightClickMode.SecondaryColor, ActiveTool.UsesColor: true })
+        {
+            Owner.ColorsSubViewModel.SwapColors(true);
+        }
+    }
+    
     private void HandleRightMouseEraseDown(IToolsHandler tools)
     private void HandleRightMouseEraseDown(IToolsHandler tools)
     {
     {
         var currentToolSize = tools.ActiveTool.Toolbar.Settings.FirstOrDefault(x => x.Name == "ToolSize");
         var currentToolSize = tools.ActiveTool.Toolbar.Settings.FirstOrDefault(x => x.Name == "ToolSize");
@@ -295,8 +314,6 @@ internal class IoViewModel : SubViewModel<ViewModelMain>
         drawingWithRight = null;
         drawingWithRight = null;
 
 
         HandleRightMouseUp(button, tools);
         HandleRightMouseUp(button, tools);
-
-        hadSwapped = false;
     }
     }
 
 
     private void HandleRightMouseUp(MouseButton button, IToolsHandler tools)
     private void HandleRightMouseUp(MouseButton button, IToolsHandler tools)
@@ -306,7 +323,7 @@ internal class IoViewModel : SubViewModel<ViewModelMain>
             case MouseButton.Middle:
             case MouseButton.Middle:
                 tools.RestorePreviousTool();
                 tools.RestorePreviousTool();
                 break;
                 break;
-            case MouseButton.Right when hadSwapped &&
+            case MouseButton.Right when Owner.ColorsSubViewModel.ColorsTempSwapped &&
                                         (tools.RightClickMode == RightClickMode.SecondaryColor ||
                                         (tools.RightClickMode == RightClickMode.SecondaryColor ||
                                          tools is
                                          tools is
                                          {
                                          {

+ 2 - 0
src/PixiEditor/ViewModels/Tools/Tools/VectorPathToolViewModel.cs

@@ -18,6 +18,8 @@ internal class VectorPathToolViewModel : ShapeTool, IVectorPathToolHandler
     public override Type LayerTypeToCreateOnEmptyUse { get; } = typeof(VectorLayerNode);
     public override Type LayerTypeToCreateOnEmptyUse { get; } = typeof(VectorLayerNode);
     public override LocalizedString Tooltip => new LocalizedString("PATH_TOOL_TOOLTIP", Shortcut);
     public override LocalizedString Tooltip => new LocalizedString("PATH_TOOL_TOOLTIP", Shortcut);
 
 
+    public string? DefaultNewLayerName => new LocalizedString("DEFAULT_PATH_LAYER_NAME");
+
     public override string DefaultIcon => PixiPerfectIcons.VectorPen;
     public override string DefaultIcon => PixiPerfectIcons.VectorPen;
     public override bool StopsLinkedToolOnUse => false;
     public override bool StopsLinkedToolOnUse => false;
     public override bool IsErasable => false;
     public override bool IsErasable => false;

+ 3 - 3
src/PixiEditor/Views/Animations/KeyFrame.cs

@@ -17,7 +17,7 @@ namespace PixiEditor.Views.Animations;
 [PseudoClasses(":selected")]
 [PseudoClasses(":selected")]
 internal class KeyFrame : TemplatedControl
 internal class KeyFrame : TemplatedControl
 {
 {
-    public static readonly StyledProperty<KeyFrameViewModel> ItemProperty = AvaloniaProperty.Register<KeyFrame, KeyFrameViewModel>(
+    public static readonly StyledProperty<CelViewModel> ItemProperty = AvaloniaProperty.Register<KeyFrame, CelViewModel>(
         nameof(Item));
         nameof(Item));
 
 
     public static readonly StyledProperty<double> ScaleProperty = AvaloniaProperty.Register<KeyFrame, double>(nameof(Scale), 100);
     public static readonly StyledProperty<double> ScaleProperty = AvaloniaProperty.Register<KeyFrame, double>(nameof(Scale), 100);
@@ -49,7 +49,7 @@ internal class KeyFrame : TemplatedControl
         set => SetValue(IsSelectedProperty, value);
         set => SetValue(IsSelectedProperty, value);
     }
     }
 
 
-    public KeyFrameViewModel Item
+    public CelViewModel Item
     {
     {
         get => GetValue(ItemProperty);
         get => GetValue(ItemProperty);
         set => SetValue(ItemProperty, value);
         set => SetValue(ItemProperty, value);
@@ -85,7 +85,7 @@ internal class KeyFrame : TemplatedControl
         _resizePanelLeft.PointerCaptureLost += UpdateKeyFrame;
         _resizePanelLeft.PointerCaptureLost += UpdateKeyFrame;
         _resizePanelRight.PointerCaptureLost += UpdateKeyFrame;
         _resizePanelRight.PointerCaptureLost += UpdateKeyFrame;
 
 
-        if (Item is not KeyFrameGroupViewModel)
+        if (Item is not CelGroupViewModel)
         {
         {
             MultiBinding marginBinding = new MultiBinding
             MultiBinding marginBinding = new MultiBinding
             {
             {

+ 23 - 23
src/PixiEditor/Views/Animations/Timeline.cs

@@ -173,8 +173,8 @@ internal class Timeline : TemplatedControl, INotifyPropertyChanged
     public ICommand ClearSelectedKeyFramesCommand { get; }
     public ICommand ClearSelectedKeyFramesCommand { get; }
     public ICommand PressedKeyFrameCommand { get; }
     public ICommand PressedKeyFrameCommand { get; }
 
 
-    public IReadOnlyCollection<KeyFrameViewModel> SelectedKeyFrames => KeyFrames != null
-        ? KeyFrames.SelectChildrenBy<KeyFrameViewModel>(x => x.IsSelected).ToList()
+    public IReadOnlyCollection<CelViewModel> SelectedKeyFrames => KeyFrames != null
+        ? KeyFrames.SelectChildrenBy<CelViewModel>(x => x.IsSelected).ToList()
         : [];
         : [];
 
 
     private ToggleButton? _playToggle;
     private ToggleButton? _playToggle;
@@ -190,7 +190,7 @@ internal class Timeline : TemplatedControl, INotifyPropertyChanged
     private Vector clickPos;
     private Vector clickPos;
     
     
     private bool shouldClearNextSelection = true;
     private bool shouldClearNextSelection = true;
-    private KeyFrameViewModel clickedKeyFrame;
+    private CelViewModel clickedCel;
     private bool dragged;
     private bool dragged;
     private int dragStartFrame;
     private int dragStartFrame;
 
 
@@ -210,12 +210,12 @@ internal class Timeline : TemplatedControl, INotifyPropertyChanged
             new DispatcherTimer(DispatcherPriority.Render) { Interval = TimeSpan.FromMilliseconds(1000f / Fps) };
             new DispatcherTimer(DispatcherPriority.Render) { Interval = TimeSpan.FromMilliseconds(1000f / Fps) };
         _playTimer.Tick += PlayTimerOnTick;
         _playTimer.Tick += PlayTimerOnTick;
         PressedKeyFrameCommand = new RelayCommand<PointerPressedEventArgs>(KeyFramePressed);
         PressedKeyFrameCommand = new RelayCommand<PointerPressedEventArgs>(KeyFramePressed);
-        ClearSelectedKeyFramesCommand = new RelayCommand<KeyFrameViewModel>(ClearSelectedKeyFrames);
+        ClearSelectedKeyFramesCommand = new RelayCommand<CelViewModel>(ClearSelectedKeyFrames);
         DraggedKeyFrameCommand = new RelayCommand<PointerEventArgs>(KeyFramesDragged);
         DraggedKeyFrameCommand = new RelayCommand<PointerEventArgs>(KeyFramesDragged);
-        ReleasedKeyFrameCommand = new RelayCommand<KeyFrameViewModel>(KeyFramesReleased);
+        ReleasedKeyFrameCommand = new RelayCommand<CelViewModel>(KeyFramesReleased);
     }
     }
     
     
-    public void SelectKeyFrame(KeyFrameViewModel? keyFrame, bool clearSelection = true)
+    public void SelectKeyFrame(CelViewModel? keyFrame, bool clearSelection = true)
     {
     {
         if (clearSelection)
         if (clearSelection)
         {
         {
@@ -245,7 +245,7 @@ internal class Timeline : TemplatedControl, INotifyPropertyChanged
         {
         {
             ChangeKeyFramesLengthCommand.Execute((SelectedKeyFrames.Select(x => x.Id).ToArray(), 0, true));
             ChangeKeyFramesLengthCommand.Execute((SelectedKeyFrames.Select(x => x.Id).ToArray(), 0, true));
         }
         }
-        clickedKeyFrame = null;
+        clickedCel = null;
     }
     }
 
 
     protected override void OnApplyTemplate(TemplateAppliedEventArgs e)
     protected override void OnApplyTemplate(TemplateAppliedEventArgs e)
@@ -280,7 +280,7 @@ internal class Timeline : TemplatedControl, INotifyPropertyChanged
         _keyFramesHost = e.NameScope.Find<ItemsControl>("PART_KeyFramesHost");
         _keyFramesHost = e.NameScope.Find<ItemsControl>("PART_KeyFramesHost");
     }
     }
     
     
-    private void KeyFramesReleased(KeyFrameViewModel? e)
+    private void KeyFramesReleased(CelViewModel? e)
     {
     {
         if (!dragged)
         if (!dragged)
         {
         {
@@ -293,21 +293,21 @@ internal class Timeline : TemplatedControl, INotifyPropertyChanged
         }
         }
 
 
         dragged = false;
         dragged = false;
-        clickedKeyFrame = null;
+        clickedCel = null;
     }
     }
 
 
     private void KeyFramesDragged(PointerEventArgs? e)
     private void KeyFramesDragged(PointerEventArgs? e)
     {
     {
-        if (clickedKeyFrame == null) return;
+        if (clickedCel == null) return;
 
 
         int frameUnderMouse = MousePosToFrame(e);
         int frameUnderMouse = MousePosToFrame(e);
         int delta = frameUnderMouse - dragStartFrame;
         int delta = frameUnderMouse - dragStartFrame;
 
 
         if (delta != 0)
         if (delta != 0)
         {
         {
-            if (!clickedKeyFrame.IsSelected)
+            if (!clickedCel.IsSelected)
             {
             {
-                SelectKeyFrame(clickedKeyFrame);
+                SelectKeyFrame(clickedCel);
             }
             }
 
 
             dragged = true;
             dragged = true;
@@ -318,7 +318,7 @@ internal class Timeline : TemplatedControl, INotifyPropertyChanged
         }
         }
     }
     }
 
 
-    private void ClearSelectedKeyFrames(KeyFrameViewModel? keyFrame)
+    private void ClearSelectedKeyFrames(CelViewModel? keyFrame)
     {
     {
         ClearSelectedKeyFrames();
         ClearSelectedKeyFrames();
     }
     }
@@ -335,7 +335,7 @@ internal class Timeline : TemplatedControl, INotifyPropertyChanged
         }
         }
 
 
         e.Pointer.Capture(target);
         e.Pointer.Capture(target);
-        clickedKeyFrame = target.Item;
+        clickedCel = target.Item;
         dragStartFrame = MousePosToFrame(e);
         dragStartFrame = MousePosToFrame(e);
         e.Handled = true;
         e.Handled = true;
     }
     }
@@ -608,19 +608,19 @@ internal class Timeline : TemplatedControl, INotifyPropertyChanged
         }
         }
     }
     }
 
 
-    private void KeyFrames_KeyFrameAdded(KeyFrameViewModel keyFrame)
+    private void KeyFrames_KeyFrameAdded(CelViewModel cel)
     {
     {
-        keyFrame.PropertyChanged += KeyFrameOnPropertyChanged;
+        cel.PropertyChanged += KeyFrameOnPropertyChanged;
         PropertyChanged(this, new PropertyChangedEventArgs(nameof(SelectedKeyFrames)));
         PropertyChanged(this, new PropertyChangedEventArgs(nameof(SelectedKeyFrames)));
         PropertyChanged(this, new PropertyChangedEventArgs(nameof(EndFrame)));
         PropertyChanged(this, new PropertyChangedEventArgs(nameof(EndFrame)));
     }
     }
 
 
-    private void KeyFrames_KeyFrameRemoved(KeyFrameViewModel keyFrame)
+    private void KeyFrames_KeyFrameRemoved(CelViewModel cel)
     {
     {
-        if (SelectedKeyFrames.Contains(keyFrame))
+        if (SelectedKeyFrames.Contains(cel))
         {
         {
-            keyFrame.Document.AnimationDataViewModel.RemoveSelectedKeyFrame(keyFrame.Id);
-            keyFrame.PropertyChanged -= KeyFrameOnPropertyChanged;
+            cel.Document.AnimationDataViewModel.RemoveSelectedKeyFrame(cel.Id);
+            cel.PropertyChanged -= KeyFrameOnPropertyChanged;
         }
         }
         
         
         PropertyChanged(this, new PropertyChangedEventArgs(nameof(SelectedKeyFrames)));
         PropertyChanged(this, new PropertyChangedEventArgs(nameof(SelectedKeyFrames)));
@@ -642,13 +642,13 @@ internal class Timeline : TemplatedControl, INotifyPropertyChanged
     
     
     private void KeyFrameOnPropertyChanged(object? sender, PropertyChangedEventArgs e)
     private void KeyFrameOnPropertyChanged(object? sender, PropertyChangedEventArgs e)
     {
     {
-        if (sender is KeyFrameViewModel keyFrame)
+        if (sender is CelViewModel keyFrame)
         {
         {
-            if (e.PropertyName == nameof(KeyFrameViewModel.IsSelected))
+            if (e.PropertyName == nameof(CelViewModel.IsSelected))
             {
             {
                 PropertyChanged(this, new PropertyChangedEventArgs(nameof(SelectedKeyFrames)));
                 PropertyChanged(this, new PropertyChangedEventArgs(nameof(SelectedKeyFrames)));
             }
             }
-            else if (e.PropertyName == nameof(KeyFrameViewModel.StartFrameBindable) || e.PropertyName == nameof(KeyFrameViewModel.DurationBindable))
+            else if (e.PropertyName == nameof(CelViewModel.StartFrameBindable) || e.PropertyName == nameof(CelViewModel.DurationBindable))
             {
             {
                 PropertyChanged(this, new PropertyChangedEventArgs(nameof(EndFrame)));
                 PropertyChanged(this, new PropertyChangedEventArgs(nameof(EndFrame)));
             }
             }

+ 2 - 2
src/PixiEditor/Views/Animations/TimelineGroupHeader.cs

@@ -12,10 +12,10 @@ namespace PixiEditor.Views.Animations;
 [PseudoClasses(":collapsed")]
 [PseudoClasses(":collapsed")]
 internal class TimelineGroupHeader : TemplatedControl
 internal class TimelineGroupHeader : TemplatedControl
 {
 {
-    public static readonly StyledProperty<KeyFrameGroupViewModel> ItemProperty = AvaloniaProperty.Register<TimelineGroupHeader, KeyFrameGroupViewModel>(
+    public static readonly StyledProperty<CelGroupViewModel> ItemProperty = AvaloniaProperty.Register<TimelineGroupHeader, CelGroupViewModel>(
         nameof(Item));
         nameof(Item));
 
 
-    public KeyFrameGroupViewModel Item
+    public CelGroupViewModel Item
     {
     {
         get => GetValue(ItemProperty);
         get => GetValue(ItemProperty);
         set => SetValue(ItemProperty, value);
         set => SetValue(ItemProperty, value);

+ 1 - 1
src/PixiEditor/Views/Dock/TimelineDockView.axaml

@@ -23,6 +23,6 @@
         OnionOpacity="{Binding DocumentManagerSubViewModel.ActiveDocument.AnimationDataViewModel.OnionOpacityBindable, Mode=TwoWay}"
         OnionOpacity="{Binding DocumentManagerSubViewModel.ActiveDocument.AnimationDataViewModel.OnionOpacityBindable, Mode=TwoWay}"
         IsPlaying="{Binding DocumentManagerSubViewModel.ActiveDocument.AnimationDataViewModel.IsPlayingBindable, Mode=TwoWay}"
         IsPlaying="{Binding DocumentManagerSubViewModel.ActiveDocument.AnimationDataViewModel.IsPlayingBindable, Mode=TwoWay}"
         DuplicateKeyFrameCommand="{xaml:Command PixiEditor.Animation.DuplicateRasterKeyFrame}"
         DuplicateKeyFrameCommand="{xaml:Command PixiEditor.Animation.DuplicateRasterKeyFrame}"
-        DeleteKeyFrameCommand="{xaml:Command PixiEditor.Animation.DeleteKeyFrames, UseProvided=True}"
+        DeleteKeyFrameCommand="{xaml:Command PixiEditor.Animation.DeleteCels, UseProvided=True}"
         ChangeKeyFramesLengthCommand="{xaml:Command PixiEditor.Animation.ChangeKeyFramesStartPos, UseProvided=True}"/>
         ChangeKeyFramesLengthCommand="{xaml:Command PixiEditor.Animation.ChangeKeyFramesStartPos, UseProvided=True}"/>
 </UserControl>
 </UserControl>

+ 2 - 2
src/PixiEditor/Views/Main/ViewportControls/Viewport.axaml

@@ -49,7 +49,6 @@
             </EventTriggerBehavior>-->
             </EventTriggerBehavior>-->
         </Interaction.Behaviors>
         </Interaction.Behaviors>
         <overlays:TogglableFlyout Margin="5" Icon="{DynamicResource icon-tool}"
         <overlays:TogglableFlyout Margin="5" Icon="{DynamicResource icon-tool}"
-                                  ui:Translator.TooltipKey="VIEWPORT_SETTINGS"
                                   ZIndex="2" HorizontalAlignment="Right" VerticalAlignment="Top">
                                   ZIndex="2" HorizontalAlignment="Right" VerticalAlignment="Top">
             <overlays:TogglableFlyout.Child>
             <overlays:TogglableFlyout.Child>
                 <Border Padding="5"
                 <Border Padding="5"
@@ -64,6 +63,7 @@
                                     BorderThickness="{DynamicResource ThemeBorderThickness}"
                                     BorderThickness="{DynamicResource ThemeBorderThickness}"
                                     CornerRadius="{DynamicResource ControlCornerRadius}"
                                     CornerRadius="{DynamicResource ControlCornerRadius}"
                                     Background="{DynamicResource ThemeControlHighBrush}"
                                     Background="{DynamicResource ThemeControlHighBrush}"
+                                    ui:Translator.TooltipKey="VIEWPORT_ROTATION"
                                     VerticalAlignment="Center">
                                     VerticalAlignment="Center">
                                 <TextBlock TextAlignment="Center"
                                 <TextBlock TextAlignment="Center"
                                            VerticalAlignment="Center"
                                            VerticalAlignment="Center"
@@ -126,7 +126,7 @@
                         <Separator />
                         <Separator />
                         <StackPanel HorizontalAlignment="Right" Orientation="Horizontal">
                         <StackPanel HorizontalAlignment="Right" Orientation="Horizontal">
                             <ToggleButton Margin="10 0 0 0" Width="32" Height="32"
                             <ToggleButton Margin="10 0 0 0" Width="32" Height="32"
-                                          ui:Translator.TooltipKey="TOGGLE_SNAPPING"
+                                          ui:Translator.TooltipKey="TOGGLE_GRIDLINES"
                                           Classes="OverlayToggleButton pixi-icon"
                                           Classes="OverlayToggleButton pixi-icon"
                                           Content="{DynamicResource icon-gridlines}"
                                           Content="{DynamicResource icon-gridlines}"
                                           IsChecked="{Binding GridLinesVisible, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=viewportControls:Viewport}, Mode=TwoWay}" />
                                           IsChecked="{Binding GridLinesVisible, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=viewportControls:Viewport}, Mode=TwoWay}" />

+ 4 - 1
src/PixiEditor/Views/Main/ViewportControls/ViewportOverlays.cs

@@ -236,7 +236,7 @@ internal class ViewportOverlays
         {
         {
             Source = Viewport, Path = "Document.LineToolOverlayViewModel.IsSizeBoxEnabled", Mode = BindingMode.TwoWay
             Source = Viewport, Path = "Document.LineToolOverlayViewModel.IsSizeBoxEnabled", Mode = BindingMode.TwoWay
         };
         };
-
+        
         lineToolOverlay.Bind(Visual.IsVisibleProperty, isVisibleBinding);
         lineToolOverlay.Bind(Visual.IsVisibleProperty, isVisibleBinding);
         lineToolOverlay.Bind(LineToolOverlay.SnappingControllerProperty, snappingBinding);
         lineToolOverlay.Bind(LineToolOverlay.SnappingControllerProperty, snappingBinding);
         lineToolOverlay.Bind(LineToolOverlay.ActionCompletedProperty, actionCompletedBinding);
         lineToolOverlay.Bind(LineToolOverlay.ActionCompletedProperty, actionCompletedBinding);
@@ -244,6 +244,9 @@ internal class ViewportOverlays
         lineToolOverlay.Bind(LineToolOverlay.LineEndProperty, lineEndBinding);
         lineToolOverlay.Bind(LineToolOverlay.LineEndProperty, lineEndBinding);
         lineToolOverlay.Bind(LineToolOverlay.ShowHandlesProperty, showHandlesBinding);
         lineToolOverlay.Bind(LineToolOverlay.ShowHandlesProperty, showHandlesBinding);
         lineToolOverlay.Bind(LineToolOverlay.IsSizeBoxEnabledProperty, isSizeBoxEnabledBinding);
         lineToolOverlay.Bind(LineToolOverlay.IsSizeBoxEnabledProperty, isSizeBoxEnabledBinding);
+        lineToolOverlay.Bind(LineToolOverlay.ShowHandlesProperty, showHandlesBinding);
+        lineToolOverlay.Bind(LineToolOverlay.IsSizeBoxEnabledProperty, isSizeBoxEnabledBinding);
+        lineToolOverlay.Bind(Visual.IsVisibleProperty, isVisibleBinding);
     }
     }
 
 
     private void BindTransformOverlay()
     private void BindTransformOverlay()

+ 11 - 6
src/PixiEditor/Views/Overlays/LineToolOverlay/LineToolOverlay.cs

@@ -82,8 +82,6 @@ internal class LineToolOverlay : Overlay
     }
     }
 
 
 
 
-    private DashedStroke dashedStroke = new DashedStroke();
-
     private Paint blackPaint = new Paint()
     private Paint blackPaint = new Paint()
     {
     {
         Color = Colors.Black, StrokeWidth = 1, Style = PaintStyle.Stroke, IsAntiAliased = true
         Color = Colors.Black, StrokeWidth = 1, Style = PaintStyle.Stroke, IsAntiAliased = true
@@ -131,7 +129,7 @@ internal class LineToolOverlay : Overlay
         moveHandle.StrokePaint = blackPaint;
         moveHandle.StrokePaint = blackPaint;
         moveHandle.OnDrag += MoveHandleOnDrag;
         moveHandle.OnDrag += MoveHandleOnDrag;
         endHandle.Cursor = new Cursor(StandardCursorType.Arrow);
         endHandle.Cursor = new Cursor(StandardCursorType.Arrow);
-        moveHandle.OnHover += (handle, _)=> Refresh();
+        moveHandle.OnHover += (handle, _) => Refresh();
         moveHandle.OnRelease += OnHandleRelease;
         moveHandle.OnRelease += OnHandleRelease;
         AddHandle(moveHandle);
         AddHandle(moveHandle);
 
 
@@ -141,9 +139,15 @@ internal class LineToolOverlay : Overlay
     protected override void OnOverlayPointerMoved(OverlayPointerArgs args)
     protected override void OnOverlayPointerMoved(OverlayPointerArgs args)
     {
     {
         base.OnOverlayPointerMoved(args);
         base.OnOverlayPointerMoved(args);
+
         lastMousePos = args.Point;
         lastMousePos = args.Point;
     }
     }
 
 
+    public override bool TestHit(VecD point)
+    {
+        return IsVisible;
+    }
+
     private void OnHandleRelease(Handle obj, OverlayPointerArgs args)
     private void OnHandleRelease(Handle obj, OverlayPointerArgs args)
     {
     {
         if (SnappingController != null)
         if (SnappingController != null)
@@ -160,7 +164,6 @@ internal class LineToolOverlay : Overlay
     protected override void ZoomChanged(double newZoom)
     protected override void ZoomChanged(double newZoom)
     {
     {
         blackPaint.StrokeWidth = 1 / (float)newZoom;
         blackPaint.StrokeWidth = 1 / (float)newZoom;
-        dashedStroke.UpdateZoom((float)newZoom);
         infoBox.ZoomScale = newZoom;
         infoBox.ZoomScale = newZoom;
     }
     }
 
 
@@ -177,8 +180,6 @@ internal class LineToolOverlay : Overlay
 
 
         moveHandle.Position = TransformHelper.GetHandlePos(new ShapeCorners(center, size), ZoomScale, moveHandle.Size);
         moveHandle.Position = TransformHelper.GetHandlePos(new ShapeCorners(center, size), ZoomScale, moveHandle.Size);
 
 
-        dashedStroke.Draw(context, mappedStart, mappedEnd);
-
         if (ShowHandles)
         if (ShowHandles)
         {
         {
             startHandle.Draw(context);
             startHandle.Draw(context);
@@ -200,6 +201,7 @@ internal class LineToolOverlay : Overlay
 
 
         movedWhileMouseDown = false;
         movedWhileMouseDown = false;
         mouseDownPos = args.Point;
         mouseDownPos = args.Point;
+        
         lineStartOnMouseDown = LineStart;
         lineStartOnMouseDown = LineStart;
         lineEndOnMouseDown = LineEnd;
         lineEndOnMouseDown = LineEnd;
 
 
@@ -278,11 +280,14 @@ internal class LineToolOverlay : Overlay
 
 
     protected override void OnOverlayPointerReleased(OverlayPointerArgs args)
     protected override void OnOverlayPointerReleased(OverlayPointerArgs args)
     {
     {
+        IsSizeBoxEnabled = false;
+        
         if (args.InitialPressMouseButton != MouseButton.Left)
         if (args.InitialPressMouseButton != MouseButton.Left)
             return;
             return;
 
 
         if (movedWhileMouseDown && ActionCompleted is not null && ActionCompleted.CanExecute(null))
         if (movedWhileMouseDown && ActionCompleted is not null && ActionCompleted.CanExecute(null))
             ActionCompleted.Execute(null);
             ActionCompleted.Execute(null);
+        
     }
     }
 
 
     private ((string, string), VecD) TrySnapLine(VecD originalStart, VecD originalEnd, VecD delta)
     private ((string, string), VecD) TrySnapLine(VecD originalStart, VecD originalEnd, VecD delta)

+ 11 - 0
src/PixiEditor/Views/Overlays/Overlay.cs

@@ -44,6 +44,7 @@ public abstract class Overlay : Decorator, IOverlay // TODO: Maybe make it not a
     }
     }
 
 
     public event Action? RefreshRequested;
     public event Action? RefreshRequested;
+    public event Action? RefreshCursorRequested;
     public event PointerEvent? PointerEnteredOverlay;
     public event PointerEvent? PointerEnteredOverlay;
     public event PointerEvent? PointerExitedOverlay;
     public event PointerEvent? PointerExitedOverlay;
     public event PointerEvent? PointerMovedOverlay;
     public event PointerEvent? PointerMovedOverlay;
@@ -77,6 +78,11 @@ public abstract class Overlay : Decorator, IOverlay // TODO: Maybe make it not a
         InvalidateVisual(); // For elements in visual tree
         InvalidateVisual(); // For elements in visual tree
     }
     }
 
 
+    public void ForceRefreshCursor()
+    {
+        RefreshCursorRequested?.Invoke();
+    }
+
     public void CaptureHandle(Handle handle)
     public void CaptureHandle(Handle handle)
     {
     {
         CapturedHandle = handle;
         CapturedHandle = handle;
@@ -273,6 +279,11 @@ public abstract class Overlay : Decorator, IOverlay // TODO: Maybe make it not a
         {
         {
             Refresh();
             Refresh();
         }
         }
+        else
+        {
+            Cursor = null;
+            RefreshCursorRequested?.Invoke();
+        }
     }
     }
 
 
     protected static void AffectsOverlayRender(params AvaloniaProperty[] properties)
     protected static void AffectsOverlayRender(params AvaloniaProperty[] properties)

+ 29 - 4
src/PixiEditor/Views/Rendering/Scene.cs

@@ -259,7 +259,7 @@ internal class Scene : Zoombox.Zoombox, ICustomHitTest
         if (checkerBitmap == null) return;
         if (checkerBitmap == null) return;
 
 
         RectD operationSurfaceRectToRender = new RectD(0, 0, dirtyBounds.Width, dirtyBounds.Height);
         RectD operationSurfaceRectToRender = new RectD(0, 0, dirtyBounds.Width, dirtyBounds.Height);
-        float checkerScale = (float)ZoomToViewportConverter.ZoomToViewport(16, Scale) * 0.25f;
+        float checkerScale = (float)ZoomToViewportConverter.ZoomToViewport(16, Scale) * 0.5f;
         checkerPaint?.Dispose();
         checkerPaint?.Dispose();
         checkerPaint = new Paint
         checkerPaint = new Paint
         {
         {
@@ -317,6 +317,8 @@ internal class Scene : Zoombox.Zoombox, ICustomHitTest
         {
         {
             OverlayPointerArgs args = ConstructPointerArgs(e);
             OverlayPointerArgs args = ConstructPointerArgs(e);
 
 
+            Cursor = DefaultCursor;
+            
             if (capturedOverlay != null)
             if (capturedOverlay != null)
             {
             {
                 capturedOverlay.MovePointer(args);
                 capturedOverlay.MovePointer(args);
@@ -371,11 +373,13 @@ internal class Scene : Zoombox.Zoombox, ICustomHitTest
             }
             }
             else
             else
             {
             {
-                for (var i = 0; i < mouseOverOverlays.Count; i++)
+                foreach (var overlay in AllOverlays)
                 {
                 {
-                    var overlay = mouseOverOverlays[i];
                     if (args.Handled) break;
                     if (args.Handled) break;
                     if (!overlay.IsVisible) continue;
                     if (!overlay.IsVisible) continue;
+                    
+                    if(!overlay.IsHitTestVisible || !overlay.TestHit(args.Point)) continue;
+                    
                     overlay.PressPointer(args);
                     overlay.PressPointer(args);
                 }
                 }
             }
             }
@@ -419,11 +423,13 @@ internal class Scene : Zoombox.Zoombox, ICustomHitTest
             }
             }
             else
             else
             {
             {
-                foreach (Overlay overlay in mouseOverOverlays)
+                foreach (Overlay overlay in AllOverlays)
                 {
                 {
                     if (args.Handled) break;
                     if (args.Handled) break;
                     if (!overlay.IsVisible) continue;
                     if (!overlay.IsVisible) continue;
 
 
+                    if(!overlay.IsHitTestVisible || !overlay.TestHit(args.Point)) continue;
+                    
                     overlay.ReleasePointer(args);
                     overlay.ReleasePointer(args);
                 }
                 }
             }
             }
@@ -494,6 +500,7 @@ internal class Scene : Zoombox.Zoombox, ICustomHitTest
             foreach (Overlay overlay in e.OldItems)
             foreach (Overlay overlay in e.OldItems)
             {
             {
                 overlay.RefreshRequested -= QueueRender;
                 overlay.RefreshRequested -= QueueRender;
+                overlay.RefreshCursorRequested -= RefreshCursor;
             }
             }
         }
         }
 
 
@@ -502,6 +509,7 @@ internal class Scene : Zoombox.Zoombox, ICustomHitTest
             foreach (Overlay overlay in e.NewItems)
             foreach (Overlay overlay in e.NewItems)
             {
             {
                 overlay.RefreshRequested += QueueRender;
                 overlay.RefreshRequested += QueueRender;
+                overlay.RefreshCursorRequested += RefreshCursor;
             }
             }
         }
         }
     }
     }
@@ -610,6 +618,23 @@ internal class Scene : Zoombox.Zoombox, ICustomHitTest
 
 
     #endregion
     #endregion
 
 
+    public void RefreshCursor()
+    {
+        Cursor = DefaultCursor;
+        if (AllOverlays != null)
+        {
+            foreach (Overlay overlay in AllOverlays)
+            {
+                if (!overlay.IsVisible) continue;
+                
+                if (overlay.IsHitTestVisible)
+                {
+                    Cursor = overlay.Cursor ?? DefaultCursor;
+                }
+            }
+        }
+    }
+    
     private void QueueRender()
     private void QueueRender()
     {
     {
         Dispatcher.UIThread.Post(InvalidateVisual, DispatcherPriority.Render);
         Dispatcher.UIThread.Post(InvalidateVisual, DispatcherPriority.Render);

+ 1 - 1
src/PixiEditor/Views/Shortcuts/KeyCombinationBox.axaml.cs

@@ -52,7 +52,7 @@ internal partial class KeyCombinationBox : UserControl
 
 
         ViewModelMain.Current.LocalizationProvider.OnLanguageChanged += _ => UpdateText();
         ViewModelMain.Current.LocalizationProvider.OnLanguageChanged += _ => UpdateText();
 
 
-        //TOOD: Fix
+        //TODO: Fix
         //InputLanguageManager.Current.InputLanguageChanged += (_, _) => UpdateText();
         //InputLanguageManager.Current.InputLanguageChanged += (_, _) => UpdateText();
     }
     }
 
 

+ 7 - 1
src/PixiEditor/Views/Windows/Settings/SettingsWindow.axaml

@@ -128,7 +128,13 @@
                                       Name="rightClickModeComboBox"
                                       Name="rightClickModeComboBox"
                                       ItemsSource="{markupExtensions:Enum preferences:RightClickMode}"
                                       ItemsSource="{markupExtensions:Enum preferences:RightClickMode}"
                                       Width="160"
                                       Width="160"
-                                      VerticalAlignment="Center"/>
+                                      VerticalAlignment="Center">
+                                <ComboBox.ItemTemplate>
+                                    <DataTemplate>
+                                        <TextBlock ui:Translator.Key="{Binding Converter={converters:EnumToLocalizedStringConverter}}"/>
+                                    </DataTemplate>
+                                </ComboBox.ItemTemplate>
+                            </ComboBox>
                             <!--Styles="{StaticResource TranslatedEnum}"-->
                             <!--Styles="{StaticResource TranslatedEnum}"-->
                         </StackPanel>
                         </StackPanel>