Profile editor - Changed approach to one timeline split into segments

This commit is contained in:
SpoinkyNL committed 2020-07-16 23:15:49 +02:00
1 parent e89199f563
commit 31cfd480a4
17 files changed
+287 -257

No files matched your search

@@ -1,5 +1,6 @@
using Artemis.Core.Models.Profile;
using Artemis.Core.Models.Profile.Conditions;
using Artemis.Core.Models.Profile.LayerProperties;
using Artemis.Core.Models.Profile.LayerProperties.Attributes;
using Artemis.Core.Models.Surface;
using Artemis.Core.Plugins.Abstract;
@@ -30,9 +30,10 @@
</ScrollViewer>
</Grid>
<StackPanel Grid.Row="3" Grid.Column="0" Margin="10" Orientation="Horizontal" VerticalAlignment="Bottom" ToolTip="When conditions are met, only go through the entire timeline once.">
<ToggleButton Style="{StaticResource MaterialDesignSwitchToggleButton}" IsChecked="True" />
<TextBlock Margin="5 0 0 0">Play once</TextBlock>
<StackPanel Grid.Row="3" Grid.Column="0" Margin="13" Orientation="Horizontal" VerticalAlignment="Bottom" ToolTip="When conditions are met, only go through the entire timeline once.">
<CheckBox Style="{StaticResource MaterialDesignCheckBox}">
Play once
</CheckBox>
</StackPanel>
<StackPanel Grid.Row="3" Grid.Column="1" Margin="10" HorizontalAlignment="Right">
@@ -79,50 +79,49 @@
</Grid.RowDefinitions>
<!-- Misc controls & time display -->
<DockPanel Grid.Row="0" VerticalAlignment="Center">
<StackPanel Orientation="Horizontal">
<Button Style="{StaticResource MaterialDesignIconForegroundButton}"
ToolTip="Play from start (Shift+Space)" Command="{s:Action PlayFromStart}"
Focusable="False">
<materialDesign:PackIcon Kind="StepForward" />
</Button>
<Button Style="{StaticResource MaterialDesignIconForegroundButton}"
ToolTip="Toggle play/pause (Space)" Command="{s:Action Play}" Focusable="False">
<StackPanel>
<materialDesign:PackIcon Kind="Play"
Visibility="{Binding Playing, Converter={x:Static s:BoolToVisibilityConverter.InverseInstance}, Mode=OneWay}" />
<materialDesign:PackIcon Kind="Pause"
Visibility="{Binding Playing, Converter={StaticResource BoolToVisibilityConverter}, Mode=OneWay}" />
</StackPanel>
</Button>
<Button Style="{StaticResource MaterialDesignIconForegroundButton}" ToolTip="Go to start"
Command="{s:Action GoToStart}" Focusable="False">
<materialDesign:PackIcon Kind="SkipBackward" />
</Button>
<Button Style="{StaticResource MaterialDesignIconForegroundButton}" ToolTip="Go to end"
Command="{s:Action GoToEnd}" Focusable="False">
<materialDesign:PackIcon Kind="SkipForward" />
</Button>
<Button Style="{StaticResource MaterialDesignIconForegroundButton}" ToolTip="Previous frame"
Command="{s:Action GoToPreviousFrame}" Focusable="False">
<materialDesign:PackIcon Kind="SkipPrevious" />
</Button>
<Button Style="{StaticResource MaterialDesignIconForegroundButton}" ToolTip="Next frame"
Command="{s:Action GoToNextFrame}" Focusable="False">
<materialDesign:PackIcon Kind="SkipNext" />
</Button>
<ToggleButton Style="{StaticResource MaterialDesignFlatToggleButton}"
ToolTip="Repeat after last keyframe"
IsChecked="{Binding RepeatAfterLastKeyframe}"
Focusable="False">
<materialDesign:PackIcon Kind="Repeat" Height="24" Width="24" />
</ToggleButton>
</StackPanel>
<StackPanel VerticalAlignment="Center">
<TextBlock Style="{StaticResource MaterialDesignHeadline6TextBlock}"
Text="{Binding FormattedCurrentTime}" HorizontalAlignment="Right" Margin="0 0 20 0" />
</StackPanel>
</DockPanel>
<Border BorderThickness="0,0,1,0" BorderBrush="{DynamicResource MaterialDesignDivider}">
<DockPanel VerticalAlignment="Center">
<StackPanel Orientation="Horizontal">
<Button Style="{StaticResource MaterialDesignIconForegroundButton}"
ToolTip="Play from start (Shift+Space)" Command="{s:Action PlayFromStart}"
Focusable="False">
<materialDesign:PackIcon Kind="StepForward" />
</Button>
<Button Style="{StaticResource MaterialDesignIconForegroundButton}"
ToolTip="Toggle play/pause (Space)" Command="{s:Action Play}" Focusable="False">
<StackPanel>
<materialDesign:PackIcon Kind="Play"
Visibility="{Binding Playing, Converter={x:Static s:BoolToVisibilityConverter.InverseInstance}, Mode=OneWay}" />
<materialDesign:PackIcon Kind="Pause"
Visibility="{Binding Playing, Converter={StaticResource BoolToVisibilityConverter}, Mode=OneWay}" />
</StackPanel>
</Button>
<Button Style="{StaticResource MaterialDesignIconForegroundButton}" ToolTip="Go to start" Command="{s:Action GoToStart}" Focusable="False">
<materialDesign:PackIcon Kind="SkipBackward" />
</Button>
<Button Style="{StaticResource MaterialDesignIconForegroundButton}" ToolTip="Go to end" Command="{s:Action GoToEnd}" Focusable="False">
<materialDesign:PackIcon Kind="SkipForward" />
</Button>
<Button Style="{StaticResource MaterialDesignIconForegroundButton}" ToolTip="Previous frame" Command="{s:Action GoToPreviousFrame}" Focusable="False">
<materialDesign:PackIcon Kind="SkipPrevious" />
</Button>
<Button Style="{StaticResource MaterialDesignIconForegroundButton}" ToolTip="Next frame" Command="{s:Action GoToNextFrame}" Focusable="False">
<materialDesign:PackIcon Kind="SkipNext" />
</Button>
<ToggleButton Style="{StaticResource MaterialDesignFlatToggleButton}"
ToolTip="Repeat after last keyframe"
IsChecked="{Binding RepeatAfterLastKeyframe}"
Focusable="False">
<materialDesign:PackIcon Kind="Repeat" Height="24" Width="24" />
</ToggleButton>
</StackPanel>
<StackPanel VerticalAlignment="Center">
<TextBlock Style="{StaticResource MaterialDesignHeadline6TextBlock}"
Text="{Binding FormattedCurrentTime}" HorizontalAlignment="Right" Margin="0 0 20 0" />
</StackPanel>
</DockPanel>
</Border>
<!-- Properties tree -->
<materialDesign:DialogHost Identifier="PropertyTreeDialogHost" DialogTheme="Inherit" CloseOnClickAway="True" Grid.Row="1">
@@ -135,7 +134,6 @@
<Grid>
<ContentControl s:View.Model="{Binding TreeViewModel}" />
</Grid>
</Border>
</ScrollViewer>
<materialDesign:TransitionerSlide>
@@ -169,28 +167,58 @@
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<!-- Timeline header -->
<ScrollViewer Grid.Row="0" x:Name="TimelineHeaderScrollViewer" HorizontalScrollBarVisibility="Hidden"
VerticalScrollBarVisibility="Hidden" ScrollChanged="TimelineScrollChanged">
<!-- Timeline headers -->
<ScrollViewer Grid.Row="0" x:Name="TimelineHeaderScrollViewer" HorizontalScrollBarVisibility="Hidden" VerticalScrollBarVisibility="Hidden" ScrollChanged="TimelineScrollChanged">
<Grid Background="{DynamicResource MaterialDesignCardBackground}">
<!-- Caret -->
<Canvas Panel.ZIndex="1"
<Grid.ColumnDefinitions>
<ColumnDefinition Width="{Binding TimelineViewModel.StartSegmentWidth}" />
<ColumnDefinition Width="{Binding TimelineViewModel.MainSegmentWidth}" />
<ColumnDefinition Width="{Binding TimelineViewModel.EndSegmentWidth}" />
</Grid.ColumnDefinitions>
<Canvas Grid.ColumnSpan="3"
Panel.ZIndex="2"
Margin="{Binding TimeCaretPosition}"
Cursor="SizeWE"
MouseDown="{s:Action TimelineMouseDown}"
MouseUp="{s:Action TimelineMouseUp}"
MouseMove="{s:Action TimelineMouseMove}">
<Polygon Points="-10,0 0,20 10,0" Fill="{StaticResource SecondaryAccentBrush}" />
<Line X1="0" X2="0" Y1="0" Y2="{Binding ActualHeight, ElementName=ContainerGrid}"
StrokeThickness="2" Stroke="{StaticResource SecondaryAccentBrush}" />
<Polygon Points="-8,0 -8,8 0,20, 8,8 8,0" Fill="{StaticResource SecondaryAccentBrush}" />
<Line X1="0" X2="0" Y1="0" Y2="{Binding ActualHeight, ElementName=ContainerGrid}" StrokeThickness="2" Stroke="{StaticResource SecondaryAccentBrush}" />
</Canvas>
<!-- Time -->
<timeline:PropertyTimelineHeader Margin="0 25 0 0"
<timeline:PropertyTimelineHeader Grid.Column="0"
Grid.ColumnSpan="3"
Margin="0 25 0 0"
Fill="{DynamicResource MaterialDesignBody}"
PixelsPerSecond="{Binding ProfileEditorService.PixelsPerSecond}"
HorizontalOffset="{Binding ContentHorizontalOffset, ElementName=TimelineHeaderScrollViewer}"
VisibleWidth="{Binding ActualWidth, ElementName=TimelineHeaderScrollViewer}"
OffsetFirstValue="True"
Width="{Binding ActualWidth, ElementName=PropertyTimeLine}" />
<!-- Start segment -->
<TextBlock Grid.Column="0" TextAlignment="Center" VerticalAlignment="Top" Padding="0 3" FontSize="11" Background="{StaticResource MaterialDesignPaper}">
Start
</TextBlock>
<!-- Main segment -->
<TextBlock Grid.Column="1" TextAlignment="Center" VerticalAlignment="Top" Padding="0 3" FontSize="11" Background="{StaticResource MaterialDesignPaper}">
Main
</TextBlock>
<!-- End segment -->
<TextBlock Grid.Column="2" TextAlignment="Center" VerticalAlignment="Top" Padding="0 3" FontSize="11" Background="{StaticResource MaterialDesignPaper}">
End
</TextBlock>
<Rectangle Grid.Column="0" RadiusX="2" RadiusY="2" Fill="{DynamicResource PrimaryHueDarkBrush}" Margin="0 -1 -2 0" Width="5" Height="20" VerticalAlignment="Top"
HorizontalAlignment="Right" Cursor="SizeWE" />
<Rectangle Grid.Column="1" RadiusX="2" RadiusY="2" Fill="{DynamicResource PrimaryHueDarkBrush}" Margin="0 -1 -2 0" Width="5" Height="20" VerticalAlignment="Top"
HorizontalAlignment="Right" Cursor="SizeWE" />
<Rectangle Grid.Column="2" RadiusX="2" RadiusY="2" Fill="{DynamicResource PrimaryHueDarkBrush}" Margin="0 -1 -2 0" Width="5" Height="20" VerticalAlignment="Top"
HorizontalAlignment="Right" Cursor="SizeWE" />
</Grid>
</ScrollViewer>
@@ -201,17 +229,18 @@
HorizontalScrollBarVisibility="Auto"
VerticalScrollBarVisibility="Auto"
ScrollChanged="TimelineScrollChanged">
<Grid>
<Canvas Panel.ZIndex="1"
<Grid Background="{DynamicResource MaterialDesignToolBarBackground}">
<Canvas Grid.Column="0"
Panel.ZIndex="1"
Margin="{Binding TimeCaretPosition}"
Cursor="SizeWE"
MouseDown="{s:Action TimelineMouseDown}"
MouseUp="{s:Action TimelineMouseUp}"
MouseMove="{s:Action TimelineMouseMove}">
<Line X1="0" X2="0" Y1="0" Y2="{Binding ActualHeight, ElementName=ContainerGrid}"
StrokeThickness="2" Stroke="{StaticResource SecondaryAccentBrush}" />
<Line X1="0" X2="0" Y1="0" Y2="{Binding ActualHeight, ElementName=ContainerGrid}" StrokeThickness="2" Stroke="{StaticResource SecondaryAccentBrush}" />
</Canvas>
<ContentControl x:Name="PropertyTimeLine" s:View.Model="{Binding TimelineViewModel}" />
<ContentControl Grid.Column="0" s:View.Model="{Binding TimelineViewModel}" />
</Grid>
</ScrollViewer>
</Grid>
@@ -291,59 +320,15 @@
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<ListBox Style="{StaticResource MaterialDesignToolToggleListBox}" SelectedIndex="{Binding CurrentTimelineIndex}" Height="20" Margin="5 0 0 0">
<ListBoxItem Padding="10 0">
<ListBoxItem.ToolTip>
<ToolTip Placement="Top" VerticalOffset="-5">
<StackPanel>
<TextBlock>Select the <Run FontWeight="Bold">enter</Run> timeline</TextBlock>
<TextBlock>Played when the folder/layer starts displaying (condition is met)</TextBlock>
</StackPanel>
</ToolTip>
</ListBoxItem.ToolTip>
<StackPanel Orientation="Horizontal">
<materialDesign:PackIcon Kind="RayStart" Width="20" Height="20" Margin="0 -4" />
<TextBlock Margin="5 0 0 0" FontSize="11">ENTER</TextBlock>
</StackPanel>
</ListBoxItem>
<ListBoxItem Padding="10 0">
<ListBoxItem.ToolTip>
<ToolTip Placement="Top" VerticalOffset="-5">
<StackPanel>
<TextBlock>Select the <Run FontWeight="Bold">main</Run> timeline</TextBlock>
<TextBlock>Played after the enter timeline finishes, either on repeat or once</TextBlock>
</StackPanel>
</ToolTip>
</ListBoxItem.ToolTip>
<StackPanel Orientation="Horizontal">
<materialDesign:PackIcon Kind="RayStartEnd" Width="20" Height="20" Margin="0 -4" />
<TextBlock Margin="5 0 0 0" FontSize="11">MAIN</TextBlock>
</StackPanel>
</ListBoxItem>
<ListBoxItem Padding="10 0">
<ListBoxItem.ToolTip>
<ToolTip Placement="Top" VerticalOffset="-5">
<StackPanel>
<TextBlock>Select the <Run FontWeight="Bold">exit</Run> timeline</TextBlock>
<TextBlock>Played when the folder/layer stops displaying (conditon no longer met)</TextBlock>
</StackPanel>
</ToolTip>
</ListBoxItem.ToolTip>
<StackPanel Orientation="Horizontal">
<materialDesign:PackIcon Kind="RayEnd" Width="20" Height="20" Margin="0 -4" />
<TextBlock Margin="5 0 0 0" FontSize="11">EXIT</TextBlock>
</StackPanel>
</ListBoxItem>
</ListBox>
<!-- Zoom control -->
<Slider Grid.Column="1"
Orientation="Horizontal"
HorizontalAlignment="Right"
Margin="10 5"
Minimum="31"
Minimum="100"
Maximum="350"
TickFrequency="1"
IsSnapToTickEnabled="True"
Value="{Binding ProfileEditorService.PixelsPerSecond}"
Width="319" />
@@ -27,14 +27,14 @@ namespace Artemis.UI.Screens.Module.ProfileEditor.LayerProperties
{
private readonly ILayerPropertyVmFactory _layerPropertyVmFactory;
private LayerPropertyGroupViewModel _brushPropertyGroup;
private bool _repeatAfterLastKeyframe;
private int _propertyTreeIndex;
private RenderProfileElement _selectedProfileElement;
private BindableCollection<LayerPropertyGroupViewModel> _layerPropertyGroups;
private TreeViewModel _treeViewModel;
private EffectsViewModel _effectsViewModel;
private TimelineViewModel _timelineViewModel;
private BindableCollection<LayerPropertyGroupViewModel> _layerPropertyGroups;
private bool _playing;
private int _propertyTreeIndex;
private bool _repeatAfterLastKeyframe;
private RenderProfileElement _selectedProfileElement;
private TimelineViewModel _timelineViewModel;
private TreeViewModel _treeViewModel;
public LayerPropertiesViewModel(IProfileEditorService profileEditorService, ICoreService coreService, ISettingsService settingsService,
ILayerPropertyVmFactory layerPropertyVmFactory)
@@ -84,16 +84,6 @@ namespace Artemis.UI.Screens.Module.ProfileEditor.LayerProperties
}
}
public int CurrentTimelineIndex
{
get => (int) ProfileEditorService.CurrentTimeline;
set
{
ProfileEditorService.CurrentTimeline = (Core.Models.Profile.LayerProperties.Timeline) value;
ProfileEditorService.CurrentTime = TimeSpan.Zero;
}
}
public bool PropertyTreeVisible => PropertyTreeIndex == 0;
public RenderProfileElement SelectedProfileElement
@@ -133,14 +123,13 @@ namespace Artemis.UI.Screens.Module.ProfileEditor.LayerProperties
get => _timelineViewModel;
set => SetAndNotify(ref _timelineViewModel, value);
}
protected override void OnInitialActivate()
{
PopulateProperties(ProfileEditorService.SelectedProfileElement);
ProfileEditorService.ProfileElementSelected += ProfileEditorServiceOnProfileElementSelected;
ProfileEditorService.CurrentTimeChanged += ProfileEditorServiceOnCurrentTimeChanged;
ProfileEditorService.CurrentTimelineChanged += ProfileEditorServiceOnCurrentTimelineChanged;
ProfileEditorService.PixelsPerSecondChanged += ProfileEditorServiceOnPixelsPerSecondChanged;
base.OnInitialActivate();
@@ -150,7 +139,6 @@ namespace Artemis.UI.Screens.Module.ProfileEditor.LayerProperties
{
ProfileEditorService.ProfileElementSelected -= ProfileEditorServiceOnProfileElementSelected;
ProfileEditorService.CurrentTimeChanged -= ProfileEditorServiceOnCurrentTimeChanged;
ProfileEditorService.CurrentTimelineChanged -= ProfileEditorServiceOnCurrentTimelineChanged;
ProfileEditorService.PixelsPerSecondChanged -= ProfileEditorServiceOnPixelsPerSecondChanged;
PopulateProperties(null);
@@ -171,6 +159,14 @@ namespace Artemis.UI.Screens.Module.ProfileEditor.LayerProperties
private void ProfileEditorServiceOnProfileElementSelected(object sender, RenderProfileElementEventArgs e)
{
// Placeholder
if (e.RenderProfileElement != null)
{
e.RenderProfileElement.StartSegmentLength = TimeSpan.FromMilliseconds(1000);
e.RenderProfileElement.MainSegmentLength = TimeSpan.FromMilliseconds(5000);
e.RenderProfileElement.EndSegmentLength = TimeSpan.FromMilliseconds(1000);
}
PopulateProperties(e.RenderProfileElement);
}
@@ -180,11 +176,6 @@ namespace Artemis.UI.Screens.Module.ProfileEditor.LayerProperties
NotifyOfPropertyChange(nameof(TimeCaretPosition));
}
private void ProfileEditorServiceOnCurrentTimelineChanged(object? sender, EventArgs e)
{
NotifyOfPropertyChange(nameof(CurrentTimelineIndex));
TimelineViewModel.UpdateKeyframes();
}
private void ProfileEditorServiceOnPixelsPerSecondChanged(object sender, EventArgs e)
{
@@ -239,6 +230,8 @@ namespace Artemis.UI.Screens.Module.ProfileEditor.LayerProperties
}
TreeViewModel = _layerPropertyVmFactory.TreeViewModel(this, LayerPropertyGroups);
TimelineViewModel?.Dispose();
TimelineViewModel = _layerPropertyVmFactory.TimelineViewModel(this, LayerPropertyGroups);
ApplyLayerBrush();
@@ -287,7 +280,7 @@ namespace Artemis.UI.Screens.Module.ProfileEditor.LayerProperties
}
SortProperties();
TimelineViewModel.UpdateKeyframes();
UpdateKeyframes();
}
private void ApplyEffects()
@@ -322,7 +315,7 @@ namespace Artemis.UI.Screens.Module.ProfileEditor.LayerProperties
}
SortProperties();
TimelineViewModel.UpdateKeyframes();
UpdateKeyframes();
}
private void SortProperties()
@@ -347,6 +340,11 @@ namespace Artemis.UI.Screens.Module.ProfileEditor.LayerProperties
}
}
private void UpdateKeyframes()
{
TimelineViewModel.Update();
}
#endregion
#region Drag and drop
@@ -23,6 +23,9 @@ namespace Artemis.UI.Screens.Module.ProfileEditor.LayerProperties.Timeline
public static readonly DependencyProperty VisibleWidthProperty = DependencyProperty.Register(nameof(VisibleWidth), typeof(double), typeof(PropertyTimelineHeader),
new FrameworkPropertyMetadata(default(double), FrameworkPropertyMetadataOptions.AffectsRender));
public static readonly DependencyProperty OffsetFirstValueProperty = DependencyProperty.Register(nameof(OffsetFirstValue), typeof(bool), typeof(PropertyTimelineHeader),
new FrameworkPropertyMetadata(default(bool), FrameworkPropertyMetadataOptions.AffectsRender));
private double _subd1;
private double _subd2;
private double _subd3;
@@ -57,6 +60,12 @@ namespace Artemis.UI.Screens.Module.ProfileEditor.LayerProperties.Timeline
set => SetValue(VisibleWidthProperty, value);
}
public bool OffsetFirstValue
{
get => (bool) GetValue(OffsetFirstValueProperty);
set => SetValue(OffsetFirstValueProperty, value);
}
protected override void OnRender(DrawingContext drawingContext)
{
base.OnRender(drawingContext);
@@ -73,7 +82,7 @@ namespace Artemis.UI.Screens.Module.ProfileEditor.LayerProperties.Timeline
var count = (width + offsetUnits) / units;
for (var i = 0; i < count; i++)
{
var x = i * units - offsetUnits + 1;
var x = i * units - offsetUnits;
// Add a 100px margin to allow the text to partially render when needed
if (x < HorizontalOffset - 100 || x > HorizontalOffset + width)
continue;
@@ -95,8 +104,10 @@ namespace Artemis.UI.Screens.Module.ProfileEditor.LayerProperties.Timeline
count = (width + offsetUnits) / units;
for (var i = 0; i < count; i++)
{
var x = i * units - offsetUnits + 1;
if (x > HorizontalOffset && x < HorizontalOffset + width)
var x = i * units - offsetUnits;
if (x == 0 && OffsetFirstValue)
drawingContext.DrawLine(linePen, new Point(1, 20), new Point(1, 30));
else if (x > HorizontalOffset && x < HorizontalOffset + width)
drawingContext.DrawLine(linePen, new Point(x, 20), new Point(x, 30));
}
@@ -107,7 +118,7 @@ namespace Artemis.UI.Screens.Module.ProfileEditor.LayerProperties.Timeline
for (var i = 0; i < count; i++)
{
if (Math.Abs(i % mul) < 0.001) continue;
var x = i * units - offsetUnits + 1;
var x = i * units - offsetUnits;
if (x > HorizontalOffset && x < HorizontalOffset + width)
drawingContext.DrawLine(linePen, new Point(x, 25), new Point(x, 30));
}
@@ -117,8 +128,8 @@ namespace Artemis.UI.Screens.Module.ProfileEditor.LayerProperties.Timeline
{
var typeFace = new Typeface(FontFamily, new FontStyle(), new FontWeight(), new FontStretch());
var formattedText = new FormattedText(text, CultureInfo.CurrentUICulture, FlowDirection.LeftToRight, typeFace, 9, Fill, null, VisualTreeHelper.GetDpi(this).PixelsPerDip);
if (x == 1)
drawingContext.DrawText(formattedText, new Point(x, 2));
if (x == 0 && OffsetFirstValue)
drawingContext.DrawText(formattedText, new Point(2, 2));
else
drawingContext.DrawText(formattedText, new Point(x - formattedText.Width / 2, 2));
}
@@ -27,7 +27,6 @@ namespace Artemis.UI.Screens.Module.ProfileEditor.LayerProperties.Timeline
var newKeyframe = new LayerPropertyKeyframe<T>(
LayerPropertyKeyframe.Value,
LayerPropertyKeyframe.Position,
LayerPropertyKeyframe.Timeline,
LayerPropertyKeyframe.EasingFunction,
LayerPropertyKeyframe.LayerProperty
);
@@ -35,7 +35,6 @@ namespace Artemis.UI.Screens.Module.ProfileEditor.LayerProperties.Timeline
{
TimelineKeyframeViewModels.Clear();
TimelineKeyframeViewModels.AddRange(LayerPropertyGroupViewModel.GetKeyframes(false)
.Where(k => k.Timeline == _profileEditorService.CurrentTimeline)
.Select(k => LayerPropertyGroupViewModel.ProfileEditorService.PixelsPerSecond * k.Position.TotalSeconds));
}
@@ -28,9 +28,7 @@ namespace Artemis.UI.Screens.Module.ProfileEditor.LayerProperties.Timeline
// Only show keyframes if they are enabled
if (LayerPropertyViewModel.LayerProperty.KeyframesEnabled)
{
var keyframes = LayerPropertyViewModel.LayerProperty.Keyframes
.Where(k => k.Timeline == _profileEditorService.CurrentTimeline)
.ToList();
var keyframes = LayerPropertyViewModel.LayerProperty.Keyframes.ToList();
var toRemove = TimelineKeyframeViewModels.Where(t => !keyframes.Contains(t.BaseLayerPropertyKeyframe)).ToList();
TimelineKeyframeViewModels.RemoveRange(toRemove);
TimelineKeyframeViewModels.AddRange(
@@ -9,11 +9,11 @@
d:DesignHeight="25"
d:DesignWidth="800"
d:DataContext="{d:DesignInstance local:TimelineViewModel}">
<Grid Background="{DynamicResource MaterialDesignToolBarBackground}"
<Grid x:Name="TimelineContainerGrid"
Background="{DynamicResource MaterialDesignToolBarBackground}"
MouseDown="{s:Action TimelineCanvasMouseDown}"
MouseUp="{s:Action TimelineCanvasMouseUp}"
MouseMove="{s:Action TimelineCanvasMouseMove}"
HorizontalAlignment="Stretch">
MouseMove="{s:Action TimelineCanvasMouseMove}">
<Grid.Triggers>
<EventTrigger RoutedEvent="UIElement.MouseLeftButtonDown">
<BeginStoryboard>
@@ -41,6 +41,29 @@
</ItemsControl.ItemTemplate>
</ItemsControl>
<Line Stroke="{StaticResource PrimaryHueDarkBrush}"
Opacity="0.5"
StrokeDashArray="4 2"
X1="{Binding StartSegmentEndPosition}"
X2="{Binding StartSegmentEndPosition}"
Y1="0"
Y2="{Binding ActualHeight, ElementName=TimelineContainerGrid}"
HorizontalAlignment="Left" />
<Line Stroke="{StaticResource PrimaryHueDarkBrush}"
Opacity="0.5"
StrokeDashArray="4 2"
X1="{Binding MainSegmentEndPosition}"
X2="{Binding MainSegmentEndPosition}"
Y1="0"
Y2="{Binding ActualHeight, ElementName=TimelineContainerGrid}" />
<Line Stroke="{StaticResource PrimaryHueDarkBrush}"
Opacity="0.5"
StrokeDashArray="4 2"
X1="{Binding EndSegmentEndPosition}"
X2="{Binding EndSegmentEndPosition}"
Y1="0"
Y2="{Binding ActualHeight, ElementName=TimelineContainerGrid}" />
<!-- Multi-selection rectangle -->
<Path x:Name="MultiSelectionPath"
Data="{Binding SelectionRectangle}"
@@ -13,21 +13,23 @@ using Stylet;
namespace Artemis.UI.Screens.Module.ProfileEditor.LayerProperties.Timeline
{
public class TimelineViewModel : Screen
public class TimelineViewModel : PropertyChangedBase, IViewAware, IDisposable
{
private readonly LayerPropertiesViewModel _layerPropertiesViewModel;
private readonly IProfileEditorService _profileEditorService;
private RectangleGeometry _selectionRectangle;
public TimelineViewModel(LayerPropertiesViewModel layerPropertiesViewModel, BindableCollection<LayerPropertyGroupViewModel> layerPropertyGroups,
IProfileEditorService profileEditorService)
public TimelineViewModel(LayerPropertiesViewModel layerPropertiesViewModel, BindableCollection<LayerPropertyGroupViewModel> layerPropertyGroups, IProfileEditorService profileEditorService)
{
_layerPropertiesViewModel = layerPropertiesViewModel;
_profileEditorService = profileEditorService;
LayerPropertyGroups = layerPropertyGroups;
SelectionRectangle = new RectangleGeometry();
UpdateKeyframes();
_profileEditorService.PixelsPerSecondChanged += ProfileEditorServiceOnPixelsPerSecondChanged;
Update();
}
public BindableCollection<LayerPropertyGroupViewModel> LayerPropertyGroups { get; }
@@ -38,7 +40,19 @@ namespace Artemis.UI.Screens.Module.ProfileEditor.LayerProperties.Timeline
set => SetAndNotify(ref _selectionRectangle, value);
}
public void UpdateKeyframes()
public double StartSegmentWidth => _profileEditorService.PixelsPerSecond * _profileEditorService.SelectedProfileElement?.StartSegmentLength.TotalSeconds ?? 0;
public double StartSegmentEndPosition => StartSegmentWidth;
public double MainSegmentWidth => _profileEditorService.PixelsPerSecond * _profileEditorService.SelectedProfileElement?.MainSegmentLength.TotalSeconds ?? 0;
public double MainSegmentEndPosition => StartSegmentWidth + MainSegmentWidth;
public double EndSegmentWidth => _profileEditorService.PixelsPerSecond * _profileEditorService.SelectedProfileElement?.EndSegmentLength.TotalSeconds ?? 0;
public double EndSegmentEndPosition => StartSegmentWidth + MainSegmentWidth + EndSegmentWidth;
public void Dispose()
{
_profileEditorService.PixelsPerSecondChanged -= ProfileEditorServiceOnPixelsPerSecondChanged;
}
public void Update()
{
foreach (var layerPropertyGroupViewModel in LayerPropertyGroups)
{
@@ -52,6 +66,16 @@ namespace Artemis.UI.Screens.Module.ProfileEditor.LayerProperties.Timeline
}
}
private void ProfileEditorServiceOnPixelsPerSecondChanged(object sender, EventArgs e)
{
NotifyOfPropertyChange(nameof(StartSegmentWidth));
NotifyOfPropertyChange(nameof(StartSegmentEndPosition));
NotifyOfPropertyChange(nameof(MainSegmentWidth));
NotifyOfPropertyChange(nameof(MainSegmentEndPosition));
NotifyOfPropertyChange(nameof(EndSegmentWidth));
NotifyOfPropertyChange(nameof(EndSegmentEndPosition));
}
#region Command handlers
public void KeyframeMouseDown(object sender, MouseButtonEventArgs e)
@@ -274,5 +298,19 @@ namespace Artemis.UI.Screens.Module.ProfileEditor.LayerProperties.Timeline
}
#endregion
#region IViewAware
public void AttachView(UIElement view)
{
if (View != null)
throw new InvalidOperationException(string.Format("Tried to attach View {0} to ViewModel {1}, but it already has a view attached", view.GetType().Name, GetType().Name));
View = view;
}
public UIElement View { get; set; }
#endregion
}
}
@@ -50,7 +50,6 @@ namespace Artemis.UI.Screens.Module.ProfileEditor.LayerProperties.Tree
LayerPropertyViewModel.LayerProperty.AddKeyframe(new LayerPropertyKeyframe<T>(
LayerPropertyViewModel.LayerProperty.CurrentValue,
_profileEditorService.CurrentTime,
_profileEditorService.CurrentTimeline,
Easings.Functions.Linear,
LayerPropertyViewModel.LayerProperty
));