Profile editor - Moved files

This commit is contained in:
SpoinkyNL committed 2020-08-15 17:53:12 +02:00
1 parent 0516ccc658
commit 698d1b8bbd
86 files changed
+199 -215

No files matched your search

@@ -0,0 +1,36 @@
using System;
using Stylet;
namespace Artemis.UI.Screens.ProfileEditor.Visualization
{
public abstract class CanvasViewModel : PropertyChangedBase, IDisposable
{
private double _x;
private double _y;
public double X
{
get => _x;
set => SetAndNotify(ref _x, value);
}
public double Y
{
get => _y;
set => SetAndNotify(ref _y, value);
}
protected virtual void Dispose(bool disposing)
{
if (disposing)
{
}
}
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
}
}
@@ -0,0 +1,62 @@
<UserControl x:Class="Artemis.UI.Screens.ProfileEditor.Visualization.ProfileDeviceView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:s="https://github.com/canton7/Stylet"
xmlns:converters="clr-namespace:Artemis.UI.Converters"
xmlns:visualization="clr-namespace:Artemis.UI.Screens.ProfileEditor.Visualization"
xmlns:materialDesign="http://materialdesigninxaml.net/winfx/xaml/themes"
xmlns:visualization1="clr-namespace:Artemis.UI.Screens.ProfileEditor.Visualization"
mc:Ignorable="d"
d:DesignHeight="450"
d:DesignWidth="800"
d:DataContext="{d:DesignInstance {x:Type visualization1:ProfileDeviceViewModel}}">
<UserControl.Resources>
<converters:NullToImageConverter x:Key="NullToImageConverter" />
<converters:NullToVisibilityConverter x:Key="NullToVisibilityConverter" />
</UserControl.Resources>
<Grid Width="{Binding Device.RgbDevice.ActualSize.Width}" Height="{Binding Device.RgbDevice.ActualSize.Height}">
<Grid.RenderTransform>
<RotateTransform Angle="{Binding Device.Rotation}" />
</Grid.RenderTransform>
<!-- Device image with fallback -->
<Image VerticalAlignment="Stretch"
HorizontalAlignment="Stretch"
Source="{Binding Device.RgbDevice.DeviceInfo.Image, Converter={StaticResource NullToImageConverter}}" />
<Rectangle Fill="{DynamicResource MaterialDesignCardBackground}"
Stroke="{DynamicResource MaterialDesignTextBoxBorder}"
Margin="-5"
StrokeThickness="1"
Visibility="{Binding Device.RgbDevice.DeviceInfo.Image, ConverterParameter=Inverted, Converter={StaticResource NullToVisibilityConverter}}" />
<TextBlock Text="{Binding Device.RgbDevice.DeviceInfo.DeviceName}"
Visibility="{Binding Device.RgbDevice.DeviceInfo.Image, ConverterParameter=Inverted, Converter={StaticResource NullToVisibilityConverter}}"
HorizontalAlignment="Center"
VerticalAlignment="Center"
TextWrapping="Wrap"
TextAlignment="Center" />
<!-- LEDs -->
<ItemsControl ItemsSource="{Binding Leds}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<Canvas />
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemContainerStyle>
<Style TargetType="{x:Type ContentPresenter}">
<Setter Property="Canvas.Left" Value="{Binding X}" />
<Setter Property="Canvas.Top" Value="{Binding Y}" />
</Style>
</ItemsControl.ItemContainerStyle>
<ItemsControl.ItemTemplate>
<DataTemplate>
<ContentControl Width="{Binding Width}" Height="{Binding Height}" s:View.Model="{Binding}" />
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</Grid>
</UserControl>
@@ -0,0 +1,96 @@
using System.Collections.ObjectModel;
using System.Linq;
using System.Threading.Tasks;
using System.Windows;
using Artemis.Core.Models.Surface;
using Stylet;
namespace Artemis.UI.Screens.ProfileEditor.Visualization
{
public class ProfileDeviceViewModel : CanvasViewModel
{
private ObservableCollection<ProfileLedViewModel> _leds;
private ArtemisDevice _device;
private bool _addedLeds;
public ProfileDeviceViewModel(ArtemisDevice device)
{
Device = device;
Leds = new ObservableCollection<ProfileLedViewModel>();
Task.Run(AddLedsAsync);
}
public ObservableCollection<ProfileLedViewModel> Leds
{
get => _leds;
set => SetAndNotify(ref _leds, value);
}
public ArtemisDevice Device
{
get => _device;
set => SetAndNotify(ref _device, value);
}
public bool AddedLeds
{
get => _addedLeds;
private set => SetAndNotify(ref _addedLeds, value);
}
public new double X
{
get => Device.X;
set => Device.X = value;
}
public new double Y
{
get => Device.Y;
set => Device.Y = value;
}
public int ZIndex
{
get => Device.ZIndex;
set => Device.ZIndex = value;
}
public Rect DeviceRectangle => Device.RgbDevice == null
? new Rect()
: new Rect(X, Y, Device.RgbDevice.Size.Width, Device.RgbDevice.Size.Height);
/// <summary>
/// Update the color of all LEDs if finished adding
/// </summary>
public void Update()
{
if (!AddedLeds)
return;
foreach (var ledViewModel in Leds)
ledViewModel.Update();
}
/// <summary>
/// Adds LEDs in batches of 5 to avoid UI freezes
/// </summary>
/// <returns></returns>
private async Task AddLedsAsync()
{
var index = 0;
foreach (var led in Device.Leds.ToList())
{
Execute.OnUIThreadSync(() => Leds.Add(new ProfileLedViewModel(led)));
if (index % 5 == 0)
await Task.Delay(1);
index++;
}
AddedLeds = true;
}
}
}
@@ -0,0 +1,62 @@
<UserControl x:Class="Artemis.UI.Screens.ProfileEditor.Visualization.ProfileLayerView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:local="clr-namespace:Artemis.UI.Screens.ProfileEditor.Visualization"
xmlns:s="https://github.com/canton7/Stylet"
xmlns:visualization="clr-namespace:Artemis.UI.Screens.ProfileEditor.Visualization"
mc:Ignorable="d"
d:DesignHeight="450"
d:DesignWidth="800"
d:DataContext="{d:DesignInstance {x:Type visualization:ProfileLayerViewModel}}">
<UserControl.Resources>
<Style TargetType="{x:Type Path}" x:Key="SelectedShapeStyle">
<Style.Triggers>
<DataTrigger Binding="{Binding IsSelected}" Value="False">
<DataTrigger.EnterActions>
<BeginStoryboard>
<Storyboard>
<ColorAnimation Storyboard.TargetProperty="(Path.Stroke).(SolidColorBrush.Color)" To="#99808080" Duration="0:0:0.5" />
</Storyboard>
</BeginStoryboard>
</DataTrigger.EnterActions>
<DataTrigger.ExitActions>
<BeginStoryboard>
<Storyboard>
<ColorAnimation Storyboard.TargetProperty="(Path.Stroke).(SolidColorBrush.Color)" To="{StaticResource Accent700}" Duration="0:0:0.5" />
</Storyboard>
</BeginStoryboard>
</DataTrigger.ExitActions>
</DataTrigger>
</Style.Triggers>
</Style>
<Style TargetType="{x:Type Path}" x:Key="SelectedLayerStyle">
<Style.Triggers>
<DataTrigger Binding="{Binding IsSelected}" Value="False">
<DataTrigger.EnterActions>
<BeginStoryboard>
<Storyboard>
<ColorAnimation Storyboard.TargetProperty="(Path.Stroke).(SolidColorBrush.Color)" To="#A7A7A7" Duration="0:0:0.5" />
</Storyboard>
</BeginStoryboard>
</DataTrigger.EnterActions>
<DataTrigger.ExitActions>
<BeginStoryboard>
<Storyboard>
<ColorAnimation Storyboard.TargetProperty="(Path.Stroke).(SolidColorBrush.Color)" To="#00A7A7A7" Duration="0:0:0.5" />
</Storyboard>
</BeginStoryboard>
</DataTrigger.ExitActions>
</DataTrigger>
</Style.Triggers>
</Style>
</UserControl.Resources>
<Canvas>
<Path Data="{Binding ShapeGeometry, Mode=OneWay}" StrokeThickness="{Binding StrokeThickness}" Style="{StaticResource SelectedShapeStyle}">
<Path.Stroke>
<SolidColorBrush Color="{StaticResource Primary700}" />
</Path.Stroke>
</Path>
</Canvas>
</UserControl>
@@ -0,0 +1,229 @@
using System;
using System.ComponentModel;
using System.Linq;
using System.Windows;
using System.Windows.Media;
using Artemis.Core.Models.Profile;
using Artemis.Core.Models.Profile.LayerShapes;
using Artemis.Core.Models.Surface;
using Artemis.UI.Extensions;
using Artemis.UI.Services.Interfaces;
using Artemis.UI.Shared.Services.Interfaces;
using Stylet;
namespace Artemis.UI.Screens.ProfileEditor.Visualization
{
public class ProfileLayerViewModel : CanvasViewModel
{
private readonly ILayerEditorService _layerEditorService;
private readonly IProfileEditorService _profileEditorService;
private readonly ProfileViewModel _profileViewModel;
private bool _isSelected;
private Geometry _shapeGeometry;
private Rect _viewportRectangle;
public ProfileLayerViewModel(Layer layer, ProfileViewModel profileViewModel, IProfileEditorService profileEditorService, ILayerEditorService layerEditorService)
{
_profileViewModel = profileViewModel;
_profileEditorService = profileEditorService;
_layerEditorService = layerEditorService;
Layer = layer;
Update();
Layer.RenderPropertiesUpdated += LayerOnRenderPropertiesUpdated;
_profileEditorService.ProfileElementSelected += OnProfileElementSelected;
_profileEditorService.SelectedProfileElementUpdated += OnSelectedProfileElementUpdated;
_profileEditorService.ProfilePreviewUpdated += ProfileEditorServiceOnProfilePreviewUpdated;
_profileViewModel.PanZoomViewModel.PropertyChanged += PanZoomViewModelOnPropertyChanged;
}
public Layer Layer { get; }
public Geometry ShapeGeometry
{
get => _shapeGeometry;
set => SetAndNotify(ref _shapeGeometry, value);
}
public Rect ViewportRectangle
{
get => _viewportRectangle;
set
{
if (!SetAndNotify(ref _viewportRectangle, value)) return;
NotifyOfPropertyChange(nameof(LayerPosition));
}
}
public double StrokeThickness
{
get
{
if (IsSelected)
return Math.Max(2 / _profileViewModel.PanZoomViewModel.Zoom, 1);
return Math.Max(2 / _profileViewModel.PanZoomViewModel.Zoom, 1) / 2;
}
}
public double LayerStrokeThickness
{
get
{
if (IsSelected)
return StrokeThickness / 2;
return StrokeThickness;
}
}
public Thickness LayerPosition => new Thickness(ViewportRectangle.Left, ViewportRectangle.Top, 0, 0);
public bool IsSelected
{
get => _isSelected;
set
{
if (!SetAndNotify(ref _isSelected, value)) return;
NotifyOfPropertyChange(nameof(StrokeThickness));
}
}
protected override void Dispose(bool disposing)
{
if (disposing)
{
Layer.RenderPropertiesUpdated -= LayerOnRenderPropertiesUpdated;
_profileEditorService.ProfileElementSelected -= OnProfileElementSelected;
_profileEditorService.SelectedProfileElementUpdated -= OnSelectedProfileElementUpdated;
_profileEditorService.ProfilePreviewUpdated -= ProfileEditorServiceOnProfilePreviewUpdated;
_profileViewModel.PanZoomViewModel.PropertyChanged -= PanZoomViewModelOnPropertyChanged;
}
base.Dispose(disposing);
}
private void PanZoomViewModelOnPropertyChanged(object sender, PropertyChangedEventArgs e)
{
if (e.PropertyName == nameof(_profileViewModel.PanZoomViewModel.Zoom))
{
NotifyOfPropertyChange(nameof(StrokeThickness));
NotifyOfPropertyChange(nameof(LayerStrokeThickness));
}
}
private void Update()
{
IsSelected = _profileEditorService.SelectedProfileElement == Layer;
CreateShapeGeometry();
CreateViewportRectangle();
}
private void CreateShapeGeometry()
{
if (Layer.LayerShape == null || !Layer.Leds.Any())
{
ShapeGeometry = Geometry.Empty;
return;
}
Execute.PostToUIThread(() =>
{
var bounds = _layerEditorService.GetLayerBounds(Layer);
var shapeGeometry = Geometry.Empty;
switch (Layer.LayerShape)
{
case Ellipse _:
shapeGeometry = new EllipseGeometry(bounds);
break;
case Rectangle _:
shapeGeometry = new RectangleGeometry(bounds);
break;
}
if (Layer.LayerBrush == null || Layer.LayerBrush.SupportsTransformation)
shapeGeometry.Transform = _layerEditorService.GetLayerTransformGroup(Layer);
ShapeGeometry = shapeGeometry;
});
}
private void CreateViewportRectangle()
{
if (!Layer.Leds.Any() || Layer.LayerShape == null)
{
ViewportRectangle = Rect.Empty;
return;
}
ViewportRectangle = _layerEditorService.GetLayerBounds(Layer);
}
private Geometry CreateRectangleGeometry(ArtemisLed led)
{
var rect = led.RgbLed.AbsoluteLedRectangle.ToWindowsRect(1);
return new RectangleGeometry(rect);
}
private Geometry CreateCircleGeometry(ArtemisLed led)
{
var rect = led.RgbLed.AbsoluteLedRectangle.ToWindowsRect(1);
return new EllipseGeometry(rect);
}
private Geometry CreateCustomGeometry(ArtemisLed led, double deflateAmount)
{
var rect = led.RgbLed.AbsoluteLedRectangle.ToWindowsRect(1);
try
{
var geometry = Geometry.Combine(
Geometry.Empty,
Geometry.Parse(led.RgbLed.ShapeData),
GeometryCombineMode.Union,
new TransformGroup
{
Children = new TransformCollection
{
new ScaleTransform(rect.Width, rect.Height),
new TranslateTransform(rect.X, rect.Y)
}
}
);
return geometry;
}
catch (Exception)
{
return CreateRectangleGeometry(led);
}
}
#region Event handlers
private void LayerOnRenderPropertiesUpdated(object sender, EventArgs e)
{
Update();
}
private void OnProfileElementSelected(object sender, EventArgs e)
{
IsSelected = _profileEditorService.SelectedProfileElement == Layer;
}
private void OnSelectedProfileElementUpdated(object sender, EventArgs e)
{
Update();
}
private void ProfileEditorServiceOnProfilePreviewUpdated(object sender, EventArgs e)
{
if (!Layer.Transform.PropertiesInitialized || !Layer.General.PropertiesInitialized)
return;
CreateShapeGeometry();
CreateViewportRectangle();
}
#endregion
}
}
@@ -0,0 +1,94 @@
<UserControl x:Class="Artemis.UI.Screens.ProfileEditor.Visualization.ProfileLedView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:converters="clr-namespace:Artemis.UI.Converters"
xmlns:visualization="clr-namespace:Artemis.UI.Screens.ProfileEditor.Visualization"
xmlns:visualization1="clr-namespace:Artemis.UI.Screens.ProfileEditor.Visualization"
mc:Ignorable="d"
d:DataContext="{d:DesignInstance {x:Type visualization1:ProfileLedViewModel}}"
d:DesignHeight="25" d:DesignWidth="25">
<UserControl.Resources>
<converters:NullToImageConverter x:Key="NullToImageConverter" />
<Style TargetType="{x:Type Path}" x:Key="DimStyle">
<Style.Triggers>
<DataTrigger Binding="{Binding IsDimmed}" Value="True">
<DataTrigger.EnterActions>
<BeginStoryboard>
<Storyboard>
<DoubleAnimation Storyboard.TargetProperty="(Shape.Opacity)" To="0.2" Duration="0:0:0.25" />
</Storyboard>
</BeginStoryboard>
</DataTrigger.EnterActions>
<DataTrigger.ExitActions>
<BeginStoryboard>
<Storyboard>
<DoubleAnimation Storyboard.TargetProperty="(Shape.Opacity)" To="1" Duration="0:0:0.25" />
</Storyboard>
</BeginStoryboard>
</DataTrigger.ExitActions>
</DataTrigger>
</Style.Triggers>
</Style>
</UserControl.Resources>
<Canvas Width="{Binding Led.RgbLed.ActualSize.Width, Mode=OneWay}" Height="{Binding Led.RgbLed.ActualSize.Height, Mode=OneWay}">
<Canvas.Background>
<ImageBrush AlignmentX="Center" AlignmentY="Center" Stretch="Fill"
ImageSource="{Binding Led.RgbLed.Image, Converter={StaticResource NullToImageConverter}, Mode=OneWay}" />
</Canvas.Background>
<Path Data="{Binding DisplayGeometry, Mode=OneWay}" ClipToBounds="False" Style="{StaticResource DimStyle}">
<Path.Fill>
<SolidColorBrush Color="{Binding DisplayColor, Mode=OneWay}" Opacity="0.333" />
</Path.Fill>
</Path>
<Path Data="{Binding StrokeGeometry, Mode=OneWay}" ClipToBounds="False" Style="{StaticResource DimStyle}">
<Path.Fill>
<SolidColorBrush Color="{Binding DisplayColor, Mode=OneWay}" />
</Path.Fill>
</Path>
<!-- Selection -->
<Path Data="{Binding DisplayGeometry, Mode=OneWay}" ClipToBounds="False" StrokeThickness="1" StrokeLineJoin="Round">
<Path.Style>
<Style TargetType="{x:Type Path}">
<Style.Triggers>
<DataTrigger Binding="{Binding IsSelected}" Value="True">
<DataTrigger.EnterActions>
<BeginStoryboard x:Name="ToSelected">
<Storyboard>
<ColorAnimation
Storyboard.TargetProperty="(Shape.Fill).(SolidColorBrush.Color)"
To="{StaticResource Accent700}" Duration="0:0:0.25" />
<ColorAnimation
Storyboard.TargetProperty="(Shape.Stroke).(SolidColorBrush.Color)"
To="{StaticResource Accent400}" Duration="0:0:0.25" />
</Storyboard>
</BeginStoryboard>
</DataTrigger.EnterActions>
<DataTrigger.ExitActions>
<BeginStoryboard>
<Storyboard>
<ColorAnimation
Storyboard.TargetProperty="(Shape.Fill).(SolidColorBrush.Color)"
To="Transparent" Duration="0:0:0.25" />
<ColorAnimation
Storyboard.TargetProperty="(Shape.Stroke).(SolidColorBrush.Color)"
To="Transparent" Duration="0:0:0.25" />
</Storyboard>
</BeginStoryboard>
</DataTrigger.ExitActions>
</DataTrigger>
</Style.Triggers>
</Style>
</Path.Style>
<Path.Fill>
<SolidColorBrush Opacity="0.25" />
</Path.Fill>
<Path.Stroke>
<SolidColorBrush Opacity="0.5" />
</Path.Stroke>
</Path>
</Canvas>
</UserControl>
@@ -0,0 +1,148 @@
using System;
using System.Windows;
using System.Windows.Media;
using Artemis.Core.Models.Surface;
using Artemis.UI.Extensions;
using RGB.NET.Core;
using Stylet;
using Color = System.Windows.Media.Color;
namespace Artemis.UI.Screens.ProfileEditor.Visualization
{
public class ProfileLedViewModel : PropertyChangedBase
{
private bool _isSelected;
private bool _isDimmed;
private Geometry _displayGeometry;
private Geometry _strokeGeometry;
private Color _displayColor;
public ProfileLedViewModel(ArtemisLed led)
{
Led = led;
// Don't want ActualLocation here since rotation is done in XAML
X = led.RgbLed.Location.X * led.RgbLed.Device.Scale.Horizontal;
Y = led.RgbLed.Location.Y * led.RgbLed.Device.Scale.Vertical;
Width = led.RgbLed.ActualSize.Width;
Height = led.RgbLed.ActualSize.Height;
Execute.PostToUIThread(CreateLedGeometry);
}
public ArtemisLed Led { get; }
public double X { get; }
public double Y { get; }
public double Width { get; }
public double Height { get; }
public bool IsSelected
{
get => _isSelected;
set => SetAndNotify(ref _isSelected, value);
}
public bool IsDimmed
{
get => _isDimmed;
set => SetAndNotify(ref _isDimmed, value);
}
public Geometry DisplayGeometry
{
get => _displayGeometry;
private set => SetAndNotify(ref _displayGeometry, value);
}
public Geometry StrokeGeometry
{
get => _strokeGeometry;
private set => SetAndNotify(ref _strokeGeometry, value);
}
public Color DisplayColor
{
get => _displayColor;
private set => SetAndNotify(ref _displayColor, value);
}
public void Update()
{
var newColor = Led.RgbLed.Color.ToMediaColor();
Execute.PostToUIThread(() =>
{
if (!DisplayColor.Equals(newColor))
DisplayColor = newColor;
});
}
private void CreateLedGeometry()
{
switch (Led.RgbLed.Shape)
{
case Shape.Custom:
if (Led.RgbLed.Device.DeviceInfo.DeviceType == RGBDeviceType.Keyboard || Led.RgbLed.Device.DeviceInfo.DeviceType == RGBDeviceType.Keypad)
CreateCustomGeometry(2.0);
else
CreateCustomGeometry(1.0);
break;
case Shape.Rectangle:
if (Led.RgbLed.Device.DeviceInfo.DeviceType == RGBDeviceType.Keyboard || Led.RgbLed.Device.DeviceInfo.DeviceType == RGBDeviceType.Keypad)
CreateKeyCapGeometry();
else
CreateRectangleGeometry();
break;
case Shape.Circle:
CreateCircleGeometry();
break;
default:
throw new ArgumentOutOfRangeException();
}
// Stroke geometry is the display geometry excluding the inner geometry
StrokeGeometry = DisplayGeometry.GetWidenedPathGeometry(new Pen(null, 1.0), 0.1, ToleranceType.Absolute);
DisplayGeometry.Freeze();
StrokeGeometry.Freeze();
}
private void CreateRectangleGeometry()
{
DisplayGeometry = new RectangleGeometry(new Rect(0.5, 0.5, Width - 1, Height - 1));
}
private void CreateCircleGeometry()
{
DisplayGeometry = new EllipseGeometry(new Rect(0.5, 0.5, Width - 1, Height - 1));
}
private void CreateKeyCapGeometry()
{
DisplayGeometry = new RectangleGeometry(new Rect(1, 1, Width - 2, Height - 2), 1.6, 1.6);
}
private void CreateCustomGeometry(double deflateAmount)
{
try
{
DisplayGeometry = Geometry.Combine(
Geometry.Empty,
Geometry.Parse(Led.RgbLed.ShapeData),
GeometryCombineMode.Union,
new TransformGroup
{
Children = new TransformCollection
{
new ScaleTransform(Width - deflateAmount, Height - deflateAmount),
new TranslateTransform(deflateAmount / 2, deflateAmount / 2)
}
}
);
}
catch (Exception)
{
CreateRectangleGeometry();
}
}
}
}
@@ -0,0 +1,204 @@
<UserControl x:Class="Artemis.UI.Screens.ProfileEditor.Visualization.ProfileView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:s="https://github.com/canton7/Stylet"
xmlns:materialDesign="http://materialdesigninxaml.net/winfx/xaml/themes"
xmlns:profileEditor="clr-namespace:Artemis.UI.Screens.ProfileEditor.Visualization"
xmlns:utilities="clr-namespace:Artemis.UI.Shared.Utilities;assembly=Artemis.UI.Shared"
xmlns:controls="clr-namespace:Artemis.UI.Shared.Controls;assembly=Artemis.UI.Shared"
xmlns:visualization="clr-namespace:Artemis.UI.Screens.ProfileEditor.Visualization"
mc:Ignorable="d"
d:DesignHeight="510.9" d:DesignWidth="800"
d:DataContext="{d:DesignInstance {x:Type visualization:ProfileViewModel}}">
<UserControl.Resources>
<Style TargetType="Grid" x:Key="InitializingFade">
<Style.Triggers>
<DataTrigger Binding="{Binding IsInitializing}" Value="False">
<DataTrigger.EnterActions>
<BeginStoryboard>
<Storyboard>
<DoubleAnimation Storyboard.TargetProperty="Opacity" From="1.0" To="0.0" Duration="0:0:0.5">
<DoubleAnimation.EasingFunction>
<QuadraticEase EasingMode="EaseInOut" />
</DoubleAnimation.EasingFunction>
</DoubleAnimation>
<ObjectAnimationUsingKeyFrames BeginTime="0:0:0.5" Storyboard.TargetProperty="Visibility">
<DiscreteObjectKeyFrame KeyTime="00:00:00" Value="{x:Static Visibility.Hidden}" />
</ObjectAnimationUsingKeyFrames>
</Storyboard>
</BeginStoryboard>
</DataTrigger.EnterActions>
<DataTrigger.ExitActions>
<BeginStoryboard>
<Storyboard>
<DoubleAnimation Storyboard.TargetProperty="Opacity" From="0.0" To="1.0" />
</Storyboard>
</BeginStoryboard>
</DataTrigger.ExitActions>
</DataTrigger>
</Style.Triggers>
</Style>
</UserControl.Resources>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<ToolBarTray Orientation="Vertical" Width="58">
<ToolBar Style="{DynamicResource MaterialDesignToolBar}" ClipToBounds="False" ToolBarTray.IsLocked="True">
<ListBox SelectedIndex="{Binding ActiveToolIndex}" ToolBar.OverflowMode="Never">
<ListBox.ItemsPanel>
<ItemsPanelTemplate>
<StackPanel Orientation="Vertical" />
</ItemsPanelTemplate>
</ListBox.ItemsPanel>
<ListBoxItem ToolTip="Pan over different parts of the surface">
<materialDesign:PackIcon Kind="HandLeft" />
</ListBoxItem>
<ListBoxItem ToolTip="Edit shape in layer" IsEnabled="{Binding CanSelectEditTool}">
<materialDesign:PackIcon Kind="Edit" />
</ListBoxItem>
<ListBoxItem ToolTip="Change layer selection (hold SHIFT to add to existing selection)">
<materialDesign:PackIcon Kind="SelectionDrag" />
</ListBoxItem>
<ListBoxItem ToolTip="Remove from layer selection">
<materialDesign:PackIcon Kind="SelectOff" />
</ListBoxItem>
</ListBox>
</ToolBar>
</ToolBarTray>
<Grid Grid.Column="1"
ClipToBounds="True"
MouseWheel="{s:Action CanvasMouseWheel}"
MouseUp="{s:Action CanvasMouseUp}"
MouseDown="{s:Action CanvasMouseDown}"
MouseMove="{s:Action CanvasMouseMove}"
Cursor="{Binding ActiveToolViewModel.Cursor}"
utilities:SizeObserver.Observe="True"
utilities:SizeObserver.ObservedWidth="{Binding PanZoomViewModel.CanvasWidth, Mode=OneWayToSource}"
utilities:SizeObserver.ObservedHeight="{Binding PanZoomViewModel.CanvasHeight, Mode=OneWayToSource}">
<Grid.Background>
<VisualBrush TileMode="Tile" Stretch="Uniform" Viewport="{Binding PanZoomViewModel.BackgroundViewport}" ViewportUnits="Absolute">
<VisualBrush.Visual>
<Grid Width="25" Height="25">
<Grid.RowDefinitions>
<RowDefinition />
<RowDefinition />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition />
<ColumnDefinition />
</Grid.ColumnDefinitions>
<Rectangle Grid.Row="0" Grid.Column="0" Fill="Black" Opacity="0.15" />
<Rectangle Grid.Row="0" Grid.Column="1" />
<Rectangle Grid.Row="1" Grid.Column="0" />
<Rectangle Grid.Row="1" Grid.Column="1" Fill="Black" Opacity="0.15" />
</Grid>
</VisualBrush.Visual>
</VisualBrush>
</Grid.Background>
<Grid Name="DeviceDisplayGrid">
<Grid.RenderTransform>
<TransformGroup>
<ScaleTransform ScaleX="{Binding PanZoomViewModel.Zoom}" ScaleY="{Binding PanZoomViewModel.Zoom}" />
<TranslateTransform X="{Binding PanZoomViewModel.PanX}" Y="{Binding PanZoomViewModel.PanY}" />
</TransformGroup>
</Grid.RenderTransform>
<ItemsControl ItemsSource="{Binding Devices}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<Canvas />
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemContainerStyle>
<Style TargetType="ContentPresenter">
<Setter Property="Canvas.Left" Value="{Binding X}" />
<Setter Property="Canvas.Top" Value="{Binding Y}" />
</Style>
</ItemsControl.ItemContainerStyle>
<ItemsControl.ItemTemplate>
<DataTemplate>
<controls:DeviceVisualizer Device="{Binding}"
ShowColors="True"
HighlightedLeds="{Binding DataContext.HighlightedLeds, RelativeSource={RelativeSource AncestorType={x:Type UserControl}}, Mode=OneWay}" />
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</Grid>
<Grid Name="EditorDisplayGrid">
<Grid.RenderTransform>
<TransformGroup>
<ScaleTransform ScaleX="{Binding PanZoomViewModel.Zoom}" ScaleY="{Binding PanZoomViewModel.Zoom}" />
<TranslateTransform X="{Binding PanZoomViewModel.PanX}" Y="{Binding PanZoomViewModel.PanY}" />
</TransformGroup>
</Grid.RenderTransform>
<ItemsControl ItemsSource="{Binding CanvasViewModels}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<Canvas />
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemContainerStyle>
<Style TargetType="ContentPresenter">
<Setter Property="Canvas.Left" Value="{Binding X}" />
<Setter Property="Canvas.Top" Value="{Binding Y}" />
</Style>
</ItemsControl.ItemContainerStyle>
<ItemsControl.ItemTemplate>
<DataTemplate>
<ContentControl s:View.Model="{Binding}" />
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</Grid>
<StackPanel ZIndex="1" VerticalAlignment="Bottom" HorizontalAlignment="Left" Margin="10">
<materialDesign:Card Padding="8">
<StackPanel Orientation="Horizontal">
<CheckBox Style="{StaticResource MaterialDesignCheckBox}" IsChecked="{Binding OnlyShowSelectedShape.Value}" IsEnabled="False">
Only show shape of selected layer (NYI)
</CheckBox>
<CheckBox Style="{StaticResource MaterialDesignCheckBox}" Margin="10 0 0 0" IsChecked="{Binding HighlightSelectedLayer.Value}">
Highlight LEDs of selected layer
</CheckBox>
</StackPanel>
</materialDesign:Card>
</StackPanel>
<StackPanel Orientation="Vertical" VerticalAlignment="Bottom" HorizontalAlignment="Right"
Margin="10" ZIndex="1">
<Slider Margin="0,0,14,0"
Orientation="Vertical"
Minimum="10"
Maximum="250"
Height="100"
FocusVisualStyle="{x:Null}"
Value="{Binding PanZoomViewModel.ZoomPercentage}"
Style="{StaticResource MaterialDesignDiscreteSlider}" />
<Button Command="{s:Action ResetZoomAndPan}"
Style="{StaticResource MaterialDesignFloatingActionMiniButton}"
HorizontalAlignment="Right"
ToolTip="Reset zoom &amp; position">
<materialDesign:PackIcon Kind="ImageFilterCenterFocus" Height="24" Width="24" />
</Button>
</StackPanel>
<!-- Loading indicator -->
<Grid Background="{StaticResource MaterialDesignPaper}" Style="{StaticResource InitializingFade}" d:IsHidden="True">
<StackPanel HorizontalAlignment="Center" VerticalAlignment="Center">
<TextBlock FontSize="16" HorizontalAlignment="Center" VerticalAlignment="Center">
Initializing LED visualization...
</TextBlock>
<ProgressBar Style="{StaticResource MaterialDesignCircularProgressBar}" Value="0" IsIndeterminate="True" />
</StackPanel>
</Grid>
</Grid>
</Grid>
</UserControl>
@@ -0,0 +1,464 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Windows;
using System.Windows.Input;
using Artemis.Core.Events;
using Artemis.Core.Models.Profile;
using Artemis.Core.Models.Surface;
using Artemis.Core.Plugins.Models;
using Artemis.Core.Services;
using Artemis.Core.Services.Storage.Interfaces;
using Artemis.UI.Events;
using Artemis.UI.Extensions;
using Artemis.UI.Ninject.Factories;
using Artemis.UI.Screens.ProfileEditor.Visualization.Tools;
using Artemis.UI.Screens.Shared;
using Artemis.UI.Shared.Services.Interfaces;
using Stylet;
namespace Artemis.UI.Screens.ProfileEditor.Visualization
{
public class ProfileViewModel : ProfileEditorPanelViewModel, IHandle<MainWindowFocusChangedEvent>, IHandle<MainWindowKeyEvent>
{
private readonly IProfileEditorService _profileEditorService;
private readonly IProfileLayerVmFactory _profileLayerVmFactory;
private readonly ISettingsService _settingsService;
private readonly ISurfaceService _surfaceService;
private readonly IVisualizationToolVmFactory _visualizationToolVmFactory;
private int _activeToolIndex;
private VisualizationToolViewModel _activeToolViewModel;
private bool _canApplyToLayer;
private bool _canSelectEditTool;
private BindableCollection<CanvasViewModel> _canvasViewModels;
private BindableCollection<ArtemisDevice> _devices;
private BindableCollection<ArtemisLed> _highlightedLeds;
private PluginSetting<bool> _highlightSelectedLayer;
private bool _isInitializing;
private PluginSetting<bool> _onlyShowSelectedShape;
private PanZoomViewModel _panZoomViewModel;
private Layer _previousSelectedLayer;
private int _previousTool;
private BindableCollection<ArtemisLed> _selectedLeds;
public ProfileViewModel(IProfileEditorService profileEditorService,
ISurfaceService surfaceService,
ISettingsService settingsService,
IEventAggregator eventAggregator,
IVisualizationToolVmFactory visualizationToolVmFactory,
IProfileLayerVmFactory profileLayerVmFactory)
{
_profileEditorService = profileEditorService;
_surfaceService = surfaceService;
_settingsService = settingsService;
_visualizationToolVmFactory = visualizationToolVmFactory;
_profileLayerVmFactory = profileLayerVmFactory;
Execute.OnUIThreadSync(() =>
{
PanZoomViewModel = new PanZoomViewModel {LimitToZero = false};
CanvasViewModels = new BindableCollection<CanvasViewModel>();
Devices = new BindableCollection<ArtemisDevice>();
HighlightedLeds = new BindableCollection<ArtemisLed>();
SelectedLeds = new BindableCollection<ArtemisLed>();
});
ApplySurfaceConfiguration(_surfaceService.ActiveSurface);
ActivateToolByIndex(0);
eventAggregator.Subscribe(this);
}
public bool IsInitializing
{
get => _isInitializing;
private set => SetAndNotify(ref _isInitializing, value);
}
public bool CanSelectEditTool
{
get => _canSelectEditTool;
set => SetAndNotify(ref _canSelectEditTool, value);
}
public PanZoomViewModel PanZoomViewModel
{
get => _panZoomViewModel;
set => SetAndNotify(ref _panZoomViewModel, value);
}
public BindableCollection<CanvasViewModel> CanvasViewModels
{
get => _canvasViewModels;
set => SetAndNotify(ref _canvasViewModels, value);
}
public BindableCollection<ArtemisDevice> Devices
{
get => _devices;
set => SetAndNotify(ref _devices, value);
}
public BindableCollection<ArtemisLed> HighlightedLeds
{
get => _highlightedLeds;
set => SetAndNotify(ref _highlightedLeds, value);
}
public BindableCollection<ArtemisLed> SelectedLeds
{
get => _selectedLeds;
set => SetAndNotify(ref _selectedLeds, value);
}
public PluginSetting<bool> OnlyShowSelectedShape
{
get => _onlyShowSelectedShape;
set => SetAndNotify(ref _onlyShowSelectedShape, value);
}
public PluginSetting<bool> HighlightSelectedLayer
{
get => _highlightSelectedLayer;
set => SetAndNotify(ref _highlightSelectedLayer, value);
}
public VisualizationToolViewModel ActiveToolViewModel
{
get => _activeToolViewModel;
private set
{
// Remove the tool from the canvas
if (_activeToolViewModel != null)
{
lock (CanvasViewModels)
{
CanvasViewModels.Remove(_activeToolViewModel);
NotifyOfPropertyChange(() => CanvasViewModels);
}
}
// Set the new tool
SetAndNotify(ref _activeToolViewModel, value);
// Add the new tool to the canvas
if (_activeToolViewModel != null)
{
lock (CanvasViewModels)
{
CanvasViewModels.Add(_activeToolViewModel);
NotifyOfPropertyChange(() => CanvasViewModels);
}
}
}
}
public int ActiveToolIndex
{
get => _activeToolIndex;
set
{
if (!SetAndNotify(ref _activeToolIndex, value)) return;
ActivateToolByIndex(value);
}
}
public bool CanApplyToLayer
{
get => _canApplyToLayer;
set => SetAndNotify(ref _canApplyToLayer, value);
}
public List<ArtemisLed> GetLedsInRectangle(Rect selectedRect)
{
return Devices.SelectMany(d => d.Leds)
.Where(led => led.RgbLed.AbsoluteLedRectangle.ToWindowsRect(1).IntersectsWith(selectedRect))
.ToList();
}
protected override void OnInitialActivate()
{
OnlyShowSelectedShape = _settingsService.GetSetting("ProfileEditor.OnlyShowSelectedShape", true);
HighlightSelectedLayer = _settingsService.GetSetting("ProfileEditor.HighlightSelectedLayer", true);
HighlightSelectedLayer.SettingChanged += HighlightSelectedLayerOnSettingChanged;
_surfaceService.ActiveSurfaceConfigurationSelected += OnActiveSurfaceConfigurationSelected;
_profileEditorService.ProfileSelected += OnProfileSelected;
_profileEditorService.ProfileElementSelected += OnProfileElementSelected;
_profileEditorService.SelectedProfileElementUpdated += OnSelectedProfileElementUpdated;
base.OnInitialActivate();
}
protected override void OnClose()
{
HighlightSelectedLayer.SettingChanged -= HighlightSelectedLayerOnSettingChanged;
_surfaceService.ActiveSurfaceConfigurationSelected -= OnActiveSurfaceConfigurationSelected;
_profileEditorService.ProfileSelected -= OnProfileSelected;
_profileEditorService.ProfileElementSelected -= OnProfileElementSelected;
_profileEditorService.SelectedProfileElementUpdated -= OnSelectedProfileElementUpdated;
if (_previousSelectedLayer != null)
_previousSelectedLayer.LayerBrushUpdated -= SelectedLayerOnLayerBrushUpdated;
OnlyShowSelectedShape.Save();
HighlightSelectedLayer.Save();
foreach (var canvasViewModel in CanvasViewModels)
canvasViewModel.Dispose();
CanvasViewModels.Clear();
base.OnClose();
}
private void OnActiveSurfaceConfigurationSelected(object sender, SurfaceConfigurationEventArgs e)
{
ApplySurfaceConfiguration(e.Surface);
}
private void ApplyActiveProfile()
{
var layerViewModels = CanvasViewModels.Where(vm => vm is ProfileLayerViewModel).Cast<ProfileLayerViewModel>().ToList();
var layers = _profileEditorService.SelectedProfile?.GetAllLayers() ?? new List<Layer>();
// Add new layers missing a VM
foreach (var layer in layers)
{
if (layerViewModels.All(vm => vm.Layer != layer))
CanvasViewModels.Add(_profileLayerVmFactory.Create(layer, this));
}
// Remove layers that no longer exist
var toRemove = layerViewModels.Where(vm => !layers.Contains(vm.Layer));
foreach (var profileLayerViewModel in toRemove)
{
profileLayerViewModel.Dispose();
CanvasViewModels.Remove(profileLayerViewModel);
}
}
protected override void OnActivate()
{
ApplyActiveProfile();
}
private void ApplySurfaceConfiguration(ArtemisSurface surface)
{
Devices.Clear();
Devices.AddRange(surface.Devices.OrderBy(d => d.ZIndex));
}
private void UpdateLedsDimStatus()
{
HighlightedLeds.Clear();
if (HighlightSelectedLayer.Value && _profileEditorService.SelectedProfileElement is Layer layer)
HighlightedLeds.AddRange(layer.Leds);
}
private void UpdateCanSelectEditTool()
{
if (_profileEditorService.SelectedProfileElement is Layer layer)
{
CanApplyToLayer = true;
CanSelectEditTool = (layer.LayerBrush == null || layer.LayerBrush.SupportsTransformation) && layer.Leds.Any();
}
else
{
CanApplyToLayer = false;
CanSelectEditTool = false;
}
if (CanSelectEditTool == false && ActiveToolIndex == 1)
ActivateToolByIndex(2);
}
#region Buttons
private void ActivateToolByIndex(int value)
{
// Consider using DI if dependencies start to add up
switch (value)
{
case 0:
ActiveToolViewModel = _visualizationToolVmFactory.ViewpointMoveToolViewModel(this);
break;
case 1:
ActiveToolViewModel = _visualizationToolVmFactory.EditToolViewModel(this);
break;
case 2:
ActiveToolViewModel = _visualizationToolVmFactory.SelectionToolViewModel(this);
break;
case 3:
ActiveToolViewModel = _visualizationToolVmFactory.SelectionRemoveToolViewModel(this);
break;
}
ActiveToolIndex = value;
}
public void ResetZoomAndPan()
{
PanZoomViewModel.Reset();
}
#endregion
#region Mouse
public void CanvasMouseDown(object sender, MouseButtonEventArgs e)
{
((IInputElement) sender).CaptureMouse();
ActiveToolViewModel?.MouseDown(sender, e);
}
public void CanvasMouseUp(object sender, MouseButtonEventArgs e)
{
((IInputElement) sender).ReleaseMouseCapture();
ActiveToolViewModel?.MouseUp(sender, e);
}
public void CanvasMouseMove(object sender, MouseEventArgs e)
{
ActiveToolViewModel?.MouseMove(sender, e);
}
public void CanvasMouseWheel(object sender, MouseWheelEventArgs e)
{
PanZoomViewModel.ProcessMouseScroll(sender, e);
ActiveToolViewModel?.MouseWheel(sender, e);
}
#endregion
#region Context menu actions
public void CreateLayer()
{
}
public void ApplyToLayer()
{
if (!(_profileEditorService.SelectedProfileElement is Layer layer))
return;
layer.ClearLeds();
layer.AddLeds(SelectedLeds);
_profileEditorService.UpdateSelectedProfileElement();
}
public void SelectAll()
{
SelectedLeds.Clear();
SelectedLeds.AddRange(Devices.SelectMany(d => d.Leds));
}
public void InverseSelection()
{
var current = SelectedLeds.ToList();
SelectedLeds.Clear();
SelectedLeds.AddRange(Devices.SelectMany(d => d.Leds).Except(current));
}
public void ClearSelection()
{
SelectedLeds.Clear();
}
#endregion
#region Event handlers
private void HighlightSelectedLayerOnSettingChanged(object sender, EventArgs e)
{
UpdateLedsDimStatus();
}
private void OnProfileSelected(object sender, EventArgs e)
{
ApplyActiveProfile();
}
private void OnProfileElementSelected(object sender, EventArgs e)
{
if (_previousSelectedLayer != null)
_previousSelectedLayer.LayerBrushUpdated -= SelectedLayerOnLayerBrushUpdated;
if (_profileEditorService.SelectedProfileElement is Layer layer)
{
_previousSelectedLayer = layer;
_previousSelectedLayer.LayerBrushUpdated += SelectedLayerOnLayerBrushUpdated;
}
else
_previousSelectedLayer = null;
UpdateLedsDimStatus();
UpdateCanSelectEditTool();
}
private void SelectedLayerOnLayerBrushUpdated(object sender, EventArgs e)
{
UpdateCanSelectEditTool();
}
private void OnSelectedProfileElementUpdated(object sender, EventArgs e)
{
ApplyActiveProfile();
UpdateLedsDimStatus();
if (_profileEditorService.SelectedProfileElement is Layer layer)
{
CanApplyToLayer = true;
CanSelectEditTool = layer.Leds.Any();
}
else
{
CanApplyToLayer = false;
CanSelectEditTool = false;
}
if (CanSelectEditTool == false && ActiveToolIndex == 1)
ActivateToolByIndex(2);
}
public void Handle(MainWindowFocusChangedEvent message)
{
// if (PauseRenderingOnFocusLoss == null || ScreenState != ScreenState.Active)
// return;
//
// try
// {
// if (PauseRenderingOnFocusLoss.Value && !message.IsFocused)
// _updateTrigger.Stop();
// else if (PauseRenderingOnFocusLoss.Value && message.IsFocused)
// _updateTrigger.Start();
// }
// catch (NullReferenceException)
// {
// // TODO: Remove when fixed in RGB.NET, or avoid double stopping
// }
}
public void Handle(MainWindowKeyEvent message)
{
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
}
}
@@ -0,0 +1,30 @@
<UserControl x:Class="Artemis.UI.Screens.ProfileEditor.Visualization.Tools.EditToolView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:local="clr-namespace:Artemis.UI.Screens.ProfileEditor.Visualization.Tools"
xmlns:userControls="clr-namespace:Artemis.UI.Screens.ProfileEditor.Visualization.UserControls"
xmlns:s="https://github.com/canton7/Stylet"
xmlns:tools="clr-namespace:Artemis.UI.Screens.ProfileEditor.Visualization.Tools"
xmlns:userControls1="clr-namespace:Artemis.UI.Screens.ProfileEditor.Visualization.UserControls"
mc:Ignorable="d"
d:DesignHeight="450"
d:DesignWidth="800"
d:DataContext="{d:DesignInstance {x:Type tools:EditToolViewModel}}">
<Canvas UseLayoutRounding="False">
<userControls1:LayerShapeControl Zoom="{Binding ProfileViewModel.PanZoomViewModel.Zoom}"
ShapePath="{Binding ShapePath}"
ShapeAnchor="{Binding ShapeAnchor}"
ShapeGeometry="{Binding ShapeGeometry}"
RotateMouseDown="{s:Action RotateMouseDown}"
RotateMouseUp="{s:Action RotateMouseUp}"
RotateMouseMove="{s:Action RotateMouseMove}"
ResizeMouseDown="{s:Action ResizeMouseDown}"
ResizeMouseUp="{s:Action ResizeMouseUp}"
ResizeMouseMove="{s:Action ResizeMouseMove}"
MoveMouseDown="{s:Action MoveMouseDown}"
MoveMouseUp="{s:Action MoveMouseUp}"
MoveMouseMove="{s:Action MoveMouseMove}" />
</Canvas>
</UserControl>
@@ -0,0 +1,420 @@
using System;
using System.Windows;
using System.Windows.Input;
using System.Windows.Media;
using Artemis.Core.Models.Profile;
using Artemis.UI.Events;
using Artemis.UI.Services.Interfaces;
using Artemis.UI.Shared.Services.Interfaces;
using SkiaSharp;
using SkiaSharp.Views.WPF;
using Stylet;
namespace Artemis.UI.Screens.ProfileEditor.Visualization.Tools
{
public class EditToolViewModel : VisualizationToolViewModel
{
private readonly ILayerEditorService _layerEditorService;
private SKPoint _dragOffset;
private SKPoint _dragStart;
private SKPoint _topLeft;
private SKPath _shapePath;
private SKPoint _shapeAnchor;
private RectangleGeometry _shapeGeometry;
public EditToolViewModel(ProfileViewModel profileViewModel, IProfileEditorService profileEditorService, ILayerEditorService layerEditorService)
: base(profileViewModel, profileEditorService)
{
_layerEditorService = layerEditorService;
Cursor = Cursors.Arrow;
Update();
profileEditorService.ProfileSelected += (sender, args) => Update();
profileEditorService.ProfileElementSelected += (sender, args) => Update();
profileEditorService.SelectedProfileElementUpdated += (sender, args) => Update();
profileEditorService.ProfilePreviewUpdated += (sender, args) => Update();
}
public SKPath ShapePath
{
get => _shapePath;
set => SetAndNotify(ref _shapePath, value);
}
public SKPoint ShapeAnchor
{
get => _shapeAnchor;
set => SetAndNotify(ref _shapeAnchor, value);
}
public RectangleGeometry ShapeGeometry
{
get => _shapeGeometry;
set => SetAndNotify(ref _shapeGeometry, value);
}
private void Update()
{
if (!(ProfileEditorService.SelectedProfileElement is Layer layer))
return;
ShapePath = _layerEditorService.GetLayerPath(layer, true, true, true);
ShapeAnchor = _layerEditorService.GetLayerAnchorPosition(layer).ToSKPoint();
Execute.PostToUIThread(() =>
{
var shapeGeometry = new RectangleGeometry(_layerEditorService.GetLayerBounds(layer))
{
Transform = _layerEditorService.GetLayerTransformGroup(layer)
};
shapeGeometry.Freeze();
ShapeGeometry = shapeGeometry;
});
// Store the last top-left for easy later on
_topLeft = _layerEditorService.GetLayerPath(layer, true, true, true).Points[0];
}
#region Rotation
private bool _rotating;
private float _previousDragAngle;
public void RotateMouseDown(object sender, ShapeControlEventArgs e)
{
_rotating = true;
if (ProfileEditorService.SelectedProfileElement is Layer layer)
_previousDragAngle = CalculateAngle(layer, sender, e.MouseEventArgs);
else
_previousDragAngle = 0;
}
public void RotateMouseUp(object sender, ShapeControlEventArgs e)
{
ProfileEditorService.UpdateSelectedProfileElement();
_rotating = false;
}
public void RotateMouseMove(object sender, ShapeControlEventArgs e)
{
if (!_rotating || !(ProfileEditorService.SelectedProfileElement is Layer layer))
return;
var previousDragAngle = _previousDragAngle;
var newRotation = CalculateAngle(layer, sender, e.MouseEventArgs);
_previousDragAngle = newRotation;
// Allow the user to rotate the shape in increments of 5
if (Keyboard.IsKeyDown(Key.LeftShift) || Keyboard.IsKeyDown(Key.RightShift))
newRotation = (float) Math.Round(newRotation / 5f) * 5f;
var difference = newRotation - previousDragAngle;
if (difference < -350)
difference += 360;
else if (difference > 350)
difference -= 360;
newRotation = layer.Transform.Rotation.CurrentValue + difference;
// Round the end-result to increments of 5 as well, to avoid staying on an offset
if (Keyboard.IsKeyDown(Key.LeftShift) || Keyboard.IsKeyDown(Key.RightShift))
newRotation = (float) Math.Round(newRotation / 5f) * 5f;
else
newRotation = (float) Math.Round(newRotation, 2, MidpointRounding.AwayFromZero);
layer.Transform.Rotation.SetCurrentValue(newRotation, ProfileEditorService.CurrentTime);
ProfileEditorService.UpdateProfilePreview();
}
#endregion
#region Size
private bool _isResizing;
private SKSize _dragStartScale;
public void ResizeMouseDown(object sender, ShapeControlEventArgs e)
{
if (_isResizing || !(ProfileEditorService.SelectedProfileElement is Layer layer))
return;
var dragStart = GetRelativePosition(sender, e.MouseEventArgs).ToSKPoint();
_dragOffset = _layerEditorService.GetDragOffset(layer, dragStart);
_dragStart = dragStart + _dragOffset;
_dragStartScale = layer.Transform.Scale.CurrentValue;
_isResizing = true;
}
public void ResizeMouseUp(object sender, ShapeControlEventArgs e)
{
ProfileEditorService.UpdateSelectedProfileElement();
_isResizing = false;
}
public void ResizeMouseMove(object sender, ShapeControlEventArgs e)
{
if (!_isResizing || !(ProfileEditorService.SelectedProfileElement is Layer layer))
return;
float width, height;
var position = GetRelativePosition(sender, e.MouseEventArgs).ToSKPoint() + _dragOffset;
switch (e.ShapeControlPoint)
{
case ShapeControlPoint.TopLeft:
height = VerticalResize(layer, position, ResizeOrigin.Top);
width = HorizontalResize(layer, position, ResizeOrigin.Left);
break;
case ShapeControlPoint.TopRight:
height = VerticalResize(layer, position, ResizeOrigin.Top);
width = HorizontalResize(layer, position, ResizeOrigin.Right);
break;
case ShapeControlPoint.BottomRight:
height = VerticalResize(layer, position, ResizeOrigin.Bottom);
width = HorizontalResize(layer, position, ResizeOrigin.Right);
break;
case ShapeControlPoint.BottomLeft:
height = VerticalResize(layer, position, ResizeOrigin.Bottom);
width = HorizontalResize(layer, position, ResizeOrigin.Left);
break;
case ShapeControlPoint.TopCenter:
height = VerticalResize(layer, position, ResizeOrigin.Top);
width = layer.Transform.Scale.CurrentValue.Width;
break;
case ShapeControlPoint.RightCenter:
width = HorizontalResize(layer, position, ResizeOrigin.Right);
height = layer.Transform.Scale.CurrentValue.Height;
break;
case ShapeControlPoint.BottomCenter:
width = layer.Transform.Scale.CurrentValue.Width;
height = VerticalResize(layer, position, ResizeOrigin.Bottom);
break;
case ShapeControlPoint.LeftCenter:
width = HorizontalResize(layer, position, ResizeOrigin.Left);
height = layer.Transform.Scale.CurrentValue.Height;
break;
default:
throw new ArgumentOutOfRangeException();
}
// Make the sides even if shift is held down
if (Keyboard.IsKeyDown(Key.LeftShift) || Keyboard.IsKeyDown(Key.RightShift) && e.ShapeControlPoint < ShapeControlPoint.TopCenter)
{
var bounds = _layerEditorService.GetLayerBounds(layer);
var smallestSide = Math.Min(bounds.Width * width, bounds.Height * height);
width = (float) Math.Round(1.0 / bounds.Width * smallestSide, 2, MidpointRounding.AwayFromZero);
height = (float) Math.Round(1.0 / bounds.Height * smallestSide, 2, MidpointRounding.AwayFromZero);
}
layer.Transform.Scale.SetCurrentValue(new SKSize(width, height), ProfileEditorService.CurrentTime);
ProfileEditorService.UpdateProfilePreview();
}
private float HorizontalResize(Layer layer, SKPoint position, ResizeOrigin origin)
{
// Apply rotation to the mouse
var points = UnTransformPoints(new[] {position, _dragStart}, layer, ShapeAnchor, false);
position = points[0];
var dragStart = points[1];
var shapePath = _layerEditorService.GetLayerPath(layer, true, false, false);
var scalePerPixel = 1f / shapePath.Bounds.Width;
var anchorDistance = origin == ResizeOrigin.Left
? shapePath.Bounds.Left - ShapeAnchor.X
: shapePath.Bounds.Right - ShapeAnchor.X;
var anchorOffset = anchorDistance / shapePath.Bounds.Width;
var pixelsToAdd = (position - dragStart).X / anchorOffset;
var scaleToAdd = scalePerPixel * pixelsToAdd * 100f;
return (float) Math.Round(Math.Max(0, _dragStartScale.Width + scaleToAdd), 2, MidpointRounding.AwayFromZero);
}
private float VerticalResize(Layer layer, SKPoint position, ResizeOrigin origin)
{
// Apply rotation to the mouse
var points = UnTransformPoints(new[] {position, _dragStart}, layer, ShapeAnchor, false);
position = points[0];
var dragStart = points[1];
var shapePath = _layerEditorService.GetLayerPath(layer, true, false, false);
var scalePerPixel = 1f / shapePath.Bounds.Height;
var anchorDistance = origin == ResizeOrigin.Top
? shapePath.Bounds.Top - ShapeAnchor.Y
: shapePath.Bounds.Bottom - ShapeAnchor.Y;
var anchorOffset = anchorDistance / shapePath.Bounds.Height;
var pixelsToAdd = (position - dragStart).Y / anchorOffset;
var scaleToAdd = scalePerPixel * pixelsToAdd * 100f;
return (float) Math.Round(Math.Max(0, _dragStartScale.Height + scaleToAdd), 2, MidpointRounding.AwayFromZero);
}
#endregion
#region Position
private bool _movingShape;
private bool _movingAnchor;
private bool _draggingHorizontally;
private bool _draggingVertically;
private SKPoint _dragStartAnchor;
public void MoveMouseDown(object sender, ShapeControlEventArgs e)
{
if (!(ProfileEditorService.SelectedProfileElement is Layer layer))
return;
if (e.ShapeControlPoint == ShapeControlPoint.LayerShape)
{
var dragStart = GetRelativePosition(sender, e.MouseEventArgs).ToSKPoint();
_dragOffset = _layerEditorService.GetDragOffset(layer, dragStart);
_dragStart = dragStart + _dragOffset;
_movingShape = true;
}
else if (e.ShapeControlPoint == ShapeControlPoint.Anchor)
{
var dragStartPosition = GetRelativePosition(sender, e.MouseEventArgs).ToSKPoint();
// Mouse doesn't care about rotation so get the layer path without rotation
var path = _layerEditorService.GetLayerPath(layer, true, true, false);
var topLeft = path.Points[0];
// Measure from the top-left of the shape (without rotation)
_dragOffset = topLeft + (dragStartPosition - topLeft);
// Get the absolute layer anchor and make it relative to the unrotated shape
_dragStartAnchor = _layerEditorService.GetLayerAnchorPosition(layer).ToSKPoint() - topLeft;
// Ensure the anchor starts in the center of the shape it is now relative to
_dragStartAnchor.X -= path.Bounds.Width / 2f;
_dragStartAnchor.Y -= path.Bounds.Height / 2f;
_movingAnchor = true;
}
}
public void MoveMouseUp(object sender, ShapeControlEventArgs e)
{
ProfileEditorService.UpdateSelectedProfileElement();
_movingShape = false;
_movingAnchor = false;
}
public void MoveMouseMove(object sender, ShapeControlEventArgs e)
{
if (_movingShape)
MoveShape(sender, e.MouseEventArgs);
else if (_movingAnchor)
MoveAnchor(sender, e.MouseEventArgs);
}
public void MoveShape(object sender, MouseEventArgs e)
{
if (!(ProfileEditorService.SelectedProfileElement is Layer layer))
return;
var position = GetRelativePosition(sender, e).ToSKPoint() + _dragOffset;
// Allow the user to move the shape only horizontally or vertically when holding down shift
if (Keyboard.IsKeyDown(Key.LeftShift) || Keyboard.IsKeyDown(Key.RightShift))
{
// Keep the X position static if dragging vertically
if (_draggingVertically)
position.X = _dragStart.X;
// Keep the Y position static if dragging horizontally
else if (_draggingHorizontally)
position.Y = _dragStart.Y;
// Snap into place only if the mouse moved atleast a full pixel
else if (Math.Abs(position.X - _dragStart.X) > 1 || Math.Abs(position.Y - _dragStart.Y) > 1)
{
// Pick between X and Y by comparing which moved the furthers from the starting point
_draggingHorizontally = Math.Abs(position.X - _dragStart.X) > Math.Abs(position.Y - _dragStart.Y);
_draggingVertically = Math.Abs(position.X - _dragStart.X) < Math.Abs(position.Y - _dragStart.Y);
return;
}
}
// Reset both states when shift is not held down
else
{
_draggingVertically = false;
_draggingHorizontally = false;
}
// Scale down the resulting position and make it relative
var scaled = _layerEditorService.GetScaledPoint(layer, position, true);
// Round and update the position property
layer.Transform.Position.SetCurrentValue(RoundPoint(scaled, 3), ProfileEditorService.CurrentTime);
ProfileEditorService.UpdateProfilePreview();
}
public void MoveAnchor(object sender, MouseEventArgs e)
{
if (!_movingAnchor || !(ProfileEditorService.SelectedProfileElement is Layer layer))
return;
// The start anchor is relative to an unrotated version of the shape
var start = _dragStartAnchor;
// Add the current position to the start anchor to determine the new position
var current = start + (GetRelativePosition(sender, e).ToSKPoint() - _dragOffset);
// In order to keep the mouse movement unrotated, counter-act the active rotation
var countered = UnTransformPoints(new[] {start, current}, layer, start, true);
var scaled = _layerEditorService.GetScaledPoint(layer, countered[1], false);
// Update the anchor point, this causes the shape to move
layer.Transform.AnchorPoint.SetCurrentValue(RoundPoint(scaled, 3), ProfileEditorService.CurrentTime);
// TopLeft is not updated yet and acts as a snapshot of the top-left before changing the anchor
var path = _layerEditorService.GetLayerPath(layer, true, true, true);
// Calculate the (scaled) difference between the old and now position
var difference = _layerEditorService.GetScaledPoint(layer, _topLeft - path.Points[0], false);
// Apply the difference so that the shape effectively stays in place
layer.Transform.Position.SetCurrentValue(RoundPoint(layer.Transform.Position.CurrentValue + difference, 3), ProfileEditorService.CurrentTime);
ProfileEditorService.UpdateProfilePreview();
}
#endregion
#region Private methods
private SKPoint RoundPoint(SKPoint point, int decimals)
{
return new SKPoint((float) Math.Round(point.X, decimals, MidpointRounding.AwayFromZero), (float) Math.Round(point.Y, decimals, MidpointRounding.AwayFromZero));
}
private SKPoint[] UnTransformPoints(SKPoint[] skPoints, Layer layer, SKPoint pivot, bool includeScale)
{
using var counterRotatePath = new SKPath();
counterRotatePath.AddPoly(skPoints, false);
counterRotatePath.Transform(SKMatrix.MakeRotationDegrees(layer.Transform.Rotation.CurrentValue * -1, pivot.X, pivot.Y));
if (includeScale)
counterRotatePath.Transform(SKMatrix.MakeScale(1f / (layer.Transform.Scale.CurrentValue.Width / 100f), 1f / (layer.Transform.Scale.CurrentValue.Height / 100f)));
return counterRotatePath.Points;
}
private Point GetRelativePosition(object sender, MouseEventArgs mouseEventArgs)
{
var parent = VisualTreeHelper.GetParent((DependencyObject) sender);
return mouseEventArgs.GetPosition((IInputElement) parent);
}
private float CalculateAngle(Layer layer, object mouseEventSender, MouseEventArgs mouseEvent)
{
var layerBounds = _layerEditorService.GetLayerBounds(layer);
var start = _layerEditorService.GetLayerAnchorPosition(layer);
var arrival = GetRelativePosition(mouseEventSender, mouseEvent);
var radian = (float) Math.Atan2(start.Y - arrival.Y, start.X - arrival.X);
var angle = radian * (180f / (float) Math.PI);
if (angle < 0f)
angle += 360f;
return angle;
}
#endregion
}
internal enum ResizeOrigin
{
Left,
Right,
Top,
Bottom
}
}
@@ -0,0 +1,45 @@
<UserControl x:Class="Artemis.UI.Screens.ProfileEditor.Visualization.Tools.SelectionRemoveToolView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
mc:Ignorable="d"
d:DesignHeight="450" d:DesignWidth="800">
<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">
<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>
@@ -0,0 +1,15 @@
using System.Windows.Controls;
namespace Artemis.UI.Screens.ProfileEditor.Visualization.Tools
{
/// <summary>
/// Interaction logic for SelectionRemoveToolView.xaml
/// </summary>
public partial class SelectionRemoveToolView : UserControl
{
public SelectionRemoveToolView()
{
InitializeComponent();
}
}
}
@@ -0,0 +1,73 @@
using System.IO;
using System.Linq;
using System.Windows;
using System.Windows.Input;
using Artemis.Core.Models.Profile;
using Artemis.UI.Properties;
using Artemis.UI.Shared.Services.Interfaces;
namespace Artemis.UI.Screens.ProfileEditor.Visualization.Tools
{
public class SelectionRemoveToolViewModel : VisualizationToolViewModel
{
private Rect _dragRectangle;
public SelectionRemoveToolViewModel(ProfileViewModel profileViewModel, IProfileEditorService profileEditorService) : base(profileViewModel, profileEditorService)
{
using (var stream = new MemoryStream(Resources.aero_pen_min))
{
Cursor = new Cursor(stream);
}
}
public Rect DragRectangle
{
get => _dragRectangle;
set => SetAndNotify(ref _dragRectangle, value);
}
public override void MouseUp(object sender, MouseButtonEventArgs e)
{
base.MouseUp(sender, e);
var position = ProfileViewModel.PanZoomViewModel.GetRelativeMousePosition(sender, e);
var selectedRect = new Rect(MouseDownStartPosition, position);
// Get selected LEDs
var selectedLeds = ProfileViewModel.GetLedsInRectangle(selectedRect);
ProfileViewModel.SelectedLeds.Clear();
ProfileViewModel.SelectedLeds.AddRange(selectedLeds);
// Apply the selection to the selected layer layer
if (ProfileEditorService.SelectedProfileElement is Layer layer)
{
var remainingLeds = layer.Leds.Except(selectedLeds).ToList();
layer.ClearLeds();
layer.AddLeds(remainingLeds);
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 = ProfileViewModel.PanZoomViewModel.GetRelativeMousePosition(sender, e);
var selectedRect = new Rect(MouseDownStartPosition, position);
var selectedLeds = ProfileViewModel.GetLedsInRectangle(selectedRect);
// Unless shift is held down, clear the current selection
if (!Keyboard.IsKeyDown(Key.LeftShift) && !Keyboard.IsKeyDown(Key.RightShift))
ProfileViewModel.SelectedLeds.Clear();
ProfileViewModel.SelectedLeds.AddRange(selectedLeds.Except(ProfileViewModel.SelectedLeds));
DragRectangle = selectedRect;
}
}
}
@@ -0,0 +1,45 @@
<UserControl x:Class="Artemis.UI.Screens.ProfileEditor.Visualization.Tools.SelectionToolView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
mc:Ignorable="d"
d:DesignHeight="450" d:DesignWidth="800">
<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">
<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>
@@ -0,0 +1,15 @@
using System.Windows.Controls;
namespace Artemis.UI.Screens.ProfileEditor.Visualization.Tools
{
/// <summary>
/// Interaction logic for SelectionToolView.xaml
/// </summary>
public partial class SelectionToolView : UserControl
{
public SelectionToolView()
{
InitializeComponent();
}
}
}
@@ -0,0 +1,97 @@
using System.IO;
using System.Linq;
using System.Windows;
using System.Windows.Input;
using Artemis.Core.Models.Profile;
using Artemis.Core.Services.Interfaces;
using Artemis.UI.Properties;
using Artemis.UI.Shared.Services.Interfaces;
namespace Artemis.UI.Screens.ProfileEditor.Visualization.Tools
{
public class SelectionToolViewModel : VisualizationToolViewModel
{
private readonly IRenderElementService _renderElementService;
private Rect _dragRectangle;
public SelectionToolViewModel(ProfileViewModel profileViewModel, IProfileEditorService profileEditorService, IRenderElementService renderElementService)
: base(profileViewModel, profileEditorService)
{
_renderElementService = renderElementService;
using (var stream = new MemoryStream(Resources.aero_crosshair))
{
Cursor = new Cursor(stream);
}
}
public Rect DragRectangle
{
get => _dragRectangle;
set => SetAndNotify(ref _dragRectangle, value);
}
public override void MouseUp(object sender, MouseButtonEventArgs e)
{
base.MouseUp(sender, e);
var position = ProfileViewModel.PanZoomViewModel.GetRelativeMousePosition(sender, e);
var selectedRect = new Rect(MouseDownStartPosition, position);
// Get selected LEDs
var selectedLeds = ProfileViewModel.GetLedsInRectangle(selectedRect);
// Apply the selection to the selected layer layer
if (ProfileEditorService.SelectedProfileElement is Layer layer)
{
// If shift is held down, add to the selection instead of replacing it
if (Keyboard.IsKeyDown(Key.LeftShift) || Keyboard.IsKeyDown(Key.RightShift))
layer.AddLeds(selectedLeds.Except(layer.Leds));
else
{
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 = _renderElementService.CreateLayer(folder.Profile, folder, "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 = _renderElementService.CreateLayer(rootFolder.Profile, rootFolder, "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 = ProfileViewModel.PanZoomViewModel.GetRelativeMousePosition(sender, e);
var selectedRect = new Rect(MouseDownStartPosition, position);
var selectedLeds = ProfileViewModel.GetLedsInRectangle(selectedRect);
// Unless shift is held down, clear the current selection
if (!Keyboard.IsKeyDown(Key.LeftShift) && !Keyboard.IsKeyDown(Key.RightShift))
ProfileViewModel.SelectedLeds.Clear();
ProfileViewModel.SelectedLeds.AddRange(selectedLeds.Except(ProfileViewModel.SelectedLeds));
DragRectangle = selectedRect;
}
}
}
@@ -0,0 +1,10 @@
<UserControl x:Class="Artemis.UI.Screens.ProfileEditor.Visualization.Tools.ViewpointMoveToolView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:local="clr-namespace:Artemis.UI.Screens.ProfileEditor.Visualization.Tools"
mc:Ignorable="d"
d:DesignHeight="450" d:DesignWidth="800">
<Grid />
</UserControl>
@@ -0,0 +1,15 @@
using System.Windows.Controls;
namespace Artemis.UI.Screens.ProfileEditor.Visualization.Tools
{
/// <summary>
/// Interaction logic for ViewpointMoveToolView.xaml
/// </summary>
public partial class ViewpointMoveToolView : UserControl
{
public ViewpointMoveToolView()
{
InitializeComponent();
}
}
}
@@ -0,0 +1,20 @@
using System.Windows.Input;
using Artemis.UI.Shared.Services.Interfaces;
namespace Artemis.UI.Screens.ProfileEditor.Visualization.Tools
{
public class ViewpointMoveToolViewModel : VisualizationToolViewModel
{
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);
ProfileViewModel.PanZoomViewModel.ProcessMouseMove(sender, e);
}
}
}
@@ -0,0 +1,88 @@
using System;
using System.Windows;
using System.Windows.Input;
using Artemis.UI.Shared.Services.Interfaces;
namespace Artemis.UI.Screens.ProfileEditor.Visualization.Tools
{
public abstract class VisualizationToolViewModel : CanvasViewModel
{
private Cursor _cursor;
private bool _isMouseDown;
private Point _mouseDownStartPosition;
protected VisualizationToolViewModel(ProfileViewModel profileViewModel, IProfileEditorService profileEditorService)
{
// Not relevant for visualization tools as they overlay the entire canvas
X = 0;
Y = 0;
ProfileViewModel = profileViewModel;
ProfileEditorService = profileEditorService;
Cursor = Cursors.Arrow;
}
public ProfileViewModel ProfileViewModel { get; }
public IProfileEditorService ProfileEditorService { get; }
public Cursor Cursor
{
get => _cursor;
protected set => SetAndNotify(ref _cursor, value);
}
public bool IsMouseDown
{
get => _isMouseDown;
protected set => SetAndNotify(ref _isMouseDown, value);
}
public Point MouseDownStartPosition
{
get => _mouseDownStartPosition;
protected set => SetAndNotify(ref _mouseDownStartPosition, value);
}
public virtual void MouseDown(object sender, MouseButtonEventArgs e)
{
IsMouseDown = true;
MouseDownStartPosition = ProfileViewModel.PanZoomViewModel.GetRelativeMousePosition(sender, e);
}
public virtual void MouseUp(object sender, MouseButtonEventArgs e)
{
IsMouseDown = false;
}
public virtual void MouseMove(object sender, MouseEventArgs e)
{
}
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);
}
}
}
@@ -0,0 +1,123 @@
<UserControl x:Class="Artemis.UI.Screens.ProfileEditor.Visualization.UserControls.LayerShapeControl"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
mc:Ignorable="d"
d:DesignHeight="450" d:DesignWidth="800">
<Canvas UseLayoutRounding="False">
<!-- Render these first so that they are always behind the actual shape -->
<Ellipse x:Name="RotateTopLeft"
Fill="Transparent"
Cursor="/Resources/Cursors/aero_rotate.cur"
MouseDown="RotationOnMouseDown"
MouseUp="RotationOnMouseUp"
MouseMove="RotationOnMouseMove" />
<Ellipse x:Name="RotateTopRight"
Fill="Transparent"
Cursor="/Resources/Cursors/aero_rotate.cur"
MouseDown="RotationOnMouseDown"
MouseUp="RotationOnMouseUp"
MouseMove="RotationOnMouseMove" />
<Ellipse x:Name="RotateBottomRight"
Fill="Transparent"
Cursor="/Resources/Cursors/aero_rotate.cur"
MouseDown="RotationOnMouseDown"
MouseUp="RotationOnMouseUp"
MouseMove="RotationOnMouseMove" />
<Ellipse x:Name="RotateBottomLeft"
Fill="Transparent"
Cursor="/Resources/Cursors/aero_rotate.cur"
MouseDown="RotationOnMouseDown"
MouseUp="RotationOnMouseUp"
MouseMove="RotationOnMouseMove" />
<!-- The part of the layer's shape that is inside the layer -->
<Path x:Name="LayerShapeOutline"
Data="{Binding ShapeGeometry, Mode=OneWay}"
Fill="Transparent"
Stroke="{DynamicResource PrimaryHueMidBrush}"
StrokeThickness="{Binding OutlineThickness}"
StrokeDashArray="2 2"
Cursor="/Resources/Cursors/aero_drag.cur"
MouseDown="MoveOnMouseDown"
MouseUp="MoveOnMouseUp"
MouseMove="MoveOnMouseMove" />
<!-- Mutation points -->
<Rectangle x:Name="ResizeTopCenter"
Fill="White"
Stroke="{DynamicResource SecondaryAccentBrush}"
StrokeThickness="0.5"
Cursor="Hand"
MouseDown="ResizeOnMouseDown"
MouseUp="ResizeOnMouseUp"
MouseMove="ResizeOnMouseMove" />
<Rectangle x:Name="ResizeRightCenter"
Fill="White"
Stroke="{DynamicResource SecondaryAccentBrush}"
StrokeThickness="0.5"
Cursor="Hand"
MouseDown="ResizeOnMouseDown"
MouseUp="ResizeOnMouseUp"
MouseMove="ResizeOnMouseMove" />
<Rectangle x:Name="ResizeBottomCenter"
Fill="White"
Stroke="{DynamicResource SecondaryAccentBrush}"
StrokeThickness="0.5"
Cursor="Hand"
MouseDown="ResizeOnMouseDown"
MouseUp="ResizeOnMouseUp"
MouseMove="ResizeOnMouseMove" />
<Rectangle x:Name="ResizeLeftCenter"
Fill="White"
Stroke="{DynamicResource SecondaryAccentBrush}"
StrokeThickness="0.5"
Cursor="Hand"
MouseDown="ResizeOnMouseDown"
MouseUp="ResizeOnMouseUp"
MouseMove="ResizeOnMouseMove" />
<Rectangle x:Name="ResizeTopLeft"
Fill="White"
Stroke="{DynamicResource SecondaryAccentBrush}"
StrokeThickness="0.5"
Cursor="Hand"
MouseDown="ResizeOnMouseDown"
MouseUp="ResizeOnMouseUp"
MouseMove="ResizeOnMouseMove" />
<Rectangle x:Name="ResizeTopRight"
Fill="White"
Stroke="{DynamicResource SecondaryAccentBrush}"
StrokeThickness="0.5"
Cursor="Hand"
MouseDown="ResizeOnMouseDown"
MouseUp="ResizeOnMouseUp"
MouseMove="ResizeOnMouseMove" />
<Rectangle x:Name="ResizeBottomRight"
Fill="White"
Stroke="{DynamicResource SecondaryAccentBrush}"
StrokeThickness="0.5"
Cursor="Hand"
MouseDown="ResizeOnMouseDown"
MouseUp="ResizeOnMouseUp"
MouseMove="ResizeOnMouseMove" />
<Rectangle x:Name="ResizeBottomLeft"
Fill="White"
Stroke="{DynamicResource SecondaryAccentBrush}"
StrokeThickness="0.5"
Cursor="Hand"
MouseDown="ResizeOnMouseDown"
MouseUp="ResizeOnMouseUp"
MouseMove="ResizeOnMouseMove" />
<!-- Anchor point -->
<Ellipse x:Name="AnchorPoint"
Fill="White"
Stroke="{DynamicResource SecondaryAccentBrush}"
StrokeThickness="0.5"
Cursor="SizeAll"
MouseDown="MoveOnMouseDown"
MouseUp="MoveOnMouseUp"
MouseMove="MoveOnMouseMove" />
</Canvas>
</UserControl>
@@ -0,0 +1,391 @@
using System;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Input;
using System.Windows.Media;
using Artemis.UI.Events;
using SkiaSharp;
namespace Artemis.UI.Screens.ProfileEditor.Visualization.UserControls
{
/// <summary>
/// Interaction logic for LayerShapeControl.xaml
/// </summary>
public partial class LayerShapeControl : UserControl
{
public static readonly DependencyProperty ZoomProperty = DependencyProperty.Register(nameof(Zoom), typeof(double), typeof(LayerShapeControl),
new FrameworkPropertyMetadata(default(double), FrameworkPropertyMetadataOptions.None, ZoomChanged));
public static readonly DependencyProperty ShapePathProperty = DependencyProperty.Register(nameof(ShapePath), typeof(SKPath), typeof(LayerShapeControl),
new FrameworkPropertyMetadata(default(SKPath), FrameworkPropertyMetadataOptions.None, ShapePathChanged));
public static readonly DependencyProperty ShapeAnchorProperty = DependencyProperty.Register(nameof(ShapeAnchor), typeof(SKPoint), typeof(LayerShapeControl),
new FrameworkPropertyMetadata(default(SKPoint), FrameworkPropertyMetadataOptions.None, ShapeAnchorChanged));
public static readonly DependencyProperty ShapeGeometryProperty = DependencyProperty.Register(nameof(ShapeGeometry), typeof(Geometry), typeof(LayerShapeControl),
new FrameworkPropertyMetadata(default(Geometry), FrameworkPropertyMetadataOptions.None, ShapeGeometryChanged));
public LayerShapeControl()
{
InitializeComponent();
UpdatePositions();
UpdateDimensions();
}
public double Zoom
{
get => (double) GetValue(ZoomProperty);
set => SetValue(ZoomProperty, value);
}
public SKPath ShapePath
{
get => (SKPath) GetValue(ShapePathProperty);
set => SetValue(ShapePathProperty, value);
}
public SKPoint ShapeAnchor
{
get => (SKPoint) GetValue(ShapeAnchorProperty);
set => SetValue(ShapeAnchorProperty, value);
}
public Geometry ShapeGeometry
{
get => (Geometry) GetValue(ShapeGeometryProperty);
set => SetValue(ShapeGeometryProperty, value);
}
public void UpdateDimensions()
{
if (Zoom == 0)
return;
// Rotation controls
UpdateRotateDimensions(RotateTopLeft);
UpdateRotateDimensions(RotateTopRight);
UpdateRotateDimensions(RotateBottomRight);
UpdateRotateDimensions(RotateBottomLeft);
// Size controls
UpdateResizeDimensions(ResizeTopCenter);
UpdateResizeDimensions(ResizeRightCenter);
UpdateResizeDimensions(ResizeBottomCenter);
UpdateResizeDimensions(ResizeLeftCenter);
UpdateResizeDimensions(ResizeTopLeft);
UpdateResizeDimensions(ResizeTopRight);
UpdateResizeDimensions(ResizeBottomRight);
UpdateResizeDimensions(ResizeBottomLeft);
// Anchor point
UpdateResizeDimensions(AnchorPoint);
// Layer outline
LayerShapeOutline.StrokeThickness = Math.Max(2 / Zoom, 1);
}
private static void ZoomChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var layerShapeControl = (LayerShapeControl) d;
layerShapeControl.SetCurrentValue(ZoomProperty, e.NewValue);
layerShapeControl.UpdateDimensions();
}
private static void ShapePathChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var layerShapeControl = (LayerShapeControl) d;
layerShapeControl.SetCurrentValue(ShapePathProperty, e.NewValue);
layerShapeControl.UpdatePositions();
}
private static void ShapeAnchorChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var layerShapeControl = (LayerShapeControl) d;
layerShapeControl.SetCurrentValue(ShapeAnchorProperty, e.NewValue);
layerShapeControl.UpdateShapeAnchor();
}
private static void ShapeGeometryChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var layerShapeControl = (LayerShapeControl) d;
layerShapeControl.SetCurrentValue(ShapeGeometryProperty, e.NewValue);
layerShapeControl.UpdateShapeGeometry();
}
private void UpdateShapeGeometry()
{
LayerShapeOutline.Data = ShapeGeometry;
}
private void UpdateShapeAnchor()
{
UpdateControlPosition(AnchorPoint, ShapeAnchor);
}
private void UpdatePositions()
{
if (ShapePath == null)
return;
// Rotation controls
UpdateControlPosition(RotateTopLeft, ShapePath.Points[0]);
UpdateControlPosition(RotateTopRight, ShapePath.Points[1]);
UpdateControlPosition(RotateBottomRight, ShapePath.Points[2]);
UpdateControlPosition(RotateBottomLeft, ShapePath.Points[3]);
// Size controls
UpdateControlPosition(ResizeTopCenter, ShapePath.Points[0], ShapePath.Points[1]);
UpdateControlPosition(ResizeRightCenter, ShapePath.Points[1], ShapePath.Points[2]);
UpdateControlPosition(ResizeBottomCenter, ShapePath.Points[2], ShapePath.Points[3]);
UpdateControlPosition(ResizeLeftCenter, ShapePath.Points[3], ShapePath.Points[0]);
UpdateControlPosition(ResizeTopLeft, ShapePath.Points[0]);
UpdateControlPosition(ResizeTopRight, ShapePath.Points[1]);
UpdateControlPosition(ResizeBottomRight, ShapePath.Points[2]);
UpdateControlPosition(ResizeBottomLeft, ShapePath.Points[3]);
}
#region Helpers
private void UpdateControlPosition(UIElement control, SKPoint point)
{
if (float.IsInfinity(point.X) || float.IsInfinity(point.Y))
return;
Canvas.SetLeft(control, point.X);
Canvas.SetTop(control, point.Y);
}
private void UpdateControlPosition(UIElement control, SKPoint point1, SKPoint point2)
{
var point = new SKPoint((point1.X + point2.X) / 2, (point1.Y + point2.Y) / 2);
UpdateControlPosition(control, point);
}
private void UpdateRotateDimensions(FrameworkElement rotateControl)
{
var controlSize = Math.Max(10 / Zoom, 4);
var rotateSize = controlSize * 8;
rotateControl.Width = rotateSize;
rotateControl.Height = rotateSize;
rotateControl.Margin = new Thickness(rotateSize / 2 * -1, rotateSize / 2 * -1, 0, 0);
}
private void UpdateResizeDimensions(FrameworkElement resizeControl)
{
var controlSize = Math.Max(10 / Zoom, 4);
resizeControl.Width = controlSize;
resizeControl.Height = controlSize;
resizeControl.Margin = new Thickness(controlSize / 2 * -1, controlSize / 2 * -1, 0, 0);
}
#endregion
#region Event handlers
private void RotationOnMouseDown(object sender, MouseEventArgs e)
{
((IInputElement) sender).CaptureMouse();
e.Handled = true;
if (ReferenceEquals(sender, RotateTopLeft))
OnRotateMouseDown(new ShapeControlEventArgs(e, ShapeControlPoint.TopLeft));
else if (ReferenceEquals(sender, RotateTopRight))
OnRotateMouseDown(new ShapeControlEventArgs(e, ShapeControlPoint.TopRight));
else if (ReferenceEquals(sender, RotateBottomRight))
OnRotateMouseDown(new ShapeControlEventArgs(e, ShapeControlPoint.BottomRight));
else if (ReferenceEquals(sender, RotateBottomLeft))
OnRotateMouseDown(new ShapeControlEventArgs(e, ShapeControlPoint.BottomLeft));
}
private void RotationOnMouseUp(object sender, MouseEventArgs e)
{
if (ReferenceEquals(sender, RotateTopLeft))
OnRotateMouseUp(new ShapeControlEventArgs(e, ShapeControlPoint.TopLeft));
else if (ReferenceEquals(sender, RotateTopRight))
OnRotateMouseUp(new ShapeControlEventArgs(e, ShapeControlPoint.TopRight));
else if (ReferenceEquals(sender, RotateBottomRight))
OnRotateMouseUp(new ShapeControlEventArgs(e, ShapeControlPoint.BottomRight));
else if (ReferenceEquals(sender, RotateBottomLeft))
OnRotateMouseUp(new ShapeControlEventArgs(e, ShapeControlPoint.BottomLeft));
((IInputElement) sender).ReleaseMouseCapture();
e.Handled = true;
}
private void RotationOnMouseMove(object sender, MouseEventArgs e)
{
if (ReferenceEquals(sender, RotateTopLeft))
OnRotateMouseMove(new ShapeControlEventArgs(e, ShapeControlPoint.TopLeft));
else if (ReferenceEquals(sender, RotateTopRight))
OnRotateMouseMove(new ShapeControlEventArgs(e, ShapeControlPoint.TopRight));
else if (ReferenceEquals(sender, RotateBottomRight))
OnRotateMouseMove(new ShapeControlEventArgs(e, ShapeControlPoint.BottomRight));
else if (ReferenceEquals(sender, RotateBottomLeft))
OnRotateMouseMove(new ShapeControlEventArgs(e, ShapeControlPoint.BottomLeft));
}
private void ResizeOnMouseDown(object sender, MouseEventArgs e)
{
((IInputElement) sender).CaptureMouse();
e.Handled = true;
if (ReferenceEquals(sender, ResizeTopCenter))
OnResizeMouseDown(new ShapeControlEventArgs(e, ShapeControlPoint.TopCenter));
else if (ReferenceEquals(sender, ResizeRightCenter))
OnResizeMouseDown(new ShapeControlEventArgs(e, ShapeControlPoint.RightCenter));
else if (ReferenceEquals(sender, ResizeBottomCenter))
OnResizeMouseDown(new ShapeControlEventArgs(e, ShapeControlPoint.BottomCenter));
else if (ReferenceEquals(sender, ResizeLeftCenter))
OnResizeMouseDown(new ShapeControlEventArgs(e, ShapeControlPoint.LeftCenter));
else if (ReferenceEquals(sender, ResizeTopLeft))
OnResizeMouseDown(new ShapeControlEventArgs(e, ShapeControlPoint.TopLeft));
else if (ReferenceEquals(sender, ResizeTopRight))
OnResizeMouseDown(new ShapeControlEventArgs(e, ShapeControlPoint.TopRight));
else if (ReferenceEquals(sender, ResizeBottomRight))
OnResizeMouseDown(new ShapeControlEventArgs(e, ShapeControlPoint.BottomRight));
else if (ReferenceEquals(sender, ResizeBottomLeft))
OnResizeMouseDown(new ShapeControlEventArgs(e, ShapeControlPoint.BottomLeft));
}
private void ResizeOnMouseUp(object sender, MouseEventArgs e)
{
if (ReferenceEquals(sender, ResizeTopCenter))
OnResizeMouseUp(new ShapeControlEventArgs(e, ShapeControlPoint.TopCenter));
else if (ReferenceEquals(sender, ResizeRightCenter))
OnResizeMouseUp(new ShapeControlEventArgs(e, ShapeControlPoint.RightCenter));
else if (ReferenceEquals(sender, ResizeBottomCenter))
OnResizeMouseUp(new ShapeControlEventArgs(e, ShapeControlPoint.BottomCenter));
else if (ReferenceEquals(sender, ResizeLeftCenter))
OnResizeMouseUp(new ShapeControlEventArgs(e, ShapeControlPoint.LeftCenter));
else if (ReferenceEquals(sender, ResizeTopLeft))
OnResizeMouseUp(new ShapeControlEventArgs(e, ShapeControlPoint.TopLeft));
else if (ReferenceEquals(sender, ResizeTopRight))
OnResizeMouseUp(new ShapeControlEventArgs(e, ShapeControlPoint.TopRight));
else if (ReferenceEquals(sender, ResizeBottomRight))
OnResizeMouseUp(new ShapeControlEventArgs(e, ShapeControlPoint.BottomRight));
else if (ReferenceEquals(sender, ResizeBottomLeft))
OnResizeMouseUp(new ShapeControlEventArgs(e, ShapeControlPoint.BottomLeft));
((IInputElement) sender).ReleaseMouseCapture();
e.Handled = true;
}
private void ResizeOnMouseMove(object sender, MouseEventArgs e)
{
if (ReferenceEquals(sender, ResizeTopCenter))
OnResizeMouseMove(new ShapeControlEventArgs(e, ShapeControlPoint.TopCenter));
else if (ReferenceEquals(sender, ResizeRightCenter))
OnResizeMouseMove(new ShapeControlEventArgs(e, ShapeControlPoint.RightCenter));
else if (ReferenceEquals(sender, ResizeBottomCenter))
OnResizeMouseMove(new ShapeControlEventArgs(e, ShapeControlPoint.BottomCenter));
else if (ReferenceEquals(sender, ResizeLeftCenter))
OnResizeMouseMove(new ShapeControlEventArgs(e, ShapeControlPoint.LeftCenter));
else if (ReferenceEquals(sender, ResizeTopLeft))
OnResizeMouseMove(new ShapeControlEventArgs(e, ShapeControlPoint.TopLeft));
else if (ReferenceEquals(sender, ResizeTopRight))
OnResizeMouseMove(new ShapeControlEventArgs(e, ShapeControlPoint.TopRight));
else if (ReferenceEquals(sender, ResizeBottomRight))
OnResizeMouseMove(new ShapeControlEventArgs(e, ShapeControlPoint.BottomRight));
else if (ReferenceEquals(sender, ResizeBottomLeft))
OnResizeMouseMove(new ShapeControlEventArgs(e, ShapeControlPoint.BottomLeft));
}
private void MoveOnMouseDown(object sender, MouseEventArgs e)
{
((IInputElement) sender).CaptureMouse();
e.Handled = true;
if (ReferenceEquals(sender, LayerShapeOutline))
OnMoveMouseDown(new ShapeControlEventArgs(e, ShapeControlPoint.LayerShape));
else if (ReferenceEquals(sender, AnchorPoint))
OnMoveMouseDown(new ShapeControlEventArgs(e, ShapeControlPoint.Anchor));
}
private void MoveOnMouseUp(object sender, MouseEventArgs e)
{
if (ReferenceEquals(sender, LayerShapeOutline))
OnMoveMouseUp(new ShapeControlEventArgs(e, ShapeControlPoint.LayerShape));
else if (ReferenceEquals(sender, AnchorPoint))
OnMoveMouseUp(new ShapeControlEventArgs(e, ShapeControlPoint.Anchor));
((IInputElement) sender).ReleaseMouseCapture();
e.Handled = true;
}
private void MoveOnMouseMove(object sender, MouseEventArgs e)
{
if (ReferenceEquals(sender, LayerShapeOutline))
OnMoveMouseMove(new ShapeControlEventArgs(e, ShapeControlPoint.LayerShape));
else if (ReferenceEquals(sender, AnchorPoint))
OnMoveMouseMove(new ShapeControlEventArgs(e, ShapeControlPoint.Anchor));
}
#endregion
#region Events
public event EventHandler<ShapeControlEventArgs> RotateMouseDown;
public event EventHandler<ShapeControlEventArgs> RotateMouseUp;
public event EventHandler<ShapeControlEventArgs> RotateMouseMove;
public event EventHandler<ShapeControlEventArgs> ResizeMouseDown;
public event EventHandler<ShapeControlEventArgs> ResizeMouseUp;
public event EventHandler<ShapeControlEventArgs> ResizeMouseMove;
public event EventHandler<ShapeControlEventArgs> MoveMouseDown;
public event EventHandler<ShapeControlEventArgs> MoveMouseUp;
public event EventHandler<ShapeControlEventArgs> MoveMouseMove;
protected virtual void OnMoveMouseMove(ShapeControlEventArgs e)
{
MoveMouseMove?.Invoke(this, e);
}
protected virtual void OnMoveMouseUp(ShapeControlEventArgs e)
{
MoveMouseUp?.Invoke(this, e);
}
protected virtual void OnMoveMouseDown(ShapeControlEventArgs e)
{
MoveMouseDown?.Invoke(this, e);
}
protected virtual void OnResizeMouseMove(ShapeControlEventArgs e)
{
ResizeMouseMove?.Invoke(this, e);
}
protected virtual void OnResizeMouseUp(ShapeControlEventArgs e)
{
ResizeMouseUp?.Invoke(this, e);
}
protected virtual void OnResizeMouseDown(ShapeControlEventArgs e)
{
ResizeMouseDown?.Invoke(this, e);
}
protected virtual void OnRotateMouseMove(ShapeControlEventArgs e)
{
RotateMouseMove?.Invoke(this, e);
}
protected virtual void OnRotateMouseUp(ShapeControlEventArgs e)
{
RotateMouseUp?.Invoke(this, e);
}
protected virtual void OnRotateMouseDown(ShapeControlEventArgs e)
{
RotateMouseDown?.Invoke(this, e);
}
#endregion
}
}