Browse Source

Animations scaffolding

flabbet 1 year ago
parent
commit
b1673e3a91
41 changed files with 508 additions and 30 deletions
  1. 2 1
      src/PixiEditor.AvaloniaUI/Data/Localization/Languages/en.json
  2. 1 0
      src/PixiEditor.AvaloniaUI/Helpers/ServiceCollectionHelpers.cs
  3. 21 7
      src/PixiEditor.AvaloniaUI/Models/DocumentModels/DocumentUpdater.cs
  4. 10 0
      src/PixiEditor.AvaloniaUI/Models/Handlers/IAnimationHandler.cs
  5. 7 0
      src/PixiEditor.AvaloniaUI/Models/Handlers/IClipHandler.cs
  6. 1 0
      src/PixiEditor.AvaloniaUI/Models/Handlers/IDocument.cs
  7. 6 0
      src/PixiEditor.AvaloniaUI/Models/Handlers/IRasterClipHandler.cs
  8. 13 0
      src/PixiEditor.AvaloniaUI/Models/Rendering/AffectedAreasGatherer.cs
  9. 7 0
      src/PixiEditor.AvaloniaUI/PixiEditor.AvaloniaUI.csproj
  10. 1 0
      src/PixiEditor.AvaloniaUI/Styles/PixiEditor.Controls.axaml
  11. 60 0
      src/PixiEditor.AvaloniaUI/Styles/Templates/Timeline.axaml
  12. 24 12
      src/PixiEditor.AvaloniaUI/ViewModels/Dock/LayoutManager.cs
  13. 27 0
      src/PixiEditor.AvaloniaUI/ViewModels/Dock/TimelineDockViewModel.cs
  14. 9 0
      src/PixiEditor.AvaloniaUI/ViewModels/Document/AnimationClipViewModel.cs
  15. 27 0
      src/PixiEditor.AvaloniaUI/ViewModels/Document/AnimationDataViewModel.cs
  16. 3 0
      src/PixiEditor.AvaloniaUI/ViewModels/Document/DocumentViewModel.cs
  17. 13 0
      src/PixiEditor.AvaloniaUI/ViewModels/Document/RasterClipViewModel.cs
  18. 28 0
      src/PixiEditor.AvaloniaUI/ViewModels/SubViewModels/AnimationsViewModel.cs
  19. 3 1
      src/PixiEditor.AvaloniaUI/ViewModels/ViewModelMain.cs
  20. 22 0
      src/PixiEditor.AvaloniaUI/Views/Animations/Timeline.cs
  21. 15 0
      src/PixiEditor.AvaloniaUI/Views/Dock/TimelineDockView.axaml
  22. 12 0
      src/PixiEditor.AvaloniaUI/Views/Dock/TimelineDockView.axaml.cs
  23. 7 0
      src/PixiEditor.ChangeableDocument/ChangeInfos/Animation/CreateRasterClip_ChangeInfo.cs
  24. 5 0
      src/PixiEditor.ChangeableDocument/ChangeInfos/Animation/DeleteClip_ChangeInfo.cs
  25. 63 0
      src/PixiEditor.ChangeableDocument/Changeables/Animations/AnimationData.cs
  26. 12 0
      src/PixiEditor.ChangeableDocument/Changeables/Animations/Clip.cs
  27. 37 0
      src/PixiEditor.ChangeableDocument/Changeables/Animations/RasterClip.cs
  28. 3 0
      src/PixiEditor.ChangeableDocument/Changeables/Document.cs
  29. 7 0
      src/PixiEditor.ChangeableDocument/Changeables/Interfaces/IReadOnlyAnimationData.cs
  30. 10 0
      src/PixiEditor.ChangeableDocument/Changeables/Interfaces/IReadOnlyClip.cs
  31. 2 0
      src/PixiEditor.ChangeableDocument/Changeables/Interfaces/IReadOnlyDocument.cs
  32. 41 0
      src/PixiEditor.ChangeableDocument/Changes/Animation/CreateRasterClip_Change.cs
  33. 1 1
      src/PixiEditor.ChangeableDocument/Changes/Properties/LayerStructure/CreateStructureMemberMask_Change.cs
  34. 1 1
      src/PixiEditor.ChangeableDocument/Changes/Properties/LayerStructure/DeleteStructureMemberMask_Change.cs
  35. 1 1
      src/PixiEditor.ChangeableDocument/Changes/Properties/LayerStructure/LayerLockTransparency_Change.cs
  36. 1 1
      src/PixiEditor.ChangeableDocument/Changes/Properties/LayerStructure/StructureMemberBlendMode_Change.cs
  37. 1 1
      src/PixiEditor.ChangeableDocument/Changes/Properties/LayerStructure/StructureMemberClipToMemberBelow_Change.cs
  38. 1 1
      src/PixiEditor.ChangeableDocument/Changes/Properties/LayerStructure/StructureMemberIsVisible_Change.cs
  39. 1 1
      src/PixiEditor.ChangeableDocument/Changes/Properties/LayerStructure/StructureMemberMaskIsVisible_Change.cs
  40. 1 1
      src/PixiEditor.ChangeableDocument/Changes/Properties/LayerStructure/StructureMemberName_Change.cs
  41. 1 1
      src/PixiEditor.ChangeableDocument/Changes/Properties/LayerStructure/StructureMemberOpacity_UpdateableChange.cs

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

@@ -592,5 +592,6 @@
   "CRASH_NOT_ALL_DOCUMENTS_RECOVERED_TITLE": "Not all documents were recovered",
   "CRASH_NOT_ALL_DOCUMENTS_RECOVERED_TITLE": "Not all documents were recovered",
   "CRASH_NOT_ALL_DOCUMENTS_RECOVERED": "Could not recover all documents. Git gud at saving your work.",
   "CRASH_NOT_ALL_DOCUMENTS_RECOVERED": "Could not recover all documents. Git gud at saving your work.",
   "SEND": "Send report",
   "SEND": "Send report",
-  "OPEN_DOCKABLE_MENU": "Open Tab"
+  "OPEN_DOCKABLE_MENU": "Open Tab",
+  "TIMELINE_TITLE": "Timeline",
 }
 }

+ 1 - 0
src/PixiEditor.AvaloniaUI/Helpers/ServiceCollectionHelpers.cs

@@ -57,6 +57,7 @@ internal static class ServiceCollectionHelpers
             .AddSingleton<SelectionViewModel>()
             .AddSingleton<SelectionViewModel>()
             .AddSingleton<ViewOptionsViewModel>()
             .AddSingleton<ViewOptionsViewModel>()
             .AddSingleton<ColorsViewModel>()
             .AddSingleton<ColorsViewModel>()
+            .AddSingleton<AnimationsViewModel>()
             .AddSingleton<IColorsHandler, ColorsViewModel>(x => x.GetRequiredService<ColorsViewModel>())
             .AddSingleton<IColorsHandler, ColorsViewModel>(x => x.GetRequiredService<ColorsViewModel>())
             .AddSingleton<RegistryViewModel>()
             .AddSingleton<RegistryViewModel>()
             .AddSingleton(static x => new DiscordViewModel(x.GetService<ViewModelMain>(), "764168193685979138"))
             .AddSingleton(static x => new DiscordViewModel(x.GetService<ViewModelMain>(), "764168193685979138"))

+ 21 - 7
src/PixiEditor.AvaloniaUI/Models/DocumentModels/DocumentUpdater.cs

@@ -1,21 +1,19 @@
-using System.Collections.Generic;
-using Avalonia.Media.Imaging;
-using ChunkyImageLib;
+using ChunkyImageLib;
 using ChunkyImageLib.DataHolders;
 using ChunkyImageLib.DataHolders;
 using PixiEditor.AvaloniaUI.Helpers;
 using PixiEditor.AvaloniaUI.Helpers;
 using PixiEditor.AvaloniaUI.Models.DocumentPassthroughActions;
 using PixiEditor.AvaloniaUI.Models.DocumentPassthroughActions;
 using PixiEditor.AvaloniaUI.Models.Handlers;
 using PixiEditor.AvaloniaUI.Models.Handlers;
 using PixiEditor.AvaloniaUI.Models.Layers;
 using PixiEditor.AvaloniaUI.Models.Layers;
+using PixiEditor.AvaloniaUI.ViewModels.Document;
+using PixiEditor.ChangeableDocument.Actions.Generated;
 using PixiEditor.ChangeableDocument.ChangeInfos;
 using PixiEditor.ChangeableDocument.ChangeInfos;
+using PixiEditor.ChangeableDocument.ChangeInfos.Animation;
 using PixiEditor.ChangeableDocument.ChangeInfos.Drawing;
 using PixiEditor.ChangeableDocument.ChangeInfos.Drawing;
 using PixiEditor.ChangeableDocument.ChangeInfos.Properties;
 using PixiEditor.ChangeableDocument.ChangeInfos.Properties;
 using PixiEditor.ChangeableDocument.ChangeInfos.Root;
 using PixiEditor.ChangeableDocument.ChangeInfos.Root;
 using PixiEditor.ChangeableDocument.ChangeInfos.Root.ReferenceLayerChangeInfos;
 using PixiEditor.ChangeableDocument.ChangeInfos.Root.ReferenceLayerChangeInfos;
 using PixiEditor.ChangeableDocument.ChangeInfos.Structure;
 using PixiEditor.ChangeableDocument.ChangeInfos.Structure;
 using PixiEditor.ChangeableDocument.Enums;
 using PixiEditor.ChangeableDocument.Enums;
-using PixiEditor.DrawingApi.Core.Numerics;
-using PixiEditor.DrawingApi.Core.Surface;
-using PixiEditor.DrawingApi.Core.Surface.ImageData;
 using PixiEditor.Numerics;
 using PixiEditor.Numerics;
 
 
 namespace PixiEditor.AvaloniaUI.Models.DocumentModels;
 namespace PixiEditor.AvaloniaUI.Models.DocumentModels;
@@ -48,6 +46,7 @@ internal class DocumentUpdater
         if (arbitraryInfo is null)
         if (arbitraryInfo is null)
             return;
             return;
 
 
+        //TODO: Find a more elegant way to do this
         switch (arbitraryInfo)
         switch (arbitraryInfo)
         {
         {
             case CreateStructureMember_ChangeInfo info:
             case CreateStructureMember_ChangeInfo info:
@@ -128,7 +127,12 @@ internal class DocumentUpdater
             case ClearSoftSelectedMembers_PassthroughAction info:
             case ClearSoftSelectedMembers_PassthroughAction info:
                 ProcessClearSoftSelectedMembers(info);
                 ProcessClearSoftSelectedMembers(info);
                 break;
                 break;
-                
+            case CreateRasterClip_ChangeInfo info:
+                ProcessCreateRasterClip(info);
+                break;
+            case DeleteClip_ChangeInfo info:
+                ProcessDeleteClip(info);
+                break;
         }
         }
     }
     }
 
 
@@ -395,4 +399,14 @@ internal class DocumentUpdater
         // TODO: Make sure property changed events are raised internally
         // TODO: Make sure property changed events are raised internally
         //doc.InternalRaiseLayersChanged(new LayersChangedEventArgs(info.GuidValue, LayerAction.Move));
         //doc.InternalRaiseLayersChanged(new LayersChangedEventArgs(info.GuidValue, LayerAction.Move));
     }
     }
+    
+    private void ProcessCreateRasterClip(CreateRasterClip_ChangeInfo info)
+    {
+        doc.AnimationHandler.Clips.Add(new RasterClipViewModel(info.TargetLayerGuid, info.Frame, 1));
+    }
+    
+    private void ProcessDeleteClip(DeleteClip_ChangeInfo info)
+    {
+        doc.AnimationHandler.Clips.RemoveAt(info.IndexOfDeletedClip);
+    }
 }
 }

+ 10 - 0
src/PixiEditor.AvaloniaUI/Models/Handlers/IAnimationHandler.cs

@@ -0,0 +1,10 @@
+using System.Collections.ObjectModel;
+using PixiEditor.ChangeableDocument.Changeables.Interfaces;
+
+namespace PixiEditor.AvaloniaUI.Models.Handlers;
+
+public interface IAnimationHandler
+{
+    public ObservableCollection<IClipHandler> Clips { get; }
+    public void AddRasterClip(Guid targetLayerGuid, int frame, bool cloneFromExisting);
+}

+ 7 - 0
src/PixiEditor.AvaloniaUI/Models/Handlers/IClipHandler.cs

@@ -0,0 +1,7 @@
+namespace PixiEditor.AvaloniaUI.Models.Handlers;
+
+public interface IClipHandler
+{
+    public int StartFrame { get; }
+    public int Duration { get; }
+}

+ 1 - 0
src/PixiEditor.AvaloniaUI/Models/Handlers/IDocument.cs

@@ -21,6 +21,7 @@ internal interface IDocument : IHandler
     public VecI SizeBindable { get; }
     public VecI SizeBindable { get; }
     public IStructureMemberHandler? SelectedStructureMember { get; }
     public IStructureMemberHandler? SelectedStructureMember { get; }
     public IReferenceLayerHandler ReferenceLayerHandler { get; }
     public IReferenceLayerHandler ReferenceLayerHandler { get; }
+    public IAnimationHandler AnimationHandler { get; }
     public VectorPath SelectionPathBindable { get; }
     public VectorPath SelectionPathBindable { get; }
     public IFolderHandler StructureRoot { get; }
     public IFolderHandler StructureRoot { get; }
     public Dictionary<ChunkResolution, Surface> Surfaces { get; set; }
     public Dictionary<ChunkResolution, Surface> Surfaces { get; set; }

+ 6 - 0
src/PixiEditor.AvaloniaUI/Models/Handlers/IRasterClipHandler.cs

@@ -0,0 +1,6 @@
+namespace PixiEditor.AvaloniaUI.Models.Handlers;
+
+public interface IRasterClipHandler : IClipHandler
+{
+    public Guid TargetLayerGuid { get; }
+}

+ 13 - 0
src/PixiEditor.AvaloniaUI/Models/Rendering/AffectedAreasGatherer.cs

@@ -4,6 +4,7 @@ using ChunkyImageLib.DataHolders;
 using PixiEditor.ChangeableDocument;
 using PixiEditor.ChangeableDocument;
 using PixiEditor.ChangeableDocument.Changeables.Interfaces;
 using PixiEditor.ChangeableDocument.Changeables.Interfaces;
 using PixiEditor.ChangeableDocument.ChangeInfos;
 using PixiEditor.ChangeableDocument.ChangeInfos;
+using PixiEditor.ChangeableDocument.ChangeInfos.Animation;
 using PixiEditor.ChangeableDocument.ChangeInfos.Drawing;
 using PixiEditor.ChangeableDocument.ChangeInfos.Drawing;
 using PixiEditor.ChangeableDocument.ChangeInfos.Properties;
 using PixiEditor.ChangeableDocument.ChangeInfos.Properties;
 using PixiEditor.ChangeableDocument.ChangeInfos.Root;
 using PixiEditor.ChangeableDocument.ChangeInfos.Root;
@@ -91,6 +92,18 @@ internal class AffectedAreasGatherer
                     AddAllToMainImage(info.GuidValue, false);
                     AddAllToMainImage(info.GuidValue, false);
                     AddAllToImagePreviews(info.GuidValue, true);
                     AddAllToImagePreviews(info.GuidValue, true);
                     break;
                     break;
+                case CreateRasterClip_ChangeInfo info:
+                    if (info.CloneFromExisting)
+                    {
+                        AddAllToMainImage(info.TargetLayerGuid);
+                        AddAllToImagePreviews(info.TargetLayerGuid);
+                    }
+                    else
+                    {
+                        AddWholeCanvasToMainImage();
+                        AddWholeCanvasToImagePreviews(info.TargetLayerGuid);
+                    }
+                    break;
             }
             }
         }
         }
     }
     }

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

@@ -138,4 +138,11 @@
     <UpToDateCheckInput Remove="Images\News\YouTube.png" />
     <UpToDateCheckInput Remove="Images\News\YouTube.png" />
   </ItemGroup>
   </ItemGroup>
 
 
+  <ItemGroup>
+    <Compile Update="Views\Dock\TimelineDockView.axaml.cs">
+      <DependentUpon>TimelineDockView.axaml</DependentUpon>
+      <SubType>Code</SubType>
+    </Compile>
+  </ItemGroup>
+
 </Project>
 </Project>

+ 1 - 0
src/PixiEditor.AvaloniaUI/Styles/PixiEditor.Controls.axaml

@@ -9,6 +9,7 @@
                 <MergeResourceInclude Source="avares://PixiEditor.AvaloniaUI/Styles/Buttons/DialogButtonTheme.axaml"/>
                 <MergeResourceInclude Source="avares://PixiEditor.AvaloniaUI/Styles/Buttons/DialogButtonTheme.axaml"/>
                 <MergeResourceInclude Source="avares://PixiEditor.AvaloniaUI/Styles/Templates/ShortcutBoxTemplate.axaml"/>
                 <MergeResourceInclude Source="avares://PixiEditor.AvaloniaUI/Styles/Templates/ShortcutBoxTemplate.axaml"/>
                 <MergeResourceInclude Source="avares://PixiEditor.AvaloniaUI/Styles/Templates/DocumentTabTemplate.axaml"/>
                 <MergeResourceInclude Source="avares://PixiEditor.AvaloniaUI/Styles/Templates/DocumentTabTemplate.axaml"/>
+                <MergeResourceInclude Source="avares://PixiEditor.AvaloniaUI/Styles/Templates/Timeline.axaml"/>
             </ResourceDictionary.MergedDictionaries>
             </ResourceDictionary.MergedDictionaries>
         </ResourceDictionary>
         </ResourceDictionary>
     </Styles.Resources>
     </Styles.Resources>

+ 60 - 0
src/PixiEditor.AvaloniaUI/Styles/Templates/Timeline.axaml

@@ -0,0 +1,60 @@
+<ResourceDictionary xmlns="https://github.com/avaloniaui"
+                    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
+                    xmlns:animations="clr-namespace:PixiEditor.AvaloniaUI.Views.Animations"
+                    xmlns:document="clr-namespace:PixiEditor.AvaloniaUI.ViewModels.Document"
+                    xmlns:handlers="clr-namespace:PixiEditor.AvaloniaUI.Models.Handlers">
+    <Design.PreviewWith>
+        <animations:Timeline>
+            <animations:Timeline.Clips>
+                <document:RasterClipViewModel Duration="100"/>
+            </animations:Timeline.Clips>
+        </animations:Timeline>
+    </Design.PreviewWith>
+
+    <ControlTheme TargetType="animations:Timeline" x:Key="{x:Type animations:Timeline}">
+        <Setter Property="Template">
+            <ControlTemplate>
+                <Grid>
+                    <Grid.RowDefinitions>
+                        <RowDefinition Height="Auto"/>
+                        <RowDefinition Height="Auto"/>
+                        <RowDefinition Height="*"/>
+                    </Grid.RowDefinitions>
+                    
+                    <ToggleButton Content="Play"/>
+                    
+                    <DockPanel Grid.Row="1" LastChildFill="True">
+                        <TextBlock Text="0" DockPanel.Dock="Left"/>
+                        <TextBlock Text="{Binding Clips.Count, RelativeSource={RelativeSource TemplatedParent}}" DockPanel.Dock="Right"/>
+                        <Slider Margin="10 0" Maximum="{Binding Clips.Count, RelativeSource={RelativeSource TemplatedParent}}"/>
+                    </DockPanel>
+                    
+                    <ScrollViewer Grid.Row="2" HorizontalScrollBarVisibility="Auto">
+                        <ItemsControl ItemsSource="{TemplateBinding Clips}">
+                            <ItemsControl.ItemsPanel>
+                                <ItemsPanelTemplate>
+                                    <StackPanel Orientation="Horizontal"/>
+                                </ItemsPanelTemplate>
+                            </ItemsControl.ItemsPanel>
+                            <ItemsControl.ItemTemplate>
+                                <DataTemplate DataType="handlers:IClipHandler">
+                                    <Border BorderBrush="Black" BorderThickness="1" Margin="2">
+                                        <Grid>
+                                            <Grid.ColumnDefinitions>
+                                                <ColumnDefinition Width="Auto"/>
+                                                <ColumnDefinition Width="*"/>
+                                            </Grid.ColumnDefinitions>
+                                            <TextBlock Text="{Binding StartFrame}"/>
+                                            <TextBlock Grid.Column="1" Text="{Binding Duration}"/>
+                                        </Grid>
+                                    </Border>
+                                </DataTemplate>
+                            </ItemsControl.ItemTemplate>
+                        </ItemsControl>
+                    </ScrollViewer>
+                </Grid>
+            </ControlTemplate>
+        </Setter>
+    </ControlTheme>
+
+</ResourceDictionary>

+ 24 - 12
src/PixiEditor.AvaloniaUI/ViewModels/Dock/LayoutManager.cs

@@ -25,7 +25,6 @@ internal class LayoutManager
 
 
     public LayoutManager()
     public LayoutManager()
     {
     {
-
     }
     }
 
 
     public void InitLayout(ViewModelMain mainViewModel)
     public void InitLayout(ViewModelMain mainViewModel)
@@ -33,9 +32,12 @@ internal class LayoutManager
         LayersDockViewModel layersDockViewModel = new(mainViewModel.DocumentManagerSubViewModel);
         LayersDockViewModel layersDockViewModel = new(mainViewModel.DocumentManagerSubViewModel);
         ColorPickerDockViewModel colorPickerDockViewModel = new(mainViewModel.ColorsSubViewModel);
         ColorPickerDockViewModel colorPickerDockViewModel = new(mainViewModel.ColorsSubViewModel);
         ColorSlidersDockViewModel colorSldersDockViewModel = new(mainViewModel.ColorsSubViewModel);
         ColorSlidersDockViewModel colorSldersDockViewModel = new(mainViewModel.ColorsSubViewModel);
-        NavigationDockViewModel navigationDockViewModel = new(mainViewModel.ColorsSubViewModel, mainViewModel.DocumentManagerSubViewModel);
+        NavigationDockViewModel navigationDockViewModel =
+            new(mainViewModel.ColorsSubViewModel, mainViewModel.DocumentManagerSubViewModel);
         SwatchesDockViewModel swatchesDockViewModel = new(mainViewModel.DocumentManagerSubViewModel);
         SwatchesDockViewModel swatchesDockViewModel = new(mainViewModel.DocumentManagerSubViewModel);
-        PaletteViewerDockViewModel paletteViewerDockViewModel = new(mainViewModel.ColorsSubViewModel, mainViewModel.DocumentManagerSubViewModel);
+        PaletteViewerDockViewModel paletteViewerDockViewModel =
+            new(mainViewModel.ColorsSubViewModel, mainViewModel.DocumentManagerSubViewModel);
+        TimelineDockViewModel timelineDockViewModel = new(mainViewModel.DocumentManagerSubViewModel);
 
 
         RegisterDockable(layersDockViewModel);
         RegisterDockable(layersDockViewModel);
         RegisterDockable(colorPickerDockViewModel);
         RegisterDockable(colorPickerDockViewModel);
@@ -43,15 +45,25 @@ internal class LayoutManager
         RegisterDockable(navigationDockViewModel);
         RegisterDockable(navigationDockViewModel);
         RegisterDockable(swatchesDockViewModel);
         RegisterDockable(swatchesDockViewModel);
         RegisterDockable(paletteViewerDockViewModel);
         RegisterDockable(paletteViewerDockViewModel);
+        RegisterDockable(timelineDockViewModel);
 
 
         DefaultLayout = new LayoutTree
         DefaultLayout = new LayoutTree
         {
         {
             Root = new DockableTree
             Root = new DockableTree
             {
             {
-                First = new DockableArea()
+                First = new DockableTree()
                 {
                 {
-                    Id = "DocumentArea",
-                    FallbackContent = new CreateDocumentFallbackView()
+                    First = new DockableArea()
+                    {
+                        Id = "DocumentArea", FallbackContent = new CreateDocumentFallbackView()
+                    },
+                    FirstSize = 0.75,
+                    SplitDirection = DockingDirection.Bottom,
+                    Second = new DockableArea
+                    {
+                        Id = "TimelineArea", 
+                        ActiveDockable = DockContext.CreateDockable(timelineDockViewModel)
+                    }
                 },
                 },
                 FirstSize = 0.8,
                 FirstSize = 0.8,
                 SplitDirection = DockingDirection.Right,
                 SplitDirection = DockingDirection.Right,
@@ -75,16 +87,14 @@ internal class LayoutManager
                         SplitDirection = DockingDirection.Bottom,
                         SplitDirection = DockingDirection.Bottom,
                         Second = new DockableArea
                         Second = new DockableArea
                         {
                         {
-                            Id = "LayersArea",
-                            ActiveDockable = DockContext.CreateDockable(layersDockViewModel)
+                            Id = "LayersArea", ActiveDockable = DockContext.CreateDockable(layersDockViewModel)
                         },
                         },
                     },
                     },
                     FirstSize = 0.66,
                     FirstSize = 0.66,
                     SplitDirection = DockingDirection.Bottom,
                     SplitDirection = DockingDirection.Bottom,
                     Second = new DockableArea
                     Second = new DockableArea
                     {
                     {
-                        Id = "NavigatorArea",
-                        ActiveDockable = DockContext.CreateDockable(navigationDockViewModel)
+                        Id = "NavigatorArea", ActiveDockable = DockContext.CreateDockable(navigationDockViewModel)
                     }
                     }
                 }
                 }
             }
             }
@@ -139,13 +149,15 @@ internal class LayoutManager
     private DockableArea? TryFindArea(string name)
     private DockableArea? TryFindArea(string name)
     {
     {
         DockableArea? result = null;
         DockableArea? result = null;
-        foreach(var element in ActiveLayout.Root)
+        foreach (var element in ActiveLayout.Root)
         {
         {
             if (element is DockableArea area && area.Id == name)
             if (element is DockableArea area && area.Id == name)
             {
             {
                 result = area;
                 result = area;
             }
             }
-        };
+        }
+
+        ;
 
 
         return result;
         return result;
     }
     }

+ 27 - 0
src/PixiEditor.AvaloniaUI/ViewModels/Dock/TimelineDockViewModel.cs

@@ -0,0 +1,27 @@
+using PixiEditor.AvaloniaUI.ViewModels.Document;
+using PixiEditor.Extensions.Common.Localization;
+
+namespace PixiEditor.AvaloniaUI.ViewModels.Dock;
+
+internal class TimelineDockViewModel : DockableViewModel
+{
+    public const string TabId = "Timeline";
+
+    public override string Id => TabId;
+    public override string Title => new LocalizedString("TIMELINE_TITLE");
+    public override bool CanFloat => true;
+    public override bool CanClose => true;
+    
+    private DocumentManagerViewModel documentManagerSubViewModel;
+
+    public DocumentManagerViewModel DocumentManagerSubViewModel
+    {
+        get => documentManagerSubViewModel;
+        set => SetProperty(ref documentManagerSubViewModel, value);
+    }
+
+    public TimelineDockViewModel(DocumentManagerViewModel documentManagerViewModel)
+    {
+        DocumentManagerSubViewModel = documentManagerViewModel;
+    }
+}

+ 9 - 0
src/PixiEditor.AvaloniaUI/ViewModels/Document/AnimationClipViewModel.cs

@@ -0,0 +1,9 @@
+using PixiEditor.AvaloniaUI.Models.Handlers;
+
+namespace PixiEditor.AvaloniaUI.ViewModels.Document;
+
+public class AnimationClipViewModel(int startFrame, int duration) : IClipHandler
+{
+    public int StartFrame { get; } = startFrame;
+    public int Duration { get; } = duration;
+}

+ 27 - 0
src/PixiEditor.AvaloniaUI/ViewModels/Document/AnimationDataViewModel.cs

@@ -0,0 +1,27 @@
+using System.Collections.ObjectModel;
+using CommunityToolkit.Mvvm.ComponentModel;
+using PixiEditor.AvaloniaUI.Models.DocumentModels;
+using PixiEditor.AvaloniaUI.Models.Handlers;
+using PixiEditor.ChangeableDocument.Actions.Generated;
+using PixiEditor.ChangeableDocument.Changeables.Interfaces;
+
+namespace PixiEditor.AvaloniaUI.ViewModels.Document;
+
+internal class AnimationDataViewModel : ObservableObject, IAnimationHandler
+{
+    public DocumentViewModel Document { get; }
+    protected DocumentInternalParts Internals { get; }
+    public ObservableCollection<IClipHandler> Clips { get; } = new();
+
+    public AnimationDataViewModel(DocumentViewModel document, DocumentInternalParts internals)
+    {
+        Document = document;
+        Internals = internals;
+    }
+
+    public void AddRasterClip(Guid targetLayerGuid, int frame, bool cloneFromExisting)
+    {
+        if (!Document.UpdateableChangeActive)
+            Internals.ActionAccumulator.AddFinishedActions(new CreateRasterClip_Action(targetLayerGuid, frame, cloneFromExisting));
+    }
+}

+ 3 - 0
src/PixiEditor.AvaloniaUI/ViewModels/Document/DocumentViewModel.cs

@@ -168,6 +168,7 @@ internal partial class DocumentViewModel : PixiObservableObject, IDocument
     public DocumentTransformViewModel TransformViewModel { get; }
     public DocumentTransformViewModel TransformViewModel { get; }
     public ReferenceLayerViewModel ReferenceLayerViewModel { get; }
     public ReferenceLayerViewModel ReferenceLayerViewModel { get; }
     public LineToolOverlayViewModel LineToolOverlayViewModel { get; }
     public LineToolOverlayViewModel LineToolOverlayViewModel { get; }
+    public AnimationDataViewModel AnimationDataViewModel { get; }
 
 
     public IReadOnlyCollection<IStructureMemberHandler> SoftSelectedStructureMembers => softSelectedStructureMembers;
     public IReadOnlyCollection<IStructureMemberHandler> SoftSelectedStructureMembers => softSelectedStructureMembers;
     private DocumentInternalParts Internals { get; }
     private DocumentInternalParts Internals { get; }
@@ -178,6 +179,7 @@ internal partial class DocumentViewModel : PixiObservableObject, IDocument
     public ILayerHandlerFactory LayerHandlerFactory { get; }
     public ILayerHandlerFactory LayerHandlerFactory { get; }
     public IFolderHandlerFactory FolderHandlerFactory { get; }
     public IFolderHandlerFactory FolderHandlerFactory { get; }
     IReferenceLayerHandler IDocument.ReferenceLayerHandler => ReferenceLayerViewModel;
     IReferenceLayerHandler IDocument.ReferenceLayerHandler => ReferenceLayerViewModel;
+    IAnimationHandler IDocument.AnimationHandler => AnimationDataViewModel;
 
 
 
 
     private DocumentViewModel()
     private DocumentViewModel()
@@ -191,6 +193,7 @@ internal partial class DocumentViewModel : PixiObservableObject, IDocument
 
 
         LayerHandlerFactory = new LayerHandlerFactory(this);
         LayerHandlerFactory = new LayerHandlerFactory(this);
         FolderHandlerFactory = new FolderHandlerFactory(this);
         FolderHandlerFactory = new FolderHandlerFactory(this);
+        AnimationDataViewModel = new(this, Internals);
 
 
         StructureRoot = new FolderViewModel(this, Internals, Internals.Tracker.Document.StructureRoot.GuidValue);
         StructureRoot = new FolderViewModel(this, Internals, Internals.Tracker.Document.StructureRoot.GuidValue);
 
 

+ 13 - 0
src/PixiEditor.AvaloniaUI/ViewModels/Document/RasterClipViewModel.cs

@@ -0,0 +1,13 @@
+using PixiEditor.AvaloniaUI.Models.Handlers;
+
+namespace PixiEditor.AvaloniaUI.ViewModels.Document;
+
+public class RasterClipViewModel : AnimationClipViewModel, IRasterClipHandler
+{
+    public Guid TargetLayerGuid { get; }
+    
+    public RasterClipViewModel(Guid targetLayerGuid, int startFrame, int duration) : base(startFrame, duration)
+    {
+        TargetLayerGuid = targetLayerGuid;
+    }
+}

+ 28 - 0
src/PixiEditor.AvaloniaUI/ViewModels/SubViewModels/AnimationsViewModel.cs

@@ -0,0 +1,28 @@
+using PixiEditor.AvaloniaUI.Models.Commands.Attributes.Commands;
+
+namespace PixiEditor.AvaloniaUI.ViewModels.SubViewModels;
+
+[Command.Group("PixiEditor.Animations", "ANIMATIONS")]
+internal class AnimationsViewModel : SubViewModel<ViewModelMain>
+{
+    public AnimationsViewModel(ViewModelMain owner) : base(owner)
+    {
+        
+    }
+    
+    [Command.Debug("PixiEditor.Animations.CreateRasterClip", "Create Raster Clip", "Create a raster clip")]
+    public void CreateRasterClip()
+    {
+        var activeDocument = Owner.DocumentManagerSubViewModel.ActiveDocument;
+        if (activeDocument == null)
+        {
+            return;
+        }
+        
+        
+        activeDocument.AnimationDataViewModel.AddRasterClip(
+            activeDocument.SelectedStructureMember.GuidValue, 
+            activeDocument.AnimationDataViewModel.Clips.Count,
+            false);
+    }
+}

+ 3 - 1
src/PixiEditor.AvaloniaUI/ViewModels/ViewModelMain.cs

@@ -75,6 +75,7 @@ internal partial class ViewModelMain : ViewModelBase, ICommandsHandler
     public LayoutViewModel LayoutSubViewModel { get; set; }
     public LayoutViewModel LayoutSubViewModel { get; set; }
 
 
     public MenuBarViewModel MenuBarViewModel { get; set; }
     public MenuBarViewModel MenuBarViewModel { get; set; }
+    public AnimationsViewModel AnimationsSubViewModel { get; set; }
 
 
     public IPreferences Preferences { get; set; }
     public IPreferences Preferences { get; set; }
     public ILocalizationProvider LocalizationProvider { get; set; }
     public ILocalizationProvider LocalizationProvider { get; set; }
@@ -160,7 +161,8 @@ internal partial class ViewModelMain : ViewModelBase, ICommandsHandler
         ToolsSubViewModel?.SetupToolsTooltipShortcuts(services);
         ToolsSubViewModel?.SetupToolsTooltipShortcuts(services);
 
 
         SearchSubViewModel = services.GetService<SearchViewModel>();
         SearchSubViewModel = services.GetService<SearchViewModel>();
-
+        
+        AnimationsSubViewModel = services.GetService<AnimationsViewModel>();
 
 
         ExtensionsSubViewModel = services.GetService<ExtensionsViewModel>(); // Must be last
         ExtensionsSubViewModel = services.GetService<ExtensionsViewModel>(); // Must be last
 
 

+ 22 - 0
src/PixiEditor.AvaloniaUI/Views/Animations/Timeline.cs

@@ -0,0 +1,22 @@
+using System.Collections.ObjectModel;
+using Avalonia;
+using Avalonia.Controls;
+using Avalonia.Controls.Primitives;
+using PixiEditor.AvaloniaUI.Models.Handlers;
+using PixiEditor.AvaloniaUI.ViewModels.Document;
+
+namespace PixiEditor.AvaloniaUI.Views.Animations;
+
+public class Timeline : TemplatedControl
+{
+    public static readonly StyledProperty<ObservableCollection<IClipHandler>> ClipsProperty =
+        AvaloniaProperty.Register<Timeline, ObservableCollection<IClipHandler>>(
+            nameof(Clips));
+
+    public ObservableCollection<IClipHandler> Clips
+    {
+        get => GetValue(ClipsProperty);
+        set => SetValue(ClipsProperty, value);
+    }
+}
+

+ 15 - 0
src/PixiEditor.AvaloniaUI/Views/Dock/TimelineDockView.axaml

@@ -0,0 +1,15 @@
+<UserControl xmlns="https://github.com/avaloniaui"
+             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
+             xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
+             xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
+             xmlns:document="clr-namespace:PixiEditor.AvaloniaUI.ViewModels.Document"
+             xmlns:dock="clr-namespace:PixiEditor.AvaloniaUI.ViewModels.Dock"
+             xmlns:animations="clr-namespace:PixiEditor.AvaloniaUI.Views.Animations"
+             mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
+             x:DataType="dock:TimelineDockViewModel"
+             x:Class="PixiEditor.AvaloniaUI.Views.Dock.TimelineDockView">
+    <Design.DataContext>
+        <dock:TimelineDockViewModel />
+    </Design.DataContext>
+    <animations:Timeline Clips="{Binding DocumentManagerSubViewModel.ActiveDocument.AnimationDataViewModel.Clips}"/>
+</UserControl>

+ 12 - 0
src/PixiEditor.AvaloniaUI/Views/Dock/TimelineDockView.axaml.cs

@@ -0,0 +1,12 @@
+using Avalonia.Controls;
+
+namespace PixiEditor.AvaloniaUI.Views.Dock;
+
+public partial class TimelineDockView : UserControl
+{
+    public TimelineDockView()
+    {
+        InitializeComponent();
+    }
+}
+

+ 7 - 0
src/PixiEditor.ChangeableDocument/ChangeInfos/Animation/CreateRasterClip_ChangeInfo.cs

@@ -0,0 +1,7 @@
+namespace PixiEditor.ChangeableDocument.ChangeInfos.Animation;
+
+public record CreateRasterClip_ChangeInfo(
+    Guid TargetLayerGuid,
+    int Frame,
+    int IndexOfCreatedClip,
+    bool CloneFromExisting) : IChangeInfo;

+ 5 - 0
src/PixiEditor.ChangeableDocument/ChangeInfos/Animation/DeleteClip_ChangeInfo.cs

@@ -0,0 +1,5 @@
+namespace PixiEditor.ChangeableDocument.ChangeInfos.Animation;
+
+public record DeleteClip_ChangeInfo(
+    int Frame,
+    int IndexOfDeletedClip) : IChangeInfo;

+ 63 - 0
src/PixiEditor.ChangeableDocument/Changeables/Animations/AnimationData.cs

@@ -0,0 +1,63 @@
+using PixiEditor.ChangeableDocument.Changeables.Interfaces;
+
+namespace PixiEditor.ChangeableDocument.Changeables.Animations;
+
+public class AnimationData : IReadOnlyAnimationData
+{
+    private int _currentFrame;
+
+    public int CurrentFrame
+    {
+        get => _currentFrame;
+        set
+        {
+            int lastFrame = value;
+            if (value < 0)
+            {
+                _currentFrame = 0;
+            }
+            else
+            {
+                _currentFrame = value;
+            }
+            
+            OnPreviewFrameChanged(lastFrame);
+        }
+    }
+    
+    public List<Clip> Clips { get; set; } = new List<Clip>();
+    IReadOnlyList<IReadOnlyClip> IReadOnlyAnimationData.Clips => Clips;
+    
+    public void ChangePreviewFrame(int frame)
+    {
+        CurrentFrame = frame;
+    }
+    
+    private void OnPreviewFrameChanged(int lastFrame)
+    {
+        if (Clips == null)
+        {
+            return;
+        }
+        
+        foreach (var clip in Clips)
+        {
+            if (IsWithinRange(clip, CurrentFrame))
+            {
+                if (!IsWithinRange(clip, lastFrame))
+                {
+                    clip.Deactivated(CurrentFrame);
+                }
+                else
+                {
+                    clip.ActiveFrameChanged(CurrentFrame);   
+                }
+            }
+        }
+    }
+
+    private bool IsWithinRange(Clip clip, int frame)
+    {
+        return frame >= clip.StartFrame && frame < clip.StartFrame + clip.Duration;
+    }
+}

+ 12 - 0
src/PixiEditor.ChangeableDocument/Changeables/Animations/Clip.cs

@@ -0,0 +1,12 @@
+using PixiEditor.ChangeableDocument.Changeables.Interfaces;
+
+namespace PixiEditor.ChangeableDocument.Changeables.Animations;
+
+public abstract class Clip : IReadOnlyClip
+{
+    public int StartFrame { get; set; }
+    public int Duration { get; } = 1;
+    
+    public abstract void ActiveFrameChanged(int atFrame);
+    public abstract void Deactivated(int atFrame);
+}

+ 37 - 0
src/PixiEditor.ChangeableDocument/Changeables/Animations/RasterClip.cs

@@ -0,0 +1,37 @@
+namespace PixiEditor.ChangeableDocument.Changeables.Animations;
+
+internal class RasterClip : Clip
+{
+    public Guid TargetLayerGuid { get; set; }
+    public ChunkyImage Image { get; set; }
+    public Document Document { get; set; }
+    
+    private ChunkyImage originalLayerImage;
+
+    public RasterClip(Guid targetLayerGuid, int startFrame, Document document, ChunkyImage? cloneFrom = null)
+    {
+        TargetLayerGuid = targetLayerGuid;
+        StartFrame = startFrame;
+        
+        Image = cloneFrom?.CloneFromCommitted() ?? new ChunkyImage(document.Size);
+
+        Document = document;
+    }
+    
+    public override void ActiveFrameChanged(int atFrame)
+    {
+        if (Document.TryFindMember<RasterLayer>(TargetLayerGuid, out var layer))
+        {
+            originalLayerImage = layer.LayerImage;
+            layer.LayerImage = Image;
+        }
+    }
+
+    public override void Deactivated(int atFrame)
+    {
+        if (Document.TryFindMember<RasterLayer>(TargetLayerGuid, out var layer))
+        {
+            layer.LayerImage = originalLayerImage;
+        }
+    }
+}

+ 3 - 0
src/PixiEditor.ChangeableDocument/Changeables/Document.cs

@@ -1,4 +1,5 @@
 using System.Diagnostics.CodeAnalysis;
 using System.Diagnostics.CodeAnalysis;
+using PixiEditor.ChangeableDocument.Changeables.Animations;
 using PixiEditor.ChangeableDocument.Changeables.Interfaces;
 using PixiEditor.ChangeableDocument.Changeables.Interfaces;
 using PixiEditor.DrawingApi.Core.Numerics;
 using PixiEditor.DrawingApi.Core.Numerics;
 using PixiEditor.Numerics;
 using PixiEditor.Numerics;
@@ -9,6 +10,7 @@ internal class Document : IChangeable, IReadOnlyDocument, IDisposable
 {
 {
     IReadOnlyFolder IReadOnlyDocument.StructureRoot => StructureRoot;
     IReadOnlyFolder IReadOnlyDocument.StructureRoot => StructureRoot;
     IReadOnlySelection IReadOnlyDocument.Selection => Selection;
     IReadOnlySelection IReadOnlyDocument.Selection => Selection;
+    IReadOnlyAnimationData IReadOnlyDocument.AnimationData => AnimationData;
     IReadOnlyStructureMember? IReadOnlyDocument.FindMember(Guid guid) => FindMember(guid);
     IReadOnlyStructureMember? IReadOnlyDocument.FindMember(Guid guid) => FindMember(guid);
     bool IReadOnlyDocument.TryFindMember(Guid guid, [NotNullWhen(true)] out IReadOnlyStructureMember? member) => TryFindMember(guid, out member);
     bool IReadOnlyDocument.TryFindMember(Guid guid, [NotNullWhen(true)] out IReadOnlyStructureMember? member) => TryFindMember(guid, out member);
     IReadOnlyList<IReadOnlyStructureMember> IReadOnlyDocument.FindMemberPath(Guid guid) => FindMemberPath(guid);
     IReadOnlyList<IReadOnlyStructureMember> IReadOnlyDocument.FindMemberPath(Guid guid) => FindMemberPath(guid);
@@ -24,6 +26,7 @@ internal class Document : IChangeable, IReadOnlyDocument, IDisposable
     internal Folder StructureRoot { get; } = new() { GuidValue = Guid.Empty };
     internal Folder StructureRoot { get; } = new() { GuidValue = Guid.Empty };
     internal Selection Selection { get; } = new();
     internal Selection Selection { get; } = new();
     internal ReferenceLayer? ReferenceLayer { get; set; }
     internal ReferenceLayer? ReferenceLayer { get; set; }
+    internal AnimationData AnimationData { get; } = new();
     public VecI Size { get; set; } = DefaultSize;
     public VecI Size { get; set; } = DefaultSize;
     public bool HorizontalSymmetryAxisEnabled { get; set; }
     public bool HorizontalSymmetryAxisEnabled { get; set; }
     public bool VerticalSymmetryAxisEnabled { get; set; }
     public bool VerticalSymmetryAxisEnabled { get; set; }

+ 7 - 0
src/PixiEditor.ChangeableDocument/Changeables/Interfaces/IReadOnlyAnimationData.cs

@@ -0,0 +1,7 @@
+namespace PixiEditor.ChangeableDocument.Changeables.Interfaces;
+
+public interface IReadOnlyAnimationData
+{
+    public int CurrentFrame { get; }
+    public IReadOnlyList<IReadOnlyClip> Clips { get; }
+}

+ 10 - 0
src/PixiEditor.ChangeableDocument/Changeables/Interfaces/IReadOnlyClip.cs

@@ -0,0 +1,10 @@
+namespace PixiEditor.ChangeableDocument.Changeables.Interfaces;
+
+public interface IReadOnlyClip
+{
+    public int StartFrame { get; }
+    public int Duration { get; }
+
+    public void ActiveFrameChanged(int atFrame);
+    public void Deactivated(int atFrame);
+}

+ 2 - 0
src/PixiEditor.ChangeableDocument/Changeables/Interfaces/IReadOnlyDocument.cs

@@ -15,6 +15,8 @@ public interface IReadOnlyDocument
     /// The selection of the document
     /// The selection of the document
     /// </summary>
     /// </summary>
     IReadOnlySelection Selection { get; }
     IReadOnlySelection Selection { get; }
+    
+    IReadOnlyAnimationData AnimationData { get; }
 
 
     /// <summary>
     /// <summary>
     /// The size of the document
     /// The size of the document

+ 41 - 0
src/PixiEditor.ChangeableDocument/Changes/Animation/CreateRasterClip_Change.cs

@@ -0,0 +1,41 @@
+using PixiEditor.ChangeableDocument.Changeables.Animations;
+using PixiEditor.ChangeableDocument.ChangeInfos.Animation;
+
+namespace PixiEditor.ChangeableDocument.Changes.Animation;
+
+internal class CreateRasterClip_Change : Change
+{
+    private readonly Guid _targetLayerGuid;
+    private readonly int _frame;
+    private readonly bool _cloneFromExisting;
+    private RasterLayer? _layer;
+    private int indexOfCreatedClip;
+    
+    [GenerateMakeChangeAction]
+    public CreateRasterClip_Change(Guid targetLayerGuid, int frame, bool cloneFromExisting = false)
+    {
+        _targetLayerGuid = targetLayerGuid;
+        _frame = frame;
+        _cloneFromExisting = cloneFromExisting;
+    }
+    
+    public override bool InitializeAndValidate(Document target)
+    {
+        return target.TryFindMember(_targetLayerGuid, out _layer);
+    }
+
+    public override OneOf<None, IChangeInfo, List<IChangeInfo>> Apply(Document target, bool firstApply, out bool ignoreInUndo)
+    {
+        indexOfCreatedClip = target.AnimationData.Clips.Count;
+        target.AnimationData.Clips.Add(new RasterClip(_targetLayerGuid, _frame, target, _cloneFromExisting ? _layer.LayerImage : null));
+        target.AnimationData.ChangePreviewFrame(_frame);
+        ignoreInUndo = false;
+        return new CreateRasterClip_ChangeInfo(_targetLayerGuid, _frame, indexOfCreatedClip, _cloneFromExisting);
+    }
+
+    public override OneOf<None, IChangeInfo, List<IChangeInfo>> Revert(Document target)
+    {
+        target.AnimationData.Clips.RemoveAt(indexOfCreatedClip);
+        return new DeleteClip_ChangeInfo(_frame, indexOfCreatedClip);
+    }
+}

+ 1 - 1
src/PixiEditor.ChangeableDocument/Changes/Properties/CreateStructureMemberMask_Change.cs → src/PixiEditor.ChangeableDocument/Changes/Properties/LayerStructure/CreateStructureMemberMask_Change.cs

@@ -1,6 +1,6 @@
 using PixiEditor.ChangeableDocument.ChangeInfos.Properties;
 using PixiEditor.ChangeableDocument.ChangeInfos.Properties;
 
 
-namespace PixiEditor.ChangeableDocument.Changes.Properties;
+namespace PixiEditor.ChangeableDocument.Changes.Properties.LayerStructure;
 
 
 internal class CreateStructureMemberMask_Change : Change
 internal class CreateStructureMemberMask_Change : Change
 {
 {

+ 1 - 1
src/PixiEditor.ChangeableDocument/Changes/Properties/DeleteStructureMemberMask_Change.cs → src/PixiEditor.ChangeableDocument/Changes/Properties/LayerStructure/DeleteStructureMemberMask_Change.cs

@@ -1,6 +1,6 @@
 using PixiEditor.ChangeableDocument.ChangeInfos.Properties;
 using PixiEditor.ChangeableDocument.ChangeInfos.Properties;
 
 
-namespace PixiEditor.ChangeableDocument.Changes.Properties;
+namespace PixiEditor.ChangeableDocument.Changes.Properties.LayerStructure;
 
 
 internal class DeleteStructureMemberMask_Change : Change
 internal class DeleteStructureMemberMask_Change : Change
 {
 {

+ 1 - 1
src/PixiEditor.ChangeableDocument/Changes/Properties/LayerLockTransparency_Change.cs → src/PixiEditor.ChangeableDocument/Changes/Properties/LayerStructure/LayerLockTransparency_Change.cs

@@ -1,7 +1,7 @@
 using PixiEditor.ChangeableDocument.Changeables.Interfaces;
 using PixiEditor.ChangeableDocument.Changeables.Interfaces;
 using PixiEditor.ChangeableDocument.ChangeInfos.Properties;
 using PixiEditor.ChangeableDocument.ChangeInfos.Properties;
 
 
-namespace PixiEditor.ChangeableDocument.Changes.Properties;
+namespace PixiEditor.ChangeableDocument.Changes.Properties.LayerStructure;
 internal class LayerLockTransparency_Change : Change
 internal class LayerLockTransparency_Change : Change
 {
 {
     private readonly Guid layerGuid;
     private readonly Guid layerGuid;

+ 1 - 1
src/PixiEditor.ChangeableDocument/Changes/Properties/StructureMemberBlendMode_Change.cs → src/PixiEditor.ChangeableDocument/Changes/Properties/LayerStructure/StructureMemberBlendMode_Change.cs

@@ -1,7 +1,7 @@
 using PixiEditor.ChangeableDocument.ChangeInfos.Properties;
 using PixiEditor.ChangeableDocument.ChangeInfos.Properties;
 using PixiEditor.ChangeableDocument.Enums;
 using PixiEditor.ChangeableDocument.Enums;
 
 
-namespace PixiEditor.ChangeableDocument.Changes.Properties;
+namespace PixiEditor.ChangeableDocument.Changes.Properties.LayerStructure;
 internal class StructureMemberBlendMode_Change : Change
 internal class StructureMemberBlendMode_Change : Change
 {
 {
     private BlendMode originalBlendMode;
     private BlendMode originalBlendMode;

+ 1 - 1
src/PixiEditor.ChangeableDocument/Changes/Properties/StructureMemberClipToMemberBelow_Change.cs → src/PixiEditor.ChangeableDocument/Changes/Properties/LayerStructure/StructureMemberClipToMemberBelow_Change.cs

@@ -1,6 +1,6 @@
 using PixiEditor.ChangeableDocument.ChangeInfos.Properties;
 using PixiEditor.ChangeableDocument.ChangeInfos.Properties;
 
 
-namespace PixiEditor.ChangeableDocument.Changes.Properties;
+namespace PixiEditor.ChangeableDocument.Changes.Properties.LayerStructure;
 internal class StructureMemberClipToMemberBelow_Change : Change
 internal class StructureMemberClipToMemberBelow_Change : Change
 {
 {
     private bool originalValue;
     private bool originalValue;

+ 1 - 1
src/PixiEditor.ChangeableDocument/Changes/Properties/StructureMemberIsVisible_Change.cs → src/PixiEditor.ChangeableDocument/Changes/Properties/LayerStructure/StructureMemberIsVisible_Change.cs

@@ -1,6 +1,6 @@
 using PixiEditor.ChangeableDocument.ChangeInfos.Properties;
 using PixiEditor.ChangeableDocument.ChangeInfos.Properties;
 
 
-namespace PixiEditor.ChangeableDocument.Changes.Properties;
+namespace PixiEditor.ChangeableDocument.Changes.Properties.LayerStructure;
 
 
 internal class StructureMemberIsVisible_Change : Change
 internal class StructureMemberIsVisible_Change : Change
 {
 {

+ 1 - 1
src/PixiEditor.ChangeableDocument/Changes/Properties/StructureMemberMaskIsVisible_Change.cs → src/PixiEditor.ChangeableDocument/Changes/Properties/LayerStructure/StructureMemberMaskIsVisible_Change.cs

@@ -1,6 +1,6 @@
 using PixiEditor.ChangeableDocument.ChangeInfos.Properties;
 using PixiEditor.ChangeableDocument.ChangeInfos.Properties;
 
 
-namespace PixiEditor.ChangeableDocument.Changes.Properties;
+namespace PixiEditor.ChangeableDocument.Changes.Properties.LayerStructure;
 internal class StructureMemberMaskIsVisible_Change : Change
 internal class StructureMemberMaskIsVisible_Change : Change
 {
 {
     private readonly Guid memberGuid;
     private readonly Guid memberGuid;

+ 1 - 1
src/PixiEditor.ChangeableDocument/Changes/Properties/StructureMemberName_Change.cs → src/PixiEditor.ChangeableDocument/Changes/Properties/LayerStructure/StructureMemberName_Change.cs

@@ -1,6 +1,6 @@
 using PixiEditor.ChangeableDocument.ChangeInfos.Properties;
 using PixiEditor.ChangeableDocument.ChangeInfos.Properties;
 
 
-namespace PixiEditor.ChangeableDocument.Changes.Properties;
+namespace PixiEditor.ChangeableDocument.Changes.Properties.LayerStructure;
 
 
 internal class StructureMemberName_Change : Change
 internal class StructureMemberName_Change : Change
 {
 {

+ 1 - 1
src/PixiEditor.ChangeableDocument/Changes/Properties/StructureMemberOpacity_UpdateableChange.cs → src/PixiEditor.ChangeableDocument/Changes/Properties/LayerStructure/StructureMemberOpacity_UpdateableChange.cs

@@ -1,6 +1,6 @@
 using PixiEditor.ChangeableDocument.ChangeInfos.Properties;
 using PixiEditor.ChangeableDocument.ChangeInfos.Properties;
 
 
-namespace PixiEditor.ChangeableDocument.Changes.Properties;
+namespace PixiEditor.ChangeableDocument.Changes.Properties.LayerStructure;
 
 
 internal class StructureMemberOpacity_UpdateableChange : UpdateableChange
 internal class StructureMemberOpacity_UpdateableChange : UpdateableChange
 {
 {