1
0
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:
Robert 2020-12-01 19:08:10 +01:00
parent f694f39219
commit aae4e71d8e
21 changed files with 524 additions and 294 deletions

View File

@ -33,7 +33,13 @@ namespace Artemis.Core
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;
EntityId = folderEntity.Id;

View File

@ -122,29 +122,6 @@ namespace Artemis.Core
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 />
public override List<ILayerProperty> GetAllLayerProperties()
{
@ -398,8 +375,15 @@ namespace Artemis.Core
canvas.DrawBitmap(Renderer.Bitmap, Renderer.TargetLocation, Renderer.Paint);
}
finally
{
try
{
canvas.Restore();
}
catch
{
// ignored
}
Renderer.Close();
}
}

View File

@ -12,8 +12,8 @@ namespace Artemis.Core
/// </summary>
public sealed class Profile : ProfileElement
{
private bool _isActivated;
private readonly object _lock = new object();
private bool _isActivated;
internal Profile(ProfileModule module, string name) : base(null!)
{
@ -57,7 +57,10 @@ namespace Artemis.Core
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> RedoStack { get; set; }
@ -118,6 +121,19 @@ namespace Artemis.Core
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 />
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
/// <summary>

View File

@ -5,7 +5,10 @@ using Newtonsoft.Json;
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

View File

@ -6,6 +6,9 @@ namespace Artemis.Storage.Entities.Profile.Abstract
{
public abstract class RenderElementEntity
{
public Guid Id { get; set; }
public Guid ParentId { get; set; }
public List<LayerEffectEntity> LayerEffects { get; set; }
public List<PropertyEntity> PropertyEntities { get; set; }
public List<string> ExpandedPropertyGroups { get; set; }

View File

@ -14,9 +14,6 @@ namespace Artemis.Storage.Entities.Profile
ExpandedPropertyGroups = new List<string>();
}
public Guid Id { get; set; }
public Guid ParentId { get; set; }
public int Order { get; set; }
public string Name { get; set; }
public bool Enabled { get; set; }

View File

@ -15,9 +15,6 @@ namespace Artemis.Storage.Entities.Profile
ExpandedPropertyGroups = new List<string>();
}
public Guid Id { get; set; }
public Guid ParentId { get; set; }
public int Order { get; set; }
public string Name { get; set; }
public bool Enabled { get; set; }

View File

@ -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;
}
}

View File

@ -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;
}
}
}
}

View File

@ -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)
{
SelectedDataBinding = SelectedProfileElement?.GetAllLayerProperties().FirstOrDefault(p => p.Path == SelectedDataBinding.Path);
OnSelectedDataBindingChanged();
if (!(profileElement.Parent is Folder parent))
return null;
object? clipboardModel = null;
switch (profileElement)
{
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
}
}

View 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");
}
}
}

View File

@ -104,9 +104,16 @@ namespace Artemis.UI.Behaviors
if (SelectedItem == model && !item.IsSelected)
{
item.IsSelected = true;
// Focus the newly selected item as if was clicked
item.Focus();
if (ExpandSelected)
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
else
{
@ -117,7 +124,6 @@ namespace Artemis.UI.Behaviors
// Recurse into children
if (recurse)
{
foreach (object subitem in item.Items)
{
TreeViewItem tvi = item.ItemContainerGenerator.ContainerFromItem(subitem) as TreeViewItem;
@ -125,7 +131,6 @@ namespace Artemis.UI.Behaviors
UpdateTreeViewItem(tvi, true);
}
}
}
// Update state of all items
private void UpdateAllTreeViewItems()
@ -143,11 +148,9 @@ namespace Artemis.UI.Behaviors
private void UpdateTreeViewItemStyle()
{
if (AssociatedObject.ItemContainerStyle == null)
{
AssociatedObject.ItemContainerStyle = new Style(
typeof(TreeViewItem),
Application.Current.TryFindResource(typeof(TreeViewItem)) as Style);
}
if (!AssociatedObject.ItemContainerStyle.Setters.Contains(_treeViewItemEventSetter))
AssociatedObject.ItemContainerStyle.Setters.Add(_treeViewItemEventSetter);

View 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;
}
}
}

View File

@ -34,10 +34,11 @@
dd:DragDrop.IsDropTarget="True"
dd:DragDrop.DropHandler="{Binding}">
<TreeView.InputBindings>
<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="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}"/>
<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="D" Modifiers="Control" Command="{s:Action DuplicateElement}" 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>
<b:Interaction.Behaviors>
<behaviors:TreeViewSelectionBehavior ExpandSelected="True" SelectedItem="{Binding SelectedTreeItem}" />

View File

@ -13,8 +13,8 @@ namespace Artemis.UI.Screens.ProfileEditor.ProfileTree
{
public class ProfileTreeViewModel : Conductor<FolderViewModel>, IProfileEditorPanelViewModel, IDropTarget
{
private readonly IProfileTreeVmFactory _profileTreeVmFactory;
private readonly IProfileEditorService _profileEditorService;
private readonly IProfileTreeVmFactory _profileTreeVmFactory;
private TreeItemViewModel _selectedTreeItem;
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)
{
DragDropType dragDropType = GetDragDropType(dropInfo);
@ -84,80 +151,6 @@ namespace Artemis.UI.Screens.ProfileEditor.ProfileTree
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
private void Subscribe()
@ -187,7 +180,9 @@ namespace Artemis.UI.Screens.ProfileEditor.ProfileTree
ActiveItem.UpdateProfileElements();
_updatingTree = false;
if (e.RenderProfileElement == null)
{
SelectedTreeItem = null;
}
else
{
TreeItemViewModel match = ActiveItem.GetAllChildren().FirstOrDefault(vm => vm.ProfileElement == e.RenderProfileElement);

View File

@ -13,17 +13,6 @@
<StackPanel Margin="-10" Background="Transparent">
<StackPanel.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.Icon>
<materialDesign:PackIcon Kind="CreateNewFolder" />
@ -34,6 +23,33 @@
<materialDesign:PackIcon Kind="LayersPlus" />
</MenuItem.Icon>
</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>
</StackPanel.ContextMenu>
<Grid Margin="10">

View File

@ -13,17 +13,28 @@
<StackPanel Margin="-10" Background="Transparent">
<StackPanel.ContextMenu>
<ContextMenu>
<MenuItem Header="Rename" Command="{s:Action RenameElement}">
<MenuItem Header="Duplicate" Command="{s:Action DuplicateElement}" InputGestureText="Ctrl+D">
<MenuItem.Icon>
<materialDesign:PackIcon Kind="RenameBox" />
<materialDesign:PackIcon Kind="ContentDuplicate" />
</MenuItem.Icon>
</MenuItem>
<MenuItem Header="Duplicate" Command="{s:Action DuplicateElement}">
<MenuItem Header="Copy" Command="{s:Action CopyElement}" InputGestureText="Ctrl+C">
<MenuItem.Icon>
<materialDesign:PackIcon Kind="ContentCopy" />
</MenuItem.Icon>
</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>
<materialDesign:PackIcon Kind="TrashCan" />
</MenuItem.Icon>

View File

@ -1,5 +1,4 @@
using System.Threading.Tasks;
using Artemis.Core;
using Artemis.Core;
using Artemis.Core.Services;
using Artemis.UI.Ninject.Factories;
using Artemis.UI.Shared.Services;
@ -8,8 +7,6 @@ namespace Artemis.UI.Screens.ProfileEditor.ProfileTree.TreeItem
{
public class LayerViewModel : TreeItemViewModel
{
private readonly IProfileEditorService _profileEditorService;
public LayerViewModel(ProfileElement layer,
IProfileEditorService profileEditorService,
IDialogService dialogService,
@ -18,15 +15,6 @@ namespace Artemis.UI.Screens.ProfileEditor.ProfileTree.TreeItem
ISurfaceService 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;

View File

@ -5,12 +5,11 @@ using System.Threading.Tasks;
using Artemis.Core;
using Artemis.Core.LayerBrushes;
using Artemis.Core.Services;
using Artemis.Storage.Entities.Profile;
using Artemis.UI.Exceptions;
using Artemis.UI.Extensions;
using Artemis.UI.Ninject.Factories;
using Artemis.UI.Screens.ProfileEditor.Dialogs;
using Artemis.UI.Shared.Services;
using Artemis.UI.Utilities;
using Stylet;
namespace Artemis.UI.Screens.ProfileEditor.ProfileTree.TreeItem
@ -178,36 +177,44 @@ namespace Artemis.UI.Screens.ProfileEditor.ProfileTree.TreeItem
if (!result)
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
TreeItemViewModel parent = (TreeItemViewModel) Parent;
ProfileElement.Parent?.RemoveChild(ProfileElement);
parent.RemoveExistingElement(this);
_profileEditorService.UpdateSelectedProfile();
_profileEditorService.ChangeSelectedProfileElement(null);
_profileEditorService.ChangeSelectedProfileElement(newSelection as RenderProfileElement);
}
public void DuplicateElement()
{
_profileEditorService.DuplicateProfileElement(ProfileElement);
}
public void CopyElement()
{
if (ProfileElement is Layer layer)
JsonClipboard.SetObject(layer.LayerEntity);
else if (ProfileElement is Folder folder)
JsonClipboard.SetObject(folder.FolderEntity);
_profileEditorService.CopyProfileElement(ProfileElement);
}
public void PasteElement()
{
object? clipboardObject = JsonClipboard.GetData();
if (clipboardObject is LayerEntity layerEntity)
{
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)
{
}
if (ProfileElement.Parent is Folder parent)
_profileEditorService.PasteProfileElement(parent, ProfileElement.Order);
}
public void UpdateProfileElements()
@ -217,19 +224,7 @@ namespace Artemis.UI.Screens.ProfileEditor.ProfileTree.TreeItem
foreach (TreeItemViewModel treeItemViewModel in toRemove)
Items.Remove(treeItemViewModel);
// Order the 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;
// Add missing children
List<TreeItemViewModel> newChildren = new List<TreeItemViewModel>();
foreach (ProfileElement profileElement in ProfileElement.Children.OrderBy(c => c.Order))
if (profileElement is Folder folder)
@ -243,15 +238,15 @@ namespace Artemis.UI.Screens.ProfileEditor.ProfileTree.TreeItem
newChildren.Add(_profileTreeVmFactory.LayerViewModel(layer));
}
if (!newChildren.Any())
return;
// Add the new children in one call, prevent extra UI events
foreach (TreeItemViewModel treeItemViewModel in newChildren)
{
treeItemViewModel.UpdateProfileElements();
Items.Add(treeItemViewModel);
}
// Order the children
((BindableCollection<TreeItemViewModel>) Items).Sort(i => i.ProfileElement.Order);
}
public void EnableToggled()

View File

@ -382,6 +382,7 @@ namespace Artemis.UI.Screens.ProfileEditor.Visualization
else
_previousSelectedLayer = null;
ApplyActiveProfile();
UpdateLedsDimStatus();
UpdateCanSelectEditTool();
}

View File

@ -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");
}
}
}