mirror of
https://github.com/Artemis-RGB/Artemis
synced 2025-12-13 05:48:35 +00:00
352 lines
13 KiB
C#
352 lines
13 KiB
C#
using System;
|
|
using System.Collections.Generic;
|
|
using System.Linq;
|
|
using System.Threading.Tasks;
|
|
using System.Windows.Input;
|
|
using Artemis.Core;
|
|
using Artemis.Core.LayerBrushes;
|
|
using Artemis.Core.Services;
|
|
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 Stylet;
|
|
|
|
namespace Artemis.UI.Screens.ProfileEditor.ProfileTree.TreeItem
|
|
{
|
|
public abstract class TreeItemViewModel : Conductor<TreeItemViewModel>.Collection.AllActive, IDisposable
|
|
{
|
|
private readonly IDialogService _dialogService;
|
|
private readonly ILayerBrushService _layerBrushService;
|
|
private readonly IProfileEditorService _profileEditorService;
|
|
private readonly IProfileTreeVmFactory _profileTreeVmFactory;
|
|
private readonly IRgbService _rgbService;
|
|
private ProfileElement _profileElement;
|
|
private string _brokenState;
|
|
|
|
protected TreeItemViewModel(ProfileElement profileElement,
|
|
IRgbService rgbService,
|
|
IProfileEditorService profileEditorService,
|
|
IDialogService dialogService,
|
|
IProfileTreeVmFactory profileTreeVmFactory,
|
|
ILayerBrushService layerBrushService)
|
|
{
|
|
_rgbService = rgbService;
|
|
_profileEditorService = profileEditorService;
|
|
_dialogService = dialogService;
|
|
_profileTreeVmFactory = profileTreeVmFactory;
|
|
_layerBrushService = layerBrushService;
|
|
|
|
ProfileElement = profileElement;
|
|
|
|
Subscribe();
|
|
UpdateProfileElements();
|
|
}
|
|
|
|
public ProfileElement ProfileElement
|
|
{
|
|
get => _profileElement;
|
|
set => SetAndNotify(ref _profileElement, value);
|
|
}
|
|
|
|
public string BrokenState
|
|
{
|
|
get => _brokenState;
|
|
set => SetAndNotify(ref _brokenState, value);
|
|
}
|
|
|
|
public bool CanPasteElement => _profileEditorService.GetCanPasteProfileElement();
|
|
|
|
public abstract bool SupportsChildren { get; }
|
|
public abstract bool IsExpanded { get; set; }
|
|
|
|
public List<TreeItemViewModel> GetAllChildren()
|
|
{
|
|
List<TreeItemViewModel> children = new();
|
|
foreach (TreeItemViewModel childFolder in Items)
|
|
{
|
|
// Add all children in this element
|
|
children.Add(childFolder);
|
|
// Add all children of children inside this element
|
|
children.AddRange(childFolder.GetAllChildren());
|
|
}
|
|
|
|
return children;
|
|
}
|
|
|
|
public void SetElementInFront(TreeItemViewModel source)
|
|
{
|
|
TreeItemViewModel sourceParent = (TreeItemViewModel) source.Parent;
|
|
TreeItemViewModel parent = (TreeItemViewModel) Parent;
|
|
|
|
// If the parents are different, remove the element from the old parent and add it to the new parent
|
|
if (source.Parent != Parent)
|
|
{
|
|
sourceParent.RemoveExistingElement(source);
|
|
parent.AddExistingElement(source);
|
|
}
|
|
|
|
parent.Unsubscribe();
|
|
parent.ProfileElement.RemoveChild(source.ProfileElement);
|
|
parent.ProfileElement.AddChild(source.ProfileElement, ProfileElement.Order);
|
|
parent.Subscribe();
|
|
|
|
parent.UpdateProfileElements();
|
|
}
|
|
|
|
public void SetElementBehind(TreeItemViewModel source)
|
|
{
|
|
TreeItemViewModel sourceParent = (TreeItemViewModel) source.Parent;
|
|
TreeItemViewModel parent = (TreeItemViewModel) Parent;
|
|
if (source.Parent != Parent)
|
|
{
|
|
sourceParent.RemoveExistingElement(source);
|
|
parent.AddExistingElement(source);
|
|
}
|
|
|
|
parent.Unsubscribe();
|
|
parent.ProfileElement.RemoveChild(source.ProfileElement);
|
|
parent.ProfileElement.AddChild(source.ProfileElement, ProfileElement.Order + 1);
|
|
parent.Subscribe();
|
|
|
|
parent.UpdateProfileElements();
|
|
}
|
|
|
|
public void RemoveExistingElement(TreeItemViewModel treeItem)
|
|
{
|
|
if (!SupportsChildren)
|
|
throw new ArtemisUIException("Cannot remove a child from a profile element of type " + ProfileElement.GetType().Name);
|
|
|
|
ProfileElement.RemoveChild(treeItem.ProfileElement);
|
|
treeItem.Parent = null;
|
|
treeItem.Dispose();
|
|
}
|
|
|
|
public void AddExistingElement(TreeItemViewModel treeItem)
|
|
{
|
|
if (!SupportsChildren)
|
|
throw new ArtemisUIException("Cannot add a child to a profile element of type " + ProfileElement.GetType().Name);
|
|
|
|
ProfileElement.AddChild(treeItem.ProfileElement);
|
|
treeItem.Parent = this;
|
|
}
|
|
|
|
public void AddFolder()
|
|
{
|
|
if (!SupportsChildren)
|
|
throw new ArtemisUIException("Cannot add a folder to a profile element of type " + ProfileElement.GetType().Name);
|
|
|
|
Folder folder = new(ProfileElement, "New folder");
|
|
ProfileElement.AddChild(folder, 0);
|
|
_profileEditorService.SaveSelectedProfileConfiguration();
|
|
}
|
|
|
|
public void AddLayer()
|
|
{
|
|
if (!SupportsChildren)
|
|
throw new ArtemisUIException("Cannot add a layer to a profile element of type " + ProfileElement.GetType().Name);
|
|
|
|
Layer layer = new(ProfileElement, "New layer");
|
|
ProfileElement.AddChild(layer, 0);
|
|
// Could be null if the default brush got disabled
|
|
LayerBrushDescriptor brush = _layerBrushService.GetDefaultLayerBrush();
|
|
if (brush != null)
|
|
layer.ChangeLayerBrush(brush);
|
|
|
|
layer.AddLeds(_rgbService.EnabledDevices.SelectMany(d => d.Leds));
|
|
_profileEditorService.SaveSelectedProfileConfiguration();
|
|
_profileEditorService.ChangeSelectedProfileElement(layer);
|
|
}
|
|
|
|
// ReSharper disable once UnusedMember.Global - Called from view
|
|
public async Task RenameElement()
|
|
{
|
|
object result = await _dialogService.ShowDialogAt<RenameViewModel>(
|
|
"ProfileTreeDialog",
|
|
new Dictionary<string, object>
|
|
{
|
|
{"subject", ProfileElement is Folder ? "folder" : "layer"},
|
|
{"currentName", ProfileElement.Name}
|
|
}
|
|
);
|
|
if (result is string newName)
|
|
{
|
|
ProfileElement.Name = newName;
|
|
_profileEditorService.SaveSelectedProfileConfiguration();
|
|
}
|
|
}
|
|
|
|
public void DeleteElement()
|
|
{
|
|
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;
|
|
}
|
|
_profileEditorService.ChangeSelectedProfileElement(newSelection as RenderProfileElement);
|
|
|
|
// Farewell, cruel world
|
|
TreeItemViewModel parent = (TreeItemViewModel) Parent;
|
|
ProfileElement.Parent?.RemoveChild(ProfileElement);
|
|
parent.RemoveExistingElement(this);
|
|
|
|
_profileEditorService.SaveSelectedProfileConfiguration();
|
|
}
|
|
|
|
public void DuplicateElement()
|
|
{
|
|
_profileEditorService.DuplicateProfileElement(ProfileElement);
|
|
}
|
|
|
|
public void CopyElement()
|
|
{
|
|
_profileEditorService.CopyProfileElement(ProfileElement);
|
|
}
|
|
|
|
public void PasteElement()
|
|
{
|
|
if (ProfileElement.Parent is Folder parent)
|
|
_profileEditorService.PasteProfileElement(parent, ProfileElement.Order - 1);
|
|
}
|
|
|
|
public void UpdateProfileElements()
|
|
{
|
|
// Remove VMs that are no longer a child
|
|
List<TreeItemViewModel> toRemove = Items.Where(c => c.ProfileElement.Parent != ProfileElement).ToList();
|
|
foreach (TreeItemViewModel treeItemViewModel in toRemove)
|
|
Items.Remove(treeItemViewModel);
|
|
|
|
// Add missing children
|
|
List<TreeItemViewModel> newChildren = new();
|
|
foreach (ProfileElement profileElement in ProfileElement.Children.OrderBy(c => c.Order))
|
|
if (profileElement is Folder folder)
|
|
{
|
|
if (Items.FirstOrDefault(p => p is FolderViewModel vm && vm.ProfileElement == folder) == null)
|
|
newChildren.Add(_profileTreeVmFactory.FolderViewModel(folder));
|
|
}
|
|
else if (profileElement is Layer layer)
|
|
{
|
|
if (Items.FirstOrDefault(p => p is LayerViewModel vm && vm.ProfileElement == layer) == null)
|
|
newChildren.Add(_profileTreeVmFactory.LayerViewModel(layer));
|
|
}
|
|
|
|
// 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 SuspendedToggled()
|
|
{
|
|
// If shift is held toggle focus state
|
|
if (Keyboard.IsKeyDown(Key.LeftShift) || Keyboard.IsKeyDown(Key.RightShift))
|
|
{
|
|
// Get all profile elements
|
|
List<ProfileElement> elements = ProfileElement.Profile.GetAllFolders().Cast<ProfileElement>().ToList();
|
|
elements.AddRange(ProfileElement.Profile.GetAllLayers().Cast<ProfileElement>().ToList());
|
|
|
|
// Separate out the targets of the focus state, the current profile element and all its parents
|
|
List<ProfileElement> targets = ProfileElement.GetAllFolders().Cast<ProfileElement>().ToList();
|
|
targets.AddRange(ProfileElement.GetAllLayers().Cast<ProfileElement>().ToList());
|
|
ProfileElement target = ProfileElement;
|
|
while (target != null)
|
|
{
|
|
targets.Add(target);
|
|
target = target.Parent;
|
|
}
|
|
|
|
// If any element is suspended, untoggle focus and unsuspend everything
|
|
if (elements.Except(targets).Any(e => e.Suspended))
|
|
{
|
|
foreach (ProfileElement profileElement in elements)
|
|
profileElement.Suspended = false;
|
|
}
|
|
// Otherwise suspend everything except the targets
|
|
else
|
|
{
|
|
foreach (ProfileElement profileElement in elements.Except(targets))
|
|
profileElement.Suspended = true;
|
|
foreach (ProfileElement profileElement in targets)
|
|
profileElement.Suspended = false;
|
|
}
|
|
}
|
|
|
|
_profileEditorService.SaveSelectedProfileConfiguration();
|
|
}
|
|
|
|
public void ContextMenuOpening(object sender, EventArgs e)
|
|
{
|
|
NotifyOfPropertyChange(nameof(CanPasteElement));
|
|
}
|
|
|
|
public abstract void UpdateBrokenState();
|
|
|
|
public async Task ShowBrokenStateExceptions()
|
|
{
|
|
List<IBreakableModel> broken = ProfileElement.GetBrokenHierarchy().Where(b => b.BrokenStateException != null).ToList();
|
|
|
|
foreach (IBreakableModel current in broken)
|
|
{
|
|
_dialogService.ShowExceptionDialog($"{current.BrokenDisplayName} - {current.BrokenState}", current.BrokenStateException!);
|
|
if (broken.Last() != current)
|
|
{
|
|
if (!await _dialogService.ShowConfirmDialog("Broken state", "Do you want to view the next exception?"))
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
|
|
private void Subscribe()
|
|
{
|
|
ProfileElement.ChildAdded += ProfileElementOnChildAdded;
|
|
ProfileElement.ChildRemoved += ProfileElementOnChildRemoved;
|
|
}
|
|
|
|
private void Unsubscribe()
|
|
{
|
|
ProfileElement.ChildAdded -= ProfileElementOnChildAdded;
|
|
ProfileElement.ChildRemoved -= ProfileElementOnChildRemoved;
|
|
}
|
|
|
|
private void ProfileElementOnChildRemoved(object sender, EventArgs e)
|
|
{
|
|
UpdateProfileElements();
|
|
}
|
|
|
|
private void ProfileElementOnChildAdded(object sender, EventArgs e)
|
|
{
|
|
UpdateProfileElements();
|
|
}
|
|
|
|
#region IDisposable
|
|
|
|
protected virtual void Dispose(bool disposing)
|
|
{
|
|
if (disposing) Unsubscribe();
|
|
}
|
|
|
|
/// <inheritdoc />
|
|
public void Dispose()
|
|
{
|
|
Dispose(true);
|
|
GC.SuppressFinalize(this);
|
|
}
|
|
|
|
#endregion
|
|
}
|
|
} |