mirror of
https://github.com/Artemis-RGB/Artemis
synced 2025-12-31 01:42:02 +00:00
Layer properties - Added back most of the reworked VMs and views
This commit is contained in:
parent
7b238e241e
commit
ea66dcd39e
@ -8,10 +8,16 @@ namespace Artemis.Core.Models.Profile.LayerProperties
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public abstract class BaseLayerPropertyKeyframe
|
public abstract class BaseLayerPropertyKeyframe
|
||||||
{
|
{
|
||||||
internal BaseLayerPropertyKeyframe()
|
internal BaseLayerPropertyKeyframe(BaseLayerProperty baseLayerProperty)
|
||||||
{
|
{
|
||||||
|
BaseLayerProperty = baseLayerProperty;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The base class of the layer property this keyframe is applied to
|
||||||
|
/// </summary>
|
||||||
|
public BaseLayerProperty BaseLayerProperty { get; internal set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The position of this keyframe in the timeline
|
/// The position of this keyframe in the timeline
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@ -20,8 +26,6 @@ namespace Artemis.Core.Models.Profile.LayerProperties
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// The easing function applied on the value of the keyframe
|
/// The easing function applied on the value of the keyframe
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public abstract Easings.Functions EasingFunction { get; set; }
|
public Easings.Functions EasingFunction { get; set; }
|
||||||
|
|
||||||
internal abstract BaseLayerProperty BaseLayerProperty { get; }
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -90,7 +90,7 @@ namespace Artemis.Core.Models.Profile.LayerProperties
|
|||||||
var currentKeyframe = Keyframes.FirstOrDefault(k => k.Position == time.Value);
|
var currentKeyframe = Keyframes.FirstOrDefault(k => k.Position == time.Value);
|
||||||
// Create a new keyframe if none found
|
// Create a new keyframe if none found
|
||||||
if (currentKeyframe == null)
|
if (currentKeyframe == null)
|
||||||
AddKeyframe(new LayerPropertyKeyframe<T>(value, time.Value, Easings.Functions.Linear));
|
AddKeyframe(new LayerPropertyKeyframe<T>(value, time.Value, Easings.Functions.Linear, this));
|
||||||
else
|
else
|
||||||
currentKeyframe.Value = value;
|
currentKeyframe.Value = value;
|
||||||
|
|
||||||
@ -105,20 +105,47 @@ namespace Artemis.Core.Models.Profile.LayerProperties
|
|||||||
/// <param name="keyframe">The keyframe to add</param>
|
/// <param name="keyframe">The keyframe to add</param>
|
||||||
public void AddKeyframe(LayerPropertyKeyframe<T> keyframe)
|
public void AddKeyframe(LayerPropertyKeyframe<T> keyframe)
|
||||||
{
|
{
|
||||||
|
if (_keyframes.Contains(keyframe))
|
||||||
|
return;
|
||||||
|
|
||||||
|
keyframe.LayerProperty?.RemoveKeyframe(keyframe);
|
||||||
|
|
||||||
keyframe.LayerProperty = this;
|
keyframe.LayerProperty = this;
|
||||||
|
keyframe.BaseLayerProperty = this;
|
||||||
_keyframes.Add(keyframe);
|
_keyframes.Add(keyframe);
|
||||||
SortKeyframes();
|
SortKeyframes();
|
||||||
OnKeyframeAdded();
|
OnKeyframeAdded();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Removes a keyframe from the layer property
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="keyframe">The keyframe to remove</param>
|
||||||
|
public LayerPropertyKeyframe<T> CopyKeyframe(LayerPropertyKeyframe<T> keyframe)
|
||||||
|
{
|
||||||
|
var newKeyframe = new LayerPropertyKeyframe<T>(
|
||||||
|
keyframe.Value,
|
||||||
|
keyframe.Position,
|
||||||
|
keyframe.EasingFunction,
|
||||||
|
keyframe.LayerProperty
|
||||||
|
);
|
||||||
|
AddKeyframe(newKeyframe);
|
||||||
|
|
||||||
|
return newKeyframe;
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Removes a keyframe from the layer property
|
/// Removes a keyframe from the layer property
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="keyframe">The keyframe to remove</param>
|
/// <param name="keyframe">The keyframe to remove</param>
|
||||||
public void RemoveKeyframe(LayerPropertyKeyframe<T> keyframe)
|
public void RemoveKeyframe(LayerPropertyKeyframe<T> keyframe)
|
||||||
{
|
{
|
||||||
|
if (!_keyframes.Contains(keyframe))
|
||||||
|
return;
|
||||||
|
|
||||||
_keyframes.Remove(keyframe);
|
_keyframes.Remove(keyframe);
|
||||||
keyframe.LayerProperty = null;
|
keyframe.LayerProperty = null;
|
||||||
|
keyframe.BaseLayerProperty = null;
|
||||||
SortKeyframes();
|
SortKeyframes();
|
||||||
OnKeyframeRemoved();
|
OnKeyframeRemoved();
|
||||||
}
|
}
|
||||||
@ -213,8 +240,9 @@ namespace Artemis.Core.Models.Profile.LayerProperties
|
|||||||
_keyframes.AddRange(entity.KeyframeEntities.Select(k => new LayerPropertyKeyframe<T>(
|
_keyframes.AddRange(entity.KeyframeEntities.Select(k => new LayerPropertyKeyframe<T>(
|
||||||
JsonConvert.DeserializeObject<T>(k.Value),
|
JsonConvert.DeserializeObject<T>(k.Value),
|
||||||
k.Position,
|
k.Position,
|
||||||
(Easings.Functions) k.EasingFunction)
|
(Easings.Functions) k.EasingFunction,
|
||||||
));
|
this
|
||||||
|
)));
|
||||||
}
|
}
|
||||||
catch (JsonException e)
|
catch (JsonException e)
|
||||||
{
|
{
|
||||||
|
|||||||
@ -7,10 +7,11 @@ namespace Artemis.Core.Models.Profile.LayerProperties
|
|||||||
{
|
{
|
||||||
private TimeSpan _position;
|
private TimeSpan _position;
|
||||||
|
|
||||||
public LayerPropertyKeyframe(T value, TimeSpan position, Easings.Functions easingFunction)
|
public LayerPropertyKeyframe(T value, TimeSpan position, Easings.Functions easingFunction, LayerProperty<T> layerProperty) : base(layerProperty)
|
||||||
{
|
{
|
||||||
_position = position;
|
_position = position;
|
||||||
Value = value;
|
Value = value;
|
||||||
|
LayerProperty = layerProperty;
|
||||||
EasingFunction = easingFunction;
|
EasingFunction = easingFunction;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -34,10 +35,5 @@ namespace Artemis.Core.Models.Profile.LayerProperties
|
|||||||
LayerProperty.SortKeyframes();
|
LayerProperty.SortKeyframes();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <inheritdoc />
|
|
||||||
public sealed override Easings.Functions EasingFunction { get; set; }
|
|
||||||
|
|
||||||
internal override BaseLayerProperty BaseLayerProperty => LayerProperty;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -10,29 +10,38 @@
|
|||||||
mc:Ignorable="d"
|
mc:Ignorable="d"
|
||||||
d:DesignHeight="163.274" d:DesignWidth="254.425"
|
d:DesignHeight="163.274" d:DesignWidth="254.425"
|
||||||
d:DataContext="{d:DesignInstance dialogs:ExceptionDialogViewModel}">
|
d:DataContext="{d:DesignInstance dialogs:ExceptionDialogViewModel}">
|
||||||
<StackPanel Margin="16">
|
<StackPanel Orientation="Vertical" HorizontalAlignment="Right" Margin="16">
|
||||||
<TextBlock Style="{StaticResource MaterialDesignHeadline6TextBlock}" Text="{Binding Header}" TextWrapping="Wrap" />
|
<TextBlock Style="{StaticResource MaterialDesignHeadline6TextBlock}" Text="{Binding Header}"
|
||||||
|
TextWrapping="Wrap" />
|
||||||
<Separator Margin="0 15" />
|
<Separator Margin="0 15" />
|
||||||
<TextBlock Style="{StaticResource MaterialDesignBody1TextBlock}" FontWeight="Bold" Margin="22 0">Exception message</TextBlock>
|
<ScrollViewer MaxHeight="500">
|
||||||
<TextBlock Style="{StaticResource MaterialDesignBody1TextBlock}" HorizontalAlignment="Left" Text="{Binding Exception.Message}" TextWrapping="Wrap" Margin="22 5" MaxWidth="1000" />
|
<StackPanel>
|
||||||
<Separator Margin="0 15" />
|
<ItemsControl ItemsSource="{Binding Exceptions}">
|
||||||
<TextBlock Style="{StaticResource MaterialDesignBody1TextBlock}" Text="Stack trace" TextWrapping="Wrap" FontWeight="Bold" Margin="22 0" />
|
<ItemsControl.ItemTemplate>
|
||||||
|
<DataTemplate>
|
||||||
|
<StackPanel>
|
||||||
|
<TextBlock Style="{StaticResource MaterialDesignBody1TextBlock}" Text="Stack trace"
|
||||||
|
TextWrapping="Wrap" FontWeight="Bold"/>
|
||||||
|
|
||||||
<avalonedit:TextEditor SyntaxHighlighting="C#"
|
<avalonedit:TextEditor SyntaxHighlighting="C#"
|
||||||
FontFamily="pack://application:,,,/Resources/Fonts/#Roboto Mono"
|
FontFamily="pack://application:,,,/Resources/Fonts/#Roboto Mono"
|
||||||
FontSize="10pt"
|
FontSize="10pt"
|
||||||
IsReadOnly="True"
|
IsReadOnly="True"
|
||||||
Document="{Binding Document}"
|
Document="{Binding Document}"
|
||||||
HorizontalScrollBarVisibility="Auto"
|
HorizontalScrollBarVisibility="Auto"
|
||||||
VerticalScrollBarVisibility="Auto"
|
VerticalScrollBarVisibility="Auto"
|
||||||
MaxWidth="1000"
|
MaxWidth="1000"
|
||||||
Margin="0 10" />
|
Margin="0 10 10 0"
|
||||||
|
Padding="10"/>
|
||||||
|
|
||||||
|
<Separator Margin="0 15" />
|
||||||
<StackPanel Orientation="Horizontal" HorizontalAlignment="Right">
|
</StackPanel>
|
||||||
<Button Style="{StaticResource MaterialDesignFlatButton}" IsDefault="True" Margin="0 8 0 0"
|
</DataTemplate>
|
||||||
Command="{s:Action Close}" Content="Close" />
|
</ItemsControl.ItemTemplate>
|
||||||
</StackPanel>
|
</ItemsControl>
|
||||||
|
</StackPanel>
|
||||||
|
</ScrollViewer>
|
||||||
|
<Button Style="{StaticResource MaterialDesignFlatButton}" IsDefault="True" Margin="0 8 0 0"
|
||||||
|
Command="{s:Action Close}" Content="Close" HorizontalAlignment="Right" />
|
||||||
</StackPanel>
|
</StackPanel>
|
||||||
</UserControl>
|
</UserControl>
|
||||||
@ -1,6 +1,6 @@
|
|||||||
using System;
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
using Artemis.UI.Shared.Services.Dialog;
|
using Artemis.UI.Shared.Services.Dialog;
|
||||||
using ICSharpCode.AvalonEdit;
|
|
||||||
using ICSharpCode.AvalonEdit.Document;
|
using ICSharpCode.AvalonEdit.Document;
|
||||||
|
|
||||||
namespace Artemis.UI.Shared.Screens.Dialogs
|
namespace Artemis.UI.Shared.Screens.Dialogs
|
||||||
@ -10,18 +10,35 @@ namespace Artemis.UI.Shared.Screens.Dialogs
|
|||||||
public ExceptionDialogViewModel(string message, Exception exception)
|
public ExceptionDialogViewModel(string message, Exception exception)
|
||||||
{
|
{
|
||||||
Header = message;
|
Header = message;
|
||||||
Exception = exception;
|
Exceptions = new List<DialogException>();
|
||||||
Document = new TextDocument(new StringTextSource(exception.StackTrace));
|
|
||||||
|
var currentException = exception;
|
||||||
|
while (currentException != null)
|
||||||
|
{
|
||||||
|
Exceptions.Add(new DialogException(currentException));
|
||||||
|
currentException = currentException.InnerException;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public string Header { get; }
|
public string Header { get; }
|
||||||
public Exception Exception { get; }
|
public List<DialogException> Exceptions { get; set; }
|
||||||
|
|
||||||
public IDocument Document { get; set; }
|
|
||||||
|
|
||||||
public void Close()
|
public void Close()
|
||||||
{
|
{
|
||||||
Session.Close();
|
Session.Close();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public class DialogException
|
||||||
|
{
|
||||||
|
public Exception Exception { get; }
|
||||||
|
public IDocument Document { get; set; }
|
||||||
|
|
||||||
|
public DialogException(Exception exception)
|
||||||
|
{
|
||||||
|
Exception = exception;
|
||||||
|
Document = new TextDocument(new StringTextSource($"{exception.Message}\r\n\r\n{exception.StackTrace}"));
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@ -20,38 +20,27 @@ namespace Artemis.UI.Screens.Module.ProfileEditor.LayerProperties
|
|||||||
{
|
{
|
||||||
public class LayerPropertiesViewModel : ProfileEditorPanelViewModel
|
public class LayerPropertiesViewModel : ProfileEditorPanelViewModel
|
||||||
{
|
{
|
||||||
private readonly ICoreService _coreService;
|
|
||||||
private readonly IProfileEditorService _profileEditorService;
|
|
||||||
private readonly ISettingsService _settingsService;
|
|
||||||
|
|
||||||
public LayerPropertiesViewModel(IProfileEditorService profileEditorService, ICoreService coreService, ISettingsService settingsService)
|
public LayerPropertiesViewModel(IProfileEditorService profileEditorService, ICoreService coreService, ISettingsService settingsService)
|
||||||
{
|
{
|
||||||
_profileEditorService = profileEditorService;
|
ProfileEditorService = profileEditorService;
|
||||||
_coreService = coreService;
|
CoreService = coreService;
|
||||||
_settingsService = settingsService;
|
SettingsService = settingsService;
|
||||||
|
|
||||||
PixelsPerSecond = 31;
|
|
||||||
LayerPropertyGroups = new BindableCollection<LayerPropertyGroupViewModel>();
|
LayerPropertyGroups = new BindableCollection<LayerPropertyGroupViewModel>();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public IProfileEditorService ProfileEditorService { get; }
|
||||||
|
public ICoreService CoreService { get; }
|
||||||
|
public ISettingsService SettingsService { get; }
|
||||||
|
|
||||||
public bool Playing { get; set; }
|
public bool Playing { get; set; }
|
||||||
public bool RepeatAfterLastKeyframe { get; set; }
|
public bool RepeatAfterLastKeyframe { get; set; }
|
||||||
public string FormattedCurrentTime => $"{Math.Floor(_profileEditorService.CurrentTime.TotalSeconds):00}.{_profileEditorService.CurrentTime.Milliseconds:000}";
|
public string FormattedCurrentTime => $"{Math.Floor(ProfileEditorService.CurrentTime.TotalSeconds):00}.{ProfileEditorService.CurrentTime.Milliseconds:000}";
|
||||||
|
|
||||||
public int PixelsPerSecond
|
|
||||||
{
|
|
||||||
get => _pixelsPerSecond;
|
|
||||||
set
|
|
||||||
{
|
|
||||||
_pixelsPerSecond = value;
|
|
||||||
OnPixelsPerSecondChanged();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public Thickness TimeCaretPosition
|
public Thickness TimeCaretPosition
|
||||||
{
|
{
|
||||||
get => new Thickness(_profileEditorService.CurrentTime.TotalSeconds * PixelsPerSecond, 0, 0, 0);
|
get => new Thickness(ProfileEditorService.CurrentTime.TotalSeconds * ProfileEditorService.PixelsPerSecond, 0, 0, 0);
|
||||||
set => _profileEditorService.CurrentTime = TimeSpan.FromSeconds(value.Left / PixelsPerSecond);
|
set => ProfileEditorService.CurrentTime = TimeSpan.FromSeconds(value.Left / ProfileEditorService.PixelsPerSecond);
|
||||||
}
|
}
|
||||||
|
|
||||||
public BindableCollection<LayerPropertyGroupViewModel> LayerPropertyGroups { get; set; }
|
public BindableCollection<LayerPropertyGroupViewModel> LayerPropertyGroups { get; set; }
|
||||||
@ -60,18 +49,18 @@ namespace Artemis.UI.Screens.Module.ProfileEditor.LayerProperties
|
|||||||
|
|
||||||
protected override void OnInitialActivate()
|
protected override void OnInitialActivate()
|
||||||
{
|
{
|
||||||
PopulateProperties(_profileEditorService.SelectedProfileElement);
|
PopulateProperties(ProfileEditorService.SelectedProfileElement);
|
||||||
|
|
||||||
_profileEditorService.ProfileElementSelected += ProfileEditorServiceOnProfileElementSelected;
|
ProfileEditorService.ProfileElementSelected += ProfileEditorServiceOnProfileElementSelected;
|
||||||
_profileEditorService.CurrentTimeChanged += ProfileEditorServiceOnCurrentTimeChanged;
|
ProfileEditorService.CurrentTimeChanged += ProfileEditorServiceOnCurrentTimeChanged;
|
||||||
|
|
||||||
base.OnInitialActivate();
|
base.OnInitialActivate();
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override void OnClose()
|
protected override void OnClose()
|
||||||
{
|
{
|
||||||
_profileEditorService.ProfileElementSelected -= ProfileEditorServiceOnProfileElementSelected;
|
ProfileEditorService.ProfileElementSelected -= ProfileEditorServiceOnProfileElementSelected;
|
||||||
_profileEditorService.CurrentTimeChanged -= ProfileEditorServiceOnCurrentTimeChanged;
|
ProfileEditorService.CurrentTimeChanged -= ProfileEditorServiceOnCurrentTimeChanged;
|
||||||
|
|
||||||
base.OnClose();
|
base.OnClose();
|
||||||
}
|
}
|
||||||
@ -109,23 +98,24 @@ namespace Artemis.UI.Screens.Module.ProfileEditor.LayerProperties
|
|||||||
layer.GetType().GetProperty(nameof(layer.Transform)),
|
layer.GetType().GetProperty(nameof(layer.Transform)),
|
||||||
typeof(PropertyGroupDescriptionAttribute)
|
typeof(PropertyGroupDescriptionAttribute)
|
||||||
);
|
);
|
||||||
LayerPropertyGroups.Add(new LayerPropertyGroupViewModel(_profileEditorService, layer.General, (PropertyGroupDescriptionAttribute) generalAttribute));
|
LayerPropertyGroups.Add(new LayerPropertyGroupViewModel(ProfileEditorService, layer.General, (PropertyGroupDescriptionAttribute) generalAttribute));
|
||||||
LayerPropertyGroups.Add(new LayerPropertyGroupViewModel(_profileEditorService, layer.Transform, (PropertyGroupDescriptionAttribute) transformAttribute));
|
LayerPropertyGroups.Add(new LayerPropertyGroupViewModel(ProfileEditorService, layer.Transform, (PropertyGroupDescriptionAttribute) transformAttribute));
|
||||||
|
|
||||||
if (layer.LayerBrush == null)
|
if (layer.LayerBrush != null)
|
||||||
return;
|
|
||||||
|
|
||||||
// Add the rout group of the brush
|
|
||||||
// The root group of the brush has no attribute so let's pull one out of our sleeve
|
|
||||||
var brushDescription = new PropertyGroupDescriptionAttribute
|
|
||||||
{
|
{
|
||||||
Name = layer.LayerBrush.Descriptor.DisplayName,
|
// Add the rout group of the brush
|
||||||
Description = layer.LayerBrush.Descriptor.Description
|
// The root group of the brush has no attribute so let's pull one out of our sleeve
|
||||||
};
|
var brushDescription = new PropertyGroupDescriptionAttribute
|
||||||
LayerPropertyGroups.Add(new LayerPropertyGroupViewModel(_profileEditorService, layer.LayerBrush.BaseProperties, brushDescription));
|
{
|
||||||
|
Name = layer.LayerBrush.Descriptor.DisplayName,
|
||||||
|
Description = layer.LayerBrush.Descriptor.Description
|
||||||
|
};
|
||||||
|
LayerPropertyGroups.Add(new LayerPropertyGroupViewModel(ProfileEditorService, layer.LayerBrush.BaseProperties, brushDescription));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
TreeViewModel = new TreeViewModel(LayerPropertyGroups);
|
|
||||||
TimelineViewModel = new TimelineViewModel(LayerPropertyGroups);
|
TreeViewModel = new TreeViewModel(this, LayerPropertyGroups);
|
||||||
|
TimelineViewModel = new TimelineViewModel(this, LayerPropertyGroups);
|
||||||
}
|
}
|
||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
@ -135,7 +125,7 @@ namespace Artemis.UI.Screens.Module.ProfileEditor.LayerProperties
|
|||||||
public void PlayFromStart()
|
public void PlayFromStart()
|
||||||
{
|
{
|
||||||
if (!Playing)
|
if (!Playing)
|
||||||
_profileEditorService.CurrentTime = TimeSpan.Zero;
|
ProfileEditorService.CurrentTime = TimeSpan.Zero;
|
||||||
|
|
||||||
Play();
|
Play();
|
||||||
}
|
}
|
||||||
@ -150,7 +140,7 @@ namespace Artemis.UI.Screens.Module.ProfileEditor.LayerProperties
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
_coreService.FrameRendering += CoreServiceOnFrameRendering;
|
CoreService.FrameRendering += CoreServiceOnFrameRendering;
|
||||||
Playing = true;
|
Playing = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -159,39 +149,39 @@ namespace Artemis.UI.Screens.Module.ProfileEditor.LayerProperties
|
|||||||
if (!Playing)
|
if (!Playing)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
_coreService.FrameRendering -= CoreServiceOnFrameRendering;
|
CoreService.FrameRendering -= CoreServiceOnFrameRendering;
|
||||||
Playing = false;
|
Playing = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
public void GoToStart()
|
public void GoToStart()
|
||||||
{
|
{
|
||||||
_profileEditorService.CurrentTime = TimeSpan.Zero;
|
ProfileEditorService.CurrentTime = TimeSpan.Zero;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void GoToEnd()
|
public void GoToEnd()
|
||||||
{
|
{
|
||||||
_profileEditorService.CurrentTime = CalculateEndTime();
|
ProfileEditorService.CurrentTime = CalculateEndTime();
|
||||||
}
|
}
|
||||||
|
|
||||||
public void GoToPreviousFrame()
|
public void GoToPreviousFrame()
|
||||||
{
|
{
|
||||||
var frameTime = 1000.0 / _settingsService.GetSetting("Core.TargetFrameRate", 25).Value;
|
var frameTime = 1000.0 / SettingsService.GetSetting("Core.TargetFrameRate", 25).Value;
|
||||||
var newTime = Math.Max(0, Math.Round((_profileEditorService.CurrentTime.TotalMilliseconds - frameTime) / frameTime) * frameTime);
|
var newTime = Math.Max(0, Math.Round((ProfileEditorService.CurrentTime.TotalMilliseconds - frameTime) / frameTime) * frameTime);
|
||||||
_profileEditorService.CurrentTime = TimeSpan.FromMilliseconds(newTime);
|
ProfileEditorService.CurrentTime = TimeSpan.FromMilliseconds(newTime);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void GoToNextFrame()
|
public void GoToNextFrame()
|
||||||
{
|
{
|
||||||
var frameTime = 1000.0 / _settingsService.GetSetting("Core.TargetFrameRate", 25).Value;
|
var frameTime = 1000.0 / SettingsService.GetSetting("Core.TargetFrameRate", 25).Value;
|
||||||
var newTime = Math.Round((_profileEditorService.CurrentTime.TotalMilliseconds + frameTime) / frameTime) * frameTime;
|
var newTime = Math.Round((ProfileEditorService.CurrentTime.TotalMilliseconds + frameTime) / frameTime) * frameTime;
|
||||||
newTime = Math.Min(newTime, CalculateEndTime().TotalMilliseconds);
|
newTime = Math.Min(newTime, CalculateEndTime().TotalMilliseconds);
|
||||||
_profileEditorService.CurrentTime = TimeSpan.FromMilliseconds(newTime);
|
ProfileEditorService.CurrentTime = TimeSpan.FromMilliseconds(newTime);
|
||||||
}
|
}
|
||||||
|
|
||||||
private TimeSpan CalculateEndTime()
|
private TimeSpan CalculateEndTime()
|
||||||
{
|
{
|
||||||
if (!(_profileEditorService.SelectedProfileElement is Layer layer))
|
if (!(ProfileEditorService.SelectedProfileElement is Layer layer))
|
||||||
return TimeSpan.MaxValue;
|
return TimeSpan.MaxValue;
|
||||||
|
|
||||||
var keyframes = GetKeyframes(false);
|
var keyframes = GetKeyframes(false);
|
||||||
@ -207,7 +197,7 @@ namespace Artemis.UI.Screens.Module.ProfileEditor.LayerProperties
|
|||||||
{
|
{
|
||||||
Execute.PostToUIThread(() =>
|
Execute.PostToUIThread(() =>
|
||||||
{
|
{
|
||||||
var newTime = _profileEditorService.CurrentTime.Add(TimeSpan.FromSeconds(e.DeltaTime));
|
var newTime = ProfileEditorService.CurrentTime.Add(TimeSpan.FromSeconds(e.DeltaTime));
|
||||||
if (RepeatAfterLastKeyframe)
|
if (RepeatAfterLastKeyframe)
|
||||||
{
|
{
|
||||||
if (newTime > CalculateEndTime().Subtract(TimeSpan.FromSeconds(10)))
|
if (newTime > CalculateEndTime().Subtract(TimeSpan.FromSeconds(10)))
|
||||||
@ -219,7 +209,7 @@ namespace Artemis.UI.Screens.Module.ProfileEditor.LayerProperties
|
|||||||
Pause();
|
Pause();
|
||||||
}
|
}
|
||||||
|
|
||||||
_profileEditorService.CurrentTime = newTime;
|
ProfileEditorService.CurrentTime = newTime;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -227,8 +217,6 @@ namespace Artemis.UI.Screens.Module.ProfileEditor.LayerProperties
|
|||||||
|
|
||||||
#region Caret movement
|
#region Caret movement
|
||||||
|
|
||||||
private int _pixelsPerSecond;
|
|
||||||
|
|
||||||
public void TimelineMouseDown(object sender, MouseButtonEventArgs e)
|
public void TimelineMouseDown(object sender, MouseButtonEventArgs e)
|
||||||
{
|
{
|
||||||
((IInputElement) sender).CaptureMouse();
|
((IInputElement) sender).CaptureMouse();
|
||||||
@ -246,28 +234,28 @@ namespace Artemis.UI.Screens.Module.ProfileEditor.LayerProperties
|
|||||||
// Get the parent grid, need that for our position
|
// Get the parent grid, need that for our position
|
||||||
var parent = (IInputElement) VisualTreeHelper.GetParent((DependencyObject) sender);
|
var parent = (IInputElement) VisualTreeHelper.GetParent((DependencyObject) sender);
|
||||||
var x = Math.Max(0, e.GetPosition(parent).X);
|
var x = Math.Max(0, e.GetPosition(parent).X);
|
||||||
var newTime = TimeSpan.FromSeconds(x / PixelsPerSecond);
|
var newTime = TimeSpan.FromSeconds(x / ProfileEditorService.PixelsPerSecond);
|
||||||
|
|
||||||
// Round the time to something that fits the current zoom level
|
// Round the time to something that fits the current zoom level
|
||||||
if (PixelsPerSecond < 200)
|
if (ProfileEditorService.PixelsPerSecond < 200)
|
||||||
newTime = TimeSpan.FromMilliseconds(Math.Round(newTime.TotalMilliseconds / 5.0) * 5.0);
|
newTime = TimeSpan.FromMilliseconds(Math.Round(newTime.TotalMilliseconds / 5.0) * 5.0);
|
||||||
else if (PixelsPerSecond < 500)
|
else if (ProfileEditorService.PixelsPerSecond < 500)
|
||||||
newTime = TimeSpan.FromMilliseconds(Math.Round(newTime.TotalMilliseconds / 2.0) * 2.0);
|
newTime = TimeSpan.FromMilliseconds(Math.Round(newTime.TotalMilliseconds / 2.0) * 2.0);
|
||||||
else
|
else
|
||||||
newTime = TimeSpan.FromMilliseconds(Math.Round(newTime.TotalMilliseconds));
|
newTime = TimeSpan.FromMilliseconds(Math.Round(newTime.TotalMilliseconds));
|
||||||
|
|
||||||
if (!Keyboard.IsKeyDown(Key.LeftShift) && !Keyboard.IsKeyDown(Key.RightShift))
|
if (!Keyboard.IsKeyDown(Key.LeftShift) && !Keyboard.IsKeyDown(Key.RightShift))
|
||||||
{
|
{
|
||||||
_profileEditorService.CurrentTime = newTime;
|
ProfileEditorService.CurrentTime = newTime;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
var visibleKeyframes = GetKeyframes(true);
|
var visibleKeyframes = GetKeyframes(true);
|
||||||
|
|
||||||
// Take a tolerance of 5 pixels (half a keyframe width)
|
// Take a tolerance of 5 pixels (half a keyframe width)
|
||||||
var tolerance = 1000f / PixelsPerSecond * 5;
|
var tolerance = 1000f / ProfileEditorService.PixelsPerSecond * 5;
|
||||||
var closeKeyframe = visibleKeyframes.FirstOrDefault(k => Math.Abs(k.Position.TotalMilliseconds - newTime.TotalMilliseconds) < tolerance);
|
var closeKeyframe = visibleKeyframes.FirstOrDefault(k => Math.Abs(k.Position.TotalMilliseconds - newTime.TotalMilliseconds) < tolerance);
|
||||||
_profileEditorService.CurrentTime = closeKeyframe?.Position ?? newTime;
|
ProfileEditorService.CurrentTime = closeKeyframe?.Position ?? newTime;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -281,16 +269,5 @@ namespace Artemis.UI.Screens.Module.ProfileEditor.LayerProperties
|
|||||||
}
|
}
|
||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
#region Events
|
|
||||||
|
|
||||||
public event EventHandler PixelsPerSecondChanged;
|
|
||||||
|
|
||||||
protected virtual void OnPixelsPerSecondChanged()
|
|
||||||
{
|
|
||||||
PixelsPerSecondChanged?.Invoke(this, EventArgs.Empty);
|
|
||||||
}
|
|
||||||
|
|
||||||
#endregion
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -77,5 +77,18 @@ namespace Artemis.UI.Screens.Module.ProfileEditor.LayerProperties
|
|||||||
foreach (var layerPropertyBaseViewModel in Children)
|
foreach (var layerPropertyBaseViewModel in Children)
|
||||||
layerPropertyBaseViewModel.Dispose();
|
layerPropertyBaseViewModel.Dispose();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public List<LayerPropertyBaseViewModel> GetAllChildren()
|
||||||
|
{
|
||||||
|
var result = new List<LayerPropertyBaseViewModel>();
|
||||||
|
foreach (var layerPropertyBaseViewModel in Children)
|
||||||
|
{
|
||||||
|
result.Add(layerPropertyBaseViewModel);
|
||||||
|
if (layerPropertyBaseViewModel is LayerPropertyGroupViewModel layerPropertyGroupViewModel)
|
||||||
|
result.AddRange(layerPropertyGroupViewModel.GetAllChildren());
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -1,14 +0,0 @@
|
|||||||
using Artemis.Core.Models.Profile.LayerProperties;
|
|
||||||
|
|
||||||
namespace Artemis.UI.Screens.Module.ProfileEditor.LayerProperties
|
|
||||||
{
|
|
||||||
public class LayerKeyframeViewModel<T>
|
|
||||||
{
|
|
||||||
public LayerKeyframeViewModel(LayerPropertyKeyframe<T> keyframe)
|
|
||||||
{
|
|
||||||
Keyframe = keyframe;
|
|
||||||
}
|
|
||||||
|
|
||||||
public LayerPropertyKeyframe<T> Keyframe { get; }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -19,7 +19,7 @@ namespace Artemis.UI.Screens.Module.ProfileEditor.LayerProperties
|
|||||||
PropertyDescription = propertyDescription;
|
PropertyDescription = propertyDescription;
|
||||||
|
|
||||||
TreePropertyViewModel = ProfileEditorService.CreateTreePropertyViewModel(this);
|
TreePropertyViewModel = ProfileEditorService.CreateTreePropertyViewModel(this);
|
||||||
TimelinePropertyViewModel = new TimelinePropertyViewModel<T>(this);
|
TimelinePropertyViewModel = new TimelinePropertyViewModel<T>(this, profileEditorService);
|
||||||
|
|
||||||
TreePropertyBaseViewModel = TreePropertyViewModel;
|
TreePropertyBaseViewModel = TreePropertyViewModel;
|
||||||
TimelinePropertyBaseViewModel = TimelinePropertyViewModel;
|
TimelinePropertyBaseViewModel = TimelinePropertyViewModel;
|
||||||
@ -50,7 +50,6 @@ namespace Artemis.UI.Screens.Module.ProfileEditor.LayerProperties
|
|||||||
public override void Dispose()
|
public override void Dispose()
|
||||||
{
|
{
|
||||||
TreePropertyViewModel.Dispose();
|
TreePropertyViewModel.Dispose();
|
||||||
TimelinePropertyViewModel.Dispose();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void SetCurrentValue(T value, bool saveChanges)
|
public void SetCurrentValue(T value, bool saveChanges)
|
||||||
|
|||||||
@ -0,0 +1,50 @@
|
|||||||
|
using System.Windows;
|
||||||
|
using System.Windows.Media;
|
||||||
|
using Artemis.Core.Utilities;
|
||||||
|
using Humanizer;
|
||||||
|
|
||||||
|
namespace Artemis.UI.Screens.Module.ProfileEditor.LayerProperties.Timeline
|
||||||
|
{
|
||||||
|
public class TimelineEasingViewModel
|
||||||
|
{
|
||||||
|
private readonly TimelineKeyframeViewModel _keyframeViewModel;
|
||||||
|
private bool _isEasingModeSelected;
|
||||||
|
|
||||||
|
public TimelineEasingViewModel(TimelineKeyframeViewModel keyframeViewModel, Easings.Functions easingFunction)
|
||||||
|
{
|
||||||
|
_keyframeViewModel = keyframeViewModel;
|
||||||
|
_isEasingModeSelected = keyframeViewModel.BaseLayerPropertyKeyframe.EasingFunction == easingFunction;
|
||||||
|
|
||||||
|
EasingFunction = easingFunction;
|
||||||
|
Description = easingFunction.Humanize();
|
||||||
|
|
||||||
|
CreateGeometry();
|
||||||
|
}
|
||||||
|
|
||||||
|
public Easings.Functions EasingFunction { get; }
|
||||||
|
public PointCollection EasingPoints { get; set; }
|
||||||
|
public string Description { get; set; }
|
||||||
|
|
||||||
|
public bool IsEasingModeSelected
|
||||||
|
{
|
||||||
|
get => _isEasingModeSelected;
|
||||||
|
set
|
||||||
|
{
|
||||||
|
_isEasingModeSelected = value;
|
||||||
|
if (_isEasingModeSelected)
|
||||||
|
_keyframeViewModel.SelectEasingMode(this);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void CreateGeometry()
|
||||||
|
{
|
||||||
|
EasingPoints = new PointCollection();
|
||||||
|
for (var i = 1; i <= 10; i++)
|
||||||
|
{
|
||||||
|
var x = i;
|
||||||
|
var y = Easings.Interpolate(i / 10.0, EasingFunction) * 10;
|
||||||
|
EasingPoints.Add(new Point(x, y));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,189 @@
|
|||||||
|
using System;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Windows;
|
||||||
|
using System.Windows.Input;
|
||||||
|
using Artemis.Core.Models.Profile.LayerProperties;
|
||||||
|
using Artemis.Core.Utilities;
|
||||||
|
using Artemis.UI.Services.Interfaces;
|
||||||
|
using Stylet;
|
||||||
|
|
||||||
|
namespace Artemis.UI.Screens.Module.ProfileEditor.LayerProperties.Timeline
|
||||||
|
{
|
||||||
|
public class TimelineKeyframeViewModel<T> : TimelineKeyframeViewModel
|
||||||
|
{
|
||||||
|
private readonly IProfileEditorService _profileEditorService;
|
||||||
|
|
||||||
|
public TimelineKeyframeViewModel(IProfileEditorService profileEditorService, TimelineViewModel timelineViewModel, LayerPropertyKeyframe<T> layerPropertyKeyframe)
|
||||||
|
: base(profileEditorService, timelineViewModel, layerPropertyKeyframe)
|
||||||
|
{
|
||||||
|
_profileEditorService = profileEditorService;
|
||||||
|
LayerPropertyKeyframe = layerPropertyKeyframe;
|
||||||
|
}
|
||||||
|
|
||||||
|
public LayerPropertyKeyframe<T> LayerPropertyKeyframe { get; }
|
||||||
|
|
||||||
|
#region Context menu actions
|
||||||
|
|
||||||
|
public void Copy()
|
||||||
|
{
|
||||||
|
var newKeyframe = new LayerPropertyKeyframe<T>(
|
||||||
|
LayerPropertyKeyframe.Value,
|
||||||
|
LayerPropertyKeyframe.Position,
|
||||||
|
LayerPropertyKeyframe.EasingFunction,
|
||||||
|
LayerPropertyKeyframe.LayerProperty
|
||||||
|
);
|
||||||
|
LayerPropertyKeyframe.LayerProperty.AddKeyframe(newKeyframe);
|
||||||
|
_profileEditorService.UpdateSelectedProfileElement();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Delete()
|
||||||
|
{
|
||||||
|
LayerPropertyKeyframe.LayerProperty.RemoveKeyframe(LayerPropertyKeyframe);
|
||||||
|
_profileEditorService.UpdateSelectedProfileElement();
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
}
|
||||||
|
|
||||||
|
public abstract class TimelineKeyframeViewModel
|
||||||
|
{
|
||||||
|
private readonly IProfileEditorService _profileEditorService;
|
||||||
|
private readonly TimelineViewModel _timelineViewModel;
|
||||||
|
private int _pixelsPerSecond;
|
||||||
|
|
||||||
|
protected TimelineKeyframeViewModel(IProfileEditorService profileEditorService, TimelineViewModel timelineViewModel, BaseLayerPropertyKeyframe baseLayerPropertyKeyframe)
|
||||||
|
{
|
||||||
|
_profileEditorService = profileEditorService;
|
||||||
|
_timelineViewModel = timelineViewModel;
|
||||||
|
BaseLayerPropertyKeyframe = baseLayerPropertyKeyframe;
|
||||||
|
}
|
||||||
|
|
||||||
|
public BaseLayerPropertyKeyframe BaseLayerPropertyKeyframe { get; }
|
||||||
|
public BindableCollection<TimelineEasingViewModel> EasingViewModels { get; set; }
|
||||||
|
|
||||||
|
public bool IsSelected { get; set; }
|
||||||
|
public double X { get; set; }
|
||||||
|
public string Timestamp { get; set; }
|
||||||
|
|
||||||
|
public UIElement ParentView { get; set; }
|
||||||
|
|
||||||
|
public void Update(int pixelsPerSecond)
|
||||||
|
{
|
||||||
|
_pixelsPerSecond = pixelsPerSecond;
|
||||||
|
|
||||||
|
X = pixelsPerSecond * BaseLayerPropertyKeyframe.Position.TotalSeconds;
|
||||||
|
Timestamp = $"{Math.Floor(BaseLayerPropertyKeyframe.Position.TotalSeconds):00}.{BaseLayerPropertyKeyframe.Position.Milliseconds:000}";
|
||||||
|
}
|
||||||
|
|
||||||
|
#region Keyframe movement
|
||||||
|
|
||||||
|
public void KeyframeMouseDown(object sender, MouseButtonEventArgs e)
|
||||||
|
{
|
||||||
|
if (e.LeftButton == MouseButtonState.Released)
|
||||||
|
return;
|
||||||
|
|
||||||
|
((IInputElement) sender).CaptureMouse();
|
||||||
|
if (Keyboard.IsKeyDown(Key.LeftShift) || Keyboard.IsKeyDown(Key.RightShift) && !IsSelected)
|
||||||
|
_timelineViewModel.SelectKeyframe(this, true, false);
|
||||||
|
else if (Keyboard.IsKeyDown(Key.LeftCtrl) || Keyboard.IsKeyDown(Key.RightCtrl))
|
||||||
|
_timelineViewModel.SelectKeyframe(this, false, true);
|
||||||
|
else if (!IsSelected)
|
||||||
|
_timelineViewModel.SelectKeyframe(this, false, false);
|
||||||
|
|
||||||
|
e.Handled = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void KeyframeMouseUp(object sender, MouseButtonEventArgs e)
|
||||||
|
{
|
||||||
|
_profileEditorService.UpdateSelectedProfileElement();
|
||||||
|
_timelineViewModel.ReleaseSelectedKeyframes();
|
||||||
|
|
||||||
|
((IInputElement) sender).ReleaseMouseCapture();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void KeyframeMouseMove(object sender, MouseEventArgs e)
|
||||||
|
{
|
||||||
|
if (e.LeftButton == MouseButtonState.Pressed)
|
||||||
|
_timelineViewModel.MoveSelectedKeyframes(GetCursorTime(e.GetPosition(ParentView)));
|
||||||
|
|
||||||
|
e.Handled = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
private TimeSpan GetCursorTime(Point position)
|
||||||
|
{
|
||||||
|
// Get the parent grid, need that for our position
|
||||||
|
var x = Math.Max(0, position.X);
|
||||||
|
var time = TimeSpan.FromSeconds(x / _pixelsPerSecond);
|
||||||
|
|
||||||
|
// Round the time to something that fits the current zoom level
|
||||||
|
if (_pixelsPerSecond < 200)
|
||||||
|
time = TimeSpan.FromMilliseconds(Math.Round(time.TotalMilliseconds / 5.0) * 5.0);
|
||||||
|
else if (_pixelsPerSecond < 500)
|
||||||
|
time = TimeSpan.FromMilliseconds(Math.Round(time.TotalMilliseconds / 2.0) * 2.0);
|
||||||
|
else
|
||||||
|
time = TimeSpan.FromMilliseconds(Math.Round(time.TotalMilliseconds));
|
||||||
|
|
||||||
|
// If shift is held, snap to the current time
|
||||||
|
// Take a tolerance of 5 pixels (half a keyframe width)
|
||||||
|
if (Keyboard.IsKeyDown(Key.LeftShift) || Keyboard.IsKeyDown(Key.RightShift))
|
||||||
|
{
|
||||||
|
var tolerance = 1000f / _pixelsPerSecond * 5;
|
||||||
|
if (Math.Abs(_profileEditorService.CurrentTime.TotalMilliseconds - time.TotalMilliseconds) < tolerance)
|
||||||
|
time = _profileEditorService.CurrentTime;
|
||||||
|
}
|
||||||
|
|
||||||
|
return time;
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region Easing
|
||||||
|
|
||||||
|
private void CreateEasingViewModels()
|
||||||
|
{
|
||||||
|
EasingViewModels.AddRange(Enum.GetValues(typeof(Easings.Functions)).Cast<Easings.Functions>().Select(v => new TimelineEasingViewModel(this, v)));
|
||||||
|
}
|
||||||
|
|
||||||
|
public void SelectEasingMode(TimelineEasingViewModel easingViewModel)
|
||||||
|
{
|
||||||
|
BaseLayerPropertyKeyframe.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();
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region Movement
|
||||||
|
|
||||||
|
private bool _movementReleased = true;
|
||||||
|
private TimeSpan _startOffset;
|
||||||
|
|
||||||
|
public void ApplyMovement(TimeSpan cursorTime)
|
||||||
|
{
|
||||||
|
if (_movementReleased)
|
||||||
|
{
|
||||||
|
_movementReleased = false;
|
||||||
|
_startOffset = cursorTime - BaseLayerPropertyKeyframe.Position;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
BaseLayerPropertyKeyframe.Position = cursorTime - _startOffset;
|
||||||
|
if (BaseLayerPropertyKeyframe.Position < TimeSpan.Zero)
|
||||||
|
BaseLayerPropertyKeyframe.Position = TimeSpan.Zero;
|
||||||
|
|
||||||
|
Update(_pixelsPerSecond);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void ReleaseMovement()
|
||||||
|
{
|
||||||
|
_movementReleased = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,86 @@
|
|||||||
|
<UserControl x:Class="Artemis.UI.Screens.Module.ProfileEditor.LayerProperties.Timeline.TimelinePropertyGroupView"
|
||||||
|
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:local="clr-namespace:Artemis.UI.Screens.Module.ProfileEditor.LayerProperties.Timeline"
|
||||||
|
xmlns:s="https://github.com/canton7/Stylet"
|
||||||
|
xmlns:layerProperties="clr-namespace:Artemis.UI.Screens.Module.ProfileEditor.LayerProperties"
|
||||||
|
xmlns:materialDesign="http://materialdesigninxaml.net/winfx/xaml/themes"
|
||||||
|
mc:Ignorable="d"
|
||||||
|
d:DesignHeight="450" d:DesignWidth="800"
|
||||||
|
d:DataContext="{d:DesignInstance local:TimelinePropertyGroupViewModel}">
|
||||||
|
<Grid>
|
||||||
|
<Grid.RowDefinitions>
|
||||||
|
<RowDefinition Height="25" />
|
||||||
|
<RowDefinition Height="Auto" />
|
||||||
|
</Grid.RowDefinitions>
|
||||||
|
<Border Height="25" BorderThickness="0,0,0,1" BorderBrush="{DynamicResource MaterialDesignDivider}" Grid.Row="0">
|
||||||
|
<ItemsControl ItemsSource="{Binding TimelineKeyframeViewModels}"
|
||||||
|
Background="{DynamicResource MaterialDesignToolBarBackground}"
|
||||||
|
HorizontalAlignment="Left">
|
||||||
|
<ItemsControl.ItemsPanel>
|
||||||
|
<ItemsPanelTemplate>
|
||||||
|
<Canvas />
|
||||||
|
</ItemsPanelTemplate>
|
||||||
|
</ItemsControl.ItemsPanel>
|
||||||
|
<ItemsControl.ItemContainerStyle>
|
||||||
|
<Style TargetType="{x:Type ContentPresenter}">
|
||||||
|
<Setter Property="Canvas.Left" Value="{Binding X}" />
|
||||||
|
</Style>
|
||||||
|
</ItemsControl.ItemContainerStyle>
|
||||||
|
<ItemsControl.ItemTemplate>
|
||||||
|
<DataTemplate>
|
||||||
|
<Ellipse Fill="{StaticResource PrimaryHueMidBrush}"
|
||||||
|
Stroke="White"
|
||||||
|
StrokeThickness="0"
|
||||||
|
Width="10"
|
||||||
|
Height="10"
|
||||||
|
Margin="-5,6,0,0"
|
||||||
|
s:View.ActionTarget="{Binding}">
|
||||||
|
<Ellipse.Style>
|
||||||
|
<Style TargetType="{x:Type Ellipse}">
|
||||||
|
<Style.Triggers>
|
||||||
|
<DataTrigger Binding="{Binding IsSelected}" Value="True">
|
||||||
|
<DataTrigger.EnterActions>
|
||||||
|
<BeginStoryboard>
|
||||||
|
<Storyboard>
|
||||||
|
<DoubleAnimation Storyboard.TargetProperty="StrokeThickness" To="1" Duration="0:0:0.25" />
|
||||||
|
</Storyboard>
|
||||||
|
</BeginStoryboard>
|
||||||
|
</DataTrigger.EnterActions>
|
||||||
|
<DataTrigger.ExitActions>
|
||||||
|
<BeginStoryboard>
|
||||||
|
<Storyboard>
|
||||||
|
<DoubleAnimation Storyboard.TargetProperty="StrokeThickness" To="0" Duration="0:0:0.25" />
|
||||||
|
</Storyboard>
|
||||||
|
</BeginStoryboard>
|
||||||
|
</DataTrigger.ExitActions>
|
||||||
|
</DataTrigger>
|
||||||
|
</Style.Triggers>
|
||||||
|
</Style>
|
||||||
|
</Ellipse.Style>
|
||||||
|
</Ellipse>
|
||||||
|
</DataTemplate>
|
||||||
|
</ItemsControl.ItemTemplate>
|
||||||
|
</ItemsControl>
|
||||||
|
</Border>
|
||||||
|
<ItemsControl ItemsSource="{Binding LayerPropertyGroupViewModel.Children}" Grid.Row="1">
|
||||||
|
<ItemsControl.Resources>
|
||||||
|
<DataTemplate DataType="{x:Type layerProperties:LayerPropertyGroupViewModel}">
|
||||||
|
<ContentControl s:View.Model="{Binding TimelinePropertyGroupViewModel}"
|
||||||
|
VerticalContentAlignment="Stretch"
|
||||||
|
HorizontalContentAlignment="Stretch"
|
||||||
|
IsTabStop="False" />
|
||||||
|
</DataTemplate>
|
||||||
|
<DataTemplate DataType="{x:Type layerProperties:LayerPropertyViewModel}">
|
||||||
|
<ContentControl s:View.Model="{Binding TimelinePropertyBaseViewModel}"
|
||||||
|
VerticalContentAlignment="Stretch"
|
||||||
|
HorizontalContentAlignment="Stretch"
|
||||||
|
IsTabStop="False" />
|
||||||
|
</DataTemplate>
|
||||||
|
</ItemsControl.Resources>
|
||||||
|
</ItemsControl>
|
||||||
|
</Grid>
|
||||||
|
|
||||||
|
</UserControl>
|
||||||
@ -0,0 +1,108 @@
|
|||||||
|
<UserControl x:Class="Artemis.UI.Screens.Module.ProfileEditor.LayerProperties.Timeline.TimelinePropertyView"
|
||||||
|
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:local="clr-namespace:Artemis.UI.Screens.Module.ProfileEditor.LayerProperties.Timeline"
|
||||||
|
xmlns:materialDesign="http://materialdesigninxaml.net/winfx/xaml/themes"
|
||||||
|
xmlns:s="https://github.com/canton7/Stylet"
|
||||||
|
mc:Ignorable="d"
|
||||||
|
d:DesignHeight="450" d:DesignWidth="800"
|
||||||
|
d:DataContext="{d:DesignInstance local:TimelinePropertyViewModel}">
|
||||||
|
<Border Height="25" BorderThickness="0,0,0,1" BorderBrush="{DynamicResource MaterialDesignDivider}">
|
||||||
|
<ItemsControl ItemsSource="{Binding TimelineKeyframeViewModels}"
|
||||||
|
Background="{DynamicResource MaterialDesignToolBarBackground}"
|
||||||
|
HorizontalAlignment="Left">
|
||||||
|
<ItemsControl.ItemsPanel>
|
||||||
|
<ItemsPanelTemplate>
|
||||||
|
<Canvas />
|
||||||
|
</ItemsPanelTemplate>
|
||||||
|
</ItemsControl.ItemsPanel>
|
||||||
|
<ItemsControl.ItemContainerStyle>
|
||||||
|
<Style TargetType="{x:Type ContentPresenter}">
|
||||||
|
<Setter Property="Canvas.Left" Value="{Binding X}" />
|
||||||
|
</Style>
|
||||||
|
</ItemsControl.ItemContainerStyle>
|
||||||
|
<ItemsControl.ItemTemplate>
|
||||||
|
<DataTemplate>
|
||||||
|
<Ellipse Fill="{StaticResource PrimaryHueMidBrush}"
|
||||||
|
Stroke="White"
|
||||||
|
StrokeThickness="0"
|
||||||
|
Width="10"
|
||||||
|
Height="10"
|
||||||
|
Margin="-5,6,0,0"
|
||||||
|
ToolTip="{Binding Timestamp}"
|
||||||
|
s:View.ActionTarget="{Binding}"
|
||||||
|
MouseDown="{s:Action KeyframeMouseDown}"
|
||||||
|
MouseUp="{s:Action KeyframeMouseUp}"
|
||||||
|
MouseMove="{s:Action KeyframeMouseMove}">
|
||||||
|
<Ellipse.Style>
|
||||||
|
<Style TargetType="{x:Type Ellipse}">
|
||||||
|
<Style.Triggers>
|
||||||
|
<DataTrigger Binding="{Binding IsSelected}" Value="True">
|
||||||
|
<DataTrigger.EnterActions>
|
||||||
|
<BeginStoryboard>
|
||||||
|
<Storyboard>
|
||||||
|
<DoubleAnimation Storyboard.TargetProperty="StrokeThickness" To="1" Duration="0:0:0.25" />
|
||||||
|
</Storyboard>
|
||||||
|
</BeginStoryboard>
|
||||||
|
</DataTrigger.EnterActions>
|
||||||
|
<DataTrigger.ExitActions>
|
||||||
|
<BeginStoryboard>
|
||||||
|
<Storyboard>
|
||||||
|
<DoubleAnimation Storyboard.TargetProperty="StrokeThickness" To="0" Duration="0:0:0.25" />
|
||||||
|
</Storyboard>
|
||||||
|
</BeginStoryboard>
|
||||||
|
</DataTrigger.ExitActions>
|
||||||
|
</DataTrigger>
|
||||||
|
</Style.Triggers>
|
||||||
|
</Style>
|
||||||
|
</Ellipse.Style>
|
||||||
|
<Ellipse.ContextMenu>
|
||||||
|
<ContextMenu>
|
||||||
|
<MenuItem Header="Copy" Command="{s:Action Copy}">
|
||||||
|
<MenuItem.Icon>
|
||||||
|
<materialDesign:PackIcon Kind="ContentCopy" />
|
||||||
|
</MenuItem.Icon>
|
||||||
|
</MenuItem>
|
||||||
|
<MenuItem Header="Delete" Command="{s:Action Delete}">
|
||||||
|
<MenuItem.Icon>
|
||||||
|
<materialDesign:PackIcon Kind="Delete" />
|
||||||
|
</MenuItem.Icon>
|
||||||
|
</MenuItem>
|
||||||
|
<Separator />
|
||||||
|
<MenuItem Header="Easing" ItemsSource="{Binding EasingViewModels}">
|
||||||
|
<MenuItem.ItemContainerStyle>
|
||||||
|
<Style TargetType="{x:Type MenuItem}" BasedOn="{StaticResource MaterialDesignMenuItem}">
|
||||||
|
<Setter Property="IsCheckable" Value="True" />
|
||||||
|
<Setter Property="IsChecked" Value="{Binding Path=IsEasingModeSelected, Mode=TwoWay}" />
|
||||||
|
</Style>
|
||||||
|
</MenuItem.ItemContainerStyle>
|
||||||
|
<MenuItem.ItemTemplate>
|
||||||
|
<DataTemplate>
|
||||||
|
<StackPanel Orientation="Horizontal">
|
||||||
|
<Polyline Stroke="{DynamicResource MaterialDesignBody}"
|
||||||
|
StrokeThickness="1"
|
||||||
|
Points="{Binding EasingPoints}"
|
||||||
|
Stretch="Uniform"
|
||||||
|
Width="20"
|
||||||
|
Height="20"
|
||||||
|
Margin="0 0 10 0" />
|
||||||
|
<TextBlock Text="{Binding Description}" />
|
||||||
|
</StackPanel>
|
||||||
|
</DataTemplate>
|
||||||
|
</MenuItem.ItemTemplate>
|
||||||
|
</MenuItem>
|
||||||
|
<!-- <MenuItem Header="Easing mode" IsEnabled="{Binding CanSelectEasingMode}"> -->
|
||||||
|
<!-- <MenuItem Header="Ease in" Command="{s:Action SetEasingMode}" CommandParameter="EaseIn" /> -->
|
||||||
|
<!-- <MenuItem Header="Ease out" Command="{s:Action SetEasingMode}" CommandParameter="EaseOut" /> -->
|
||||||
|
<!-- <MenuItem Header="Ease in and out" Command="{s:Action SetEasingMode}" CommandParameter="EaseInOut" /> -->
|
||||||
|
<!-- </MenuItem> -->
|
||||||
|
</ContextMenu>
|
||||||
|
</Ellipse.ContextMenu>
|
||||||
|
</Ellipse>
|
||||||
|
</DataTemplate>
|
||||||
|
</ItemsControl.ItemTemplate>
|
||||||
|
</ItemsControl>
|
||||||
|
</Border>
|
||||||
|
</UserControl>
|
||||||
@ -1,30 +1,49 @@
|
|||||||
using System;
|
using System.Linq;
|
||||||
using Artemis.UI.Screens.Module.ProfileEditor.LayerProperties.Abstract;
|
using Artemis.UI.Screens.Module.ProfileEditor.LayerProperties.Abstract;
|
||||||
|
using Artemis.UI.Services.Interfaces;
|
||||||
|
using Stylet;
|
||||||
|
|
||||||
namespace Artemis.UI.Screens.Module.ProfileEditor.LayerProperties.Timeline
|
namespace Artemis.UI.Screens.Module.ProfileEditor.LayerProperties.Timeline
|
||||||
{
|
{
|
||||||
public class TimelinePropertyViewModel<T> : TimelinePropertyViewModel
|
public class TimelinePropertyViewModel<T> : TimelinePropertyViewModel
|
||||||
{
|
{
|
||||||
public TimelinePropertyViewModel(LayerPropertyBaseViewModel layerPropertyBaseViewModel) : base(layerPropertyBaseViewModel)
|
private readonly IProfileEditorService _profileEditorService;
|
||||||
|
|
||||||
|
public TimelinePropertyViewModel(LayerPropertyBaseViewModel layerPropertyBaseViewModel, IProfileEditorService profileEditorService) : base(layerPropertyBaseViewModel)
|
||||||
{
|
{
|
||||||
|
_profileEditorService = profileEditorService;
|
||||||
LayerPropertyViewModel = (LayerPropertyViewModel<T>) layerPropertyBaseViewModel;
|
LayerPropertyViewModel = (LayerPropertyViewModel<T>) layerPropertyBaseViewModel;
|
||||||
}
|
}
|
||||||
|
|
||||||
public LayerPropertyViewModel<T> LayerPropertyViewModel { get; }
|
public LayerPropertyViewModel<T> LayerPropertyViewModel { get; }
|
||||||
|
|
||||||
public override void Dispose()
|
public override void UpdateKeyframes(TimelineViewModel timelineViewModel)
|
||||||
{
|
{
|
||||||
|
var keyframes = LayerPropertyViewModel.LayerProperty.Keyframes.ToList();
|
||||||
|
TimelineKeyframeViewModels.RemoveRange(
|
||||||
|
TimelineKeyframeViewModels.Where(t => !keyframes.Contains(t.BaseLayerPropertyKeyframe))
|
||||||
|
);
|
||||||
|
TimelineKeyframeViewModels.AddRange(
|
||||||
|
keyframes.Where(k => TimelineKeyframeViewModels.All(t => t.BaseLayerPropertyKeyframe != k))
|
||||||
|
.Select(k => new TimelineKeyframeViewModel<T>(_profileEditorService, timelineViewModel, k))
|
||||||
|
);
|
||||||
|
|
||||||
|
foreach (var timelineKeyframeViewModel in TimelineKeyframeViewModels)
|
||||||
|
timelineKeyframeViewModel.Update(_profileEditorService.PixelsPerSecond);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public abstract class TimelinePropertyViewModel : IDisposable
|
public abstract class TimelinePropertyViewModel
|
||||||
{
|
{
|
||||||
protected TimelinePropertyViewModel(LayerPropertyBaseViewModel layerPropertyBaseViewModel)
|
protected TimelinePropertyViewModel(LayerPropertyBaseViewModel layerPropertyBaseViewModel)
|
||||||
{
|
{
|
||||||
LayerPropertyBaseViewModel = layerPropertyBaseViewModel;
|
LayerPropertyBaseViewModel = layerPropertyBaseViewModel;
|
||||||
|
TimelineKeyframeViewModels = new BindableCollection<TimelineKeyframeViewModel>();
|
||||||
}
|
}
|
||||||
|
|
||||||
public LayerPropertyBaseViewModel LayerPropertyBaseViewModel { get; }
|
public LayerPropertyBaseViewModel LayerPropertyBaseViewModel { get; }
|
||||||
public abstract void Dispose();
|
public BindableCollection<TimelineKeyframeViewModel> TimelineKeyframeViewModels { get; set; }
|
||||||
|
|
||||||
|
public abstract void UpdateKeyframes(TimelineViewModel timelineViewModel);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -4,9 +4,52 @@
|
|||||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||||
xmlns:local="clr-namespace:Artemis.UI.Screens.Module.ProfileEditor.LayerProperties.Timeline"
|
xmlns:local="clr-namespace:Artemis.UI.Screens.Module.ProfileEditor.LayerProperties.Timeline"
|
||||||
mc:Ignorable="d"
|
xmlns:s="https://github.com/canton7/Stylet"
|
||||||
d:DesignHeight="450" d:DesignWidth="800">
|
mc:Ignorable="d"
|
||||||
<Grid>
|
d:DesignHeight="25"
|
||||||
|
d:DesignWidth="800"
|
||||||
|
d:DataContext="{d:DesignInstance local:TimelineViewModel}">
|
||||||
|
<Grid Background="{DynamicResource MaterialDesignToolBarBackground}"
|
||||||
|
MouseDown="{s:Action TimelineCanvasMouseDown}"
|
||||||
|
MouseUp="{s:Action TimelineCanvasMouseUp}"
|
||||||
|
MouseMove="{s:Action TimelineCanvasMouseMove}">
|
||||||
|
<Grid.Triggers>
|
||||||
|
<EventTrigger RoutedEvent="UIElement.MouseLeftButtonDown">
|
||||||
|
<BeginStoryboard>
|
||||||
|
<Storyboard Storyboard.TargetName="MultiSelectionPath" Storyboard.TargetProperty="Opacity">
|
||||||
|
<DoubleAnimation From="0" To="1" Duration="0:0:0.1" />
|
||||||
|
</Storyboard>
|
||||||
|
</BeginStoryboard>
|
||||||
|
</EventTrigger>
|
||||||
|
<EventTrigger RoutedEvent="UIElement.MouseLeftButtonUp">
|
||||||
|
<BeginStoryboard>
|
||||||
|
<Storyboard Storyboard.TargetName="MultiSelectionPath" Storyboard.TargetProperty="Opacity">
|
||||||
|
<DoubleAnimation From="1" To="0" Duration="0:0:0.2" />
|
||||||
|
</Storyboard>
|
||||||
|
</BeginStoryboard>
|
||||||
|
</EventTrigger>
|
||||||
|
</Grid.Triggers>
|
||||||
|
|
||||||
|
<ItemsControl ItemsSource="{Binding LayerPropertyGroups}"
|
||||||
|
Width="{Binding Width}"
|
||||||
|
MinWidth="{Binding ActualWidth, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=ScrollViewer}}"
|
||||||
|
HorizontalAlignment="Left">
|
||||||
|
<ItemsControl.ItemTemplate>
|
||||||
|
<DataTemplate>
|
||||||
|
<ContentControl s:View.Model="{Binding TimelinePropertyGroupViewModel}" VerticalContentAlignment="Stretch" HorizontalContentAlignment="Stretch" />
|
||||||
|
</DataTemplate>
|
||||||
|
</ItemsControl.ItemTemplate>
|
||||||
|
</ItemsControl>
|
||||||
|
|
||||||
|
<!-- Multi-selection rectangle -->
|
||||||
|
<Path Data="{Binding SelectionRectangle}" Opacity="0"
|
||||||
|
Stroke="{DynamicResource PrimaryHueLightBrush}"
|
||||||
|
StrokeThickness="1"
|
||||||
|
x:Name="MultiSelectionPath"
|
||||||
|
IsHitTestVisible="False">
|
||||||
|
<Path.Fill>
|
||||||
|
<SolidColorBrush Color="{DynamicResource Primary400}" Opacity="0.25" />
|
||||||
|
</Path.Fill>
|
||||||
|
</Path>
|
||||||
</Grid>
|
</Grid>
|
||||||
</UserControl>
|
</UserControl>
|
||||||
@ -1,14 +1,174 @@
|
|||||||
using Stylet;
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Windows;
|
||||||
|
using System.Windows.Input;
|
||||||
|
using System.Windows.Media;
|
||||||
|
using Artemis.UI.Screens.Module.ProfileEditor.LayerProperties.Abstract;
|
||||||
|
using Artemis.UI.Shared.Utilities;
|
||||||
|
using Stylet;
|
||||||
|
|
||||||
namespace Artemis.UI.Screens.Module.ProfileEditor.LayerProperties.Timeline
|
namespace Artemis.UI.Screens.Module.ProfileEditor.LayerProperties.Timeline
|
||||||
{
|
{
|
||||||
public class TimelineViewModel
|
public class TimelineViewModel
|
||||||
{
|
{
|
||||||
public TimelineViewModel(BindableCollection<LayerPropertyGroupViewModel> layerPropertyGroups)
|
private readonly LayerPropertiesViewModel _layerPropertiesViewModel;
|
||||||
|
|
||||||
|
public TimelineViewModel(LayerPropertiesViewModel layerPropertiesViewModel, BindableCollection<LayerPropertyGroupViewModel> layerPropertyGroups)
|
||||||
{
|
{
|
||||||
|
_layerPropertiesViewModel = layerPropertiesViewModel;
|
||||||
LayerPropertyGroups = layerPropertyGroups;
|
LayerPropertyGroups = layerPropertyGroups;
|
||||||
|
SelectionRectangle = new RectangleGeometry();
|
||||||
|
|
||||||
|
UpdateKeyframes();
|
||||||
}
|
}
|
||||||
|
|
||||||
public BindableCollection<LayerPropertyGroupViewModel> LayerPropertyGroups { get; }
|
public BindableCollection<LayerPropertyGroupViewModel> LayerPropertyGroups { get; }
|
||||||
|
|
||||||
|
public double Width { get; set; }
|
||||||
|
public RectangleGeometry SelectionRectangle { get; set; }
|
||||||
|
|
||||||
|
public void UpdateKeyframes()
|
||||||
|
{
|
||||||
|
foreach (var layerPropertyGroupViewModel in LayerPropertyGroups)
|
||||||
|
{
|
||||||
|
foreach (var layerPropertyBaseViewModel in layerPropertyGroupViewModel.GetAllChildren())
|
||||||
|
{
|
||||||
|
if (layerPropertyBaseViewModel is LayerPropertyViewModel layerPropertyViewModel)
|
||||||
|
layerPropertyViewModel.TimelinePropertyBaseViewModel.UpdateKeyframes(this);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#region Keyframe movement
|
||||||
|
|
||||||
|
public void MoveSelectedKeyframes(TimeSpan cursorTime)
|
||||||
|
{
|
||||||
|
// Ensure the selection rectangle doesn't show, the view isn't aware of different types of dragging
|
||||||
|
SelectionRectangle.Rect = new Rect();
|
||||||
|
|
||||||
|
var keyframeViewModels = GetAllKeyframeViewModels();
|
||||||
|
foreach (var keyframeViewModel in keyframeViewModels.Where(k => k.IsSelected))
|
||||||
|
keyframeViewModel.ApplyMovement(cursorTime);
|
||||||
|
|
||||||
|
_layerPropertiesViewModel.ProfileEditorService.UpdateProfilePreview();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public void ReleaseSelectedKeyframes()
|
||||||
|
{
|
||||||
|
var keyframeViewModels = GetAllKeyframeViewModels();
|
||||||
|
foreach (var keyframeViewModel in keyframeViewModels.Where(k => k.IsSelected))
|
||||||
|
keyframeViewModel.ReleaseMovement();
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region Keyframe selection
|
||||||
|
|
||||||
|
private Point _mouseDragStartPoint;
|
||||||
|
private bool _mouseDragging;
|
||||||
|
|
||||||
|
// ReSharper disable once UnusedMember.Global - Called from view
|
||||||
|
public void TimelineCanvasMouseDown(object sender, MouseButtonEventArgs e)
|
||||||
|
{
|
||||||
|
if (e.LeftButton == MouseButtonState.Released)
|
||||||
|
return;
|
||||||
|
|
||||||
|
((IInputElement) sender).CaptureMouse();
|
||||||
|
|
||||||
|
SelectionRectangle.Rect = new Rect();
|
||||||
|
_mouseDragStartPoint = e.GetPosition((IInputElement) sender);
|
||||||
|
_mouseDragging = true;
|
||||||
|
e.Handled = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ReSharper disable once UnusedMember.Global - Called from view
|
||||||
|
public void TimelineCanvasMouseUp(object sender, MouseEventArgs e)
|
||||||
|
{
|
||||||
|
if (!_mouseDragging)
|
||||||
|
return;
|
||||||
|
|
||||||
|
var position = e.GetPosition((IInputElement) sender);
|
||||||
|
var selectedRect = new Rect(_mouseDragStartPoint, position);
|
||||||
|
SelectionRectangle.Rect = selectedRect;
|
||||||
|
|
||||||
|
var keyframeViewModels = GetAllKeyframeViewModels();
|
||||||
|
var selectedKeyframes = HitTestUtilities.GetHitViewModels<TimelineKeyframeViewModel>((Visual) sender, SelectionRectangle);
|
||||||
|
foreach (var keyframeViewModel in keyframeViewModels)
|
||||||
|
keyframeViewModel.IsSelected = selectedKeyframes.Contains(keyframeViewModel);
|
||||||
|
|
||||||
|
_mouseDragging = false;
|
||||||
|
e.Handled = true;
|
||||||
|
((IInputElement) sender).ReleaseMouseCapture();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void TimelineCanvasMouseMove(object sender, MouseEventArgs e)
|
||||||
|
{
|
||||||
|
if (_mouseDragging && e.LeftButton == MouseButtonState.Pressed)
|
||||||
|
{
|
||||||
|
var position = e.GetPosition((IInputElement) sender);
|
||||||
|
var selectedRect = new Rect(_mouseDragStartPoint, position);
|
||||||
|
SelectionRectangle.Rect = selectedRect;
|
||||||
|
e.Handled = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void SelectKeyframe(TimelineKeyframeViewModel clicked, bool selectBetween, bool toggle)
|
||||||
|
{
|
||||||
|
var keyframeViewModels = GetAllKeyframeViewModels();
|
||||||
|
if (selectBetween)
|
||||||
|
{
|
||||||
|
var selectedIndex = keyframeViewModels.FindIndex(k => k.IsSelected);
|
||||||
|
// If nothing is selected, select only the clicked
|
||||||
|
if (selectedIndex == -1)
|
||||||
|
{
|
||||||
|
clicked.IsSelected = true;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach (var keyframeViewModel in keyframeViewModels)
|
||||||
|
keyframeViewModel.IsSelected = false;
|
||||||
|
|
||||||
|
var clickedIndex = keyframeViewModels.IndexOf(clicked);
|
||||||
|
if (clickedIndex < selectedIndex)
|
||||||
|
{
|
||||||
|
foreach (var keyframeViewModel in keyframeViewModels.Skip(clickedIndex).Take(selectedIndex - clickedIndex + 1))
|
||||||
|
keyframeViewModel.IsSelected = true;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
foreach (var keyframeViewModel in keyframeViewModels.Skip(selectedIndex).Take(clickedIndex - selectedIndex + 1))
|
||||||
|
keyframeViewModel.IsSelected = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (toggle)
|
||||||
|
{
|
||||||
|
// Toggle only the clicked keyframe, leave others alone
|
||||||
|
clicked.IsSelected = !clicked.IsSelected;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// Only select the clicked keyframe
|
||||||
|
foreach (var keyframeViewModel in keyframeViewModels)
|
||||||
|
keyframeViewModel.IsSelected = false;
|
||||||
|
clicked.IsSelected = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private List<TimelineKeyframeViewModel> GetAllKeyframeViewModels()
|
||||||
|
{
|
||||||
|
var viewModels = new List<LayerPropertyBaseViewModel>();
|
||||||
|
foreach (var layerPropertyGroupViewModel in LayerPropertyGroups)
|
||||||
|
viewModels.AddRange(layerPropertyGroupViewModel.GetAllChildren());
|
||||||
|
|
||||||
|
var keyframes = viewModels.Where(vm => vm is LayerPropertyViewModel)
|
||||||
|
.SelectMany(vm => ((LayerPropertyViewModel) vm).TimelinePropertyBaseViewModel.TimelineKeyframeViewModels)
|
||||||
|
.ToList();
|
||||||
|
|
||||||
|
return keyframes;
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -1,13 +1,18 @@
|
|||||||
using System;
|
using System;
|
||||||
using Artemis.UI.Screens.Module.ProfileEditor.LayerProperties.Abstract;
|
using Artemis.UI.Screens.Module.ProfileEditor.LayerProperties.Abstract;
|
||||||
using Artemis.UI.Screens.Module.ProfileEditor.LayerProperties.Tree.PropertyInput.Abstract;
|
using Artemis.UI.Screens.Module.ProfileEditor.LayerProperties.Tree.PropertyInput.Abstract;
|
||||||
|
using Artemis.UI.Services.Interfaces;
|
||||||
|
|
||||||
namespace Artemis.UI.Screens.Module.ProfileEditor.LayerProperties.Tree
|
namespace Artemis.UI.Screens.Module.ProfileEditor.LayerProperties.Tree
|
||||||
{
|
{
|
||||||
public class TreePropertyViewModel<T> : TreePropertyViewModel
|
public class TreePropertyViewModel<T> : TreePropertyViewModel
|
||||||
{
|
{
|
||||||
public TreePropertyViewModel(LayerPropertyBaseViewModel layerPropertyBaseViewModel, PropertyInputViewModel<T> propertyInputViewModel) : base(layerPropertyBaseViewModel)
|
private readonly IProfileEditorService _profileEditorService;
|
||||||
|
|
||||||
|
public TreePropertyViewModel(LayerPropertyBaseViewModel layerPropertyBaseViewModel, PropertyInputViewModel<T> propertyInputViewModel,
|
||||||
|
IProfileEditorService profileEditorService) : base(layerPropertyBaseViewModel)
|
||||||
{
|
{
|
||||||
|
_profileEditorService = profileEditorService;
|
||||||
LayerPropertyViewModel = (LayerPropertyViewModel<T>) layerPropertyBaseViewModel;
|
LayerPropertyViewModel = (LayerPropertyViewModel<T>) layerPropertyBaseViewModel;
|
||||||
PropertyInputViewModel = propertyInputViewModel;
|
PropertyInputViewModel = propertyInputViewModel;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -90,7 +90,7 @@
|
|||||||
<TreeView.ItemContainerStyle>
|
<TreeView.ItemContainerStyle>
|
||||||
<Style TargetType="TreeViewItem" BasedOn="{StaticResource PropertyTreeStyle}">
|
<Style TargetType="TreeViewItem" BasedOn="{StaticResource PropertyTreeStyle}">
|
||||||
<Setter Property="IsExpanded" Value="{Binding IsExpanded, Mode=TwoWay}" />
|
<Setter Property="IsExpanded" Value="{Binding IsExpanded, Mode=TwoWay}" />
|
||||||
<Setter Property="Visibility" Value="{Binding IsVisible}" />
|
<Setter Property="Visibility" Value="{Binding IsVisible, Converter={x:Static s:BoolToVisibilityConverter.Instance}, Mode=OneWay}" />
|
||||||
</Style>
|
</Style>
|
||||||
</TreeView.ItemContainerStyle>
|
</TreeView.ItemContainerStyle>
|
||||||
<TreeView.Resources>
|
<TreeView.Resources>
|
||||||
|
|||||||
@ -7,8 +7,11 @@ namespace Artemis.UI.Screens.Module.ProfileEditor.LayerProperties.Tree
|
|||||||
{
|
{
|
||||||
public class TreeViewModel
|
public class TreeViewModel
|
||||||
{
|
{
|
||||||
public TreeViewModel(BindableCollection<LayerPropertyGroupViewModel> layerPropertyGroups)
|
private readonly LayerPropertiesViewModel _layerPropertiesViewModel;
|
||||||
|
|
||||||
|
public TreeViewModel(LayerPropertiesViewModel layerPropertiesViewModel, BindableCollection<LayerPropertyGroupViewModel> layerPropertyGroups)
|
||||||
{
|
{
|
||||||
|
_layerPropertiesViewModel = layerPropertiesViewModel;
|
||||||
LayerPropertyGroups = layerPropertyGroups;
|
LayerPropertyGroups = layerPropertyGroups;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -7,7 +7,6 @@ using Artemis.UI.Events;
|
|||||||
using Artemis.UI.Screens.Module.ProfileEditor.LayerProperties;
|
using Artemis.UI.Screens.Module.ProfileEditor.LayerProperties;
|
||||||
using Artemis.UI.Screens.Module.ProfileEditor.LayerProperties.Abstract;
|
using Artemis.UI.Screens.Module.ProfileEditor.LayerProperties.Abstract;
|
||||||
using Artemis.UI.Screens.Module.ProfileEditor.LayerProperties.Tree;
|
using Artemis.UI.Screens.Module.ProfileEditor.LayerProperties.Tree;
|
||||||
using Artemis.UI.Screens.Module.ProfileEditor.LayerProperties.Tree.PropertyInput.Abstract;
|
|
||||||
|
|
||||||
namespace Artemis.UI.Services.Interfaces
|
namespace Artemis.UI.Services.Interfaces
|
||||||
{
|
{
|
||||||
@ -16,6 +15,7 @@ namespace Artemis.UI.Services.Interfaces
|
|||||||
Profile SelectedProfile { get; }
|
Profile SelectedProfile { get; }
|
||||||
ProfileElement SelectedProfileElement { get; }
|
ProfileElement SelectedProfileElement { get; }
|
||||||
TimeSpan CurrentTime { get; set; }
|
TimeSpan CurrentTime { get; set; }
|
||||||
|
int PixelsPerSecond { get; set; }
|
||||||
|
|
||||||
LayerPropertyBaseViewModel CreateLayerPropertyViewModel(BaseLayerProperty baseLayerProperty, PropertyDescriptionAttribute propertyDescription);
|
LayerPropertyBaseViewModel CreateLayerPropertyViewModel(BaseLayerProperty baseLayerProperty, PropertyDescriptionAttribute propertyDescription);
|
||||||
void ChangeSelectedProfile(Profile profile);
|
void ChangeSelectedProfile(Profile profile);
|
||||||
@ -53,6 +53,11 @@ namespace Artemis.UI.Services.Interfaces
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
event EventHandler CurrentTimeChanged;
|
event EventHandler CurrentTimeChanged;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Occurs when the pixels per second (zoom level) is changed
|
||||||
|
/// </summary>
|
||||||
|
event EventHandler PixelsPerSecondChanged;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Occurs when the profile preview has been updated
|
/// Occurs when the profile preview has been updated
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|||||||
@ -29,6 +29,7 @@ namespace Artemis.UI.Services
|
|||||||
private readonly IKernel _kernel;
|
private readonly IKernel _kernel;
|
||||||
private TimeSpan _currentTime;
|
private TimeSpan _currentTime;
|
||||||
private TimeSpan _lastUpdateTime;
|
private TimeSpan _lastUpdateTime;
|
||||||
|
private int _pixelsPerSecond;
|
||||||
|
|
||||||
public ProfileEditorService(ICoreService coreService, IProfileService profileService, IKernel kernel)
|
public ProfileEditorService(ICoreService coreService, IProfileService profileService, IKernel kernel)
|
||||||
{
|
{
|
||||||
@ -46,6 +47,7 @@ namespace Artemis.UI.Services
|
|||||||
{typeof(SKPoint), typeof(SKPointPropertyInputViewModel)},
|
{typeof(SKPoint), typeof(SKPointPropertyInputViewModel)},
|
||||||
{typeof(SKSize), typeof(SKSizePropertyInputViewModel)}
|
{typeof(SKSize), typeof(SKSizePropertyInputViewModel)}
|
||||||
};
|
};
|
||||||
|
PixelsPerSecond = 31;
|
||||||
}
|
}
|
||||||
|
|
||||||
public Dictionary<Type, Type> RegisteredPropertyEditors { get; set; }
|
public Dictionary<Type, Type> RegisteredPropertyEditors { get; set; }
|
||||||
@ -66,6 +68,17 @@ namespace Artemis.UI.Services
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public int PixelsPerSecond
|
||||||
|
{
|
||||||
|
get => _pixelsPerSecond;
|
||||||
|
set
|
||||||
|
{
|
||||||
|
_pixelsPerSecond = value;
|
||||||
|
OnPixelsPerSecondChanged();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
public LayerPropertyBaseViewModel CreateLayerPropertyViewModel(BaseLayerProperty baseLayerProperty, PropertyDescriptionAttribute propertyDescription)
|
public LayerPropertyBaseViewModel CreateLayerPropertyViewModel(BaseLayerProperty baseLayerProperty, PropertyDescriptionAttribute propertyDescription)
|
||||||
{
|
{
|
||||||
// Go through the pain of instantiating a generic type VM now via reflection to make things a lot simpler down the line
|
// Go through the pain of instantiating a generic type VM now via reflection to make things a lot simpler down the line
|
||||||
@ -93,7 +106,7 @@ namespace Artemis.UI.Services
|
|||||||
{
|
{
|
||||||
new ConstructorArgument("layerPropertyViewModel", layerPropertyViewModel)
|
new ConstructorArgument("layerPropertyViewModel", layerPropertyViewModel)
|
||||||
};
|
};
|
||||||
return new TreePropertyViewModel<T>(layerPropertyViewModel, (PropertyInputViewModel<T>) _kernel.Get(type, parameters));
|
return new TreePropertyViewModel<T>(layerPropertyViewModel, (PropertyInputViewModel<T>) _kernel.Get(type, parameters), this);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void ChangeSelectedProfile(Profile profile)
|
public void ChangeSelectedProfile(Profile profile)
|
||||||
@ -181,8 +194,9 @@ namespace Artemis.UI.Services
|
|||||||
public event EventHandler<ProfileElementEventArgs> ProfileElementSelected;
|
public event EventHandler<ProfileElementEventArgs> ProfileElementSelected;
|
||||||
public event EventHandler<ProfileElementEventArgs> SelectedProfileElementUpdated;
|
public event EventHandler<ProfileElementEventArgs> SelectedProfileElementUpdated;
|
||||||
public event EventHandler CurrentTimeChanged;
|
public event EventHandler CurrentTimeChanged;
|
||||||
|
public event EventHandler PixelsPerSecondChanged;
|
||||||
public event EventHandler ProfilePreviewUpdated;
|
public event EventHandler ProfilePreviewUpdated;
|
||||||
|
|
||||||
public void StopRegularRender()
|
public void StopRegularRender()
|
||||||
{
|
{
|
||||||
_coreService.ModuleUpdatingDisabled = true;
|
_coreService.ModuleUpdatingDisabled = true;
|
||||||
@ -218,6 +232,11 @@ namespace Artemis.UI.Services
|
|||||||
CurrentTimeChanged?.Invoke(this, EventArgs.Empty);
|
CurrentTimeChanged?.Invoke(this, EventArgs.Empty);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected virtual void OnPixelsPerSecondChanged()
|
||||||
|
{
|
||||||
|
PixelsPerSecondChanged?.Invoke(this, EventArgs.Empty);
|
||||||
|
}
|
||||||
|
|
||||||
protected virtual void OnProfilePreviewUpdated()
|
protected virtual void OnProfilePreviewUpdated()
|
||||||
{
|
{
|
||||||
ProfilePreviewUpdated?.Invoke(this, EventArgs.Empty);
|
ProfilePreviewUpdated?.Invoke(this, EventArgs.Empty);
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user