mirror of
https://github.com/Artemis-RGB/Artemis
synced 2026-11-12 03:48:56 +00:00
Meta - Replace WPF projects with Avalonia projects
This commit is contained in:
1 parent
fcf0376a9a
commit
d7f3ee1190
1110 files changed
+10872
-52240
No files matched your search
@@ -1,61 +0,0 @@
|
||||
<UserControl x:Class="Artemis.UI.Screens.SurfaceEditor.Dialogs.SurfaceDeviceDetectInputView"
|
||||
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.SurfaceEditor.Dialogs"
|
||||
xmlns:materialDesign="http://materialdesigninxaml.net/winfx/xaml/themes"
|
||||
xmlns:s="https://github.com/canton7/Stylet"
|
||||
mc:Ignorable="d"
|
||||
d:DesignHeight="450" d:DesignWidth="800"
|
||||
d:DataContext="{d:DesignInstance {x:Type local:SurfaceDeviceDetectInputViewModel}}">
|
||||
<StackPanel Margin="16" Width="500">
|
||||
<TextBlock Text="{Binding Title}" Style="{StaticResource MaterialDesignHeadline6TextBlock}" Margin="0 0 0 25" TextWrapping="Wrap" />
|
||||
|
||||
<TextBlock Style="{StaticResource MaterialDesignBody1TextBlock}" TextWrapping="Wrap">
|
||||
Press a button/key on your <Run Text="{Binding SurfaceDeviceViewModel.Device.RgbDevice.DeviceInfo.DeviceName, Mode=OneWay}" />
|
||||
that is currently showing a <Bold Foreground="Yellow">yellow</Bold> color.
|
||||
</TextBlock>
|
||||
|
||||
<materialDesign:PackIcon Kind="Keyboard"
|
||||
Width="300"
|
||||
Height="340"
|
||||
HorizontalAlignment="Center"
|
||||
Visibility="{Binding IsMouse, Converter={x:Static s:BoolToVisibilityConverter.InverseInstance}, Mode=OneWay}" />
|
||||
<materialDesign:PackIcon Kind="Mouse"
|
||||
Width="250"
|
||||
Height="340"
|
||||
HorizontalAlignment="Center"
|
||||
Visibility="{Binding IsMouse, Converter={x:Static s:BoolToVisibilityConverter.Instance}, Mode=OneWay}" />
|
||||
|
||||
<TextBlock Style="{StaticResource MaterialDesignBody1TextBlock}" TextWrapping="Wrap">
|
||||
This will teach Artemis to associate button/key presses with this specific device.
|
||||
</TextBlock>
|
||||
|
||||
<Button HorizontalAlignment="Right"
|
||||
Style="{StaticResource MaterialDesignFlatButton}"
|
||||
IsCancel="True"
|
||||
Command="{s:Action Cancel}"
|
||||
Visibility="{Binding IsMouse, Converter={x:Static s:BoolToVisibilityConverter.Instance}, Mode=OneWay}">
|
||||
<Button.CommandParameter>
|
||||
<system:Boolean xmlns:system="clr-namespace:System;assembly=mscorlib">
|
||||
False
|
||||
</system:Boolean>
|
||||
</Button.CommandParameter>
|
||||
PRESS ESC TO CANCEL
|
||||
</Button>
|
||||
|
||||
<Button HorizontalAlignment="Right"
|
||||
Style="{StaticResource MaterialDesignFlatButton}"
|
||||
IsCancel="True"
|
||||
Command="{s:Action Cancel}"
|
||||
Visibility="{Binding IsMouse, Converter={x:Static s:BoolToVisibilityConverter.InverseInstance}, Mode=OneWay}">
|
||||
<Button.CommandParameter>
|
||||
<system:Boolean xmlns:system="clr-namespace:System;assembly=mscorlib">
|
||||
False
|
||||
</system:Boolean>
|
||||
</Button.CommandParameter>
|
||||
CANCEL
|
||||
</Button>
|
||||
</StackPanel>
|
||||
</UserControl>
|
||||
@@ -1,55 +0,0 @@
|
||||
using System;
|
||||
using System.Linq;
|
||||
using Artemis.Core;
|
||||
using Artemis.Core.Services;
|
||||
using Artemis.UI.Shared.Services;
|
||||
using MaterialDesignThemes.Wpf;
|
||||
using RGB.NET.Core;
|
||||
|
||||
namespace Artemis.UI.Screens.SurfaceEditor.Dialogs
|
||||
{
|
||||
public class SurfaceDeviceDetectInputViewModel : DialogViewModelBase
|
||||
{
|
||||
private readonly IInputService _inputService;
|
||||
private readonly IMessageService _messageService;
|
||||
private readonly IRgbService _rgbService;
|
||||
private readonly ListLedGroup _ledGroup;
|
||||
|
||||
public SurfaceDeviceDetectInputViewModel(ArtemisDevice device, IInputService inputService, IMessageService messageService, IRgbService rgbService)
|
||||
{
|
||||
Device = device;
|
||||
Title = $"{Device.RgbDevice.DeviceInfo.DeviceName} - Detect input";
|
||||
IsMouse = Device.DeviceType == RGBDeviceType.Mouse;
|
||||
|
||||
_inputService = inputService;
|
||||
_messageService = messageService;
|
||||
_rgbService = rgbService;
|
||||
_inputService.IdentifyDevice(Device);
|
||||
_inputService.DeviceIdentified += InputServiceOnDeviceIdentified;
|
||||
|
||||
// Create a LED group way at the top
|
||||
_ledGroup = new ListLedGroup(_rgbService.Surface, Device.Leds.Select(l => l.RgbLed))
|
||||
{
|
||||
Brush = new SolidColorBrush(new Color(255, 255, 0)),
|
||||
ZIndex = 999
|
||||
};
|
||||
}
|
||||
|
||||
public ArtemisDevice Device { get; }
|
||||
public string Title { get; }
|
||||
public bool IsMouse { get; }
|
||||
|
||||
public override void OnDialogClosed(object sender, DialogClosingEventArgs e)
|
||||
{
|
||||
base.OnDialogClosed(sender, e);
|
||||
_inputService.DeviceIdentified -= InputServiceOnDeviceIdentified;
|
||||
_ledGroup.Detach();
|
||||
}
|
||||
|
||||
private void InputServiceOnDeviceIdentified(object sender, EventArgs e)
|
||||
{
|
||||
Session?.Close(true);
|
||||
_messageService.ShowMessage($"{Device.RgbDevice.DeviceInfo.DeviceName} identified 😁");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
<UserControl xmlns="https://github.com/avaloniaui"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
|
||||
x:Class="Artemis.UI.Screens.SurfaceEditor.ListDeviceView">
|
||||
Welcome to Avalonia!
|
||||
</UserControl>
|
||||
@@ -0,0 +1,18 @@
|
||||
using Avalonia.Controls;
|
||||
using Avalonia.Markup.Xaml;
|
||||
|
||||
namespace Artemis.UI.Screens.SurfaceEditor
|
||||
{
|
||||
public partial class ListDeviceView : UserControl
|
||||
{
|
||||
public ListDeviceView()
|
||||
{
|
||||
InitializeComponent();
|
||||
}
|
||||
|
||||
private void InitializeComponent()
|
||||
{
|
||||
AvaloniaXamlLoader.Load(this);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
using Artemis.Core;
|
||||
using Artemis.UI.Shared;
|
||||
using ReactiveUI;
|
||||
using SkiaSharp;
|
||||
|
||||
namespace Artemis.UI.Screens.SurfaceEditor
|
||||
{
|
||||
public class ListDeviceViewModel : ViewModelBase
|
||||
{
|
||||
private SKColor _color;
|
||||
private bool _isSelected;
|
||||
|
||||
public ListDeviceViewModel(ArtemisDevice device)
|
||||
{
|
||||
Device = device;
|
||||
}
|
||||
|
||||
public ArtemisDevice Device { get; }
|
||||
|
||||
public bool IsSelected
|
||||
{
|
||||
get => _isSelected;
|
||||
set => RaiseAndSetIfChanged(ref _isSelected, value);
|
||||
}
|
||||
|
||||
public SKColor Color
|
||||
{
|
||||
get => _color;
|
||||
set => RaiseAndSetIfChanged(ref _color, value);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
<UserControl xmlns="https://github.com/avaloniaui"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
xmlns:controls="clr-namespace:Artemis.UI.Shared.Controls;assembly=Artemis.UI.Shared"
|
||||
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
|
||||
x:Class="Artemis.UI.Screens.SurfaceEditor.SurfaceDeviceView">
|
||||
<Grid>
|
||||
<Grid.Styles>
|
||||
<Style Selector="Border.selection-border">
|
||||
<Setter Property="Opacity" Value="0" />
|
||||
<Setter Property="Transitions">
|
||||
<Transitions>
|
||||
<DoubleTransition Property="Opacity" Duration="0:0:0.2" />
|
||||
</Transitions>
|
||||
</Setter>
|
||||
</Style>
|
||||
<Style Selector="Border.selection-border-selected">
|
||||
<Setter Property="Opacity" Value="1" />
|
||||
</Style>
|
||||
</Grid.Styles>
|
||||
|
||||
<controls:DeviceVisualizer Device="{Binding Device}" ShowColors="True"/>
|
||||
<Border x:Name="SurfaceDeviceBorder"
|
||||
Classes="selection-border"
|
||||
Classes.selection-border-selected="{Binding Highlighted}"
|
||||
BorderThickness="1">
|
||||
<Border.BorderBrush>
|
||||
<SolidColorBrush Color="{DynamicResource SystemAccentColorLight2}"></SolidColorBrush>
|
||||
</Border.BorderBrush>
|
||||
<Border.Background>
|
||||
<SolidColorBrush Color="{DynamicResource SystemAccentColorLight2}" Opacity="0.2"></SolidColorBrush>
|
||||
</Border.Background>
|
||||
</Border>
|
||||
</Grid>
|
||||
|
||||
</UserControl>
|
||||
@@ -0,0 +1,43 @@
|
||||
using Avalonia.Input;
|
||||
using Avalonia.Markup.Xaml;
|
||||
using Avalonia.ReactiveUI;
|
||||
|
||||
namespace Artemis.UI.Screens.SurfaceEditor
|
||||
{
|
||||
public class SurfaceDeviceView : ReactiveUserControl<SurfaceDeviceViewModel>
|
||||
{
|
||||
public SurfaceDeviceView()
|
||||
{
|
||||
InitializeComponent();
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void OnPointerEnter(PointerEventArgs e)
|
||||
{
|
||||
if (ViewModel?.SelectionStatus == SelectionStatus.None)
|
||||
{
|
||||
ViewModel.SelectionStatus = SelectionStatus.Hover;
|
||||
Cursor = new Cursor(StandardCursorType.Hand);
|
||||
}
|
||||
|
||||
base.OnPointerEnter(e);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void OnPointerLeave(PointerEventArgs e)
|
||||
{
|
||||
if (ViewModel?.SelectionStatus == SelectionStatus.Hover)
|
||||
{
|
||||
ViewModel.SelectionStatus = SelectionStatus.None;
|
||||
Cursor = new Cursor(StandardCursorType.Arrow);
|
||||
}
|
||||
|
||||
base.OnPointerLeave(e);
|
||||
}
|
||||
|
||||
private void InitializeComponent()
|
||||
{
|
||||
AvaloniaXamlLoader.Load(this);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,150 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Reactive;
|
||||
using System.Threading.Tasks;
|
||||
using Artemis.Core;
|
||||
using Artemis.Core.Services;
|
||||
using Artemis.UI.Ninject.Factories;
|
||||
using Artemis.UI.Shared;
|
||||
using Artemis.UI.Shared.Services.Interfaces;
|
||||
using Avalonia.Input;
|
||||
using ReactiveUI;
|
||||
using RGB.NET.Core;
|
||||
using SkiaSharp;
|
||||
using Point = Avalonia.Point;
|
||||
|
||||
namespace Artemis.UI.Screens.SurfaceEditor
|
||||
{
|
||||
public class SurfaceDeviceViewModel : ActivatableViewModelBase
|
||||
{
|
||||
private readonly IRgbService _rgbService;
|
||||
private readonly IDeviceService _deviceService;
|
||||
private readonly ISettingsService _settingsService;
|
||||
private readonly IDeviceVmFactory _deviceVmFactory;
|
||||
private readonly IWindowService _windowService;
|
||||
private Cursor _cursor;
|
||||
private double _dragOffsetX;
|
||||
private double _dragOffsetY;
|
||||
private SelectionStatus _selectionStatus;
|
||||
|
||||
public SurfaceDeviceViewModel(ArtemisDevice device, IRgbService rgbService, IDeviceService deviceService, ISettingsService settingsService, IDeviceVmFactory deviceVmFactory,
|
||||
IWindowService windowService)
|
||||
{
|
||||
_rgbService = rgbService;
|
||||
_deviceService = deviceService;
|
||||
_settingsService = settingsService;
|
||||
_deviceVmFactory = deviceVmFactory;
|
||||
_windowService = windowService;
|
||||
_cursor = Cursor.Default;
|
||||
|
||||
Device = device;
|
||||
|
||||
IdentifyDevice = ReactiveCommand.Create<ArtemisDevice>(ExecuteIdentifyDevice);
|
||||
ViewProperties = ReactiveCommand.CreateFromTask<ArtemisDevice>(ExecuteViewProperties);
|
||||
}
|
||||
|
||||
public ReactiveCommand<ArtemisDevice, Unit> IdentifyDevice { get; }
|
||||
public ReactiveCommand<ArtemisDevice, Unit> ViewProperties { get; }
|
||||
|
||||
public ArtemisDevice Device { get; }
|
||||
|
||||
public SelectionStatus SelectionStatus
|
||||
{
|
||||
get => _selectionStatus;
|
||||
set
|
||||
{
|
||||
RaiseAndSetIfChanged(ref _selectionStatus, value);
|
||||
this.RaisePropertyChanged(nameof(Highlighted));
|
||||
}
|
||||
}
|
||||
|
||||
public bool Highlighted => SelectionStatus != SelectionStatus.None;
|
||||
|
||||
public bool CanDetectInput => Device.DeviceType == RGBDeviceType.Keyboard || Device.DeviceType == RGBDeviceType.Mouse;
|
||||
|
||||
public Cursor Cursor
|
||||
{
|
||||
get => _cursor;
|
||||
set => RaiseAndSetIfChanged(ref _cursor, value);
|
||||
}
|
||||
|
||||
public void StartMouseDrag(Point mouseStartPosition)
|
||||
{
|
||||
if (SelectionStatus != SelectionStatus.Selected)
|
||||
return;
|
||||
|
||||
_dragOffsetX = Device.X - mouseStartPosition.X;
|
||||
_dragOffsetY = Device.Y - mouseStartPosition.Y;
|
||||
}
|
||||
|
||||
public void UpdateMouseDrag(Point mousePosition, bool round, bool ignoreOverlap)
|
||||
{
|
||||
if (SelectionStatus != SelectionStatus.Selected)
|
||||
return;
|
||||
|
||||
float x = (float) (mousePosition.X + _dragOffsetX);
|
||||
float y = (float) (mousePosition.Y + _dragOffsetY);
|
||||
|
||||
if (round)
|
||||
{
|
||||
x = (float) Math.Round(x / 10d, 0, MidpointRounding.AwayFromZero) * 10f;
|
||||
y = (float) Math.Round(y / 10d, 0, MidpointRounding.AwayFromZero) * 10f;
|
||||
}
|
||||
|
||||
|
||||
if (Fits(x, y, ignoreOverlap))
|
||||
{
|
||||
Device.X = x;
|
||||
Device.Y = y;
|
||||
}
|
||||
else if (Fits(x, Device.Y, ignoreOverlap))
|
||||
{
|
||||
Device.X = x;
|
||||
}
|
||||
else if (Fits(Device.X, y, ignoreOverlap))
|
||||
{
|
||||
Device.Y = y;
|
||||
}
|
||||
}
|
||||
|
||||
private void ExecuteIdentifyDevice(ArtemisDevice device)
|
||||
{
|
||||
_deviceService.IdentifyDevice(device);
|
||||
}
|
||||
|
||||
private async Task ExecuteViewProperties(ArtemisDevice device)
|
||||
{
|
||||
await _windowService.ShowDialogAsync(_deviceVmFactory.DevicePropertiesViewModel(device));
|
||||
}
|
||||
|
||||
private bool Fits(float x, float y, bool ignoreOverlap)
|
||||
{
|
||||
if (x < 0 || y < 0)
|
||||
return false;
|
||||
|
||||
double maxTextureSize = 4096 / _settingsService.GetSetting("Core.RenderScale", 0.25).Value;
|
||||
if (x + Device.Rectangle.Width > maxTextureSize || y + Device.Rectangle.Height > maxTextureSize)
|
||||
return false;
|
||||
|
||||
if (ignoreOverlap)
|
||||
return true;
|
||||
|
||||
IEnumerable<SKRect> own = Device.Leds
|
||||
.Select(l => SKRect.Create(l.Rectangle.Left + x, l.Rectangle.Top + y, l.Rectangle.Width, l.Rectangle.Height));
|
||||
IEnumerable<SKRect> others = _rgbService.EnabledDevices
|
||||
.Where(d => d != Device && d.IsEnabled)
|
||||
.SelectMany(d => d.Leds)
|
||||
.Select(l => SKRect.Create(l.Rectangle.Left + l.Device.X, l.Rectangle.Top + l.Device.Y, l.Rectangle.Width, l.Rectangle.Height));
|
||||
|
||||
return !own.Any(o => others.Any(l => l.IntersectsWith(o)));
|
||||
}
|
||||
}
|
||||
|
||||
public enum SelectionStatus
|
||||
{
|
||||
None,
|
||||
Hover,
|
||||
Selected
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,114 @@
|
||||
<UserControl xmlns="https://github.com/avaloniaui"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
xmlns:paz="clr-namespace:Avalonia.Controls.PanAndZoom;assembly=Avalonia.Controls.PanAndZoom"
|
||||
xmlns:avalonia="clr-namespace:Material.Icons.Avalonia;assembly=Material.Icons.Avalonia"
|
||||
xmlns:controls="clr-namespace:Artemis.UI.Shared.Controls;assembly=Artemis.UI.Shared"
|
||||
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
|
||||
x:Class="Artemis.UI.Screens.SurfaceEditor.SurfaceEditorView">
|
||||
|
||||
<Border Classes="router-container">
|
||||
<paz:ZoomBorder Name="ZoomBorder"
|
||||
Stretch="None"
|
||||
ClipToBounds="True"
|
||||
Focusable="True"
|
||||
VerticalAlignment="Stretch"
|
||||
HorizontalAlignment="Stretch"
|
||||
Background="{DynamicResource LargeCheckerboardBrush}"
|
||||
ZoomChanged="ZoomBorder_OnZoomChanged"
|
||||
PointerPressed="ZoomBorder_OnPointerPressed"
|
||||
PointerMoved="ZoomBorder_OnPointerMoved"
|
||||
PointerReleased="ZoomBorder_OnPointerReleased">
|
||||
<Grid Name="ContainerGrid" Background="Transparent">
|
||||
<Grid.Transitions>
|
||||
<Transitions>
|
||||
<TransformOperationsTransition Property="RenderTransform" Duration="0:0:0.2" Easing="CubicEaseOut"/>
|
||||
</Transitions>
|
||||
</Grid.Transitions>
|
||||
<ItemsControl Items="{Binding SurfaceDeviceViewModels}" ClipToBounds="False">
|
||||
<ItemsControl.Styles>
|
||||
<Style Selector="ContentPresenter">
|
||||
<Setter Property="Canvas.Left" Value="{Binding Device.X}" />
|
||||
<Setter Property="Canvas.Top" Value="{Binding Device.Y}" />
|
||||
</Style>
|
||||
</ItemsControl.Styles>
|
||||
<ItemsControl.ItemsPanel>
|
||||
<ItemsPanelTemplate>
|
||||
<Canvas />
|
||||
</ItemsPanelTemplate>
|
||||
</ItemsControl.ItemsPanel>
|
||||
<ItemsControl.ItemTemplate>
|
||||
<DataTemplate>
|
||||
<ContentControl Content="{Binding}">
|
||||
<ContentControl.ContextFlyout>
|
||||
<MenuFlyout>
|
||||
<MenuItem Header="Identify" Command="{Binding IdentifyDevice}" CommandParameter="{Binding Device}">
|
||||
<MenuItem.Icon>
|
||||
<avalonia:MaterialIcon Kind="AlarmLight" />
|
||||
</MenuItem.Icon>
|
||||
</MenuItem>
|
||||
<MenuItem Header="-" />
|
||||
<MenuItem Header="Bring to Front" Command="{Binding $parent[4].DataContext.BringToFront}" CommandParameter="{Binding Device}">
|
||||
<MenuItem.Icon>
|
||||
<avalonia:MaterialIcon Kind="ArrangeBringToFront" />
|
||||
</MenuItem.Icon>
|
||||
</MenuItem>
|
||||
<MenuItem Header="Bring Forward" Command="{Binding $parent[4].DataContext.BringForward}" CommandParameter="{Binding Device}">
|
||||
<MenuItem.Icon>
|
||||
<avalonia:MaterialIcon Kind="ArrangeBringForward" />
|
||||
</MenuItem.Icon>
|
||||
</MenuItem>
|
||||
<MenuItem Header="Send to Back" Command="{Binding $parent[4].DataContext.SendToBack}" CommandParameter="{Binding Device}">
|
||||
<MenuItem.Icon>
|
||||
<avalonia:MaterialIcon Kind="ArrangeSendToBack" />
|
||||
</MenuItem.Icon>
|
||||
</MenuItem>
|
||||
<MenuItem Header="Send Backward" Command="{Binding $parent[4].DataContext.SendBackward}" CommandParameter="{Binding Device}">
|
||||
<MenuItem.Icon>
|
||||
<avalonia:MaterialIcon Kind="ArrangeSendBackward" />
|
||||
</MenuItem.Icon>
|
||||
</MenuItem>
|
||||
<MenuItem Header="-" />
|
||||
<MenuItem Header="Identify input"
|
||||
Command="{Binding DetectInput}"
|
||||
CommandParameter="{Binding Device}"
|
||||
ToolTip.Tip="Teach Artemis which keypresses and/or button presses belong to this device">
|
||||
<MenuItem.Icon>
|
||||
<avalonia:MaterialIcon Kind="GestureDoubleTap" />
|
||||
</MenuItem.Icon>
|
||||
</MenuItem>
|
||||
<MenuItem Header="View properties" Command="{Binding ViewProperties}" CommandParameter="{Binding Device}">
|
||||
<MenuItem.Icon>
|
||||
<avalonia:MaterialIcon Kind="Gear" />
|
||||
</MenuItem.Icon>
|
||||
</MenuItem>
|
||||
</MenuFlyout>
|
||||
</ContentControl.ContextFlyout>
|
||||
</ContentControl>
|
||||
</DataTemplate>
|
||||
</ItemsControl.ItemTemplate>
|
||||
</ItemsControl>
|
||||
|
||||
<controls:SelectionRectangle Name="SelectionRectangle"
|
||||
InputElement="{Binding #ZoomBorder}"
|
||||
SelectionUpdated="SelectionRectangle_OnSelectionUpdated"
|
||||
BorderBrush="{DynamicResource SystemAccentColor}"
|
||||
BorderRadius="8">
|
||||
<controls:SelectionRectangle.Background>
|
||||
<SolidColorBrush Color="{DynamicResource SystemAccentColorLight1}" Opacity="0.2"></SolidColorBrush>
|
||||
</controls:SelectionRectangle.Background>
|
||||
</controls:SelectionRectangle>
|
||||
|
||||
<Border Name="SurfaceBounds"
|
||||
VerticalAlignment="Top"
|
||||
HorizontalAlignment="Left"
|
||||
Width="{Binding MaxTextureSize}"
|
||||
Height="{Binding MaxTextureSize}"
|
||||
BorderThickness="2"
|
||||
BorderBrush="{DynamicResource SystemAccentColorLight3}"
|
||||
CornerRadius="8" />
|
||||
</Grid>
|
||||
</paz:ZoomBorder>
|
||||
</Border>
|
||||
</UserControl>
|
||||
@@ -0,0 +1,112 @@
|
||||
using System.Reactive;
|
||||
using Artemis.UI.Shared.Controls;
|
||||
using Artemis.UI.Shared.Events;
|
||||
using Avalonia;
|
||||
using Avalonia.Controls;
|
||||
using Avalonia.Controls.PanAndZoom;
|
||||
using Avalonia.Input;
|
||||
using Avalonia.Markup.Xaml;
|
||||
using Avalonia.Media;
|
||||
using Avalonia.ReactiveUI;
|
||||
|
||||
namespace Artemis.UI.Screens.SurfaceEditor
|
||||
{
|
||||
public class SurfaceEditorView : ReactiveUserControl<SurfaceEditorViewModel>
|
||||
{
|
||||
private readonly Grid _containerGrid;
|
||||
private readonly SelectionRectangle _selectionRectangle;
|
||||
private readonly Border _surfaceBounds;
|
||||
private readonly ZoomBorder _zoomBorder;
|
||||
private bool _dragging;
|
||||
|
||||
public SurfaceEditorView()
|
||||
{
|
||||
InitializeComponent();
|
||||
|
||||
_zoomBorder = this.Find<ZoomBorder>("ZoomBorder");
|
||||
_containerGrid = this.Find<Grid>("ContainerGrid");
|
||||
_selectionRectangle = this.Find<SelectionRectangle>("SelectionRectangle");
|
||||
_surfaceBounds = this.Find<Border>("SurfaceBounds");
|
||||
|
||||
_zoomBorder.PropertyChanged += ZoomBorderOnPropertyChanged;
|
||||
UpdateZoomBorderBackground();
|
||||
}
|
||||
|
||||
private void ZoomBorderOnPropertyChanged(object? sender, AvaloniaPropertyChangedEventArgs e)
|
||||
{
|
||||
if (e.Property.Name == nameof(_zoomBorder.Background))
|
||||
UpdateZoomBorderBackground();
|
||||
}
|
||||
|
||||
private void InitializeComponent()
|
||||
{
|
||||
AvaloniaXamlLoader.Load(this);
|
||||
}
|
||||
|
||||
private void ZoomBorder_OnZoomChanged(object sender, ZoomChangedEventArgs e)
|
||||
{
|
||||
UpdateZoomBorderBackground();
|
||||
_selectionRectangle.BorderThickness = 1 / _zoomBorder.ZoomX;
|
||||
_surfaceBounds.BorderThickness = new Thickness(2 / _zoomBorder.ZoomX);
|
||||
}
|
||||
|
||||
private void ZoomBorder_OnPointerPressed(object? sender, PointerPressedEventArgs e)
|
||||
{
|
||||
if (!e.GetCurrentPoint(this).Properties.IsLeftButtonPressed)
|
||||
return;
|
||||
|
||||
_dragging = false;
|
||||
|
||||
if (e.Source is Border {Name: "SurfaceDeviceBorder"})
|
||||
{
|
||||
e.Pointer.Capture(_zoomBorder);
|
||||
e.Handled = true;
|
||||
}
|
||||
}
|
||||
|
||||
private void ZoomBorder_OnPointerMoved(object? sender, PointerEventArgs e)
|
||||
{
|
||||
if (!e.GetCurrentPoint(this).Properties.IsLeftButtonPressed)
|
||||
return;
|
||||
|
||||
if (ReferenceEquals(e.Pointer.Captured, sender))
|
||||
{
|
||||
if (!_dragging)
|
||||
ViewModel?.StartMouseDrag(e.GetPosition(_containerGrid));
|
||||
ViewModel?.UpdateMouseDrag(e.GetPosition(_containerGrid), e.KeyModifiers.HasFlag(KeyModifiers.Shift), e.KeyModifiers.HasFlag(KeyModifiers.Alt));
|
||||
}
|
||||
|
||||
_dragging = true;
|
||||
}
|
||||
|
||||
private void ZoomBorder_OnPointerReleased(object? sender, PointerReleasedEventArgs e)
|
||||
{
|
||||
if (e.InitialPressMouseButton != MouseButton.Left)
|
||||
return;
|
||||
|
||||
// If the mouse didn't move, apply selection
|
||||
if (!_dragging)
|
||||
{
|
||||
ViewModel?.SelectFirstDeviceAtPoint(e.GetPosition(_containerGrid), e.KeyModifiers.HasFlag(KeyModifiers.Shift));
|
||||
return;
|
||||
}
|
||||
|
||||
if (ReferenceEquals(e.Pointer.Captured, sender))
|
||||
{
|
||||
ViewModel?.StopMouseDrag(e.GetPosition(_containerGrid), e.KeyModifiers.HasFlag(KeyModifiers.Shift), e.KeyModifiers.HasFlag(KeyModifiers.Alt));
|
||||
e.Pointer.Capture(null);
|
||||
}
|
||||
}
|
||||
|
||||
private void SelectionRectangle_OnSelectionUpdated(object? sender, SelectionRectangleEventArgs e)
|
||||
{
|
||||
ViewModel?.UpdateSelection(e.Rectangle, e.KeyModifiers.HasFlag(KeyModifiers.Shift));
|
||||
}
|
||||
|
||||
private void UpdateZoomBorderBackground()
|
||||
{
|
||||
if (_zoomBorder.Background is VisualBrush visualBrush)
|
||||
visualBrush.DestinationRect = new RelativeRect(_zoomBorder.OffsetX * -1, _zoomBorder.OffsetY * -1, 20, 20, RelativeUnit.Absolute);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,335 +0,0 @@
|
||||
<UserControl
|
||||
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:shared="clr-namespace:Artemis.UI.Shared;assembly=Artemis.UI.Shared"
|
||||
xmlns:local="clr-namespace:Artemis.UI.Screens.SurfaceEditor"
|
||||
x:Class="Artemis.UI.Screens.SurfaceEditor.SurfaceEditorView"
|
||||
mc:Ignorable="d"
|
||||
d:DesignHeight="600" d:DesignWidth="600"
|
||||
d:DataContext="{d:DesignInstance {x:Type local:SurfaceEditorViewModel}}">
|
||||
<UserControl.Resources>
|
||||
<ResourceDictionary>
|
||||
<ResourceDictionary.MergedDictionaries>
|
||||
<ResourceDictionary
|
||||
Source="pack://application:,,,/MaterialDesignThemes.Wpf;component/Themes/MaterialDesignTheme.TextBlock.xaml" />
|
||||
<ResourceDictionary
|
||||
Source="pack://application:,,,/MaterialDesignThemes.Wpf;component/Themes/MaterialDesignTheme.PopupBox.xaml" />
|
||||
</ResourceDictionary.MergedDictionaries>
|
||||
</ResourceDictionary>
|
||||
</UserControl.Resources>
|
||||
<Grid Margin="10">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="*" MinWidth="100" />
|
||||
<ColumnDefinition Width="5" />
|
||||
<ColumnDefinition Width="{Binding SurfaceListWidth.Value, Mode=TwoWay}" MinWidth="100" />
|
||||
</Grid.ColumnDefinitions>
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition Height="*" />
|
||||
<RowDefinition Height="Auto" />
|
||||
</Grid.RowDefinitions>
|
||||
|
||||
<StackPanel Grid.Row="0" Grid.Column="0" Grid.ColumnSpan="3">
|
||||
<Grid>
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="25" />
|
||||
<ColumnDefinition />
|
||||
</Grid.ColumnDefinitions>
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition />
|
||||
<RowDefinition />
|
||||
</Grid.RowDefinitions>
|
||||
<materialDesign:PackIcon Grid.Column="0" Grid.RowSpan="2" Kind="AboutOutline" />
|
||||
<TextBlock Grid.Row="0" Grid.Column="1">
|
||||
The surface is a digital representation of your LED setup. Set this up accurately and effects will seamlessly move from one
|
||||
device to the other.
|
||||
</TextBlock>
|
||||
<TextBlock Grid.Row="1" Grid.Column="1">
|
||||
Are devices showing up as gray blocks and/or are LEDs in wrong place and not sure why?
|
||||
<Run Text="Check out the" FontWeight="Bold" />
|
||||
<Hyperlink Style="{StaticResource ArtemisHyperlink}"
|
||||
RequestNavigate="{s:Action OpenHyperlink}"
|
||||
FontWeight="Bold"
|
||||
NavigateUri="https://wiki.artemis-rgb.com/faq#q-my-device-is-missing-an-image-and-the-leds-are-in-the-wrong-place-whats-wrong">
|
||||
FAQ
|
||||
</Hyperlink>
|
||||
<Run Text="more info." FontWeight="Bold" />
|
||||
</TextBlock>
|
||||
</Grid>
|
||||
<Border BorderBrush="{DynamicResource MaterialDesignDivider}" BorderThickness="0 0 0 1" Margin="0 10" />
|
||||
</StackPanel>
|
||||
|
||||
<materialDesign:Card materialDesign:ShadowAssist.ShadowDepth="Depth1" Grid.Row="1" Grid.Column="0" VerticalAlignment="Stretch" Margin="0,0,5,0">
|
||||
<Grid ClipToBounds="False"
|
||||
Focusable="True"
|
||||
FocusVisualStyle="{StaticResource FocusVisual}"
|
||||
KeyUp="{s:Action EditorGridKeyUp}"
|
||||
KeyDown="{s:Action EditorGridKeyDown}"
|
||||
MouseWheel="{s:Action EditorGridMouseWheel}"
|
||||
MouseUp="{s:Action EditorGridMouseClick}"
|
||||
MouseDown="{s:Action EditorGridMouseClick}"
|
||||
MouseMove="{s:Action EditorGridMouseMove}"
|
||||
Cursor="{Binding Cursor}">
|
||||
<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.Triggers>
|
||||
<EventTrigger RoutedEvent="UIElement.MouseLeftButtonDown">
|
||||
<BeginStoryboard>
|
||||
<Storyboard Storyboard.TargetName="MultiSelectionPath" Storyboard.TargetProperty="Opacity">
|
||||
<DoubleAnimation From="0" To="1" Duration="0:0:0.1" />
|
||||
</Storyboard>
|
||||
</BeginStoryboard>
|
||||
</EventTrigger>
|
||||
<EventTrigger RoutedEvent="UIElement.MouseLeftButtonUp">
|
||||
<BeginStoryboard>
|
||||
<Storyboard Storyboard.TargetName="MultiSelectionPath" Storyboard.TargetProperty="Opacity">
|
||||
<DoubleAnimation From="1" To="0" Duration="0:0:0.2" />
|
||||
</Storyboard>
|
||||
</BeginStoryboard>
|
||||
</EventTrigger>
|
||||
</Grid.Triggers>
|
||||
|
||||
<Grid x:Name="EditorDisplayGrid"
|
||||
shared:SizeObserver.Observe="True"
|
||||
shared:SizeObserver.ObservedHeight="{Binding PanZoomViewModel.CanvasHeight, Mode=TwoWay}"
|
||||
shared:SizeObserver.ObservedWidth="{Binding PanZoomViewModel.CanvasWidth, Mode=TwoWay}">
|
||||
<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 SurfaceDeviceViewModels}">
|
||||
<ItemsControl.ItemsPanel>
|
||||
<ItemsPanelTemplate>
|
||||
<Canvas />
|
||||
</ItemsPanelTemplate>
|
||||
</ItemsControl.ItemsPanel>
|
||||
<ItemsControl.ItemContainerStyle>
|
||||
<Style TargetType="{x:Type ContentPresenter}">
|
||||
<Setter Property="Canvas.Left" Value="{Binding Device.X}" />
|
||||
<Setter Property="Canvas.Top" Value="{Binding Device.Y}" />
|
||||
</Style>
|
||||
</ItemsControl.ItemContainerStyle>
|
||||
<ItemsControl.ItemTemplate>
|
||||
<DataTemplate>
|
||||
<ContentControl s:View.Model="{Binding}">
|
||||
<ContentControl.ContextMenu>
|
||||
<ContextMenu>
|
||||
<MenuItem Header="Identify" Command="{s:Action IdentifyDevice}" CommandParameter="{Binding Device}">
|
||||
<MenuItem.Icon>
|
||||
<materialDesign:PackIcon Kind="AlarmLight" />
|
||||
</MenuItem.Icon>
|
||||
</MenuItem>
|
||||
<Separator />
|
||||
<MenuItem Header="Bring to Front" Command="{s:Action BringToFront}" CommandParameter="{Binding Device}">
|
||||
<MenuItem.Icon>
|
||||
<materialDesign:PackIcon Kind="ArrangeBringToFront" />
|
||||
</MenuItem.Icon>
|
||||
</MenuItem>
|
||||
<MenuItem Header="Bring Forward" Command="{s:Action BringForward}" CommandParameter="{Binding Device}">
|
||||
<MenuItem.Icon>
|
||||
<materialDesign:PackIcon Kind="ArrangeBringForward" />
|
||||
</MenuItem.Icon>
|
||||
</MenuItem>
|
||||
<MenuItem Header="Send to Back" Command="{s:Action SendToBack}" CommandParameter="{Binding Device}">
|
||||
<MenuItem.Icon>
|
||||
<materialDesign:PackIcon Kind="ArrangeSendToBack" />
|
||||
</MenuItem.Icon>
|
||||
</MenuItem>
|
||||
<MenuItem Header="Send Backward" Command="{s:Action SendBackward}" CommandParameter="{Binding Device}">
|
||||
<MenuItem.Icon>
|
||||
<materialDesign:PackIcon Kind="ArrangeSendBackward" />
|
||||
</MenuItem.Icon>
|
||||
</MenuItem>
|
||||
<Separator />
|
||||
<!-- Stylet can't disable it for us here, do it ourselves -->
|
||||
<MenuItem Header="Identify input"
|
||||
Command="{s:Action DetectInput}"
|
||||
CommandParameter="{Binding Device}"
|
||||
IsEnabled="{Binding CanDetectInput}"
|
||||
ToolTip="Teach Artemis which keypresses and/or button presses belong to this device">
|
||||
<MenuItem.Icon>
|
||||
<materialDesign:PackIcon Kind="GestureDoubleTap" />
|
||||
</MenuItem.Icon>
|
||||
</MenuItem>
|
||||
<MenuItem Header="View properties" Command="{s:Action ViewProperties}" CommandParameter="{Binding Device}">
|
||||
<MenuItem.Icon>
|
||||
<materialDesign:PackIcon Kind="Gear" />
|
||||
</MenuItem.Icon>
|
||||
</MenuItem>
|
||||
</ContextMenu>
|
||||
</ContentControl.ContextMenu>
|
||||
</ContentControl>
|
||||
</DataTemplate>
|
||||
</ItemsControl.ItemTemplate>
|
||||
</ItemsControl>
|
||||
<Canvas>
|
||||
<Rectangle Width="{Binding MaxTextureSize}"
|
||||
Height="{Binding MaxTextureSize}"
|
||||
Stroke="{DynamicResource SecondaryHueMidBrush}"
|
||||
StrokeThickness="{Binding MaxTextureSizeIndicatorThickness}"
|
||||
StrokeDashArray="2 2" />
|
||||
</Canvas>
|
||||
</Grid>
|
||||
|
||||
<!-- Multi-selection rectangle -->
|
||||
<Path Data="{Binding SelectionRectangle}" Opacity="0"
|
||||
Stroke="{DynamicResource PrimaryHueLightBrush}"
|
||||
StrokeThickness="1"
|
||||
x:Name="MultiSelectionPath"
|
||||
IsHitTestVisible="False">
|
||||
<Path.Fill>
|
||||
<SolidColorBrush Color="{DynamicResource Primary400}" Opacity="0.25" />
|
||||
</Path.Fill>
|
||||
</Path>
|
||||
|
||||
<StackPanel ZIndex="1" VerticalAlignment="Bottom" HorizontalAlignment="Left" Margin="10" Cursor="Arrow">
|
||||
<materialDesign:Card Padding="8">
|
||||
<StackPanel Orientation="Horizontal">
|
||||
<CheckBox Style="{StaticResource MaterialDesignCheckBox}"
|
||||
IsChecked="{Binding ColorDevices}"
|
||||
ToolTip="If selected, each device is completely lid up with a random color">
|
||||
Show random device colors
|
||||
</CheckBox>
|
||||
<CheckBox Style="{StaticResource MaterialDesignCheckBox}"
|
||||
IsChecked="{Binding ColorFirstLedOnly}"
|
||||
IsEnabled="{Binding ColorDevices}"
|
||||
ToolTip="If selected, only the first LED of each device is lid up, aiding with rotation settings"
|
||||
Margin="5 0 0 0">
|
||||
Light up first LED only
|
||||
</CheckBox>
|
||||
</StackPanel>
|
||||
</materialDesign:Card>
|
||||
</StackPanel>
|
||||
|
||||
<StackPanel Orientation="Horizontal" VerticalAlignment="Bottom" HorizontalAlignment="Right" Margin="0 0 15 15">
|
||||
<Button Command="{s:Action AutoArrange}"
|
||||
Style="{StaticResource MaterialDesignFloatingActionMiniButton}"
|
||||
HorizontalAlignment="Right"
|
||||
ToolTip="Apply auto-arrange"
|
||||
VerticalAlignment="Bottom">
|
||||
<materialDesign:PackIcon Kind="Wand" Height="24" Width="24" />
|
||||
</Button>
|
||||
<StackPanel Orientation="Vertical" Margin="10 0 0 0">
|
||||
<Slider Orientation="Vertical"
|
||||
HorizontalAlignment="Center"
|
||||
Margin="0 10"
|
||||
Height="120"
|
||||
Minimum="10"
|
||||
Maximum="400"
|
||||
Value="{Binding PanZoomViewModel.ZoomPercentage}" />
|
||||
<Button Command="{s:Action ResetZoomAndPan}"
|
||||
Style="{StaticResource MaterialDesignFloatingActionMiniButton}"
|
||||
HorizontalAlignment="Right"
|
||||
ToolTip="Reset zoom & position">
|
||||
<materialDesign:PackIcon Kind="ImageFilterCenterFocus" Height="24" Width="24" />
|
||||
</Button>
|
||||
</StackPanel>
|
||||
</StackPanel>
|
||||
|
||||
</Grid>
|
||||
</materialDesign:Card>
|
||||
|
||||
<GridSplitter Grid.Column="1" Grid.Row="1" Width="5" HorizontalAlignment="Stretch" Cursor="SizeWE" />
|
||||
|
||||
<materialDesign:Card materialDesign:ShadowAssist.ShadowDepth="Depth1" Grid.Row="1" Grid.Column="2" VerticalAlignment="Stretch" Margin="5,0,0,0">
|
||||
<materialDesign:DialogHost Identifier="SurfaceListDialogHost" DialogTheme="Inherit" CloseOnClickAway="True">
|
||||
|
||||
<ListBox Grid.Row="0" HorizontalContentAlignment="Stretch" ItemsSource="{Binding ListDeviceViewModels}" SelectionMode="Multiple">
|
||||
<ListBox.ItemContainerStyle>
|
||||
<Style TargetType="{x:Type ListBoxItem}" BasedOn="{StaticResource {x:Type ListBoxItem}}">
|
||||
<Setter Property="IsSelected" Value="{Binding IsSelected, Mode=TwoWay}" />
|
||||
</Style>
|
||||
|
||||
</ListBox.ItemContainerStyle>
|
||||
<ListBox.ItemTemplate>
|
||||
<DataTemplate>
|
||||
<ContentControl s:View.Model="{Binding}" VerticalContentAlignment="Stretch" HorizontalContentAlignment="Stretch" IsTabStop="False">
|
||||
<ContentControl.ContextMenu>
|
||||
<ContextMenu>
|
||||
<MenuItem Header="Identify" Command="{s:Action IdentifyDevice}" CommandParameter="{Binding Device}">
|
||||
<MenuItem.Icon>
|
||||
<materialDesign:PackIcon Kind="AlarmLight" />
|
||||
</MenuItem.Icon>
|
||||
</MenuItem>
|
||||
<Separator />
|
||||
<MenuItem Header="Bring to Front" Command="{s:Action BringToFront}" CommandParameter="{Binding Device}">
|
||||
<MenuItem.Icon>
|
||||
<materialDesign:PackIcon Kind="ArrangeBringToFront" />
|
||||
</MenuItem.Icon>
|
||||
</MenuItem>
|
||||
<MenuItem Header="Bring Forward" Command="{s:Action BringForward}" CommandParameter="{Binding Device}">
|
||||
<MenuItem.Icon>
|
||||
<materialDesign:PackIcon Kind="ArrangeBringForward" />
|
||||
</MenuItem.Icon>
|
||||
</MenuItem>
|
||||
<MenuItem Header="Send to Back" Command="{s:Action SendToBack}" CommandParameter="{Binding Device}">
|
||||
<MenuItem.Icon>
|
||||
<materialDesign:PackIcon Kind="ArrangeSendToBack" />
|
||||
</MenuItem.Icon>
|
||||
</MenuItem>
|
||||
<MenuItem Header="Send Backward" Command="{s:Action SendBackward}" CommandParameter="{Binding Device}">
|
||||
<MenuItem.Icon>
|
||||
<materialDesign:PackIcon Kind="ArrangeSendBackward" />
|
||||
</MenuItem.Icon>
|
||||
</MenuItem>
|
||||
<Separator />
|
||||
<!-- Stylet can't disable it for us here, do it ourselves -->
|
||||
<MenuItem Header="Identify input"
|
||||
Command="{s:Action DetectInput}"
|
||||
CommandParameter="{Binding Device}"
|
||||
IsEnabled="{Binding CanDetectInput}"
|
||||
ToolTip="Teach Artemis which keypresses and/or button presses belong to this device">
|
||||
<MenuItem.Icon>
|
||||
<materialDesign:PackIcon Kind="GestureDoubleTap" />
|
||||
</MenuItem.Icon>
|
||||
</MenuItem>
|
||||
<MenuItem Header="View properties" Command="{s:Action ViewProperties}" CommandParameter="{Binding Device}">
|
||||
<MenuItem.Icon>
|
||||
<materialDesign:PackIcon Kind="Gear" />
|
||||
</MenuItem.Icon>
|
||||
</MenuItem>
|
||||
</ContextMenu>
|
||||
</ContentControl.ContextMenu>
|
||||
</ContentControl>
|
||||
</DataTemplate>
|
||||
</ListBox.ItemTemplate>
|
||||
</ListBox>
|
||||
</materialDesign:DialogHost>
|
||||
</materialDesign:Card>
|
||||
|
||||
<TextBlock Grid.Column="0" Grid.Row="2" Grid.ColumnSpan="2" Style="{StaticResource MaterialDesignCaptionTextBlock}" Margin="0 5 0 -5">
|
||||
<InlineUIContainer>
|
||||
<materialDesign:PackIcon Kind="Keyboard" Margin="0 0 0 -3" />
|
||||
</InlineUIContainer>
|
||||
<Run Text="Hold " /><Run FontWeight="Bold" Text="shift" />
|
||||
<Run Text="or click and drag to select multiple devices at once. To move around the surface hold down" />
|
||||
<Run FontWeight="Bold" Text="ctrl" />
|
||||
<Run Text="and drag." />
|
||||
</TextBlock>
|
||||
</Grid>
|
||||
</UserControl>
|
||||
@@ -1,210 +1,144 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Linq;
|
||||
using System.Reactive;
|
||||
using System.Threading.Tasks;
|
||||
using System.Windows;
|
||||
using System.Windows.Controls;
|
||||
using System.Windows.Input;
|
||||
using System.Windows.Media;
|
||||
using System.Windows.Navigation;
|
||||
using Artemis.Core;
|
||||
using Artemis.Core.Services;
|
||||
using Artemis.UI.Extensions;
|
||||
using Artemis.UI.Ninject.Factories;
|
||||
using Artemis.UI.Screens.Shared;
|
||||
using Artemis.UI.Screens.SurfaceEditor.Dialogs;
|
||||
using Artemis.UI.Screens.SurfaceEditor.Visualization;
|
||||
using Artemis.UI.Shared;
|
||||
using Artemis.UI.Shared.Services;
|
||||
using Avalonia;
|
||||
using Avalonia.Skia;
|
||||
using ReactiveUI;
|
||||
using SkiaSharp;
|
||||
using SkiaSharp.Views.WPF;
|
||||
using Stylet;
|
||||
using MouseButton = System.Windows.Input.MouseButton;
|
||||
|
||||
namespace Artemis.UI.Screens.SurfaceEditor
|
||||
{
|
||||
public class SurfaceEditorViewModel : MainScreenViewModel
|
||||
{
|
||||
private readonly ICoreService _coreService;
|
||||
private readonly IDeviceService _deviceService;
|
||||
private readonly IWindowManager _windowManager;
|
||||
private readonly IDeviceDebugVmFactory _deviceDebugVmFactory;
|
||||
private readonly IDialogService _dialogService;
|
||||
private readonly IRgbService _rgbService;
|
||||
private readonly ISettingsService _settingsService;
|
||||
private Cursor _cursor;
|
||||
private PanZoomViewModel _panZoomViewModel;
|
||||
private RectangleGeometry _selectionRectangle;
|
||||
private PluginSetting<GridLength> _surfaceListWidth;
|
||||
private bool _saving;
|
||||
|
||||
public SurfaceEditorViewModel(IRgbService rgbService,
|
||||
ICoreService coreService,
|
||||
IDialogService dialogService,
|
||||
ISettingsService settingsService,
|
||||
IDeviceService deviceService,
|
||||
IWindowManager windowManager,
|
||||
IDeviceDebugVmFactory deviceDebugVmFactory)
|
||||
public SurfaceEditorViewModel(IScreen hostScreen,
|
||||
IRgbService rgbService,
|
||||
SurfaceVmFactory surfaceVmFactory,
|
||||
ISettingsService settingsService) : base(hostScreen, "surface-editor")
|
||||
{
|
||||
DisplayName = "Surface Editor";
|
||||
SelectionRectangle = new RectangleGeometry();
|
||||
PanZoomViewModel = new PanZoomViewModel();
|
||||
Cursor = null;
|
||||
ColorDevices = true;
|
||||
|
||||
SurfaceDeviceViewModels = new BindableCollection<SurfaceDeviceViewModel>();
|
||||
ListDeviceViewModels = new BindableCollection<ListDeviceViewModel>();
|
||||
|
||||
_rgbService = rgbService;
|
||||
_coreService = coreService;
|
||||
_dialogService = dialogService;
|
||||
_settingsService = settingsService;
|
||||
_deviceService = deviceService;
|
||||
_windowManager = windowManager;
|
||||
_deviceDebugVmFactory = deviceDebugVmFactory;
|
||||
DisplayName = "Surface Editor";
|
||||
SurfaceDeviceViewModels = new ObservableCollection<SurfaceDeviceViewModel>(rgbService.Devices.OrderBy(d => d.ZIndex).Select(surfaceVmFactory.SurfaceDeviceViewModel));
|
||||
ListDeviceViewModels = new ObservableCollection<ListDeviceViewModel>(rgbService.Devices.OrderBy(d => d.ZIndex).Select(surfaceVmFactory.ListDeviceViewModel));
|
||||
|
||||
BringToFront = ReactiveCommand.Create<ArtemisDevice>(ExecuteBringToFront);
|
||||
BringForward = ReactiveCommand.Create<ArtemisDevice>(ExecuteBringForward);
|
||||
SendToBack = ReactiveCommand.Create<ArtemisDevice>(ExecuteSendToBack);
|
||||
SendBackward = ReactiveCommand.Create<ArtemisDevice>(ExecuteSendBackward);
|
||||
}
|
||||
|
||||
public BindableCollection<SurfaceDeviceViewModel> SurfaceDeviceViewModels { get; }
|
||||
public BindableCollection<ListDeviceViewModel> ListDeviceViewModels { get; }
|
||||
public ObservableCollection<SurfaceDeviceViewModel> SurfaceDeviceViewModels { get; }
|
||||
public ObservableCollection<ListDeviceViewModel> ListDeviceViewModels { get; }
|
||||
|
||||
public RectangleGeometry SelectionRectangle
|
||||
{
|
||||
get => _selectionRectangle;
|
||||
set => SetAndNotify(ref _selectionRectangle, value);
|
||||
}
|
||||
|
||||
public PanZoomViewModel PanZoomViewModel
|
||||
{
|
||||
get => _panZoomViewModel;
|
||||
set => SetAndNotify(ref _panZoomViewModel, value);
|
||||
}
|
||||
|
||||
public PluginSetting<GridLength> SurfaceListWidth
|
||||
{
|
||||
get => _surfaceListWidth;
|
||||
set => SetAndNotify(ref _surfaceListWidth, value);
|
||||
}
|
||||
|
||||
public Cursor Cursor
|
||||
{
|
||||
get => _cursor;
|
||||
set => SetAndNotify(ref _cursor, value);
|
||||
}
|
||||
|
||||
public bool ColorDevices
|
||||
{
|
||||
get => _colorDevices;
|
||||
set
|
||||
{
|
||||
SetAndNotify(ref _colorDevices, value);
|
||||
if (!value)
|
||||
ColorFirstLedOnly = false;
|
||||
}
|
||||
}
|
||||
|
||||
public bool ColorFirstLedOnly
|
||||
{
|
||||
get => _colorFirstLedOnly;
|
||||
set => SetAndNotify(ref _colorFirstLedOnly, value);
|
||||
}
|
||||
public ReactiveCommand<ArtemisDevice, Unit> BringToFront { get; }
|
||||
public ReactiveCommand<ArtemisDevice, Unit> BringForward { get; }
|
||||
public ReactiveCommand<ArtemisDevice, Unit> SendToBack { get; }
|
||||
public ReactiveCommand<ArtemisDevice, Unit> SendBackward { get; }
|
||||
|
||||
public double MaxTextureSize => 4096 / _settingsService.GetSetting("Core.RenderScale", 0.25).Value;
|
||||
public double MaxTextureSizeIndicatorThickness => 2 / PanZoomViewModel.Zoom;
|
||||
|
||||
public void OpenHyperlink(object sender, RequestNavigateEventArgs e)
|
||||
public void ClearSelection()
|
||||
{
|
||||
Core.Utilities.OpenUrl(e.Uri.AbsoluteUri);
|
||||
foreach (SurfaceDeviceViewModel surfaceDeviceViewModel in SurfaceDeviceViewModels)
|
||||
surfaceDeviceViewModel.SelectionStatus = SelectionStatus.None;
|
||||
}
|
||||
|
||||
public async Task AutoArrange()
|
||||
public void StartMouseDrag(Point mousePosition)
|
||||
{
|
||||
bool confirmed = await _dialogService.ShowConfirmDialog("Auto-arrange layout", "Are you sure you want to auto-arrange your layout? Your current settings will be overwritten.");
|
||||
if (!confirmed)
|
||||
return;
|
||||
|
||||
_rgbService.AutoArrangeDevices();
|
||||
}
|
||||
|
||||
private void LoadWorkspaceSettings()
|
||||
{
|
||||
SurfaceListWidth = _settingsService.GetSetting("SurfaceEditor.SurfaceListWidth", new GridLength(300.0));
|
||||
}
|
||||
|
||||
private void SaveWorkspaceSettings()
|
||||
{
|
||||
SurfaceListWidth.Save();
|
||||
}
|
||||
|
||||
|
||||
private void CoreServiceOnFrameRendering(object sender, FrameRenderingEventArgs e)
|
||||
{
|
||||
if (!ColorDevices)
|
||||
return;
|
||||
|
||||
e.Canvas.Clear(new SKColor(0, 0, 0));
|
||||
foreach (ListDeviceViewModel listDeviceViewModel in ListDeviceViewModels)
|
||||
SurfaceDeviceViewModel? startedOn = GetViewModelAtPoint(mousePosition);
|
||||
if (startedOn != null && startedOn.SelectionStatus != SelectionStatus.Selected)
|
||||
{
|
||||
// Order by position to accurately get the first LED
|
||||
List<ArtemisLed> leds = listDeviceViewModel.Device.Leds.OrderBy(l => l.RgbLed.Location.Y).ThenBy(l => l.RgbLed.Location.X).ToList();
|
||||
for (int index = 0; index < leds.Count; index++)
|
||||
startedOn.SelectionStatus = SelectionStatus.Selected;
|
||||
foreach (SurfaceDeviceViewModel device in SurfaceDeviceViewModels.Where(vm => vm != startedOn))
|
||||
device.SelectionStatus = SelectionStatus.None;
|
||||
}
|
||||
|
||||
foreach (SurfaceDeviceViewModel surfaceDeviceViewModel in SurfaceDeviceViewModels)
|
||||
surfaceDeviceViewModel.StartMouseDrag(mousePosition);
|
||||
}
|
||||
|
||||
public void UpdateMouseDrag(Point mousePosition, bool round, bool ignoreOverlap)
|
||||
{
|
||||
foreach (SurfaceDeviceViewModel surfaceDeviceViewModel in SurfaceDeviceViewModels)
|
||||
surfaceDeviceViewModel.UpdateMouseDrag(mousePosition, round, ignoreOverlap);
|
||||
}
|
||||
|
||||
public void StopMouseDrag(Point mousePosition, bool round, bool ignoreOverlap)
|
||||
{
|
||||
foreach (SurfaceDeviceViewModel surfaceDeviceViewModel in SurfaceDeviceViewModels)
|
||||
surfaceDeviceViewModel.UpdateMouseDrag(mousePosition, round, ignoreOverlap);
|
||||
|
||||
if (_saving)
|
||||
return;
|
||||
|
||||
Task.Run(() =>
|
||||
{
|
||||
try
|
||||
{
|
||||
ArtemisLed artemisLed = leds[index];
|
||||
if (ColorFirstLedOnly && index == 0 || !ColorFirstLedOnly)
|
||||
e.Canvas.DrawRect(artemisLed.AbsoluteRectangle, new SKPaint {Color = listDeviceViewModel.Color});
|
||||
_saving = true;
|
||||
_rgbService.SaveDevices();
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
_saving = false;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
#region Overrides of Screen
|
||||
|
||||
protected override void OnInitialActivate()
|
||||
public void SelectFirstDeviceAtPoint(Point point, bool expand)
|
||||
{
|
||||
LoadWorkspaceSettings();
|
||||
SurfaceDeviceViewModels.AddRange(_rgbService.EnabledDevices.OrderBy(d => d.ZIndex).Select(d => new SurfaceDeviceViewModel(d, _rgbService, _settingsService)));
|
||||
ListDeviceViewModels.AddRange(_rgbService.EnabledDevices.OrderBy(d => d.ZIndex * -1).Select(d => new ListDeviceViewModel(d, this)));
|
||||
SurfaceDeviceViewModel? toSelect = GetViewModelAtPoint(point);
|
||||
if (toSelect != null)
|
||||
toSelect.SelectionStatus = SelectionStatus.Selected;
|
||||
|
||||
List<ArtemisDevice> shuffledDevices = _rgbService.EnabledDevices.OrderBy(d => Guid.NewGuid()).ToList();
|
||||
float amount = 360f / shuffledDevices.Count;
|
||||
for (int i = 0; i < shuffledDevices.Count; i++)
|
||||
if (!expand)
|
||||
{
|
||||
ArtemisDevice rgbServiceDevice = shuffledDevices[i];
|
||||
ListDeviceViewModel vm = ListDeviceViewModels.First(l => l.Device == rgbServiceDevice);
|
||||
vm.Color = SKColor.FromHsv(amount * i, 100, 100);
|
||||
foreach (SurfaceDeviceViewModel device in SurfaceDeviceViewModels.Where(vm => vm != toSelect))
|
||||
device.SelectionStatus = SelectionStatus.None;
|
||||
}
|
||||
|
||||
_coreService.FrameRendering += CoreServiceOnFrameRendering;
|
||||
PanZoomViewModel.PropertyChanged += PanZoomViewModelOnPropertyChanged;
|
||||
base.OnInitialActivate();
|
||||
ApplySurfaceSelection();
|
||||
}
|
||||
|
||||
protected override void OnClose()
|
||||
private SurfaceDeviceViewModel? GetViewModelAtPoint(Point point)
|
||||
{
|
||||
SaveWorkspaceSettings();
|
||||
SurfaceDeviceViewModels.Clear();
|
||||
ListDeviceViewModels.Clear();
|
||||
|
||||
_coreService.FrameRendering -= CoreServiceOnFrameRendering;
|
||||
PanZoomViewModel.PropertyChanged -= PanZoomViewModelOnPropertyChanged;
|
||||
|
||||
base.OnClose();
|
||||
SKPoint hitTestPoint = point.ToSKPoint();
|
||||
return SurfaceDeviceViewModels.OrderByDescending(vm => vm.Device.ZIndex).FirstOrDefault(d => d.Device.Rectangle.Contains(hitTestPoint));
|
||||
}
|
||||
|
||||
private void PanZoomViewModelOnPropertyChanged(object sender, PropertyChangedEventArgs e)
|
||||
public void UpdateSelection(Rect rect, bool expand)
|
||||
{
|
||||
if (e.PropertyName == nameof(PanZoomViewModel.Zoom))
|
||||
NotifyOfPropertyChange(nameof(MaxTextureSizeIndicatorThickness));
|
||||
SKRect hitTestRect = rect.ToSKRect();
|
||||
foreach (SurfaceDeviceViewModel device in SurfaceDeviceViewModels)
|
||||
{
|
||||
if (device.Device.Rectangle.IntersectsWith(hitTestRect))
|
||||
device.SelectionStatus = SelectionStatus.Selected;
|
||||
else if (!expand)
|
||||
device.SelectionStatus = SelectionStatus.None;
|
||||
}
|
||||
|
||||
ApplySurfaceSelection();
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Context menu actions
|
||||
|
||||
public void IdentifyDevice(ArtemisDevice device)
|
||||
private void ApplySurfaceSelection()
|
||||
{
|
||||
_deviceService.IdentifyDevice(device);
|
||||
foreach (ListDeviceViewModel viewModel in ListDeviceViewModels)
|
||||
viewModel.IsSelected = SurfaceDeviceViewModels.Any(s => s.Device == viewModel.Device && s.SelectionStatus == SelectionStatus.Selected);
|
||||
}
|
||||
|
||||
public void BringToFront(ArtemisDevice device)
|
||||
#region Context menu commands
|
||||
|
||||
private void ExecuteBringToFront(ArtemisDevice device)
|
||||
{
|
||||
SurfaceDeviceViewModel surfaceDeviceViewModel = SurfaceDeviceViewModels.First(d => d.Device == device);
|
||||
SurfaceDeviceViewModels.Move(SurfaceDeviceViewModels.IndexOf(surfaceDeviceViewModel), SurfaceDeviceViewModels.Count - 1);
|
||||
@@ -219,7 +153,7 @@ namespace Artemis.UI.Screens.SurfaceEditor
|
||||
_rgbService.SaveDevices();
|
||||
}
|
||||
|
||||
public void BringForward(ArtemisDevice device)
|
||||
private void ExecuteBringForward(ArtemisDevice device)
|
||||
{
|
||||
SurfaceDeviceViewModel surfaceDeviceViewModel = SurfaceDeviceViewModels.First(d => d.Device == device);
|
||||
int currentIndex = SurfaceDeviceViewModels.IndexOf(surfaceDeviceViewModel);
|
||||
@@ -237,7 +171,7 @@ namespace Artemis.UI.Screens.SurfaceEditor
|
||||
_rgbService.SaveDevices();
|
||||
}
|
||||
|
||||
public void SendToBack(ArtemisDevice device)
|
||||
private void ExecuteSendToBack(ArtemisDevice device)
|
||||
{
|
||||
SurfaceDeviceViewModel surfaceDeviceViewModel = SurfaceDeviceViewModels.First(d => d.Device == device);
|
||||
SurfaceDeviceViewModels.Move(SurfaceDeviceViewModels.IndexOf(surfaceDeviceViewModel), 0);
|
||||
@@ -252,7 +186,7 @@ namespace Artemis.UI.Screens.SurfaceEditor
|
||||
_rgbService.SaveDevices();
|
||||
}
|
||||
|
||||
public void SendBackward(ArtemisDevice device)
|
||||
private void ExecuteSendBackward(ArtemisDevice device)
|
||||
{
|
||||
SurfaceDeviceViewModel surfaceDeviceViewModel = SurfaceDeviceViewModels.First(d => d.Device == device);
|
||||
int currentIndex = SurfaceDeviceViewModels.IndexOf(surfaceDeviceViewModel);
|
||||
@@ -269,199 +203,6 @@ namespace Artemis.UI.Screens.SurfaceEditor
|
||||
_rgbService.SaveDevices();
|
||||
}
|
||||
|
||||
public void ViewProperties(ArtemisDevice device)
|
||||
{
|
||||
_windowManager.ShowWindow(_deviceDebugVmFactory.DeviceDialogViewModel(device));
|
||||
}
|
||||
|
||||
public async Task DetectInput(ArtemisDevice device)
|
||||
{
|
||||
object madeChanges = await _dialogService.ShowDialog<SurfaceDeviceDetectInputViewModel>(new Dictionary<string, object> {{"device", device}});
|
||||
if ((bool) madeChanges)
|
||||
_rgbService.SaveDevice(device);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Selection
|
||||
|
||||
private MouseDragStatus _mouseDragStatus;
|
||||
private Point _mouseDragStartPoint;
|
||||
private bool _colorDevices;
|
||||
private bool _colorFirstLedOnly;
|
||||
|
||||
// ReSharper disable once UnusedMember.Global - Called from view
|
||||
public void EditorGridMouseClick(object sender, MouseButtonEventArgs e)
|
||||
{
|
||||
if (e.LeftButton == MouseButtonState.Pressed)
|
||||
((IInputElement) sender).CaptureMouse();
|
||||
else
|
||||
((IInputElement) sender).ReleaseMouseCapture();
|
||||
|
||||
if (IsPanKeyDown() || e.ChangedButton == MouseButton.Right)
|
||||
return;
|
||||
|
||||
Point position = e.GetPosition((IInputElement) sender);
|
||||
Point relative = PanZoomViewModel.GetRelativeMousePosition(sender, e);
|
||||
if (e.LeftButton == MouseButtonState.Pressed)
|
||||
StartMouseDrag(sender, position, relative);
|
||||
else
|
||||
StopMouseDrag(position);
|
||||
}
|
||||
|
||||
// ReSharper disable once UnusedMember.Global - Called from view
|
||||
public void EditorGridMouseMove(object sender, MouseEventArgs e)
|
||||
{
|
||||
// If holding down Ctrl, pan instead of move/select
|
||||
if (IsPanKeyDown())
|
||||
{
|
||||
Pan(sender, e);
|
||||
return;
|
||||
}
|
||||
|
||||
Point position = e.GetPosition((IInputElement) sender);
|
||||
Point relative = PanZoomViewModel.GetRelativeMousePosition(sender, e);
|
||||
if (_mouseDragStatus == MouseDragStatus.Dragging)
|
||||
MoveSelected(relative);
|
||||
else if (_mouseDragStatus == MouseDragStatus.Selecting)
|
||||
UpdateSelection(position);
|
||||
}
|
||||
|
||||
private void StartMouseDrag(object sender, Point position, Point relative)
|
||||
{
|
||||
// If drag started on top of a device, initialise dragging
|
||||
RectangleGeometry selectedRect = new(new Rect(position, new Size(1, 1)));
|
||||
SurfaceDeviceViewModel device = HitTestUtilities.GetHitViewModels<SurfaceDeviceViewModel>((Visual) sender, selectedRect).FirstOrDefault();
|
||||
if (device != null)
|
||||
{
|
||||
_rgbService.SetRenderPaused(true);
|
||||
_mouseDragStatus = MouseDragStatus.Dragging;
|
||||
// If the device is not selected, deselect others and select only this one (if shift not held)
|
||||
if (device.SelectionStatus != SelectionStatus.Selected)
|
||||
{
|
||||
if (!Keyboard.IsKeyDown(Key.LeftShift) && !Keyboard.IsKeyDown(Key.RightShift))
|
||||
foreach (SurfaceDeviceViewModel others in SurfaceDeviceViewModels)
|
||||
others.SelectionStatus = SelectionStatus.None;
|
||||
|
||||
device.SelectionStatus = SelectionStatus.Selected;
|
||||
}
|
||||
|
||||
foreach (SurfaceDeviceViewModel selectedDevice in SurfaceDeviceViewModels.Where(d => d.SelectionStatus == SelectionStatus.Selected))
|
||||
selectedDevice.StartMouseDrag(relative);
|
||||
}
|
||||
// Start multi-selection
|
||||
else
|
||||
{
|
||||
_mouseDragStatus = MouseDragStatus.Selecting;
|
||||
_mouseDragStartPoint = position;
|
||||
}
|
||||
|
||||
// Any time dragging starts, start with a new rect
|
||||
SelectionRectangle.Rect = new Rect();
|
||||
ApplySurfaceSelection();
|
||||
}
|
||||
|
||||
private void StopMouseDrag(Point position)
|
||||
{
|
||||
if (_mouseDragStatus != MouseDragStatus.Dragging)
|
||||
{
|
||||
SKRect hitTestRect = PanZoomViewModel.UnTransformContainingRect(new Rect(_mouseDragStartPoint, position)).ToSKRect();
|
||||
foreach (SurfaceDeviceViewModel device in SurfaceDeviceViewModels)
|
||||
{
|
||||
if (device.Device.Rectangle.IntersectsWith(hitTestRect))
|
||||
device.SelectionStatus = SelectionStatus.Selected;
|
||||
else if (!Keyboard.IsKeyDown(Key.LeftShift) && !Keyboard.IsKeyDown(Key.RightShift))
|
||||
device.SelectionStatus = SelectionStatus.None;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
_rgbService.SaveDevices();
|
||||
}
|
||||
|
||||
_mouseDragStatus = MouseDragStatus.None;
|
||||
_rgbService.SetRenderPaused(false);
|
||||
ApplySurfaceSelection();
|
||||
}
|
||||
|
||||
private void UpdateSelection(Point position)
|
||||
{
|
||||
if (IsPanKeyDown())
|
||||
return;
|
||||
|
||||
Rect selectedRect = new(_mouseDragStartPoint, position);
|
||||
SelectionRectangle.Rect = selectedRect;
|
||||
|
||||
SKRect hitTestRect = PanZoomViewModel.UnTransformContainingRect(new Rect(_mouseDragStartPoint, position)).ToSKRect();
|
||||
foreach (SurfaceDeviceViewModel device in SurfaceDeviceViewModels)
|
||||
{
|
||||
if (device.Device.Rectangle.IntersectsWith(hitTestRect))
|
||||
device.SelectionStatus = SelectionStatus.Selected;
|
||||
else if (!Keyboard.IsKeyDown(Key.LeftShift) && !Keyboard.IsKeyDown(Key.RightShift))
|
||||
device.SelectionStatus = SelectionStatus.None;
|
||||
}
|
||||
|
||||
ApplySurfaceSelection();
|
||||
}
|
||||
|
||||
private void MoveSelected(Point position)
|
||||
{
|
||||
foreach (SurfaceDeviceViewModel device in SurfaceDeviceViewModels.Where(d => d.SelectionStatus == SelectionStatus.Selected))
|
||||
device.UpdateMouseDrag(position);
|
||||
}
|
||||
|
||||
private void ApplySurfaceSelection()
|
||||
{
|
||||
foreach (ListDeviceViewModel viewModel in ListDeviceViewModels)
|
||||
viewModel.IsSelected = SurfaceDeviceViewModels.Any(s => s.Device == viewModel.Device && s.SelectionStatus == SelectionStatus.Selected);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Panning and zooming
|
||||
|
||||
public void EditorGridMouseWheel(object sender, MouseWheelEventArgs e)
|
||||
{
|
||||
PanZoomViewModel.ProcessMouseScroll(sender, e);
|
||||
}
|
||||
|
||||
public void EditorGridKeyDown(object sender, KeyEventArgs e)
|
||||
{
|
||||
if ((e.Key == Key.LeftCtrl || e.Key == Key.RightCtrl) && e.IsDown)
|
||||
Cursor = Cursors.ScrollAll;
|
||||
}
|
||||
|
||||
public void EditorGridKeyUp(object sender, KeyEventArgs e)
|
||||
{
|
||||
if ((e.Key == Key.LeftCtrl || e.Key == Key.RightCtrl) && e.IsUp)
|
||||
Cursor = null;
|
||||
}
|
||||
|
||||
public void Pan(object sender, MouseEventArgs e)
|
||||
{
|
||||
PanZoomViewModel.ProcessMouseMove(sender, e);
|
||||
|
||||
// Empty the selection rect since it's shown while mouse is down
|
||||
SelectionRectangle.Rect = Rect.Empty;
|
||||
}
|
||||
|
||||
public void ResetZoomAndPan()
|
||||
{
|
||||
PanZoomViewModel.Reset();
|
||||
}
|
||||
|
||||
private bool IsPanKeyDown()
|
||||
{
|
||||
return Keyboard.IsKeyDown(Key.LeftCtrl) || Keyboard.IsKeyDown(Key.RightCtrl);
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
|
||||
internal enum MouseDragStatus
|
||||
{
|
||||
None,
|
||||
Selecting,
|
||||
Dragging
|
||||
}
|
||||
}
|
||||
@@ -1,38 +0,0 @@
|
||||
<UserControl x:Class="Artemis.UI.Screens.SurfaceEditor.Visualization.ListDeviceView"
|
||||
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.SurfaceEditor.Visualization"
|
||||
xmlns:shared="clr-namespace:Artemis.UI.Shared;assembly=Artemis.UI.Shared"
|
||||
xmlns:s="https://github.com/canton7/Stylet"
|
||||
mc:Ignorable="d"
|
||||
d:DesignHeight="450" d:DesignWidth="800"
|
||||
d:DataContext="{d:DesignInstance {x:Type local:ListDeviceViewModel}}">
|
||||
<UserControl.Resources>
|
||||
<shared:SKColorToColorConverter x:Key="SKColorToColorConverter" />
|
||||
</UserControl.Resources>
|
||||
<Grid>
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="50" />
|
||||
<ColumnDefinition Width="*" />
|
||||
<ColumnDefinition Width="Auto" />
|
||||
</Grid.ColumnDefinitions>
|
||||
<shared:DeviceVisualizer Device="{Binding Device}" Width="30" Height="30" Grid.Column="0" VerticalAlignment="Center" HorizontalAlignment="Left" />
|
||||
<StackPanel Grid.Column="1" VerticalAlignment="Center">
|
||||
<TextBlock Text="{Binding Device.RgbDevice.DeviceInfo.Model}" />
|
||||
<TextBlock Text="{Binding Device.RgbDevice.DeviceInfo.Manufacturer}" Foreground="{DynamicResource MaterialDesignBodyLight}" />
|
||||
</StackPanel>
|
||||
<Ellipse Grid.Column="2"
|
||||
ToolTip="This is the current random device color"
|
||||
Width="20"
|
||||
Height="20"
|
||||
Stroke="{DynamicResource MaterialDesignDivider}"
|
||||
StrokeThickness="1"
|
||||
Visibility="{Binding Parent.ColorDevices, Converter={x:Static s:BoolToVisibilityConverter.Instance}}">
|
||||
<Ellipse.Fill>
|
||||
<SolidColorBrush Color="{Binding Color, Converter={StaticResource SKColorToColorConverter}}" />
|
||||
</Ellipse.Fill>
|
||||
</Ellipse>
|
||||
</Grid>
|
||||
</UserControl>
|
||||
@@ -1,33 +0,0 @@
|
||||
using Artemis.Core;
|
||||
using SkiaSharp;
|
||||
using Stylet;
|
||||
|
||||
namespace Artemis.UI.Screens.SurfaceEditor.Visualization
|
||||
{
|
||||
public class ListDeviceViewModel : PropertyChangedBase
|
||||
{
|
||||
private SKColor _color;
|
||||
private bool _isSelected;
|
||||
|
||||
public ListDeviceViewModel(ArtemisDevice device, SurfaceEditorViewModel surfaceEditorViewModel)
|
||||
{
|
||||
Device = device;
|
||||
Parent = surfaceEditorViewModel;
|
||||
}
|
||||
|
||||
public ArtemisDevice Device { get; }
|
||||
public SurfaceEditorViewModel Parent { get; }
|
||||
|
||||
public bool IsSelected
|
||||
{
|
||||
get => _isSelected;
|
||||
set => SetAndNotify(ref _isSelected, value);
|
||||
}
|
||||
|
||||
public SKColor Color
|
||||
{
|
||||
get => _color;
|
||||
set => SetAndNotify(ref _color, value);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,106 +0,0 @@
|
||||
<UserControl x:Class="Artemis.UI.Screens.SurfaceEditor.Visualization.SurfaceDeviceView"
|
||||
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:shared="clr-namespace:Artemis.UI.Shared;assembly=Artemis.UI.Shared"
|
||||
xmlns:local="clr-namespace:Artemis.UI.Screens.SurfaceEditor.Visualization"
|
||||
mc:Ignorable="d"
|
||||
d:DataContext="{d:DesignInstance {x:Type local:SurfaceDeviceViewModel}}"
|
||||
d:DesignHeight="450" d:DesignWidth="800"
|
||||
Cursor="{Binding Cursor}"
|
||||
MouseEnter="{s:Action MouseEnter}"
|
||||
MouseLeave="{s:Action MouseLeave}"
|
||||
ToolTip="{Binding Device.RgbDevice.DeviceInfo.DeviceName}">
|
||||
<UserControl.Resources>
|
||||
<shared:NullToVisibilityConverter x:Key="NullToVisibilityConverter" />
|
||||
</UserControl.Resources>
|
||||
<Grid>
|
||||
<!-- Content -->
|
||||
<Grid>
|
||||
<Rectangle Fill="{DynamicResource MaterialDesignCardBackground}"
|
||||
Stroke="{DynamicResource MaterialDesignTextBoxBorder}"
|
||||
StrokeThickness="1"
|
||||
Visibility="{Binding Device.Layout.Image, ConverterParameter=Inverted, Converter={StaticResource NullToVisibilityConverter}}"
|
||||
IsHitTestVisible="False"/>
|
||||
<TextBlock Text="{Binding Device.RgbDevice.DeviceInfo.DeviceName}"
|
||||
HorizontalAlignment="Center"
|
||||
VerticalAlignment="Center"
|
||||
TextWrapping="Wrap"
|
||||
TextAlignment="Center"
|
||||
Visibility="{Binding Device.Layout.Image, ConverterParameter=Inverted, Converter={StaticResource NullToVisibilityConverter}}"
|
||||
IsHitTestVisible="False"/>
|
||||
|
||||
<shared:DeviceVisualizer Device="{Binding Device}"
|
||||
RenderOptions.BitmapScalingMode="HighQuality"
|
||||
ShowColors="True"
|
||||
VerticalAlignment="Top"
|
||||
HorizontalAlignment="Left" />
|
||||
|
||||
</Grid>
|
||||
|
||||
<!-- Selection rectangle -->
|
||||
<Rectangle Width="Auto" Height="Auto" StrokeThickness="2">
|
||||
<Rectangle.Stroke>
|
||||
<SolidColorBrush Color="{StaticResource Accent700}" />
|
||||
</Rectangle.Stroke>
|
||||
<Rectangle.Fill>
|
||||
<SolidColorBrush Color="{StaticResource Accent700}" Opacity="0.25" />
|
||||
</Rectangle.Fill>
|
||||
<Rectangle.Style>
|
||||
<Style TargetType="{x:Type Rectangle}">
|
||||
<Style.Triggers>
|
||||
<DataTrigger Binding="{Binding SelectionStatus}" Value="{x:Static local:SelectionStatus.Hover}">
|
||||
<DataTrigger.EnterActions>
|
||||
<StopStoryboard BeginStoryboardName="ToSelected" />
|
||||
<BeginStoryboard x:Name="ToHover">
|
||||
<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 Accent700}" Duration="0:0:0.25" />
|
||||
</Storyboard>
|
||||
</BeginStoryboard>
|
||||
</DataTrigger.EnterActions>
|
||||
</DataTrigger>
|
||||
<DataTrigger Binding="{Binding SelectionStatus}" Value="{x:Static local:SelectionStatus.Selected}">
|
||||
<DataTrigger.EnterActions>
|
||||
<StopStoryboard BeginStoryboardName="ToHover" />
|
||||
<BeginStoryboard x:Name="ToSelected">
|
||||
<Storyboard>
|
||||
<ColorAnimation
|
||||
Storyboard.TargetProperty="(Shape.Fill).(SolidColorBrush.Color)"
|
||||
To="{StaticResource Primary400}" Duration="0:0:0.25" />
|
||||
<ColorAnimation
|
||||
Storyboard.TargetProperty="(Shape.Stroke).(SolidColorBrush.Color)"
|
||||
To="{StaticResource Primary400}" Duration="0:0:0.25" />
|
||||
</Storyboard>
|
||||
</BeginStoryboard>
|
||||
</DataTrigger.EnterActions>
|
||||
</DataTrigger>
|
||||
<DataTrigger Binding="{Binding SelectionStatus}" Value="{x:Static local:SelectionStatus.None}">
|
||||
<DataTrigger.EnterActions>
|
||||
<BeginStoryboard>
|
||||
<Storyboard>
|
||||
<DoubleAnimation Storyboard.TargetProperty="Opacity" To="0" Duration="0:0:0.25" />
|
||||
</Storyboard>
|
||||
</BeginStoryboard>
|
||||
</DataTrigger.EnterActions>
|
||||
<DataTrigger.ExitActions>
|
||||
<BeginStoryboard>
|
||||
<Storyboard>
|
||||
<DoubleAnimation Storyboard.TargetProperty="Opacity" To="1" Duration="0:0:0.25" />
|
||||
</Storyboard>
|
||||
</BeginStoryboard>
|
||||
</DataTrigger.ExitActions>
|
||||
</DataTrigger>
|
||||
</Style.Triggers>
|
||||
</Style>
|
||||
</Rectangle.Style>
|
||||
</Rectangle>
|
||||
</Grid>
|
||||
</UserControl>
|
||||
@@ -1,138 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Windows.Input;
|
||||
using Artemis.Core;
|
||||
using Artemis.Core.Services;
|
||||
using RGB.NET.Core;
|
||||
using SkiaSharp;
|
||||
using Stylet;
|
||||
using Point = System.Windows.Point;
|
||||
|
||||
namespace Artemis.UI.Screens.SurfaceEditor.Visualization
|
||||
{
|
||||
public class SurfaceDeviceViewModel : PropertyChangedBase
|
||||
{
|
||||
private readonly IRgbService _rgbService;
|
||||
private readonly ISettingsService _settingsService;
|
||||
private Cursor _cursor;
|
||||
private double _dragOffsetX;
|
||||
private double _dragOffsetY;
|
||||
private SelectionStatus _selectionStatus;
|
||||
|
||||
public SurfaceDeviceViewModel(ArtemisDevice device, IRgbService rgbService, ISettingsService settingsService)
|
||||
{
|
||||
Device = device;
|
||||
_rgbService = rgbService;
|
||||
_settingsService = settingsService;
|
||||
}
|
||||
|
||||
public ArtemisDevice Device { get; }
|
||||
|
||||
public SelectionStatus SelectionStatus
|
||||
{
|
||||
get => _selectionStatus;
|
||||
set => SetAndNotify(ref _selectionStatus, value);
|
||||
}
|
||||
|
||||
public bool CanDetectInput => Device.DeviceType == RGBDeviceType.Keyboard ||
|
||||
Device.DeviceType == RGBDeviceType.Mouse;
|
||||
|
||||
public Cursor Cursor
|
||||
{
|
||||
get => _cursor;
|
||||
set => SetAndNotify(ref _cursor, value);
|
||||
}
|
||||
|
||||
public void StartMouseDrag(Point mouseStartPosition)
|
||||
{
|
||||
_dragOffsetX = Device.X - mouseStartPosition.X;
|
||||
_dragOffsetY = Device.Y - mouseStartPosition.Y;
|
||||
}
|
||||
|
||||
public void UpdateMouseDrag(Point mousePosition)
|
||||
{
|
||||
float roundedX = (float) Math.Round((mousePosition.X + _dragOffsetX) / 10d, 0, MidpointRounding.AwayFromZero) * 10f;
|
||||
float roundedY = (float) Math.Round((mousePosition.Y + _dragOffsetY) / 10d, 0, MidpointRounding.AwayFromZero) * 10f;
|
||||
|
||||
if (Fits(roundedX, roundedY))
|
||||
{
|
||||
Device.X = roundedX;
|
||||
Device.Y = roundedY;
|
||||
}
|
||||
else if (Fits(roundedX, (float) Device.Y))
|
||||
{
|
||||
Device.X = roundedX;
|
||||
}
|
||||
else if (Fits((float) Device.X, roundedY))
|
||||
{
|
||||
Device.Y = roundedY;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// ReSharper disable once UnusedMember.Global - Called from view
|
||||
public void MouseEnter(object sender, MouseEventArgs e)
|
||||
{
|
||||
if (SelectionStatus == SelectionStatus.None)
|
||||
{
|
||||
SelectionStatus = SelectionStatus.Hover;
|
||||
Cursor = Cursors.Hand;
|
||||
}
|
||||
}
|
||||
|
||||
// ReSharper disable once UnusedMember.Global - Called from view
|
||||
public void MouseLeave()
|
||||
{
|
||||
if (SelectionStatus == SelectionStatus.Hover)
|
||||
{
|
||||
SelectionStatus = SelectionStatus.None;
|
||||
Cursor = Cursors.Arrow;
|
||||
}
|
||||
}
|
||||
|
||||
public MouseDevicePosition GetMouseDevicePosition(Point position)
|
||||
{
|
||||
if ((new Point(0, 0) - position).LengthSquared < 5) return MouseDevicePosition.TopLeft;
|
||||
|
||||
return MouseDevicePosition.Regular;
|
||||
}
|
||||
|
||||
private bool Fits(float x, float y)
|
||||
{
|
||||
if (x < 0 || y < 0)
|
||||
return false;
|
||||
|
||||
double maxTextureSize = 4096 / _settingsService.GetSetting("Core.RenderScale", 0.25).Value;
|
||||
if (x + Device.Rectangle.Width > maxTextureSize || y + Device.Rectangle.Height > maxTextureSize)
|
||||
return false;
|
||||
|
||||
List<SKRect> own = Device.Leds
|
||||
.Select(l => SKRect.Create(l.Rectangle.Left + x, l.Rectangle.Top + y, l.Rectangle.Width, l.Rectangle.Height))
|
||||
.ToList();
|
||||
List<SKRect> others = _rgbService.EnabledDevices
|
||||
.Where(d => d != Device && d.IsEnabled)
|
||||
.SelectMany(d => d.Leds)
|
||||
.Select(l => SKRect.Create(l.Rectangle.Left + (float) l.Device.X, l.Rectangle.Top + (float) l.Device.Y, l.Rectangle.Width, l.Rectangle.Height))
|
||||
.ToList();
|
||||
|
||||
return !own.Any(o => others.Any(l => l.IntersectsWith(o)));
|
||||
}
|
||||
}
|
||||
|
||||
public enum MouseDevicePosition
|
||||
{
|
||||
Regular,
|
||||
TopLeft,
|
||||
TopRight,
|
||||
BottomLeft,
|
||||
BottomRight
|
||||
}
|
||||
|
||||
public enum SelectionStatus
|
||||
{
|
||||
None,
|
||||
Hover,
|
||||
Selected
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user