Pārlūkot izejas kodu

Collapsable key frame groups

flabbet 1 gadu atpakaļ
vecāks
revīzija
7fd8cb7a11

+ 41 - 17
src/PixiEditor.AvaloniaUI/Models/Rendering/MemberPreviewUpdater.cs

@@ -1,6 +1,7 @@
 #nullable enable
 
 using System.Collections.Generic;
+using System.Diagnostics.CodeAnalysis;
 using System.Linq;
 using System.Threading.Tasks;
 using ChunkyImageLib;
@@ -505,12 +506,15 @@ internal class MemberPreviewUpdater
 
             if (memberVM is ILayerHandler)
             {
-                RenderLayerMainPreview((IReadOnlyLayerNode)member, memberVM, affArea.Value, position, scaling);
+                RenderLayerMainPreview((IReadOnlyLayerNode)member,
+                    memberVM.PreviewSurface, affArea.Value, position, scaling,
+                    doc.AnimationHandler.ActiveFrameBindable);
 
                 if (doc.AnimationHandler.FindKeyFrame(guid, out IKeyFrameHandler? keyFrame))
                 {
                     if (keyFrame is IKeyFrameGroupHandler group)
                     {
+                        RenderGroupPreview(keyFrame, memberVM, member, affArea, position, scaling);
                         foreach (var child in group.Children)
                         {
                             if (member is IReadOnlyImageNode rasterLayer)
@@ -535,6 +539,24 @@ internal class MemberPreviewUpdater
         }
     }
 
+    private void RenderGroupPreview(IKeyFrameHandler keyFrame, IStructureMemberHandler memberVM,
+        IReadOnlyStructureNode member, [DisallowNull] AffectedArea? affArea, VecI position, float scaling)
+    {
+        bool isEditingRootImage = !member.KeyFrames.Any(x => x.IsInFrame(doc.AnimationHandler.ActiveFrameBindable));
+        if(!isEditingRootImage)
+            return;
+        
+        if (keyFrame.PreviewSurface == null ||
+            keyFrame.PreviewSurface.Size != memberVM.PreviewSurface.Size)
+        {
+            keyFrame.PreviewSurface?.Dispose();
+            keyFrame.PreviewSurface = new Surface(memberVM.PreviewSurface.Size);
+        }
+
+        RenderLayerMainPreview((IReadOnlyLayerNode)member, keyFrame.PreviewSurface, affArea.Value,
+            position, scaling, 0);
+    }
+
     /// <summary>
     /// Re-render the <paramref name="area"/> of the main preview of the <paramref name="memberVM"/> folder
     /// </summary>
@@ -561,9 +583,10 @@ internal class MemberPreviewUpdater
             }
             else
             {
-                rendered = doc.Renderer.RenderChunk(chunk, ChunkResolution.Full, contentNode, doc.AnimationHandler.ActiveFrameBindable);
+                rendered = doc.Renderer.RenderChunk(chunk, ChunkResolution.Full, contentNode,
+                    doc.AnimationHandler.ActiveFrameBindable);
             }
-            
+
             if (rendered.IsT0)
             {
                 memberVM.PreviewSurface.DrawingSurface.Canvas.DrawSurface(rendered.AsT0.Surface.DrawingSurface, pos,
@@ -583,31 +606,31 @@ internal class MemberPreviewUpdater
     /// <summary>
     /// Re-render the <paramref name="area"/> of the main preview of the <paramref name="memberVM"/> layer
     /// </summary>
-    private void RenderLayerMainPreview(IReadOnlyLayerNode layer, IStructureMemberHandler memberVM, AffectedArea area,
-        VecI position, float scaling)
+    private void RenderLayerMainPreview(IReadOnlyLayerNode layer, Surface surface, AffectedArea area,
+        VecI position, float scaling, int frame)
     {
-        memberVM.PreviewSurface.DrawingSurface.Canvas.Save();
-        memberVM.PreviewSurface.DrawingSurface.Canvas.Scale(scaling);
-        memberVM.PreviewSurface.DrawingSurface.Canvas.Translate(-position);
-        memberVM.PreviewSurface.DrawingSurface.Canvas.ClipRect((RectD)area.GlobalArea);
+        surface.DrawingSurface.Canvas.Save();
+        surface.DrawingSurface.Canvas.Scale(scaling);
+        surface.DrawingSurface.Canvas.Translate(-position);
+        surface.DrawingSurface.Canvas.ClipRect((RectD)area.GlobalArea);
 
         foreach (var chunk in area.Chunks)
         {
             var pos = chunk * ChunkResolution.Full.PixelSize();
             if (layer is not IReadOnlyImageNode raster) return;
-            IReadOnlyChunkyImage? result = raster.GetLayerImageAtFrame(doc.AnimationHandler.ActiveFrameBindable);
+            IReadOnlyChunkyImage? result = raster.GetLayerImageAtFrame(frame);
 
             if (!result.DrawCommittedChunkOn(
                     chunk,
-                    ChunkResolution.Full, memberVM.PreviewSurface.DrawingSurface, pos,
+                    ChunkResolution.Full, surface.DrawingSurface, pos,
                     scaling < smoothingThreshold ? SmoothReplacingPaint : ReplacingPaint))
             {
-                memberVM.PreviewSurface.DrawingSurface.Canvas.DrawRect(pos.X, pos.Y, ChunkyImage.FullChunkSize,
+                surface.DrawingSurface.Canvas.DrawRect(pos.X, pos.Y, ChunkyImage.FullChunkSize,
                     ChunkyImage.FullChunkSize, ClearPaint);
             }
         }
 
-        memberVM.PreviewSurface.DrawingSurface.Canvas.Restore();
+        surface.DrawingSurface.Canvas.Restore();
     }
 
     private void RenderAnimationFramePreview(IReadOnlyImageNode node, IKeyFrameHandler keyFrameVM, AffectedArea area)
@@ -697,7 +720,7 @@ internal class MemberPreviewUpdater
 
     private void RenderNodePreviews(List<IRenderInfo> infos)
     {
-        foreach(var node in internals.Tracker.Document.NodeGraph.AllNodes)
+        foreach (var node in internals.Tracker.Document.NodeGraph.AllNodes)
         {
             if (node is null)
                 return;
@@ -712,7 +735,7 @@ internal class MemberPreviewUpdater
             {
                 return;
             }
-            
+
             if (nodeVm.ResultPreview == null)
             {
                 nodeVm.ResultPreview =
@@ -726,8 +749,9 @@ internal class MemberPreviewUpdater
             nodeVm.ResultPreview.DrawingSurface.Canvas.Scale(scalingX, scalingY);
 
             RectI region = new RectI(0, 0, node.CachedResult.Size.X, node.CachedResult.Size.Y);
-           
-            nodeVm.ResultPreview.DrawingSurface.Canvas.DrawSurface(node.CachedResult.DrawingSurface, 0, 0, ReplacingPaint);
+
+            nodeVm.ResultPreview.DrawingSurface.Canvas.DrawSurface(node.CachedResult.DrawingSurface, 0, 0,
+                ReplacingPaint);
 
             nodeVm.ResultPreview.DrawingSurface.Canvas.Restore();
             infos.Add(new NodePreviewDirty_RenderInfo(node.Id));

+ 19 - 1
src/PixiEditor.AvaloniaUI/Styles/PortingWipStyles.axaml

@@ -69,7 +69,25 @@
         <Setter Property="Background" Value="Transparent" />
    </Style> 
     <Style Selector="ToggleButton.ExpandCollapseToggleStyle">
-
+        <Setter Property="Content" Value="{DynamicResource icon-chevron-down}" />
+        <Setter Property="Template">
+            <Setter.Value>
+                <ControlTemplate>
+                    <Border Cursor="Hand" Background="{TemplateBinding Background}">
+                        <TextBlock Text="{TemplateBinding Content}" FontSize="16" Classes="pixi-icon" />
+                    </Border>
+                </ControlTemplate>
+            </Setter.Value>
+        </Setter>
+    </Style>
+    
+    <Style Selector="ToggleButton.ExpandCollapseToggleStyle:pressed">
+        <Setter Property="Background" Value="Transparent" />
+    </Style>
+    
+    <Style Selector="ToggleButton.ExpandCollapseToggleStyle:checked">
+        <Setter Property="Content" Value="{DynamicResource icon-chevron-left}" />
+        <Setter Property="Background" Value="Transparent"/>
     </Style>
 
     <Style Selector="Button.SocialMediaButton">

+ 16 - 3
src/PixiEditor.AvaloniaUI/Styles/Templates/KeyFrame.axaml

@@ -6,19 +6,23 @@
                     xmlns:converters="clr-namespace:PixiEditor.AvaloniaUI.Helpers.Converters">
     <ControlTheme TargetType="animations:KeyFrame" x:Key="{x:Type animations:KeyFrame}">
         <Setter Property="ClipToBounds" Value="False"/>
+        <Setter Property="Height" Value="70"/>
+        <Setter Property="MinWidth" Value="35"/>
         <Setter Property="Template">
             <ControlTemplate>
                 <Grid>
                     <Border CornerRadius="{DynamicResource ControlCornerRadius}" Name="MainBorder"
-                            Background="{DynamicResource ThemeBackgroundBrush1}" Height="20"
+                            Background="{DynamicResource ThemeBackgroundBrush1}" Margin="0 5"
                             BorderBrush="{DynamicResource ThemeBorderMidBrush}" BorderThickness="1">
                         <Grid>
                             <Panel HorizontalAlignment="Right" Name="PART_ResizePanelRight" Width="5" Cursor="SizeWestEast" Background="Transparent" ZIndex="1"/>
-                            <Panel Margin="-35, 0, 0, 0" HorizontalAlignment="Left" Name="PART_ResizePanelLeft" Width="5" Cursor="SizeWestEast" Background="Transparent" ZIndex="1"/>
+                            <Panel Margin="-35, 0, 0, 0" HorizontalAlignment="Left" Name="PART_ResizePanelLeft"
+                                   Width="5" Cursor="SizeWestEast" Background="Transparent" ZIndex="1"/>
                         </Grid>
                     </Border>
                     
-                    <Border CornerRadius="{DynamicResource ControlCornerRadius}" Width="60" Height="60" Margin="-30, 0, 0, 0"
+                    <Border IsVisible="{Binding !IsCollapsed, RelativeSource={RelativeSource TemplatedParent}}"
+                        CornerRadius="{DynamicResource ControlCornerRadius}" Width="60" Height="60" Margin="-30, 0, 0, 0"
                             BorderThickness="1" VerticalAlignment="Center" IsHitTestVisible="True" Name="PreviewBorder"
                             HorizontalAlignment="Left" BorderBrush="{DynamicResource ThemeBorderMidBrush}"
                             RenderOptions.BitmapInterpolationMode="None">
@@ -45,6 +49,15 @@
             </ControlTemplate>
         </Setter>
         
+        <Style Selector="^:collapsed">
+            <Setter Property="Height" Value="30"/>
+            <Setter Property="MinWidth" Value="5"/>
+        </Style>
+        
+        <Style Selector="^:collapsed /template/ Panel#PART_ResizePanelLeft">
+            <Setter Property="Margin" Value="0"/>
+        </Style>
+        
         <Style Selector="^:selected /template/ Border#MainBorder">
             <Setter Property="BorderBrush" Value="{DynamicResource ThemeAccent2Color}" />
         </Style>

+ 8 - 2
src/PixiEditor.AvaloniaUI/Styles/Templates/Timeline.axaml

@@ -157,6 +157,11 @@
                                                                             Item="{Binding}" />
                                         </DataTemplate>
                                     </ItemsControl.DataTemplates>
+                                    <ItemsControl.Styles>
+                                        <Style Selector="animations|TimelineGroupHeader:collapsed">
+                                            <Setter Property="Height" Value="30" />
+                                        </Style>
+                                    </ItemsControl.Styles>
                                 </ItemsControl>
                             </StackPanel>
                         </ScrollViewer>
@@ -177,8 +182,8 @@
                                               ItemsSource="{Binding KeyFrames, RelativeSource={RelativeSource TemplatedParent}}">
                                     <ItemsControl.DataTemplates>
                                         <DataTemplate DataType="document:KeyFrameGroupViewModel">
-                                            <ItemsControl Padding="0 5" ClipToBounds="False" Height="70"
-                                                          BorderThickness="0, 0, 0, 1"
+                                            <ItemsControl ClipToBounds="False"
+                                                          BorderThickness="0, 0, 0, 1" 
                                                           BorderBrush="{DynamicResource ThemeBorderMidBrush}"
                                                           ItemsSource="{Binding Children}">
                                                 <ItemsControl.ItemContainerTheme>
@@ -203,6 +208,7 @@
                                                 Scale="{Binding Scale, RelativeSource={RelativeSource FindAncestor, AncestorType=animations:Timeline}}"
                                                 IsEnabled="{Binding IsVisible}"
                                                 IsSelected="{Binding IsSelected, Mode=TwoWay}"
+                                                IsCollapsed="{Binding IsCollapsed}"
                                                 Min="{Binding ElementName=PART_TimelineSlider, Path=Minimum}"
                                                 Item="{Binding}">
                                                 <animations:KeyFrame.Width>

+ 30 - 4
src/PixiEditor.AvaloniaUI/Styles/Templates/TimelineGroupHeader.axaml

@@ -8,10 +8,36 @@
         <Setter Property="Template">
             <ControlTemplate>
                 <Border BorderBrush="{DynamicResource ThemeBorderMidBrush}" BorderThickness="0 0 1 1">
-                    <StackPanel Orientation="Horizontal" VerticalAlignment="Center">
-                        <CheckBox Classes="ImageCheckBox" IsChecked="{Binding Item.IsVisible, RelativeSource={RelativeSource TemplatedParent}}"/>
-                        <TextBlock Text="{Binding Item.LayerName, RelativeSource={RelativeSource TemplatedParent}}" />
-                    </StackPanel>
+                    <DockPanel LastChildFill="False" VerticalAlignment="Center">
+                        <CheckBox VerticalAlignment="Center" Classes="ImageCheckBox" DockPanel.Dock="Left"
+                                  IsChecked="{Binding Item.IsVisible, RelativeSource={RelativeSource TemplatedParent}}" />
+                        <Border IsVisible="{Binding ElementName=PART_CollapseButton, Path=!IsChecked}" CornerRadius="{DynamicResource ControlCornerRadius}" Width="60" Height="60"
+                                BorderThickness="1" VerticalAlignment="Center" IsHitTestVisible="True"
+                                Name="PreviewBorder" Margin="5 0"
+                                HorizontalAlignment="Left" BorderBrush="{DynamicResource ThemeBorderMidBrush}"
+                                RenderOptions.BitmapInterpolationMode="None">
+                            <Border.Background>
+                                <ImageBrush Source="/Images/CheckerTile.png" TileMode="Tile">
+                                    <ImageBrush.Transform>
+                                        <ScaleTransform ScaleX="0.4" ScaleY="0.4" />
+                                    </ImageBrush.Transform>
+                                </ImageBrush>
+                            </Border.Background>
+                            <visuals:SurfaceControl
+                                Surface="{Binding Item.PreviewSurface, RelativeSource={RelativeSource TemplatedParent}}"
+                                Stretch="Uniform" Width="60" Height="60">
+                                <ui:RenderOptionsBindable.BitmapInterpolationMode>
+                                    <MultiBinding Converter="{converters:WidthToBitmapScalingModeConverter}">
+                                        <Binding Path="Item.PreviewSurface.Size.X"
+                                                 RelativeSource="{RelativeSource TemplatedParent}" />
+                                        <Binding RelativeSource="{RelativeSource Mode=Self}" Path="Bounds.Width" />
+                                    </MultiBinding>
+                                </ui:RenderOptionsBindable.BitmapInterpolationMode>
+                            </visuals:SurfaceControl>
+                        </Border>
+                        <TextBlock Margin="5 0 0 0" VerticalAlignment="Center" Text="{Binding Item.LayerName, RelativeSource={RelativeSource TemplatedParent}}" />
+                        <ToggleButton Name="PART_CollapseButton" Margin="0 0 5 0" DockPanel.Dock="Right" Classes="ExpandCollapseToggleStyle" HorizontalAlignment="Right" VerticalAlignment="Center" />
+                    </DockPanel>
                 </Border>
             </ControlTemplate>
         </Setter>

+ 2 - 2
src/PixiEditor.AvaloniaUI/ViewModels/Dock/LayoutManager.cs

@@ -63,7 +63,7 @@ internal class LayoutManager
                         Id = "DocumentArea", FallbackContent = new CreateDocumentFallbackView(),
                         Dockables = [ DockContext.CreateDockable(nodeGraphDockViewModel) ]
                     },
-                    FirstSize = 0.75,
+                    FirstSize = 0.85,
                     SplitDirection = DockingDirection.Bottom,
                     Second = new DockableArea
                     {
@@ -71,7 +71,7 @@ internal class LayoutManager
                         ActiveDockable = DockContext.CreateDockable(timelineDockViewModel)
                     }
                 },
-                FirstSize = 0.8,
+                FirstSize = 0.85,
                 SplitDirection = DockingDirection.Right,
                 Second = new DockableTree
                 {

+ 38 - 0
src/PixiEditor.AvaloniaUI/ViewModels/Document/KeyFrameGroupViewModel.cs

@@ -1,4 +1,5 @@
 using System.Collections.ObjectModel;
+using System.Collections.Specialized;
 using System.Reactive.Linq;
 using PixiEditor.AvaloniaUI.Models.DocumentModels;
 using PixiEditor.AvaloniaUI.Models.Handlers;
@@ -14,6 +15,24 @@ internal class KeyFrameGroupViewModel : KeyFrameViewModel, IKeyFrameGroupHandler
 
     public string LayerName => Document.StructureHelper.Find(LayerGuid).NodeNameBindable;
 
+    public bool IsCollapsed
+    {
+        get => _isCollapsed;
+        set
+        {
+            SetProperty(ref _isCollapsed, value);
+            foreach (var child in Children)
+            {
+                if (child is KeyFrameViewModel keyFrame)
+                {
+                    keyFrame.IsCollapsed = value;
+                }
+            }
+        }
+    }
+
+    private bool _isCollapsed;
+
     public override void SetVisibility(bool isVisible)
     {
         foreach (var child in Children)
@@ -30,6 +49,7 @@ internal class KeyFrameGroupViewModel : KeyFrameViewModel, IKeyFrameGroupHandler
     public KeyFrameGroupViewModel(int startFrame, int duration, Guid layerGuid, Guid id, DocumentViewModel doc, DocumentInternalParts internalParts) 
         : base(startFrame, duration, layerGuid, id, doc, internalParts)
     {
+        Children.CollectionChanged += ChildrenOnCollectionChanged;
         Document.StructureHelper.Find(LayerGuid).PropertyChanged += (sender, args) =>
         {
             if (args.PropertyName == nameof(StructureMemberViewModel.NodeNameBindable))
@@ -38,4 +58,22 @@ internal class KeyFrameGroupViewModel : KeyFrameViewModel, IKeyFrameGroupHandler
             }
         };
     }
+
+    private void ChildrenOnCollectionChanged(object? sender, NotifyCollectionChangedEventArgs e)
+    {
+        OnPropertyChanged(nameof(StartFrameBindable));
+        OnPropertyChanged(nameof(DurationBindable));
+        
+        if (e.Action == NotifyCollectionChangedAction.Add)
+        {
+            foreach (var item in e.NewItems)
+            {
+                if (item is KeyFrameViewModel keyFrame)
+                {
+                    keyFrame.IsCollapsed = IsCollapsed;
+                    keyFrame.SetVisibility(IsVisible);
+                }
+            }
+        }
+    }
 }

+ 10 - 3
src/PixiEditor.AvaloniaUI/ViewModels/Document/KeyFrameViewModel.cs

@@ -14,6 +14,13 @@ internal abstract class KeyFrameViewModel : ObservableObject, IKeyFrameHandler
     private int durationBindable;
     private bool isVisibleBindable = true;
     private bool isSelected;
+    private bool isCollapsed;
+    
+    public bool IsCollapsed
+    {
+        get => isCollapsed;
+        set => SetProperty(ref isCollapsed, value);
+    }
 
     public DocumentViewModel Document { get; }
     protected DocumentInternalParts Internals { get; }
@@ -69,7 +76,7 @@ internal abstract class KeyFrameViewModel : ObservableObject, IKeyFrameHandler
         get => isVisibleBindable;
         set
         {
-            if(!Document.UpdateableChangeActive)
+            if (!Document.UpdateableChangeActive)
             {
                 Internals.ActionAccumulator.AddFinishedActions(new KeyFrameVisibility_Action(Id, value));
             }
@@ -107,7 +114,7 @@ internal abstract class KeyFrameViewModel : ObservableObject, IKeyFrameHandler
         durationBindable = newDuration;
         OnPropertyChanged(nameof(DurationBindable));
     }
-    
+
     public void ChangeFrameLength(int newStartFrame, int newDuration)
     {
         newStartFrame = Math.Max(0, newStartFrame);
@@ -115,7 +122,7 @@ internal abstract class KeyFrameViewModel : ObservableObject, IKeyFrameHandler
         Internals.ActionAccumulator.AddActions(
             new KeyFrameLength_Action(Id, newStartFrame, newDuration));
     }
-    
+
     public void EndChangeFrameLength()
     {
         Internals.ActionAccumulator.AddFinishedActions(new EndKeyFrameLength_Action());

+ 20 - 0
src/PixiEditor.AvaloniaUI/Views/Animations/KeyFrame.cs

@@ -28,6 +28,15 @@ internal class KeyFrame : TemplatedControl
     public static readonly StyledProperty<double> MinProperty = AvaloniaProperty.Register<KeyFrame, double>(
         nameof(Min), 1);
 
+    public static readonly StyledProperty<bool> IsCollapsedProperty = AvaloniaProperty.Register<KeyFrame, bool>(
+        nameof(IsCollapsed)); 
+
+    public bool IsCollapsed
+    {
+        get => GetValue(IsCollapsedProperty);
+        set => SetValue(IsCollapsedProperty, value);
+    }
+    
     public double Min
     {
         get => GetValue(MinProperty);
@@ -58,6 +67,7 @@ internal class KeyFrame : TemplatedControl
     static KeyFrame()
     {
         IsSelectedProperty.Changed.Subscribe(IsSelectedChanged);
+        IsCollapsedProperty.Changed.Subscribe(IsCollapsedChanged);
     }
 
     protected override void OnApplyTemplate(TemplateAppliedEventArgs e)
@@ -178,4 +188,14 @@ internal class KeyFrame : TemplatedControl
 
         keyFrame.PseudoClasses.Set(":selected", keyFrame.IsSelected);
     }
+    
+    private static void IsCollapsedChanged(AvaloniaPropertyChangedEventArgs e)
+    {
+        if (e.Sender is not KeyFrame keyFrame)
+        {
+            return;
+        }
+
+        keyFrame.PseudoClasses.Set(":collapsed", keyFrame.IsCollapsed);
+    }
 }

+ 22 - 1
src/PixiEditor.AvaloniaUI/Views/Animations/TimelineGroupHeader.cs

@@ -1,18 +1,39 @@
 using Avalonia;
+using Avalonia.Controls;
+using Avalonia.Controls.Metadata;
 using Avalonia.Controls.Primitives;
+using Avalonia.Interactivity;
 using Avalonia.Markup.Xaml.Templates;
 using PixiEditor.AvaloniaUI.ViewModels.Document;
 
 namespace PixiEditor.AvaloniaUI.Views.Animations;
 
+[TemplatePart("PART_CollapseButton", typeof(ToggleButton))]
+[PseudoClasses(":collapsed")]
 internal class TimelineGroupHeader : TemplatedControl
 {
     public static readonly StyledProperty<KeyFrameGroupViewModel> ItemProperty = AvaloniaProperty.Register<TimelineGroupHeader, KeyFrameGroupViewModel>(
-        "Item");
+        nameof(Item));
 
     public KeyFrameGroupViewModel Item
     {
         get => GetValue(ItemProperty);
         set => SetValue(ItemProperty, value);
     }
+
+    protected override void OnApplyTemplate(TemplateAppliedEventArgs e)
+    {
+        base.OnApplyTemplate(e);
+        if (e.NameScope.Find("PART_CollapseButton") is { } collapseButton)
+        {
+            (collapseButton as ToggleButton).IsCheckedChanged += CollapseButtonOnIsCheckedChanged;
+        }
+    }
+
+    private void CollapseButtonOnIsCheckedChanged(object? sender, RoutedEventArgs e)
+    {
+        bool isCollapsed = (sender as ToggleButton).IsChecked == true;
+        PseudoClasses.Set(":collapsed", isCollapsed);
+        Item.IsCollapsed = isCollapsed;
+    }
 }

+ 2 - 2
src/PixiEditor.AvaloniaUI/Views/MainView.axaml

@@ -32,9 +32,9 @@
 
                 <tools:Toolbar Grid.Row="0" DataContext="{Binding .}" />
                 <tools:ToolsPicker ZIndex="2" Grid.Row="1"
-                                   Margin="10 0 0 0"
+                                   Margin="10 50 0 0"
                                    HorizontalAlignment="Left"
-                                   VerticalAlignment="Center"
+                                   VerticalAlignment="Top"
                                    Tools="{Binding Path=ToolsSubViewModel.ToolSet}" />
                 <controls:DockableAreaRegion Grid.Row="1"
                                              Root="{Binding LayoutSubViewModel.LayoutManager.ActiveLayout.Root}"

+ 1 - 0
src/PixiEditor.ChangeableDocument/Changeables/Interfaces/IReadOnlyKeyFrameData.cs

@@ -8,4 +8,5 @@ public interface IReadOnlyKeyFrameData
     object Data { get; }
     string AffectedElement { get; }
     bool IsVisible { get; }
+    bool IsInFrame(int frame);
 }