mirror of
https://github.com/Artemis-RGB/Artemis
synced 2026-11-12 11:56:30 +00:00
Restructured the VM/Vs folder layout while it's still feasible
This commit is contained in:
1 parent
7443c0eda7
commit
368b73ce9c
48 files changed
+397
-469
No files matched your search
@@ -0,0 +1,200 @@
|
||||
<UserControl x:Class="Artemis.UI.Screens.Module.ProfileEditor.ProfileEditorView"
|
||||
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:materialDesign="http://materialdesigninxaml.net/winfx/xaml/themes"
|
||||
xmlns:s="https://github.com/canton7/Stylet"
|
||||
xmlns:profileEditor="clr-namespace:Artemis.UI.Screens.Module.ProfileEditor"
|
||||
mc:Ignorable="d"
|
||||
d:DesignHeight="450" d:DesignWidth="800"
|
||||
d:DataContext="{d:DesignInstance {x:Type profileEditor:ProfileEditorViewModel}}">
|
||||
<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="16">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="*" />
|
||||
<ColumnDefinition Width="Auto" />
|
||||
<ColumnDefinition Width="300" />
|
||||
</Grid.ColumnDefinitions>
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition Height="*" />
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition Height="200" />
|
||||
</Grid.RowDefinitions>
|
||||
|
||||
<StackPanel Grid.Row="0" Grid.Column="0" Grid.ColumnSpan="3">
|
||||
<TextBlock>
|
||||
<materialDesign:PackIcon Kind="AboutOutline" Margin="0 0 0 -3" /> The profile defines what colors the LEDs will have. Multiple groups of LEDs are defined into layers. On these layers you can apply effects.
|
||||
</TextBlock>
|
||||
<Border BorderBrush="{DynamicResource MaterialDesignDivider}" BorderThickness="0 0 0 1" Margin="0 10" />
|
||||
</StackPanel>
|
||||
|
||||
<!-- Design area -->
|
||||
<materialDesign:Card materialDesign:ShadowAssist.ShadowDepth="Depth1" Grid.Row="1" Grid.Column="0" VerticalAlignment="Stretch">
|
||||
<Grid ClipToBounds="True"
|
||||
KeyUp="{s:Action EditorGridKeyUp}"
|
||||
KeyDown="{s:Action EditorGridKeyDown}"
|
||||
MouseWheel="{s:Action EditorGridMouseWheel}"
|
||||
MouseUp="{s:Action EditorGridMouseClick}"
|
||||
MouseDown="{s:Action EditorGridMouseClick}"
|
||||
MouseMove="{s:Action EditorGridMouseMove}">
|
||||
<Grid.Background>
|
||||
<VisualBrush TileMode="Tile" Stretch="Uniform"
|
||||
Viewport="{Binding PanZoomViewModel.BackgroundViewport}" ViewportUnits="Absolute">
|
||||
<VisualBrush.Visual>
|
||||
<Grid Width="20" Height="20">
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition />
|
||||
<RowDefinition />
|
||||
</Grid.RowDefinitions>
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition />
|
||||
<ColumnDefinition />
|
||||
</Grid.ColumnDefinitions>
|
||||
<Rectangle Grid.Row="0" Grid.Column="0" Fill="LightGray" />
|
||||
<Rectangle Grid.Row="0" Grid.Column="1" />
|
||||
<Rectangle Grid.Row="1" Grid.Column="0" />
|
||||
<Rectangle Grid.Row="1" Grid.Column="1" Fill="LightGray" />
|
||||
</Grid>
|
||||
</VisualBrush.Visual>
|
||||
</VisualBrush>
|
||||
</Grid.Background>
|
||||
|
||||
<Grid.Triggers>
|
||||
<EventTrigger RoutedEvent="Grid.MouseLeftButtonDown">
|
||||
<BeginStoryboard>
|
||||
<Storyboard TargetName="MultiSelectionPath" TargetProperty="Opacity">
|
||||
<DoubleAnimation From="0" To="1" Duration="0:0:0.1" />
|
||||
</Storyboard>
|
||||
</BeginStoryboard>
|
||||
</EventTrigger>
|
||||
<EventTrigger RoutedEvent="Grid.MouseLeftButtonUp">
|
||||
<BeginStoryboard>
|
||||
<Storyboard TargetName="MultiSelectionPath" TargetProperty="Opacity">
|
||||
<DoubleAnimation From="1" To="0" Duration="0:0:0.2" />
|
||||
</Storyboard>
|
||||
</BeginStoryboard>
|
||||
</EventTrigger>
|
||||
</Grid.Triggers>
|
||||
|
||||
<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 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>
|
||||
<ContentControl Width="{Binding Device.RgbDevice.Size.Width}"
|
||||
Height="{Binding Device.RgbDevice.Size.Height}"
|
||||
s:View.Model="{Binding}" />
|
||||
</DataTemplate>
|
||||
</ItemsControl.ItemTemplate>
|
||||
</ItemsControl>
|
||||
</Grid>
|
||||
|
||||
<!-- Multi-selection rectangle -->
|
||||
<Path Data="{Binding SelectionRectangle}" Opacity="0"
|
||||
Stroke="{DynamicResource PrimaryHueLightBrush}"
|
||||
StrokeThickness="1"
|
||||
Name="MultiSelectionPath"
|
||||
IsHitTestVisible="False">
|
||||
<Path.Fill>
|
||||
<SolidColorBrush Color="{DynamicResource Primary400}" Opacity="0.25" />
|
||||
</Path.Fill>
|
||||
</Path>
|
||||
|
||||
<StackPanel Orientation="Vertical" VerticalAlignment="Bottom" HorizontalAlignment="Right"
|
||||
Margin="0, 0, 15, 15">
|
||||
<Slider Margin="0,0,14,0"
|
||||
Orientation="Vertical"
|
||||
Minimum="10"
|
||||
Maximum="400"
|
||||
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>
|
||||
|
||||
<Grid Background="{StaticResource MaterialDesignPaper}" Visibility="{Binding IsInitializing, Converter={StaticResource BoolToVisibilityConverter}, Mode=OneWay}">
|
||||
<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>
|
||||
</materialDesign:Card>
|
||||
|
||||
<GridSplitter Grid.Row="1" Grid.Column="1" Width="5" HorizontalAlignment="Stretch" ShowsPreview="True" Cursor="SizeWE" Margin="5 0" />
|
||||
|
||||
<!-- Right panels -->
|
||||
<Grid Grid.Row="1" Grid.Column="2">
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="*" />
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition Height="*" />
|
||||
</Grid.RowDefinitions>
|
||||
|
||||
<materialDesign:Card Grid.Row="0" materialDesign:ShadowAssist.ShadowDepth="Depth1" VerticalAlignment="Stretch">
|
||||
<TextBlock>Right top</TextBlock>
|
||||
</materialDesign:Card>
|
||||
|
||||
<GridSplitter Grid.Row="1" Height="5" HorizontalAlignment="Stretch" ShowsPreview="True" Cursor="SizeNS" Margin="0 5" />
|
||||
|
||||
<materialDesign:Card Grid.Row="2" materialDesign:ShadowAssist.ShadowDepth="Depth1" VerticalAlignment="Stretch">
|
||||
<TextBlock>Right bottom</TextBlock>
|
||||
</materialDesign:Card>
|
||||
</Grid>
|
||||
|
||||
<GridSplitter Grid.Row="2" Grid.Column="0" Grid.ColumnSpan="3" Height="5" HorizontalAlignment="Stretch" ShowsPreview="True" Cursor="SizeNS" Margin="0 5" />
|
||||
|
||||
<!-- Bottom panels -->
|
||||
<Grid Grid.Row="3" Grid.Column="0" Grid.ColumnSpan="3">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="*" />
|
||||
<ColumnDefinition Width="Auto" />
|
||||
<ColumnDefinition Width="*" />
|
||||
</Grid.ColumnDefinitions>
|
||||
<materialDesign:Card Grid.Column="0" materialDesign:ShadowAssist.ShadowDepth="Depth1" VerticalAlignment="Stretch">
|
||||
<TextBlock>Bottom left</TextBlock>
|
||||
</materialDesign:Card>
|
||||
|
||||
<GridSplitter Grid.Column="1" Width="5" Margin="5 0" HorizontalAlignment="Stretch" ShowsPreview="True" Cursor="SizeWE" />
|
||||
|
||||
<materialDesign:Card Grid.Column="2" materialDesign:ShadowAssist.ShadowDepth="Depth1" VerticalAlignment="Stretch">
|
||||
<TextBlock>Bottom right</TextBlock>
|
||||
</materialDesign:Card>
|
||||
</Grid>
|
||||
</Grid>
|
||||
</UserControl>
|
||||
@@ -0,0 +1,214 @@
|
||||
using System;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Linq;
|
||||
using System.Windows;
|
||||
using System.Windows.Input;
|
||||
using System.Windows.Media;
|
||||
using Artemis.Core.Events;
|
||||
using Artemis.Core.Models.Surface;
|
||||
using Artemis.Core.Plugins.Abstract;
|
||||
using Artemis.Core.Services;
|
||||
using Artemis.Core.Services.Storage;
|
||||
using Artemis.UI.Screens.Module.ProfileEditor.Visualization;
|
||||
using Artemis.UI.Screens.Shared;
|
||||
using Artemis.UI.Screens.SurfaceEditor;
|
||||
using RGB.NET.Core;
|
||||
using Stylet;
|
||||
using Point = System.Windows.Point;
|
||||
|
||||
namespace Artemis.UI.Screens.Module.ProfileEditor
|
||||
{
|
||||
public class ProfileEditorViewModel : ModuleViewModel
|
||||
{
|
||||
private readonly TimerUpdateTrigger _updateTrigger;
|
||||
|
||||
public ProfileEditorViewModel(Core.Plugins.Abstract.Module module, ISurfaceService surfaceService, ISettingsService settingsService) : base(module, "Profile Editor")
|
||||
{
|
||||
surfaceService.ActiveSurfaceConfigurationChanged += OnActiveSurfaceConfigurationChanged;
|
||||
Devices = new ObservableCollection<ProfileDeviceViewModel>();
|
||||
Execute.OnUIThread(() =>
|
||||
{
|
||||
SelectionRectangle = new RectangleGeometry();
|
||||
PanZoomViewModel = new PanZoomViewModel();
|
||||
});
|
||||
|
||||
ApplySurfaceConfiguration(surfaceService.ActiveSurface);
|
||||
|
||||
// Borrow RGB.NET's update trigger but limit the FPS
|
||||
var targetFpsSetting = settingsService.GetSetting("TargetFrameRate", 25);
|
||||
var editorTargetFpsSetting = settingsService.GetSetting("EditorTargetFrameRate", 15);
|
||||
var targetFps = Math.Min(targetFpsSetting.Value, editorTargetFpsSetting.Value);
|
||||
_updateTrigger = new TimerUpdateTrigger {UpdateFrequency = 1.0 / targetFps};
|
||||
_updateTrigger.Update += UpdateLeds;
|
||||
}
|
||||
|
||||
public ObservableCollection<ProfileDeviceViewModel> Devices { get; set; }
|
||||
public bool IsInitializing { get; private set; }
|
||||
public RectangleGeometry SelectionRectangle { get; set; }
|
||||
public PanZoomViewModel PanZoomViewModel { get; set; }
|
||||
|
||||
private void OnActiveSurfaceConfigurationChanged(object sender, SurfaceConfigurationEventArgs e)
|
||||
{
|
||||
ApplySurfaceConfiguration(e.Surface);
|
||||
}
|
||||
|
||||
private void UpdateLeds(object sender, CustomUpdateData customUpdateData)
|
||||
{
|
||||
if (IsInitializing)
|
||||
IsInitializing = Devices.Any(d => !d.AddedLeds);
|
||||
|
||||
foreach (var profileDeviceViewModel in Devices)
|
||||
profileDeviceViewModel.Update();
|
||||
}
|
||||
|
||||
private void ApplySurfaceConfiguration(Surface surface)
|
||||
{
|
||||
// Make sure all devices have an up-to-date VM
|
||||
foreach (var surfaceDeviceConfiguration in surface.Devices)
|
||||
{
|
||||
// Create VMs for missing devices
|
||||
var viewModel = Devices.FirstOrDefault(vm => vm.Device.RgbDevice == surfaceDeviceConfiguration.RgbDevice);
|
||||
if (viewModel == null)
|
||||
{
|
||||
// Create outside the UI thread to avoid slowdowns as much as possible
|
||||
var profileDeviceViewModel = new ProfileDeviceViewModel(surfaceDeviceConfiguration);
|
||||
Execute.OnUIThread(() =>
|
||||
{
|
||||
// Gotta call IsInitializing on the UI thread or its never gets picked up
|
||||
IsInitializing = true;
|
||||
Devices.Add(profileDeviceViewModel);
|
||||
});
|
||||
}
|
||||
// Update existing devices
|
||||
else
|
||||
viewModel.Device = surfaceDeviceConfiguration;
|
||||
}
|
||||
|
||||
// Sort the devices by ZIndex
|
||||
Execute.OnUIThread(() =>
|
||||
{
|
||||
foreach (var device in Devices.OrderBy(d => d.ZIndex).ToList())
|
||||
Devices.Move(Devices.IndexOf(device), device.ZIndex - 1);
|
||||
});
|
||||
}
|
||||
|
||||
protected override void OnActivate()
|
||||
{
|
||||
_updateTrigger.Start();
|
||||
base.OnActivate();
|
||||
}
|
||||
|
||||
protected override void OnDeactivate()
|
||||
{
|
||||
_updateTrigger.Stop();
|
||||
base.OnDeactivate();
|
||||
}
|
||||
|
||||
#region Selection
|
||||
|
||||
private MouseDragStatus _mouseDragStatus;
|
||||
private Point _mouseDragStartPoint;
|
||||
|
||||
// ReSharper disable once UnusedMember.Global - Called from view
|
||||
public void EditorGridMouseClick(object sender, MouseEventArgs e)
|
||||
{
|
||||
if (IsPanKeyDown())
|
||||
return;
|
||||
|
||||
var position = e.GetPosition((IInputElement) sender);
|
||||
var relative = PanZoomViewModel.GetRelativeMousePosition(sender, e);
|
||||
if (e.LeftButton == MouseButtonState.Pressed)
|
||||
StartMouseDrag(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;
|
||||
}
|
||||
|
||||
var position = e.GetPosition((IInputElement) sender);
|
||||
if (_mouseDragStatus == MouseDragStatus.Selecting)
|
||||
UpdateSelection(position);
|
||||
}
|
||||
|
||||
private void StartMouseDrag(Point position, Point relative)
|
||||
{
|
||||
_mouseDragStatus = MouseDragStatus.Selecting;
|
||||
_mouseDragStartPoint = position;
|
||||
|
||||
// Any time dragging starts, start with a new rect
|
||||
SelectionRectangle.Rect = new Rect();
|
||||
}
|
||||
|
||||
private void StopMouseDrag(Point position)
|
||||
{
|
||||
var selectedRect = new Rect(_mouseDragStartPoint, position);
|
||||
// TODO: Select LEDs
|
||||
|
||||
Mouse.OverrideCursor = null;
|
||||
_mouseDragStatus = MouseDragStatus.None;
|
||||
}
|
||||
|
||||
private void UpdateSelection(Point position)
|
||||
{
|
||||
if (IsPanKeyDown())
|
||||
return;
|
||||
|
||||
lock (Devices)
|
||||
{
|
||||
var selectedRect = new Rect(_mouseDragStartPoint, position);
|
||||
SelectionRectangle.Rect = selectedRect;
|
||||
|
||||
// TODO: Highlight LEDs
|
||||
}
|
||||
}
|
||||
|
||||
#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)
|
||||
Mouse.OverrideCursor = Cursors.ScrollAll;
|
||||
}
|
||||
|
||||
public void EditorGridKeyUp(object sender, KeyEventArgs e)
|
||||
{
|
||||
if ((e.Key == Key.LeftCtrl || e.Key == Key.RightCtrl) && e.IsUp)
|
||||
Mouse.OverrideCursor = 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
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,51 @@
|
||||
<UserControl x:Class="Artemis.UI.Screens.Module.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.Module.ProfileEditor.Visualization"
|
||||
mc:Ignorable="d"
|
||||
d:DataContext="{d:DesignInstance {x:Type visualization:ProfileDeviceViewModel}}"
|
||||
d:DesignHeight="450" d:DesignWidth="800">
|
||||
<UserControl.Resources>
|
||||
<converters:NullToImageConverter x:Key="NullToImageConverter" />
|
||||
<converters:NullToVisibilityConverter x:Key="NullToVisibilityConverter" />
|
||||
</UserControl.Resources>
|
||||
<Grid>
|
||||
<!-- Device image with fallback -->
|
||||
<Image Source="{Binding Device.RgbDevice.DeviceInfo.Image, Converter={StaticResource NullToImageConverter}, Mode=OneWay}" />
|
||||
|
||||
<Rectangle Fill="{DynamicResource ControlBackgroundBrush}"
|
||||
Stroke="{DynamicResource ControlBorderBrush}"
|
||||
StrokeThickness="1"
|
||||
Visibility="{Binding Device.RgbDevice.DeviceInfo.Image, ConverterParameter=Inverted, Converter={StaticResource NullToVisibilityConverter}, Mode=OneWay}" />
|
||||
<TextBlock Text="{Binding Device.RgbDevice.DeviceInfo.DeviceName}"
|
||||
Visibility="{Binding Device.RgbDevice.DeviceInfo.Image, ConverterParameter=Inverted, Converter={StaticResource NullToVisibilityConverter}, Mode=OneWay}"
|
||||
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, Mode=OneWay}" />
|
||||
<Setter Property="Canvas.Top" Value="{Binding Y, Mode=OneWay}" />
|
||||
</Style>
|
||||
</ItemsControl.ItemContainerStyle>
|
||||
<ItemsControl.ItemTemplate>
|
||||
<DataTemplate>
|
||||
<ContentControl Width="{Binding Width, Mode=OneWay}" Height="{Binding Height, Mode=OneWay}" s:View.Model="{Binding}" />
|
||||
</DataTemplate>
|
||||
</ItemsControl.ItemTemplate>
|
||||
</ItemsControl>
|
||||
</Grid>
|
||||
</UserControl>
|
||||
@@ -0,0 +1,79 @@
|
||||
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.Module.ProfileEditor.Visualization
|
||||
{
|
||||
public class ProfileDeviceViewModel : PropertyChangedBase
|
||||
{
|
||||
public ProfileDeviceViewModel(Device device)
|
||||
{
|
||||
Device = device;
|
||||
Leds = new ObservableCollection<ProfileLedViewModel>();
|
||||
|
||||
if (Device.RgbDevice != null)
|
||||
Task.Run(AddLedsAsync);
|
||||
}
|
||||
|
||||
public ObservableCollection<ProfileLedViewModel> Leds { get; set; }
|
||||
public Device Device { get; set; }
|
||||
public bool AddedLeds { get; private set; }
|
||||
|
||||
public double X
|
||||
{
|
||||
get => Device.X;
|
||||
set => Device.X = value;
|
||||
}
|
||||
|
||||
public 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>
|
||||
/// 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.RgbDevice.ToList())
|
||||
{
|
||||
Execute.OnUIThreadSync(() => Leds.Add(new ProfileLedViewModel(led)));
|
||||
if (index % 5 == 0)
|
||||
await Task.Delay(1);
|
||||
|
||||
index++;
|
||||
}
|
||||
|
||||
AddedLeds = true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Update the color of all LEDs if finished adding
|
||||
/// </summary>
|
||||
public void Update()
|
||||
{
|
||||
if (!AddedLeds)
|
||||
return;
|
||||
|
||||
foreach (var ledViewModel in Leds)
|
||||
ledViewModel.Update();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
<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:converters="clr-namespace:Artemis.UI.Converters"
|
||||
xmlns:visualization="clr-namespace:Artemis.UI.Screens.Module.ProfileEditor.Visualization"
|
||||
x:Class="Artemis.UI.Screens.Module.ProfileEditor.Visualization.ProfileLedView"
|
||||
mc:Ignorable="d"
|
||||
d:DataContext="{d:DesignInstance {x:Type visualization:ProfileLedViewModel}}"
|
||||
d:DesignHeight="25" d:DesignWidth="25">
|
||||
<UserControl.Resources>
|
||||
<converters:NullToImageConverter x:Key="NullToImageConverter" />
|
||||
</UserControl.Resources>
|
||||
<Canvas Width="{Binding Width, Mode=OneWay}" Height="{Binding Height, Mode=OneWay}">
|
||||
<Canvas.Background>
|
||||
<ImageBrush AlignmentX="Center" AlignmentY="Center" Stretch="Fill"
|
||||
ImageSource="{Binding Led.Image, Converter={StaticResource NullToImageConverter}, Mode=OneWay}" />
|
||||
</Canvas.Background>
|
||||
|
||||
<Path Data="{Binding DisplayGeometry, Mode=OneWay}" ClipToBounds="False">
|
||||
<Path.Fill>
|
||||
<SolidColorBrush Color="{Binding DisplayColor, Mode=OneWay}" Opacity="0.333" />
|
||||
</Path.Fill>
|
||||
</Path>
|
||||
<Path Data="{Binding StrokeGeometry, Mode=OneWay}" ClipToBounds="False">
|
||||
<Path.Fill>
|
||||
<SolidColorBrush Color="{Binding DisplayColor, Mode=OneWay}" />
|
||||
</Path.Fill>
|
||||
</Path>
|
||||
</Canvas>
|
||||
</UserControl>
|
||||
@@ -0,0 +1,113 @@
|
||||
using System;
|
||||
using System.Windows;
|
||||
using System.Windows.Media;
|
||||
using Artemis.UI.Extensions;
|
||||
using RGB.NET.Core;
|
||||
using Stylet;
|
||||
using Color = System.Windows.Media.Color;
|
||||
|
||||
namespace Artemis.UI.Screens.Module.ProfileEditor.Visualization
|
||||
{
|
||||
public class ProfileLedViewModel : PropertyChangedBase
|
||||
{
|
||||
public ProfileLedViewModel(Led led)
|
||||
{
|
||||
Led = led;
|
||||
X = Led.LedRectangle.X;
|
||||
Y = Led.LedRectangle.Y;
|
||||
Width = Led.LedRectangle.Width;
|
||||
Height = Led.LedRectangle.Height;
|
||||
|
||||
Execute.OnUIThread(CreateLedGeometry);
|
||||
}
|
||||
|
||||
public Led Led { get; }
|
||||
|
||||
public double X { get; }
|
||||
public double Y { get; }
|
||||
public double Width { get; }
|
||||
public double Height { get; }
|
||||
|
||||
public Geometry DisplayGeometry { get; private set; }
|
||||
public Geometry StrokeGeometry { get; private set; }
|
||||
public Color DisplayColor { get; private set; }
|
||||
|
||||
private void CreateLedGeometry()
|
||||
{
|
||||
switch (Led.Shape)
|
||||
{
|
||||
case Shape.Custom:
|
||||
if (Led.Device.DeviceInfo.DeviceType == RGBDeviceType.Keyboard || Led.Device.DeviceInfo.DeviceType == RGBDeviceType.Keypad)
|
||||
CreateCustomGeometry(2.0);
|
||||
else
|
||||
CreateCustomGeometry(1.0);
|
||||
break;
|
||||
case Shape.Rectangle:
|
||||
if (Led.Device.DeviceInfo.DeviceType == RGBDeviceType.Keyboard || Led.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, Led.LedRectangle.Width - 1, Led.LedRectangle.Height - 1));
|
||||
}
|
||||
|
||||
private void CreateCircleGeometry()
|
||||
{
|
||||
DisplayGeometry = new EllipseGeometry(new Rect(0.5, 0.5, Led.LedRectangle.Width - 1, Led.LedRectangle.Height - 1));
|
||||
}
|
||||
|
||||
private void CreateKeyCapGeometry()
|
||||
{
|
||||
DisplayGeometry = new RectangleGeometry(new Rect(1, 1, Led.LedRectangle.Width - 2, Led.LedRectangle.Height - 2), 1.6, 1.6);
|
||||
}
|
||||
|
||||
private void CreateCustomGeometry(double deflateAmount)
|
||||
{
|
||||
try
|
||||
{
|
||||
DisplayGeometry = Geometry.Combine(
|
||||
Geometry.Empty,
|
||||
Geometry.Parse(Led.ShapeData),
|
||||
GeometryCombineMode.Union,
|
||||
new TransformGroup
|
||||
{
|
||||
Children = new TransformCollection
|
||||
{
|
||||
new ScaleTransform(Led.LedRectangle.Width - deflateAmount, Led.LedRectangle.Height - deflateAmount),
|
||||
new TranslateTransform(deflateAmount / 2, deflateAmount / 2)
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
CreateRectangleGeometry();
|
||||
}
|
||||
}
|
||||
|
||||
public void Update()
|
||||
{
|
||||
var newColor = Led.Color.ToMediaColor();
|
||||
Execute.OnUIThread(() =>
|
||||
{
|
||||
if (!DisplayColor.Equals(newColor))
|
||||
DisplayColor = newColor;
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user