diff --git a/src/Artemis.Core/Models/Profile/Layer.cs b/src/Artemis.Core/Models/Profile/Layer.cs index e456e3d64..f363c3d03 100644 --- a/src/Artemis.Core/Models/Profile/Layer.cs +++ b/src/Artemis.Core/Models/Profile/Layer.cs @@ -166,6 +166,7 @@ namespace Artemis.Core.Models.Profile { var layerEffectEntity = new LayerEffectEntity { + Id = layerEffect.EntityId, PluginGuid = layerEffect.PluginInfo.Guid, EffectType = layerEffect.GetType().Name, Name = layerEffect.Name, @@ -524,7 +525,7 @@ namespace Artemis.Core.Models.Profile var index = 0; foreach (var baseLayerEffect in LayerEffects.OrderBy(e => e.Order)) { - baseLayerEffect.UpdateOrder(index + 1); + baseLayerEffect.Order = Order = index + 1; index++; } diff --git a/src/Artemis.Core/Models/Profile/LayerPropertyGroup.cs b/src/Artemis.Core/Models/Profile/LayerPropertyGroup.cs index d26c7403f..599f1d704 100644 --- a/src/Artemis.Core/Models/Profile/LayerPropertyGroup.cs +++ b/src/Artemis.Core/Models/Profile/LayerPropertyGroup.cs @@ -78,6 +78,11 @@ namespace Artemis.Core.Models.Profile } } + /// + /// Gets or sets whether the group is expanded in the UI + /// + public bool IsExpanded { get; set; } + /// /// A list of all layer properties in this group /// @@ -107,21 +112,6 @@ namespace Artemis.Core.Models.Profile return _allLayerProperties; } - public void UpdateOrder(int oldOrder) - { - // Expanded state is tied to the path so save it before changing the path - var expanded = Layer.IsPropertyGroupExpanded(this); - Layer.SetPropertyGroupExpanded(this, false); - - Path = Path.Replace($"LayerEffect.{oldOrder}.", $"LayerEffect.{LayerEffect.Order}."); - // Restore the expanded state with the new path - Layer.SetPropertyGroupExpanded(this, expanded); - - // Update children - foreach (var layerPropertyGroup in LayerPropertyGroups) - layerPropertyGroup.UpdateOrder(oldOrder); - } - /// /// Called before properties are fully initialized to allow you to populate /// on the properties you want diff --git a/src/Artemis.Core/Plugins/LayerEffect/Abstract/BaseLayerEffect.cs b/src/Artemis.Core/Plugins/LayerEffect/Abstract/BaseLayerEffect.cs index 2c01cbc6d..bd884e2d1 100644 --- a/src/Artemis.Core/Plugins/LayerEffect/Abstract/BaseLayerEffect.cs +++ b/src/Artemis.Core/Plugins/LayerEffect/Abstract/BaseLayerEffect.cs @@ -12,6 +12,11 @@ namespace Artemis.Core.Plugins.LayerEffect.Abstract /// public abstract class BaseLayerEffect : PropertyChangedBase, IDisposable { + /// + /// Gets the unique ID of this effect + /// + public Guid EntityId { get; internal set; } + /// /// Gets the layer this effect is applied to /// @@ -36,7 +41,7 @@ namespace Artemis.Core.Plugins.LayerEffect.Abstract /// /// Gets the order in which this effect appears in the update loop and editor /// - public int Order { get; internal set; } + public int Order { get; set; } /// /// Gets the descriptor of this effect @@ -53,7 +58,7 @@ namespace Artemis.Core.Plugins.LayerEffect.Abstract /// public virtual LayerPropertyGroup BaseProperties => null; - internal string PropertyRootPath => $"LayerEffect.{Order}.{GetType().Name}."; + internal string PropertyRootPath => $"LayerEffect.{EntityId}.{GetType().Name}."; public void Dispose() { @@ -85,17 +90,7 @@ namespace Artemis.Core.Plugins.LayerEffect.Abstract /// Called after the layer of folder has been rendered /// public abstract void PostProcess(SKCanvas canvas, SKImageInfo canvasInfo, SKPath path, SKPaint paint); - - public void UpdateOrder(int newOrder) - { - if (newOrder == Order) - return; - - var oldOrder = Order; - Order = newOrder; - BaseProperties.UpdateOrder(oldOrder); - } - + internal void InternalPreProcess(SKCanvas canvas, SKImageInfo canvasInfo, SKPath path, SKPaint paint) { // Move the canvas to the top-left of the render path diff --git a/src/Artemis.Core/Services/Interfaces/ILayerService.cs b/src/Artemis.Core/Services/Interfaces/ILayerService.cs index 1cdc7fc4f..dae95c425 100644 --- a/src/Artemis.Core/Services/Interfaces/ILayerService.cs +++ b/src/Artemis.Core/Services/Interfaces/ILayerService.cs @@ -17,6 +17,12 @@ namespace Artemis.Core.Services.Interfaces /// Layer CreateLayer(Profile profile, ProfileElement parent, string name); + /// + /// Removes the currently active layer brush from the and deletes any settings + /// + /// The layer to remove the active brush from + void RemoveLayerBrush(Layer layer); + /// /// Instantiates and adds the described by the provided /// diff --git a/src/Artemis.Core/Services/LayerService.cs b/src/Artemis.Core/Services/LayerService.cs index 2c844a6a0..eb9967fa7 100644 --- a/src/Artemis.Core/Services/LayerService.cs +++ b/src/Artemis.Core/Services/LayerService.cs @@ -1,4 +1,5 @@ -using System.Linq; +using System; +using System.Linq; using Artemis.Core.Exceptions; using Artemis.Core.Models.Profile; using Artemis.Core.Plugins.Abstract; @@ -40,6 +41,12 @@ namespace Artemis.Core.Services return layer; } + public void RemoveLayerBrush(Layer layer) + { + layer.RemoveLayerBrush(); + layer.OnLayerBrushUpdated(); + } + public BaseLayerBrush InstantiateLayerBrush(Layer layer) { if (layer.LayerBrush != null) @@ -87,6 +94,7 @@ namespace Artemis.Core.Services continue; var effect = (BaseLayerEffect) _kernel.Get(descriptor.LayerEffectType); + effect.EntityId = layerEntityLayerEffect.Id; effect.Layer = layer; effect.Order = layerEntityLayerEffect.Order; effect.Name = layerEntityLayerEffect.Name; @@ -104,6 +112,7 @@ namespace Artemis.Core.Services public BaseLayerEffect AddLayerEffect(Layer layer, LayerEffectDescriptor layerEffectDescriptor) { var effect = (BaseLayerEffect) _kernel.Get(layerEffectDescriptor.LayerEffectType); + effect.EntityId = Guid.NewGuid(); effect.Layer = layer; effect.Order = layer.LayerEffects.Count + 1; effect.Descriptor = layerEffectDescriptor; @@ -113,7 +122,7 @@ namespace Artemis.Core.Services layer.AddLayerEffect(effect); _logger.Debug("Added layer effect with root path {rootPath}", effect.PropertyRootPath); - + layer.OnLayerEffectsUpdated(); return effect; } diff --git a/src/Artemis.Storage/Entities/Profile/LayerEffectEntity.cs b/src/Artemis.Storage/Entities/Profile/LayerEffectEntity.cs index ce7239258..85d3d1a34 100644 --- a/src/Artemis.Storage/Entities/Profile/LayerEffectEntity.cs +++ b/src/Artemis.Storage/Entities/Profile/LayerEffectEntity.cs @@ -4,6 +4,7 @@ namespace Artemis.Storage.Entities.Profile { public class LayerEffectEntity { + public Guid Id { get; set; } public Guid PluginGuid { get; set; } public string EffectType { get; set; } public string Name { get; set; } diff --git a/src/Artemis.UI.Shared/Controls/DraggableFloat.xaml.cs b/src/Artemis.UI.Shared/Controls/DraggableFloat.xaml.cs index 3e5ec23a6..7d2ba3c10 100644 --- a/src/Artemis.UI.Shared/Controls/DraggableFloat.xaml.cs +++ b/src/Artemis.UI.Shared/Controls/DraggableFloat.xaml.cs @@ -80,6 +80,8 @@ namespace Artemis.UI.Shared.Controls private void InputMouseDown(object sender, MouseButtonEventArgs e) { + e.Handled = true; + _startValue = Value; ((IInputElement) sender).CaptureMouse(); _mouseDragStartPoint = e.GetPosition((IInputElement) sender); @@ -87,6 +89,8 @@ namespace Artemis.UI.Shared.Controls private void InputMouseUp(object sender, MouseButtonEventArgs e) { + e.Handled = true; + var position = e.GetPosition((IInputElement) sender); if (position == _mouseDragStartPoint) DisplayInput(); @@ -104,6 +108,8 @@ namespace Artemis.UI.Shared.Controls if (e.LeftButton != MouseButtonState.Pressed) return; + e.Handled = true; + if (!_calledDragStarted) { OnDragStarted(); diff --git a/src/Artemis.UI.Shared/Utilities/EnumUtilities.cs b/src/Artemis.UI.Shared/Utilities/EnumUtilities.cs index 4fe404335..720b6042b 100644 --- a/src/Artemis.UI.Shared/Utilities/EnumUtilities.cs +++ b/src/Artemis.UI.Shared/Utilities/EnumUtilities.cs @@ -28,6 +28,11 @@ namespace Artemis.UI.Shared.Utilities return Enum.GetValues(t).Cast().Select(e => new ValueDescription {Value = e, Description = e.Humanize()}).ToList(); } + + public static string HumanizeValue(Enum value) + { + return value.Humanize(); + } } public class ValueDescription diff --git a/src/Artemis.UI/PropertyInput/BrushPropertyInputViewModel.cs b/src/Artemis.UI/PropertyInput/BrushPropertyInputViewModel.cs index b5784fafb..b62721c9a 100644 --- a/src/Artemis.UI/PropertyInput/BrushPropertyInputViewModel.cs +++ b/src/Artemis.UI/PropertyInput/BrushPropertyInputViewModel.cs @@ -52,6 +52,7 @@ namespace Artemis.UI.PropertyInput protected override void OnInputValueApplied() { + _layerService.RemoveLayerBrush(LayerProperty.Layer); _layerService.InstantiateLayerBrush(LayerProperty.Layer); } diff --git a/src/Artemis.UI/Screens/Module/ProfileEditor/LayerProperties/LayerPropertiesViewModel.cs b/src/Artemis.UI/Screens/Module/ProfileEditor/LayerProperties/LayerPropertiesViewModel.cs index 0b3f878bf..73fcdfbd2 100644 --- a/src/Artemis.UI/Screens/Module/ProfileEditor/LayerProperties/LayerPropertiesViewModel.cs +++ b/src/Artemis.UI/Screens/Module/ProfileEditor/LayerProperties/LayerPropertiesViewModel.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Diagnostics; using System.Linq; using System.Windows; using System.Windows.Input; @@ -14,13 +15,17 @@ using Artemis.UI.Ninject.Factories; using Artemis.UI.Screens.Module.ProfileEditor.LayerProperties.LayerEffects; using Artemis.UI.Screens.Module.ProfileEditor.LayerProperties.Timeline; using Artemis.UI.Screens.Module.ProfileEditor.LayerProperties.Tree; +using Artemis.UI.Screens.Module.ProfileEditor.ProfileTree; +using Artemis.UI.Screens.Module.ProfileEditor.ProfileTree.TreeItem; using Artemis.UI.Shared.Events; using Artemis.UI.Shared.Services.Interfaces; +using GongSolutions.Wpf.DragDrop; using Stylet; +using static Artemis.UI.Screens.Module.ProfileEditor.LayerProperties.LayerPropertyGroupViewModel.ViewModelType; namespace Artemis.UI.Screens.Module.ProfileEditor.LayerProperties { - public class LayerPropertiesViewModel : ProfileEditorPanelViewModel + public class LayerPropertiesViewModel : ProfileEditorPanelViewModel, IDropTarget { private readonly ILayerPropertyVmFactory _layerPropertyVmFactory; private LayerPropertyGroupViewModel _brushPropertyGroup; @@ -215,6 +220,7 @@ namespace Artemis.UI.Screens.Module.ProfileEditor.LayerProperties LayerPropertyGroups.Add(_brushPropertyGroup); } + SortProperties(); TimelineViewModel.UpdateKeyframes(); } @@ -244,9 +250,100 @@ namespace Artemis.UI.Screens.Module.ProfileEditor.LayerProperties LayerPropertyGroups.Add(_layerPropertyVmFactory.LayerPropertyGroupViewModel(layerEffect.BaseProperties, brushDescription)); } + SortProperties(); TimelineViewModel.UpdateKeyframes(); } + private void SortProperties() + { + // Get all non-effect properties + var nonEffectProperties = LayerPropertyGroups.Where(l => l.GroupType != LayerEffectRoot).ToList(); + // Order the effects + var effectProperties = LayerPropertyGroups.Where(l => l.GroupType == LayerEffectRoot).OrderBy(l => l.LayerPropertyGroup.LayerEffect.Order).ToList(); + + // Put the non-effect properties in front + for (var index = 0; index < nonEffectProperties.Count; index++) + { + var layerPropertyGroupViewModel = nonEffectProperties[index]; + LayerPropertyGroups.Move(LayerPropertyGroups.IndexOf(layerPropertyGroupViewModel), index); + } + + // Put the effect properties after, sorted by their order + for (var index = 0; index < effectProperties.Count; index++) + { + var layerPropertyGroupViewModel = effectProperties[index]; + LayerPropertyGroups.Move(LayerPropertyGroups.IndexOf(layerPropertyGroupViewModel), index + nonEffectProperties.Count); + } + } + + #endregion + + #region Drag and drop + + public void DragOver(IDropInfo dropInfo) + { + // Workaround for https://github.com/punker76/gong-wpf-dragdrop/issues/344 + // Luckily we know the index can never be 1 so it's an easy enough fix + if (dropInfo.InsertIndex == 1) + return; + + var source = dropInfo.Data as LayerPropertyGroupViewModel; + var target = dropInfo.TargetItem as LayerPropertyGroupViewModel; + + if (source == target || target?.GroupType != LayerEffectRoot || source?.GroupType != LayerEffectRoot) + return; + + dropInfo.DropTargetAdorner = DropTargetAdorners.Insert; + dropInfo.Effects = DragDropEffects.Move; + } + + public void Drop(IDropInfo dropInfo) + { + // Workaround for https://github.com/punker76/gong-wpf-dragdrop/issues/344 + // Luckily we know the index can never be 1 so it's an easy enough fix + if (dropInfo.InsertIndex == 1) + return; + + var source = dropInfo.Data as LayerPropertyGroupViewModel; + var target = dropInfo.TargetItem as LayerPropertyGroupViewModel; + + if (source == target || target?.GroupType != LayerEffectRoot || source?.GroupType != LayerEffectRoot) + return; + + if (dropInfo.InsertPosition == RelativeInsertPosition.BeforeTargetItem) + MoveBefore(source, target); + else if (dropInfo.InsertPosition == RelativeInsertPosition.AfterTargetItem) + MoveAfter(source, target); + + ApplyCurrentEffectsOrder(); + ProfileEditorService.UpdateSelectedProfile(true); + } + + private void MoveBefore(LayerPropertyGroupViewModel source, LayerPropertyGroupViewModel target) + { + if (LayerPropertyGroups.IndexOf(target) == LayerPropertyGroups.IndexOf(source) + 1) + return; + + LayerPropertyGroups.Move(LayerPropertyGroups.IndexOf(source), LayerPropertyGroups.IndexOf(target)); + } + + private void MoveAfter(LayerPropertyGroupViewModel source, LayerPropertyGroupViewModel target) + { + LayerPropertyGroups.Remove(source); + LayerPropertyGroups.Insert(LayerPropertyGroups.IndexOf(target) + 1, source); + } + + private void ApplyCurrentEffectsOrder() + { + var order = 1; + foreach (var groupViewModel in LayerPropertyGroups.Where(p => p.GroupType == LayerEffectRoot)) + { + + groupViewModel.UpdateOrder(order); + order++; + } + } + #endregion #region Controls diff --git a/src/Artemis.UI/Screens/Module/ProfileEditor/LayerProperties/LayerPropertyGroupViewModel.cs b/src/Artemis.UI/Screens/Module/ProfileEditor/LayerProperties/LayerPropertyGroupViewModel.cs index db8dfd445..d1e45882e 100644 --- a/src/Artemis.UI/Screens/Module/ProfileEditor/LayerProperties/LayerPropertyGroupViewModel.cs +++ b/src/Artemis.UI/Screens/Module/ProfileEditor/LayerProperties/LayerPropertyGroupViewModel.cs @@ -155,5 +155,11 @@ namespace Artemis.UI.Screens.Module.ProfileEditor.LayerProperties { NotifyOfPropertyChange(nameof(IsVisible)); } + + public void UpdateOrder(int order) + { + LayerPropertyGroup.LayerEffect.Order = order; + NotifyOfPropertyChange(nameof(IsExpanded)); + } } } \ No newline at end of file diff --git a/src/Artemis.UI/Screens/Module/ProfileEditor/LayerProperties/Tree/TreePropertyGroupView.xaml b/src/Artemis.UI/Screens/Module/ProfileEditor/LayerProperties/Tree/TreePropertyGroupView.xaml index 8604092ba..e8a33bc36 100644 --- a/src/Artemis.UI/Screens/Module/ProfileEditor/LayerProperties/Tree/TreePropertyGroupView.xaml +++ b/src/Artemis.UI/Screens/Module/ProfileEditor/LayerProperties/Tree/TreePropertyGroupView.xaml @@ -7,6 +7,7 @@ xmlns:materialDesign="http://materialdesigninxaml.net/winfx/xaml/themes" xmlns:s="https://github.com/canton7/Stylet" xmlns:converters="clr-namespace:Artemis.UI.Converters" + xmlns:dd="urn:gong-wpf-dragdrop" mc:Ignorable="d" d:DataContext="{d:DesignInstance local:TreePropertyGroupViewModel}" d:DesignHeight="450" d:DesignWidth="800"> @@ -16,6 +17,7 @@ - + - + \ No newline at end of file diff --git a/src/Artemis.UI/Screens/Module/ProfileEditor/LayerProperties/Tree/TreeView.xaml b/src/Artemis.UI/Screens/Module/ProfileEditor/LayerProperties/Tree/TreeView.xaml index bd84c6cb5..f96b3d4a2 100644 --- a/src/Artemis.UI/Screens/Module/ProfileEditor/LayerProperties/Tree/TreeView.xaml +++ b/src/Artemis.UI/Screens/Module/ProfileEditor/LayerProperties/Tree/TreeView.xaml @@ -8,6 +8,7 @@ xmlns:converters="clr-namespace:Artemis.UI.Converters" xmlns:abstract="clr-namespace:Artemis.UI.Screens.Module.ProfileEditor.LayerProperties.Abstract" xmlns:layerProperties="clr-namespace:Artemis.UI.Screens.Module.ProfileEditor.LayerProperties" + xmlns:dd="urn:gong-wpf-dragdrop" mc:Ignorable="d" d:DesignHeight="450" d:DesignWidth="800" d:DataContext="{d:DesignInstance {x:Type local:TreeViewModel}}"> @@ -87,6 +88,9 @@ HorizontalContentAlignment="Stretch" Background="{DynamicResource MaterialDesignToolBarBackground}" PreviewMouseWheel="{s:Action PropertyTreePreviewMouseWheel}" + dd:DragDrop.IsDragSource="True" + dd:DragDrop.IsDropTarget="True" + dd:DragDrop.DropHandler="{Binding LayerPropertiesViewModel}" Margin="0 -1">