1
0
mirror of https://github.com/Artemis-RGB/Artemis synced 2025-12-13 05:48:35 +00:00

Basic implementation of a few editor tools

This commit is contained in:
Robert 2019-12-16 22:46:11 +01:00
parent 580db3185e
commit 8539f05d90
26 changed files with 634 additions and 174 deletions

View File

@ -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;
}

View File

@ -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();
}
/// <summary>

View File

@ -67,6 +67,11 @@ namespace Artemis.Core.Models.Profile
}
}
public Folder GetRootFolder()
{
return (Folder) Children.Single();
}
internal override void ApplyToEntity()
{
ProfileEntity.Id = EntityId;

View File

@ -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<Type> 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)

View File

@ -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;
@ -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)
{
}
}

View File

@ -50,14 +50,6 @@
<Compile Include="WootingDeviceProvider.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />
</ItemGroup>
<ItemGroup>
<Content Include="x64\wooting-rgb-sdk64.dll">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
<Content Include="x86\wooting-rgb-sdk.dll">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
</ItemGroup>
<ItemGroup>
<None Include="plugin.json">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
@ -69,6 +61,10 @@
<Name>Artemis.Core</Name>
</ProjectReference>
</ItemGroup>
<ItemGroup>
<Folder Include="x64\" />
<Folder Include="x86\" />
</ItemGroup>
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
<PropertyGroup>
<PostBuildEvent>echo Copying resources to plugin output directory

View File

@ -172,6 +172,7 @@
<Compile Include="Converters\NullToImageConverter.cs" />
<Compile Include="Converters\NullToVisibilityConverter.cs" />
<Compile Include="Events\MainWindowFocusChangedEvent.cs" />
<Compile Include="Events\MainWindowKeyEvent.cs" />
<Compile Include="Events\WindowsThemeEventArgs.cs" />
<Compile Include="Screens\GradientEditor\GradientEditorViewModel.cs" />
<Compile Include="Screens\Module\ProfileEditor\LayerProperties\LayerPropertiesView.xaml.cs">
@ -222,8 +223,8 @@
<Compile Include="Screens\Sidebar\SidebarViewModel.cs" />
<Compile Include="Services\Interfaces\IProfileEditorService.cs" />
<Compile Include="Services\ProfileEditorService.cs" />
<Compile Include="Utilities\BindableSelectedItemBehavior.cs" />
<Compile Include="Utilities\ThemeWatcher.cs" />
<Compile Include="Behaviors\TreeViewSelectionBehavior.cs" />
<Compile Include="Utilities\TriggerTracing.cs" />
<Compile Include="Exceptions\ArtemisCoreException.cs" />
<Compile Include="Extensions\RgbColorExtensions.cs" />
@ -520,6 +521,7 @@
<ItemGroup>
<None Include="Resources\aero_fill.cur" />
</ItemGroup>
<ItemGroup />
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
<PropertyGroup>
<PostBuildEvent>

View File

@ -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<TreeView>
{
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<object> 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;
}
}
}
}

View File

@ -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;
}
}
}

View File

@ -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}">
<i:Interaction.Behaviors>
<utilities:BindableSelectedItemBehavior SelectedItem="{Binding SelectedTreeItem, Mode=TwoWay}" />
<behaviors:TreeViewSelectionBehavior ExpandSelected="True" SelectedItem="{Binding SelectedTreeItem}" />
</i:Interaction.Behaviors>
<TreeView.Resources>
<HierarchicalDataTemplate DataType="{x:Type treeItem:FolderViewModel}" ItemsSource="{Binding Children}">

View File

@ -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);
}

View File

@ -49,7 +49,7 @@
</Path>
<!-- Selection -->
<Path Data="{Binding DisplayGeometry, Mode=OneWay}" ClipToBounds="False" StrokeThickness="2">
<Path Data="{Binding DisplayGeometry, Mode=OneWay}" ClipToBounds="False" StrokeThickness="1">
<Path.Style>
<Style TargetType="{x:Type Path}">
<Style.Triggers>
@ -83,10 +83,10 @@
</Style>
</Path.Style>
<Path.Fill>
<SolidColorBrush Opacity="0.65" />
<SolidColorBrush Opacity="0.25" />
</Path.Fill>
<Path.Stroke>
<SolidColorBrush />
<SolidColorBrush Opacity="0.5"/>
</Path.Stroke>
</Path>
</Canvas>

View File

@ -46,17 +46,17 @@
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<ToolBarTray Orientation="Vertical" Width="58">
<ToolBar Style="{DynamicResource MaterialDesignToolBar}" ClipToBounds="False" ToolBarTray.IsLocked="True" >
<ToolBar Style="{DynamicResource MaterialDesignToolBar}" ClipToBounds="False" ToolBarTray.IsLocked="True">
<ListBox SelectedIndex="{Binding ActiveToolIndex}" ToolBar.OverflowMode="Never">
<ListBox.ItemsPanel>
<ItemsPanelTemplate>
<StackPanel Orientation="Vertical"/>
<StackPanel Orientation="Vertical" />
</ItemsPanelTemplate>
</ListBox.ItemsPanel>
<ListBoxItem ToolTip="Pan over different parts of the surface" >
<ListBoxItem ToolTip="Pan over different parts of the surface">
<materialDesign:PackIcon Kind="HandLeft" />
</ListBoxItem>
<ListBoxItem ToolTip="Change layer selection" >
<ListBoxItem ToolTip="Change layer selection">
<materialDesign:PackIcon Kind="SelectionDrag" />
</ListBoxItem>
<ListBoxItem ToolTip="Add to layer selection">
@ -84,11 +84,9 @@
</ToolBarTray>
<Grid Grid.Column="1"
ClipToBounds="True"
KeyUp="{s:Action CanvasKeyUp}"
KeyDown="{s:Action CanvasKeyDown}"
MouseWheel="{s:Action CanvasMouseWheel}"
MouseUp="{s:Action CanvasMouseDown}"
MouseDown="{s:Action CanvasMouseUp}"
MouseUp="{s:Action CanvasMouseUp}"
MouseDown="{s:Action CanvasMouseDown}"
MouseMove="{s:Action CanvasMouseMove}"
Cursor="{Binding ActiveToolViewModel.Cursor}"
utilities:SizeObserver.Observe="True"
@ -123,7 +121,7 @@
<TranslateTransform X="{Binding PanZoomViewModel.PanX}" Y="{Binding PanZoomViewModel.PanY}" />
</TransformGroup>
</Grid.RenderTransform>
<ItemsControl ItemsSource="{Binding Devices}">
<ItemsControl ItemsSource="{Binding CanvasViewModels}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<Canvas />

View File

@ -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<MainWindowFocusChangedEvent>
public class ProfileViewModel : ProfileEditorPanelViewModel, IHandle<MainWindowFocusChangedEvent>, IHandle<MainWindowKeyEvent>
{
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
}
}

View File

@ -8,23 +8,30 @@
xmlns:materialDesign="http://materialdesigninxaml.net/winfx/xaml/themes"
mc:Ignorable="d"
d:DesignHeight="450" d:DesignWidth="800">
<Canvas>
<Canvas.Triggers>
<EventTrigger RoutedEvent="Canvas.MouseLeftButtonDown">
<BeginStoryboard>
<Storyboard TargetName="Preview" TargetProperty="Opacity">
<DoubleAnimation From="0" To="1" Duration="0:0:0.1" />
</Storyboard>
</BeginStoryboard>
</EventTrigger>
<EventTrigger RoutedEvent="Canvas.MouseLeftButtonUp">
<BeginStoryboard>
<Storyboard TargetName="Preview" TargetProperty="Opacity">
<DoubleAnimation From="1" To="0" Duration="0:0:0.2" />
</Storyboard>
</BeginStoryboard>
</EventTrigger>
</Canvas.Triggers>
<UserControl.Resources>
<Style TargetType="Shape" x:Key="ShowIfMouseDown">
<Style.Triggers>
<DataTrigger Binding="{Binding IsMouseDown}" Value="True">
<DataTrigger.EnterActions>
<BeginStoryboard>
<Storyboard TargetProperty="Opacity">
<DoubleAnimation From="0" To="1" Duration="0:0:0.1" />
</Storyboard>
</BeginStoryboard>
</DataTrigger.EnterActions>
<DataTrigger.ExitActions>
<BeginStoryboard>
<Storyboard TargetProperty="Opacity">
<DoubleAnimation From="1" To="0" Duration="0:0:0.2" />
</Storyboard>
</BeginStoryboard>
</DataTrigger.ExitActions>
</DataTrigger>
</Style.Triggers>
</Style>
</UserControl.Resources>
<Canvas Background="Transparent" Width="50" Height="50">
<Canvas.ContextMenu>
<ContextMenu>
<MenuItem Header="Ellipse action 1" Command="{s:Action CreateLayer}" CommandParameter="{Binding}">
@ -39,7 +46,14 @@
</MenuItem>
</ContextMenu>
</Canvas.ContextMenu>
<Ellipse Stroke="{DynamicResource PrimaryHueLightBrush}" StrokeThickness="1" IsHitTestVisible="False" x:Name="Preview" Opacity="1" Width="100" Height="100">
<Ellipse Style="{StaticResource ShowIfMouseDown}"
Width="{Binding DragRectangle.Width}"
Height="{Binding DragRectangle.Height}"
Canvas.Left="{Binding DragRectangle.X}"
Canvas.Top="{Binding DragRectangle.Y}"
Stroke="{DynamicResource PrimaryHueLightBrush}"
StrokeThickness="1"
Opacity="0">
<Ellipse.Fill>
<SolidColorBrush Color="{DynamicResource Primary400}" Opacity="0.25" />
</Ellipse.Fill>

View File

@ -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;
}
}
}

View File

@ -4,9 +4,59 @@
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:materialDesign="http://materialdesigninxaml.net/winfx/xaml/themes"
xmlns:s="https://github.com/canton7/Stylet"
mc:Ignorable="d"
d:DesignHeight="450" d:DesignWidth="800">
<Grid>
<UserControl.Resources>
<Style TargetType="Shape" x:Key="ShowIfMouseDown">
<Style.Triggers>
<DataTrigger Binding="{Binding IsMouseDown}" Value="True">
<DataTrigger.EnterActions>
<BeginStoryboard>
<Storyboard TargetProperty="Opacity">
<DoubleAnimation From="0" To="1" Duration="0:0:0.1" />
</Storyboard>
</BeginStoryboard>
</DataTrigger.EnterActions>
<DataTrigger.ExitActions>
<BeginStoryboard>
<Storyboard TargetProperty="Opacity">
<DoubleAnimation From="1" To="0" Duration="0:0:0.2" />
</Storyboard>
</BeginStoryboard>
</DataTrigger.ExitActions>
</DataTrigger>
</Style.Triggers>
</Style>
</UserControl.Resources>
<Canvas Background="Transparent" Width="50" Height="50">
</Grid>
<Canvas.ContextMenu>
<ContextMenu>
<MenuItem Header="Ellipse action 1" Command="{s:Action CreateLayer}" CommandParameter="{Binding}">
<MenuItem.Icon>
<materialDesign:PackIcon Kind="LayersPlus" />
</MenuItem.Icon>
</MenuItem>
<MenuItem Header="Ellipse action 2" Command="{s:Action ApplyToLayer}" CommandParameter="{Binding}">
<MenuItem.Icon>
<materialDesign:PackIcon Kind="Selection" />
</MenuItem.Icon>
</MenuItem>
</ContextMenu>
</Canvas.ContextMenu>
<Rectangle Style="{StaticResource ShowIfMouseDown}"
Width="{Binding DragRectangle.Width}"
Height="{Binding DragRectangle.Height}"
Canvas.Left="{Binding DragRectangle.X}"
Canvas.Top="{Binding DragRectangle.Y}"
Stroke="{DynamicResource PrimaryHueLightBrush}"
StrokeThickness="1"
Opacity="0">
<Rectangle.Fill>
<SolidColorBrush Color="{DynamicResource Primary400}" Opacity="0.25" />
</Rectangle.Fill>
</Rectangle>
</Canvas>
</UserControl>

View File

@ -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;
}
}
}

View File

@ -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">
<Grid>
<UserControl.Resources>
<Style TargetType="Shape" x:Key="ShowIfMouseDown">
<Style.Triggers>
<DataTrigger Binding="{Binding IsMouseDown}" Value="True">
<DataTrigger.EnterActions>
<BeginStoryboard>
<Storyboard TargetProperty="Opacity">
<DoubleAnimation From="0" To="1" Duration="0:0:0.1" />
</Storyboard>
</BeginStoryboard>
</DataTrigger.EnterActions>
<DataTrigger.ExitActions>
<BeginStoryboard>
<Storyboard TargetProperty="Opacity">
<DoubleAnimation From="1" To="0" Duration="0:0:0.2" />
</Storyboard>
</BeginStoryboard>
</DataTrigger.ExitActions>
</DataTrigger>
</Style.Triggers>
</Style>
</UserControl.Resources>
<Canvas Background="Transparent" Width="50" Height="50">
</Grid>
<Canvas.ContextMenu>
<ContextMenu>
<MenuItem Header="Ellipse action 1" Command="{s:Action CreateLayer}" CommandParameter="{Binding}">
<MenuItem.Icon>
<materialDesign:PackIcon Kind="LayersPlus" />
</MenuItem.Icon>
</MenuItem>
<MenuItem Header="Ellipse action 2" Command="{s:Action ApplyToLayer}" CommandParameter="{Binding}">
<MenuItem.Icon>
<materialDesign:PackIcon Kind="Selection" />
</MenuItem.Icon>
</MenuItem>
</ContextMenu>
</Canvas.ContextMenu>
<Rectangle Style="{StaticResource ShowIfMouseDown}"
Width="{Binding DragRectangle.Width}"
Height="{Binding DragRectangle.Height}"
Canvas.Left="{Binding DragRectangle.X}"
Canvas.Top="{Binding DragRectangle.Y}"
Stroke="{DynamicResource PrimaryHueLightBrush}"
StrokeDashArray="4 4"
StrokeThickness="1"
Opacity="0">
<Rectangle.Fill>
<SolidColorBrush Color="{DynamicResource Primary400}" Opacity="0.25" />
</Rectangle.Fill>
</Rectangle>
</Canvas>
</UserControl>

View File

@ -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<ArtemisLed>();
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;
}
}
}

View File

@ -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);
}
}
}

View File

@ -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);
}
}
}

View File

@ -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}">

View File

@ -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));
}
}
}

View File

@ -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()

View File

@ -1,47 +0,0 @@
using System.Windows;
using System.Windows.Controls;
using System.Windows.Interactivity;
namespace Artemis.UI.Utilities
{
public class BindableSelectedItemBehavior : Behavior<TreeView>
{
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<object> 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
}
}