mirror of
https://github.com/Artemis-RGB/Artemis
synced 2025-12-13 05:48:35 +00:00
Profile tree - Implemented proper copy/pasting
This commit is contained in:
parent
f694f39219
commit
aae4e71d8e
@ -33,7 +33,13 @@ namespace Artemis.Core
|
|||||||
Parent.AddChild(this);
|
Parent.AddChild(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
internal Folder(Profile profile, ProfileElement parent, FolderEntity folderEntity) : base(parent.Profile)
|
/// <summary>
|
||||||
|
/// Creates a new instance of the <see cref="Folder" /> class based on the provided folder entity
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="profile">The profile the folder belongs to</param>
|
||||||
|
/// <param name="parent">The parent of the folder</param>
|
||||||
|
/// <param name="folderEntity">The entity of the folder</param>
|
||||||
|
public Folder(Profile profile, ProfileElement parent, FolderEntity folderEntity) : base(parent.Profile)
|
||||||
{
|
{
|
||||||
FolderEntity = folderEntity;
|
FolderEntity = folderEntity;
|
||||||
EntityId = folderEntity.Id;
|
EntityId = folderEntity.Id;
|
||||||
|
|||||||
@ -122,29 +122,6 @@ namespace Artemis.Core
|
|||||||
|
|
||||||
internal override RenderElementEntity RenderElementEntity => LayerEntity;
|
internal override RenderElementEntity RenderElementEntity => LayerEntity;
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Creates a deep copy of the layer
|
|
||||||
/// </summary>
|
|
||||||
/// <returns>The newly created copy</returns>
|
|
||||||
public Layer CreateCopy()
|
|
||||||
{
|
|
||||||
if (Parent == null)
|
|
||||||
throw new ArtemisCoreException("Cannot create a copy of a layer without a parent");
|
|
||||||
|
|
||||||
LayerEntity entityCopy = CoreJson.DeserializeObject<LayerEntity>(CoreJson.SerializeObject(LayerEntity, true), true)!;
|
|
||||||
entityCopy.Id = Guid.NewGuid();
|
|
||||||
entityCopy.Name += " - Copy";
|
|
||||||
|
|
||||||
Layer copy = new Layer(Profile, Parent, entityCopy);
|
|
||||||
if (LayerBrush?.Descriptor != null)
|
|
||||||
copy.ChangeLayerBrush(LayerBrush.Descriptor);
|
|
||||||
copy.AddLeds(Leds);
|
|
||||||
|
|
||||||
Parent.AddChild(copy, Order + 1);
|
|
||||||
|
|
||||||
return copy;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public override List<ILayerProperty> GetAllLayerProperties()
|
public override List<ILayerProperty> GetAllLayerProperties()
|
||||||
{
|
{
|
||||||
@ -399,7 +376,14 @@ namespace Artemis.Core
|
|||||||
}
|
}
|
||||||
finally
|
finally
|
||||||
{
|
{
|
||||||
canvas.Restore();
|
try
|
||||||
|
{
|
||||||
|
canvas.Restore();
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
// ignored
|
||||||
|
}
|
||||||
Renderer.Close();
|
Renderer.Close();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -12,8 +12,8 @@ namespace Artemis.Core
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public sealed class Profile : ProfileElement
|
public sealed class Profile : ProfileElement
|
||||||
{
|
{
|
||||||
private bool _isActivated;
|
|
||||||
private readonly object _lock = new object();
|
private readonly object _lock = new object();
|
||||||
|
private bool _isActivated;
|
||||||
|
|
||||||
internal Profile(ProfileModule module, string name) : base(null!)
|
internal Profile(ProfileModule module, string name) : base(null!)
|
||||||
{
|
{
|
||||||
@ -57,7 +57,10 @@ namespace Artemis.Core
|
|||||||
private set => SetAndNotify(ref _isActivated, value);
|
private set => SetAndNotify(ref _isActivated, value);
|
||||||
}
|
}
|
||||||
|
|
||||||
internal ProfileEntity ProfileEntity { get; set; }
|
/// <summary>
|
||||||
|
/// Gets the profile entity this profile uses for persistent storage
|
||||||
|
/// </summary>
|
||||||
|
public ProfileEntity ProfileEntity { get; internal set; }
|
||||||
|
|
||||||
internal Stack<string> UndoStack { get; set; }
|
internal Stack<string> UndoStack { get; set; }
|
||||||
internal Stack<string> RedoStack { get; set; }
|
internal Stack<string> RedoStack { get; set; }
|
||||||
@ -118,6 +121,19 @@ namespace Artemis.Core
|
|||||||
return $"[Profile] {nameof(Name)}: {Name}, {nameof(IsActivated)}: {IsActivated}, {nameof(Module)}: {Module}";
|
return $"[Profile] {nameof(Name)}: {Name}, {nameof(IsActivated)}: {IsActivated}, {nameof(Module)}: {Module}";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Populates all the LEDs on the elements in this profile
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="surface">The currently active surface that contains the LEDs</param>
|
||||||
|
public void PopulateLeds(ArtemisSurface surface)
|
||||||
|
{
|
||||||
|
if (Disposed)
|
||||||
|
throw new ObjectDisposedException("Profile");
|
||||||
|
|
||||||
|
foreach (Layer layer in GetAllLayers())
|
||||||
|
layer.PopulateLeds(surface);
|
||||||
|
}
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
protected override void Dispose(bool disposing)
|
protected override void Dispose(bool disposing)
|
||||||
{
|
{
|
||||||
@ -196,15 +212,6 @@ namespace Artemis.Core
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
internal void PopulateLeds(ArtemisSurface surface)
|
|
||||||
{
|
|
||||||
if (Disposed)
|
|
||||||
throw new ObjectDisposedException("Profile");
|
|
||||||
|
|
||||||
foreach (Layer layer in GetAllLayers())
|
|
||||||
layer.PopulateLeds(surface);
|
|
||||||
}
|
|
||||||
|
|
||||||
#region Events
|
#region Events
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|||||||
@ -5,7 +5,10 @@ using Newtonsoft.Json;
|
|||||||
|
|
||||||
namespace Artemis.Core
|
namespace Artemis.Core
|
||||||
{
|
{
|
||||||
internal static class CoreJson
|
/// <summary>
|
||||||
|
/// A static helper class that serializes and deserializes JSON with the Artemis Core JSON settings
|
||||||
|
/// </summary>
|
||||||
|
public static class CoreJson
|
||||||
{
|
{
|
||||||
#region Serialize
|
#region Serialize
|
||||||
|
|
||||||
|
|||||||
@ -6,6 +6,9 @@ namespace Artemis.Storage.Entities.Profile.Abstract
|
|||||||
{
|
{
|
||||||
public abstract class RenderElementEntity
|
public abstract class RenderElementEntity
|
||||||
{
|
{
|
||||||
|
public Guid Id { get; set; }
|
||||||
|
public Guid ParentId { get; set; }
|
||||||
|
|
||||||
public List<LayerEffectEntity> LayerEffects { get; set; }
|
public List<LayerEffectEntity> LayerEffects { get; set; }
|
||||||
public List<PropertyEntity> PropertyEntities { get; set; }
|
public List<PropertyEntity> PropertyEntities { get; set; }
|
||||||
public List<string> ExpandedPropertyGroups { get; set; }
|
public List<string> ExpandedPropertyGroups { get; set; }
|
||||||
|
|||||||
@ -14,9 +14,6 @@ namespace Artemis.Storage.Entities.Profile
|
|||||||
ExpandedPropertyGroups = new List<string>();
|
ExpandedPropertyGroups = new List<string>();
|
||||||
}
|
}
|
||||||
|
|
||||||
public Guid Id { get; set; }
|
|
||||||
public Guid ParentId { get; set; }
|
|
||||||
|
|
||||||
public int Order { get; set; }
|
public int Order { get; set; }
|
||||||
public string Name { get; set; }
|
public string Name { get; set; }
|
||||||
public bool Enabled { get; set; }
|
public bool Enabled { get; set; }
|
||||||
|
|||||||
@ -15,9 +15,6 @@ namespace Artemis.Storage.Entities.Profile
|
|||||||
ExpandedPropertyGroups = new List<string>();
|
ExpandedPropertyGroups = new List<string>();
|
||||||
}
|
}
|
||||||
|
|
||||||
public Guid Id { get; set; }
|
|
||||||
public Guid ParentId { get; set; }
|
|
||||||
|
|
||||||
public int Order { get; set; }
|
public int Order { get; set; }
|
||||||
public string Name { get; set; }
|
public string Name { get; set; }
|
||||||
public bool Enabled { get; set; }
|
public bool Enabled { get; set; }
|
||||||
|
|||||||
@ -92,46 +92,6 @@ namespace Artemis.UI.Shared.Services
|
|||||||
/// <returns>The current module the profile editor is initialized for</returns>
|
/// <returns>The current module the profile editor is initialized for</returns>
|
||||||
ProfileModule? GetCurrentModule();
|
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>
|
/// <summary>
|
||||||
/// Registers a new property input view model used in the profile editor for the generic type defined in
|
/// Registers a new property input view model used in the profile editor for the generic type defined in
|
||||||
/// <see cref="PropertyInputViewModel{T}" />
|
/// <see cref="PropertyInputViewModel{T}" />
|
||||||
@ -174,5 +134,66 @@ namespace Artemis.UI.Shared.Services
|
|||||||
/// <typeparamref name="T" />
|
/// <typeparamref name="T" />
|
||||||
/// </summary>
|
/// </summary>
|
||||||
PropertyInputViewModel<T>? CreatePropertyInputViewModel<T>(LayerProperty<T> layerProperty);
|
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;
|
||||||
using Artemis.Core.Modules;
|
using Artemis.Core.Modules;
|
||||||
using Artemis.Core.Services;
|
using Artemis.Core.Services;
|
||||||
|
using Artemis.Storage.Entities.Profile;
|
||||||
|
using Artemis.UI.Shared.Services.Models;
|
||||||
|
using Newtonsoft.Json;
|
||||||
using Ninject;
|
using Ninject;
|
||||||
using Ninject.Parameters;
|
using Ninject.Parameters;
|
||||||
using Serilog;
|
using Serilog;
|
||||||
@ -14,23 +17,25 @@ namespace Artemis.UI.Shared.Services
|
|||||||
{
|
{
|
||||||
internal class ProfileEditorService : IProfileEditorService
|
internal class ProfileEditorService : IProfileEditorService
|
||||||
{
|
{
|
||||||
private readonly ILogger _logger;
|
|
||||||
private readonly ICoreService _coreService;
|
private readonly ICoreService _coreService;
|
||||||
|
private readonly ISurfaceService _surfaceService;
|
||||||
|
private readonly IKernel _kernel;
|
||||||
|
private readonly ILogger _logger;
|
||||||
private readonly IProfileService _profileService;
|
private readonly IProfileService _profileService;
|
||||||
private readonly List<PropertyInputRegistration> _registeredPropertyEditors;
|
private readonly List<PropertyInputRegistration> _registeredPropertyEditors;
|
||||||
private readonly object _selectedProfileElementLock = new object();
|
private readonly object _selectedProfileElementLock = new object();
|
||||||
private readonly object _selectedProfileLock = new object();
|
private readonly object _selectedProfileLock = new object();
|
||||||
private TimeSpan _currentTime;
|
private TimeSpan _currentTime;
|
||||||
private int _pixelsPerSecond;
|
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;
|
_kernel = kernel;
|
||||||
|
_logger = logger;
|
||||||
|
_profileService = profileService;
|
||||||
|
_coreService = coreService;
|
||||||
|
_surfaceService = surfaceService;
|
||||||
|
_registeredPropertyEditors = new List<PropertyInputRegistration>();
|
||||||
|
|
||||||
PixelsPerSecond = 100;
|
PixelsPerSecond = 100;
|
||||||
}
|
}
|
||||||
@ -41,6 +46,30 @@ namespace Artemis.UI.Shared.Services
|
|||||||
Execute.PostToUIThread(OnProfilePreviewUpdated);
|
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 ReadOnlyCollection<PropertyInputRegistration> RegisteredPropertyEditors => _registeredPropertyEditors.AsReadOnly();
|
||||||
public Profile? SelectedProfile { get; private set; }
|
public Profile? SelectedProfile { get; private set; }
|
||||||
public RenderProfileElement? SelectedProfileElement { get; private set; }
|
public RenderProfileElement? SelectedProfileElement { get; private set; }
|
||||||
@ -107,7 +136,7 @@ namespace Artemis.UI.Shared.Services
|
|||||||
return;
|
return;
|
||||||
|
|
||||||
_profileService.UpdateProfile(SelectedProfile, true);
|
_profileService.UpdateProfile(SelectedProfile, true);
|
||||||
OnSelectedProfileChanged(new ProfileEventArgs(SelectedProfile));
|
OnSelectedProfileUpdated(new ProfileEventArgs(SelectedProfile));
|
||||||
UpdateProfilePreview();
|
UpdateProfilePreview();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -206,7 +235,7 @@ namespace Artemis.UI.Shared.Services
|
|||||||
if (supportedType.IsGenericParameter)
|
if (supportedType.IsGenericParameter)
|
||||||
{
|
{
|
||||||
if (supportedType.BaseType == null)
|
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;
|
supportedType = supportedType.BaseType;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -258,11 +287,9 @@ namespace Artemis.UI.Shared.Services
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (snapToCurrentTime)
|
if (snapToCurrentTime)
|
||||||
{
|
|
||||||
// Snap to the current time
|
// Snap to the current time
|
||||||
if (Math.Abs(time.TotalMilliseconds - CurrentTime.TotalMilliseconds) < tolerance.TotalMilliseconds)
|
if (Math.Abs(time.TotalMilliseconds - CurrentTime.TotalMilliseconds) < tolerance.TotalMilliseconds)
|
||||||
return CurrentTime;
|
return CurrentTime;
|
||||||
}
|
|
||||||
|
|
||||||
if (snapTimes != null)
|
if (snapTimes != null)
|
||||||
{
|
{
|
||||||
@ -289,9 +316,13 @@ namespace Artemis.UI.Shared.Services
|
|||||||
viewModelType = registration.ViewModelType.MakeGenericType(layerProperty.GetType().GenericTypeArguments);
|
viewModelType = registration.ViewModelType.MakeGenericType(layerProperty.GetType().GenericTypeArguments);
|
||||||
}
|
}
|
||||||
else if (registration != null)
|
else if (registration != null)
|
||||||
|
{
|
||||||
viewModelType = registration.ViewModelType;
|
viewModelType = registration.ViewModelType;
|
||||||
|
}
|
||||||
else
|
else
|
||||||
|
{
|
||||||
return null;
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
if (viewModelType == null)
|
if (viewModelType == null)
|
||||||
return null;
|
return null;
|
||||||
@ -308,30 +339,80 @@ namespace Artemis.UI.Shared.Services
|
|||||||
return SelectedProfile?.Module;
|
return SelectedProfile?.Module;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void ReloadProfile()
|
#region Copy/paste
|
||||||
{
|
|
||||||
if (SelectedProfile == null)
|
|
||||||
return;
|
|
||||||
|
|
||||||
// Trigger a profile change
|
public ProfileElement? DuplicateProfileElement(ProfileElement profileElement)
|
||||||
OnSelectedProfileChanged(new ProfileEventArgs(SelectedProfile, SelectedProfile));
|
{
|
||||||
// Trigger a selected element change
|
if (!(profileElement.Parent is Folder parent))
|
||||||
RenderProfileElement? previousSelectedProfileElement = SelectedProfileElement;
|
return null;
|
||||||
if (SelectedProfileElement is Folder folder)
|
|
||||||
SelectedProfileElement = SelectedProfile.GetAllFolders().FirstOrDefault(f => f.EntityId == folder.EntityId);
|
object? clipboardModel = null;
|
||||||
else if (SelectedProfileElement is Layer layer)
|
switch (profileElement)
|
||||||
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);
|
case Folder folder:
|
||||||
OnSelectedDataBindingChanged();
|
{
|
||||||
|
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
|
#region Events
|
||||||
|
|
||||||
public event EventHandler<ProfileEventArgs>? ProfileSelected;
|
public event EventHandler<ProfileEventArgs>? ProfileSelected;
|
||||||
@ -396,6 +477,5 @@ namespace Artemis.UI.Shared.Services
|
|||||||
}
|
}
|
||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
58
src/Artemis.UI.Shared/Utilities/JsonClipboard.cs
Normal file
58
src/Artemis.UI.Shared/Utilities/JsonClipboard.cs
Normal file
@ -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");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -104,9 +104,16 @@ namespace Artemis.UI.Behaviors
|
|||||||
if (SelectedItem == model && !item.IsSelected)
|
if (SelectedItem == model && !item.IsSelected)
|
||||||
{
|
{
|
||||||
item.IsSelected = true;
|
item.IsSelected = true;
|
||||||
|
// Focus the newly selected item as if was clicked
|
||||||
|
item.Focus();
|
||||||
if (ExpandSelected)
|
if (ExpandSelected)
|
||||||
item.IsExpanded = true;
|
item.IsExpanded = true;
|
||||||
}
|
}
|
||||||
|
else if (SelectedItem == model && !item.IsFocused)
|
||||||
|
{
|
||||||
|
// Focus the newly selected item as if was clicked
|
||||||
|
item.Focus();
|
||||||
|
}
|
||||||
// If the selected item is a parent of this model - expand
|
// If the selected item is a parent of this model - expand
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
@ -117,14 +124,12 @@ namespace Artemis.UI.Behaviors
|
|||||||
|
|
||||||
// Recurse into children
|
// Recurse into children
|
||||||
if (recurse)
|
if (recurse)
|
||||||
{
|
|
||||||
foreach (object subitem in item.Items)
|
foreach (object subitem in item.Items)
|
||||||
{
|
{
|
||||||
TreeViewItem tvi = item.ItemContainerGenerator.ContainerFromItem(subitem) as TreeViewItem;
|
TreeViewItem tvi = item.ItemContainerGenerator.ContainerFromItem(subitem) as TreeViewItem;
|
||||||
if (tvi != null)
|
if (tvi != null)
|
||||||
UpdateTreeViewItem(tvi, true);
|
UpdateTreeViewItem(tvi, true);
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Update state of all items
|
// Update state of all items
|
||||||
@ -143,11 +148,9 @@ namespace Artemis.UI.Behaviors
|
|||||||
private void UpdateTreeViewItemStyle()
|
private void UpdateTreeViewItemStyle()
|
||||||
{
|
{
|
||||||
if (AssociatedObject.ItemContainerStyle == null)
|
if (AssociatedObject.ItemContainerStyle == null)
|
||||||
{
|
|
||||||
AssociatedObject.ItemContainerStyle = new Style(
|
AssociatedObject.ItemContainerStyle = new Style(
|
||||||
typeof(TreeViewItem),
|
typeof(TreeViewItem),
|
||||||
Application.Current.TryFindResource(typeof(TreeViewItem)) as Style);
|
Application.Current.TryFindResource(typeof(TreeViewItem)) as Style);
|
||||||
}
|
|
||||||
|
|
||||||
if (!AssociatedObject.ItemContainerStyle.Setters.Contains(_treeViewItemEventSetter))
|
if (!AssociatedObject.ItemContainerStyle.Setters.Contains(_treeViewItemEventSetter))
|
||||||
AssociatedObject.ItemContainerStyle.Setters.Add(_treeViewItemEventSetter);
|
AssociatedObject.ItemContainerStyle.Setters.Add(_treeViewItemEventSetter);
|
||||||
|
|||||||
20
src/Artemis.UI/Extensions/ScreenExtensions.cs
Normal file
20
src/Artemis.UI/Extensions/ScreenExtensions.cs
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
using Stylet;
|
||||||
|
|
||||||
|
namespace Artemis.UI.Extensions
|
||||||
|
{
|
||||||
|
public static class ScreenExtensions
|
||||||
|
{
|
||||||
|
public static T FindScreenOfType<T>(this Screen screen) where T : Screen
|
||||||
|
{
|
||||||
|
Screen parent = screen.Parent as Screen;
|
||||||
|
while (parent != null)
|
||||||
|
{
|
||||||
|
if (parent is T match)
|
||||||
|
return match;
|
||||||
|
parent = parent.Parent as Screen;
|
||||||
|
}
|
||||||
|
|
||||||
|
return default;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -34,10 +34,11 @@
|
|||||||
dd:DragDrop.IsDropTarget="True"
|
dd:DragDrop.IsDropTarget="True"
|
||||||
dd:DragDrop.DropHandler="{Binding}">
|
dd:DragDrop.DropHandler="{Binding}">
|
||||||
<TreeView.InputBindings>
|
<TreeView.InputBindings>
|
||||||
<KeyBinding Key="F2" Command="{s:Action RenameElement}" s:View.ActionTarget="{Binding SelectedTreeItem}"/>
|
<KeyBinding Key="F2" Command="{s:Action RenameElement}" s:View.ActionTarget="{Binding SelectedTreeItem}" />
|
||||||
<KeyBinding Key="Delete" Command="{s:Action DeleteElement}" s:View.ActionTarget="{Binding SelectedTreeItem}"/>
|
<KeyBinding Key="Delete" Command="{s:Action DeleteElement}" s:View.ActionTarget="{Binding SelectedTreeItem}" />
|
||||||
<KeyBinding Key="C" Modifiers="Control" Command="{s:Action CopyElement}" s:View.ActionTarget="{Binding SelectedTreeItem}"/>
|
<KeyBinding Key="D" Modifiers="Control" Command="{s:Action DuplicateElement}" s:View.ActionTarget="{Binding SelectedTreeItem}" />
|
||||||
<KeyBinding Key="V" Modifiers="Control" Command="{s:Action PasteElement}" s:View.ActionTarget="{Binding SelectedTreeItem}"/>
|
<KeyBinding Key="C" Modifiers="Control" Command="{s:Action CopyElement}" s:View.ActionTarget="{Binding SelectedTreeItem}" />
|
||||||
|
<KeyBinding Key="V" Modifiers="Control" Command="{s:Action PasteElement}" s:View.ActionTarget="{Binding SelectedTreeItem}" />
|
||||||
</TreeView.InputBindings>
|
</TreeView.InputBindings>
|
||||||
<b:Interaction.Behaviors>
|
<b:Interaction.Behaviors>
|
||||||
<behaviors:TreeViewSelectionBehavior ExpandSelected="True" SelectedItem="{Binding SelectedTreeItem}" />
|
<behaviors:TreeViewSelectionBehavior ExpandSelected="True" SelectedItem="{Binding SelectedTreeItem}" />
|
||||||
|
|||||||
@ -13,8 +13,8 @@ namespace Artemis.UI.Screens.ProfileEditor.ProfileTree
|
|||||||
{
|
{
|
||||||
public class ProfileTreeViewModel : Conductor<FolderViewModel>, IProfileEditorPanelViewModel, IDropTarget
|
public class ProfileTreeViewModel : Conductor<FolderViewModel>, IProfileEditorPanelViewModel, IDropTarget
|
||||||
{
|
{
|
||||||
private readonly IProfileTreeVmFactory _profileTreeVmFactory;
|
|
||||||
private readonly IProfileEditorService _profileEditorService;
|
private readonly IProfileEditorService _profileEditorService;
|
||||||
|
private readonly IProfileTreeVmFactory _profileTreeVmFactory;
|
||||||
private TreeItemViewModel _selectedTreeItem;
|
private TreeItemViewModel _selectedTreeItem;
|
||||||
private bool _updatingTree;
|
private bool _updatingTree;
|
||||||
|
|
||||||
@ -41,6 +41,73 @@ namespace Artemis.UI.Screens.ProfileEditor.ProfileTree
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ReSharper disable once UnusedMember.Global - Called from view
|
||||||
|
public void AddFolder()
|
||||||
|
{
|
||||||
|
ActiveItem?.AddFolder();
|
||||||
|
}
|
||||||
|
|
||||||
|
// ReSharper disable once UnusedMember.Global - Called from view
|
||||||
|
public void AddLayer()
|
||||||
|
{
|
||||||
|
ActiveItem?.AddLayer();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void OnInitialActivate()
|
||||||
|
{
|
||||||
|
Subscribe();
|
||||||
|
base.OnInitialActivate();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void OnClose()
|
||||||
|
{
|
||||||
|
Unsubscribe();
|
||||||
|
base.OnClose();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void CreateRootFolderViewModel()
|
||||||
|
{
|
||||||
|
_updatingTree = true;
|
||||||
|
ProfileElement firstChild = _profileEditorService.SelectedProfile?.Children?.FirstOrDefault();
|
||||||
|
if (!(firstChild is Folder folder))
|
||||||
|
{
|
||||||
|
ActivateItem(null);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
ActivateItem(_profileTreeVmFactory.FolderViewModel(folder));
|
||||||
|
_updatingTree = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static DragDropType GetDragDropType(IDropInfo dropInfo)
|
||||||
|
{
|
||||||
|
TreeItemViewModel source = (TreeItemViewModel) dropInfo.Data;
|
||||||
|
TreeItemViewModel target = (TreeItemViewModel) dropInfo.TargetItem;
|
||||||
|
if (source == target)
|
||||||
|
return DragDropType.None;
|
||||||
|
|
||||||
|
TreeItemViewModel parent = target;
|
||||||
|
while (parent != null)
|
||||||
|
{
|
||||||
|
if (parent == source)
|
||||||
|
return DragDropType.None;
|
||||||
|
parent = parent.Parent as TreeItemViewModel;
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (dropInfo.InsertPosition)
|
||||||
|
{
|
||||||
|
case RelativeInsertPosition.AfterTargetItem | RelativeInsertPosition.TargetItemCenter:
|
||||||
|
case RelativeInsertPosition.BeforeTargetItem | RelativeInsertPosition.TargetItemCenter:
|
||||||
|
return target.SupportsChildren ? DragDropType.Add : DragDropType.None;
|
||||||
|
case RelativeInsertPosition.BeforeTargetItem:
|
||||||
|
return DragDropType.InsertBefore;
|
||||||
|
case RelativeInsertPosition.AfterTargetItem:
|
||||||
|
return DragDropType.InsertAfter;
|
||||||
|
default:
|
||||||
|
return DragDropType.None;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public void DragOver(IDropInfo dropInfo)
|
public void DragOver(IDropInfo dropInfo)
|
||||||
{
|
{
|
||||||
DragDropType dragDropType = GetDragDropType(dropInfo);
|
DragDropType dragDropType = GetDragDropType(dropInfo);
|
||||||
@ -84,80 +151,6 @@ namespace Artemis.UI.Screens.ProfileEditor.ProfileTree
|
|||||||
Subscribe();
|
Subscribe();
|
||||||
}
|
}
|
||||||
|
|
||||||
// ReSharper disable once UnusedMember.Global - Called from view
|
|
||||||
public void AddFolder()
|
|
||||||
{
|
|
||||||
ActiveItem?.AddFolder();
|
|
||||||
}
|
|
||||||
|
|
||||||
// ReSharper disable once UnusedMember.Global - Called from view
|
|
||||||
public void AddLayer()
|
|
||||||
{
|
|
||||||
ActiveItem?.AddLayer();
|
|
||||||
}
|
|
||||||
|
|
||||||
protected override void OnInitialActivate()
|
|
||||||
{
|
|
||||||
Subscribe();
|
|
||||||
base.OnInitialActivate();
|
|
||||||
}
|
|
||||||
|
|
||||||
protected override void OnClose()
|
|
||||||
{
|
|
||||||
Unsubscribe();
|
|
||||||
base.OnClose();
|
|
||||||
}
|
|
||||||
|
|
||||||
private void CreateRootFolderViewModel()
|
|
||||||
{
|
|
||||||
_updatingTree = true;
|
|
||||||
ProfileElement firstChild = _profileEditorService.SelectedProfile?.Children?.FirstOrDefault();
|
|
||||||
if (!(firstChild is Folder folder))
|
|
||||||
{
|
|
||||||
ActivateItem(null);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
ActivateItem(_profileTreeVmFactory.FolderViewModel(folder));
|
|
||||||
_updatingTree = false;
|
|
||||||
|
|
||||||
// Auto-select the first layer
|
|
||||||
// if (_profileEditorService.SelectedProfile != null && SelectedTreeItem == null)
|
|
||||||
// {
|
|
||||||
// if (_profileEditorService.SelectedProfile.GetRootFolder().Children.FirstOrDefault() is RenderProfileElement firstElement)
|
|
||||||
// Execute.PostToUIThread(() => _profileEditorService.ChangeSelectedProfileElement(firstElement));
|
|
||||||
// }
|
|
||||||
}
|
|
||||||
|
|
||||||
private static DragDropType GetDragDropType(IDropInfo dropInfo)
|
|
||||||
{
|
|
||||||
TreeItemViewModel source = (TreeItemViewModel) dropInfo.Data;
|
|
||||||
TreeItemViewModel target = (TreeItemViewModel) dropInfo.TargetItem;
|
|
||||||
if (source == target)
|
|
||||||
return DragDropType.None;
|
|
||||||
|
|
||||||
TreeItemViewModel parent = target;
|
|
||||||
while (parent != null)
|
|
||||||
{
|
|
||||||
if (parent == source)
|
|
||||||
return DragDropType.None;
|
|
||||||
parent = parent.Parent as TreeItemViewModel;
|
|
||||||
}
|
|
||||||
|
|
||||||
switch (dropInfo.InsertPosition)
|
|
||||||
{
|
|
||||||
case RelativeInsertPosition.AfterTargetItem | RelativeInsertPosition.TargetItemCenter:
|
|
||||||
case RelativeInsertPosition.BeforeTargetItem | RelativeInsertPosition.TargetItemCenter:
|
|
||||||
return target.SupportsChildren ? DragDropType.Add : DragDropType.None;
|
|
||||||
case RelativeInsertPosition.BeforeTargetItem:
|
|
||||||
return DragDropType.InsertBefore;
|
|
||||||
case RelativeInsertPosition.AfterTargetItem:
|
|
||||||
return DragDropType.InsertAfter;
|
|
||||||
default:
|
|
||||||
return DragDropType.None;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#region Event handlers
|
#region Event handlers
|
||||||
|
|
||||||
private void Subscribe()
|
private void Subscribe()
|
||||||
@ -187,7 +180,9 @@ namespace Artemis.UI.Screens.ProfileEditor.ProfileTree
|
|||||||
ActiveItem.UpdateProfileElements();
|
ActiveItem.UpdateProfileElements();
|
||||||
_updatingTree = false;
|
_updatingTree = false;
|
||||||
if (e.RenderProfileElement == null)
|
if (e.RenderProfileElement == null)
|
||||||
|
{
|
||||||
SelectedTreeItem = null;
|
SelectedTreeItem = null;
|
||||||
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
TreeItemViewModel match = ActiveItem.GetAllChildren().FirstOrDefault(vm => vm.ProfileElement == e.RenderProfileElement);
|
TreeItemViewModel match = ActiveItem.GetAllChildren().FirstOrDefault(vm => vm.ProfileElement == e.RenderProfileElement);
|
||||||
|
|||||||
@ -13,17 +13,6 @@
|
|||||||
<StackPanel Margin="-10" Background="Transparent">
|
<StackPanel Margin="-10" Background="Transparent">
|
||||||
<StackPanel.ContextMenu>
|
<StackPanel.ContextMenu>
|
||||||
<ContextMenu>
|
<ContextMenu>
|
||||||
<MenuItem Header="Rename" Command="{s:Action RenameElement}">
|
|
||||||
<MenuItem.Icon>
|
|
||||||
<materialDesign:PackIcon Kind="RenameBox" />
|
|
||||||
</MenuItem.Icon>
|
|
||||||
</MenuItem>
|
|
||||||
<MenuItem Header="Delete" Command="{s:Action DeleteElement}">
|
|
||||||
<MenuItem.Icon>
|
|
||||||
<materialDesign:PackIcon Kind="TrashCan" />
|
|
||||||
</MenuItem.Icon>
|
|
||||||
</MenuItem>
|
|
||||||
<Separator />
|
|
||||||
<MenuItem Header="Add new folder" Command="{s:Action AddFolder}">
|
<MenuItem Header="Add new folder" Command="{s:Action AddFolder}">
|
||||||
<MenuItem.Icon>
|
<MenuItem.Icon>
|
||||||
<materialDesign:PackIcon Kind="CreateNewFolder" />
|
<materialDesign:PackIcon Kind="CreateNewFolder" />
|
||||||
@ -34,6 +23,33 @@
|
|||||||
<materialDesign:PackIcon Kind="LayersPlus" />
|
<materialDesign:PackIcon Kind="LayersPlus" />
|
||||||
</MenuItem.Icon>
|
</MenuItem.Icon>
|
||||||
</MenuItem>
|
</MenuItem>
|
||||||
|
<Separator />
|
||||||
|
<MenuItem Header="Duplicate" Command="{s:Action DuplicateElement}" InputGestureText="Ctrl+D">
|
||||||
|
<MenuItem.Icon>
|
||||||
|
<materialDesign:PackIcon Kind="ContentDuplicate" />
|
||||||
|
</MenuItem.Icon>
|
||||||
|
</MenuItem>
|
||||||
|
<MenuItem Header="Copy" Command="{s:Action CopyElement}" InputGestureText="Ctrl+C">
|
||||||
|
<MenuItem.Icon>
|
||||||
|
<materialDesign:PackIcon Kind="ContentCopy" />
|
||||||
|
</MenuItem.Icon>
|
||||||
|
</MenuItem>
|
||||||
|
<MenuItem Header="Paste" Command="{s:Action PasteElement}" InputGestureText="Ctrl+V">
|
||||||
|
<MenuItem.Icon>
|
||||||
|
<materialDesign:PackIcon Kind="ContentPaste" />
|
||||||
|
</MenuItem.Icon>
|
||||||
|
</MenuItem>
|
||||||
|
<Separator />
|
||||||
|
<MenuItem Header="Rename" Command="{s:Action RenameElement}" InputGestureText="F2">
|
||||||
|
<MenuItem.Icon>
|
||||||
|
<materialDesign:PackIcon Kind="RenameBox" />
|
||||||
|
</MenuItem.Icon>
|
||||||
|
</MenuItem>
|
||||||
|
<MenuItem Header="Delete" Command="{s:Action DeleteElement}" InputGestureText="Del">
|
||||||
|
<MenuItem.Icon>
|
||||||
|
<materialDesign:PackIcon Kind="TrashCan" />
|
||||||
|
</MenuItem.Icon>
|
||||||
|
</MenuItem>
|
||||||
</ContextMenu>
|
</ContextMenu>
|
||||||
</StackPanel.ContextMenu>
|
</StackPanel.ContextMenu>
|
||||||
<Grid Margin="10">
|
<Grid Margin="10">
|
||||||
|
|||||||
@ -13,17 +13,28 @@
|
|||||||
<StackPanel Margin="-10" Background="Transparent">
|
<StackPanel Margin="-10" Background="Transparent">
|
||||||
<StackPanel.ContextMenu>
|
<StackPanel.ContextMenu>
|
||||||
<ContextMenu>
|
<ContextMenu>
|
||||||
<MenuItem Header="Rename" Command="{s:Action RenameElement}">
|
<MenuItem Header="Duplicate" Command="{s:Action DuplicateElement}" InputGestureText="Ctrl+D">
|
||||||
<MenuItem.Icon>
|
<MenuItem.Icon>
|
||||||
<materialDesign:PackIcon Kind="RenameBox" />
|
<materialDesign:PackIcon Kind="ContentDuplicate" />
|
||||||
</MenuItem.Icon>
|
</MenuItem.Icon>
|
||||||
</MenuItem>
|
</MenuItem>
|
||||||
<MenuItem Header="Duplicate" Command="{s:Action DuplicateElement}">
|
<MenuItem Header="Copy" Command="{s:Action CopyElement}" InputGestureText="Ctrl+C">
|
||||||
<MenuItem.Icon>
|
<MenuItem.Icon>
|
||||||
<materialDesign:PackIcon Kind="ContentCopy" />
|
<materialDesign:PackIcon Kind="ContentCopy" />
|
||||||
</MenuItem.Icon>
|
</MenuItem.Icon>
|
||||||
</MenuItem>
|
</MenuItem>
|
||||||
<MenuItem Header="Delete" Command="{s:Action DeleteElement}">
|
<MenuItem Header="Paste" Command="{s:Action PasteElement}" InputGestureText="Ctrl+V">
|
||||||
|
<MenuItem.Icon>
|
||||||
|
<materialDesign:PackIcon Kind="ContentPaste" />
|
||||||
|
</MenuItem.Icon>
|
||||||
|
</MenuItem>
|
||||||
|
<Separator />
|
||||||
|
<MenuItem Header="Rename" Command="{s:Action RenameElement}" InputGestureText="F2">
|
||||||
|
<MenuItem.Icon>
|
||||||
|
<materialDesign:PackIcon Kind="RenameBox" />
|
||||||
|
</MenuItem.Icon>
|
||||||
|
</MenuItem>
|
||||||
|
<MenuItem Header="Delete" Command="{s:Action DeleteElement}" InputGestureText="Del">
|
||||||
<MenuItem.Icon>
|
<MenuItem.Icon>
|
||||||
<materialDesign:PackIcon Kind="TrashCan" />
|
<materialDesign:PackIcon Kind="TrashCan" />
|
||||||
</MenuItem.Icon>
|
</MenuItem.Icon>
|
||||||
|
|||||||
@ -1,5 +1,4 @@
|
|||||||
using System.Threading.Tasks;
|
using Artemis.Core;
|
||||||
using Artemis.Core;
|
|
||||||
using Artemis.Core.Services;
|
using Artemis.Core.Services;
|
||||||
using Artemis.UI.Ninject.Factories;
|
using Artemis.UI.Ninject.Factories;
|
||||||
using Artemis.UI.Shared.Services;
|
using Artemis.UI.Shared.Services;
|
||||||
@ -8,8 +7,6 @@ namespace Artemis.UI.Screens.ProfileEditor.ProfileTree.TreeItem
|
|||||||
{
|
{
|
||||||
public class LayerViewModel : TreeItemViewModel
|
public class LayerViewModel : TreeItemViewModel
|
||||||
{
|
{
|
||||||
private readonly IProfileEditorService _profileEditorService;
|
|
||||||
|
|
||||||
public LayerViewModel(ProfileElement layer,
|
public LayerViewModel(ProfileElement layer,
|
||||||
IProfileEditorService profileEditorService,
|
IProfileEditorService profileEditorService,
|
||||||
IDialogService dialogService,
|
IDialogService dialogService,
|
||||||
@ -18,15 +15,6 @@ namespace Artemis.UI.Screens.ProfileEditor.ProfileTree.TreeItem
|
|||||||
ISurfaceService surfaceService) :
|
ISurfaceService surfaceService) :
|
||||||
base(layer, profileEditorService, dialogService, profileTreeVmFactory, layerBrushService, surfaceService)
|
base(layer, profileEditorService, dialogService, profileTreeVmFactory, layerBrushService, surfaceService)
|
||||||
{
|
{
|
||||||
_profileEditorService = profileEditorService;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void DuplicateElement()
|
|
||||||
{
|
|
||||||
Layer layer = Layer.CreateCopy();
|
|
||||||
|
|
||||||
_profileEditorService.UpdateSelectedProfile();
|
|
||||||
_profileEditorService.ChangeSelectedProfileElement(layer);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public Layer Layer => ProfileElement as Layer;
|
public Layer Layer => ProfileElement as Layer;
|
||||||
|
|||||||
@ -5,12 +5,11 @@ using System.Threading.Tasks;
|
|||||||
using Artemis.Core;
|
using Artemis.Core;
|
||||||
using Artemis.Core.LayerBrushes;
|
using Artemis.Core.LayerBrushes;
|
||||||
using Artemis.Core.Services;
|
using Artemis.Core.Services;
|
||||||
using Artemis.Storage.Entities.Profile;
|
|
||||||
using Artemis.UI.Exceptions;
|
using Artemis.UI.Exceptions;
|
||||||
|
using Artemis.UI.Extensions;
|
||||||
using Artemis.UI.Ninject.Factories;
|
using Artemis.UI.Ninject.Factories;
|
||||||
using Artemis.UI.Screens.ProfileEditor.Dialogs;
|
using Artemis.UI.Screens.ProfileEditor.Dialogs;
|
||||||
using Artemis.UI.Shared.Services;
|
using Artemis.UI.Shared.Services;
|
||||||
using Artemis.UI.Utilities;
|
|
||||||
using Stylet;
|
using Stylet;
|
||||||
|
|
||||||
namespace Artemis.UI.Screens.ProfileEditor.ProfileTree.TreeItem
|
namespace Artemis.UI.Screens.ProfileEditor.ProfileTree.TreeItem
|
||||||
@ -178,36 +177,44 @@ namespace Artemis.UI.Screens.ProfileEditor.ProfileTree.TreeItem
|
|||||||
if (!result)
|
if (!result)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
|
ProfileElement newSelection = null;
|
||||||
|
if (ProfileElement.Parent != null)
|
||||||
|
{
|
||||||
|
int index = ProfileElement.Parent.Children.IndexOf(ProfileElement);
|
||||||
|
// If there is a next element, select that
|
||||||
|
if (index < ProfileElement.Parent.Children.Count - 1)
|
||||||
|
newSelection = ProfileElement.Parent.Children[index + 1];
|
||||||
|
// Otherwise select the previous element
|
||||||
|
else if (index > 0)
|
||||||
|
newSelection = ProfileElement.Parent.Children[index - 1];
|
||||||
|
// And if that's not there, fall back to the parent
|
||||||
|
else
|
||||||
|
newSelection = ProfileElement.Parent;
|
||||||
|
}
|
||||||
|
|
||||||
// Farewell, cruel world
|
// Farewell, cruel world
|
||||||
TreeItemViewModel parent = (TreeItemViewModel) Parent;
|
TreeItemViewModel parent = (TreeItemViewModel) Parent;
|
||||||
ProfileElement.Parent?.RemoveChild(ProfileElement);
|
ProfileElement.Parent?.RemoveChild(ProfileElement);
|
||||||
parent.RemoveExistingElement(this);
|
parent.RemoveExistingElement(this);
|
||||||
|
|
||||||
_profileEditorService.UpdateSelectedProfile();
|
_profileEditorService.UpdateSelectedProfile();
|
||||||
_profileEditorService.ChangeSelectedProfileElement(null);
|
_profileEditorService.ChangeSelectedProfileElement(newSelection as RenderProfileElement);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void DuplicateElement()
|
||||||
|
{
|
||||||
|
_profileEditorService.DuplicateProfileElement(ProfileElement);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void CopyElement()
|
public void CopyElement()
|
||||||
{
|
{
|
||||||
if (ProfileElement is Layer layer)
|
_profileEditorService.CopyProfileElement(ProfileElement);
|
||||||
JsonClipboard.SetObject(layer.LayerEntity);
|
|
||||||
else if (ProfileElement is Folder folder)
|
|
||||||
JsonClipboard.SetObject(folder.FolderEntity);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void PasteElement()
|
public void PasteElement()
|
||||||
{
|
{
|
||||||
object? clipboardObject = JsonClipboard.GetData();
|
if (ProfileElement.Parent is Folder parent)
|
||||||
if (clipboardObject is LayerEntity layerEntity)
|
_profileEditorService.PasteProfileElement(parent, ProfileElement.Order);
|
||||||
{
|
|
||||||
layerEntity.Id = Guid.NewGuid();
|
|
||||||
layerEntity.Name += " - copy";
|
|
||||||
Layer pasted = new Layer(ProfileElement.Profile, ProfileElement.Parent, layerEntity);
|
|
||||||
ProfileElement.Parent.AddChild(pasted, ProfileElement.Order - 1);
|
|
||||||
}
|
|
||||||
else if (clipboardObject is FolderEntity folderEntity)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void UpdateProfileElements()
|
public void UpdateProfileElements()
|
||||||
@ -217,19 +224,7 @@ namespace Artemis.UI.Screens.ProfileEditor.ProfileTree.TreeItem
|
|||||||
foreach (TreeItemViewModel treeItemViewModel in toRemove)
|
foreach (TreeItemViewModel treeItemViewModel in toRemove)
|
||||||
Items.Remove(treeItemViewModel);
|
Items.Remove(treeItemViewModel);
|
||||||
|
|
||||||
// Order the children
|
// Add missing children
|
||||||
List<TreeItemViewModel> vmsList = Items.OrderBy(v => v.ProfileElement.Order).ToList();
|
|
||||||
for (int index = 0; index < vmsList.Count; index++)
|
|
||||||
{
|
|
||||||
TreeItemViewModel profileElementViewModel = vmsList[index];
|
|
||||||
if (Items.IndexOf(profileElementViewModel) != index)
|
|
||||||
((BindableCollection<TreeItemViewModel>) Items).Move(Items.IndexOf(profileElementViewModel), index);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Ensure every child element has an up-to-date VM
|
|
||||||
if (ProfileElement.Children == null)
|
|
||||||
return;
|
|
||||||
|
|
||||||
List<TreeItemViewModel> newChildren = new List<TreeItemViewModel>();
|
List<TreeItemViewModel> newChildren = new List<TreeItemViewModel>();
|
||||||
foreach (ProfileElement profileElement in ProfileElement.Children.OrderBy(c => c.Order))
|
foreach (ProfileElement profileElement in ProfileElement.Children.OrderBy(c => c.Order))
|
||||||
if (profileElement is Folder folder)
|
if (profileElement is Folder folder)
|
||||||
@ -243,15 +238,15 @@ namespace Artemis.UI.Screens.ProfileEditor.ProfileTree.TreeItem
|
|||||||
newChildren.Add(_profileTreeVmFactory.LayerViewModel(layer));
|
newChildren.Add(_profileTreeVmFactory.LayerViewModel(layer));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!newChildren.Any())
|
|
||||||
return;
|
|
||||||
|
|
||||||
// Add the new children in one call, prevent extra UI events
|
// Add the new children in one call, prevent extra UI events
|
||||||
foreach (TreeItemViewModel treeItemViewModel in newChildren)
|
foreach (TreeItemViewModel treeItemViewModel in newChildren)
|
||||||
{
|
{
|
||||||
treeItemViewModel.UpdateProfileElements();
|
treeItemViewModel.UpdateProfileElements();
|
||||||
Items.Add(treeItemViewModel);
|
Items.Add(treeItemViewModel);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Order the children
|
||||||
|
((BindableCollection<TreeItemViewModel>) Items).Sort(i => i.ProfileElement.Order);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void EnableToggled()
|
public void EnableToggled()
|
||||||
|
|||||||
@ -382,6 +382,7 @@ namespace Artemis.UI.Screens.ProfileEditor.Visualization
|
|||||||
else
|
else
|
||||||
_previousSelectedLayer = null;
|
_previousSelectedLayer = null;
|
||||||
|
|
||||||
|
ApplyActiveProfile();
|
||||||
UpdateLedsDimStatus();
|
UpdateLedsDimStatus();
|
||||||
UpdateCanSelectEditTool();
|
UpdateCanSelectEditTool();
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,35 +0,0 @@
|
|||||||
using System.Windows;
|
|
||||||
using Newtonsoft.Json;
|
|
||||||
|
|
||||||
namespace Artemis.UI.Utilities
|
|
||||||
{
|
|
||||||
public static class JsonClipboard
|
|
||||||
{
|
|
||||||
private static readonly JsonSerializerSettings JsonSettings = new JsonSerializerSettings {TypeNameHandling = TypeNameHandling.All};
|
|
||||||
|
|
||||||
public static void SetObject(object clipboardObject)
|
|
||||||
{
|
|
||||||
string json = JsonConvert.SerializeObject(clipboardObject, JsonSettings);
|
|
||||||
Clipboard.SetData("Artemis", json);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static object GetData()
|
|
||||||
{
|
|
||||||
string json = Clipboard.GetData("Artemis")?.ToString();
|
|
||||||
if (json != null)
|
|
||||||
return JsonConvert.DeserializeObject(json, JsonSettings);
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static T GetData<T>()
|
|
||||||
{
|
|
||||||
object data = GetData();
|
|
||||||
return data is T castData ? castData : default;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static bool ContainsArtemisData()
|
|
||||||
{
|
|
||||||
return Clipboard.ContainsData("Artemis");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Loading…
x
Reference in New Issue
Block a user