mirror of
https://github.com/Artemis-RGB/Artemis
synced 2026-11-12 11:56:30 +00:00
Profile editor - Moved files
This commit is contained in:
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 & 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>
|
||||
+15
@@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
+73
@@ -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>
|
||||
+15
@@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
+20
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
+88
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
+123
@@ -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>
|
||||
+391
@@ -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
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user