mirror of
https://github.com/Artemis-RGB/Artemis
synced 2025-12-13 05:48:35 +00:00
Workshop - Layout submission WIP
This commit is contained in:
parent
190d797f1a
commit
c77d51fb58
@ -15,6 +15,11 @@ public class ArtemisLedLayout
|
|||||||
DeviceLayout = deviceLayout;
|
DeviceLayout = deviceLayout;
|
||||||
RgbLayout = led;
|
RgbLayout = led;
|
||||||
LayoutCustomLedData = (LayoutCustomLedData?) led.CustomData ?? new LayoutCustomLedData();
|
LayoutCustomLedData = (LayoutCustomLedData?) led.CustomData ?? new LayoutCustomLedData();
|
||||||
|
|
||||||
|
// Default to the first logical layout for images
|
||||||
|
LayoutCustomLedDataLogicalLayout? defaultLogicalLayout = LayoutCustomLedData.LogicalLayouts?.FirstOrDefault();
|
||||||
|
if (defaultLogicalLayout != null)
|
||||||
|
ApplyLogicalLayout(defaultLogicalLayout);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@ -54,6 +59,11 @@ public class ArtemisLedLayout
|
|||||||
.ThenBy(l => l.Name == null)
|
.ThenBy(l => l.Name == null)
|
||||||
.First();
|
.First();
|
||||||
|
|
||||||
|
ApplyLogicalLayout(logicalLayout);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void ApplyLogicalLayout(LayoutCustomLedDataLogicalLayout logicalLayout)
|
||||||
|
{
|
||||||
LogicalName = logicalLayout.Name;
|
LogicalName = logicalLayout.Name;
|
||||||
Image = new Uri(Path.Combine(Path.GetDirectoryName(DeviceLayout.FilePath)!, logicalLayout.Image!), UriKind.Absolute);
|
Image = new Uri(Path.Combine(Path.GetDirectoryName(DeviceLayout.FilePath)!, logicalLayout.Image!), UriKind.Absolute);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,12 +1,11 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.ComponentModel;
|
|
||||||
using System.IO;
|
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using Artemis.Core;
|
using Artemis.Core;
|
||||||
using Artemis.Core.Services;
|
using Artemis.Core.Services;
|
||||||
using Artemis.UI.Shared.Events;
|
using Artemis.UI.Shared.Events;
|
||||||
|
using Artemis.UI.Shared.Extensions;
|
||||||
using Avalonia;
|
using Avalonia;
|
||||||
using Avalonia.Controls;
|
using Avalonia.Controls;
|
||||||
using Avalonia.Input;
|
using Avalonia.Input;
|
||||||
@ -69,11 +68,7 @@ public class DeviceVisualizer : Control
|
|||||||
|
|
||||||
// Render device and LED images
|
// Render device and LED images
|
||||||
if (_deviceImage != null)
|
if (_deviceImage != null)
|
||||||
drawingContext.DrawImage(
|
drawingContext.DrawImage(_deviceImage, new Rect(_deviceImage.Size), new Rect(0, 0, Device.RgbDevice.ActualSize.Width, Device.RgbDevice.ActualSize.Height));
|
||||||
_deviceImage,
|
|
||||||
new Rect(_deviceImage.Size),
|
|
||||||
new Rect(0, 0, Device.RgbDevice.ActualSize.Width, Device.RgbDevice.ActualSize.Height)
|
|
||||||
);
|
|
||||||
|
|
||||||
if (!ShowColors)
|
if (!ShowColors)
|
||||||
return;
|
return;
|
||||||
@ -316,34 +311,15 @@ public class DeviceVisualizer : Control
|
|||||||
|
|
||||||
private RenderTargetBitmap? GetDeviceImage(ArtemisDevice device)
|
private RenderTargetBitmap? GetDeviceImage(ArtemisDevice device)
|
||||||
{
|
{
|
||||||
string? path = device.Layout?.Image?.LocalPath;
|
ArtemisLayout? layout = device.Layout;
|
||||||
if (path == null)
|
if (layout == null)
|
||||||
return null;
|
return null;
|
||||||
|
|
||||||
if (BitmapCache.TryGetValue(path, out RenderTargetBitmap? existingBitmap))
|
if (BitmapCache.TryGetValue(layout.FilePath, out RenderTargetBitmap? existingBitmap))
|
||||||
return existingBitmap;
|
return existingBitmap;
|
||||||
if (!File.Exists(path))
|
|
||||||
{
|
|
||||||
BitmapCache[path] = null;
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Create a bitmap that'll be used to render the device and LED images just once
|
RenderTargetBitmap renderTargetBitmap = layout.RenderLayout(false);
|
||||||
// Render 4 times the actual size of the device to make sure things look sharp when zoomed in
|
BitmapCache[layout.FilePath] = renderTargetBitmap;
|
||||||
RenderTargetBitmap renderTargetBitmap = new(new PixelSize((int) device.RgbDevice.ActualSize.Width * 2, (int) device.RgbDevice.ActualSize.Height * 2));
|
|
||||||
|
|
||||||
using DrawingContext context = renderTargetBitmap.CreateDrawingContext();
|
|
||||||
using Bitmap bitmap = new(path);
|
|
||||||
using Bitmap scaledBitmap = bitmap.CreateScaledBitmap(renderTargetBitmap.PixelSize);
|
|
||||||
|
|
||||||
context.DrawImage(scaledBitmap, new Rect(scaledBitmap.Size));
|
|
||||||
lock (_deviceVisualizerLeds)
|
|
||||||
{
|
|
||||||
foreach (DeviceVisualizerLed deviceVisualizerLed in _deviceVisualizerLeds)
|
|
||||||
deviceVisualizerLed.DrawBitmap(context, 2 * device.Scale);
|
|
||||||
}
|
|
||||||
|
|
||||||
// BitmapCache[path] = renderTargetBitmap;
|
|
||||||
return renderTargetBitmap;
|
return renderTargetBitmap;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -1,9 +1,7 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.IO;
|
|
||||||
using Artemis.Core;
|
using Artemis.Core;
|
||||||
using Avalonia;
|
using Avalonia;
|
||||||
using Avalonia.Media;
|
using Avalonia.Media;
|
||||||
using Avalonia.Media.Imaging;
|
|
||||||
using RGB.NET.Core;
|
using RGB.NET.Core;
|
||||||
using Color = Avalonia.Media.Color;
|
using Color = Avalonia.Media.Color;
|
||||||
using Point = Avalonia.Point;
|
using Point = Avalonia.Point;
|
||||||
@ -31,26 +29,6 @@ internal class DeviceVisualizerLed
|
|||||||
public ArtemisLed Led { get; }
|
public ArtemisLed Led { get; }
|
||||||
public Geometry? DisplayGeometry { get; private set; }
|
public Geometry? DisplayGeometry { get; private set; }
|
||||||
|
|
||||||
public void DrawBitmap(DrawingContext drawingContext, double scale)
|
|
||||||
{
|
|
||||||
if (Led.Layout?.Image == null || !File.Exists(Led.Layout.Image.LocalPath))
|
|
||||||
return;
|
|
||||||
|
|
||||||
try
|
|
||||||
{
|
|
||||||
using Bitmap bitmap = new(Led.Layout.Image.LocalPath);
|
|
||||||
using Bitmap scaledBitmap = bitmap.CreateScaledBitmap(new PixelSize((Led.RgbLed.Size.Width * scale).RoundToInt(), (Led.RgbLed.Size.Height * scale).RoundToInt()));
|
|
||||||
drawingContext.DrawImage(
|
|
||||||
scaledBitmap,
|
|
||||||
new Rect(Led.RgbLed.Location.X * scale, Led.RgbLed.Location.Y * scale, scaledBitmap.Size.Width, scaledBitmap.Size.Height)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
catch
|
|
||||||
{
|
|
||||||
// ignored
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public void RenderGeometry(DrawingContext drawingContext)
|
public void RenderGeometry(DrawingContext drawingContext)
|
||||||
{
|
{
|
||||||
if (DisplayGeometry == null)
|
if (DisplayGeometry == null)
|
||||||
|
|||||||
139
src/Artemis.UI.Shared/Extensions/ArtemisLayoutExtensions.cs
Normal file
139
src/Artemis.UI.Shared/Extensions/ArtemisLayoutExtensions.cs
Normal file
@ -0,0 +1,139 @@
|
|||||||
|
using System;
|
||||||
|
using System.IO;
|
||||||
|
using Artemis.Core;
|
||||||
|
using Avalonia;
|
||||||
|
using Avalonia.Media;
|
||||||
|
using Avalonia.Media.Imaging;
|
||||||
|
using RGB.NET.Core;
|
||||||
|
using Color = Avalonia.Media.Color;
|
||||||
|
using SolidColorBrush = Avalonia.Media.SolidColorBrush;
|
||||||
|
|
||||||
|
namespace Artemis.UI.Shared.Extensions;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Provides extension methods for the <see cref="ArtemisLayout" /> type.
|
||||||
|
/// </summary>
|
||||||
|
public static class ArtemisLayoutExtensions
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Renders the layout to a bitmap.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="layout">The layout to render</param>
|
||||||
|
/// <returns>The resulting bitmap.</returns>
|
||||||
|
public static RenderTargetBitmap RenderLayout(this ArtemisLayout layout, bool previewLeds)
|
||||||
|
{
|
||||||
|
string? path = layout.Image?.LocalPath;
|
||||||
|
|
||||||
|
// Create a bitmap that'll be used to render the device and LED images just once
|
||||||
|
// Render 4 times the actual size of the device to make sure things look sharp when zoomed in
|
||||||
|
RenderTargetBitmap renderTargetBitmap = new(new PixelSize((int) layout.RgbLayout.Width * 2, (int) layout.RgbLayout.Height * 2));
|
||||||
|
|
||||||
|
using DrawingContext context = renderTargetBitmap.CreateDrawingContext();
|
||||||
|
|
||||||
|
// Draw device background
|
||||||
|
if (path != null && File.Exists(path))
|
||||||
|
{
|
||||||
|
using Bitmap bitmap = new(path);
|
||||||
|
using Bitmap scaledBitmap = bitmap.CreateScaledBitmap(renderTargetBitmap.PixelSize);
|
||||||
|
context.DrawImage(scaledBitmap, new Rect(scaledBitmap.Size));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Draw LED images
|
||||||
|
foreach (ArtemisLedLayout led in layout.Leds)
|
||||||
|
{
|
||||||
|
string? ledPath = led.Image?.LocalPath;
|
||||||
|
if (ledPath == null || !File.Exists(ledPath))
|
||||||
|
continue;
|
||||||
|
using Bitmap bitmap = new(ledPath);
|
||||||
|
using Bitmap scaledBitmap = bitmap.CreateScaledBitmap(new PixelSize((led.RgbLayout.Width * 2).RoundToInt(), (led.RgbLayout.Height * 2).RoundToInt()));
|
||||||
|
context.DrawImage(scaledBitmap, new Rect(led.RgbLayout.X * 2, led.RgbLayout.Y * 2, scaledBitmap.Size.Width, scaledBitmap.Size.Height));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!previewLeds)
|
||||||
|
return renderTargetBitmap;
|
||||||
|
|
||||||
|
// Draw LED geometry using a rainbow gradient
|
||||||
|
ColorGradient colors = ColorGradient.GetUnicornBarf();
|
||||||
|
colors.ToggleSeamless();
|
||||||
|
context.PushTransform(Matrix.CreateScale(2, 2));
|
||||||
|
foreach (ArtemisLedLayout led in layout.Leds)
|
||||||
|
{
|
||||||
|
Geometry? geometry = CreateLedGeometry(led);
|
||||||
|
if (geometry == null)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
Color color = colors.GetColor((led.RgbLayout.X + led.RgbLayout.Width / 2) / layout.RgbLayout.Width).ToColor();
|
||||||
|
SolidColorBrush fillBrush = new() {Color = color, Opacity = 0.4};
|
||||||
|
SolidColorBrush penBrush = new() {Color = color};
|
||||||
|
Pen pen = new(penBrush) {LineJoin = PenLineJoin.Round};
|
||||||
|
context.DrawGeometry(fillBrush, pen, geometry);
|
||||||
|
}
|
||||||
|
|
||||||
|
return renderTargetBitmap;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static Geometry? CreateLedGeometry(ArtemisLedLayout led)
|
||||||
|
{
|
||||||
|
// The minimum required size for geometry to be created
|
||||||
|
if (led.RgbLayout.Width < 2 || led.RgbLayout.Height < 2)
|
||||||
|
return null;
|
||||||
|
|
||||||
|
switch (led.RgbLayout.Shape)
|
||||||
|
{
|
||||||
|
case Shape.Custom:
|
||||||
|
if (led.DeviceLayout.RgbLayout.Type is RGBDeviceType.Keyboard or RGBDeviceType.Keypad)
|
||||||
|
return CreateCustomGeometry(led, 2.0);
|
||||||
|
return CreateCustomGeometry(led, 1.0);
|
||||||
|
case Shape.Rectangle:
|
||||||
|
if (led.DeviceLayout.RgbLayout.Type is RGBDeviceType.Keyboard or RGBDeviceType.Keypad)
|
||||||
|
return CreateKeyCapGeometry(led);
|
||||||
|
return CreateRectangleGeometry(led);
|
||||||
|
case Shape.Circle:
|
||||||
|
return CreateCircleGeometry(led);
|
||||||
|
default:
|
||||||
|
throw new ArgumentOutOfRangeException();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static RectangleGeometry CreateRectangleGeometry(ArtemisLedLayout led)
|
||||||
|
{
|
||||||
|
return new RectangleGeometry(new Rect(led.RgbLayout.X + 0.5, led.RgbLayout.Y + 0.5, led.RgbLayout.Width - 1, led.RgbLayout.Height - 1));
|
||||||
|
}
|
||||||
|
|
||||||
|
private static EllipseGeometry CreateCircleGeometry(ArtemisLedLayout led)
|
||||||
|
{
|
||||||
|
return new EllipseGeometry(new Rect(led.RgbLayout.X + 0.5, led.RgbLayout.Y + 0.5, led.RgbLayout.Width - 1, led.RgbLayout.Height - 1));
|
||||||
|
}
|
||||||
|
|
||||||
|
private static RectangleGeometry CreateKeyCapGeometry(ArtemisLedLayout led)
|
||||||
|
{
|
||||||
|
return new RectangleGeometry(new Rect(led.RgbLayout.X + 1, led.RgbLayout.Y + 1, led.RgbLayout.Width - 2, led.RgbLayout.Height - 2));
|
||||||
|
}
|
||||||
|
|
||||||
|
private static Geometry? CreateCustomGeometry(ArtemisLedLayout led, double deflateAmount)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
if (led.RgbLayout.ShapeData == null)
|
||||||
|
return null;
|
||||||
|
|
||||||
|
double width = led.RgbLayout.Width - deflateAmount;
|
||||||
|
double height = led.RgbLayout.Height - deflateAmount;
|
||||||
|
|
||||||
|
Geometry geometry = Geometry.Parse(led.RgbLayout.ShapeData);
|
||||||
|
geometry.Transform = new TransformGroup
|
||||||
|
{
|
||||||
|
Children = new Transforms
|
||||||
|
{
|
||||||
|
new ScaleTransform(width, height),
|
||||||
|
new TranslateTransform(led.RgbLayout.X + deflateAmount / 2, led.RgbLayout.Y + deflateAmount / 2)
|
||||||
|
}
|
||||||
|
};
|
||||||
|
return geometry;
|
||||||
|
}
|
||||||
|
catch (Exception)
|
||||||
|
{
|
||||||
|
return CreateRectangleGeometry(led);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,68 @@
|
|||||||
|
<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:core="clr-namespace:Artemis.Core;assembly=Artemis.Core"
|
||||||
|
xmlns:layout="clr-namespace:Artemis.UI.Screens.Workshop.SubmissionWizard.Steps.Layout"
|
||||||
|
xmlns:shared="clr-namespace:Artemis.UI.Shared;assembly=Artemis.UI.Shared"
|
||||||
|
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
|
||||||
|
x:Class="Artemis.UI.Screens.Workshop.SubmissionWizard.Steps.Layout.LayoutSelectionStepView"
|
||||||
|
x:DataType="layout:LayoutSelectionStepViewModel">
|
||||||
|
<UserControl.Resources>
|
||||||
|
<VisualBrush x:Key="LargeCheckerboardBrush" TileMode="Tile" Stretch="Uniform" SourceRect="0,0,20,20">
|
||||||
|
<VisualBrush.Visual>
|
||||||
|
<Canvas Width="20" Height="20">
|
||||||
|
<Rectangle Width="10" Height="10" Fill="Black" Opacity="0.15" />
|
||||||
|
<Rectangle Width="10" Height="10" Canvas.Left="10" />
|
||||||
|
<Rectangle Width="10" Height="10" Canvas.Top="10" />
|
||||||
|
<Rectangle Width="10" Height="10" Canvas.Left="10" Canvas.Top="10" Fill="Black" Opacity="0.15" />
|
||||||
|
</Canvas>
|
||||||
|
</VisualBrush.Visual>
|
||||||
|
</VisualBrush>
|
||||||
|
</UserControl.Resources>
|
||||||
|
<Grid RowDefinitions="Auto,*">
|
||||||
|
<StackPanel>
|
||||||
|
<StackPanel.Styles>
|
||||||
|
<Styles>
|
||||||
|
<Style Selector="TextBlock">
|
||||||
|
<Setter Property="TextWrapping" Value="Wrap"></Setter>
|
||||||
|
</Style>
|
||||||
|
</Styles>
|
||||||
|
</StackPanel.Styles>
|
||||||
|
<TextBlock Theme="{StaticResource TitleTextBlockStyle}" TextWrapping="Wrap">
|
||||||
|
Layout selection
|
||||||
|
</TextBlock>
|
||||||
|
<TextBlock TextWrapping="Wrap">
|
||||||
|
Please select the layout you want to share by either selecting a device or browsing for the layout file
|
||||||
|
</TextBlock>
|
||||||
|
|
||||||
|
<ComboBox ItemsSource="{CompiledBinding Devices}" SelectedItem="{CompiledBinding SelectedDevice}"
|
||||||
|
Width="460"
|
||||||
|
VerticalContentAlignment="Center"
|
||||||
|
Height="50"
|
||||||
|
Margin="0 15"
|
||||||
|
PlaceholderText="Select the layout of a device">
|
||||||
|
<ComboBox.ItemTemplate>
|
||||||
|
<DataTemplate DataType="core:ArtemisDevice">
|
||||||
|
<Grid RowDefinitions="Auto,*" ColumnDefinitions="Auto,*">
|
||||||
|
<TextBlock Grid.Row="0" Grid.Column="1" Text="{CompiledBinding RgbDevice.DeviceInfo.Model}" TextTrimming="CharacterEllipsis"></TextBlock>
|
||||||
|
<TextBlock Grid.Row="1" Grid.Column="1" Text="{CompiledBinding RgbDevice.DeviceInfo.Manufacturer}" TextTrimming="CharacterEllipsis" Classes="subtitle"></TextBlock>
|
||||||
|
</Grid>
|
||||||
|
</DataTemplate>
|
||||||
|
</ComboBox.ItemTemplate>
|
||||||
|
</ComboBox>
|
||||||
|
|
||||||
|
<Button Command="{CompiledBinding BrowseLayout}">Browse layout</Button>
|
||||||
|
|
||||||
|
<TextBlock Text="{CompiledBinding Layout.FilePath, FallbackValue=''}" Margin="0 10 0 5" ></TextBlock>
|
||||||
|
</StackPanel>
|
||||||
|
<Border Grid.Row="1"
|
||||||
|
Classes="card"
|
||||||
|
Padding="0"
|
||||||
|
ClipToBounds="True"
|
||||||
|
IsVisible="{CompiledBinding LayoutImage, Converter={x:Static ObjectConverters.IsNotNull}}"
|
||||||
|
Background="{DynamicResource CheckerboardBrush}">
|
||||||
|
<Image Source="{CompiledBinding LayoutImage}" Margin="25"/>
|
||||||
|
</Border>
|
||||||
|
</Grid>
|
||||||
|
</UserControl>
|
||||||
@ -0,0 +1,14 @@
|
|||||||
|
using Avalonia;
|
||||||
|
using Avalonia.Controls;
|
||||||
|
using Avalonia.Markup.Xaml;
|
||||||
|
using Avalonia.ReactiveUI;
|
||||||
|
|
||||||
|
namespace Artemis.UI.Screens.Workshop.SubmissionWizard.Steps.Layout;
|
||||||
|
|
||||||
|
public partial class LayoutSelectionStepView : ReactiveUserControl<LayoutSelectionStepViewModel>
|
||||||
|
{
|
||||||
|
public LayoutSelectionStepView()
|
||||||
|
{
|
||||||
|
InitializeComponent();
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,116 @@
|
|||||||
|
using System.Collections.ObjectModel;
|
||||||
|
using System.Linq;
|
||||||
|
using Artemis.Core;
|
||||||
|
using Artemis.Core.Services;
|
||||||
|
using PropertyChanged.SourceGenerator;
|
||||||
|
using ReactiveUI;
|
||||||
|
using System;
|
||||||
|
using System.IO;
|
||||||
|
using System.Reactive.Linq;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using Artemis.UI.Extensions;
|
||||||
|
using Artemis.UI.Shared.Extensions;
|
||||||
|
using Artemis.UI.Shared.Services;
|
||||||
|
using Avalonia;
|
||||||
|
using Avalonia.Media.Imaging;
|
||||||
|
using Material.Icons;
|
||||||
|
using RGB.NET.Core;
|
||||||
|
using SkiaSharp;
|
||||||
|
|
||||||
|
namespace Artemis.UI.Screens.Workshop.SubmissionWizard.Steps.Layout;
|
||||||
|
|
||||||
|
public partial class LayoutSelectionStepViewModel : SubmissionViewModel
|
||||||
|
{
|
||||||
|
private readonly IWindowService _windowService;
|
||||||
|
[Notify] private ArtemisDevice? _selectedDevice;
|
||||||
|
[Notify] private ArtemisLayout? _layout;
|
||||||
|
[Notify] private Bitmap? _layoutImage;
|
||||||
|
|
||||||
|
public LayoutSelectionStepViewModel(IDeviceService deviceService, IWindowService windowService)
|
||||||
|
{
|
||||||
|
_windowService = windowService;
|
||||||
|
Devices = new ObservableCollection<ArtemisDevice>(
|
||||||
|
deviceService.Devices
|
||||||
|
.Where(d => d.Layout != null && d.Layout.IsValid)
|
||||||
|
.DistinctBy(d => d.Layout?.FilePath)
|
||||||
|
.OrderBy(d => d.RgbDevice.DeviceInfo.Model)
|
||||||
|
);
|
||||||
|
|
||||||
|
Continue = ReactiveCommand.Create(ExecuteContinue, this.WhenAnyValue(vm => vm.Layout).Select(p => p != null));
|
||||||
|
|
||||||
|
this.WhenAnyValue(vm => vm.SelectedDevice).WhereNotNull().Subscribe(d => Layout = d.Layout);
|
||||||
|
this.WhenAnyValue(vm => vm.Layout).Subscribe(CreatePreviewDevice);
|
||||||
|
}
|
||||||
|
|
||||||
|
public ObservableCollection<ArtemisDevice> Devices { get; }
|
||||||
|
|
||||||
|
public async Task BrowseLayout()
|
||||||
|
{
|
||||||
|
string[]? selected = await _windowService.CreateOpenFileDialog().HavingFilter(f => f.WithExtension("xml").WithName("Artemis Layout")).ShowAsync();
|
||||||
|
if (selected == null || selected.Length != 1)
|
||||||
|
return;
|
||||||
|
|
||||||
|
ArtemisLayout layout = new(selected[0], LayoutSource.User);
|
||||||
|
if (!layout.IsValid)
|
||||||
|
{
|
||||||
|
await _windowService.ShowConfirmContentDialog("Invalid layout file", "The selected file does not appear to be a valid RGB.NET layout file", cancel: null);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
SelectedDevice = null;
|
||||||
|
Layout = layout;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void CreatePreviewDevice(ArtemisLayout? layout)
|
||||||
|
{
|
||||||
|
if (layout == null)
|
||||||
|
{
|
||||||
|
LayoutImage = null;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
LayoutImage = layout.RenderLayout(true);
|
||||||
|
Layout = layout;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void ExecuteContinue()
|
||||||
|
{
|
||||||
|
if (Layout == null)
|
||||||
|
return;
|
||||||
|
|
||||||
|
State.EntrySource = Layout;
|
||||||
|
State.Name = Layout.RgbLayout.Name ?? "";
|
||||||
|
State.Summary = !string.IsNullOrWhiteSpace(Layout.RgbLayout.Vendor)
|
||||||
|
? $"{Layout.RgbLayout.Vendor} {Layout.RgbLayout.Type} device layout"
|
||||||
|
: $"{Layout.RgbLayout.Type} device layout";
|
||||||
|
|
||||||
|
// Go through the hassle of resizing the image to 128x128 without losing aspect ratio, padding is added for this
|
||||||
|
using RenderTargetBitmap image = Layout.RenderLayout(false);
|
||||||
|
using MemoryStream stream = new();
|
||||||
|
image.Save(stream);
|
||||||
|
stream.Seek(0, SeekOrigin.Begin);
|
||||||
|
|
||||||
|
MemoryStream output = new();
|
||||||
|
using SKBitmap? sourceBitmap = SKBitmap.Decode(stream);
|
||||||
|
int sourceWidth = sourceBitmap.Width;
|
||||||
|
int sourceHeight = sourceBitmap.Height;
|
||||||
|
float scale = Math.Min((float) 128 / sourceWidth, (float) 128 / sourceHeight);
|
||||||
|
|
||||||
|
SKSizeI scaledDimensions = new((int) Math.Floor(sourceWidth * scale), (int) Math.Floor(sourceHeight * scale));
|
||||||
|
SKPointI offset = new((128 - scaledDimensions.Width) / 2, (128 - scaledDimensions.Height) / 2);
|
||||||
|
|
||||||
|
using (SKBitmap? scaleBitmap = sourceBitmap.Resize(scaledDimensions, SKFilterQuality.High))
|
||||||
|
using (SKBitmap targetBitmap = new(128, 128))
|
||||||
|
using (SKCanvas canvas = new(targetBitmap))
|
||||||
|
{
|
||||||
|
canvas.Clear(SKColors.Transparent);
|
||||||
|
canvas.DrawBitmap(scaleBitmap, offset.X, offset.Y);
|
||||||
|
targetBitmap.Encode(output, SKEncodedImageFormat.Png, 100);
|
||||||
|
output.Seek(0, SeekOrigin.Begin);
|
||||||
|
}
|
||||||
|
|
||||||
|
State.Icon?.Dispose();
|
||||||
|
State.Icon = output;
|
||||||
|
State.ChangeScreen<SpecificationsStepViewModel>();
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -5,6 +5,7 @@ using System.Linq;
|
|||||||
using System.Reactive.Disposables;
|
using System.Reactive.Disposables;
|
||||||
using Artemis.UI.Extensions;
|
using Artemis.UI.Extensions;
|
||||||
using Artemis.UI.Screens.Workshop.Entries;
|
using Artemis.UI.Screens.Workshop.Entries;
|
||||||
|
using Artemis.UI.Screens.Workshop.SubmissionWizard.Steps.Layout;
|
||||||
using Artemis.UI.Screens.Workshop.SubmissionWizard.Steps.Profile;
|
using Artemis.UI.Screens.Workshop.SubmissionWizard.Steps.Profile;
|
||||||
using Artemis.WebClient.Workshop;
|
using Artemis.WebClient.Workshop;
|
||||||
using DynamicData;
|
using DynamicData;
|
||||||
@ -38,6 +39,7 @@ public partial class SpecificationsStepViewModel : SubmissionViewModel
|
|||||||
switch (State.EntryType)
|
switch (State.EntryType)
|
||||||
{
|
{
|
||||||
case EntryType.Layout:
|
case EntryType.Layout:
|
||||||
|
State.ChangeScreen<LayoutSelectionStepViewModel>();
|
||||||
break;
|
break;
|
||||||
case EntryType.Plugin:
|
case EntryType.Plugin:
|
||||||
break;
|
break;
|
||||||
|
|||||||
@ -1,6 +1,7 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
|
using Artemis.UI.Screens.Workshop.SubmissionWizard.Steps.Layout;
|
||||||
using Artemis.UI.Screens.Workshop.SubmissionWizard.Steps.Profile;
|
using Artemis.UI.Screens.Workshop.SubmissionWizard.Steps.Profile;
|
||||||
using Artemis.UI.Shared.Services;
|
using Artemis.UI.Shared.Services;
|
||||||
using Artemis.WebClient.Workshop;
|
using Artemis.WebClient.Workshop;
|
||||||
@ -56,6 +57,8 @@ public class SubmissionWizardState
|
|||||||
{
|
{
|
||||||
if (EntryType == EntryType.Profile)
|
if (EntryType == EntryType.Profile)
|
||||||
ChangeScreen<ProfileSelectionStepViewModel>();
|
ChangeScreen<ProfileSelectionStepViewModel>();
|
||||||
|
else if (EntryType == EntryType.Layout)
|
||||||
|
ChangeScreen<LayoutSelectionStepViewModel>();
|
||||||
else
|
else
|
||||||
throw new NotImplementedException();
|
throw new NotImplementedException();
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user