diff --git a/Artemis/Artemis/Artemis.csproj b/Artemis/Artemis/Artemis.csproj index 33e0b522d..67a903f4e 100644 --- a/Artemis/Artemis/Artemis.csproj +++ b/Artemis/Artemis/Artemis.csproj @@ -148,6 +148,10 @@ ..\packages\CUE.NET.1.0.2.2\lib\net45\CUE.NET.dll True + + ..\packages\gong-wpf-dragdrop.0.1.4.3\lib\net40\GongSolutions.Wpf.DragDrop.dll + True + ..\packages\Hardcodet.NotifyIcon.Wpf.1.0.8\lib\net451\Hardcodet.Wpf.TaskbarNotification.dll True @@ -418,6 +422,8 @@ True Offsets.settings + + diff --git a/Artemis/Artemis/Models/Profiles/LayerModel.cs b/Artemis/Artemis/Models/Profiles/LayerModel.cs index 2952d7e62..149dd7819 100644 --- a/Artemis/Artemis/Models/Profiles/LayerModel.cs +++ b/Artemis/Artemis/Models/Profiles/LayerModel.cs @@ -149,91 +149,7 @@ namespace Artemis.Models.Profiles Brush = new SolidColorBrush(ColorHelpers.GetRandomRainbowMediaColor()) }; } - - public void Reorder(LayerModel selectedLayer, bool moveUp) - { - // Fix the sorting just in case - FixOrder(); - - // Possibly remove selectedLayer from a folder - if (selectedLayer.Parent?.LayerType == LayerType.Folder) - { - var parent = selectedLayer.Parent; - var siblings = parent.Children; - if ((selectedLayer == siblings.FirstOrDefault() && moveUp) || - (selectedLayer == siblings.LastOrDefault() && !moveUp)) - { - // If selectedLayer is on the edge of a folder and moved off of it, remove it from the folder - parent.Children.Remove(selectedLayer); - if (parent.Parent != null) - parent.Parent.Children.Add(selectedLayer); - else - parent.Profile.Layers.Add(selectedLayer); - - if (moveUp) - selectedLayer.Order = parent.Order - 1; - else - selectedLayer.Order = parent.Order + 1; - - return; - } - } - - int newOrder; - if (moveUp) - newOrder = selectedLayer.Order - 1; - else - newOrder = selectedLayer.Order + 1; - - var target = Children.FirstOrDefault(l => l.Order == newOrder); - if (target == null) - return; - - ApplyReorder(selectedLayer, target, newOrder, moveUp); - } - - - public static void ApplyReorder(LayerModel selectedLayer, LayerModel target, int newOrder, bool moveUp) - { - if (target.LayerType == LayerType.Folder) - { - if (selectedLayer.Parent == null) - selectedLayer.Profile.Layers.Remove(selectedLayer); - else - selectedLayer.Parent.Children.Remove(selectedLayer); - - target.Children.Add(selectedLayer); - selectedLayer.Parent = target; - - if (moveUp) - { - var parentTarget = target.Children.OrderBy(c => c.Order).LastOrDefault(); - if (parentTarget != null) - { - parentTarget.Order--; - selectedLayer.Order = parentTarget.Order + 1; - } - else - selectedLayer.Order = 1; - } - else - { - var parentTarget = target.Children.OrderBy(c => c.Order).FirstOrDefault(); - if (parentTarget != null) - { - parentTarget.Order++; - selectedLayer.Order = parentTarget.Order - 1; - } - else - selectedLayer.Order = 1; - } - target.FixOrder(); - return; - } - target.Order = selectedLayer.Order; - selectedLayer.Order = newOrder; - } - + public void FixOrder() { Children.Sort(l => l.Order); diff --git a/Artemis/Artemis/Models/Profiles/ProfileModel.cs b/Artemis/Artemis/Models/Profiles/ProfileModel.cs index 2537c3d8e..7e4325c94 100644 --- a/Artemis/Artemis/Models/Profiles/ProfileModel.cs +++ b/Artemis/Artemis/Models/Profiles/ProfileModel.cs @@ -86,23 +86,6 @@ namespace Artemis.Models.Profiles return layer; } - public void Reorder(LayerModel selectedLayer, bool moveUp) - { - FixOrder(); - - int newOrder; - if (moveUp) - newOrder = selectedLayer.Order - 1; - else - newOrder = selectedLayer.Order + 1; - - var target = Layers.FirstOrDefault(l => l.Order == newOrder); - if (target == null) - return; - - LayerModel.ApplyReorder(selectedLayer, target, newOrder, moveUp); - } - public void FixOrder() { Layers.Sort(l => l.Order); diff --git a/Artemis/Artemis/Styles/DropTargetAdorners/DropTargetMetroHighlightAdorner.cs b/Artemis/Artemis/Styles/DropTargetAdorners/DropTargetMetroHighlightAdorner.cs new file mode 100644 index 000000000..2b5c7086d --- /dev/null +++ b/Artemis/Artemis/Styles/DropTargetAdorners/DropTargetMetroHighlightAdorner.cs @@ -0,0 +1,75 @@ +// Direct copy-paste from GongSolutions.WPF.DragDrop - https://github.com/punker76/gong-wpf-dragdrop +// All credit goes to their awesome library, I merely copied this in order to change the highlight color. + +// BSD 3-Clause License +// +// Copyright (c) 2015, Jan Karger (Steven Kirk) +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are met: +// +// * Redistributions of source code must retain the above copyright notice, this +// list of conditions and the following disclaimer. +// +// * Redistributions in binary form must reproduce the above copyright notice, +// this list of conditions and the following disclaimer in the documentation +// and/or other materials provided with the distribution. +// +// * Neither the name of gong-wpf-dragdrop nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +// CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +// OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +using System.Windows; +using System.Windows.Controls; +using System.Windows.Media; +using GongSolutions.Wpf.DragDrop; +using MahApps.Metro; + +namespace Artemis.Styles.DropTargetAdorners +{ + public class DropTargetMetroHighlightAdorner : DropTargetAdorner + { + public DropTargetMetroHighlightAdorner(UIElement adornedElement) + : base(adornedElement) + { + } + + protected override void OnRender(DrawingContext drawingContext) + { + var visualTargetItem = DropInfo.VisualTargetItem; + if (visualTargetItem != null) + { + var rect = Rect.Empty; + + var tvItem = visualTargetItem as TreeViewItem; + if (tvItem != null && VisualTreeHelper.GetChildrenCount(tvItem) > 0) + { + var descendant = VisualTreeHelper.GetDescendantBounds(tvItem); + var translatePoint = tvItem.TranslatePoint(new Point(), AdornedElement); + var itemRect = new Rect(translatePoint, tvItem.RenderSize); + descendant.Union(itemRect); + rect = new Rect(translatePoint, new Size(descendant.Width - translatePoint.X, tvItem.ActualHeight)); + } + if (rect.IsEmpty) + { + rect = new Rect(visualTargetItem.TranslatePoint(new Point(), AdornedElement), + VisualTreeHelper.GetDescendantBounds(visualTargetItem).Size); + } + var color = (Color) ThemeManager.DetectAppStyle(Application.Current).Item2.Resources["AccentColor"]; + drawingContext.DrawRoundedRectangle(null, new Pen(new SolidColorBrush(color), 2), rect, 2, 2); + } + } + } +} \ No newline at end of file diff --git a/Artemis/Artemis/Styles/DropTargetAdorners/DropTargetMetroInsertionAdorner.cs b/Artemis/Artemis/Styles/DropTargetAdorners/DropTargetMetroInsertionAdorner.cs new file mode 100644 index 000000000..f70c8cac1 --- /dev/null +++ b/Artemis/Artemis/Styles/DropTargetAdorners/DropTargetMetroInsertionAdorner.cs @@ -0,0 +1,169 @@ +// Direct copy-paste from GongSolutions.WPF.DragDrop - https://github.com/punker76/gong-wpf-dragdrop +// All credit goes to their awesome library, I merely copied this in order to change the highlight color. + +// BSD 3-Clause License +// +// Copyright (c) 2015, Jan Karger (Steven Kirk) +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are met: +// +// * Redistributions of source code must retain the above copyright notice, this +// list of conditions and the following disclaimer. +// +// * Redistributions in binary form must reproduce the above copyright notice, +// this list of conditions and the following disclaimer in the documentation +// and/or other materials provided with the distribution. +// +// * Neither the name of gong-wpf-dragdrop nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +// CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +// OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +using System; +using System.Windows; +using System.Windows.Controls; +using System.Windows.Media; +using GongSolutions.Wpf.DragDrop; +using MahApps.Metro; + +namespace Artemis.Styles.DropTargetAdorners +{ + public class DropTargetMetroInsertionAdorner : DropTargetAdorner + { + private static readonly Pen m_Pen; + private static readonly PathGeometry m_Triangle; + + static DropTargetMetroInsertionAdorner() + { + // Create the pen and triangle in a static constructor and freeze them to improve performance. + const int triangleSize = 5; + + var color = (Color)ThemeManager.DetectAppStyle(Application.Current).Item2.Resources["AccentColor"]; + m_Pen = new Pen(new SolidColorBrush(color), 2); + m_Pen.Freeze(); + + var firstLine = new LineSegment(new Point(0, -triangleSize), false); + firstLine.Freeze(); + var secondLine = new LineSegment(new Point(0, triangleSize), false); + secondLine.Freeze(); + + var figure = new PathFigure { StartPoint = new Point(triangleSize, 0) }; + figure.Segments.Add(firstLine); + figure.Segments.Add(secondLine); + figure.Freeze(); + + m_Triangle = new PathGeometry(); + m_Triangle.Figures.Add(figure); + m_Triangle.Freeze(); + } + + public DropTargetMetroInsertionAdorner(UIElement adornedElement) + : base(adornedElement) + { + } + + protected override void OnRender(DrawingContext drawingContext) + { + var itemsControl = DropInfo.VisualTarget as ItemsControl; + + if (itemsControl != null) + { + // Get the position of the item at the insertion index. If the insertion point is + // to be after the last item, then get the position of the last item and add an + // offset later to draw it at the end of the list. + ItemsControl itemParent; + + if (DropInfo.VisualTargetItem != null) + { + itemParent = ItemsControl.ItemsControlFromItemContainer(DropInfo.VisualTargetItem); + } + else + { + itemParent = itemsControl; + } + + var index = Math.Min(DropInfo.InsertIndex, itemParent.Items.Count - 1); + + var lastItemInGroup = false; + var targetGroup = DropInfo.TargetGroup; + if (targetGroup != null && targetGroup.IsBottomLevel && + DropInfo.InsertPosition.HasFlag(RelativeInsertPosition.AfterTargetItem)) + { + var indexOf = targetGroup.Items.IndexOf(DropInfo.TargetItem); + lastItemInGroup = indexOf == targetGroup.ItemCount - 1; + if (lastItemInGroup && DropInfo.InsertIndex != itemParent.Items.Count) + { + index--; + } + } + + var itemContainer = (UIElement)itemParent.ItemContainerGenerator.ContainerFromIndex(index); + + if (itemContainer != null) + { + var itemRect = new Rect(itemContainer.TranslatePoint(new Point(), AdornedElement), + itemContainer.RenderSize); + Point point1, point2; + double rotation = 0; + + if (DropInfo.VisualTargetOrientation == Orientation.Vertical) + { + if (DropInfo.InsertIndex == itemParent.Items.Count || lastItemInGroup) + { + itemRect.Y += itemContainer.RenderSize.Height; + } + + point1 = new Point(itemRect.X, itemRect.Y); + point2 = new Point(itemRect.Right, itemRect.Y); + } + else + { + var itemRectX = itemRect.X; + + if (DropInfo.VisualTargetFlowDirection == FlowDirection.LeftToRight && + DropInfo.InsertIndex == itemParent.Items.Count) + { + itemRectX += itemContainer.RenderSize.Width; + } + else if (DropInfo.VisualTargetFlowDirection == FlowDirection.RightToLeft && + DropInfo.InsertIndex != itemParent.Items.Count) + { + itemRectX += itemContainer.RenderSize.Width; + } + + point1 = new Point(itemRectX, itemRect.Y); + point2 = new Point(itemRectX, itemRect.Bottom); + rotation = 90; + } + + drawingContext.DrawLine(m_Pen, point1, point2); + DrawTriangle(drawingContext, point1, rotation); + DrawTriangle(drawingContext, point2, 180 + rotation); + } + } + } + + private void DrawTriangle(DrawingContext drawingContext, Point origin, double rotation) + { + drawingContext.PushTransform(new TranslateTransform(origin.X, origin.Y)); + drawingContext.PushTransform(new RotateTransform(rotation)); + + drawingContext.DrawGeometry(m_Pen.Brush, null, m_Triangle); + + drawingContext.Pop(); + drawingContext.Pop(); + } + } +} \ No newline at end of file diff --git a/Artemis/Artemis/ViewModels/ProfileEditorViewModel.cs b/Artemis/Artemis/ViewModels/ProfileEditorViewModel.cs index 925b224e6..6b4068740 100644 --- a/Artemis/Artemis/ViewModels/ProfileEditorViewModel.cs +++ b/Artemis/Artemis/ViewModels/ProfileEditorViewModel.cs @@ -18,16 +18,18 @@ using Artemis.Models; using Artemis.Models.Profiles; using Artemis.Models.Profiles.Properties; using Artemis.Services; +using Artemis.Styles.DropTargetAdorners; using Artemis.Utilities; using Artemis.ViewModels.LayerEditor; using Caliburn.Micro; +using GongSolutions.Wpf.DragDrop; using MahApps.Metro; using Ninject; using Timer = System.Timers.Timer; namespace Artemis.ViewModels { - public sealed class ProfileEditorViewModel : Screen, IHandle + public sealed class ProfileEditorViewModel : Screen, IHandle, IDropTarget { private readonly GameModel _gameModel; private readonly MainManager _mainManager; @@ -150,6 +152,66 @@ namespace Artemis.ViewModels private KeyboardProvider ActiveKeyboard { get; set; } + public void DragOver(IDropInfo dropInfo) + { + var sourceItem = dropInfo.Data as LayerModel; + var targetItem = dropInfo.TargetItem as LayerModel; + if (sourceItem == null || targetItem == null) + return; + + if (dropInfo.InsertPosition == RelativeInsertPosition.TargetItemCenter && + targetItem.LayerType == LayerType.Folder) + { + dropInfo.DropTargetAdorner = typeof(DropTargetMetroHighlightAdorner); + dropInfo.Effects = DragDropEffects.Copy; + } + else + { + dropInfo.DropTargetAdorner = typeof(DropTargetMetroInsertionAdorner); + dropInfo.Effects = DragDropEffects.Move; + } + } + + public void Drop(IDropInfo dropInfo) + { + var sourceItem = dropInfo.Data as LayerModel; + var targetItem = dropInfo.TargetItem as LayerModel; + if (sourceItem == null || targetItem == null || sourceItem == targetItem) + return; + + if (dropInfo.InsertPosition == RelativeInsertPosition.TargetItemCenter && + targetItem.LayerType == LayerType.Folder) + { + // Insert into folder + + return; + } + + // Remove the source from it's old profile/parent + if (sourceItem.Parent == null) + sourceItem.Profile.Layers.Remove(sourceItem); + else + sourceItem.Parent.Children.Remove(sourceItem); + + // Insert the source into it's new profile/parent and update the order + if (dropInfo.InsertPosition == RelativeInsertPosition.AfterTargetItem) + sourceItem.Order = targetItem.Order + 1; + else + sourceItem.Order = targetItem.Order - 1; + if (targetItem.Parent == null) + { + targetItem.Profile.Layers.Add(sourceItem); + targetItem.Profile.FixOrder(); + } + else + { + targetItem.Parent.Children.Add(sourceItem); + targetItem.Parent.FixOrder(); + } + + UpdateLayerList(sourceItem); + } + /// /// Handles chaning the active keyboard, updating the preview image and profiles collection /// @@ -318,41 +380,6 @@ namespace Artemis.ViewModels SelectedProfile.FixOrder(); } - /// - /// Moves the currently selected layer up in the profile's layer tree - /// - public void LayerUp() - { - MoveLayer(true); - } - - /// - /// Moves the currently selected layer down in the profile's layer tree - /// - public void LayerDown() - { - MoveLayer(false); - } - - /// - /// Moves the currently selected layer up or down in the profile's layer tree - /// - /// - private void MoveLayer(bool moveUp) - { - if (SelectedLayer == null) - return; - - var reorderLayer = SelectedLayer; - - if (SelectedLayer.Parent != null) - SelectedLayer.Parent.Reorder(SelectedLayer, moveUp); - else - SelectedLayer.Profile.Reorder(SelectedLayer, moveUp); - - UpdateLayerList(reorderLayer); - } - private void UpdateLayerList(LayerModel selectModel) { // Update the UI @@ -421,7 +448,7 @@ namespace Artemis.ViewModels var hoverLayer = SelectedProfile.GetEnabledLayers() .Where(l => l.MustDraw()) .FirstOrDefault(l => ((KeyboardPropertiesModel) l.Properties) - .GetRect(1).Contains(x, y)); + .GetRect(1).Contains(x, y)); HandleDragging(e, x, y, hoverLayer); diff --git a/Artemis/Artemis/Views/ProfileEditorView.xaml b/Artemis/Artemis/Views/ProfileEditorView.xaml index 8718fb8e6..9e5a51472 100644 --- a/Artemis/Artemis/Views/ProfileEditorView.xaml +++ b/Artemis/Artemis/Views/ProfileEditorView.xaml @@ -7,6 +7,7 @@ xmlns:i="http://schemas.microsoft.com/expression/2010/interactivity" xmlns:itemBehaviours="clr-namespace:Artemis.ItemBehaviours" xmlns:utilities="clr-namespace:Artemis.Utilities" + xmlns:dragDrop="clr-namespace:GongSolutions.Wpf.DragDrop;assembly=GongSolutions.Wpf.DragDrop" mc:Ignorable="d" d:DesignHeight="473" Width="1055"> @@ -16,7 +17,6 @@ - @@ -27,7 +27,7 @@