Browse Source

Timeline layout changes

flabbet 1 year ago
parent
commit
ce5f8bfc91

+ 1 - 1
src/PixiEditor.AvaloniaUI/Helpers/Converters/TimelineSliderWidthToMaximumConverter.cs

@@ -19,7 +19,7 @@ internal class TimelineSliderWidthToMaximumConverter : SingleInstanceMultiValueC
 
         if (values[0] is Rect bounds && values[1] is double scale)
         {
-            return (bounds.Width) / scale;
+            return Math.Floor((bounds.Width) / scale);
         }
 
         return 0;

+ 3 - 1
src/PixiEditor.AvaloniaUI/Models/Handlers/IStructureMemberHandler.cs

@@ -1,4 +1,5 @@
-using Avalonia.Media.Imaging;
+using System.ComponentModel;
+using Avalonia.Media.Imaging;
 using ChunkyImageLib;
 using PixiEditor.AvaloniaUI.Models.Layers;
 using PixiEditor.DrawingApi.Core.Numerics;
@@ -28,4 +29,5 @@ internal interface IStructureMemberHandler : IHandler
     public void SetOpacity(float infoOpacity);
     public void SetIsVisible(bool infoIsVisible);
     public void SetName(string infoName);
+    event PropertyChangedEventHandler PropertyChanged;
 }

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

@@ -12,6 +12,7 @@
                 <MergeResourceInclude Source="avares://PixiEditor.AvaloniaUI/Styles/Templates/Timeline.axaml"/>
                 <MergeResourceInclude Source="avares://PixiEditor.AvaloniaUI/Styles/Templates/KeyFrame.axaml"/>
                 <MergeResourceInclude Source="avares://PixiEditor.AvaloniaUI/Styles/Templates/TimelineSlider.axaml"/>
+                <MergeResourceInclude Source="avares://PixiEditor.AvaloniaUI/Styles/Templates/TimelineGroupHeader.axaml"/>
             </ResourceDictionary.MergedDictionaries>
         </ResourceDictionary>
     </Styles.Resources>

+ 29 - 27
src/PixiEditor.AvaloniaUI/Styles/Templates/KeyFrame.axaml

@@ -5,40 +5,42 @@
                     xmlns:ui="clr-namespace:PixiEditor.Extensions.UI;assembly=PixiEditor.Extensions"
                     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="ClipToBounds" Value="False"/>
         <Setter Property="Template">
             <ControlTemplate>
                 <Grid>
                     <Border CornerRadius="{DynamicResource ControlCornerRadius}"
-                            Background="{DynamicResource ThemeBackgroundBrush1}" Height="20" ClipToBounds="False"
+                            Background="{DynamicResource ThemeBackgroundBrush1}" Height="20"
                             BorderBrush="{DynamicResource ThemeBorderMidBrush}" BorderThickness="1">
-                        <Border CornerRadius="{DynamicResource ControlCornerRadius}" Width="30" Height="30"
-                                BorderThickness="1" VerticalAlignment="Center"
-                                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="30" Height="30">
-                                <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>
+                        <Grid>
+                            <Panel HorizontalAlignment="Right" Name="PART_ResizePanelRight" Width="5" Cursor="SizeWestEast" Background="Transparent" ZIndex="1"/>
+                            <Panel Margin="-20, 0, 0, 0" HorizontalAlignment="Left" Name="PART_ResizePanelLeft" Width="5" Cursor="SizeWestEast" Background="Transparent" ZIndex="1"/>
+                        </Grid>
                     </Border>
                     
-                    <Panel HorizontalAlignment="Right" Name="PART_ResizePanelRight" Width="5" Cursor="SizeWestEast" Background="Transparent" ZIndex="1"/>
-                    <Panel HorizontalAlignment="Left" Name="PART_ResizePanelLeft" Width="5" Cursor="SizeWestEast" Background="Transparent" ZIndex="1"/>
+                    <Border CornerRadius="{DynamicResource ControlCornerRadius}" Width="60" Height="60" Margin="-30, 0, 0, 0"
+                            BorderThickness="1" VerticalAlignment="Center" IsHitTestVisible="False"
+                            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="30" Height="30">
+                            <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>
                 </Grid>
             </ControlTemplate>
         </Setter>

+ 57 - 51
src/PixiEditor.AvaloniaUI/Styles/Templates/Timeline.axaml

@@ -42,7 +42,8 @@
                         <TextBlock DockPanel.Dock="Left" Text="Scale:" />
                         <Slider Minimum="1" Maximum="100" Value="{TemplateBinding Scale, Mode=TwoWay}" Width="100" />
                         <TextBlock Text="{Binding Scale, RelativeSource={RelativeSource TemplatedParent}}" />
-                        <input:NumberInput Min="1" Value="{Binding Fps, RelativeSource={RelativeSource TemplatedParent}, Mode=TwoWay}" />
+                        <input:NumberInput Min="1"
+                                           Value="{Binding Fps, RelativeSource={RelativeSource TemplatedParent}, Mode=TwoWay}" />
                         <Panel>
                             <ToggleButton HorizontalAlignment="Center" Content="Play" Name="PART_PlayToggle" />
                         </Panel>
@@ -50,7 +51,7 @@
 
                     <DockPanel Grid.Column="1" Grid.Row="1"
                                LastChildFill="True">
-                        <animations:TimelineSlider Margin="0 0" TickFrequency="1" TickPlacement="TopLeft"
+                        <animations:TimelineSlider Margin="20, 0" TickFrequency="1" TickPlacement="TopLeft"
                                                    SmallChange="1"
                                                    LargeChange="10"
                                                    IsSnapToTickEnabled="True"
@@ -77,56 +78,61 @@
                         </animations:TimelineSlider>
                     </DockPanel>
 
-                    <ScrollViewer Background="{DynamicResource ThemeBackgroundBrush}" Grid.Column="0"
-                                  Grid.ColumnSpan="2" Grid.Row="2" HorizontalScrollBarVisibility="Auto">
-                            <TreeView ItemsSource="{TemplateBinding KeyFrames}">
-                                <TreeView.ItemContainerTheme>
-                                    <ControlTheme TargetType="TreeViewItem">
-                                        <Setter Property="ClipToBounds" Value="False"/>
-                                        <Setter Property="Margin" Value="0, 5"/>
-                                        <Setter Property="Template">
-                                            <ControlTemplate>
-                                                <StackPanel Orientation="Horizontal">
-                                                    <ContentPresenter Name="PART_HeaderPresenter" Margin="0"
-                                                                      Padding="0"
-                                                                      Background="Transparent"
-                                                                      HorizontalContentAlignment="{TemplateBinding HorizontalAlignment}"
-                                                                      Content="{TemplateBinding Header}"
-                                                                      ContentTemplate="{TemplateBinding HeaderTemplate}"
-                                                                      Focusable="False" />
-                                                    <ItemsPresenter Name="PART_ItemsPresenter">
-                                                        <ItemsPresenter.ItemsPanel>
-                                                            <ItemsPanelTemplate>
-                                                                <Grid/>
-                                                            </ItemsPanelTemplate>
-                                                        </ItemsPresenter.ItemsPanel>
-                                                    </ItemsPresenter>
-                                                </StackPanel>
-                                            </ControlTemplate>
-                                        </Setter>
-                                    </ControlTheme>
-                                </TreeView.ItemContainerTheme>
-                                <TreeView.DataTemplates>
-                                    <TreeDataTemplate DataType="document:KeyFrameGroupViewModel"
-                                                      ItemsSource="{Binding Children}">
-                                        <Grid Width="200" Margin="0, 5">
-                                            <TextBlock Text="{Binding StartFrameBindable}" />
-                                        </Grid>
-                                    </TreeDataTemplate>
-                                    <DataTemplate DataType="document:RasterKeyFrameViewModel">
-                                        <animations:KeyFrame
-                                                             Scale="{Binding Scale, RelativeSource={RelativeSource FindAncestor, AncestorType=animations:Timeline}}"
-                                                             Item="{Binding}">
-                                            <animations:KeyFrame.Width>
-                                                <MultiBinding Converter="{converters:DurationToWidthConverter}">
-                                                    <Binding Path="DurationBindable" />
-                                                    <Binding RelativeSource="{RelativeSource FindAncestor, AncestorType=animations:Timeline}" Path="Scale" />
-                                                </MultiBinding>
-                                            </animations:KeyFrame.Width>
-                                        </animations:KeyFrame>
+                    <ScrollViewer Grid.Column="0" Grid.ColumnSpan="2" Grid.Row="2"
+                                  HorizontalScrollBarVisibility="Disabled" VerticalScrollBarVisibility="Auto">
+                        <Grid>
+                            <Grid.ColumnDefinitions>
+                                <ColumnDefinition Width="200" />
+                                <ColumnDefinition Width="*" />
+                            </Grid.ColumnDefinitions>
+                            <ItemsControl
+                                ItemsSource="{Binding KeyFrames, RelativeSource={RelativeSource TemplatedParent}}">
+                                <ItemsControl.DataTemplates>
+                                    <DataTemplate DataType="document:KeyFrameGroupViewModel">
+                                        <animations:TimelineGroupHeader Height="70" Item="{Binding}" />
                                     </DataTemplate>
-                                </TreeView.DataTemplates>
-                            </TreeView>
+                                </ItemsControl.DataTemplates>
+                            </ItemsControl>
+                            <ScrollViewer Background="{DynamicResource ThemeBackgroundBrush}"
+                                          Grid.Column="1" VerticalScrollBarVisibility="Disabled"
+                                          HorizontalScrollBarVisibility="Auto">
+                                <ItemsControl
+                                    ItemsSource="{Binding KeyFrames, RelativeSource={RelativeSource TemplatedParent}}">
+                                    <ItemsControl.DataTemplates>
+                                        <DataTemplate DataType="document:KeyFrameGroupViewModel">
+                                            <ItemsControl Padding="0 5" ClipToBounds="False" Height="70"
+                                                          ItemsSource="{Binding Children}">
+                                                <ItemsControl.ItemContainerTheme>
+                                                    <ControlTheme TargetType="ContentPresenter">
+                                                        <Setter Property="HorizontalAlignment" Value="Left" />
+                                                        <Setter Property="ZIndex" Value="{Binding StartFrameBindable}" />
+                                                    </ControlTheme>
+                                                </ItemsControl.ItemContainerTheme>
+                                                <ItemsControl.ItemsPanel>
+                                                    <ItemsPanelTemplate>
+                                                        <Grid Margin="30, 0, 0, 0"/>
+                                                    </ItemsPanelTemplate>
+                                                </ItemsControl.ItemsPanel>
+                                            </ItemsControl>
+                                        </DataTemplate>
+                                        <DataTemplate DataType="document:RasterKeyFrameViewModel">
+                                            <animations:KeyFrame
+                                                Scale="{Binding Scale, RelativeSource={RelativeSource FindAncestor, AncestorType=animations:Timeline}}"
+                                                Item="{Binding}">
+                                                <animations:KeyFrame.Width>
+                                                    <MultiBinding Converter="{converters:DurationToWidthConverter}">
+                                                        <Binding Path="DurationBindable" />
+                                                        <Binding
+                                                            RelativeSource="{RelativeSource FindAncestor, AncestorType=animations:Timeline}"
+                                                            Path="Scale" />
+                                                    </MultiBinding>
+                                                </animations:KeyFrame.Width>
+                                            </animations:KeyFrame>
+                                        </DataTemplate>
+                                    </ItemsControl.DataTemplates>
+                                </ItemsControl>
+                            </ScrollViewer>
+                        </Grid>
                     </ScrollViewer>
                 </Grid>
             </ControlTemplate>

+ 17 - 0
src/PixiEditor.AvaloniaUI/Styles/Templates/TimelineGroupHeader.axaml

@@ -0,0 +1,17 @@
+<ResourceDictionary xmlns="https://github.com/avaloniaui"
+                    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
+                    xmlns:animations="clr-namespace:PixiEditor.AvaloniaUI.Views.Animations"
+                    xmlns:visuals="clr-namespace:PixiEditor.AvaloniaUI.Views.Visuals"
+                    xmlns:ui="clr-namespace:PixiEditor.Extensions.UI;assembly=PixiEditor.Extensions"
+                    xmlns:converters="clr-namespace:PixiEditor.AvaloniaUI.Helpers.Converters">
+    <ControlTheme TargetType="animations:TimelineGroupHeader" x:Key="{x:Type animations:TimelineGroupHeader}">
+        <Setter Property="Template">
+            <ControlTemplate>
+                <Border BorderBrush="{DynamicResource ThemeBorderMidBrush}" BorderThickness="1">
+                    <TextBlock Text="{Binding Item.LayerName, RelativeSource={RelativeSource TemplatedParent}}"/>
+                </Border>
+            </ControlTemplate>
+        </Setter>
+    </ControlTheme>
+
+</ResourceDictionary>

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

@@ -1,4 +1,5 @@
 using System.Collections.ObjectModel;
+using System.Reactive.Linq;
 using PixiEditor.AvaloniaUI.Models.DocumentModels;
 using PixiEditor.AvaloniaUI.Models.Handlers;
 
@@ -11,8 +12,17 @@ internal class KeyFrameGroupViewModel : KeyFrameViewModel, IKeyFrameGroupHandler
     public override int StartFrameBindable => Children.Count > 0 ? Children.Min(x => x.StartFrameBindable) : 0;
     public override int DurationBindable => Children.Count > 0 ? Children.Max(x => x.StartFrameBindable + x.DurationBindable) - StartFrameBindable : 0;
 
+    public string LayerName => Document.StructureHelper.Find(LayerGuid).NameBindable;
+
     public KeyFrameGroupViewModel(int startFrame, int duration, Guid layerGuid, Guid id, DocumentViewModel doc, DocumentInternalParts internalParts) 
         : base(startFrame, duration, layerGuid, id, doc, internalParts)
     {
+        Document.StructureHelper.Find(LayerGuid).PropertyChanged += (sender, args) =>
+        {
+            if (args.PropertyName == nameof(StructureMemberViewModel.NameBindable))
+            {
+                OnPropertyChanged(nameof(LayerName));
+            }
+        };
     }
 }

+ 16 - 2
src/PixiEditor.AvaloniaUI/ViewModels/SubViewModels/AnimationsViewModel.cs

@@ -36,7 +36,7 @@ internal class AnimationsViewModel : SubViewModel<ViewModelMain>
             return;
         }
 
-        int newFrame = activeDocument.AnimationDataViewModel.ActiveFrameBindable;
+        int newFrame = GetActiveFrame(activeDocument, activeDocument.SelectedStructureMember.GuidValue);
         
         activeDocument.AnimationDataViewModel.CreateRasterKeyFrame(
             activeDocument.SelectedStructureMember.GuidValue, 
@@ -45,7 +45,21 @@ internal class AnimationsViewModel : SubViewModel<ViewModelMain>
         
         activeDocument.Operations.SetActiveFrame(newFrame);
     }
-    
+
+    private static int GetActiveFrame(DocumentViewModel activeDocument, Guid targetLayer)
+    {
+        int active = activeDocument.AnimationDataViewModel.ActiveFrameBindable;
+        if (activeDocument.AnimationDataViewModel.TryFindKeyFrame<KeyFrameGroupViewModel>(targetLayer, out KeyFrameGroupViewModel groupViewModel))
+        {
+            if(active == groupViewModel.StartFrameBindable + groupViewModel.DurationBindable - 1)
+            {
+                return groupViewModel.StartFrameBindable + groupViewModel.DurationBindable;
+            }
+        }
+        
+        return active;
+    }
+
     [Command.Internal("PixiEditor.Document.StartChangeActiveFrame", CanExecute = "PixiEditor.HasDocument")]
     public void StartChangeActiveFrame(int newActiveFrame)
     {

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

@@ -1,6 +1,7 @@
 using Avalonia;
 using Avalonia.Controls;
 using Avalonia.Controls.Metadata;
+using Avalonia.Controls.Presenters;
 using Avalonia.Controls.Primitives;
 using Avalonia.Data;
 using Avalonia.Input;
@@ -66,7 +67,8 @@ internal class KeyFrame : TemplatedControl
                 },
             };
 
-            this.FindAncestorOfType<TreeViewItem>().Bind(MarginProperty, marginBinding);
+            ContentPresenter contentPresenter = this.FindAncestorOfType<ContentPresenter>();
+            contentPresenter.Bind(MarginProperty, marginBinding);
         }
     }
     

+ 18 - 0
src/PixiEditor.AvaloniaUI/Views/Animations/TimelineGroupHeader.cs

@@ -0,0 +1,18 @@
+using Avalonia;
+using Avalonia.Controls.Primitives;
+using Avalonia.Markup.Xaml.Templates;
+using PixiEditor.AvaloniaUI.ViewModels.Document;
+
+namespace PixiEditor.AvaloniaUI.Views.Animations;
+
+internal class TimelineGroupHeader : TemplatedControl
+{
+    public static readonly StyledProperty<KeyFrameGroupViewModel> ItemProperty = AvaloniaProperty.Register<TimelineGroupHeader, KeyFrameGroupViewModel>(
+        "Item");
+
+    public KeyFrameGroupViewModel Item
+    {
+        get => GetValue(ItemProperty);
+        set => SetValue(ItemProperty, value);
+    }
+}