diff --git a/src/Artemis.Core/Models/Profile/LayerProperties/ILayerPropertyKeyframe.cs b/src/Artemis.Core/Models/Profile/LayerProperties/ILayerPropertyKeyframe.cs
new file mode 100644
index 000000000..bcf182c27
--- /dev/null
+++ b/src/Artemis.Core/Models/Profile/LayerProperties/ILayerPropertyKeyframe.cs
@@ -0,0 +1,36 @@
+using System;
+using Artemis.Storage.Entities.Profile;
+
+namespace Artemis.Core
+{
+ ///
+ /// Represents a keyframe on a containing a value and a timestamp
+ ///
+ public interface ILayerPropertyKeyframe
+ {
+ ///
+ /// Gets an untyped reference to the layer property of this keyframe
+ ///
+ ILayerProperty UntypedLayerProperty { get; }
+
+ ///
+ /// The position of this keyframe in the timeline
+ ///
+ TimeSpan Position { get; set; }
+
+ ///
+ /// The easing function applied on the value of the keyframe
+ ///
+ Easings.Functions EasingFunction { get; set; }
+
+ ///
+ /// Gets the entity this keyframe uses for persistent storage
+ ///
+ KeyframeEntity GetKeyframeEntity();
+
+ ///
+ /// Removes the keyframe from the layer property
+ ///
+ void Remove();
+ }
+}
\ 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 64a7e1f34..9d8330785 100644
--- a/src/Artemis.Core/Models/Profile/LayerProperties/LayerProperty.cs
+++ b/src/Artemis.Core/Models/Profile/LayerProperties/LayerProperty.cs
@@ -559,12 +559,7 @@ namespace Artemis.Core
Entity.Value = CoreJson.SerializeObject(BaseValue);
Entity.KeyframesEnabled = KeyframesEnabled;
Entity.KeyframeEntities.Clear();
- Entity.KeyframeEntities.AddRange(Keyframes.Select(k => new KeyframeEntity
- {
- Value = CoreJson.SerializeObject(k.Value),
- Position = k.Position,
- EasingFunction = (int) k.EasingFunction
- }));
+ Entity.KeyframeEntities.AddRange(Keyframes.Select(k => k.GetKeyframeEntity()));
Entity.DataBindingEntities.Clear();
foreach (IDataBinding dataBinding in _dataBindings)
diff --git a/src/Artemis.Core/Models/Profile/LayerProperties/LayerPropertyKeyFrame.cs b/src/Artemis.Core/Models/Profile/LayerProperties/LayerPropertyKeyFrame.cs
index e58dd5635..299c0638e 100644
--- a/src/Artemis.Core/Models/Profile/LayerProperties/LayerPropertyKeyFrame.cs
+++ b/src/Artemis.Core/Models/Profile/LayerProperties/LayerPropertyKeyFrame.cs
@@ -1,11 +1,12 @@
using System;
+using Artemis.Storage.Entities.Profile;
namespace Artemis.Core
{
///
/// Represents a keyframe on a containing a value and a timestamp
///
- public class LayerPropertyKeyframe : CorePropertyChanged
+ public class LayerPropertyKeyframe : CorePropertyChanged, ILayerPropertyKeyframe
{
private LayerProperty _layerProperty;
private TimeSpan _position;
@@ -45,10 +46,10 @@ namespace Artemis.Core
set => SetAndNotify(ref _value, value);
}
-
- ///
- /// The position of this keyframe in the timeline
- ///
+ ///
+ public ILayerProperty UntypedLayerProperty => LayerProperty;
+
+ ///
public TimeSpan Position
{
get => _position;
@@ -59,14 +60,21 @@ namespace Artemis.Core
}
}
- ///
- /// The easing function applied on the value of the keyframe
- ///
+ ///
public Easings.Functions EasingFunction { get; set; }
- ///
- /// Removes the keyframe from the layer property
- ///
+ ///
+ public KeyframeEntity GetKeyframeEntity()
+ {
+ return new KeyframeEntity
+ {
+ Value = CoreJson.SerializeObject(Value),
+ Position = Position,
+ EasingFunction = (int) EasingFunction
+ };
+ }
+
+ ///
public void Remove()
{
LayerProperty.RemoveKeyframe(this);
diff --git a/src/Artemis.Core/Models/Profile/ProfileElement.cs b/src/Artemis.Core/Models/Profile/ProfileElement.cs
index 5b25c82c5..52c5b3f8e 100644
--- a/src/Artemis.Core/Models/Profile/ProfileElement.cs
+++ b/src/Artemis.Core/Models/Profile/ProfileElement.cs
@@ -123,7 +123,7 @@ namespace Artemis.Core
/// Adds a profile element to the collection, optionally at the given position (1-based)
///
/// The profile element to add
- /// The order where to place the child (1-based), defaults to the end of the collection
+ /// The order where to place the child (0-based), defaults to the end of the collection
public virtual void AddChild(ProfileElement child, int? order = null)
{
if (Disposed)
@@ -136,31 +136,19 @@ namespace Artemis.Core
// Add to the end of the list
if (order == null)
- {
ChildrenList.Add(child);
- child.Order = ChildrenList.Count;
- }
- // Shift everything after the given order
+ // Insert at the given index
else
{
if (order < 0)
order = 0;
- foreach (ProfileElement profileElement in ChildrenList.Where(c => c.Order >= order).ToList())
- profileElement.Order++;
-
- int targetIndex;
- if (order == 0)
- targetIndex = 0;
- else if (order > ChildrenList.Count)
- targetIndex = ChildrenList.Count;
- else
- targetIndex = ChildrenList.FindIndex(c => c.Order == order + 1);
-
- ChildrenList.Insert(targetIndex, child);
- child.Order = order.Value;
+ if (order > ChildrenList.Count)
+ order = ChildrenList.Count;
+ ChildrenList.Insert(order.Value, child);
}
child.Parent = this;
+ StreamlineOrder();
}
OnChildAdded();
@@ -178,10 +166,7 @@ namespace Artemis.Core
lock (ChildrenList)
{
ChildrenList.Remove(child);
-
- // Shift everything after the given order
- foreach (ProfileElement profileElement in ChildrenList.Where(c => c.Order > child.Order).ToList())
- profileElement.Order--;
+ StreamlineOrder();
child.Parent = null;
}
@@ -189,6 +174,12 @@ namespace Artemis.Core
OnChildRemoved();
}
+ private void StreamlineOrder()
+ {
+ for (int index = 0; index < ChildrenList.Count; index++)
+ ChildrenList[index].Order = index;
+ }
+
///
/// Returns a flattened list of all child folders
///
diff --git a/src/Artemis.UI.Shared/Services/Interfaces/IProfileEditorService.cs b/src/Artemis.UI.Shared/Services/Interfaces/IProfileEditorService.cs
index e279f7df1..32b6a92e1 100644
--- a/src/Artemis.UI.Shared/Services/Interfaces/IProfileEditorService.cs
+++ b/src/Artemis.UI.Shared/Services/Interfaces/IProfileEditorService.cs
@@ -156,6 +156,11 @@ namespace Artemis.UI.Shared.Services
/// The pasted render element
ProfileElement? PasteProfileElement(Folder target, int position);
+ ///
+ /// Gets a boolean indicating whether a profile element is on the clipboard
+ ///
+ bool GetCanPasteProfileElement();
+
///
/// Occurs when a new profile is selected
///
diff --git a/src/Artemis.UI.Shared/Services/ProfileEditorService.cs b/src/Artemis.UI.Shared/Services/ProfileEditorService.cs
index 493a9e287..a3a32d01f 100644
--- a/src/Artemis.UI.Shared/Services/ProfileEditorService.cs
+++ b/src/Artemis.UI.Shared/Services/ProfileEditorService.cs
@@ -384,6 +384,12 @@ namespace Artemis.UI.Shared.Services
return clipboardObject != null ? PasteClipboardData(clipboardObject, target, position) : null;
}
+ public bool GetCanPasteProfileElement()
+ {
+ object? clipboardObject = JsonClipboard.GetData();
+ return clipboardObject is LayerEntity || clipboardObject is FolderClipboardModel;
+ }
+
private RenderProfileElement? PasteClipboardData(object clipboardObject, Folder target, int position)
{
RenderProfileElement? pasted = null;
diff --git a/src/Artemis.UI/Screens/ProfileEditor/LayerProperties/LayerPropertiesView.xaml b/src/Artemis.UI/Screens/ProfileEditor/LayerProperties/LayerPropertiesView.xaml
index e43278cf1..053ab91d9 100644
--- a/src/Artemis.UI/Screens/ProfileEditor/LayerProperties/LayerPropertiesView.xaml
+++ b/src/Artemis.UI/Screens/ProfileEditor/LayerProperties/LayerPropertiesView.xaml
@@ -160,7 +160,9 @@
VerticalScrollBarVisibility="Hidden"
ScrollChanged="TimelineScrollChanged">
-
+
diff --git a/src/Artemis.UI/Screens/ProfileEditor/LayerProperties/LayerPropertiesViewModel.cs b/src/Artemis.UI/Screens/ProfileEditor/LayerProperties/LayerPropertiesViewModel.cs
index a4df1b62b..5da0d6163 100644
--- a/src/Artemis.UI/Screens/ProfileEditor/LayerProperties/LayerPropertiesViewModel.cs
+++ b/src/Artemis.UI/Screens/ProfileEditor/LayerProperties/LayerPropertiesViewModel.cs
@@ -35,6 +35,7 @@ namespace Artemis.UI.Screens.ProfileEditor.LayerProperties
private int _rightSideIndex;
private RenderProfileElement _selectedProfileElement;
private DateTime _lastEffectsViewModelToggle;
+ private double _treeViewModelHeight;
public LayerPropertiesViewModel(IProfileEditorService profileEditorService,
ICoreService coreService,
@@ -157,6 +158,12 @@ namespace Artemis.UI.Screens.ProfileEditor.LayerProperties
public Layer SelectedLayer => SelectedProfileElement as Layer;
public Folder SelectedFolder => SelectedProfileElement as Folder;
+ public double TreeViewModelHeight
+ {
+ get => _treeViewModelHeight;
+ set => SetAndNotify(ref _treeViewModelHeight, value);
+ }
+
#region Segments
diff --git a/src/Artemis.UI/Screens/ProfileEditor/LayerProperties/Timeline/Models/KeyframeClipboardModel.cs b/src/Artemis.UI/Screens/ProfileEditor/LayerProperties/Timeline/Models/KeyframeClipboardModel.cs
new file mode 100644
index 000000000..03a078f0e
--- /dev/null
+++ b/src/Artemis.UI/Screens/ProfileEditor/LayerProperties/Timeline/Models/KeyframeClipboardModel.cs
@@ -0,0 +1,32 @@
+using System;
+using System.Collections.Generic;
+using Artemis.Core;
+using Artemis.Storage.Entities.Profile;
+using Artemis.UI.Exceptions;
+
+namespace Artemis.UI.Screens.ProfileEditor.LayerProperties.Timeline.Models
+{
+ public class KeyframeClipboardModel
+ {
+ public Dictionary KeyframeEntities { get; set; }
+ public KeyframeClipboardModel(List keyframes)
+ {
+ KeyframeEntities = new Dictionary();
+ foreach (ILayerPropertyKeyframe keyframe in keyframes)
+ {
+ KeyframeEntities.Add(keyframe.UntypedLayerProperty.Path, );
+ }
+ }
+
+ public void Paste(RenderProfileElement target, TimeSpan pastePosition)
+ {
+ if (target == null) throw new ArgumentNullException(nameof(target));
+ if (HasBeenPasted)
+ throw new ArtemisUIException("Clipboard model can only be pasted once");
+
+ HasBeenPasted = true;
+ }
+
+ public bool HasBeenPasted { get; set; }
+ }
+}
\ No newline at end of file
diff --git a/src/Artemis.UI/Screens/ProfileEditor/LayerProperties/Timeline/TimelineKeyframeViewModel.cs b/src/Artemis.UI/Screens/ProfileEditor/LayerProperties/Timeline/TimelineKeyframeViewModel.cs
index 6e06a734a..3bd23bd47 100644
--- a/src/Artemis.UI/Screens/ProfileEditor/LayerProperties/Timeline/TimelineKeyframeViewModel.cs
+++ b/src/Artemis.UI/Screens/ProfileEditor/LayerProperties/Timeline/TimelineKeyframeViewModel.cs
@@ -48,6 +48,7 @@ namespace Artemis.UI.Screens.ProfileEditor.LayerProperties.Timeline
}
public TimeSpan Position => LayerPropertyKeyframe.Position;
+ public ILayerPropertyKeyframe Keyframe => LayerPropertyKeyframe;
public void Dispose()
{
@@ -158,35 +159,12 @@ namespace Artemis.UI.Screens.ProfileEditor.LayerProperties.Timeline
#endregion
#region Context menu actions
-
- public void Copy()
- {
- LayerPropertyKeyframe newKeyframe = new LayerPropertyKeyframe(
- LayerPropertyKeyframe.Value,
- LayerPropertyKeyframe.Position,
- LayerPropertyKeyframe.EasingFunction,
- LayerPropertyKeyframe.LayerProperty
- );
- // If possible, shift the keyframe to the right by 11 pixels
- TimeSpan desiredPosition = newKeyframe.Position + TimeSpan.FromMilliseconds(1000f / _profileEditorService.PixelsPerSecond * 11);
- if (desiredPosition <= newKeyframe.LayerProperty.ProfileElement.Timeline.Length)
- newKeyframe.Position = desiredPosition;
- // Otherwise if possible shift it to the left by 11 pixels
- else
- {
- desiredPosition = newKeyframe.Position - TimeSpan.FromMilliseconds(1000f / _profileEditorService.PixelsPerSecond * 11);
- if (desiredPosition > TimeSpan.Zero)
- newKeyframe.Position = desiredPosition;
- }
-
- LayerPropertyKeyframe.LayerProperty.AddKeyframe(newKeyframe);
- _profileEditorService.UpdateSelectedProfileElement();
- }
-
- public void Delete()
+
+ public void Delete(bool save = true)
{
LayerPropertyKeyframe.LayerProperty.RemoveKeyframe(LayerPropertyKeyframe);
- _profileEditorService.UpdateSelectedProfileElement();
+ if (save)
+ _profileEditorService.UpdateSelectedProfileElement();
}
#endregion
@@ -196,6 +174,7 @@ namespace Artemis.UI.Screens.ProfileEditor.LayerProperties.Timeline
{
bool IsSelected { get; set; }
TimeSpan Position { get; }
+ ILayerPropertyKeyframe Keyframe { get; }
#region Movement
@@ -210,8 +189,7 @@ namespace Artemis.UI.Screens.ProfileEditor.LayerProperties.Timeline
void PopulateEasingViewModels();
void ClearEasingViewModels();
- void Copy();
- void Delete();
+ void Delete(bool save = true);
#endregion
}
diff --git a/src/Artemis.UI/Screens/ProfileEditor/LayerProperties/Timeline/TimelinePropertyView.xaml b/src/Artemis.UI/Screens/ProfileEditor/LayerProperties/Timeline/TimelinePropertyView.xaml
index 5fa7f7181..844e39add 100644
--- a/src/Artemis.UI/Screens/ProfileEditor/LayerProperties/Timeline/TimelinePropertyView.xaml
+++ b/src/Artemis.UI/Screens/ProfileEditor/LayerProperties/Timeline/TimelinePropertyView.xaml
@@ -36,8 +36,8 @@
MouseDown="{s:Action KeyframeMouseDown}"
MouseUp="{s:Action KeyframeMouseUp}"
MouseMove="{s:Action KeyframeMouseMove}"
- ContextMenuOpening="{s:Action ContextMenuOpening}"
- ContextMenuClosing="{s:Action ContextMenuClosing}">
+ ContextMenuOpening="{s:Action KeyframeContextMenuOpening}"
+ ContextMenuClosing="{s:Action KeyframeContextMenuClosing}">