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) 
 [![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
 
-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
 
 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>
 
@@ -51,7 +72,6 @@ Follow these instructions to get PixiEditor working on your machine.
 3. Launch it
 4. Follow the steps in the installer to finish the installation
 
-
 ## Featured content
 
 ### 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:
 
-* Check out [documentation](https://github.com/flabbet/PixiEditor/wiki)
+* Check out [documentation](https://pixieditor.net/docs)
 
 * Ask on [Discord](https://discord.gg/qSRMYmq)
+* Check out [Forum](https://forum.pixieditor.net)
 * 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
 
 ### Software Requirements
 
-* .NET 7
+* .NET 8 SDK
+* [wasi-sdk](https://github.com/WebAssembly/wasi-sdk) - PixiEditor uses WASI modules for extensions
 
 ### 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 
 
-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
 

+ 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);
 
     /// <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>
-    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);
             }
-
-            image.EnqueueDrawEllipse(rect, color, color, 1, 0, antiAliasing, srcPaint);
+            
+            image.EnqueueDrawEllipse(rect, color, color, 0, 0, antiAliasing, srcPaint);
         }
 
         var affChunks = image.FindAffectedArea(opCount);
@@ -130,18 +130,19 @@ internal class LineBasedPen_UpdateableChange : UpdateableChange
                 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)
     {
+        if (hardness >= 1) return;
         srcPaint.Shader?.Dispose();
         float radius = strokeWidth / 2f;
         radius = MathF.Max(1, radius);
         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,

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

@@ -12,7 +12,7 @@ using Drawie.Numerics;
 
 namespace PixiEditor.ChangeableDocument.Changes.Drawing;
 
-internal class TransformSelected_UpdateableChange : UpdateableChange
+internal class TransformSelected_UpdateableChange : InterruptableUpdateableChange
 {
     private readonly bool drawOnMask;
     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,
         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");
             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 validationResult = change.InitializeAndValidate(document);
@@ -218,12 +237,18 @@ public class DocumentChangeTracker : IDisposable
             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)
             AddToUndo(change, source);
         else
             change.Dispose();
-        return info;
+        return changeInfos;
     }
 
     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 ScrollStep = 0.5;
 
     public VecD ToScreenSpace(VecD p)
     {
@@ -429,10 +430,10 @@ public partial class Zoombox : ContentControl, INotifyPropertyChanged
 
     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++)
         {
-            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_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_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)
     {
-        double newSize = Math.Clamp(factor / scale, 2, 9999);
+        double newSize = Math.Clamp(factor / scale, 1, 9999);
 
         double log = Math.Log(newSize, 2);
         //round to power of 2

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

@@ -277,7 +277,7 @@ public class SnappingController
             return pos;
         }
 
-        if (direction == VecD.Zero)
+        if (direction.X == 0 || direction.Y == 0)
         {
             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)
     {
-        var vm = new RasterKeyFrameViewModel(info.TargetLayerGuid, info.Frame, 1,
+        var vm = new IRasterCelViewModel(info.TargetLayerGuid, info.Frame, 1,
             info.KeyFrameId,
             (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 Drawie.Numerics;
 using PixiEditor.ChangeableDocument.Changeables.Graph.Interfaces;
+using PixiEditor.Models.DocumentModels.UpdateableChangeExecutors.Features;
 
 namespace PixiEditor.Models.DocumentModels.UpdateableChangeExecutors;
 
@@ -33,6 +34,7 @@ internal abstract class DrawableShapeToolExecutor<T> : SimpleShapeToolExecutor w
     private bool noMovement = true;
     protected IBasicShapeToolbar toolbar;
     private IColorsHandler? colorsVM;
+    private bool ignoreNextColorChange = false;
 
     public override bool CanUndo => document.TransformHandler.HasUndo;
     public override bool CanRedo => document.TransformHandler.HasRedo;
@@ -60,6 +62,7 @@ internal abstract class DrawableShapeToolExecutor<T> : SimpleShapeToolExecutor w
             {
                 toolbar.FillColor = colorsVM.PrimaryColor.ToColor();
                 toolbar.StrokeColor = colorsVM.PrimaryColor.ToColor();
+                ignoreNextColorChange = colorsVM.ColorsTempSwapped;
             }
             
             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)
     {
-        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)

+ 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 Drawie.Numerics;
 using PixiEditor.Helpers;
+using PixiEditor.Models.Controllers.InputDevice;
 
 namespace PixiEditor.Models.DocumentModels.UpdateableChangeExecutors;
 
@@ -27,6 +28,7 @@ internal abstract class LineExecutor<T> : SimpleShapeToolExecutor where T : ILin
     private T? toolViewModel;
     private IColorsHandler? colorsVM;
     protected ILineToolbar? toolbar;
+    private bool ignoreNextColorChange = false;
 
     public override bool CanUndo => document.LineToolOverlayHandler.HasUndo;
     public override bool CanRedo => document.LineToolOverlayHandler.HasRedo;
@@ -54,8 +56,10 @@ internal abstract class LineExecutor<T> : SimpleShapeToolExecutor where T : ILin
             if (toolbar.SyncWithPrimaryColor)
             {
                 toolbar.StrokeColor = colorsVM.PrimaryColor.ToColor();
+                ignoreNextColorChange = colorsVM.ColorsTempSwapped;
             }
 
+            document.LineToolOverlayHandler.Hide();
             document.LineToolOverlayHandler.Show(startDrawingPos, startDrawingPos, false);
             document.LineToolOverlayHandler.ShowHandles = false;
             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)
     {
-        if (!primary || !toolbar!.SyncWithPrimaryColor || ActiveMode != ShapeToolMode.Transform)
+        if (!primary || !toolbar!.SyncWithPrimaryColor || ActiveMode == ShapeToolMode.Preview || ignoreNextColorChange)
+        {
+            if (primary)
+            {
+                ignoreNextColorChange = false;
+            }
             return;
+        }
 
+        ignoreNextColorChange = ActiveMode == ShapeToolMode.Drawing;
         toolbar!.StrokeColor = color.ToColor();
         var colorChangedAction = SettingsChange();
         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)
 /// </summary>
 internal abstract class SimpleShapeToolExecutor : UpdateableChangeExecutor, 
-    ITransformableExecutor, IMidChangeUndoableExecutor
+    ITransformableExecutor, IMidChangeUndoableExecutor, IDelayedColorSwapFeature
 {
     private ShapeToolMode activeMode;
 
@@ -228,6 +228,11 @@ internal abstract class SimpleShapeToolExecutor : UpdateableChangeExecutor,
             return ActiveMode == ShapeToolMode.Transform;
         }
         
+        if (feature is IDelayedColorSwapFeature)
+        {
+            return true;
+        }
+        
         return false;
     }
 }

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

@@ -14,7 +14,7 @@ internal class TransformReferenceLayerExecutor : UpdateableChangeExecutor, ITran
             return ExecutionState.Error;
 
         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;
         internals!.ActionAccumulator.AddActions(new TransformReferenceLayer_Action(corners));
         return ExecutionState.Success;

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

@@ -196,6 +196,16 @@ internal class VectorPathToolExecutor : UpdateableChangeExecutor, IPathExecutorF
 
     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))
         {
             StrokeWidth = toolbar.ToolSize,

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

@@ -341,7 +341,11 @@ internal class CrashReport : IDisposable
         Process process = new();
 
         //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";
+#endif
 
         process.StartInfo = new()
         {

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

@@ -4,7 +4,7 @@ namespace PixiEditor.Models.Handlers;
 
 internal interface IAnimationHandler
 {
-    public IReadOnlyCollection<IKeyFrameHandler> KeyFrames { get; }
+    public IReadOnlyCollection<ICelHandler> KeyFrames { get; }
     public int ActiveFrameBindable { get; set; }
     public KeyFrameTime ActiveFrameTime { get; }
     public bool OnionSkinningEnabledBindable { get; set; }
@@ -16,8 +16,8 @@ internal interface IAnimationHandler
     public void SetActiveFrame(int newFrame);
     public void SetFrameLength(Guid keyFrameId, int newStartFrame, int newDuration);
     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);
     public void AddSelectedKeyFrame(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;
 
-internal interface IKeyFrameHandler
+internal interface ICelHandler
 {
     public PreviewPainter? PreviewPainter { get; set; }
     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 Color PrimaryColor { get; set; }
     public Color SecondaryColor { get; set; }
+    public bool ColorsTempSwapped { get; }
     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)
         {
-            if (keyFrame is IKeyFrameGroupHandler groupHandler)
+            if (keyFrame is ICelGroupHandler groupHandler)
             {
                 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);
         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
 // by using the '*' as shown below:
 // [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
                                     ItemsSource="{Binding KeyFrames, RelativeSource={RelativeSource TemplatedParent}}">
                                     <ItemsControl.DataTemplates>
-                                        <DataTemplate DataType="document:KeyFrameGroupViewModel">
+                                        <DataTemplate DataType="document:CelGroupViewModel">
                                             <animations:TimelineGroupHeader Height="70"
                                                                             Item="{Binding}" />
                                         </DataTemplate>
@@ -234,7 +234,7 @@
                                 <ItemsControl ClipToBounds="False" Name="PART_KeyFramesHost"
                                               ItemsSource="{Binding KeyFrames, RelativeSource={RelativeSource TemplatedParent}}">
                                     <ItemsControl.DataTemplates>
-                                        <DataTemplate DataType="document:KeyFrameGroupViewModel">
+                                        <DataTemplate DataType="document:CelGroupViewModel">
                                             <ItemsControl ClipToBounds="False"
                                                           BorderThickness="0, 0, 0, 1"
                                                           BorderBrush="{DynamicResource ThemeBorderMidBrush}"
@@ -256,7 +256,7 @@
                                                 </ItemsControl.ItemsPanel>
                                             </ItemsControl>
                                         </DataTemplate>
-                                        <DataTemplate DataType="document:RasterKeyFrameViewModel">
+                                        <DataTemplate DataType="document:IRasterCelViewModel">
                                             <animations:KeyFrame
                                                 Scale="{Binding Scale, RelativeSource={RelativeSource FindAncestor, AncestorType=animations:Timeline}}"
                                                 IsEnabled="{Binding IsVisible}"

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

@@ -20,12 +20,12 @@ internal class AnimationDataViewModel : ObservableObject, IAnimationHandler
     
     public DocumentViewModel Document { 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 List<IKeyFrameHandler> allKeyFrames = new List<IKeyFrameHandler>();
+    private List<ICelHandler> allCels = new List<ICelHandler>();
     private bool onionSkinningEnabled;
     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)
         {
@@ -213,7 +213,7 @@ internal class AnimationDataViewModel : ObservableObject, IAnimationHandler
 
     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.SetDuration(newDuration);
@@ -223,34 +223,34 @@ internal class AnimationDataViewModel : ObservableObject, IAnimationHandler
 
     public void SetKeyFrameVisibility(Guid keyFrameId, bool isVisible)
     {
-        if (TryFindKeyFrame(keyFrameId, out KeyFrameViewModel keyFrame))
+        if (TryFindCels(keyFrameId, out CelViewModel keyFrame))
         {
             keyFrame.SetVisibility(isVisible);
             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
         {
             var group =
-                new KeyFrameGroupViewModel(keyFrame.StartFrameBindable, keyFrame.DurationBindable, id, id, Document,
+                new CelGroupViewModel(iCel.StartFrameBindable, iCel.DurationBindable, id, id, Document,
                     Internals);
-            group.Children.Add((KeyFrameViewModel)keyFrame);
+            group.Children.Add((CelViewModel)iCel);
             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();
@@ -258,16 +258,16 @@ internal class AnimationDataViewModel : ObservableObject, IAnimationHandler
 
     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);
-                keyFrames.NotifyCollectionChanged(NotifyCollectionChangedAction.Remove, (KeyFrameViewModel)frame);
+                keyFrames.NotifyCollectionChanged(NotifyCollectionChangedAction.Remove, (CelViewModel)frame);
 
                 if (parent.Children.Count == 0)
                 {
-                    keyFrames.Remove(parent as KeyFrameGroupViewModel);
+                    keyFrames.Remove(parent as CelGroupViewModel);
                 }
             }
             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)
     {
-        if (TryFindKeyFrame(keyFrameId, out KeyFrameViewModel keyFrame))
+        if (TryFindCels(keyFrameId, out CelViewModel keyFrame))
         {
             keyFrame.IsSelected = true;
         }
@@ -289,7 +289,7 @@ internal class AnimationDataViewModel : ObservableObject, IAnimationHandler
 
     public void RemoveSelectedKeyFrame(Guid keyFrameId)
     {
-        if (TryFindKeyFrame(keyFrameId, out KeyFrameViewModel keyFrame))
+        if (TryFindCels(keyFrameId, out CelViewModel keyFrame))
         {
             keyFrame.IsSelected = false;
         }
@@ -297,7 +297,7 @@ internal class AnimationDataViewModel : ObservableObject, IAnimationHandler
 
     public void ClearSelectedKeyFrames()
     {
-        var selectedFrames = keyFrames.SelectChildrenBy<KeyFrameViewModel>(x => x.IsSelected);
+        var selectedFrames = keyFrames.SelectChildrenBy<CelViewModel>(x => x.IsSelected);
         foreach (var frame in selectedFrames)
         {
             frame.IsSelected = false;
@@ -306,36 +306,36 @@ internal class AnimationDataViewModel : ObservableObject, IAnimationHandler
 
     public void RemoveKeyFrames(List<Guid> keyFrameIds)
     {
-        List<KeyFrameViewModel> framesToRemove = new List<KeyFrameViewModel>();
+        List<CelViewModel> framesToRemove = new List<CelViewModel>();
         foreach (var keyFrame in keyFrameIds)
         {
-            TryFindKeyFrame<KeyFrameViewModel>(keyFrame, out _, (frame, parent) =>
+            TryFindCels<CelViewModel>(keyFrame, out _, (frame, parent) =>
             {
                 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);
     }
 
-    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
-    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,
-        Action<IKeyFrameHandler, IKeyFrameGroupHandler?> onFound) where T : IKeyFrameHandler
+        Action<ICelHandler, ICelGroupHandler?> onFound) where T : ICelHandler
     {
         for (var i = 0; i < root.Count; i++)
         {
@@ -347,9 +347,9 @@ internal class AnimationDataViewModel : ObservableObject, IAnimationHandler
                 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)
                 {
                     return true;
@@ -365,10 +365,10 @@ internal class AnimationDataViewModel : ObservableObject, IAnimationHandler
     {
         var allLayers = Document.StructureHelper.GetAllLayers();
         var unsortedKeyFrames = keyFrames.ToList();
-        var layerKeyFrames = new List<KeyFrameGroupViewModel>();
+        var layerKeyFrames = new List<CelGroupViewModel>();
         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)
             {
                 layerKeyFrames.Insert(0, group);
@@ -377,7 +377,7 @@ internal class AnimationDataViewModel : ObservableObject, IAnimationHandler
 
         foreach (var remaining in unsortedKeyFrames)
         {
-            if (remaining is KeyFrameGroupViewModel group && !layerKeyFrames.Contains(group))
+            if (remaining is CelGroupViewModel group && !layerKeyFrames.Contains(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;
 
-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 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);
             foreach (var child in Children)
             {
-                if (child is KeyFrameViewModel keyFrame)
+                if (child is CelViewModel keyFrame)
                 {
                     keyFrame.IsCollapsed = value;
                 }
@@ -37,7 +37,7 @@ internal class KeyFrameGroupViewModel : KeyFrameViewModel, IKeyFrameGroupHandler
     {
         foreach (var child in Children)
         {
-            if(child is KeyFrameViewModel keyFrame)
+            if(child is CelViewModel keyFrame)
             {
                 keyFrame.SetVisibility(isVisible);
             }
@@ -46,7 +46,7 @@ internal class KeyFrameGroupViewModel : KeyFrameViewModel, IKeyFrameGroupHandler
         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)
     {
         Children.CollectionChanged += ChildrenOnCollectionChanged;
@@ -68,7 +68,7 @@ internal class KeyFrameGroupViewModel : KeyFrameViewModel, IKeyFrameGroupHandler
         {
             foreach (var item in e.NewItems)
             {
-                if (item is KeyFrameViewModel keyFrame)
+                if (item is CelViewModel keyFrame)
                 {
                     keyFrame.IsCollapsed = IsCollapsed;
                     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;
 
-internal abstract class KeyFrameViewModel : ObservableObject, IKeyFrameHandler
+internal abstract class CelViewModel : ObservableObject, ICelHandler
 {
     private PreviewPainter? previewPainter;
     private int startFrameBindable;
@@ -26,7 +26,7 @@ internal abstract class KeyFrameViewModel : ObservableObject, IKeyFrameHandler
     public DocumentViewModel Document { get; }
     protected DocumentInternalParts Internals { get; }
 
-    IDocument IKeyFrameHandler.Document => Document;
+    IDocument ICelHandler.Document => Document;
 
     public PreviewPainter? PreviewPainter
     {
@@ -93,7 +93,7 @@ internal abstract class KeyFrameViewModel : ObservableObject, IKeyFrameHandler
         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)
     {
         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;
 
-internal class KeyFrameCollection : ObservableCollection<KeyFrameGroupViewModel>
+internal class KeyFrameCollection : ObservableCollection<CelGroupViewModel>
 {
     public KeyFrameCollection()
     {
         
     }
 
-    public KeyFrameCollection(IEnumerable<KeyFrameGroupViewModel> source)
+    public KeyFrameCollection(IEnumerable<CelGroupViewModel> 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()
     {
         OnPropertyChanged(new PropertyChangedEventArgs(nameof(FrameCount)));
     }
 
-    public void NotifyCollectionChanged(NotifyCollectionChangedAction action, KeyFrameViewModel keyFrame)
+    public void NotifyCollectionChanged(NotifyCollectionChangedAction action, CelViewModel cel)
     {
         NotifyCollectionChanged();
         if (action == NotifyCollectionChangedAction.Add)
         {
-            KeyFrameAdded?.Invoke(keyFrame);
+            KeyFrameAdded?.Invoke(cel);
         }
         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)
         {

+ 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;
         IsEnabled = false;
         ShowApplyButton = false;
+        IsSizeBoxEnabled = false;
     }
 
     public bool Nudge(VecD distance)

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

@@ -75,7 +75,7 @@ internal class AnimationsViewModel : SubViewModel<ViewModelMain>
             "Raster",
             activeDocument.AnimationDataViewModel.FrameRateBindable,
             activeDocument.AnimationDataViewModel.FramesCount,
-            activeDocument.AnimationDataViewModel.AllKeyFrames.Count);
+            activeDocument.AnimationDataViewModel.AllCels.Count);
     }
 
     [Command.Basic("PixiEditor.Animation.ToggleOnionSkinning", "TOGGLE_ONION_SKINNING",
@@ -89,28 +89,28 @@ internal class AnimationsViewModel : SubViewModel<ViewModelMain>
         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)]
-    public void DeleteKeyFrames()
+    public void DeleteCels()
     {
         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)
             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--;
             }
         }
 
-        activeDocument.AnimationDataViewModel.DeleteKeyFrames(keyFrameIds);
+        activeDocument.AnimationDataViewModel.DeleteCels(celIds);
     }
 
     [Command.Internal("PixiEditor.Animation.ChangeKeyFramesStartPos")]
@@ -135,8 +135,8 @@ internal class AnimationsViewModel : SubViewModel<ViewModelMain>
     private static int GetActiveFrame(DocumentViewModel activeDocument, Guid targetLayer)
     {
         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)
             {

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

@@ -50,6 +50,8 @@ internal class ColorsViewModel : SubViewModel<ViewModelMain>, IColorsHandler
     public LocalPalettesFetcher LocalPaletteFetcher => _localPaletteFetcher ??=
         (LocalPalettesFetcher)PaletteProvider.DataSources.FirstOrDefault(x => x is LocalPalettesFetcher)!;
 
+    public bool ColorsTempSwapped { get; private set; }
+    
     private Color primaryColor = Colors.Black;
     private Color secondaryColor = Colors.White;
     private ColorState primaryColorState;
@@ -364,9 +366,19 @@ internal class ColorsViewModel : SubViewModel<ViewModelMain>, IColorsHandler
         Icon = PixiPerfectIcons.ColorsSwap, AnalyticsTrack = true)]
     public void SwapColors(object parameter)
     {
+        if (parameter is true)
+        {
+            ColorsTempSwapped = !ColorsTempSwapped;
+        }
+        else
+        {
+            ColorsTempSwapped = false;    
+        }
+        
         (PrimaryColor, SecondaryColor) = (SecondaryColor, PrimaryColor);
     }
 
+
     public void AddSwatch(PaletteColor color)
     {
         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.Input;
 using Drawie.Numerics;
+using PixiEditor.Models.DocumentModels.UpdateableChangeExecutors.Features;
 using PixiEditor.ViewModels.Document;
 using PixiEditor.ViewModels.Tools.Tools;
 using PixiEditor.Views;
@@ -26,7 +27,6 @@ namespace PixiEditor.ViewModels.SubViewModels;
 #nullable enable
 internal class IoViewModel : SubViewModel<ViewModelMain>
 {
-    private bool hadSwapped;
     private int? previousEraseSize;
     private bool hadSharedToolbar;
     private bool? drawingWithRight;
@@ -196,6 +196,11 @@ internal class IoViewModel : SubViewModel<ViewModelMain>
         activeDocument.EventInlet.OnCanvasLeftMouseButtonDown(args);
         Owner.ToolsSubViewModel.UseToolEventInlet(args.PositionOnCanvas, args.Button);
 
+        if (args.Button == MouseButton.Right)
+        {
+            HandleRightSwapColor();
+        }
+
         Analytics.SendUseTool(Owner.ToolsSubViewModel.ActiveTool, args.PositionOnCanvas, activeDocument.SizeBindable);
     }
 
@@ -208,17 +213,15 @@ internal class IoViewModel : SubViewModel<ViewModelMain>
         switch (tools.RightClickMode)
         {
             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;
             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)
     {
         var currentToolSize = tools.ActiveTool.Toolbar.Settings.FirstOrDefault(x => x.Name == "ToolSize");
@@ -295,8 +314,6 @@ internal class IoViewModel : SubViewModel<ViewModelMain>
         drawingWithRight = null;
 
         HandleRightMouseUp(button, tools);
-
-        hadSwapped = false;
     }
 
     private void HandleRightMouseUp(MouseButton button, IToolsHandler tools)
@@ -306,7 +323,7 @@ internal class IoViewModel : SubViewModel<ViewModelMain>
             case MouseButton.Middle:
                 tools.RestorePreviousTool();
                 break;
-            case MouseButton.Right when hadSwapped &&
+            case MouseButton.Right when Owner.ColorsSubViewModel.ColorsTempSwapped &&
                                         (tools.RightClickMode == RightClickMode.SecondaryColor ||
                                          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 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 bool StopsLinkedToolOnUse => false;
     public override bool IsErasable => false;

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

@@ -17,7 +17,7 @@ namespace PixiEditor.Views.Animations;
 [PseudoClasses(":selected")]
 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));
 
     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);
     }
 
-    public KeyFrameViewModel Item
+    public CelViewModel Item
     {
         get => GetValue(ItemProperty);
         set => SetValue(ItemProperty, value);
@@ -85,7 +85,7 @@ internal class KeyFrame : TemplatedControl
         _resizePanelLeft.PointerCaptureLost += UpdateKeyFrame;
         _resizePanelRight.PointerCaptureLost += UpdateKeyFrame;
 
-        if (Item is not KeyFrameGroupViewModel)
+        if (Item is not CelGroupViewModel)
         {
             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 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;
@@ -190,7 +190,7 @@ internal class Timeline : TemplatedControl, INotifyPropertyChanged
     private Vector clickPos;
     
     private bool shouldClearNextSelection = true;
-    private KeyFrameViewModel clickedKeyFrame;
+    private CelViewModel clickedCel;
     private bool dragged;
     private int dragStartFrame;
 
@@ -210,12 +210,12 @@ internal class Timeline : TemplatedControl, INotifyPropertyChanged
             new DispatcherTimer(DispatcherPriority.Render) { Interval = TimeSpan.FromMilliseconds(1000f / Fps) };
         _playTimer.Tick += PlayTimerOnTick;
         PressedKeyFrameCommand = new RelayCommand<PointerPressedEventArgs>(KeyFramePressed);
-        ClearSelectedKeyFramesCommand = new RelayCommand<KeyFrameViewModel>(ClearSelectedKeyFrames);
+        ClearSelectedKeyFramesCommand = new RelayCommand<CelViewModel>(ClearSelectedKeyFrames);
         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)
         {
@@ -245,7 +245,7 @@ internal class Timeline : TemplatedControl, INotifyPropertyChanged
         {
             ChangeKeyFramesLengthCommand.Execute((SelectedKeyFrames.Select(x => x.Id).ToArray(), 0, true));
         }
-        clickedKeyFrame = null;
+        clickedCel = null;
     }
 
     protected override void OnApplyTemplate(TemplateAppliedEventArgs e)
@@ -280,7 +280,7 @@ internal class Timeline : TemplatedControl, INotifyPropertyChanged
         _keyFramesHost = e.NameScope.Find<ItemsControl>("PART_KeyFramesHost");
     }
     
-    private void KeyFramesReleased(KeyFrameViewModel? e)
+    private void KeyFramesReleased(CelViewModel? e)
     {
         if (!dragged)
         {
@@ -293,21 +293,21 @@ internal class Timeline : TemplatedControl, INotifyPropertyChanged
         }
 
         dragged = false;
-        clickedKeyFrame = null;
+        clickedCel = null;
     }
 
     private void KeyFramesDragged(PointerEventArgs? e)
     {
-        if (clickedKeyFrame == null) return;
+        if (clickedCel == null) return;
 
         int frameUnderMouse = MousePosToFrame(e);
         int delta = frameUnderMouse - dragStartFrame;
 
         if (delta != 0)
         {
-            if (!clickedKeyFrame.IsSelected)
+            if (!clickedCel.IsSelected)
             {
-                SelectKeyFrame(clickedKeyFrame);
+                SelectKeyFrame(clickedCel);
             }
 
             dragged = true;
@@ -318,7 +318,7 @@ internal class Timeline : TemplatedControl, INotifyPropertyChanged
         }
     }
 
-    private void ClearSelectedKeyFrames(KeyFrameViewModel? keyFrame)
+    private void ClearSelectedKeyFrames(CelViewModel? keyFrame)
     {
         ClearSelectedKeyFrames();
     }
@@ -335,7 +335,7 @@ internal class Timeline : TemplatedControl, INotifyPropertyChanged
         }
 
         e.Pointer.Capture(target);
-        clickedKeyFrame = target.Item;
+        clickedCel = target.Item;
         dragStartFrame = MousePosToFrame(e);
         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(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)));
@@ -642,13 +642,13 @@ internal class Timeline : TemplatedControl, INotifyPropertyChanged
     
     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)));
             }
-            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)));
             }

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

@@ -12,10 +12,10 @@ namespace PixiEditor.Views.Animations;
 [PseudoClasses(":collapsed")]
 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));
 
-    public KeyFrameGroupViewModel Item
+    public CelGroupViewModel Item
     {
         get => GetValue(ItemProperty);
         set => SetValue(ItemProperty, value);

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

@@ -23,6 +23,6 @@
         OnionOpacity="{Binding DocumentManagerSubViewModel.ActiveDocument.AnimationDataViewModel.OnionOpacityBindable, Mode=TwoWay}"
         IsPlaying="{Binding DocumentManagerSubViewModel.ActiveDocument.AnimationDataViewModel.IsPlayingBindable, Mode=TwoWay}"
         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}"/>
 </UserControl>

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

@@ -49,7 +49,6 @@
             </EventTriggerBehavior>-->
         </Interaction.Behaviors>
         <overlays:TogglableFlyout Margin="5" Icon="{DynamicResource icon-tool}"
-                                  ui:Translator.TooltipKey="VIEWPORT_SETTINGS"
                                   ZIndex="2" HorizontalAlignment="Right" VerticalAlignment="Top">
             <overlays:TogglableFlyout.Child>
                 <Border Padding="5"
@@ -64,6 +63,7 @@
                                     BorderThickness="{DynamicResource ThemeBorderThickness}"
                                     CornerRadius="{DynamicResource ControlCornerRadius}"
                                     Background="{DynamicResource ThemeControlHighBrush}"
+                                    ui:Translator.TooltipKey="VIEWPORT_ROTATION"
                                     VerticalAlignment="Center">
                                 <TextBlock TextAlignment="Center"
                                            VerticalAlignment="Center"
@@ -126,7 +126,7 @@
                         <Separator />
                         <StackPanel HorizontalAlignment="Right" Orientation="Horizontal">
                             <ToggleButton Margin="10 0 0 0" Width="32" Height="32"
-                                          ui:Translator.TooltipKey="TOGGLE_SNAPPING"
+                                          ui:Translator.TooltipKey="TOGGLE_GRIDLINES"
                                           Classes="OverlayToggleButton pixi-icon"
                                           Content="{DynamicResource icon-gridlines}"
                                           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
         };
-
+        
         lineToolOverlay.Bind(Visual.IsVisibleProperty, isVisibleBinding);
         lineToolOverlay.Bind(LineToolOverlay.SnappingControllerProperty, snappingBinding);
         lineToolOverlay.Bind(LineToolOverlay.ActionCompletedProperty, actionCompletedBinding);
@@ -244,6 +244,9 @@ internal class ViewportOverlays
         lineToolOverlay.Bind(LineToolOverlay.LineEndProperty, lineEndBinding);
         lineToolOverlay.Bind(LineToolOverlay.ShowHandlesProperty, showHandlesBinding);
         lineToolOverlay.Bind(LineToolOverlay.IsSizeBoxEnabledProperty, isSizeBoxEnabledBinding);
+        lineToolOverlay.Bind(LineToolOverlay.ShowHandlesProperty, showHandlesBinding);
+        lineToolOverlay.Bind(LineToolOverlay.IsSizeBoxEnabledProperty, isSizeBoxEnabledBinding);
+        lineToolOverlay.Bind(Visual.IsVisibleProperty, isVisibleBinding);
     }
 
     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()
     {
         Color = Colors.Black, StrokeWidth = 1, Style = PaintStyle.Stroke, IsAntiAliased = true
@@ -131,7 +129,7 @@ internal class LineToolOverlay : Overlay
         moveHandle.StrokePaint = blackPaint;
         moveHandle.OnDrag += MoveHandleOnDrag;
         endHandle.Cursor = new Cursor(StandardCursorType.Arrow);
-        moveHandle.OnHover += (handle, _)=> Refresh();
+        moveHandle.OnHover += (handle, _) => Refresh();
         moveHandle.OnRelease += OnHandleRelease;
         AddHandle(moveHandle);
 
@@ -141,9 +139,15 @@ internal class LineToolOverlay : Overlay
     protected override void OnOverlayPointerMoved(OverlayPointerArgs args)
     {
         base.OnOverlayPointerMoved(args);
+
         lastMousePos = args.Point;
     }
 
+    public override bool TestHit(VecD point)
+    {
+        return IsVisible;
+    }
+
     private void OnHandleRelease(Handle obj, OverlayPointerArgs args)
     {
         if (SnappingController != null)
@@ -160,7 +164,6 @@ internal class LineToolOverlay : Overlay
     protected override void ZoomChanged(double newZoom)
     {
         blackPaint.StrokeWidth = 1 / (float)newZoom;
-        dashedStroke.UpdateZoom((float)newZoom);
         infoBox.ZoomScale = newZoom;
     }
 
@@ -177,8 +180,6 @@ internal class LineToolOverlay : Overlay
 
         moveHandle.Position = TransformHelper.GetHandlePos(new ShapeCorners(center, size), ZoomScale, moveHandle.Size);
 
-        dashedStroke.Draw(context, mappedStart, mappedEnd);
-
         if (ShowHandles)
         {
             startHandle.Draw(context);
@@ -200,6 +201,7 @@ internal class LineToolOverlay : Overlay
 
         movedWhileMouseDown = false;
         mouseDownPos = args.Point;
+        
         lineStartOnMouseDown = LineStart;
         lineEndOnMouseDown = LineEnd;
 
@@ -278,11 +280,14 @@ internal class LineToolOverlay : Overlay
 
     protected override void OnOverlayPointerReleased(OverlayPointerArgs args)
     {
+        IsSizeBoxEnabled = false;
+        
         if (args.InitialPressMouseButton != MouseButton.Left)
             return;
 
         if (movedWhileMouseDown && ActionCompleted is not null && ActionCompleted.CanExecute(null))
             ActionCompleted.Execute(null);
+        
     }
 
     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? RefreshCursorRequested;
     public event PointerEvent? PointerEnteredOverlay;
     public event PointerEvent? PointerExitedOverlay;
     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
     }
 
+    public void ForceRefreshCursor()
+    {
+        RefreshCursorRequested?.Invoke();
+    }
+
     public void CaptureHandle(Handle handle)
     {
         CapturedHandle = handle;
@@ -273,6 +279,11 @@ public abstract class Overlay : Decorator, IOverlay // TODO: Maybe make it not a
         {
             Refresh();
         }
+        else
+        {
+            Cursor = null;
+            RefreshCursorRequested?.Invoke();
+        }
     }
 
     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;
 
         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 = new Paint
         {
@@ -317,6 +317,8 @@ internal class Scene : Zoombox.Zoombox, ICustomHitTest
         {
             OverlayPointerArgs args = ConstructPointerArgs(e);
 
+            Cursor = DefaultCursor;
+            
             if (capturedOverlay != null)
             {
                 capturedOverlay.MovePointer(args);
@@ -371,11 +373,13 @@ internal class Scene : Zoombox.Zoombox, ICustomHitTest
             }
             else
             {
-                for (var i = 0; i < mouseOverOverlays.Count; i++)
+                foreach (var overlay in AllOverlays)
                 {
-                    var overlay = mouseOverOverlays[i];
                     if (args.Handled) break;
                     if (!overlay.IsVisible) continue;
+                    
+                    if(!overlay.IsHitTestVisible || !overlay.TestHit(args.Point)) continue;
+                    
                     overlay.PressPointer(args);
                 }
             }
@@ -419,11 +423,13 @@ internal class Scene : Zoombox.Zoombox, ICustomHitTest
             }
             else
             {
-                foreach (Overlay overlay in mouseOverOverlays)
+                foreach (Overlay overlay in AllOverlays)
                 {
                     if (args.Handled) break;
                     if (!overlay.IsVisible) continue;
 
+                    if(!overlay.IsHitTestVisible || !overlay.TestHit(args.Point)) continue;
+                    
                     overlay.ReleasePointer(args);
                 }
             }
@@ -494,6 +500,7 @@ internal class Scene : Zoombox.Zoombox, ICustomHitTest
             foreach (Overlay overlay in e.OldItems)
             {
                 overlay.RefreshRequested -= QueueRender;
+                overlay.RefreshCursorRequested -= RefreshCursor;
             }
         }
 
@@ -502,6 +509,7 @@ internal class Scene : Zoombox.Zoombox, ICustomHitTest
             foreach (Overlay overlay in e.NewItems)
             {
                 overlay.RefreshRequested += QueueRender;
+                overlay.RefreshCursorRequested += RefreshCursor;
             }
         }
     }
@@ -610,6 +618,23 @@ internal class Scene : Zoombox.Zoombox, ICustomHitTest
 
     #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()
     {
         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();
 
-        //TOOD: Fix
+        //TODO: Fix
         //InputLanguageManager.Current.InputLanguageChanged += (_, _) => UpdateText();
     }
 

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

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