diff --git a/src/Artemis.Core/Models/Profile/LayerProperties/ILayerPropertyKeyframe.cs b/src/Artemis.Core/Models/Profile/LayerProperties/ILayerPropertyKeyframe.cs
index bcf182c27..be7528402 100644
--- a/src/Artemis.Core/Models/Profile/LayerProperties/ILayerPropertyKeyframe.cs
+++ b/src/Artemis.Core/Models/Profile/LayerProperties/ILayerPropertyKeyframe.cs
@@ -14,12 +14,12 @@ namespace Artemis.Core
ILayerProperty UntypedLayerProperty { get; }
///
- /// The position of this keyframe in the timeline
+ /// Gets or sets the position of this keyframe in the timeline
///
TimeSpan Position { get; set; }
///
- /// The easing function applied on the value of the keyframe
+ /// Gets or sets the easing function applied on the value of the keyframe
///
Easings.Functions EasingFunction { get; set; }
diff --git a/src/Avalonia/Artemis.UI.Shared/Services/ProfileEditor/Commands/ChangeKeyframeEasing.cs b/src/Avalonia/Artemis.UI.Shared/Services/ProfileEditor/Commands/ChangeKeyframeEasing.cs
new file mode 100644
index 000000000..d7e2ca932
--- /dev/null
+++ b/src/Avalonia/Artemis.UI.Shared/Services/ProfileEditor/Commands/ChangeKeyframeEasing.cs
@@ -0,0 +1,43 @@
+using Artemis.Core;
+using Humanizer;
+
+namespace Artemis.UI.Shared.Services.ProfileEditor.Commands;
+
+///
+/// Represents a profile editor command that can be used to change the easing function of a keyframe.
+///
+public class ChangeKeyframeEasing : IProfileEditorCommand
+{
+ private readonly ILayerPropertyKeyframe _keyframe;
+ private readonly Easings.Functions _easingFunction;
+ private readonly Easings.Functions _originalEasingFunction;
+
+ ///
+ /// Creates a new instance of the class.
+ ///
+ public ChangeKeyframeEasing(ILayerPropertyKeyframe keyframe, Easings.Functions easingFunction)
+ {
+ _keyframe = keyframe;
+ _easingFunction = easingFunction;
+ _originalEasingFunction = keyframe.EasingFunction;
+ }
+
+ #region Implementation of IProfileEditorCommand
+
+ ///
+ public string DisplayName => "Change easing to " + _easingFunction.Humanize(LetterCasing.LowerCase);
+
+ ///
+ public void Execute()
+ {
+ _keyframe.EasingFunction = _easingFunction;
+ }
+
+ ///
+ public void Undo()
+ {
+ _keyframe.EasingFunction = _originalEasingFunction;
+ }
+
+ #endregion
+}
\ No newline at end of file
diff --git a/src/Avalonia/Artemis.UI.Shared/Services/ProfileEditor/Commands/MoveKeyframe.cs b/src/Avalonia/Artemis.UI.Shared/Services/ProfileEditor/Commands/MoveKeyframe.cs
new file mode 100644
index 000000000..790cf229d
--- /dev/null
+++ b/src/Avalonia/Artemis.UI.Shared/Services/ProfileEditor/Commands/MoveKeyframe.cs
@@ -0,0 +1,43 @@
+using System;
+using Artemis.Core;
+
+namespace Artemis.UI.Shared.Services.ProfileEditor.Commands;
+
+///
+/// Represents a profile editor command that can be used to change the position of a keyframe.
+///
+public class MoveKeyframe : IProfileEditorCommand
+{
+ private readonly ILayerPropertyKeyframe _keyframe;
+ private readonly TimeSpan _originalPosition;
+ private readonly TimeSpan _position;
+
+ ///
+ /// Creates a new instance of the class.
+ ///
+ public MoveKeyframe(ILayerPropertyKeyframe keyframe, TimeSpan position)
+ {
+ _keyframe = keyframe;
+ _position = position;
+ _originalPosition = keyframe.Position;
+ }
+
+ #region Implementation of IProfileEditorCommand
+
+ ///
+ public string DisplayName => "Move keyframe";
+
+ ///
+ public void Execute()
+ {
+ _keyframe.Position = _position;
+ }
+
+ ///
+ public void Undo()
+ {
+ _keyframe.Position = _originalPosition;
+ }
+
+ #endregion
+}
\ No newline at end of file
diff --git a/src/Avalonia/Artemis.UI.Shared/Services/ProfileEditor/CompositeProfileEditorCommand.cs b/src/Avalonia/Artemis.UI.Shared/Services/ProfileEditor/CompositeProfileEditorCommand.cs
new file mode 100644
index 000000000..e2a2eb4e4
--- /dev/null
+++ b/src/Avalonia/Artemis.UI.Shared/Services/ProfileEditor/CompositeProfileEditorCommand.cs
@@ -0,0 +1,56 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+
+namespace Artemis.UI.Shared.Services.ProfileEditor;
+
+///
+/// Represents a profile editor command that can be used to combine multiple commands into one.
+///
+public class CompositeProfileEditorCommand : IProfileEditorCommand, IDisposable
+{
+ private readonly List _commands;
+
+ ///
+ /// Creates a new instance of the class.
+ ///
+ /// The commands to execute.
+ /// The display name of the composite command.
+ public CompositeProfileEditorCommand(IEnumerable commands, string displayName)
+ {
+ if (commands == null)
+ throw new ArgumentNullException(nameof(commands));
+ _commands = commands.ToList();
+ DisplayName = displayName ?? throw new ArgumentNullException(nameof(displayName));
+ }
+
+ ///
+ public void Dispose()
+ {
+ foreach (IProfileEditorCommand profileEditorCommand in _commands)
+ if (profileEditorCommand is IDisposable disposable)
+ disposable.Dispose();
+ }
+
+ #region Implementation of IProfileEditorCommand
+
+ ///
+ public string DisplayName { get; }
+
+ ///
+ public void Execute()
+ {
+ foreach (IProfileEditorCommand profileEditorCommand in _commands)
+ profileEditorCommand.Execute();
+ }
+
+ ///
+ public void Undo()
+ {
+ // Undo in reverse by iterating from the back
+ for (int index = _commands.Count; index >= 0; index--)
+ _commands[index].Undo();
+ }
+
+ #endregion
+}
\ No newline at end of file
diff --git a/src/Avalonia/Artemis.UI/Screens/ProfileEditor/Panels/Properties/Timeline/ITimelineKeyframeViewModel.cs b/src/Avalonia/Artemis.UI/Screens/ProfileEditor/Panels/Properties/Timeline/ITimelineKeyframeViewModel.cs
index d91a2d9a5..e90f1a699 100644
--- a/src/Avalonia/Artemis.UI/Screens/ProfileEditor/Panels/Properties/Timeline/ITimelineKeyframeViewModel.cs
+++ b/src/Avalonia/Artemis.UI/Screens/ProfileEditor/Panels/Properties/Timeline/ITimelineKeyframeViewModel.cs
@@ -25,7 +25,6 @@ public interface ITimelineKeyframeViewModel
#region Context menu actions
void PopulateEasingViewModels();
- void ClearEasingViewModels();
void Delete(bool save = true);
#endregion
diff --git a/src/Avalonia/Artemis.UI/Screens/ProfileEditor/Panels/Properties/Timeline/TimelineEasingView.axaml b/src/Avalonia/Artemis.UI/Screens/ProfileEditor/Panels/Properties/Timeline/TimelineEasingView.axaml
new file mode 100644
index 000000000..a5672ee0e
--- /dev/null
+++ b/src/Avalonia/Artemis.UI/Screens/ProfileEditor/Panels/Properties/Timeline/TimelineEasingView.axaml
@@ -0,0 +1,17 @@
+
+
+
+
+
+
diff --git a/src/Avalonia/Artemis.UI/Screens/ProfileEditor/Panels/Properties/Timeline/TimelineEasingView.axaml.cs b/src/Avalonia/Artemis.UI/Screens/ProfileEditor/Panels/Properties/Timeline/TimelineEasingView.axaml.cs
new file mode 100644
index 000000000..ea41308db
--- /dev/null
+++ b/src/Avalonia/Artemis.UI/Screens/ProfileEditor/Panels/Properties/Timeline/TimelineEasingView.axaml.cs
@@ -0,0 +1,18 @@
+using Avalonia.Controls;
+using Avalonia.Markup.Xaml;
+
+namespace Artemis.UI.Screens.ProfileEditor.Properties.Timeline
+{
+ public partial class TimelineEasingView : UserControl
+ {
+ public TimelineEasingView()
+ {
+ InitializeComponent();
+ }
+
+ private void InitializeComponent()
+ {
+ AvaloniaXamlLoader.Load(this);
+ }
+ }
+}
diff --git a/src/Avalonia/Artemis.UI/Screens/ProfileEditor/Panels/Properties/Timeline/TimelineEasingViewModel.cs b/src/Avalonia/Artemis.UI/Screens/ProfileEditor/Panels/Properties/Timeline/TimelineEasingViewModel.cs
index d43aaf9c6..2a43c5b75 100644
--- a/src/Avalonia/Artemis.UI/Screens/ProfileEditor/Panels/Properties/Timeline/TimelineEasingViewModel.cs
+++ b/src/Avalonia/Artemis.UI/Screens/ProfileEditor/Panels/Properties/Timeline/TimelineEasingViewModel.cs
@@ -1,5 +1,4 @@
-using System;
-using System.Collections.Generic;
+using System.Collections.Generic;
using Artemis.Core;
using Artemis.UI.Shared;
using Avalonia;
@@ -9,11 +8,11 @@ namespace Artemis.UI.Screens.ProfileEditor.Properties.Timeline;
public class TimelineEasingViewModel : ViewModelBase
{
- private bool _isEasingModeSelected;
+ private readonly ILayerPropertyKeyframe _keyframe;
- public TimelineEasingViewModel(Easings.Functions easingFunction, bool isSelected)
+ public TimelineEasingViewModel(Easings.Functions easingFunction, ILayerPropertyKeyframe keyframe)
{
- _isEasingModeSelected = isSelected;
+ _keyframe = keyframe;
EasingFunction = easingFunction;
Description = easingFunction.Humanize();
@@ -30,21 +29,5 @@ public class TimelineEasingViewModel : ViewModelBase
public Easings.Functions EasingFunction { get; }
public List EasingPoints { get; }
public string Description { get; }
-
- public bool IsEasingModeSelected
- {
- get => _isEasingModeSelected;
- set
- {
- _isEasingModeSelected = value;
- if (value) OnEasingModeSelected();
- }
- }
-
- public event EventHandler EasingModeSelected;
-
- protected virtual void OnEasingModeSelected()
- {
- EasingModeSelected?.Invoke(this, EventArgs.Empty);
- }
+ public bool IsEasingModeSelected => _keyframe.EasingFunction == EasingFunction;
}
\ No newline at end of file
diff --git a/src/Avalonia/Artemis.UI/Screens/ProfileEditor/Panels/Properties/Timeline/TimelineKeyframeView.axaml b/src/Avalonia/Artemis.UI/Screens/ProfileEditor/Panels/Properties/Timeline/TimelineKeyframeView.axaml
index 6bcd67db5..a9f501b03 100644
--- a/src/Avalonia/Artemis.UI/Screens/ProfileEditor/Panels/Properties/Timeline/TimelineKeyframeView.axaml
+++ b/src/Avalonia/Artemis.UI/Screens/ProfileEditor/Panels/Properties/Timeline/TimelineKeyframeView.axaml
@@ -3,6 +3,7 @@
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:avalonia="clr-namespace:Material.Icons.Avalonia;assembly=Material.Icons.Avalonia"
+ xmlns:timeline="clr-namespace:Artemis.UI.Screens.ProfileEditor.Properties.Timeline"
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
x:Class="Artemis.UI.Screens.ProfileEditor.Properties.Timeline.TimelineKeyframeView"
ClipToBounds="False"
@@ -31,25 +32,24 @@
-
+