1
0
mirror of https://github.com/Artemis-RGB/Artemis synced 2025-12-13 05:48:35 +00:00

Profile editor - Reimplementing the timeline

This commit is contained in:
Robert 2020-09-11 19:51:24 +02:00 committed by SpoinkyNL
parent 3009a793dd
commit 13a006ba48
20 changed files with 231 additions and 348 deletions

View File

@ -80,8 +80,9 @@ namespace Artemis.UI.Ninject.Factories
{
LayerPropertyViewModel LayerPropertyViewModel(ILayerProperty layerProperty);
LayerPropertyGroupViewModel LayerPropertyGroupViewModel(LayerPropertyGroup layerPropertyGroup);
LayerPropertyTreeViewModel<T> LayerPropertyGroupViewModel<T>(LayerProperty<T> layerProperty);
LayerPropertyGroupTreeViewModel LayerPropertyGroupTreeViewModel(LayerPropertyGroupViewModel layerPropertyGroupViewModel);
TreePropertyViewModel<T> LayerPropertyGroupViewModel<T>(LayerProperty<T> layerProperty);
TreeGroupViewModel TreeGroupViewModel(LayerPropertyGroupViewModel layerPropertyGroupViewModel);
TimelineGroupViewModel TimelineGroupViewModel(LayerPropertyGroupViewModel layerPropertyGroupViewModel);
LayerPropertyGroupViewModel LayerPropertyGroupViewModel(LayerPropertyGroup layerPropertyGroup, PropertyGroupDescriptionAttribute propertyGroupDescription);
TreeViewModel TreeViewModel(LayerPropertiesViewModel layerPropertiesViewModel, BindableCollection<LayerPropertyGroupViewModel> layerPropertyGroups);
EffectsViewModel EffectsViewModel(LayerPropertiesViewModel layerPropertiesViewModel);

View File

@ -0,0 +1,14 @@
using System;
using System.Reflection;
using Ninject.Extensions.Factory;
namespace Artemis.UI.Ninject.InstanceProviders
{
public class DataBindingsViewModelInstanceProvider : StandardInstanceProvider
{
protected override Type GetType(MethodInfo methodInfo, object[] arguments)
{
return base.GetType(methodInfo, arguments);
}
}
}

View File

@ -16,7 +16,7 @@ using Artemis.UI.Shared;
using Artemis.UI.Shared.Services;
using GongSolutions.Wpf.DragDrop;
using Stylet;
using static Artemis.UI.Screens.ProfileEditor.LayerProperties.Tree.LayerPropertyGroupTreeViewModel.LayerPropertyGroupType;
using static Artemis.UI.Screens.ProfileEditor.LayerProperties.Tree.TreeGroupViewModel.LayerPropertyGroupType;
namespace Artemis.UI.Screens.ProfileEditor.LayerProperties
{
@ -246,7 +246,8 @@ namespace Artemis.UI.Screens.ProfileEditor.LayerProperties
{
RightSideIndex = 1;
DataBindingsViewModel?.Dispose();
DataBindingsViewModel = _dataBindingsVmFactory.DataBindingsViewModel(ProfileEditorService.SelectedDataBinding);
// TODO
// DataBindingsViewModel = _dataBindingsVmFactory.DataBindingsViewModel(ProfileEditorService.SelectedDataBinding);
}
else
RightSideIndex = 0;
@ -357,7 +358,7 @@ namespace Artemis.UI.Screens.ProfileEditor.LayerProperties
}
SortProperties();
UpdateKeyframes();
TimelineViewModel.Update();
}
private void ApplyEffects()
@ -392,18 +393,18 @@ namespace Artemis.UI.Screens.ProfileEditor.LayerProperties
}
SortProperties();
UpdateKeyframes();
TimelineViewModel.Update();
}
private void SortProperties()
{
// Get all non-effect properties
var nonEffectProperties = LayerPropertyGroups
.Where(l => l.LayerPropertyGroupTreeViewModel.GroupType != LayerEffectRoot)
.Where(l => l.TreeGroupViewModel.GroupType != LayerEffectRoot)
.ToList();
// Order the effects
var effectProperties = LayerPropertyGroups
.Where(l => l.LayerPropertyGroupTreeViewModel.GroupType == LayerEffectRoot)
.Where(l => l.TreeGroupViewModel.GroupType == LayerEffectRoot)
.OrderBy(l => l.LayerPropertyGroup.LayerEffect.Order)
.ToList();
@ -421,12 +422,7 @@ namespace Artemis.UI.Screens.ProfileEditor.LayerProperties
LayerPropertyGroups.Move(LayerPropertyGroups.IndexOf(layerPropertyGroupViewModel), index + nonEffectProperties.Count);
}
}
private void UpdateKeyframes()
{
TimelineViewModel.Update();
}
#endregion
#region Drag and drop
@ -442,8 +438,8 @@ namespace Artemis.UI.Screens.ProfileEditor.LayerProperties
var target = dropInfo.TargetItem as LayerPropertyGroupViewModel;
if (source == target ||
target?.LayerPropertyGroupTreeViewModel.GroupType != LayerEffectRoot ||
source?.LayerPropertyGroupTreeViewModel.GroupType != LayerEffectRoot)
target?.TreeGroupViewModel.GroupType != LayerEffectRoot ||
source?.TreeGroupViewModel.GroupType != LayerEffectRoot)
return;
dropInfo.DropTargetAdorner = DropTargetAdorners.Insert;
@ -461,8 +457,8 @@ namespace Artemis.UI.Screens.ProfileEditor.LayerProperties
var target = dropInfo.TargetItem as LayerPropertyGroupViewModel;
if (source == target ||
target?.LayerPropertyGroupTreeViewModel.GroupType != LayerEffectRoot ||
source?.LayerPropertyGroupTreeViewModel.GroupType != LayerEffectRoot)
target?.TreeGroupViewModel.GroupType != LayerEffectRoot ||
source?.TreeGroupViewModel.GroupType != LayerEffectRoot)
return;
if (dropInfo.InsertPosition == RelativeInsertPosition.BeforeTargetItem)
@ -491,7 +487,7 @@ namespace Artemis.UI.Screens.ProfileEditor.LayerProperties
private void ApplyCurrentEffectsOrder()
{
var order = 1;
foreach (var groupViewModel in LayerPropertyGroups.Where(p => p.LayerPropertyGroupTreeViewModel.GroupType == LayerEffectRoot))
foreach (var groupViewModel in LayerPropertyGroups.Where(p => p.TreeGroupViewModel.GroupType == LayerEffectRoot))
{
groupViewModel.UpdateOrder(order);
order++;

View File

@ -2,6 +2,7 @@
using System.Collections.Generic;
using Artemis.Core;
using Artemis.UI.Ninject.Factories;
using Artemis.UI.Screens.ProfileEditor.LayerProperties.Timeline;
using Artemis.UI.Screens.ProfileEditor.LayerProperties.Tree;
using Stylet;
@ -16,13 +17,17 @@ namespace Artemis.UI.Screens.ProfileEditor.LayerProperties
_layerPropertyVmFactory = layerPropertyVmFactory;
LayerPropertyGroup = layerPropertyGroup;
LayerPropertyGroupTreeViewModel = layerPropertyVmFactory.LayerPropertyGroupTreeViewModel(this);
Children = new BindableCollection<PropertyChangedBase>();
TreeGroupViewModel = layerPropertyVmFactory.TreeGroupViewModel(this);
TimelineGroupViewModel = layerPropertyVmFactory.TimelineGroupViewModel(this);
PopulateChildren();
}
public LayerPropertyGroup LayerPropertyGroup { get; }
public LayerPropertyGroupTreeViewModel LayerPropertyGroupTreeViewModel { get; }
public BindableCollection<PropertyChangedBase> Children { get; set; }
public TreeGroupViewModel TreeGroupViewModel { get; }
public TimelineGroupViewModel TimelineGroupViewModel { get; }
public BindableCollection<PropertyChangedBase> Children { get; }
public bool IsVisible => !LayerPropertyGroup.IsHidden;
@ -51,7 +56,7 @@ namespace Artemis.UI.Screens.ProfileEditor.LayerProperties
{
var layerPropertyViewModel = _layerPropertyVmFactory.LayerPropertyViewModel(layerProperty);
// After creation ensure a supported input VM was found, if not, discard the VM
if (!layerPropertyViewModel.LayerPropertyTreeViewModel.HasPropertyInputViewModel)
if (!layerPropertyViewModel.TreePropertyViewModel.HasPropertyInputViewModel)
layerPropertyViewModel.Dispose();
else
Children.Add(layerPropertyViewModel);
@ -86,7 +91,7 @@ namespace Artemis.UI.Screens.ProfileEditor.LayerProperties
foreach (var child in Children)
{
if (child is LayerPropertyViewModel layerPropertyViewModel)
result.AddRange(layerPropertyViewModel.LayerPropertyTimelineViewModel.GetAllKeyframePositions());
result.AddRange(layerPropertyViewModel.TimelinePropertyViewModel.GetAllKeyframePositions());
else if (child is LayerPropertyGroupViewModel layerPropertyGroupViewModel)
result.AddRange(layerPropertyGroupViewModel.GetAllKeyframePositions(expandedOnly));
}

View File

@ -1,5 +1,6 @@
using System;
using Artemis.Core;
using Artemis.UI.Screens.ProfileEditor.LayerProperties.Timeline;
using Artemis.UI.Screens.ProfileEditor.LayerProperties.Tree;
using Ninject;
using Ninject.Parameters;
@ -14,24 +15,24 @@ namespace Artemis.UI.Screens.ProfileEditor.LayerProperties
LayerProperty = layerProperty;
var parameter = new ConstructorArgument("layerProperty", LayerProperty);
var treeViewModelType = typeof(LayerPropertyTreeViewModel<>).MakeGenericType(layerProperty.GetType().GetGenericArguments());
var timelineViewModelType = typeof(LayerPropertyTimelineViewModel<>).MakeGenericType(layerProperty.GetType().GetGenericArguments());
var treeViewModelType = typeof(TreePropertyViewModel<>).MakeGenericType(layerProperty.GetType().GetGenericArguments());
var timelineViewModelType = typeof(TimelinePropertyViewModel<>).MakeGenericType(layerProperty.GetType().GetGenericArguments());
LayerPropertyTreeViewModel = (ILayerPropertyTreeViewModel) kernel.Get(treeViewModelType, parameter);
LayerPropertyTimelineViewModel = (ILayerPropertyTimelineViewModel) kernel.Get(timelineViewModelType, parameter);
TreePropertyViewModel = (ITreePropertyViewModel) kernel.Get(treeViewModelType, parameter);
TimelinePropertyViewModel = (ITimelinePropertyViewModel) kernel.Get(timelineViewModelType, parameter);
}
public ILayerProperty LayerProperty { get; }
public ILayerPropertyTreeViewModel LayerPropertyTreeViewModel { get; }
public ILayerPropertyTimelineViewModel LayerPropertyTimelineViewModel { get; }
public ITreePropertyViewModel TreePropertyViewModel { get; }
public ITimelinePropertyViewModel TimelinePropertyViewModel { get; }
public bool IsVisible { get; set; }
public bool IsExpanded { get; set; }
public void Dispose()
{
LayerPropertyTreeViewModel?.Dispose();
LayerPropertyTimelineViewModel?.Dispose();
TreePropertyViewModel?.Dispose();
TimelinePropertyViewModel?.Dispose();
}
}
}

View File

@ -1,34 +0,0 @@
using System;
using System.Collections.Generic;
using System.Linq;
using Artemis.Core;
using Stylet;
namespace Artemis.UI.Screens.ProfileEditor.LayerProperties
{
public class LayerPropertyTimelineViewModel<T> : Screen, ILayerPropertyTimelineViewModel
{
public LayerProperty<T> LayerProperty { get; }
public LayerPropertyViewModel LayerPropertyViewModel { get; }
public LayerPropertyTimelineViewModel(LayerProperty<T> layerProperty, LayerPropertyViewModel layerPropertyViewModel)
{
LayerProperty = layerProperty;
LayerPropertyViewModel = layerPropertyViewModel;
}
public List<TimeSpan> GetAllKeyframePositions()
{
return LayerProperty.Keyframes.Select(k => k.Position).ToList();
}
public void Dispose()
{
}
}
public interface ILayerPropertyTimelineViewModel : IScreen, IDisposable
{
List<TimeSpan> GetAllKeyframePositions();
}
}

View File

@ -1,17 +0,0 @@
using System;
using System.Collections.Generic;
using System.Text;
using Artemis.Core;
namespace Artemis.UI.Screens.ProfileEditor.LayerProperties.Timeline.Rails
{
public class TimelineGroupViewModel
{
public LayerPropertyGroup LayerPropertyGroup { get; }
public TimelineGroupViewModel(LayerPropertyGroup layerPropertyGroup)
{
LayerPropertyGroup = layerPropertyGroup;
}
}
}

View File

@ -1,4 +1,5 @@
using System.Windows;
using System;
using System.Windows;
using System.Windows.Media;
using Artemis.Core;
using Humanizer;
@ -7,17 +8,11 @@ namespace Artemis.UI.Screens.ProfileEditor.LayerProperties.Timeline
{
public class TimelineEasingViewModel
{
private readonly TimelineKeyframeViewModel _keyframeViewModel;
private bool _isEasingModeSelected;
public TimelineEasingViewModel(TimelineKeyframeViewModel keyframeViewModel, Easings.Functions easingFunction)
public TimelineEasingViewModel(Easings.Functions easingFunction, bool isSelected)
{
// Can be null if used by DataBindingViewModel because I'm lazy
if (keyframeViewModel != null)
{
_keyframeViewModel = keyframeViewModel;
_isEasingModeSelected = keyframeViewModel.BaseLayerPropertyKeyframe.EasingFunction == easingFunction;
}
_isEasingModeSelected = isSelected;
EasingFunction = easingFunction;
Description = easingFunction.Humanize();
@ -41,9 +36,15 @@ namespace Artemis.UI.Screens.ProfileEditor.LayerProperties.Timeline
set
{
_isEasingModeSelected = value;
if (_isEasingModeSelected)
_keyframeViewModel.SelectEasingMode(this);
if (value) OnEasingModeSelected();
}
}
public event EventHandler EasingModeSelected;
protected virtual void OnEasingModeSelected()
{
EasingModeSelected?.Invoke(this, EventArgs.Empty);
}
}
}

View File

@ -1,14 +1,14 @@
<UserControl x:Class="Artemis.UI.Screens.ProfileEditor.LayerProperties.Timeline.TimelinePropertyGroupView"
<UserControl x:Class="Artemis.UI.Screens.ProfileEditor.LayerProperties.Timeline.TimelineGroupView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:local="clr-namespace:Artemis.UI.Screens.ProfileEditor.LayerProperties.Timeline"
xmlns:s="https://github.com/canton7/Stylet"
xmlns:layerProperties1="clr-namespace:Artemis.UI.Screens.ProfileEditor.LayerProperties"
mc:Ignorable="d"
xmlns:layerProperties="clr-namespace:Artemis.UI.Screens.ProfileEditor.LayerProperties"
mc:Ignorable="d"
d:DesignHeight="450" d:DesignWidth="800"
d:DataContext="{d:DesignInstance local:TimelinePropertyGroupViewModel}"
d:DataContext="{d:DesignInstance local:TimelineGroupViewModel}"
Visibility="{Binding LayerPropertyGroupViewModel.IsVisible, Converter={x:Static s:BoolToVisibilityConverter.Instance}, Mode=OneWay}">
<Grid>
<Grid.RowDefinitions>
@ -20,7 +20,7 @@
<ItemsControl Grid.Row="0"
Height="24"
Visibility="{Binding LayerPropertyGroupViewModel.IsExpanded, Converter={x:Static s:BoolToVisibilityConverter.InverseInstance}, Mode=OneWay}"
ItemsSource="{Binding TimelineKeyframeViewModels}"
ItemsSource="{Binding KeyframePositions}"
Background="{DynamicResource MaterialDesignToolBarBackground}"
HorizontalAlignment="Stretch">
<ItemsControl.ItemsPanel>
@ -59,14 +59,13 @@
</Style>
</ItemsControl.ItemContainerStyle>
<ItemsControl.Resources>
<DataTemplate DataType="{x:Type layerProperties1:LayerPropertyGroupViewModel}">
<ContentControl s:View.Model="{Binding TimelinePropertyGroupViewModel}" IsTabStop="False" HorizontalAlignment="Stretch" />
<DataTemplate DataType="{x:Type layerProperties:LayerPropertyGroupViewModel}">
<ContentControl s:View.Model="{Binding TimelineGroupViewModel}" IsTabStop="False" HorizontalAlignment="Stretch" />
</DataTemplate>
<DataTemplate DataType="{x:Type layerProperties1:LayerPropertyViewModel}">
<ContentControl s:View.Model="{Binding TimelinePropertyBaseViewModel}" IsTabStop="False" HorizontalAlignment="Stretch" />
<DataTemplate DataType="{x:Type layerProperties:LayerPropertyViewModel}">
<ContentControl s:View.Model="{Binding TimelinePropertyViewModel}" IsTabStop="False" HorizontalAlignment="Stretch" />
</DataTemplate>
</ItemsControl.Resources>
</ItemsControl>
</Grid>
</UserControl>
</UserControl>

View File

@ -0,0 +1,48 @@
using System;
using System.Linq;
using Artemis.Core;
using Artemis.UI.Shared.Services;
using Stylet;
namespace Artemis.UI.Screens.ProfileEditor.LayerProperties.Timeline
{
public class TimelineGroupViewModel : IDisposable
{
private readonly IProfileEditorService _profileEditorService;
public TimelineGroupViewModel(LayerPropertyGroupViewModel layerPropertyGroupViewModel, IProfileEditorService profileEditorService)
{
_profileEditorService = profileEditorService;
LayerPropertyGroupViewModel = layerPropertyGroupViewModel;
LayerPropertyGroup = LayerPropertyGroupViewModel.LayerPropertyGroup;
KeyframePositions = new BindableCollection<double>();
_profileEditorService.PixelsPerSecondChanged += ProfileEditorServiceOnPixelsPerSecondChanged;
UpdateKeyframePositions();
}
public LayerPropertyGroupViewModel LayerPropertyGroupViewModel { get; }
public LayerPropertyGroup LayerPropertyGroup { get; }
public BindableCollection<double> KeyframePositions { get; }
public void Dispose()
{
_profileEditorService.PixelsPerSecondChanged -= ProfileEditorServiceOnPixelsPerSecondChanged;
}
private void ProfileEditorServiceOnPixelsPerSecondChanged(object? sender, EventArgs e)
{
UpdateKeyframePositions();
}
public void UpdateKeyframePositions()
{
KeyframePositions.Clear();
KeyframePositions.AddRange(LayerPropertyGroupViewModel
.GetAllKeyframePositions(false)
.Select(p => p.TotalSeconds * _profileEditorService.PixelsPerSecond));
}
}
}

View File

@ -7,73 +7,23 @@ using Stylet;
namespace Artemis.UI.Screens.ProfileEditor.LayerProperties.Timeline
{
public class TimelineKeyframeViewModel<T> : TimelineKeyframeViewModel
public class TimelineKeyframeViewModel<T> : Screen, IDisposable
{
private readonly IProfileEditorService _profileEditorService;
public TimelineKeyframeViewModel(IProfileEditorService profileEditorService, LayerPropertyKeyframe<T> layerPropertyKeyframe)
: base(profileEditorService, layerPropertyKeyframe)
{
_profileEditorService = profileEditorService;
LayerPropertyKeyframe = layerPropertyKeyframe;
}
public LayerPropertyKeyframe<T> LayerPropertyKeyframe { get; }
#region Context menu actions
public override void Copy()
{
var newKeyframe = new LayerPropertyKeyframe<T>(
LayerPropertyKeyframe.Value,
LayerPropertyKeyframe.Position,
LayerPropertyKeyframe.EasingFunction,
LayerPropertyKeyframe.LayerProperty
);
// If possible, shift the keyframe to the right by 11 pixels
var desiredPosition = newKeyframe.Position + TimeSpan.FromMilliseconds(1000f / _profileEditorService.PixelsPerSecond * 11);
if (desiredPosition <= newKeyframe.LayerProperty.ProfileElement.TimelineLength)
newKeyframe.Position = desiredPosition;
// Otherwise if possible shift it to the left by 11 pixels
else
{
desiredPosition = newKeyframe.Position - TimeSpan.FromMilliseconds(1000f / _profileEditorService.PixelsPerSecond * 11);
if (desiredPosition > TimeSpan.Zero)
newKeyframe.Position = desiredPosition;
}
LayerPropertyKeyframe.LayerProperty.AddKeyframe(newKeyframe);
_profileEditorService.UpdateSelectedProfileElement();
}
public override void Delete()
{
LayerPropertyKeyframe.LayerProperty.RemoveKeyframe(LayerPropertyKeyframe);
_profileEditorService.UpdateSelectedProfileElement();
}
#endregion
}
public abstract class TimelineKeyframeViewModel : PropertyChangedBase, IDisposable
{
private readonly IProfileEditorService _profileEditorService;
private BindableCollection<TimelineEasingViewModel> _easingViewModels;
private bool _isSelected;
private int _pixelsPerSecond;
private string _timestamp;
private double _x;
protected TimelineKeyframeViewModel(IProfileEditorService profileEditorService, BaseLayerPropertyKeyframe baseLayerPropertyKeyframe)
public TimelineKeyframeViewModel(LayerPropertyKeyframe<T> layerPropertyKeyframe, IProfileEditorService profileEditorService)
{
_profileEditorService = profileEditorService;
BaseLayerPropertyKeyframe = baseLayerPropertyKeyframe;
EasingViewModels = new BindableCollection<TimelineEasingViewModel>();
BaseLayerPropertyKeyframe.PropertyChanged += BaseLayerPropertyKeyframeOnPropertyChanged;
LayerPropertyKeyframe = layerPropertyKeyframe;
LayerPropertyKeyframe.PropertyChanged += LayerPropertyKeyframeOnPropertyChanged;
}
public BaseLayerPropertyKeyframe BaseLayerPropertyKeyframe { get; }
public LayerPropertyKeyframe<T> LayerPropertyKeyframe { get; }
public BindableCollection<TimelineEasingViewModel> EasingViewModels
{
@ -101,42 +51,51 @@ namespace Artemis.UI.Screens.ProfileEditor.LayerProperties.Timeline
public void Dispose()
{
BaseLayerPropertyKeyframe.PropertyChanged -= BaseLayerPropertyKeyframeOnPropertyChanged;
LayerPropertyKeyframe.PropertyChanged -= LayerPropertyKeyframeOnPropertyChanged;
foreach (var timelineEasingViewModel in EasingViewModels)
timelineEasingViewModel.EasingModeSelected -= TimelineEasingViewModelOnEasingModeSelected;
}
public void Update(int pixelsPerSecond)
public void Update()
{
_pixelsPerSecond = pixelsPerSecond;
X = pixelsPerSecond * BaseLayerPropertyKeyframe.Position.TotalSeconds;
Timestamp = $"{Math.Floor(BaseLayerPropertyKeyframe.Position.TotalSeconds):00}.{BaseLayerPropertyKeyframe.Position.Milliseconds:000}";
X = _profileEditorService.PixelsPerSecond * LayerPropertyKeyframe.Position.TotalSeconds;
Timestamp = $"{Math.Floor(LayerPropertyKeyframe.Position.TotalSeconds):00}.{LayerPropertyKeyframe.Position.Milliseconds:000}";
}
public abstract void Copy();
public abstract void Delete();
private void BaseLayerPropertyKeyframeOnPropertyChanged(object sender, PropertyChangedEventArgs e)
private void LayerPropertyKeyframeOnPropertyChanged(object sender, PropertyChangedEventArgs e)
{
if (e.PropertyName == nameof(BaseLayerPropertyKeyframe.Position))
Update(_pixelsPerSecond);
if (e.PropertyName == nameof(LayerPropertyKeyframe.Position))
Update();
}
#region Easing
public void CreateEasingViewModels()
{
EasingViewModels.AddRange(Enum.GetValues(typeof(Easings.Functions)).Cast<Easings.Functions>().Select(v => new TimelineEasingViewModel(this, v)));
if (EasingViewModels.Any())
return;
EasingViewModels.AddRange(Enum.GetValues(typeof(Easings.Functions))
.Cast<Easings.Functions>()
.Select(e => new TimelineEasingViewModel(e, e == LayerPropertyKeyframe.EasingFunction)));
foreach (var timelineEasingViewModel in EasingViewModels)
timelineEasingViewModel.EasingModeSelected += TimelineEasingViewModelOnEasingModeSelected;
}
private void TimelineEasingViewModelOnEasingModeSelected(object sender, EventArgs e)
{
SelectEasingMode((TimelineEasingViewModel) sender);
}
public void SelectEasingMode(TimelineEasingViewModel easingViewModel)
{
BaseLayerPropertyKeyframe.EasingFunction = easingViewModel.EasingFunction;
LayerPropertyKeyframe.EasingFunction = easingViewModel.EasingFunction;
// Set every selection to false except on the VM that made the change
foreach (var propertyTrackEasingViewModel in EasingViewModels.Where(vm => vm != easingViewModel))
propertyTrackEasingViewModel.IsEasingModeSelected = false;
_profileEditorService.UpdateSelectedProfileElement();
}
@ -151,7 +110,7 @@ namespace Artemis.UI.Screens.ProfileEditor.LayerProperties.Timeline
_offset = null;
}
public void SaveOffsetToKeyframe(TimelineKeyframeViewModel keyframeViewModel)
public void SaveOffsetToKeyframe(TimelineKeyframeViewModel<T> keyframeViewModel)
{
if (keyframeViewModel == this)
{
@ -162,27 +121,61 @@ namespace Artemis.UI.Screens.ProfileEditor.LayerProperties.Timeline
if (_offset != null)
return;
_offset = BaseLayerPropertyKeyframe.Position - keyframeViewModel.BaseLayerPropertyKeyframe.Position;
_offset = LayerPropertyKeyframe.Position - keyframeViewModel.LayerPropertyKeyframe.Position;
}
public void ApplyOffsetToKeyframe(TimelineKeyframeViewModel keyframeViewModel)
public void ApplyOffsetToKeyframe(TimelineKeyframeViewModel<T> keyframeViewModel)
{
if (keyframeViewModel == this || _offset == null)
return;
UpdatePosition(keyframeViewModel.BaseLayerPropertyKeyframe.Position + _offset.Value);
UpdatePosition(keyframeViewModel.LayerPropertyKeyframe.Position + _offset.Value);
}
public void UpdatePosition(TimeSpan position)
{
if (position < TimeSpan.Zero)
BaseLayerPropertyKeyframe.Position = TimeSpan.Zero;
LayerPropertyKeyframe.Position = TimeSpan.Zero;
else if (position > _profileEditorService.SelectedProfileElement.TimelineLength)
BaseLayerPropertyKeyframe.Position = _profileEditorService.SelectedProfileElement.TimelineLength;
LayerPropertyKeyframe.Position = _profileEditorService.SelectedProfileElement.TimelineLength;
else
BaseLayerPropertyKeyframe.Position = position;
LayerPropertyKeyframe.Position = position;
Update(_pixelsPerSecond);
Update();
}
#endregion
#region Context menu actions
public void Copy()
{
var newKeyframe = new LayerPropertyKeyframe<T>(
LayerPropertyKeyframe.Value,
LayerPropertyKeyframe.Position,
LayerPropertyKeyframe.EasingFunction,
LayerPropertyKeyframe.LayerProperty
);
// If possible, shift the keyframe to the right by 11 pixels
var desiredPosition = newKeyframe.Position + TimeSpan.FromMilliseconds(1000f / _profileEditorService.PixelsPerSecond * 11);
if (desiredPosition <= newKeyframe.LayerProperty.ProfileElement.TimelineLength)
newKeyframe.Position = desiredPosition;
// Otherwise if possible shift it to the left by 11 pixels
else
{
desiredPosition = newKeyframe.Position - TimeSpan.FromMilliseconds(1000f / _profileEditorService.PixelsPerSecond * 11);
if (desiredPosition > TimeSpan.Zero)
newKeyframe.Position = desiredPosition;
}
LayerPropertyKeyframe.LayerProperty.AddKeyframe(newKeyframe);
_profileEditorService.UpdateSelectedProfileElement();
}
public void Delete()
{
LayerPropertyKeyframe.LayerProperty.RemoveKeyframe(LayerPropertyKeyframe);
_profileEditorService.UpdateSelectedProfileElement();
}
#endregion

View File

@ -1,59 +0,0 @@
using System;
using System.ComponentModel;
using System.Linq;
using Artemis.UI.Screens.ProfileEditor.LayerProperties.Abstract;
using Artemis.UI.Shared.Services;
using Stylet;
namespace Artemis.UI.Screens.ProfileEditor.LayerProperties.Timeline
{
public class TimelinePropertyGroupViewModel : PropertyChangedBase
{
private readonly IProfileEditorService _profileEditorService;
private BindableCollection<double> _timelineKeyframeViewModels;
public TimelinePropertyGroupViewModel(LayerPropertyBaseViewModel layerPropertyBaseViewModel, IProfileEditorService profileEditorService)
{
_profileEditorService = profileEditorService;
LayerPropertyGroupViewModel = (LayerPropertyGroupViewModel) layerPropertyBaseViewModel;
TimelineKeyframeViewModels = new BindableCollection<double>();
LayerPropertyGroupViewModel.ProfileEditorService.PixelsPerSecondChanged += ProfileEditorServiceOnPixelsPerSecondChanged;
LayerPropertyGroupViewModel.PropertyChanged += LayerPropertyGroupViewModelOnPropertyChanged;
UpdateKeyframes();
}
public LayerPropertyGroupViewModel LayerPropertyGroupViewModel { get; }
public BindableCollection<double> TimelineKeyframeViewModels
{
get => _timelineKeyframeViewModels;
set => SetAndNotify(ref _timelineKeyframeViewModels, value);
}
public void UpdateKeyframes()
{
TimelineKeyframeViewModels.Clear();
TimelineKeyframeViewModels.AddRange(LayerPropertyGroupViewModel.GetKeyframes(false)
.Select(k => LayerPropertyGroupViewModel.ProfileEditorService.PixelsPerSecond * k.Position.TotalSeconds));
}
public void Dispose()
{
LayerPropertyGroupViewModel.ProfileEditorService.PixelsPerSecondChanged -= ProfileEditorServiceOnPixelsPerSecondChanged;
LayerPropertyGroupViewModel.PropertyChanged -= LayerPropertyGroupViewModelOnPropertyChanged;
}
private void LayerPropertyGroupViewModelOnPropertyChanged(object sender, PropertyChangedEventArgs e)
{
if (e.PropertyName == nameof(LayerPropertyGroupViewModel.IsExpanded))
UpdateKeyframes();
}
private void ProfileEditorServiceOnPixelsPerSecondChanged(object sender, EventArgs e)
{
UpdateKeyframes();
}
}
}

View File

@ -9,12 +9,11 @@
xmlns:timeline="clr-namespace:Artemis.UI.Screens.ProfileEditor.LayerProperties.Timeline"
mc:Ignorable="d"
d:DesignHeight="450" d:DesignWidth="800"
d:DataContext="{d:DesignInstance local:TimelinePropertyViewModel}"
Visibility="{Binding LayerPropertyBaseViewModel.IsVisible, Converter={x:Static s:BoolToVisibilityConverter.Instance}, Mode=OneWay}"
MinWidth="{Binding Width}"
HorizontalAlignment="Stretch">
<Border Height="25" BorderThickness="0,0,0,1" BorderBrush="{DynamicResource MaterialDesignDivider}">
<ItemsControl ItemsSource="{Binding TimelineKeyframeViewModels}"
<ItemsControl ItemsSource="{Binding Items}"
Background="{DynamicResource MaterialDesignToolBarBackground}"
HorizontalAlignment="Left">
<ItemsControl.ItemsPanel>

View File

@ -1,108 +1,61 @@
using System;
using System.Collections.Generic;
using System.Linq;
using Artemis.UI.Screens.ProfileEditor.LayerProperties.Abstract;
using Artemis.Core;
using Artemis.UI.Shared.Services;
using Stylet;
namespace Artemis.UI.Screens.ProfileEditor.LayerProperties.Timeline
{
public class TimelinePropertyViewModel<T> : TimelinePropertyViewModel
public class TimelinePropertyViewModel<T> : Conductor<TimelineKeyframeViewModel<T>>.Collection.AllActive, ITimelinePropertyViewModel
{
private readonly IProfileEditorService _profileEditorService;
public LayerProperty<T> LayerProperty { get; }
public LayerPropertyViewModel LayerPropertyViewModel { get; }
public TimelinePropertyViewModel(LayerPropertyBaseViewModel layerPropertyBaseViewModel, IProfileEditorService profileEditorService) : base(layerPropertyBaseViewModel)
public TimelinePropertyViewModel(LayerProperty<T> layerProperty, LayerPropertyViewModel layerPropertyViewModel, IProfileEditorService profileEditorService)
{
_profileEditorService = profileEditorService;
LayerPropertyViewModel = (LayerPropertyViewModel<T>) layerPropertyBaseViewModel;
LayerPropertyViewModel.LayerProperty.KeyframeAdded += LayerPropertyOnKeyframeModified;
LayerPropertyViewModel.LayerProperty.KeyframeRemoved += LayerPropertyOnKeyframeModified;
LayerPropertyViewModel.LayerProperty.KeyframesToggled += LayerPropertyOnKeyframeModified;
_profileEditorService.PixelsPerSecondChanged += ProfileEditorServiceOnPixelsPerSecondChanged;
LayerProperty = layerProperty;
LayerPropertyViewModel = layerPropertyViewModel;
}
public LayerPropertyViewModel<T> LayerPropertyViewModel { get; }
public List<TimeSpan> GetAllKeyframePositions()
{
return LayerProperty.Keyframes.Select(k => k.Position).ToList();
}
public override void UpdateKeyframes()
private void UpdateKeyframes()
{
// Only show keyframes if they are enabled
if (LayerPropertyViewModel.LayerProperty.KeyframesEnabled)
if (LayerProperty.KeyframesEnabled)
{
var keyframes = LayerPropertyViewModel.LayerProperty.Keyframes.ToList();
var toRemove = TimelineKeyframeViewModels.Where(t => !keyframes.Contains(t.BaseLayerPropertyKeyframe)).ToList();
var keyframes = LayerProperty.Keyframes.ToList();
var toRemove = Items.Where(t => !keyframes.Contains(t.LayerPropertyKeyframe)).ToList();
foreach (var timelineKeyframeViewModel in toRemove)
timelineKeyframeViewModel.Dispose();
TimelineKeyframeViewModels.RemoveRange(toRemove);
TimelineKeyframeViewModels.AddRange(keyframes
.Where(k => TimelineKeyframeViewModels.All(t => t.BaseLayerPropertyKeyframe != k))
.Select(k => new TimelineKeyframeViewModel<T>(_profileEditorService, k))
Items.RemoveRange(toRemove);
Items.AddRange(keyframes
.Where(k => Items.All(t => t.LayerPropertyKeyframe != k))
.Select(k => new TimelineKeyframeViewModel<T>(k, _profileEditorService))
);
}
else
DisposeKeyframeViewModels();
Items.Clear();
foreach (var timelineKeyframeViewModel in TimelineKeyframeViewModels)
timelineKeyframeViewModel.Update(_profileEditorService.PixelsPerSecond);
foreach (var timelineKeyframeViewModel in Items)
timelineKeyframeViewModel.Update();
}
public override void Dispose()
public void Dispose()
{
_profileEditorService.PixelsPerSecondChanged -= ProfileEditorServiceOnPixelsPerSecondChanged;
LayerPropertyViewModel.LayerProperty.KeyframeAdded -= LayerPropertyOnKeyframeModified;
LayerPropertyViewModel.LayerProperty.KeyframeRemoved -= LayerPropertyOnKeyframeModified;
LayerPropertyViewModel.LayerProperty.KeyframesToggled -= LayerPropertyOnKeyframeModified;
DisposeKeyframeViewModels();
}
private void DisposeKeyframeViewModels()
{
foreach (var timelineKeyframeViewModel in TimelineKeyframeViewModels)
timelineKeyframeViewModel.Dispose();
TimelineKeyframeViewModels.Clear();
}
private void LayerPropertyOnKeyframeModified(object sender, EventArgs e)
{
UpdateKeyframes();
}
private void ProfileEditorServiceOnPixelsPerSecondChanged(object sender, EventArgs e)
{
foreach (var timelineKeyframeViewModel in TimelineKeyframeViewModels)
timelineKeyframeViewModel.Update(_profileEditorService.PixelsPerSecond);
Width = TimelineKeyframeViewModels.Any() ? TimelineKeyframeViewModels.Max(t => t.X) + 25 : 0;
}
}
public abstract class TimelinePropertyViewModel : PropertyChangedBase, IDisposable
public interface ITimelinePropertyViewModel : IScreen, IDisposable
{
private BindableCollection<TimelineKeyframeViewModel> _timelineKeyframeViewModels;
private double _width;
protected TimelinePropertyViewModel(LayerPropertyBaseViewModel layerPropertyBaseViewModel)
{
LayerPropertyBaseViewModel = layerPropertyBaseViewModel;
TimelineKeyframeViewModels = new BindableCollection<TimelineKeyframeViewModel>();
}
public LayerPropertyBaseViewModel LayerPropertyBaseViewModel { get; }
public BindableCollection<TimelineKeyframeViewModel> TimelineKeyframeViewModels
{
get => _timelineKeyframeViewModels;
set => SetAndNotify(ref _timelineKeyframeViewModels, value);
}
public double Width
{
get => _width;
set => SetAndNotify(ref _width, value);
}
public abstract void Dispose();
public abstract void UpdateKeyframes();
List<TimeSpan> GetAllKeyframePositions();
}
}

View File

@ -8,7 +8,6 @@ using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Shapes;
using Artemis.Core;
using Artemis.UI.Screens.ProfileEditor.LayerProperties.Abstract;
using Artemis.UI.Shared;
using Artemis.UI.Shared.Services;
using Stylet;
@ -32,8 +31,6 @@ namespace Artemis.UI.Screens.ProfileEditor.LayerProperties.Timeline
SelectedProfileElement.PropertyChanged += SelectedProfileElementOnPropertyChanged;
_profileEditorService.PixelsPerSecondChanged += ProfileEditorServiceOnPixelsPerSecondChanged;
Update();
}
public RenderProfileElement SelectedProfileElement { get; set; }
@ -62,21 +59,7 @@ namespace Artemis.UI.Screens.ProfileEditor.LayerProperties.Timeline
_profileEditorService.PixelsPerSecondChanged -= ProfileEditorServiceOnPixelsPerSecondChanged;
SelectedProfileElement.PropertyChanged -= SelectedProfileElementOnPropertyChanged;
}
public void Update()
{
foreach (var layerPropertyGroupViewModel in LayerPropertyGroups)
{
layerPropertyGroupViewModel.TimelinePropertyGroupViewModel.UpdateKeyframes();
foreach (var layerPropertyBaseViewModel in layerPropertyGroupViewModel.GetAllChildren())
{
if (layerPropertyBaseViewModel is LayerPropertyViewModel layerPropertyViewModel)
layerPropertyViewModel.TimelinePropertyBaseViewModel.UpdateKeyframes();
}
}
}
private void SelectedProfileElementOnPropertyChanged(object sender, PropertyChangedEventArgs e)
{
if (e.PropertyName == nameof(_profileEditorService.SelectedProfileElement.StartSegmentLength))

View File

@ -1,4 +1,4 @@
<UserControl x:Class="Artemis.UI.Screens.ProfileEditor.LayerProperties.Tree.TreePropertyGroupView"
<UserControl x:Class="Artemis.UI.Screens.ProfileEditor.LayerProperties.Tree.TreeGroupView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
@ -9,7 +9,7 @@
xmlns:converters="clr-namespace:Artemis.UI.Converters"
xmlns:dd="urn:gong-wpf-dragdrop"
mc:Ignorable="d"
d:DataContext="{d:DesignInstance local:LayerPropertyGroupTreeViewModel}"
d:DataContext="{d:DesignInstance local:TreeGroupViewModel}"
d:DesignHeight="450" d:DesignWidth="800">
<UserControl.Resources>
<converters:NullToVisibilityConverter x:Key="NullToVisibilityConverter" />

View File

@ -14,14 +14,14 @@ using Stylet;
namespace Artemis.UI.Screens.ProfileEditor.LayerProperties.Tree
{
public class LayerPropertyGroupTreeViewModel : PropertyChangedBase
public class TreeGroupViewModel : PropertyChangedBase
{
private readonly IDialogService _dialogService;
private readonly IKernel _kernel;
private readonly IProfileEditorService _profileEditorService;
private readonly IWindowManager _windowManager;
public LayerPropertyGroupTreeViewModel(
public TreeGroupViewModel(
LayerPropertyGroupViewModel layerPropertyGroupViewModel,
IProfileEditorService profileEditorService,
IDialogService dialogService,

View File

@ -1,4 +1,4 @@
<UserControl x:Class="Artemis.UI.Screens.ProfileEditor.LayerProperties.Tree.LayerPropertyTreeView"
<UserControl x:Class="Artemis.UI.Screens.ProfileEditor.LayerProperties.Tree.TreePropertyView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"

View File

@ -7,12 +7,12 @@ using Stylet;
namespace Artemis.UI.Screens.ProfileEditor.LayerProperties.Tree
{
public class LayerPropertyTreeViewModel<T> : Screen, ILayerPropertyTreeViewModel
public class TreePropertyViewModel<T> : Screen, ITreePropertyViewModel
{
private readonly IProfileEditorService _profileEditorService;
private PropertyInputViewModel<T> _propertyInputViewModel;
public LayerPropertyTreeViewModel(LayerProperty<T> layerProperty, LayerPropertyViewModel layerPropertyViewModel, IProfileEditorService profileEditorService)
public TreePropertyViewModel(LayerProperty<T> layerProperty, LayerPropertyViewModel layerPropertyViewModel, IProfileEditorService profileEditorService)
{
_profileEditorService = profileEditorService;
LayerProperty = layerProperty;
@ -86,7 +86,7 @@ namespace Artemis.UI.Screens.ProfileEditor.LayerProperties.Tree
#endregion
}
public interface ILayerPropertyTreeViewModel : IScreen, IDisposable
public interface ITreePropertyViewModel : IScreen, IDisposable
{
bool HasPropertyInputViewModel { get; }
}

View File

@ -99,10 +99,10 @@
</TreeView.ItemContainerStyle>
<TreeView.Resources>
<HierarchicalDataTemplate DataType="{x:Type local:LayerPropertyGroupViewModel}" ItemsSource="{Binding Items}">
<ContentControl s:View.Model="{Binding LayerPropertyGroupTreeViewModel}" />
<ContentControl s:View.Model="{Binding TreeGroupViewModel}" />
</HierarchicalDataTemplate>
<DataTemplate DataType="{x:Type local:LayerPropertyViewModel}">
<ContentControl s:View.Model="{Binding LayerPropertyTreeViewModel}" dd:DragDrop.DragSourceIgnore="True" />
<ContentControl s:View.Model="{Binding TreePropertyViewModel}" dd:DragDrop.DragSourceIgnore="True" />
</DataTemplate>
</TreeView.Resources>
</TreeView>