mirror of
https://github.com/Artemis-RGB/Artemis
synced 2026-11-12 03:48:56 +00:00
Profile tree - Implemented proper copy/pasting
This commit is contained in:
1 parent
f694f39219
commit
aae4e71d8e
21 files changed
+524
-294
No files matched your search
@@ -92,46 +92,6 @@ namespace Artemis.UI.Shared.Services
|
||||
/// <returns>The current module the profile editor is initialized for</returns>
|
||||
ProfileModule? GetCurrentModule();
|
||||
|
||||
/// <summary>
|
||||
/// Occurs when a new profile is selected
|
||||
/// </summary>
|
||||
event EventHandler<ProfileEventArgs> ProfileSelected;
|
||||
|
||||
/// <summary>
|
||||
/// Occurs then the currently selected profile is updated
|
||||
/// </summary>
|
||||
event EventHandler<ProfileEventArgs> SelectedProfileUpdated;
|
||||
|
||||
/// <summary>
|
||||
/// Occurs when a new profile element is selected
|
||||
/// </summary>
|
||||
event EventHandler<RenderProfileElementEventArgs> ProfileElementSelected;
|
||||
|
||||
/// <summary>
|
||||
/// Occurs when the currently selected profile element is updated
|
||||
/// </summary>
|
||||
event EventHandler<RenderProfileElementEventArgs> SelectedProfileElementUpdated;
|
||||
|
||||
/// <summary>
|
||||
/// Occurs when the currently selected data binding layer property is changed
|
||||
/// </summary>
|
||||
event EventHandler SelectedDataBindingChanged;
|
||||
|
||||
/// <summary>
|
||||
/// Occurs when the current editor time is changed
|
||||
/// </summary>
|
||||
event EventHandler CurrentTimeChanged;
|
||||
|
||||
/// <summary>
|
||||
/// Occurs when the pixels per second (zoom level) is changed
|
||||
/// </summary>
|
||||
event EventHandler PixelsPerSecondChanged;
|
||||
|
||||
/// <summary>
|
||||
/// Occurs when the profile preview has been updated
|
||||
/// </summary>
|
||||
event EventHandler ProfilePreviewUpdated;
|
||||
|
||||
/// <summary>
|
||||
/// Registers a new property input view model used in the profile editor for the generic type defined in
|
||||
/// <see cref="PropertyInputViewModel{T}" />
|
||||
@@ -174,5 +134,66 @@ namespace Artemis.UI.Shared.Services
|
||||
/// <typeparamref name="T" />
|
||||
/// </summary>
|
||||
PropertyInputViewModel<T>? CreatePropertyInputViewModel<T>(LayerProperty<T> layerProperty);
|
||||
|
||||
/// <summary>
|
||||
/// Duplicates the provided profile element placing it in the same folder and one position higher
|
||||
/// </summary>
|
||||
/// <param name="profileElement">The profile element to duplicate</param>
|
||||
/// <returns>The duplicated profile element</returns>
|
||||
ProfileElement? DuplicateProfileElement(ProfileElement profileElement);
|
||||
|
||||
/// <summary>
|
||||
/// Copies the provided profile element onto the clipboard
|
||||
/// </summary>
|
||||
/// <param name="profileElement">The profile element to copy</param>
|
||||
void CopyProfileElement(ProfileElement profileElement);
|
||||
|
||||
/// <summary>
|
||||
/// Pastes a render profile element from the clipboard into the target folder
|
||||
/// </summary>
|
||||
/// <param name="target">The folder to paste the render element in to</param>
|
||||
/// <param name="position">The position at which to paste the element</param>
|
||||
/// <returns>The pasted render element</returns>
|
||||
ProfileElement? PasteProfileElement(Folder target, int position);
|
||||
|
||||
/// <summary>
|
||||
/// Occurs when a new profile is selected
|
||||
/// </summary>
|
||||
event EventHandler<ProfileEventArgs> ProfileSelected;
|
||||
|
||||
/// <summary>
|
||||
/// Occurs then the currently selected profile is updated
|
||||
/// </summary>
|
||||
event EventHandler<ProfileEventArgs> SelectedProfileUpdated;
|
||||
|
||||
/// <summary>
|
||||
/// Occurs when a new profile element is selected
|
||||
/// </summary>
|
||||
event EventHandler<RenderProfileElementEventArgs> ProfileElementSelected;
|
||||
|
||||
/// <summary>
|
||||
/// Occurs when the currently selected profile element is updated
|
||||
/// </summary>
|
||||
event EventHandler<RenderProfileElementEventArgs> SelectedProfileElementUpdated;
|
||||
|
||||
/// <summary>
|
||||
/// Occurs when the currently selected data binding layer property is changed
|
||||
/// </summary>
|
||||
event EventHandler SelectedDataBindingChanged;
|
||||
|
||||
/// <summary>
|
||||
/// Occurs when the current editor time is changed
|
||||
/// </summary>
|
||||
event EventHandler CurrentTimeChanged;
|
||||
|
||||
/// <summary>
|
||||
/// Occurs when the pixels per second (zoom level) is changed
|
||||
/// </summary>
|
||||
event EventHandler PixelsPerSecondChanged;
|
||||
|
||||
/// <summary>
|
||||
/// Occurs when the profile preview has been updated
|
||||
/// </summary>
|
||||
event EventHandler ProfilePreviewUpdated;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,79 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using Artemis.Core;
|
||||
using Artemis.Storage.Entities.Profile;
|
||||
using Artemis.Storage.Entities.Profile.Abstract;
|
||||
|
||||
namespace Artemis.UI.Shared.Services.Models
|
||||
{
|
||||
internal class FolderClipboardModel
|
||||
{
|
||||
public FolderClipboardModel(Folder folder)
|
||||
{
|
||||
FolderEntity = folder.FolderEntity;
|
||||
Folders = new List<FolderEntity>();
|
||||
Layers = new List<LayerEntity>();
|
||||
foreach (Folder allFolder in folder.GetAllFolders())
|
||||
Folders.Add(allFolder.FolderEntity);
|
||||
foreach (Layer allLayer in folder.GetAllLayers())
|
||||
Layers.Add(allLayer.LayerEntity);
|
||||
}
|
||||
|
||||
// ReSharper disable once UnusedMember.Global - For JSON.NET
|
||||
public FolderClipboardModel()
|
||||
{
|
||||
FolderEntity = null;
|
||||
Folders = new List<FolderEntity>();
|
||||
Layers = new List<LayerEntity>();
|
||||
}
|
||||
|
||||
public FolderEntity? FolderEntity { get; set; }
|
||||
public List<FolderEntity> Folders { get; set; }
|
||||
public List<LayerEntity> Layers { get; set; }
|
||||
public bool HasBeenPasted { get; set; }
|
||||
|
||||
public Folder Paste(Profile profile, ProfileElement parent)
|
||||
{
|
||||
if (FolderEntity == null)
|
||||
throw new ArtemisSharedUIException("Couldn't paste folder because FolderEntity deserialized as null");
|
||||
if (HasBeenPasted)
|
||||
throw new ArtemisSharedUIException("Clipboard model can only be pasted once");
|
||||
|
||||
HasBeenPasted = true;
|
||||
|
||||
// Generate new GUIDs
|
||||
ReplaceGuid(FolderEntity);
|
||||
foreach (FolderEntity folderEntity in Folders)
|
||||
ReplaceGuid(folderEntity);
|
||||
foreach (LayerEntity layerEntity in Layers)
|
||||
ReplaceGuid(layerEntity);
|
||||
|
||||
// Inject the pasted elements into the profile
|
||||
profile.ProfileEntity.Folders.AddRange(Folders);
|
||||
profile.ProfileEntity.Layers.AddRange(Layers);
|
||||
|
||||
// Let the folder initialize and load as usual
|
||||
FolderEntity.Name += " - copy";
|
||||
Folder folder = new Folder(profile, parent, FolderEntity);
|
||||
return folder;
|
||||
}
|
||||
|
||||
private void ReplaceGuid(RenderElementEntity parent)
|
||||
{
|
||||
Guid old = parent.Id;
|
||||
parent.Id = Guid.NewGuid();
|
||||
|
||||
foreach (FolderEntity child in Folders)
|
||||
{
|
||||
if (child.ParentId == old)
|
||||
child.ParentId = parent.Id;
|
||||
}
|
||||
|
||||
foreach (LayerEntity child in Layers)
|
||||
{
|
||||
if (child.ParentId == old)
|
||||
child.ParentId = parent.Id;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -5,6 +5,9 @@ using System.Linq;
|
||||
using Artemis.Core;
|
||||
using Artemis.Core.Modules;
|
||||
using Artemis.Core.Services;
|
||||
using Artemis.Storage.Entities.Profile;
|
||||
using Artemis.UI.Shared.Services.Models;
|
||||
using Newtonsoft.Json;
|
||||
using Ninject;
|
||||
using Ninject.Parameters;
|
||||
using Serilog;
|
||||
@@ -14,23 +17,25 @@ namespace Artemis.UI.Shared.Services
|
||||
{
|
||||
internal class ProfileEditorService : IProfileEditorService
|
||||
{
|
||||
private readonly ILogger _logger;
|
||||
private readonly ICoreService _coreService;
|
||||
private readonly ISurfaceService _surfaceService;
|
||||
private readonly IKernel _kernel;
|
||||
private readonly ILogger _logger;
|
||||
private readonly IProfileService _profileService;
|
||||
private readonly List<PropertyInputRegistration> _registeredPropertyEditors;
|
||||
private readonly object _selectedProfileElementLock = new object();
|
||||
private readonly object _selectedProfileLock = new object();
|
||||
private TimeSpan _currentTime;
|
||||
private int _pixelsPerSecond;
|
||||
private readonly IKernel _kernel;
|
||||
|
||||
public ProfileEditorService(IProfileService profileService, IKernel kernel, ILogger logger, ICoreService coreService)
|
||||
public ProfileEditorService(IKernel kernel, ILogger logger, IProfileService profileService, ICoreService coreService, ISurfaceService surfaceService)
|
||||
{
|
||||
_profileService = profileService;
|
||||
_logger = logger;
|
||||
_coreService = coreService;
|
||||
_registeredPropertyEditors = new List<PropertyInputRegistration>();
|
||||
_kernel = kernel;
|
||||
_logger = logger;
|
||||
_profileService = profileService;
|
||||
_coreService = coreService;
|
||||
_surfaceService = surfaceService;
|
||||
_registeredPropertyEditors = new List<PropertyInputRegistration>();
|
||||
|
||||
PixelsPerSecond = 100;
|
||||
}
|
||||
@@ -41,6 +46,30 @@ namespace Artemis.UI.Shared.Services
|
||||
Execute.PostToUIThread(OnProfilePreviewUpdated);
|
||||
}
|
||||
|
||||
private void ReloadProfile()
|
||||
{
|
||||
if (SelectedProfile == null)
|
||||
return;
|
||||
|
||||
// Trigger a profile change
|
||||
OnSelectedProfileChanged(new ProfileEventArgs(SelectedProfile, SelectedProfile));
|
||||
// Trigger a selected element change
|
||||
RenderProfileElement? previousSelectedProfileElement = SelectedProfileElement;
|
||||
if (SelectedProfileElement is Folder folder)
|
||||
SelectedProfileElement = SelectedProfile.GetAllFolders().FirstOrDefault(f => f.EntityId == folder.EntityId);
|
||||
else if (SelectedProfileElement is Layer layer)
|
||||
SelectedProfileElement = SelectedProfile.GetAllLayers().FirstOrDefault(l => l.EntityId == layer.EntityId);
|
||||
OnSelectedProfileElementChanged(new RenderProfileElementEventArgs(SelectedProfileElement, previousSelectedProfileElement));
|
||||
// Trigger selected data binding change
|
||||
if (SelectedDataBinding != null)
|
||||
{
|
||||
SelectedDataBinding = SelectedProfileElement?.GetAllLayerProperties().FirstOrDefault(p => p.Path == SelectedDataBinding.Path);
|
||||
OnSelectedDataBindingChanged();
|
||||
}
|
||||
|
||||
UpdateProfilePreview();
|
||||
}
|
||||
|
||||
public ReadOnlyCollection<PropertyInputRegistration> RegisteredPropertyEditors => _registeredPropertyEditors.AsReadOnly();
|
||||
public Profile? SelectedProfile { get; private set; }
|
||||
public RenderProfileElement? SelectedProfileElement { get; private set; }
|
||||
@@ -107,7 +136,7 @@ namespace Artemis.UI.Shared.Services
|
||||
return;
|
||||
|
||||
_profileService.UpdateProfile(SelectedProfile, true);
|
||||
OnSelectedProfileChanged(new ProfileEventArgs(SelectedProfile));
|
||||
OnSelectedProfileUpdated(new ProfileEventArgs(SelectedProfile));
|
||||
UpdateProfilePreview();
|
||||
}
|
||||
}
|
||||
@@ -206,7 +235,7 @@ namespace Artemis.UI.Shared.Services
|
||||
if (supportedType.IsGenericParameter)
|
||||
{
|
||||
if (supportedType.BaseType == null)
|
||||
throw new ArtemisSharedUIException($"Generic property input VM type must have a type constraint");
|
||||
throw new ArtemisSharedUIException("Generic property input VM type must have a type constraint");
|
||||
supportedType = supportedType.BaseType;
|
||||
}
|
||||
|
||||
@@ -258,11 +287,9 @@ namespace Artemis.UI.Shared.Services
|
||||
}
|
||||
|
||||
if (snapToCurrentTime)
|
||||
{
|
||||
// Snap to the current time
|
||||
if (Math.Abs(time.TotalMilliseconds - CurrentTime.TotalMilliseconds) < tolerance.TotalMilliseconds)
|
||||
return CurrentTime;
|
||||
}
|
||||
|
||||
if (snapTimes != null)
|
||||
{
|
||||
@@ -289,9 +316,13 @@ namespace Artemis.UI.Shared.Services
|
||||
viewModelType = registration.ViewModelType.MakeGenericType(layerProperty.GetType().GenericTypeArguments);
|
||||
}
|
||||
else if (registration != null)
|
||||
{
|
||||
viewModelType = registration.ViewModelType;
|
||||
}
|
||||
else
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
if (viewModelType == null)
|
||||
return null;
|
||||
@@ -308,30 +339,80 @@ namespace Artemis.UI.Shared.Services
|
||||
return SelectedProfile?.Module;
|
||||
}
|
||||
|
||||
private void ReloadProfile()
|
||||
{
|
||||
if (SelectedProfile == null)
|
||||
return;
|
||||
#region Copy/paste
|
||||
|
||||
// Trigger a profile change
|
||||
OnSelectedProfileChanged(new ProfileEventArgs(SelectedProfile, SelectedProfile));
|
||||
// Trigger a selected element change
|
||||
RenderProfileElement? previousSelectedProfileElement = SelectedProfileElement;
|
||||
if (SelectedProfileElement is Folder folder)
|
||||
SelectedProfileElement = SelectedProfile.GetAllFolders().FirstOrDefault(f => f.EntityId == folder.EntityId);
|
||||
else if (SelectedProfileElement is Layer layer)
|
||||
SelectedProfileElement = SelectedProfile.GetAllLayers().FirstOrDefault(l => l.EntityId == layer.EntityId);
|
||||
OnSelectedProfileElementChanged(new RenderProfileElementEventArgs(SelectedProfileElement, previousSelectedProfileElement));
|
||||
// Trigger selected data binding change
|
||||
if (SelectedDataBinding != null)
|
||||
public ProfileElement? DuplicateProfileElement(ProfileElement profileElement)
|
||||
{
|
||||
if (!(profileElement.Parent is Folder parent))
|
||||
return null;
|
||||
|
||||
object? clipboardModel = null;
|
||||
switch (profileElement)
|
||||
{
|
||||
SelectedDataBinding = SelectedProfileElement?.GetAllLayerProperties().FirstOrDefault(p => p.Path == SelectedDataBinding.Path);
|
||||
OnSelectedDataBindingChanged();
|
||||
case Folder folder:
|
||||
{
|
||||
clipboardModel = CoreJson.DeserializeObject(CoreJson.SerializeObject(new FolderClipboardModel(folder), true), true);
|
||||
break;
|
||||
}
|
||||
case Layer layer:
|
||||
clipboardModel = CoreJson.DeserializeObject(CoreJson.SerializeObject(layer.LayerEntity, true), true);
|
||||
break;
|
||||
}
|
||||
|
||||
UpdateProfilePreview();
|
||||
return clipboardModel == null ? null : PasteClipboardData(clipboardModel, parent, profileElement.Order);
|
||||
}
|
||||
|
||||
public void CopyProfileElement(ProfileElement profileElement)
|
||||
{
|
||||
switch (profileElement)
|
||||
{
|
||||
case Folder folder:
|
||||
{
|
||||
FolderClipboardModel clipboardModel = new FolderClipboardModel(folder);
|
||||
JsonClipboard.SetObject(clipboardModel);
|
||||
break;
|
||||
}
|
||||
case Layer layer:
|
||||
JsonClipboard.SetObject(layer.LayerEntity);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
public ProfileElement? PasteProfileElement(Folder target, int position)
|
||||
{
|
||||
object? clipboardObject = JsonClipboard.GetData();
|
||||
return clipboardObject != null ? PasteClipboardData(clipboardObject, target, position) : null;
|
||||
}
|
||||
|
||||
private RenderProfileElement? PasteClipboardData(object clipboardObject, Folder target, int position)
|
||||
{
|
||||
RenderProfileElement? pasted = null;
|
||||
switch (clipboardObject)
|
||||
{
|
||||
case FolderClipboardModel folderClipboardModel:
|
||||
pasted = folderClipboardModel.Paste(target.Profile, target);
|
||||
target.AddChild(pasted, position);
|
||||
break;
|
||||
case LayerEntity layerEntity:
|
||||
layerEntity.Id = Guid.NewGuid();
|
||||
layerEntity.Name += " - copy";
|
||||
pasted = new Layer(target.Profile, target, layerEntity);
|
||||
target.AddChild(pasted, position);
|
||||
break;
|
||||
}
|
||||
|
||||
if (pasted != null)
|
||||
{
|
||||
target.Profile.PopulateLeds(_surfaceService.ActiveSurface);
|
||||
UpdateSelectedProfile();
|
||||
ChangeSelectedProfileElement(pasted);
|
||||
}
|
||||
|
||||
return pasted;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Events
|
||||
|
||||
public event EventHandler<ProfileEventArgs>? ProfileSelected;
|
||||
@@ -396,6 +477,5 @@ namespace Artemis.UI.Shared.Services
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,58 @@
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Windows;
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace Artemis.UI.Shared
|
||||
{
|
||||
/// <summary>
|
||||
/// Provides access to the clipboard via JSON-serialized objects
|
||||
/// </summary>
|
||||
public static class JsonClipboard
|
||||
{
|
||||
private static readonly JsonSerializerSettings JsonSettings = new JsonSerializerSettings {TypeNameHandling = TypeNameHandling.All};
|
||||
|
||||
/// <summary>
|
||||
/// Sets the provided object on the clipboard
|
||||
/// </summary>
|
||||
/// <param name="clipboardObject">The object to set on the clipboard</param>
|
||||
public static void SetObject(object clipboardObject)
|
||||
{
|
||||
string json = JsonConvert.SerializeObject(clipboardObject, JsonSettings);
|
||||
Clipboard.SetData("Artemis", json);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// If set, gets the current object off of the clipboard
|
||||
/// </summary>
|
||||
/// <returns>The object that is on the clipboard, if none is set <see langword="null" />.</returns>
|
||||
public static object? GetData()
|
||||
{
|
||||
string? json = Clipboard.GetData("Artemis")?.ToString();
|
||||
return json != null ? JsonConvert.DeserializeObject(json, JsonSettings) : null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// If set, gets the current object of type <typeparamref name="T" /> off of the clipboard
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The type of object to get</typeparam>
|
||||
/// <returns>
|
||||
/// The object that is on the clipboard. If none is set or not of type <typeparamref name="T" />,
|
||||
/// <see langword="null" />.
|
||||
/// </returns>
|
||||
[return: MaybeNull]
|
||||
public static T GetData<T>()
|
||||
{
|
||||
object? data = GetData();
|
||||
return data is T castData ? castData : default;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Determines whether the clipboard currently contains Artemis data
|
||||
/// </summary>
|
||||
/// <returns><see langword="true" /> if the clipboard contains Artemis data, otherwise <see langword="false" /></returns>
|
||||
public static bool ContainsArtemisData()
|
||||
{
|
||||
return Clipboard.ContainsData("Artemis");
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user