diff --git a/src/.idea/.idea.Artemis/.idea/avalonia.xml b/src/.idea/.idea.Artemis/.idea/avalonia.xml
index 8ad483249..03fd0fca9 100644
--- a/src/.idea/.idea.Artemis/.idea/avalonia.xml
+++ b/src/.idea/.idea.Artemis/.idea/avalonia.xml
@@ -16,6 +16,7 @@
+
@@ -55,6 +56,8 @@
+
+
diff --git a/src/Artemis.Core/Models/Profile/LayerProperties/ILayerProperty.cs b/src/Artemis.Core/Models/Profile/LayerProperties/ILayerProperty.cs
index 009852cec..2f36515f8 100644
--- a/src/Artemis.Core/Models/Profile/LayerProperties/ILayerProperty.cs
+++ b/src/Artemis.Core/Models/Profile/LayerProperties/ILayerProperty.cs
@@ -1,5 +1,4 @@
using System;
-using System.Collections.Generic;
using System.Collections.ObjectModel;
using Artemis.Storage.Entities.Profile;
@@ -79,11 +78,11 @@ namespace Artemis.Core
void Initialize(RenderProfileElement profileElement, LayerPropertyGroup group, PropertyEntity entity, bool fromStorage, PropertyDescriptionAttribute description);
///
- /// Attempts to load and add the provided keyframe entity to the layer property
+ /// Attempts to create a keyframe for this property from the provided entity
///
- /// The entity representing the keyframe to add
+ /// The entity representing the keyframe to create
/// If succeeded the resulting keyframe, otherwise
- ILayerPropertyKeyframe? AddKeyframeEntity(KeyframeEntity keyframeEntity);
+ ILayerPropertyKeyframe? CreateKeyframeFromEntity(KeyframeEntity keyframeEntity);
///
/// Overrides the property value with the default value
diff --git a/src/Artemis.Core/Models/Profile/LayerProperties/ILayerPropertyKeyframe.cs b/src/Artemis.Core/Models/Profile/LayerProperties/ILayerPropertyKeyframe.cs
index be7528402..ab3446359 100644
--- a/src/Artemis.Core/Models/Profile/LayerProperties/ILayerPropertyKeyframe.cs
+++ b/src/Artemis.Core/Models/Profile/LayerProperties/ILayerPropertyKeyframe.cs
@@ -32,5 +32,12 @@ namespace Artemis.Core
/// Removes the keyframe from the layer property
///
void Remove();
+
+ ///
+ /// Creates a copy of this keyframe.
+ /// Note: The copied keyframe is not added to the layer property.
+ ///
+ /// The resulting copy
+ ILayerPropertyKeyframe CreateCopy();
}
}
\ No newline at end of file
diff --git a/src/Artemis.Core/Models/Profile/LayerProperties/LayerProperty.cs b/src/Artemis.Core/Models/Profile/LayerProperties/LayerProperty.cs
index 28e2d5db4..8c16c3b93 100644
--- a/src/Artemis.Core/Models/Profile/LayerProperties/LayerProperty.cs
+++ b/src/Artemis.Core/Models/Profile/LayerProperties/LayerProperty.cs
@@ -1,9 +1,7 @@
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
-using System.Diagnostics.CodeAnalysis;
using System.Linq;
-using System.Text;
using Artemis.Storage.Entities.Profile;
using Newtonsoft.Json;
@@ -326,19 +324,24 @@ namespace Artemis.Core
}
///
- public ILayerPropertyKeyframe? AddKeyframeEntity(KeyframeEntity keyframeEntity)
+ public ILayerPropertyKeyframe? CreateKeyframeFromEntity(KeyframeEntity keyframeEntity)
{
if (keyframeEntity.Position > ProfileElement.Timeline.Length)
return null;
- T? value = CoreJson.DeserializeObject(keyframeEntity.Value);
- if (value == null)
- return null;
- LayerPropertyKeyframe keyframe = new(
- CoreJson.DeserializeObject(keyframeEntity.Value)!, keyframeEntity.Position, (Easings.Functions) keyframeEntity.EasingFunction, this
- );
- AddKeyframe(keyframe);
- return keyframe;
+ try
+ {
+ T? value = CoreJson.DeserializeObject(keyframeEntity.Value);
+ if (value == null)
+ return null;
+
+ LayerPropertyKeyframe keyframe = new(value, keyframeEntity.Position, (Easings.Functions) keyframeEntity.EasingFunction, this);
+ return keyframe;
+ }
+ catch (JsonException)
+ {
+ return null;
+ }
}
///
@@ -514,7 +517,7 @@ namespace Artemis.Core
// Create the path to this property by walking up the tree
Path = LayerPropertyGroup.Path + "." + description.Identifier;
-
+
OnInitialize();
}
@@ -547,7 +550,7 @@ namespace Artemis.Core
try
{
foreach (KeyframeEntity keyframeEntity in Entity.KeyframeEntities.Where(k => k.Position <= ProfileElement.Timeline.Length))
- AddKeyframeEntity(keyframeEntity);
+ CreateKeyframeFromEntity(keyframeEntity);
}
catch (JsonException)
{
diff --git a/src/Artemis.Core/Models/Profile/LayerProperties/LayerPropertyKeyFrame.cs b/src/Artemis.Core/Models/Profile/LayerProperties/LayerPropertyKeyFrame.cs
index 1f26a1876..4a889f613 100644
--- a/src/Artemis.Core/Models/Profile/LayerProperties/LayerPropertyKeyFrame.cs
+++ b/src/Artemis.Core/Models/Profile/LayerProperties/LayerPropertyKeyFrame.cs
@@ -80,5 +80,11 @@ namespace Artemis.Core
{
LayerProperty.RemoveKeyframe(this);
}
+
+ ///
+ public ILayerPropertyKeyframe CreateCopy()
+ {
+ return new LayerPropertyKeyframe(Value, Position, EasingFunction, LayerProperty);
+ }
}
}
\ No newline at end of file
diff --git a/src/Artemis.Core/Models/ProfileConfiguration/ProfileConfiguration.cs b/src/Artemis.Core/Models/ProfileConfiguration/ProfileConfiguration.cs
index d07241c1b..2ed2cc21e 100644
--- a/src/Artemis.Core/Models/ProfileConfiguration/ProfileConfiguration.cs
+++ b/src/Artemis.Core/Models/ProfileConfiguration/ProfileConfiguration.cs
@@ -241,7 +241,9 @@ namespace Artemis.Core
Icon.Load();
- ActivationCondition.LoadFromEntity(Entity.ActivationCondition);
+ if (Entity.ActivationCondition != null)
+ ActivationCondition.LoadFromEntity(Entity.ActivationCondition);
+
EnableHotkey = Entity.EnableHotkey != null ? new Hotkey(Entity.EnableHotkey) : null;
DisableHotkey = Entity.DisableHotkey != null ? new Hotkey(Entity.DisableHotkey) : null;
}
diff --git a/src/Artemis.UI.Shared/Extensions/ClipboardExtensions.cs b/src/Artemis.UI.Shared/Extensions/ClipboardExtensions.cs
new file mode 100644
index 000000000..b63cb4faf
--- /dev/null
+++ b/src/Artemis.UI.Shared/Extensions/ClipboardExtensions.cs
@@ -0,0 +1,29 @@
+using System.Text;
+using System.Threading.Tasks;
+using Artemis.Core;
+using Avalonia.Input.Platform;
+
+namespace Artemis.UI.Shared.Extensions;
+
+///
+/// Provides extension methods for Avalonia's type.
+///
+public static class ClipboardExtensions
+{
+ ///
+ /// Retrieves clipboard JSON data representing and deserializes it into an instance of
+ /// .
+ ///
+ /// The clipboard to retrieve the data off.
+ /// The data format to retrieve data for.
+ /// The type of data to retrieve
+ ///
+ /// The resulting value or if the clipboard did not contain data for the provided ;
+ /// .
+ ///
+ public static async Task GetJsonAsync(this IClipboard clipboard, string format)
+ {
+ byte[]? bytes = (byte[]?) await clipboard.GetDataAsync(format);
+ return bytes == null ? default : CoreJson.DeserializeObject(Encoding.Unicode.GetString(bytes), true);
+ }
+}
\ No newline at end of file
diff --git a/src/Artemis.UI.Shared/Services/ProfileEditor/Commands/DuplicateKeyframe.cs b/src/Artemis.UI.Shared/Services/ProfileEditor/Commands/DuplicateKeyframe.cs
new file mode 100644
index 000000000..a9c82af39
--- /dev/null
+++ b/src/Artemis.UI.Shared/Services/ProfileEditor/Commands/DuplicateKeyframe.cs
@@ -0,0 +1,55 @@
+using System;
+using Artemis.Core;
+
+namespace Artemis.UI.Shared.Services.ProfileEditor.Commands;
+
+///
+/// Represents a profile editor command that can be used to duplicate a keyframe at a new position.
+///
+public class DuplicateKeyframe : IProfileEditorCommand
+{
+ private readonly ILayerPropertyKeyframe _keyframe;
+ private readonly TimeSpan _position;
+
+ ///
+ /// Gets the duplicated keyframe, only available after the command has been executed.
+ ///
+ public ILayerPropertyKeyframe? Duplication { get; private set; }
+
+ ///
+ /// Creates a new instance of the class.
+ ///
+ /// The keyframe to duplicate.
+ /// The position of the duplicated keyframe.
+ public DuplicateKeyframe(ILayerPropertyKeyframe keyframe, TimeSpan position)
+ {
+ _keyframe = keyframe;
+ _position = position;
+ }
+
+ #region Implementation of IProfileEditorCommand
+
+ ///
+ public string DisplayName => "Duplicate keyframe";
+
+ ///
+ public void Execute()
+ {
+ if (Duplication == null)
+ {
+ Duplication = _keyframe.CreateCopy();
+ Duplication.Position = _position;
+ }
+
+ _keyframe.UntypedLayerProperty.AddUntypedKeyframe(Duplication);
+ }
+
+ ///
+ public void Undo()
+ {
+ if (Duplication != null)
+ _keyframe.UntypedLayerProperty.RemoveUntypedKeyframe(Duplication);
+ }
+
+ #endregion
+}
\ No newline at end of file
diff --git a/src/Artemis.UI.Shared/Services/ProfileEditor/IProfileEditorService.cs b/src/Artemis.UI.Shared/Services/ProfileEditor/IProfileEditorService.cs
index 7d1314755..de5c56670 100644
--- a/src/Artemis.UI.Shared/Services/ProfileEditor/IProfileEditorService.cs
+++ b/src/Artemis.UI.Shared/Services/ProfileEditor/IProfileEditorService.cs
@@ -1,8 +1,8 @@
using System;
using System.Collections.Generic;
+using System.Collections.ObjectModel;
using System.Threading.Tasks;
using Artemis.Core;
-using DynamicData;
namespace Artemis.UI.Shared.Services.ProfileEditor;
@@ -52,15 +52,14 @@ public interface IProfileEditorService : IArtemisSharedUIService
IObservable SuspendedEditing { get; }
///
- /// Gets a source list of all available editor tools.
+ /// Gets an observable read only collection of all available editor tools.
///
- SourceList Tools { get; }
+ ReadOnlyObservableCollection Tools { get; }
///
- /// Connect to the observable list of keyframes and observe any changes starting with the list's initial items.
+ /// Gets an observable read only collection of selected keyframes.
///
- /// An observable which emits the change set.
- IObservable> ConnectToKeyframes();
+ ReadOnlyObservableCollection SelectedKeyframes { get; }
///
/// Changes the selected profile by its .
@@ -188,4 +187,16 @@ public interface IProfileEditorService : IArtemisSharedUIService
/// Pauses profile preview playback.
///
void Pause();
+
+ ///
+ /// Adds a profile editor tool by it's view model.
+ ///
+ /// The view model of the tool to add.
+ void AddTool(IToolViewModel toolViewModel);
+
+ ///
+ /// Removes a profile editor tool by it's view model.
+ ///
+ /// The view model of the tool to remove.
+ void RemoveTool(IToolViewModel toolViewModel);
}
\ No newline at end of file
diff --git a/src/Artemis.UI.Shared/Services/ProfileEditor/IToolViewModel.cs b/src/Artemis.UI.Shared/Services/ProfileEditor/IToolViewModel.cs
index bb841721c..e34febdfe 100644
--- a/src/Artemis.UI.Shared/Services/ProfileEditor/IToolViewModel.cs
+++ b/src/Artemis.UI.Shared/Services/ProfileEditor/IToolViewModel.cs
@@ -1,7 +1,5 @@
using System;
-using System.Windows.Input;
using Material.Icons;
-using ReactiveUI;
namespace Artemis.UI.Shared.Services.ProfileEditor;
@@ -47,6 +45,7 @@ public interface IToolViewModel : IDisposable
public string ToolTip { get; }
}
+///
public abstract class ToolViewModel : ActivatableViewModelBase, IToolViewModel
{
private bool _isSelected;
diff --git a/src/Artemis.UI.Shared/Services/ProfileEditor/ProfileEditorService.cs b/src/Artemis.UI.Shared/Services/ProfileEditor/ProfileEditorService.cs
index 5ec54e4f3..368f3961c 100644
--- a/src/Artemis.UI.Shared/Services/ProfileEditor/ProfileEditorService.cs
+++ b/src/Artemis.UI.Shared/Services/ProfileEditor/ProfileEditorService.cs
@@ -1,5 +1,6 @@
using System;
using System.Collections.Generic;
+using System.Collections.ObjectModel;
using System.Linq;
using System.Reactive.Linq;
using System.Reactive.Subjects;
@@ -26,9 +27,10 @@ internal class ProfileEditorService : IProfileEditorService
private readonly Dictionary _profileEditorHistories = new();
private readonly BehaviorSubject _profileElementSubject = new(null);
private readonly IProfileService _profileService;
- private readonly SourceList _selectedKeyframes = new();
private readonly BehaviorSubject _suspendedEditingSubject = new(false);
private readonly BehaviorSubject _timeSubject = new(TimeSpan.Zero);
+ private readonly SourceList _tools;
+ private readonly SourceList _selectedKeyframes;
private readonly IWindowService _windowService;
private ProfileEditorCommandScope? _profileEditorHistoryScope;
@@ -46,6 +48,12 @@ internal class ProfileEditorService : IProfileEditorService
_layerBrushService = layerBrushService;
_windowService = windowService;
+ _tools = new SourceList();
+ _selectedKeyframes = new SourceList();
+ _tools.Connect().AutoRefreshOnObservable(t => t.WhenAnyValue(vm => vm.IsSelected)).Subscribe(OnToolSelected);
+ _tools.Connect().Bind(out ReadOnlyObservableCollection tools).Subscribe();
+ _selectedKeyframes.Connect().Bind(out ReadOnlyObservableCollection selectedKeyframes).Subscribe();
+
ProfileConfiguration = _profileConfigurationSubject.AsObservable();
ProfileElement = _profileElementSubject.AsObservable();
LayerProperty = _layerPropertySubject.AsObservable();
@@ -54,60 +62,8 @@ internal class ProfileEditorService : IProfileEditorService
Playing = _playingSubject.AsObservable();
SuspendedEditing = _suspendedEditingSubject.AsObservable();
PixelsPerSecond = _pixelsPerSecondSubject.AsObservable();
- Tools = new SourceList();
- Tools.Connect().AutoRefreshOnObservable(t => t.WhenAnyValue(vm => vm.IsSelected)).Subscribe(set =>
- {
- IToolViewModel? changed = set.FirstOrDefault()?.Item.Current;
- if (changed == null)
- return;
-
- // Disable all others if the changed one is selected and exclusive
- if (changed.IsSelected && changed.IsExclusive)
- Tools.Edit(list =>
- {
- foreach (IToolViewModel toolViewModel in list.Where(t => t.IsExclusive && t != changed))
- toolViewModel.IsSelected = false;
- });
- });
- }
-
- private ProfileEditorHistory? GetHistory(ProfileConfiguration? profileConfiguration)
- {
- if (profileConfiguration == null)
- return null;
- if (_profileEditorHistories.TryGetValue(profileConfiguration, out ProfileEditorHistory? history))
- return history;
-
- ProfileEditorHistory newHistory = new(profileConfiguration);
- _profileEditorHistories.Add(profileConfiguration, newHistory);
- return newHistory;
- }
-
- private void Tick(TimeSpan time)
- {
- if (_profileConfigurationSubject.Value?.Profile == null || _suspendedEditingSubject.Value)
- return;
-
- TickProfileElement(_profileConfigurationSubject.Value.Profile.GetRootFolder(), time);
- }
-
- private void TickProfileElement(ProfileElement profileElement, TimeSpan time)
- {
- if (profileElement is not RenderProfileElement renderElement)
- return;
-
- if (renderElement.Suspended)
- {
- renderElement.Disable();
- }
- else
- {
- renderElement.Enable();
- renderElement.OverrideTimelineAndApply(time);
-
- foreach (ProfileElement child in renderElement.Children)
- TickProfileElement(child, time);
- }
+ Tools = tools;
+ SelectedKeyframes = selectedKeyframes;
}
public IObservable ProfileConfiguration { get; }
@@ -118,12 +74,8 @@ internal class ProfileEditorService : IProfileEditorService
public IObservable Time { get; }
public IObservable Playing { get; }
public IObservable PixelsPerSecond { get; }
- public SourceList Tools { get; }
-
- public IObservable> ConnectToKeyframes()
- {
- return _selectedKeyframes.Connect();
- }
+ public ReadOnlyObservableCollection Tools { get; }
+ public ReadOnlyObservableCollection SelectedKeyframes { get; }
public void ChangeCurrentProfileConfiguration(ProfileConfiguration? profileConfiguration)
{
@@ -318,7 +270,7 @@ internal class ProfileEditorService : IProfileEditorService
return folder;
}
}
-
+
///
public Layer CreateAndAddLayer(ProfileElement target)
{
@@ -380,6 +332,18 @@ internal class ProfileEditorService : IProfileEditorService
_playingSubject.OnNext(false);
}
+ ///
+ public void AddTool(IToolViewModel toolViewModel)
+ {
+ _tools.Add(toolViewModel);
+ }
+
+ ///
+ public void RemoveTool(IToolViewModel toolViewModel)
+ {
+ _tools.Remove(toolViewModel);
+ }
+
#region Commands
public void ExecuteCommand(IProfileEditorCommand command)
@@ -433,4 +397,58 @@ internal class ProfileEditorService : IProfileEditorService
}
#endregion
+
+ private void OnToolSelected(IChangeSet changeSet)
+ {
+ IToolViewModel? changed = changeSet.FirstOrDefault()?.Item.Current;
+ if (changed == null)
+ return;
+
+ // Disable all others if the changed one is selected and exclusive
+ if (changed.IsSelected && changed.IsExclusive)
+ _tools.Edit(list =>
+ {
+ foreach (IToolViewModel toolViewModel in list.Where(t => t.IsExclusive && t != changed))
+ toolViewModel.IsSelected = false;
+ });
+ }
+
+ private ProfileEditorHistory? GetHistory(ProfileConfiguration? profileConfiguration)
+ {
+ if (profileConfiguration == null)
+ return null;
+ if (_profileEditorHistories.TryGetValue(profileConfiguration, out ProfileEditorHistory? history))
+ return history;
+
+ ProfileEditorHistory newHistory = new(profileConfiguration);
+ _profileEditorHistories.Add(profileConfiguration, newHistory);
+ return newHistory;
+ }
+
+ private void Tick(TimeSpan time)
+ {
+ if (_profileConfigurationSubject.Value?.Profile == null || _suspendedEditingSubject.Value)
+ return;
+
+ TickProfileElement(_profileConfigurationSubject.Value.Profile.GetRootFolder(), time);
+ }
+
+ private void TickProfileElement(ProfileElement profileElement, TimeSpan time)
+ {
+ if (profileElement is not RenderProfileElement renderElement)
+ return;
+
+ if (renderElement.Suspended)
+ {
+ renderElement.Disable();
+ }
+ else
+ {
+ renderElement.Enable();
+ renderElement.OverrideTimelineAndApply(time);
+
+ foreach (ProfileElement child in renderElement.Children)
+ TickProfileElement(child, time);
+ }
+ }
}
\ No newline at end of file
diff --git a/src/Artemis.UI.Windows/App.axaml.cs b/src/Artemis.UI.Windows/App.axaml.cs
index d527e4d2d..336bb3b37 100644
--- a/src/Artemis.UI.Windows/App.axaml.cs
+++ b/src/Artemis.UI.Windows/App.axaml.cs
@@ -1,7 +1,5 @@
using Artemis.Core.Services;
-using Artemis.UI.Shared.Providers;
using Artemis.UI.Windows.Ninject;
-using Artemis.UI.Windows.Providers;
using Artemis.UI.Windows.Providers.Input;
using Avalonia;
using Avalonia.Controls.ApplicationLifetimes;
@@ -17,6 +15,7 @@ namespace Artemis.UI.Windows
public override void Initialize()
{
_kernel = ArtemisBootstrapper.Bootstrap(this, new WindowsModule());
+ Program.CreateLogger(_kernel);
RxApp.MainThreadScheduler = AvaloniaScheduler.Instance;
AvaloniaXamlLoader.Load(this);
}
diff --git a/src/Artemis.UI.Windows/Program.cs b/src/Artemis.UI.Windows/Program.cs
index 7ca72259b..37d6e82f7 100644
--- a/src/Artemis.UI.Windows/Program.cs
+++ b/src/Artemis.UI.Windows/Program.cs
@@ -1,6 +1,8 @@
using System;
using Avalonia;
using Avalonia.ReactiveUI;
+using Ninject;
+using Serilog;
namespace Artemis.UI.Windows
{
@@ -12,7 +14,15 @@ namespace Artemis.UI.Windows
[STAThread]
public static void Main(string[] args)
{
- BuildAvaloniaApp().StartWithClassicDesktopLifetime(args);
+ try
+ {
+ BuildAvaloniaApp().StartWithClassicDesktopLifetime(args);
+ }
+ catch (Exception e)
+ {
+ Logger?.Fatal(e, "Fatal exception, shutting down");
+ throw;
+ }
}
// Avalonia configuration, don't remove; also used by visual designer.
@@ -20,5 +30,12 @@ namespace Artemis.UI.Windows
{
return AppBuilder.Configure().UsePlatformDetect().LogToTrace().UseReactiveUI();
}
+
+ private static ILogger? Logger { get; set; }
+
+ public static void CreateLogger(IKernel kernel)
+ {
+ Logger = kernel.Get().ForContext();
+ }
}
}
\ No newline at end of file
diff --git a/src/Artemis.UI/Models/KeyframeClipboardModel.cs b/src/Artemis.UI/Models/KeyframeClipboardModel.cs
new file mode 100644
index 000000000..e225fe225
--- /dev/null
+++ b/src/Artemis.UI/Models/KeyframeClipboardModel.cs
@@ -0,0 +1,25 @@
+using Artemis.Core;
+using Artemis.Storage.Entities.Profile;
+using Newtonsoft.Json;
+
+namespace Artemis.UI.Models;
+
+public class KeyframeClipboardModel
+{
+ public const string ClipboardDataFormat = "Artemis.Keyframes";
+
+ [JsonConstructor]
+ public KeyframeClipboardModel()
+ {
+ }
+
+ public KeyframeClipboardModel(ILayerPropertyKeyframe keyframe)
+ {
+ KeyframeEntity entity = keyframe.GetKeyframeEntity();
+ Path = keyframe.UntypedLayerProperty.Path;
+ Entity = entity;
+ }
+
+ public string Path { get; set; } = null!;
+ public KeyframeEntity Entity { get; set; } = null!;
+}
\ No newline at end of file
diff --git a/src/Artemis.UI/Screens/ProfileEditor/Panels/Properties/Timeline/Keyframes/ITimelineKeyframeViewModel.cs b/src/Artemis.UI/Screens/ProfileEditor/Panels/Properties/Timeline/Keyframes/ITimelineKeyframeViewModel.cs
index 91b4af8b5..6a5b6cca3 100644
--- a/src/Artemis.UI/Screens/ProfileEditor/Panels/Properties/Timeline/Keyframes/ITimelineKeyframeViewModel.cs
+++ b/src/Artemis.UI/Screens/ProfileEditor/Panels/Properties/Timeline/Keyframes/ITimelineKeyframeViewModel.cs
@@ -1,5 +1,7 @@
using System;
+using System.Reactive;
using Artemis.Core;
+using ReactiveUI;
namespace Artemis.UI.Screens.ProfileEditor.Properties.Timeline.Keyframes;
@@ -22,10 +24,11 @@ public interface ITimelineKeyframeViewModel
#region Context menu actions
void PopulateEasingViewModels();
- void Duplicate();
- void Copy();
- void Paste();
- void Delete();
+
+ ReactiveCommand Duplicate { get; }
+ ReactiveCommand Copy { get; }
+ ReactiveCommand Paste { get; }
+ ReactiveCommand Delete { get; }
#endregion
}
\ No newline at end of file
diff --git a/src/Artemis.UI/Screens/ProfileEditor/Panels/Properties/Timeline/Keyframes/TimelineKeyframeView.axaml b/src/Artemis.UI/Screens/ProfileEditor/Panels/Properties/Timeline/Keyframes/TimelineKeyframeView.axaml
index 49836e813..3e4d5856d 100644
--- a/src/Artemis.UI/Screens/ProfileEditor/Panels/Properties/Timeline/Keyframes/TimelineKeyframeView.axaml
+++ b/src/Artemis.UI/Screens/ProfileEditor/Panels/Properties/Timeline/Keyframes/TimelineKeyframeView.axaml
@@ -52,23 +52,23 @@
-