Implemented layer LED assignment

Added a centralised ProfileEditorSurface for communication between VMs
Prefixed Surface, Device and Led with Artemis to differentiate them better
This commit is contained in:
Robert committed 2019-11-29 17:37:22 +01:00
1 parent c7a5532c34
commit 16decaae42
50 files changed
+556 -662

No files matched your search

@@ -0,0 +1,67 @@
<UserControl x:Class="Artemis.UI.Screens.Module.ProfileEditor.ProfileTree.ProfileTreeView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:materialDesign="http://materialdesigninxaml.net/winfx/xaml/themes"
xmlns:s="https://github.com/canton7/Stylet"
xmlns:dd="urn:gong-wpf-dragdrop"
xmlns:i="http://schemas.microsoft.com/expression/2010/interactivity"
xmlns:utilities="clr-namespace:Artemis.UI.Utilities"
xmlns:profileTree="clr-namespace:Artemis.UI.Screens.Module.ProfileEditor.ProfileTree"
xmlns:treeItem="clr-namespace:Artemis.UI.Screens.Module.ProfileEditor.ProfileTree.TreeItem"
mc:Ignorable="d"
d:DesignHeight="450" d:DesignWidth="800"
d:DataContext="{d:DesignInstance {x:Type profileTree:ProfileTreeViewModel}}">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="*" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<StackPanel>
<TextBlock Style="{StaticResource MaterialDesignSubheadingTextBlock}" Margin="10 5 0 -4">
Profile elements
</TextBlock>
<Separator Style="{StaticResource MaterialDesignDarkSeparator}" Margin="8 0" />
</StackPanel>
<TreeView Grid.Row="1"
ItemsSource="{Binding RootFolder.Children}"
HorizontalContentAlignment="Stretch"
dd:DragDrop.IsDragSource="True"
dd:DragDrop.IsDropTarget="True"
dd:DragDrop.DropHandler="{Binding}">
<i:Interaction.Behaviors>
<utilities:BindableSelectedItemBehavior SelectedItem="{Binding SelectedTreeItem, Mode=TwoWay}" />
</i:Interaction.Behaviors>
<TreeView.Resources>
<HierarchicalDataTemplate DataType="{x:Type treeItem:FolderViewModel}" ItemsSource="{Binding Children}">
<ContentControl s:View.Model="{Binding}" />
</HierarchicalDataTemplate>
<HierarchicalDataTemplate DataType="{x:Type treeItem:LayerViewModel}" ItemsSource="{Binding Children}">
<ContentControl s:View.Model="{Binding}" />
</HierarchicalDataTemplate>
</TreeView.Resources>
</TreeView>
<StackPanel HorizontalAlignment="Right" Grid.Row="2" Orientation="Horizontal" Margin="8">
<Button Style="{StaticResource MaterialDesignToolButton}"
Width="30"
Padding="2 0 2 0"
materialDesign:RippleAssist.IsCentered="True"
ToolTip="Add new folder to root"
Command="{s:Action AddFolder}">
<materialDesign:PackIcon Kind="CreateNewFolder" />
</Button>
<Button Style="{StaticResource MaterialDesignToolButton}"
Width="30"
Padding="2 0 2 0"
materialDesign:RippleAssist.IsCentered="True"
ToolTip="Add new layer to root"
Command="{s:Action AddLayer}">
<materialDesign:PackIcon Kind="LayersPlus" />
</Button>
</StackPanel>
</Grid>
</UserControl>
@@ -0,0 +1,157 @@
using System;
using System.Linq;
using System.Windows;
using Artemis.Core.Models.Profile;
using Artemis.UI.Screens.Module.ProfileEditor.ProfileTree.TreeItem;
using Artemis.UI.Services.Interfaces;
using GongSolutions.Wpf.DragDrop;
namespace Artemis.UI.Screens.Module.ProfileEditor.ProfileTree
{
public class ProfileTreeViewModel : ProfileEditorPanelViewModel, IDropTarget
{
private readonly IProfileEditorService _profileEditorService;
private TreeItemViewModel _selectedTreeItem;
public ProfileTreeViewModel(IProfileEditorService profileEditorService)
{
_profileEditorService = profileEditorService;
CreateRootFolderViewModel();
_profileEditorService.SelectedProfileChanged += OnSelectedProfileChanged;
_profileEditorService.SelectedProfileElementChanged += OnSelectedElementChanged;
}
public FolderViewModel RootFolder { get; set; }
public TreeItemViewModel SelectedTreeItem
{
get => _selectedTreeItem;
set
{
_selectedTreeItem = value;
_profileEditorService.ChangeSelectedProfileElement(value?.ProfileElement);
}
}
public void DragOver(IDropInfo dropInfo)
{
var dragDropType = GetDragDropType(dropInfo);
switch (dragDropType)
{
case DragDropType.Add:
dropInfo.DropTargetAdorner = DropTargetAdorners.Highlight;
dropInfo.Effects = DragDropEffects.Move;
break;
case DragDropType.InsertBefore:
case DragDropType.InsertAfter:
dropInfo.DropTargetAdorner = DropTargetAdorners.Insert;
dropInfo.Effects = DragDropEffects.Move;
break;
}
}
public void Drop(IDropInfo dropInfo)
{
var source = (TreeItemViewModel) dropInfo.Data;
var target = (TreeItemViewModel) dropInfo.TargetItem;
var dragDropType = GetDragDropType(dropInfo);
switch (dragDropType)
{
case DragDropType.Add:
source.Parent.RemoveExistingElement(source);
target.AddExistingElement(source);
break;
case DragDropType.InsertBefore:
target.SetElementInFront(source);
break;
case DragDropType.InsertAfter:
target.SetElementBehind(source);
break;
}
_profileEditorService.UpdateSelectedProfile();
}
// ReSharper disable once UnusedMember.Global - Called from view
public void AddFolder()
{
RootFolder?.AddFolder();
}
// ReSharper disable once UnusedMember.Global - Called from view
public void AddLayer()
{
RootFolder?.AddLayer();
}
private void CreateRootFolderViewModel()
{
var firstChild = _profileEditorService.SelectedProfile?.Children?.FirstOrDefault();
if (!(firstChild is Folder folder))
{
RootFolder = null;
return;
}
RootFolder = new FolderViewModel(null, folder, _profileEditorService);
}
private static DragDropType GetDragDropType(IDropInfo dropInfo)
{
var source = (TreeItemViewModel) dropInfo.Data;
var target = (TreeItemViewModel) dropInfo.TargetItem;
if (source == target)
return DragDropType.None;
var parent = target;
while (parent != null)
{
if (parent == source)
return DragDropType.None;
parent = parent.Parent;
}
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 OnSelectedElementChanged(object sender, EventArgs e)
{
var vms = RootFolder.GetAllChildren();
// Don't set it using the setter or that will trigger the event again
_selectedTreeItem = vms.FirstOrDefault(vm => vm.ProfileElement == _profileEditorService.SelectedProfileElement);
NotifyOfPropertyChange(() => SelectedTreeItem);
}
private void OnSelectedProfileChanged(object sender, EventArgs e)
{
CreateRootFolderViewModel();
}
#endregion
}
public enum DragDropType
{
None,
Add,
InsertBefore,
InsertAfter
}
}
@@ -0,0 +1,46 @@
<UserControl x:Class="Artemis.UI.Screens.Module.ProfileEditor.ProfileTree.TreeItem.FolderView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:s="https://github.com/canton7/Stylet"
xmlns:materialDesign="http://materialdesigninxaml.net/winfx/xaml/themes"
xmlns:treeItem="clr-namespace:Artemis.UI.Screens.Module.ProfileEditor.ProfileTree.TreeItem"
mc:Ignorable="d"
d:DesignHeight="450" d:DesignWidth="800"
d:DataContext="{d:DesignInstance {x:Type treeItem:FolderViewModel}}">
<!-- Capture clicks on full tree view item -->
<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" />
</MenuItem.Icon>
</MenuItem>
<MenuItem Header="Add new layer" Command="{s:Action AddLayer}">
<MenuItem.Icon>
<materialDesign:PackIcon Kind="LayersPlus" />
</MenuItem.Icon>
</MenuItem>
</ContextMenu>
</StackPanel.ContextMenu>
<StackPanel Orientation="Horizontal" Margin="10">
<materialDesign:PackIcon Kind="Folder" Visibility="{Binding IsExpanded, Converter={x:Static s:BoolToVisibilityConverter.InverseInstance}, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type TreeViewItem}}}"/>
<materialDesign:PackIcon Kind="FolderOpen" Visibility="{Binding IsExpanded, Converter={x:Static s:BoolToVisibilityConverter.Instance}, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type TreeViewItem}}}"/>
<TextBlock Text="{Binding ProfileElement.Name}" Margin="10 0 0 0" />
</StackPanel>
</StackPanel>
</UserControl>
@@ -0,0 +1,14 @@
using Artemis.Core.Models.Profile.Abstract;
using Artemis.UI.Services.Interfaces;
namespace Artemis.UI.Screens.Module.ProfileEditor.ProfileTree.TreeItem
{
public class FolderViewModel : TreeItemViewModel
{
public FolderViewModel(TreeItemViewModel parent, ProfileElement folder, IProfileEditorService profileEditorService) : base(parent, folder, profileEditorService)
{
}
public override bool SupportsChildren => true;
}
}
@@ -0,0 +1,33 @@
<UserControl x:Class="Artemis.UI.Screens.Module.ProfileEditor.ProfileTree.TreeItem.LayerView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:s="https://github.com/canton7/Stylet"
xmlns:materialDesign="http://materialdesigninxaml.net/winfx/xaml/themes"
xmlns:treeItem="clr-namespace:Artemis.UI.Screens.Module.ProfileEditor.ProfileTree.TreeItem"
mc:Ignorable="d"
d:DesignHeight="450" d:DesignWidth="800"
d:DataContext="{d:DesignInstance {x:Type treeItem:LayerViewModel}}">
<!-- Capture clicks on full tree view item -->
<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>
</ContextMenu>
</StackPanel.ContextMenu>
<StackPanel Orientation="Horizontal" Margin="10">
<materialDesign:PackIcon Kind="Layers" />
<TextBlock Text="{Binding ProfileElement.Name}" Margin="10 0 0 0" />
</StackPanel>
</StackPanel>
</UserControl>
@@ -0,0 +1,14 @@
using Artemis.Core.Models.Profile.Abstract;
using Artemis.UI.Services.Interfaces;
namespace Artemis.UI.Screens.Module.ProfileEditor.ProfileTree.TreeItem
{
public class LayerViewModel : TreeItemViewModel
{
public LayerViewModel(TreeItemViewModel parent, ProfileElement layer, IProfileEditorService profileEditorService) : base(parent, layer, profileEditorService)
{
}
public override bool SupportsChildren => false;
}
}
@@ -0,0 +1,178 @@
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Artemis.Core.Models.Profile;
using Artemis.Core.Models.Profile.Abstract;
using Artemis.UI.Exceptions;
using Artemis.UI.Services.Interfaces;
using Stylet;
namespace Artemis.UI.Screens.Module.ProfileEditor.ProfileTree.TreeItem
{
public abstract class TreeItemViewModel : PropertyChangedBase
{
protected TreeItemViewModel(TreeItemViewModel parent, ProfileElement profileElement, IProfileEditorService profileEditorService)
{
Parent = parent;
ProfileElement = profileElement;
ProfileEditorService = profileEditorService;
Children = new BindableCollection<TreeItemViewModel>();
UpdateProfileElements();
}
public TreeItemViewModel Parent { get; set; }
public ProfileElement ProfileElement { get; set; }
public IProfileEditorService ProfileEditorService { get; set; }
public abstract bool SupportsChildren { get; }
public BindableCollection<TreeItemViewModel> Children { get; set; }
public List<TreeItemViewModel> GetAllChildren()
{
var children = new List<TreeItemViewModel>();
foreach (var childFolder in Children)
{
// 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)
{
if (source.Parent != Parent)
{
source.Parent.RemoveExistingElement(source);
Parent.AddExistingElement(source);
}
Parent.ProfileElement.RemoveChild(source.ProfileElement);
Parent.ProfileElement.AddChild(source.ProfileElement, ProfileElement.Order);
Parent.UpdateProfileElements();
}
public void SetElementBehind(TreeItemViewModel source)
{
if (source.Parent != Parent)
{
source.Parent.RemoveExistingElement(source);
Parent.AddExistingElement(source);
}
Parent.ProfileElement.RemoveChild(source.ProfileElement);
Parent.ProfileElement.AddChild(source.ProfileElement, ProfileElement.Order + 1);
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);
Children.Remove(treeItem);
treeItem.Parent = null;
}
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);
Children.Add(treeItem);
treeItem.Parent = this;
}
public void AddFolder()
{
if (!SupportsChildren)
throw new ArtemisUIException("Cannot add a folder to a profile element of type " + ProfileElement.GetType().Name);
ProfileElement.AddChild(new Folder(ProfileElement.Profile, ProfileElement, "New folder"));
UpdateProfileElements();
ProfileEditorService.UpdateSelectedProfile();
}
public void AddLayer()
{
if (!SupportsChildren)
throw new ArtemisUIException("Cannot add a layer to a profile element of type " + ProfileElement.GetType().Name);
ProfileElement.AddChild(new Layer(ProfileElement.Profile, ProfileElement, "New layer"));
UpdateProfileElements();
ProfileEditorService.UpdateSelectedProfile();
}
// ReSharper disable once UnusedMember.Global - Called from view
public async Task RenameElement()
{
// var result = await ProfileEditorService.DialogService.ShowDialog<ProfileElementRenameViewModel>(
// new Dictionary<string, object> {{"profileElement", ProfileElement}}
// );
// if (result is string newName)
// {
// ProfileElement.Name = newName;
// ProfileEditorService.UpdateSelectedProfile();
// }
}
// ReSharper disable once UnusedMember.Global - Called from view
public async Task DeleteElement()
{
// var result = await ProfileEditorService.DialogService.ShowConfirmDialog(
// "Delete profile element",
// "Are you sure you want to delete this element? This cannot be undone."
// );
//
// if (!result)
// return;
//
// // Farewell, cruel world
// var parent = Parent;
// ProfileElement.Parent.RemoveChild(ProfileElement);
// parent.RemoveExistingElement(this);
// parent.UpdateProfileElements();
//
// ProfileEditorService.UpdateSelectedProfile();
}
private void UpdateProfileElements()
{
// Order the children
var vmsList = Children.OrderBy(v => v.ProfileElement.Order).ToList();
for (var index = 0; index < vmsList.Count; index++)
{
var profileElementViewModel = vmsList[index];
Children.Move(Children.IndexOf(profileElementViewModel), index);
}
// Ensure every child element has an up-to-date VM
if (ProfileElement.Children != null)
{
foreach (var profileElement in ProfileElement.Children.OrderBy(c => c.Order))
{
TreeItemViewModel existing = null;
if (profileElement is Folder folder)
{
existing = Children.FirstOrDefault(p => p is FolderViewModel vm && vm.ProfileElement == folder);
if (existing == null)
Children.Add(new FolderViewModel(this, folder, ProfileEditorService));
}
else if (profileElement is Layer layer)
{
existing = Children.FirstOrDefault(p => p is LayerViewModel vm && vm.ProfileElement == layer);
if (existing == null)
Children.Add(new LayerViewModel(this, layer, ProfileEditorService));
}
existing?.UpdateProfileElements();
}
}
}
}
}