diff --git a/src/Artemis.Core/Models/Profile/Folder.cs b/src/Artemis.Core/Models/Profile/Folder.cs index 561754017..f679e78a0 100644 --- a/src/Artemis.Core/Models/Profile/Folder.cs +++ b/src/Artemis.Core/Models/Profile/Folder.cs @@ -1,5 +1,6 @@ using System; using System.Linq; +using Artemis.Core.Models.Profile.LayerShapes; using Artemis.Core.Models.Surface; using Artemis.Storage.Entities.Profile; using SkiaSharp; @@ -72,6 +73,7 @@ namespace Artemis.Core.Models.Profile public Layer AddLayer(string name) { var layer = new Layer(Profile, this, name) {Order = Children.LastOrDefault()?.Order ?? 1}; + layer.LayerShape = new Fill(layer); AddChild(layer); return layer; } diff --git a/src/Artemis.Core/Models/Profile/Layer.cs b/src/Artemis.Core/Models/Profile/Layer.cs index c3f93c7df..dd6c12229 100644 --- a/src/Artemis.Core/Models/Profile/Layer.cs +++ b/src/Artemis.Core/Models/Profile/Layer.cs @@ -97,7 +97,8 @@ namespace Artemis.Core.Models.Profile set { _layerShape = value; - _layerShape.CalculateRenderProperties(); + if (Path != null) + _layerShape.CalculateRenderProperties(); } } @@ -146,15 +147,18 @@ namespace Artemis.Core.Models.Profile LayerEntity.Condition.Clear(); // Brush - LayerEntity.BrushEntity = new BrushEntity + if (LayerBrush != null) { - BrushPluginGuid = LayerBrush.Descriptor.LayerBrushProvider.PluginInfo.Guid, - BrushType = LayerBrush.GetType().Name, - Configuration = JsonConvert.SerializeObject(LayerBrush.Settings) - }; + LayerEntity.BrushEntity = new BrushEntity + { + BrushPluginGuid = LayerBrush.Descriptor.LayerBrushProvider.PluginInfo.Guid, + BrushType = LayerBrush.GetType().Name, + Configuration = JsonConvert.SerializeObject(LayerBrush.Settings) + }; + } // Shape - LayerShape.ApplyToEntity(); + LayerShape?.ApplyToEntity(); } /// diff --git a/src/Artemis.Core/Models/Profile/Profile.cs b/src/Artemis.Core/Models/Profile/Profile.cs index be8df1869..87e67a462 100644 --- a/src/Artemis.Core/Models/Profile/Profile.cs +++ b/src/Artemis.Core/Models/Profile/Profile.cs @@ -67,6 +67,11 @@ namespace Artemis.Core.Models.Profile } } + public Folder GetRootFolder() + { + return (Folder) Children.Single(); + } + internal override void ApplyToEntity() { ProfileEntity.Id = EntityId; diff --git a/src/Artemis.Core/Services/PluginService.cs b/src/Artemis.Core/Services/PluginService.cs index 8ec9b04da..35c6c8bea 100644 --- a/src/Artemis.Core/Services/PluginService.cs +++ b/src/Artemis.Core/Services/PluginService.cs @@ -2,6 +2,7 @@ using System.Collections.Generic; using System.IO; using System.Linq; +using System.Reflection; using AppDomainToolkit; using Artemis.Core.Events; using Artemis.Core.Exceptions; @@ -215,8 +216,19 @@ namespace Artemis.Core.Services } // Get the Plugin implementation from the main assembly and if there is only one, instantiate it - var mainAssembly = pluginInfo.Context.Domain.GetAssemblies().First(a => a.Location == mainFile); - var pluginTypes = mainAssembly.GetTypes().Where(t => typeof(Plugin).IsAssignableFrom(t)).ToList(); + List pluginTypes; + var mainAssembly = pluginInfo.Context.Domain.GetAssemblies().FirstOrDefault(a => a.Location == mainFile); + if (mainAssembly == null) + throw new ArtemisPluginException(pluginInfo, "Found no supported assembly in the plugins main file"); + try + { + pluginTypes = mainAssembly.GetTypes().Where(t => typeof(Plugin).IsAssignableFrom(t)).ToList(); + } + catch (ReflectionTypeLoadException e) + { + throw new ArtemisPluginException(pluginInfo, "Failed to initialize the plugin assembly", new AggregateException(e.LoaderExceptions)); + } + if (pluginTypes.Count > 1) throw new ArtemisPluginException(pluginInfo, $"Plugin contains {pluginTypes.Count} implementations of Plugin, only 1 allowed"); if (pluginTypes.Count == 0) diff --git a/src/Artemis.Core/Services/RgbService.cs b/src/Artemis.Core/Services/RgbService.cs index 82040faaa..85527b199 100644 --- a/src/Artemis.Core/Services/RgbService.cs +++ b/src/Artemis.Core/Services/RgbService.cs @@ -1,10 +1,12 @@ using System; using System.Collections.Generic; +using System.Linq; using Artemis.Core.Events; using Artemis.Core.Plugins.Models; using Artemis.Core.RGB.NET; using Artemis.Core.Services.Interfaces; using RGB.NET.Brushes; +using RGB.NET.Brushes.Gradients; using RGB.NET.Core; using RGB.NET.Groups; using Serilog; @@ -30,7 +32,7 @@ namespace Artemis.Core.Services _targetFrameRateSetting = settingsService.GetSetting("Core.TargetFrameRate", 25); Surface = RGBSurface.Instance; - + // Let's throw these for now Surface.Exception += SurfaceOnException; _renderScaleSetting.SettingChanged += RenderScaleSettingOnSettingChanged; @@ -104,7 +106,7 @@ namespace Artemis.Core.Services { // Apply the application wide brush and decorator BitmapBrush = new BitmapBrush(new Scale(_renderScaleSetting.Value)); - _surfaceLedGroup = new ListLedGroup(Surface.Leds) { Brush = BitmapBrush }; + _surfaceLedGroup = new ListLedGroup(Surface.Leds) {Brush = BitmapBrush}; return; } @@ -115,11 +117,11 @@ namespace Artemis.Core.Services // Apply the application wide brush and decorator BitmapBrush.Scale = new Scale(_renderScaleSetting.Value); - _surfaceLedGroup = new ListLedGroup(Surface.Leds) { Brush = BitmapBrush }; + _surfaceLedGroup = new ListLedGroup(Surface.Leds) {Brush = BitmapBrush}; } + lock (BitmapBrush) { - } } diff --git a/src/Artemis.Plugins.Devices.Wooting/Artemis.Plugins.Devices.Wooting.csproj b/src/Artemis.Plugins.Devices.Wooting/Artemis.Plugins.Devices.Wooting.csproj index 7a9cf0fc1..2189fd57f 100644 --- a/src/Artemis.Plugins.Devices.Wooting/Artemis.Plugins.Devices.Wooting.csproj +++ b/src/Artemis.Plugins.Devices.Wooting/Artemis.Plugins.Devices.Wooting.csproj @@ -50,14 +50,6 @@ - - - PreserveNewest - - - PreserveNewest - - PreserveNewest @@ -69,6 +61,10 @@ Artemis.Core + + + + echo Copying resources to plugin output directory diff --git a/src/Artemis.UI/Artemis.UI.csproj b/src/Artemis.UI/Artemis.UI.csproj index 950c80899..3527006e4 100644 --- a/src/Artemis.UI/Artemis.UI.csproj +++ b/src/Artemis.UI/Artemis.UI.csproj @@ -172,6 +172,7 @@ + @@ -222,8 +223,8 @@ - + @@ -520,6 +521,7 @@ + diff --git a/src/Artemis.UI/Behaviors/TreeViewSelectionBehavior.cs b/src/Artemis.UI/Behaviors/TreeViewSelectionBehavior.cs new file mode 100644 index 000000000..df9b60df9 --- /dev/null +++ b/src/Artemis.UI/Behaviors/TreeViewSelectionBehavior.cs @@ -0,0 +1,174 @@ +using System.Collections.Specialized; +using System.Windows; +using System.Windows.Controls; +using System.Windows.Interactivity; + +namespace Artemis.UI.Behaviors +{ + public class TreeViewSelectionBehavior : Behavior + { + public delegate bool IsChildOfPredicate(object nodeA, object nodeB); + + public static readonly DependencyProperty SelectedItemProperty = + DependencyProperty.Register(nameof(SelectedItem), typeof(object), + typeof(TreeViewSelectionBehavior), + new FrameworkPropertyMetadata(null, FrameworkPropertyMetadataOptions.BindsTwoWayByDefault, + OnSelectedItemChanged)); + + public static readonly DependencyProperty HierarchyPredicateProperty = + DependencyProperty.Register(nameof(HierarchyPredicate), typeof(IsChildOfPredicate), + typeof(TreeViewSelectionBehavior), + new FrameworkPropertyMetadata(null)); + + public static readonly DependencyProperty ExpandSelectedProperty = + DependencyProperty.Register(nameof(ExpandSelected), typeof(bool), + typeof(TreeViewSelectionBehavior), + new FrameworkPropertyMetadata(false)); + + private readonly EventSetter _treeViewItemEventSetter; + private bool _modelHandled; + + public TreeViewSelectionBehavior() + { + _treeViewItemEventSetter = new EventSetter( + FrameworkElement.LoadedEvent, + new RoutedEventHandler(OnTreeViewItemLoaded)); + } + + // Bindable selected item + public object SelectedItem + { + get => GetValue(SelectedItemProperty); + set => SetValue(SelectedItemProperty, value); + } + + // Predicate that checks if two items are hierarchically related + public IsChildOfPredicate HierarchyPredicate + { + get => (IsChildOfPredicate) GetValue(HierarchyPredicateProperty); + set => SetValue(HierarchyPredicateProperty, value); + } + + // Should expand selected? + public bool ExpandSelected + { + get => (bool) GetValue(ExpandSelectedProperty); + set => SetValue(ExpandSelectedProperty, value); + } + + private static void OnSelectedItemChanged(DependencyObject sender, DependencyPropertyChangedEventArgs args) + { + var behavior = (TreeViewSelectionBehavior) sender; + if (behavior._modelHandled) return; + + if (behavior.AssociatedObject == null) + return; + + behavior._modelHandled = true; + behavior.UpdateAllTreeViewItems(); + behavior._modelHandled = false; + } + + // Update state of all items starting with given, with optional recursion + private void UpdateTreeViewItem(TreeViewItem item, bool recurse) + { + if (SelectedItem == null) return; + var model = item.DataContext; + + // If the selected item is this model and is not yet selected - select and return + if (SelectedItem == model && !item.IsSelected) + { + item.IsSelected = true; + if (ExpandSelected) + item.IsExpanded = true; + } + // If the selected item is a parent of this model - expand + else + { + var isParentOfModel = HierarchyPredicate?.Invoke(SelectedItem, model) ?? true; + if (isParentOfModel) + item.IsExpanded = true; + } + + // Recurse into children + if (recurse) + { + foreach (var subitem in item.Items) + { + var tvi = item.ItemContainerGenerator.ContainerFromItem(subitem) as TreeViewItem; + if (tvi != null) + UpdateTreeViewItem(tvi, true); + } + } + } + + // Update state of all items + private void UpdateAllTreeViewItems() + { + var treeView = AssociatedObject; + foreach (var item in treeView.Items) + { + var tvi = treeView.ItemContainerGenerator.ContainerFromItem(item) as TreeViewItem; + if (tvi != null) + UpdateTreeViewItem(tvi, true); + } + } + + // Inject Loaded event handler into ItemContainerStyle + 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); + } + + private void OnTreeViewItemsChanged(object sender, + NotifyCollectionChangedEventArgs args) + { + UpdateAllTreeViewItems(); + } + + private void OnTreeViewSelectedItemChanged(object sender, RoutedPropertyChangedEventArgs args) + { + if (_modelHandled) return; + if (AssociatedObject.Items.SourceCollection == null) return; + SelectedItem = args.NewValue; + } + + private void OnTreeViewItemLoaded(object sender, RoutedEventArgs args) + { + UpdateTreeViewItem((TreeViewItem) sender, false); + } + + protected override void OnAttached() + { + base.OnAttached(); + + AssociatedObject.SelectedItemChanged += OnTreeViewSelectedItemChanged; + ((INotifyCollectionChanged) AssociatedObject.Items).CollectionChanged += OnTreeViewItemsChanged; + + UpdateTreeViewItemStyle(); + _modelHandled = true; + UpdateAllTreeViewItems(); + _modelHandled = false; + } + + protected override void OnDetaching() + { + base.OnDetaching(); + + if (AssociatedObject != null) + { + AssociatedObject.ItemContainerStyle?.Setters?.Remove(_treeViewItemEventSetter); + AssociatedObject.SelectedItemChanged -= OnTreeViewSelectedItemChanged; + ((INotifyCollectionChanged) AssociatedObject.Items).CollectionChanged -= OnTreeViewItemsChanged; + } + } + } +} \ No newline at end of file diff --git a/src/Artemis.UI/Events/MainWindowKeyEvent.cs b/src/Artemis.UI/Events/MainWindowKeyEvent.cs new file mode 100644 index 000000000..5163f8aea --- /dev/null +++ b/src/Artemis.UI/Events/MainWindowKeyEvent.cs @@ -0,0 +1,16 @@ +using System.Windows.Input; + +namespace Artemis.UI.Events +{ + public class MainWindowKeyEvent + { + public bool KeyDown { get; } + public KeyEventArgs EventArgs { get; } + + public MainWindowKeyEvent(bool keyDown, KeyEventArgs eventArgs) + { + KeyDown = keyDown; + EventArgs = eventArgs; + } + } +} \ No newline at end of file diff --git a/src/Artemis.UI/Screens/Module/ProfileEditor/ProfileTree/ProfileTreeView.xaml b/src/Artemis.UI/Screens/Module/ProfileEditor/ProfileTree/ProfileTreeView.xaml index 72aca0e22..06ad2e454 100644 --- a/src/Artemis.UI/Screens/Module/ProfileEditor/ProfileTree/ProfileTreeView.xaml +++ b/src/Artemis.UI/Screens/Module/ProfileEditor/ProfileTree/ProfileTreeView.xaml @@ -7,9 +7,9 @@ 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" + xmlns:behaviors="clr-namespace:Artemis.UI.Behaviors" mc:Ignorable="d" d:DesignHeight="450" d:DesignWidth="800" d:DataContext="{d:DesignInstance {x:Type profileTree:ProfileTreeViewModel}}"> @@ -33,7 +33,7 @@ dd:DragDrop.IsDropTarget="True" dd:DragDrop.DropHandler="{Binding}"> - + diff --git a/src/Artemis.UI/Screens/Module/ProfileEditor/ProfileTree/ProfileTreeViewModel.cs b/src/Artemis.UI/Screens/Module/ProfileEditor/ProfileTree/ProfileTreeViewModel.cs index e1df7fa0a..543666734 100644 --- a/src/Artemis.UI/Screens/Module/ProfileEditor/ProfileTree/ProfileTreeViewModel.cs +++ b/src/Artemis.UI/Screens/Module/ProfileEditor/ProfileTree/ProfileTreeViewModel.cs @@ -2,10 +2,12 @@ using System.Linq; using System.Windows; using Artemis.Core.Models.Profile; +using Artemis.UI.Behaviors; using Artemis.UI.Ninject.Factories; using Artemis.UI.Screens.Module.ProfileEditor.ProfileTree.TreeItem; using Artemis.UI.Services.Interfaces; using GongSolutions.Wpf.DragDrop; +using Stylet; namespace Artemis.UI.Screens.Module.ProfileEditor.ProfileTree { @@ -13,12 +15,16 @@ namespace Artemis.UI.Screens.Module.ProfileEditor.ProfileTree { private readonly IProfileEditorService _profileEditorService; private readonly IFolderViewModelFactory _folderViewModelFactory; + private readonly ILayerViewModelFactory _layerViewModelFactory; private TreeItemViewModel _selectedTreeItem; - public ProfileTreeViewModel(IProfileEditorService profileEditorService, IFolderViewModelFactory folderViewModelFactory) + public ProfileTreeViewModel(IProfileEditorService profileEditorService, + IFolderViewModelFactory folderViewModelFactory, + ILayerViewModelFactory layerViewModelFactory) { _profileEditorService = profileEditorService; _folderViewModelFactory = folderViewModelFactory; + _layerViewModelFactory = layerViewModelFactory; CreateRootFolderViewModel(); _profileEditorService.SelectedProfileChanged += OnSelectedProfileChanged; @@ -139,6 +145,27 @@ namespace Artemis.UI.Screens.Module.ProfileEditor.ProfileTree // Don't set it using the setter or that will trigger the event again _selectedTreeItem = vms?.FirstOrDefault(vm => vm.ProfileElement == _profileEditorService.SelectedProfileElement); + + if (_selectedTreeItem == null && _profileEditorService.SelectedProfileElement != null) + { + var parent = vms?.FirstOrDefault(vm => vm.ProfileElement == _profileEditorService.SelectedProfileElement.Parent); + if (parent == null) + { + // Eh.. we did our best.. start over + CreateRootFolderViewModel(); + return; + } + + // Create a new TreeItemViewModel for the new ProfileElement + TreeItemViewModel treeItemViewModel; + if (_profileEditorService.SelectedProfileElement is Folder folder) + treeItemViewModel = _folderViewModelFactory.Create(parent, folder); + else + treeItemViewModel = _layerViewModelFactory.Create(parent, (Layer) _profileEditorService.SelectedProfileElement); + parent.AddExistingElement(treeItemViewModel); + _selectedTreeItem = treeItemViewModel; + } + NotifyOfPropertyChange(() => SelectedTreeItem); } diff --git a/src/Artemis.UI/Screens/Module/ProfileEditor/Visualization/ProfileLedView.xaml b/src/Artemis.UI/Screens/Module/ProfileEditor/Visualization/ProfileLedView.xaml index fe127e10f..748391ed3 100644 --- a/src/Artemis.UI/Screens/Module/ProfileEditor/Visualization/ProfileLedView.xaml +++ b/src/Artemis.UI/Screens/Module/ProfileEditor/Visualization/ProfileLedView.xaml @@ -49,7 +49,7 @@ - + - + - + diff --git a/src/Artemis.UI/Screens/Module/ProfileEditor/Visualization/ProfileView.xaml b/src/Artemis.UI/Screens/Module/ProfileEditor/Visualization/ProfileView.xaml index bbb7aade4..d67bd66ce 100644 --- a/src/Artemis.UI/Screens/Module/ProfileEditor/Visualization/ProfileView.xaml +++ b/src/Artemis.UI/Screens/Module/ProfileEditor/Visualization/ProfileView.xaml @@ -46,17 +46,17 @@ - + - + - + - + @@ -79,16 +79,14 @@ - + - + @@ -123,7 +121,7 @@ - + @@ -142,7 +140,7 @@ - + diff --git a/src/Artemis.UI/Screens/Module/ProfileEditor/Visualization/ProfileViewModel.cs b/src/Artemis.UI/Screens/Module/ProfileEditor/Visualization/ProfileViewModel.cs index b79c23b41..4d30e85e8 100644 --- a/src/Artemis.UI/Screens/Module/ProfileEditor/Visualization/ProfileViewModel.cs +++ b/src/Artemis.UI/Screens/Module/ProfileEditor/Visualization/ProfileViewModel.cs @@ -1,9 +1,9 @@ using System; using System.Collections.Generic; using System.Collections.ObjectModel; +using System.Diagnostics; using System.Linq; using System.Windows.Input; -using System.Windows.Media; using Artemis.Core.Events; using Artemis.Core.Models.Profile; using Artemis.Core.Models.Surface; @@ -19,11 +19,14 @@ using Stylet; namespace Artemis.UI.Screens.Module.ProfileEditor.Visualization { - public class ProfileViewModel : ProfileEditorPanelViewModel, IHandle + public class ProfileViewModel : ProfileEditorPanelViewModel, IHandle, IHandle { private readonly IProfileEditorService _profileEditorService; private readonly ISettingsService _settingsService; private readonly ISurfaceService _surfaceService; + private int _activeToolIndex; + private VisualizationToolViewModel _activeToolViewModel; + private int _previousTool; private TimerUpdateTrigger _updateTrigger; public ProfileViewModel(IProfileEditorService profileEditorService, ISurfaceService surfaceService, ISettingsService settingsService, IEventAggregator eventAggregator) @@ -66,12 +69,19 @@ namespace Artemis.UI.Screens.Module.ProfileEditor.Visualization { // Remove the tool from the canvas if (_activeToolViewModel != null) + { CanvasViewModels.Remove(_activeToolViewModel); + NotifyOfPropertyChange(() => CanvasViewModels); + } + // Set the new tool _activeToolViewModel = value; // Add the new tool to the canvas if (_activeToolViewModel != null) + { CanvasViewModels.Add(_activeToolViewModel); + NotifyOfPropertyChange(() => CanvasViewModels); + } } } @@ -80,8 +90,12 @@ namespace Artemis.UI.Screens.Module.ProfileEditor.Visualization get => _activeToolIndex; set { - _activeToolIndex = value; - ActivateToolByIndex(value); + if (_activeToolIndex != value) + { + _activeToolIndex = value; + ActivateToolByIndex(value); + NotifyOfPropertyChange(() => ActiveToolIndex); + } } } @@ -215,19 +229,21 @@ namespace Artemis.UI.Screens.Module.ProfileEditor.Visualization case 3: ActiveToolViewModel = new SelectionRemoveToolViewModel(this, _profileEditorService); break; - case 4: + case 5: ActiveToolViewModel = new EllipseToolViewModel(this, _profileEditorService); break; - case 5: + case 6: ActiveToolViewModel = new RectangleToolViewModel(this, _profileEditorService); break; - case 6: + case 7: ActiveToolViewModel = new PolygonToolViewModel(this, _profileEditorService); break; - case 7: + case 8: ActiveToolViewModel = new FillToolViewModel(this, _profileEditorService); break; } + + ActiveToolIndex = value; } public void ResetZoomAndPan() @@ -256,6 +272,7 @@ namespace Artemis.UI.Screens.Module.ProfileEditor.Visualization public void CanvasMouseWheel(object sender, MouseWheelEventArgs e) { + PanZoomViewModel.ProcessMouseScroll(sender, e); ActiveToolViewModel?.MouseWheel(sender, e); } @@ -300,27 +317,6 @@ namespace Artemis.UI.Screens.Module.ProfileEditor.Visualization #endregion - #region Keys - - private int _previousTool; - private int _activeToolIndex; - private VisualizationToolViewModel _activeToolViewModel; - - public void CanvasKeyDown(object sender, KeyEventArgs e) - { - _previousTool = ActiveToolIndex; - if ((e.Key == Key.LeftCtrl || e.Key == Key.RightCtrl) && e.IsDown) - ActiveToolViewModel = new ViewpointMoveToolViewModel(this, _profileEditorService); - } - - public void CanvasKeyUp(object sender, KeyEventArgs e) - { - if ((e.Key == Key.LeftCtrl || e.Key == Key.RightCtrl) && e.IsUp) - ActivateToolByIndex(_previousTool); - } - - #endregion - #region Event handlers private void HighlightSelectedLayerOnSettingChanged(object sender, EventArgs e) @@ -352,6 +348,29 @@ namespace Artemis.UI.Screens.Module.ProfileEditor.Visualization } } + public void Handle(MainWindowKeyEvent message) + { + Debug.WriteLine(message.KeyDown); + if (message.KeyDown) + { + if (ActiveToolIndex != 0) + { + _previousTool = ActiveToolIndex; + if ((message.EventArgs.Key == Key.LeftCtrl || message.EventArgs.Key == Key.RightCtrl) && message.EventArgs.IsDown) + ActivateToolByIndex(0); + } + + ActiveToolViewModel?.KeyDown(message.EventArgs); + } + else + { + if ((message.EventArgs.Key == Key.LeftCtrl || message.EventArgs.Key == Key.RightCtrl) && message.EventArgs.IsUp) + ActivateToolByIndex(_previousTool); + + ActiveToolViewModel?.KeyUp(message.EventArgs); + } + } + #endregion } } \ No newline at end of file diff --git a/src/Artemis.UI/Screens/Module/ProfileEditor/Visualization/Tools/EllipseToolView.xaml b/src/Artemis.UI/Screens/Module/ProfileEditor/Visualization/Tools/EllipseToolView.xaml index 869d0d643..dcebf2e50 100644 --- a/src/Artemis.UI/Screens/Module/ProfileEditor/Visualization/Tools/EllipseToolView.xaml +++ b/src/Artemis.UI/Screens/Module/ProfileEditor/Visualization/Tools/EllipseToolView.xaml @@ -8,23 +8,30 @@ xmlns:materialDesign="http://materialdesigninxaml.net/winfx/xaml/themes" mc:Ignorable="d" d:DesignHeight="450" d:DesignWidth="800"> - - - - - - - - - - - - - - - - - + + + + + @@ -39,7 +46,14 @@ - + diff --git a/src/Artemis.UI/Screens/Module/ProfileEditor/Visualization/Tools/EllipseToolViewModel.cs b/src/Artemis.UI/Screens/Module/ProfileEditor/Visualization/Tools/EllipseToolViewModel.cs index 51eebd5db..c51f3a34b 100644 --- a/src/Artemis.UI/Screens/Module/ProfileEditor/Visualization/Tools/EllipseToolViewModel.cs +++ b/src/Artemis.UI/Screens/Module/ProfileEditor/Visualization/Tools/EllipseToolViewModel.cs @@ -1,4 +1,5 @@ using System.IO; +using System.Windows; using System.Windows.Input; using Artemis.UI.Properties; using Artemis.UI.Services.Interfaces; @@ -7,6 +8,8 @@ namespace Artemis.UI.Screens.Module.ProfileEditor.Visualization.Tools { public class EllipseToolViewModel : VisualizationToolViewModel { + private bool _shiftDown; + public EllipseToolViewModel(ProfileViewModel profileViewModel, IProfileEditorService profileEditorService) : base(profileViewModel, profileEditorService) { using (var stream = new MemoryStream(Resources.aero_crosshair)) @@ -14,5 +17,37 @@ namespace Artemis.UI.Screens.Module.ProfileEditor.Visualization.Tools Cursor = new Cursor(stream); } } + + public Rect DragRectangle { get; set; } + + public override void MouseMove(object sender, MouseEventArgs e) + { + base.MouseMove(sender, e); + + if (!IsMouseDown) + return; + + var position = ProfileViewModel.PanZoomViewModel.GetRelativeMousePosition(sender, e); + if (!_shiftDown) + DragRectangle = new Rect(MouseDownStartPosition, position); + else + DragRectangle = GetSquareRectBetweenPoints(MouseDownStartPosition, position); + } + + public override void KeyUp(KeyEventArgs e) + { + base.KeyUp(e); + + if (e.Key == Key.LeftShift || e.Key == Key.RightShift) + _shiftDown = false; + } + + public override void KeyDown(KeyEventArgs e) + { + base.KeyDown(e); + + if (e.Key == Key.LeftShift || e.Key == Key.RightShift) + _shiftDown = true; + } } } \ No newline at end of file diff --git a/src/Artemis.UI/Screens/Module/ProfileEditor/Visualization/Tools/RectangleToolView.xaml b/src/Artemis.UI/Screens/Module/ProfileEditor/Visualization/Tools/RectangleToolView.xaml index 62cd40ae9..f1a04656c 100644 --- a/src/Artemis.UI/Screens/Module/ProfileEditor/Visualization/Tools/RectangleToolView.xaml +++ b/src/Artemis.UI/Screens/Module/ProfileEditor/Visualization/Tools/RectangleToolView.xaml @@ -1,12 +1,62 @@  - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/Artemis.UI/Screens/Module/ProfileEditor/Visualization/Tools/RectangleToolViewModel.cs b/src/Artemis.UI/Screens/Module/ProfileEditor/Visualization/Tools/RectangleToolViewModel.cs index ad74e3bdb..f9ba56717 100644 --- a/src/Artemis.UI/Screens/Module/ProfileEditor/Visualization/Tools/RectangleToolViewModel.cs +++ b/src/Artemis.UI/Screens/Module/ProfileEditor/Visualization/Tools/RectangleToolViewModel.cs @@ -1,4 +1,5 @@ using System.IO; +using System.Windows; using System.Windows.Input; using Artemis.UI.Properties; using Artemis.UI.Services.Interfaces; @@ -7,6 +8,8 @@ namespace Artemis.UI.Screens.Module.ProfileEditor.Visualization.Tools { public class RectangleToolViewModel : VisualizationToolViewModel { + private bool _shiftDown; + public RectangleToolViewModel(ProfileViewModel profileViewModel, IProfileEditorService profileEditorService) : base(profileViewModel, profileEditorService) { using (var stream = new MemoryStream(Resources.aero_crosshair)) @@ -14,5 +17,37 @@ namespace Artemis.UI.Screens.Module.ProfileEditor.Visualization.Tools Cursor = new Cursor(stream); } } + + public Rect DragRectangle { get; set; } + + public override void MouseMove(object sender, MouseEventArgs e) + { + base.MouseMove(sender, e); + + if (!IsMouseDown) + return; + + var position = ProfileViewModel.PanZoomViewModel.GetRelativeMousePosition(sender, e); + if (!_shiftDown) + DragRectangle = new Rect(MouseDownStartPosition, position); + else + DragRectangle = GetSquareRectBetweenPoints(MouseDownStartPosition, position); + } + + public override void KeyUp(KeyEventArgs e) + { + base.KeyUp(e); + + if (e.Key == Key.LeftShift || e.Key == Key.RightShift) + _shiftDown = false; + } + + public override void KeyDown(KeyEventArgs e) + { + base.KeyDown(e); + + if (e.Key == Key.LeftShift || e.Key == Key.RightShift) + _shiftDown = true; + } } } \ No newline at end of file diff --git a/src/Artemis.UI/Screens/Module/ProfileEditor/Visualization/Tools/SelectionToolView.xaml b/src/Artemis.UI/Screens/Module/ProfileEditor/Visualization/Tools/SelectionToolView.xaml index 55faa6787..25af68504 100644 --- a/src/Artemis.UI/Screens/Module/ProfileEditor/Visualization/Tools/SelectionToolView.xaml +++ b/src/Artemis.UI/Screens/Module/ProfileEditor/Visualization/Tools/SelectionToolView.xaml @@ -4,9 +4,60 @@ xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:local="clr-namespace:Artemis.UI.Screens.Module.ProfileEditor.Visualization.Tools" + xmlns:s="https://github.com/canton7/Stylet" + xmlns:materialDesign="http://materialdesigninxaml.net/winfx/xaml/themes" mc:Ignorable="d" d:DesignHeight="450" d:DesignWidth="800"> - - - + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/Artemis.UI/Screens/Module/ProfileEditor/Visualization/Tools/SelectionToolViewModel.cs b/src/Artemis.UI/Screens/Module/ProfileEditor/Visualization/Tools/SelectionToolViewModel.cs index aded6d1e3..7ccd141e3 100644 --- a/src/Artemis.UI/Screens/Module/ProfileEditor/Visualization/Tools/SelectionToolViewModel.cs +++ b/src/Artemis.UI/Screens/Module/ProfileEditor/Visualization/Tools/SelectionToolViewModel.cs @@ -1,6 +1,10 @@ -using System.IO; +using System.Collections.Generic; +using System.IO; using System.Windows; using System.Windows.Input; +using Artemis.Core.Models.Profile; +using Artemis.Core.Models.Surface; +using Artemis.UI.Extensions; using Artemis.UI.Properties; using Artemis.UI.Services.Interfaces; @@ -16,49 +20,79 @@ namespace Artemis.UI.Screens.Module.ProfileEditor.Visualization.Tools } } - public override void MouseDown(object sender, MouseButtonEventArgs e) - { - base.MouseDown(sender, e); -// ProfileViewModel.SelectionRectangle.Rect = new Rect(); - } + public Rect DragRectangle { get; set; } + public override void MouseUp(object sender, MouseButtonEventArgs e) { base.MouseUp(sender, e); - var position = e.GetPosition((IInputElement) sender); + var position = ProfileViewModel.PanZoomViewModel.GetRelativeMousePosition(sender, e); var selectedRect = new Rect(MouseDownStartPosition, position); + // Get selected LEDs + var selectedLeds = new List(); foreach (var device in ProfileViewModel.Devices) { foreach (var ledViewModel in device.Leds) { - if (ProfileViewModel.PanZoomViewModel.TransformContainingRect(ledViewModel.Led.RgbLed.AbsoluteLedRectangle).IntersectsWith(selectedRect)) - ledViewModel.IsSelected = true; - else if (!Keyboard.IsKeyDown(Key.LeftShift) && !Keyboard.IsKeyDown(Key.RightShift)) - ledViewModel.IsSelected = false; + if (ledViewModel.Led.RgbLed.AbsoluteLedRectangle.ToWindowsRect(1).IntersectsWith(selectedRect)) + selectedLeds.Add(ledViewModel.Led); + // Unselect everything + ledViewModel.IsSelected = false; } } + + // Apply the selection to the selected layer layer + if (ProfileEditorService.SelectedProfileElement is Layer layer) + { + layer.ClearLeds(); + layer.AddLeds(selectedLeds); + ProfileEditorService.UpdateSelectedProfileElement(); + } + // If no layer selected, apply it to a new layer in the selected folder + else if (ProfileEditorService.SelectedProfileElement is Folder folder) + { + var newLayer = folder.AddLayer("New layer"); + newLayer.AddLeds(selectedLeds); + ProfileEditorService.ChangeSelectedProfileElement(newLayer); + ProfileEditorService.UpdateSelectedProfileElement(); + } + // If no folder selected, apply it to a new layer in the root folder + else + { + var rootFolder = ProfileEditorService.SelectedProfile.GetRootFolder(); + var newLayer = rootFolder.AddLayer("New layer"); + newLayer.AddLeds(selectedLeds); + ProfileEditorService.ChangeSelectedProfileElement(newLayer); + ProfileEditorService.UpdateSelectedProfileElement(); + } } public override void MouseMove(object sender, MouseEventArgs e) { base.MouseMove(sender, e); + if (!IsMouseDown) + { + DragRectangle = new Rect(-1, -1, 0, 0); + return; + } - var position = e.GetPosition((IInputElement) sender); + var position = ProfileViewModel.PanZoomViewModel.GetRelativeMousePosition(sender, e); var selectedRect = new Rect(MouseDownStartPosition, position); -// ProfileViewModel.SelectionRectangle.Rect = selectedRect; foreach (var device in ProfileViewModel.Devices) { foreach (var ledViewModel in device.Leds) { - if (ProfileViewModel.PanZoomViewModel.TransformContainingRect(ledViewModel.Led.RgbLed.AbsoluteLedRectangle).IntersectsWith(selectedRect)) + if (ledViewModel.Led.RgbLed.AbsoluteLedRectangle.ToWindowsRect(1).IntersectsWith(selectedRect)) ledViewModel.IsSelected = true; else if (!Keyboard.IsKeyDown(Key.LeftShift) && !Keyboard.IsKeyDown(Key.RightShift)) ledViewModel.IsSelected = false; } } + + DragRectangle = selectedRect; } } } \ No newline at end of file diff --git a/src/Artemis.UI/Screens/Module/ProfileEditor/Visualization/Tools/ViewpointMoveToolViewModel.cs b/src/Artemis.UI/Screens/Module/ProfileEditor/Visualization/Tools/ViewpointMoveToolViewModel.cs index 5205f0a76..1e5458478 100644 --- a/src/Artemis.UI/Screens/Module/ProfileEditor/Visualization/Tools/ViewpointMoveToolViewModel.cs +++ b/src/Artemis.UI/Screens/Module/ProfileEditor/Visualization/Tools/ViewpointMoveToolViewModel.cs @@ -8,19 +8,13 @@ namespace Artemis.UI.Screens.Module.ProfileEditor.Visualization.Tools public ViewpointMoveToolViewModel(ProfileViewModel profileViewModel, IProfileEditorService profileEditorService) : base(profileViewModel, profileEditorService) { Cursor = Cursors.Hand; + ProfileViewModel.PanZoomViewModel.LastPanPosition = null; } public override void MouseMove(object sender, MouseEventArgs e) { base.MouseMove(sender, e); - if (IsMouseDown) - ProfileViewModel.PanZoomViewModel.ProcessMouseMove(sender, e); - } - - public override void MouseWheel(object sender, MouseWheelEventArgs e) - { - base.MouseWheel(sender, e); - ProfileViewModel.PanZoomViewModel.ProcessMouseScroll(sender, e); + ProfileViewModel.PanZoomViewModel.ProcessMouseMove(sender, e); } } } \ No newline at end of file diff --git a/src/Artemis.UI/Screens/Module/ProfileEditor/Visualization/Tools/VisualizationToolViewModel.cs b/src/Artemis.UI/Screens/Module/ProfileEditor/Visualization/Tools/VisualizationToolViewModel.cs index c109ac901..26d6c38b6 100644 --- a/src/Artemis.UI/Screens/Module/ProfileEditor/Visualization/Tools/VisualizationToolViewModel.cs +++ b/src/Artemis.UI/Screens/Module/ProfileEditor/Visualization/Tools/VisualizationToolViewModel.cs @@ -1,4 +1,5 @@ -using System.Windows; +using System; +using System.Windows; using System.Windows.Input; using Artemis.UI.Services.Interfaces; @@ -26,7 +27,7 @@ namespace Artemis.UI.Screens.Module.ProfileEditor.Visualization.Tools public virtual void MouseDown(object sender, MouseButtonEventArgs e) { IsMouseDown = true; - MouseDownStartPosition = e.GetPosition((IInputElement) sender); + MouseDownStartPosition = ProfileViewModel.PanZoomViewModel.GetRelativeMousePosition(sender, e); } public virtual void MouseUp(object sender, MouseButtonEventArgs e) @@ -41,5 +42,28 @@ namespace Artemis.UI.Screens.Module.ProfileEditor.Visualization.Tools public virtual void MouseWheel(object sender, MouseWheelEventArgs e) { } + + public virtual void KeyUp(KeyEventArgs e) + { + } + + public virtual void KeyDown(KeyEventArgs e) + { + } + + protected Rect GetSquareRectBetweenPoints(Point start, Point end) + { + // Find the shortest side + var size = Math.Min(Math.Abs(start.X - end.X), Math.Abs(start.Y - end.Y)); + + // There's probably a very elegant way to do this, and this is not it + if (end.X < start.X && end.Y < start.Y) + return new Rect(start.X - size, start.Y - size, size, size); + if (end.X < start.X) + return new Rect(start.X - size, Math.Min(start.Y, end.Y), size, size); + if (end.Y < start.Y) + return new Rect(Math.Min(start.X, end.X), start.Y - size, size, size); + return new Rect(Math.Min(start.X, end.X), Math.Min(start.Y, end.Y), size, size); + } } } \ No newline at end of file diff --git a/src/Artemis.UI/Screens/RootView.xaml b/src/Artemis.UI/Screens/RootView.xaml index cb62adb8b..5cad84c15 100644 --- a/src/Artemis.UI/Screens/RootView.xaml +++ b/src/Artemis.UI/Screens/RootView.xaml @@ -17,6 +17,8 @@ UseLayoutRounding="True" Deactivated="{s:Action WindowDeactivated}" Activated="{s:Action WindowActivated}" + KeyDown="{s:Action WindowKeyDown}" + KeyUp="{s:Action WindowKeyUp}" d:DesignHeight="640" d:DesignWidth="1200" d:DataContext="{d:DesignInstance screens:RootViewModel}"> diff --git a/src/Artemis.UI/Screens/RootViewModel.cs b/src/Artemis.UI/Screens/RootViewModel.cs index 1e4f11a2a..5af4656e4 100644 --- a/src/Artemis.UI/Screens/RootViewModel.cs +++ b/src/Artemis.UI/Screens/RootViewModel.cs @@ -3,6 +3,7 @@ using System.ComponentModel; using System.Threading; using System.Threading.Tasks; using System.Windows; +using System.Windows.Input; using Artemis.UI.Events; using Artemis.UI.Screens.Sidebar; using Artemis.UI.Utilities; @@ -80,5 +81,15 @@ namespace Artemis.UI.Screens _lostFocus = false; _eventAggregator.Publish(new MainWindowFocusChangedEvent(true)); } + + public void WindowKeyDown(object sender, KeyEventArgs e) + { + _eventAggregator.Publish(new MainWindowKeyEvent(true, e)); + } + + public void WindowKeyUp(object sender, KeyEventArgs e) + { + _eventAggregator.Publish(new MainWindowKeyEvent(false, e)); + } } } \ No newline at end of file diff --git a/src/Artemis.UI/Screens/Shared/PanZoomViewModel.cs b/src/Artemis.UI/Screens/Shared/PanZoomViewModel.cs index 2b88d9e43..727b815ff 100644 --- a/src/Artemis.UI/Screens/Shared/PanZoomViewModel.cs +++ b/src/Artemis.UI/Screens/Shared/PanZoomViewModel.cs @@ -12,7 +12,7 @@ namespace Artemis.UI.Screens.Shared { public class PanZoomViewModel : PropertyChangedBase { - private Point? _lastPanPosition; + public Point? LastPanPosition { get; set; } public double Zoom { get; set; } = 1; public double ZoomPercentage @@ -51,20 +51,20 @@ namespace Artemis.UI.Screens.Shared { if (e.LeftButton == MouseButtonState.Released) { - _lastPanPosition = null; + LastPanPosition = null; return; } - if (_lastPanPosition == null) - _lastPanPosition = e.GetPosition((IInputElement) sender); + if (LastPanPosition == null) + LastPanPosition = e.GetPosition((IInputElement) sender); var position = e.GetPosition((IInputElement) sender); - var delta = _lastPanPosition - position; + var delta = LastPanPosition - position; PanX = Math.Min(0, PanX - delta.Value.X); PanY = Math.Min(0, PanY - delta.Value.Y); - _lastPanPosition = position; + LastPanPosition = position; } public void Reset() diff --git a/src/Artemis.UI/Utilities/BindableSelectedItemBehavior.cs b/src/Artemis.UI/Utilities/BindableSelectedItemBehavior.cs deleted file mode 100644 index 90a44188d..000000000 --- a/src/Artemis.UI/Utilities/BindableSelectedItemBehavior.cs +++ /dev/null @@ -1,47 +0,0 @@ -using System.Windows; -using System.Windows.Controls; -using System.Windows.Interactivity; - -namespace Artemis.UI.Utilities -{ - public class BindableSelectedItemBehavior : Behavior - { - protected override void OnAttached() - { - base.OnAttached(); - - AssociatedObject.SelectedItemChanged += OnTreeViewSelectedItemChanged; - } - - protected override void OnDetaching() - { - base.OnDetaching(); - - if (AssociatedObject != null) AssociatedObject.SelectedItemChanged -= OnTreeViewSelectedItemChanged; - } - - private void OnTreeViewSelectedItemChanged(object sender, RoutedPropertyChangedEventArgs e) - { - SelectedItem = e.NewValue; - } - - #region SelectedItem Property - - public object SelectedItem - { - get => GetValue(SelectedItemProperty); - set => SetValue(SelectedItemProperty, value); - } - - public static readonly DependencyProperty SelectedItemProperty = - DependencyProperty.Register("SelectedItem", typeof(object), typeof(BindableSelectedItemBehavior), new UIPropertyMetadata(null, OnSelectedItemChanged)); - - private static void OnSelectedItemChanged(DependencyObject sender, DependencyPropertyChangedEventArgs e) - { - var item = e.NewValue as TreeViewItem; - if (item != null) item.SetValue(TreeViewItem.IsSelectedProperty, true); - } - - #endregion - } -} \ No newline at end of file