mirror of
https://github.com/Artemis-RGB/Artemis
synced 2025-12-13 05:48:35 +00:00
Workshop - Layout info and images WIP
This commit is contained in:
parent
f4b9b67f1a
commit
c1e0dadce8
@ -149,6 +149,16 @@ public class ContentDialogBuilder
|
|||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Changes the dialog to be full screen.
|
||||||
|
/// </summary>
|
||||||
|
/// <returns>The builder that can be used to further build the dialog.</returns>
|
||||||
|
public ContentDialogBuilder WithFullScreen()
|
||||||
|
{
|
||||||
|
_contentDialog.Classes.Add("fullscreen");
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Asynchronously shows the content dialog.
|
/// Asynchronously shows the content dialog.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|||||||
@ -2,6 +2,7 @@
|
|||||||
using System.Linq;
|
using System.Linq;
|
||||||
using Avalonia.Controls;
|
using Avalonia.Controls;
|
||||||
using Avalonia.Platform.Storage;
|
using Avalonia.Platform.Storage;
|
||||||
|
using SkiaSharp;
|
||||||
|
|
||||||
namespace Artemis.UI.Shared.Services.Builders;
|
namespace Artemis.UI.Shared.Services.Builders;
|
||||||
|
|
||||||
@ -37,6 +38,29 @@ public class FileDialogFilterBuilder
|
|||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Adds all supported bitmap types to the filter.
|
||||||
|
/// </summary>
|
||||||
|
public FileDialogFilterBuilder WithBitmaps()
|
||||||
|
{
|
||||||
|
// Formats from SKEncodedImageFormat
|
||||||
|
return WithExtension("astc")
|
||||||
|
.WithExtension("avif")
|
||||||
|
.WithExtension("bmp")
|
||||||
|
.WithExtension("dng")
|
||||||
|
.WithExtension("gif")
|
||||||
|
.WithExtension("heif")
|
||||||
|
.WithExtension("ico")
|
||||||
|
.WithExtension("jpg")
|
||||||
|
.WithExtension("jpeg")
|
||||||
|
.WithExtension("ktx")
|
||||||
|
.WithExtension("pkm")
|
||||||
|
.WithExtension("png")
|
||||||
|
.WithExtension("wbmp")
|
||||||
|
.WithExtension("webp")
|
||||||
|
.WithName("Bitmap image");
|
||||||
|
}
|
||||||
|
|
||||||
internal FilePickerFileType Build()
|
internal FilePickerFileType Build()
|
||||||
{
|
{
|
||||||
return new FilePickerFileType(_name)
|
return new FilePickerFileType(_name)
|
||||||
|
|||||||
@ -26,12 +26,10 @@
|
|||||||
Name="RegionsAutoCompleteBox">
|
Name="RegionsAutoCompleteBox">
|
||||||
<AutoCompleteBox.ItemTemplate>
|
<AutoCompleteBox.ItemTemplate>
|
||||||
<DataTemplate DataType="{x:Type globalization:RegionInfo}">
|
<DataTemplate DataType="{x:Type globalization:RegionInfo}">
|
||||||
<StackPanel Orientation="Horizontal">
|
<TextBlock>
|
||||||
<TextBlock Text="{CompiledBinding EnglishName}"></TextBlock>
|
<Run Text="{CompiledBinding EnglishName}"></Run>
|
||||||
<TextBlock Text=" ("/>
|
<Run Text="(" /><Run FontWeight="SemiBold" Text="{CompiledBinding TwoLetterISORegionName}"></Run><Run Text=")" />
|
||||||
<TextBlock FontWeight="SemiBold" Text="{CompiledBinding TwoLetterISORegionName}"></TextBlock>
|
</TextBlock>
|
||||||
<TextBlock Text=")"/>
|
|
||||||
</StackPanel>
|
|
||||||
</DataTemplate>
|
</DataTemplate>
|
||||||
</AutoCompleteBox.ItemTemplate>
|
</AutoCompleteBox.ItemTemplate>
|
||||||
</AutoCompleteBox>
|
</AutoCompleteBox>
|
||||||
|
|||||||
@ -181,7 +181,7 @@ public partial class ProfileConfigurationEditViewModel : DialogViewModelBase<Pro
|
|||||||
private async Task ExecuteBrowseBitmapFile()
|
private async Task ExecuteBrowseBitmapFile()
|
||||||
{
|
{
|
||||||
string[]? result = await _windowService.CreateOpenFileDialog()
|
string[]? result = await _windowService.CreateOpenFileDialog()
|
||||||
.HavingFilter(f => f.WithExtension("png").WithExtension("jpg").WithExtension("bmp").WithName("Bitmap image"))
|
.HavingFilter(f => f.WithBitmaps())
|
||||||
.ShowAsync();
|
.ShowAsync();
|
||||||
|
|
||||||
if (result == null)
|
if (result == null)
|
||||||
|
|||||||
@ -0,0 +1,41 @@
|
|||||||
|
<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:image="clr-namespace:Artemis.UI.Screens.Workshop.Image"
|
||||||
|
xmlns:avalonia="clr-namespace:Material.Icons.Avalonia;assembly=Material.Icons.Avalonia"
|
||||||
|
xmlns:converters="clr-namespace:Artemis.UI.Shared.Converters;assembly=Artemis.UI.Shared"
|
||||||
|
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
|
||||||
|
x:Class="Artemis.UI.Screens.Workshop.Image.ImageSubmissionView"
|
||||||
|
x:DataType="image:ImageSubmissionViewModel">
|
||||||
|
<UserControl.Resources>
|
||||||
|
<converters:BytesToStringConverter x:Key="BytesToStringConverter" />
|
||||||
|
</UserControl.Resources>
|
||||||
|
<Border Classes="card" Padding="0" Width="300" ClipToBounds="True" Margin="5">
|
||||||
|
<Grid RowDefinitions="230,*">
|
||||||
|
<Rectangle Grid.Row="0" Fill="{DynamicResource CheckerboardBrush}"/>
|
||||||
|
<Image Grid.Row="0"
|
||||||
|
VerticalAlignment="Center"
|
||||||
|
HorizontalAlignment="Center"
|
||||||
|
RenderOptions.BitmapInterpolationMode="HighQuality"
|
||||||
|
Source="{CompiledBinding Bitmap}"/>
|
||||||
|
<StackPanel Grid.Row="1" Margin="12">
|
||||||
|
<TextBlock Text="{CompiledBinding FileName, FallbackValue=Unnamed image}" TextTrimming="CharacterEllipsis" />
|
||||||
|
<StackPanel>
|
||||||
|
<TextBlock TextWrapping="Wrap" Classes="subtitle" Text="{CompiledBinding ImageDimensions, Mode=OneWay}" />
|
||||||
|
<TextBlock TextWrapping="Wrap" Classes="subtitle" Text="{CompiledBinding FileSize, Converter={StaticResource BytesToStringConverter}, Mode=OneWay}" />
|
||||||
|
</StackPanel>
|
||||||
|
</StackPanel>
|
||||||
|
|
||||||
|
<Button Grid.Row="1"
|
||||||
|
VerticalAlignment="Bottom"
|
||||||
|
HorizontalAlignment="Right"
|
||||||
|
Margin="6"
|
||||||
|
Classes="icon-button"
|
||||||
|
Command="{CompiledBinding Remove}"
|
||||||
|
ToolTip.Tip="Remove">
|
||||||
|
<avalonia:MaterialIcon Kind="Trash" />
|
||||||
|
</Button>
|
||||||
|
</Grid>
|
||||||
|
</Border>
|
||||||
|
</UserControl>
|
||||||
@ -0,0 +1,11 @@
|
|||||||
|
using Avalonia.ReactiveUI;
|
||||||
|
|
||||||
|
namespace Artemis.UI.Screens.Workshop.Image;
|
||||||
|
|
||||||
|
public partial class ImageSubmissionView : ReactiveUserControl<ImageSubmissionViewModel>
|
||||||
|
{
|
||||||
|
public ImageSubmissionView()
|
||||||
|
{
|
||||||
|
InitializeComponent();
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,42 @@
|
|||||||
|
using System.IO;
|
||||||
|
using System.Reactive.Disposables;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using System.Windows.Input;
|
||||||
|
using Artemis.UI.Shared;
|
||||||
|
using Artemis.UI.Shared.Services;
|
||||||
|
using Avalonia.Media.Imaging;
|
||||||
|
using Avalonia.Threading;
|
||||||
|
using PropertyChanged.SourceGenerator;
|
||||||
|
using ReactiveUI;
|
||||||
|
|
||||||
|
namespace Artemis.UI.Screens.Workshop.Image;
|
||||||
|
|
||||||
|
public partial class ImageSubmissionViewModel : ActivatableViewModelBase
|
||||||
|
{
|
||||||
|
[Notify(Setter.Private)] private Bitmap? _bitmap;
|
||||||
|
[Notify(Setter.Private)] private string? _fileName;
|
||||||
|
[Notify(Setter.Private)] private string? _imageDimensions;
|
||||||
|
[Notify(Setter.Private)] private long _fileSize;
|
||||||
|
[Notify] private ICommand? _remove;
|
||||||
|
|
||||||
|
public ImageSubmissionViewModel(Stream imageStream)
|
||||||
|
{
|
||||||
|
this.WhenActivated(d =>
|
||||||
|
{
|
||||||
|
Dispatcher.UIThread.Invoke(() =>
|
||||||
|
{
|
||||||
|
imageStream.Seek(0, SeekOrigin.Begin);
|
||||||
|
Bitmap = new Bitmap(imageStream);
|
||||||
|
FileSize = imageStream.Length;
|
||||||
|
ImageDimensions = Bitmap.Size.Width + "x" + Bitmap.Size.Height;
|
||||||
|
|
||||||
|
if (imageStream is FileStream fileStream)
|
||||||
|
FileName = Path.GetFileName(fileStream.Name);
|
||||||
|
else
|
||||||
|
FileName = "Unnamed image";
|
||||||
|
|
||||||
|
Bitmap.DisposeWith(d);
|
||||||
|
}, DispatcherPriority.Background);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,62 @@
|
|||||||
|
<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:dialogs="clr-namespace:Artemis.UI.Screens.Workshop.Layout.Dialogs"
|
||||||
|
xmlns:avalonia="clr-namespace:Material.Icons.Avalonia;assembly=Material.Icons.Avalonia"
|
||||||
|
xmlns:deviceProviders="clr-namespace:Artemis.Core.DeviceProviders;assembly=Artemis.Core"
|
||||||
|
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.Layout.Dialogs.DeviceProviderPickerDialogView"
|
||||||
|
x:DataType="dialogs:DeviceProviderPickerDialogViewModel">
|
||||||
|
<Grid RowDefinitions="Auto,*">
|
||||||
|
<ListBox Name="EffectDescriptorsList"
|
||||||
|
Grid.Row="1"
|
||||||
|
ItemsSource="{CompiledBinding DeviceProviders}"
|
||||||
|
IsVisible="{CompiledBinding DeviceProviders.Count}"
|
||||||
|
Height="300">
|
||||||
|
|
||||||
|
<ListBox.DataTemplates>
|
||||||
|
<DataTemplate DataType="{x:Type deviceProviders:DeviceProvider}">
|
||||||
|
<Grid RowDefinitions="Auto,*"
|
||||||
|
ColumnDefinitions="Auto,Auto"
|
||||||
|
Background="Transparent"
|
||||||
|
PointerReleased="InputElement_OnPointerReleased"
|
||||||
|
Margin="0 4"
|
||||||
|
VerticalAlignment="Center">
|
||||||
|
<shared:ArtemisIcon Grid.Column="0"
|
||||||
|
Grid.RowSpan="2"
|
||||||
|
Icon="{CompiledBinding Plugin.Info.Icon}"
|
||||||
|
Width="24"
|
||||||
|
Height="24"
|
||||||
|
VerticalAlignment="Center"
|
||||||
|
Margin="0 0 15 0" />
|
||||||
|
<TextBlock Grid.Column="1"
|
||||||
|
Grid.Row="0"
|
||||||
|
Classes="BodyStrongTextBlockStyle"
|
||||||
|
Text="{CompiledBinding Info.Name}"
|
||||||
|
VerticalAlignment="Bottom"
|
||||||
|
Width="450"
|
||||||
|
TextWrapping="Wrap" />
|
||||||
|
<TextBlock Grid.Column="1"
|
||||||
|
Grid.Row="1"
|
||||||
|
Foreground="{DynamicResource TextFillColorSecondary}"
|
||||||
|
Text="{CompiledBinding Plugin.Info.Name}"
|
||||||
|
VerticalAlignment="Top"
|
||||||
|
Width="450"
|
||||||
|
TextWrapping="Wrap" />
|
||||||
|
</Grid>
|
||||||
|
</DataTemplate>
|
||||||
|
</ListBox.DataTemplates>
|
||||||
|
</ListBox>
|
||||||
|
<Grid Grid.Row="1" Height="300">
|
||||||
|
<StackPanel VerticalAlignment="Center"
|
||||||
|
Spacing="20"
|
||||||
|
IsVisible="{CompiledBinding !DeviceProviders.Count}">
|
||||||
|
<avalonia:MaterialIcon Kind="CloseCircle" Width="32" Height="32" />
|
||||||
|
<TextBlock Classes="h5" TextAlignment="Center">You do not have any device providers enabled</TextBlock>
|
||||||
|
</StackPanel>
|
||||||
|
</Grid>
|
||||||
|
|
||||||
|
</Grid>
|
||||||
|
</UserControl>
|
||||||
@ -0,0 +1,24 @@
|
|||||||
|
using Artemis.Core.DeviceProviders;
|
||||||
|
using Avalonia;
|
||||||
|
using Avalonia.Controls;
|
||||||
|
using Avalonia.Input;
|
||||||
|
using Avalonia.Markup.Xaml;
|
||||||
|
using Avalonia.ReactiveUI;
|
||||||
|
|
||||||
|
namespace Artemis.UI.Screens.Workshop.Layout.Dialogs;
|
||||||
|
|
||||||
|
public partial class DeviceProviderPickerDialogView : ReactiveUserControl<DeviceProviderPickerDialogViewModel>
|
||||||
|
{
|
||||||
|
public DeviceProviderPickerDialogView()
|
||||||
|
{
|
||||||
|
InitializeComponent();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void InputElement_OnPointerReleased(object? sender, PointerReleasedEventArgs e)
|
||||||
|
{
|
||||||
|
if (sender is not IDataContextProvider {DataContext: DeviceProvider deviceProvider} || ViewModel == null)
|
||||||
|
return;
|
||||||
|
|
||||||
|
ViewModel?.SelectDeviceProvider(deviceProvider);
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,24 @@
|
|||||||
|
using System.Collections.ObjectModel;
|
||||||
|
using Artemis.Core.DeviceProviders;
|
||||||
|
using Artemis.Core.Services;
|
||||||
|
using Artemis.UI.Shared;
|
||||||
|
|
||||||
|
namespace Artemis.UI.Screens.Workshop.Layout.Dialogs;
|
||||||
|
|
||||||
|
public class DeviceProviderPickerDialogViewModel : ContentDialogViewModelBase
|
||||||
|
{
|
||||||
|
public ObservableCollection<DeviceProvider> DeviceProviders { get; }
|
||||||
|
|
||||||
|
public DeviceProviderPickerDialogViewModel(IPluginManagementService pluginManagementService)
|
||||||
|
{
|
||||||
|
DeviceProviders = new ObservableCollection<DeviceProvider>(pluginManagementService.GetFeaturesOfType<DeviceProvider>());
|
||||||
|
}
|
||||||
|
|
||||||
|
public DeviceProvider? DeviceProvider { get; set; }
|
||||||
|
|
||||||
|
public void SelectDeviceProvider(DeviceProvider deviceProvider)
|
||||||
|
{
|
||||||
|
DeviceProvider = deviceProvider;
|
||||||
|
ContentDialog?.Hide();
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -3,8 +3,55 @@
|
|||||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||||
xmlns:layout="clr-namespace:Artemis.UI.Screens.Workshop.Layout"
|
xmlns:layout="clr-namespace:Artemis.UI.Screens.Workshop.Layout"
|
||||||
|
xmlns:avalonia="clr-namespace:Material.Icons.Avalonia;assembly=Material.Icons.Avalonia"
|
||||||
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
|
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
|
||||||
x:Class="Artemis.UI.Screens.Workshop.Layout.LayoutInfoView"
|
x:Class="Artemis.UI.Screens.Workshop.Layout.LayoutInfoView"
|
||||||
x:DataType="layout:LayoutInfoViewModel">
|
x:DataType="layout:LayoutInfoViewModel">
|
||||||
Welcome to Avalonia!
|
<Grid RowDefinitions="Auto,Auto,Auto" ColumnDefinitions="*,*">
|
||||||
|
<StackPanel Grid.Row="0" Grid.Column="0" Margin="0 0 4 0">
|
||||||
|
<Label>Model</Label>
|
||||||
|
<TextBox Text="{CompiledBinding Model}"></TextBox>
|
||||||
|
</StackPanel>
|
||||||
|
|
||||||
|
<Grid Grid.Row="0" Grid.Column="1" Margin="4 0 0 4" ColumnDefinitions="*,*" RowDefinitions="*,*">
|
||||||
|
<Label Grid.Row="0" Grid.Column="0" VerticalAlignment="Bottom">Vendor</Label>
|
||||||
|
<TextBox Grid.Row="1" Grid.Column="0" Grid.ColumnSpan="2" Text="{CompiledBinding Vendor}"></TextBox>
|
||||||
|
</Grid>
|
||||||
|
|
||||||
|
<StackPanel Grid.Row="1" Grid.Column="0" Grid.ColumnSpan="2">
|
||||||
|
<Label>Device provider ID</Label>
|
||||||
|
<TextBox Text="{CompiledBinding DeviceProviderIdInput}"></TextBox>
|
||||||
|
</StackPanel>
|
||||||
|
|
||||||
|
<Button Grid.Row="1"
|
||||||
|
Grid.Column="0"
|
||||||
|
Grid.ColumnSpan="2"
|
||||||
|
HorizontalAlignment="Right"
|
||||||
|
VerticalAlignment="Bottom"
|
||||||
|
Classes="AppBarButton"
|
||||||
|
Command="{CompiledBinding BrowseDeviceProvider}"
|
||||||
|
ToolTip.Tip="Browse">
|
||||||
|
...
|
||||||
|
</Button>
|
||||||
|
|
||||||
|
<TextBlock Grid.Row="2"
|
||||||
|
Grid.Column="0"
|
||||||
|
Grid.ColumnSpan="2"
|
||||||
|
Classes="subtitle"
|
||||||
|
Margin="0 2"
|
||||||
|
Text="{CompiledBinding DeviceProviders}"
|
||||||
|
VerticalAlignment="Top">
|
||||||
|
</TextBlock>
|
||||||
|
|
||||||
|
<Button Grid.Row="2"
|
||||||
|
Grid.Column="0"
|
||||||
|
Grid.ColumnSpan="2"
|
||||||
|
Margin="0 5"
|
||||||
|
HorizontalAlignment="Right"
|
||||||
|
VerticalAlignment="Top"
|
||||||
|
Command="{CompiledBinding Remove}"
|
||||||
|
Classes="icon-button">
|
||||||
|
<avalonia:MaterialIcon Kind="Trash" />
|
||||||
|
</Button>
|
||||||
|
</Grid>
|
||||||
</UserControl>
|
</UserControl>
|
||||||
@ -1,10 +1,8 @@
|
|||||||
using Avalonia;
|
using Avalonia.ReactiveUI;
|
||||||
using Avalonia.Controls;
|
|
||||||
using Avalonia.Markup.Xaml;
|
|
||||||
|
|
||||||
namespace Artemis.UI.Screens.Workshop.Layout;
|
namespace Artemis.UI.Screens.Workshop.Layout;
|
||||||
|
|
||||||
public partial class LayoutInfoView : UserControl
|
public partial class LayoutInfoView : ReactiveUserControl<LayoutInfoViewModel>
|
||||||
{
|
{
|
||||||
public LayoutInfoView()
|
public LayoutInfoView()
|
||||||
{
|
{
|
||||||
|
|||||||
@ -1,32 +1,68 @@
|
|||||||
using System;
|
using System;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Reactive.Linq;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using System.Windows.Input;
|
||||||
using Artemis.Core;
|
using Artemis.Core;
|
||||||
using Artemis.UI.Screens.Workshop.SubmissionWizard.Models;
|
using Artemis.Core.DeviceProviders;
|
||||||
|
using Artemis.Core.Services;
|
||||||
|
using Artemis.UI.Screens.Workshop.Layout.Dialogs;
|
||||||
using Artemis.UI.Shared;
|
using Artemis.UI.Shared;
|
||||||
|
using Artemis.UI.Shared.Services;
|
||||||
using PropertyChanged.SourceGenerator;
|
using PropertyChanged.SourceGenerator;
|
||||||
using RGB.NET.Core;
|
using ReactiveUI;
|
||||||
using KeyboardLayoutType = Artemis.Core.KeyboardLayoutType;
|
using ReactiveUI.Validation.Extensions;
|
||||||
|
|
||||||
namespace Artemis.UI.Screens.Workshop.Layout;
|
namespace Artemis.UI.Screens.Workshop.Layout;
|
||||||
|
|
||||||
public partial class LayoutInfoViewModel : ViewModelBase
|
public partial class LayoutInfoViewModel : ValidatableViewModelBase
|
||||||
{
|
{
|
||||||
[Notify] private Guid _deviceProvider;
|
private readonly IWindowService _windowService;
|
||||||
|
private readonly ObservableAsPropertyHelper<string?> _deviceProviders;
|
||||||
[Notify] private string? _vendor;
|
[Notify] private string? _vendor;
|
||||||
[Notify] private string? _model;
|
[Notify] private string? _model;
|
||||||
[Notify] private KeyboardLayoutType? _physicalLayout;
|
[Notify] private Guid _deviceProviderId;
|
||||||
[Notify] private string? _logicalLayout;
|
[Notify] private string? _deviceProviderIdInput;
|
||||||
|
[Notify] private ICommand? _remove;
|
||||||
|
|
||||||
/// <inheritdoc />
|
public LayoutInfoViewModel(ArtemisLayout layout,
|
||||||
public LayoutInfoViewModel(ArtemisLayout layout)
|
IDeviceService deviceService,
|
||||||
|
IWindowService windowService,
|
||||||
|
IPluginManagementService pluginManagementService)
|
||||||
{
|
{
|
||||||
DisplayKeyboardLayout = layout.RgbLayout.Type == RGBDeviceType.Keyboard;
|
_windowService = windowService;
|
||||||
|
_vendor = layout.RgbLayout.Vendor;
|
||||||
|
_model = layout.RgbLayout.Model;
|
||||||
|
|
||||||
|
DeviceProvider? deviceProvider = deviceService.Devices.FirstOrDefault(d => d.Layout == layout)?.DeviceProvider;
|
||||||
|
if (deviceProvider != null)
|
||||||
|
_deviceProviderId = deviceProvider.Plugin.Guid;
|
||||||
|
|
||||||
|
_deviceProviders = this.WhenAnyValue(vm => vm.DeviceProviderId)
|
||||||
|
.Select(id => pluginManagementService.GetAllPlugins().FirstOrDefault(p => p.Guid == id)?.Features.Select(f => f.Name))
|
||||||
|
.Select(names => names != null ? string.Join(", ", names) : "")
|
||||||
|
.ToProperty(this, vm => vm.DeviceProviders);
|
||||||
|
|
||||||
|
this.WhenAnyValue(vm => vm.DeviceProviderId).Subscribe(g => DeviceProviderIdInput = g.ToString());
|
||||||
|
this.WhenAnyValue(vm => vm.DeviceProviderIdInput).Where(i => Guid.TryParse(i, out _)).Subscribe(i => DeviceProviderId = Guid.Parse(i!));
|
||||||
|
|
||||||
|
this.ValidationRule(vm => vm.Model, input => !string.IsNullOrWhiteSpace(input), "Device model is required");
|
||||||
|
this.ValidationRule(vm => vm.Vendor, input => !string.IsNullOrWhiteSpace(input), "Device vendor is required");
|
||||||
|
this.ValidationRule(vm => vm.DeviceProviderIdInput, input => Guid.TryParse(input, out _), "Must be a valid GUID formatted as: 00000000-0000-0000-0000-000000000000");
|
||||||
|
this.ValidationRule(vm => vm.DeviceProviderIdInput, input => !string.IsNullOrWhiteSpace(input), "Device provider ID is required");
|
||||||
}
|
}
|
||||||
|
|
||||||
public LayoutInfoViewModel(ArtemisLayout layout, LayoutInfo layoutInfo)
|
public string? DeviceProviders => _deviceProviders.Value;
|
||||||
|
|
||||||
|
public async Task BrowseDeviceProvider()
|
||||||
{
|
{
|
||||||
DisplayKeyboardLayout = layout.RgbLayout.Type == RGBDeviceType.Keyboard;
|
await _windowService.CreateContentDialog()
|
||||||
|
.WithTitle("Select device provider")
|
||||||
|
.WithViewModel(out DeviceProviderPickerDialogViewModel vm)
|
||||||
|
.ShowAsync();
|
||||||
|
|
||||||
|
DeviceProvider? deviceProvider = vm.DeviceProvider;
|
||||||
|
if (deviceProvider != null)
|
||||||
|
DeviceProviderId = deviceProvider.Plugin.Guid;
|
||||||
}
|
}
|
||||||
|
|
||||||
public bool DisplayKeyboardLayout { get; }
|
|
||||||
}
|
}
|
||||||
@ -1,8 +1,8 @@
|
|||||||
using System;
|
using System.Collections.Generic;
|
||||||
using System.Collections.Generic;
|
using System.Collections.ObjectModel;
|
||||||
|
using System.Linq;
|
||||||
using Artemis.Core;
|
using Artemis.Core;
|
||||||
using Artemis.WebClient.Workshop;
|
using Artemis.UI.Screens.Workshop.Layout;
|
||||||
using KeyboardLayoutType = Artemis.WebClient.Workshop.KeyboardLayoutType;
|
|
||||||
|
|
||||||
namespace Artemis.UI.Screens.Workshop.SubmissionWizard.Models;
|
namespace Artemis.UI.Screens.Workshop.SubmissionWizard.Models;
|
||||||
|
|
||||||
@ -14,15 +14,16 @@ public class LayoutEntrySource : IEntrySource
|
|||||||
}
|
}
|
||||||
|
|
||||||
public ArtemisLayout Layout { get; set; }
|
public ArtemisLayout Layout { get; set; }
|
||||||
public List<LayoutInfo> LayoutInfo { get; } = new();
|
public ObservableCollection<LayoutInfoViewModel> LayoutInfo { get; } = new();
|
||||||
}
|
public KeyboardLayoutType PhysicalLayout { get; set; }
|
||||||
|
|
||||||
public class LayoutInfo
|
private List<LayoutCustomLedDataLogicalLayout> GetLogicalLayouts()
|
||||||
{
|
{
|
||||||
public Guid DeviceProvider { get; set; }
|
return Layout.Leds
|
||||||
public RGBDeviceType DeviceType { get; set; }
|
.Where(l => l.LayoutCustomLedData.LogicalLayouts != null)
|
||||||
public string Model { get; set; }
|
.SelectMany(l => l.LayoutCustomLedData.LogicalLayouts!)
|
||||||
public string Vendor { get; set; }
|
.Where(l => !string.IsNullOrWhiteSpace(l.Name))
|
||||||
public string? LogicalLayout { get; set; }
|
.DistinctBy(l => l.Name)
|
||||||
public KeyboardLayoutType? PhysicalLayout { get; set; }
|
.ToList();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@ -9,7 +9,7 @@ using DryIoc;
|
|||||||
|
|
||||||
namespace Artemis.UI.Screens.Workshop.SubmissionWizard.Models;
|
namespace Artemis.UI.Screens.Workshop.SubmissionWizard.Models;
|
||||||
|
|
||||||
public class SubmissionWizardState
|
public class SubmissionWizardState : IDisposable
|
||||||
{
|
{
|
||||||
private readonly IContainer _container;
|
private readonly IContainer _container;
|
||||||
private readonly IWindowService _windowService;
|
private readonly IWindowService _windowService;
|
||||||
@ -62,4 +62,11 @@ public class SubmissionWizardState
|
|||||||
else
|
else
|
||||||
throw new NotImplementedException();
|
throw new NotImplementedException();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void Dispose()
|
||||||
|
{
|
||||||
|
Icon?.Dispose();
|
||||||
|
foreach (Stream stream in Images)
|
||||||
|
stream.Dispose();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@ -0,0 +1,34 @@
|
|||||||
|
<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:steps="clr-namespace:Artemis.UI.Screens.Workshop.SubmissionWizard.Steps"
|
||||||
|
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
|
||||||
|
x:Class="Artemis.UI.Screens.Workshop.SubmissionWizard.Steps.ImagesStepView"
|
||||||
|
x:DataType="steps:ImagesStepViewModel">
|
||||||
|
<Grid RowDefinitions="Auto,*">
|
||||||
|
<StackPanel Grid.Row="0">
|
||||||
|
<StackPanel.Styles>
|
||||||
|
<Styles>
|
||||||
|
<Style Selector="TextBlock">
|
||||||
|
<Setter Property="TextWrapping" Value="Wrap"></Setter>
|
||||||
|
</Style>
|
||||||
|
</Styles>
|
||||||
|
</StackPanel.Styles>
|
||||||
|
<TextBlock Theme="{StaticResource TitleTextBlockStyle}" TextWrapping="Wrap">Images</TextBlock>
|
||||||
|
<TextBlock TextWrapping="Wrap">
|
||||||
|
Optionally provide some images of your submission.
|
||||||
|
</TextBlock>
|
||||||
|
</StackPanel>
|
||||||
|
|
||||||
|
<ScrollViewer Grid.Row="1" Margin="0 20 0 0">
|
||||||
|
<ItemsControl ItemsSource="{CompiledBinding Images}">
|
||||||
|
<ItemsControl.ItemsPanel>
|
||||||
|
<ItemsPanelTemplate>
|
||||||
|
<WrapPanel />
|
||||||
|
</ItemsPanelTemplate>
|
||||||
|
</ItemsControl.ItemsPanel>
|
||||||
|
</ItemsControl>
|
||||||
|
</ScrollViewer>
|
||||||
|
</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;
|
||||||
|
|
||||||
|
public partial class ImagesStepView : ReactiveUserControl<ImagesStepViewModel>
|
||||||
|
{
|
||||||
|
public ImagesStepView()
|
||||||
|
{
|
||||||
|
InitializeComponent();
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,68 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.ObjectModel;
|
||||||
|
using System.IO;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Reactive.Disposables;
|
||||||
|
using System.Threading;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using Artemis.UI.Screens.Workshop.Image;
|
||||||
|
using Artemis.UI.Shared.Services;
|
||||||
|
using DynamicData;
|
||||||
|
using ReactiveUI;
|
||||||
|
|
||||||
|
namespace Artemis.UI.Screens.Workshop.SubmissionWizard.Steps;
|
||||||
|
|
||||||
|
public class ImagesStepViewModel : SubmissionViewModel
|
||||||
|
{
|
||||||
|
private readonly IWindowService _windowService;
|
||||||
|
private readonly SourceList<Stream> _imageStreams;
|
||||||
|
|
||||||
|
public ImagesStepViewModel(IWindowService windowService, Func<Stream, ImageSubmissionViewModel> imageSubmissionViewModel)
|
||||||
|
{
|
||||||
|
_windowService = windowService;
|
||||||
|
|
||||||
|
Continue = ReactiveCommand.Create(() => State.ChangeScreen<UploadStepViewModel>());
|
||||||
|
GoBack = ReactiveCommand.Create(() => State.ChangeScreen<SpecificationsStepViewModel>());
|
||||||
|
Secondary = ReactiveCommand.CreateFromTask(ExecuteAddImage);
|
||||||
|
SecondaryText = "Add image";
|
||||||
|
|
||||||
|
_imageStreams = new SourceList<Stream>();
|
||||||
|
_imageStreams.Connect()
|
||||||
|
.Transform(p => CreateImageSubmissionViewModel(imageSubmissionViewModel, p))
|
||||||
|
.Bind(out ReadOnlyObservableCollection<ImageSubmissionViewModel> images)
|
||||||
|
.Subscribe();
|
||||||
|
Images = images;
|
||||||
|
|
||||||
|
this.WhenActivated((CompositeDisposable d) =>
|
||||||
|
{
|
||||||
|
_imageStreams.Clear();
|
||||||
|
_imageStreams.AddRange(State.Images);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public ReadOnlyObservableCollection<ImageSubmissionViewModel> Images { get; }
|
||||||
|
|
||||||
|
private ImageSubmissionViewModel CreateImageSubmissionViewModel(Func<Stream, ImageSubmissionViewModel> imageSubmissionViewModel, Stream stream)
|
||||||
|
{
|
||||||
|
ImageSubmissionViewModel viewModel = imageSubmissionViewModel(stream);
|
||||||
|
viewModel.Remove = ReactiveCommand.Create(() => _imageStreams.Remove(stream));
|
||||||
|
return viewModel;
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task ExecuteAddImage(CancellationToken arg)
|
||||||
|
{
|
||||||
|
string[]? result = await _windowService.CreateOpenFileDialog().WithAllowMultiple().HavingFilter(f => f.WithBitmaps()).ShowAsync();
|
||||||
|
if (result == null)
|
||||||
|
return;
|
||||||
|
|
||||||
|
foreach (string path in result)
|
||||||
|
{
|
||||||
|
if (_imageStreams.Items.Any(i => i is FileStream fs && fs.Name == path))
|
||||||
|
continue;
|
||||||
|
|
||||||
|
FileStream stream = new(path, FileMode.Open);
|
||||||
|
_imageStreams.Add(stream);
|
||||||
|
State.Images.Add(stream);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -3,10 +3,12 @@
|
|||||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||||
xmlns:layout="clr-namespace:Artemis.UI.Screens.Workshop.SubmissionWizard.Steps.Layout"
|
xmlns:layout="clr-namespace:Artemis.UI.Screens.Workshop.SubmissionWizard.Steps.Layout"
|
||||||
|
xmlns:shared="clr-namespace:Artemis.UI.Shared;assembly=Artemis.UI.Shared"
|
||||||
|
xmlns:controls="clr-namespace:FluentAvalonia.UI.Controls;assembly=FluentAvalonia"
|
||||||
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
|
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
|
||||||
x:Class="Artemis.UI.Screens.Workshop.SubmissionWizard.Steps.Layout.LayoutInfoStepView"
|
x:Class="Artemis.UI.Screens.Workshop.SubmissionWizard.Steps.Layout.LayoutInfoStepView"
|
||||||
x:DataType="layout:LayoutInfoStepViewModel">
|
x:DataType="layout:LayoutInfoStepViewModel">
|
||||||
<Grid RowDefinitions="Auto,*">
|
<Grid RowDefinitions="Auto,Auto,*">
|
||||||
<StackPanel>
|
<StackPanel>
|
||||||
<StackPanel.Styles>
|
<StackPanel.Styles>
|
||||||
<Styles>
|
<Styles>
|
||||||
@ -24,7 +26,25 @@
|
|||||||
</TextBlock>
|
</TextBlock>
|
||||||
</StackPanel>
|
</StackPanel>
|
||||||
|
|
||||||
<ScrollViewer Grid.Row="1"
|
<StackPanel Grid.Row="1"
|
||||||
|
Grid.Column="0"
|
||||||
|
IsVisible="{CompiledBinding IsKeyboardLayout}"
|
||||||
|
Margin="0 20 0 0">
|
||||||
|
<Label>Physical layout</Label>
|
||||||
|
<shared:EnumComboBox Value="{CompiledBinding PhysicalLayout}"></shared:EnumComboBox>
|
||||||
|
</StackPanel>
|
||||||
|
|
||||||
|
<controls:HyperlinkButton Grid.Row="1"
|
||||||
|
Grid.Column="0"
|
||||||
|
IsVisible="{CompiledBinding IsKeyboardLayout}"
|
||||||
|
Margin="0 10 0 0"
|
||||||
|
VerticalAlignment="Top"
|
||||||
|
HorizontalAlignment="Right"
|
||||||
|
NavigateUri="https://wiki.artemis-rgb.com/en/guides/developer/layouts/keyboard-layouts?mtm_campaign=artemis&mtm_kwd=workshop-wizard">
|
||||||
|
Learn about physical layouts
|
||||||
|
</controls:HyperlinkButton>
|
||||||
|
|
||||||
|
<ScrollViewer Grid.Row="2"
|
||||||
Grid.Column="0"
|
Grid.Column="0"
|
||||||
Margin="0 10 0 0"
|
Margin="0 10 0 0"
|
||||||
Classes="with-padding"
|
Classes="with-padding"
|
||||||
@ -34,7 +54,10 @@
|
|||||||
<ItemsRepeater ItemsSource="{CompiledBinding LayoutInfo}">
|
<ItemsRepeater ItemsSource="{CompiledBinding LayoutInfo}">
|
||||||
<ItemsRepeater.ItemTemplate>
|
<ItemsRepeater.ItemTemplate>
|
||||||
<DataTemplate>
|
<DataTemplate>
|
||||||
|
<StackPanel>
|
||||||
|
<Border Classes="card-separator" />
|
||||||
<ContentControl Content="{CompiledBinding}" />
|
<ContentControl Content="{CompiledBinding}" />
|
||||||
|
</StackPanel>
|
||||||
</DataTemplate>
|
</DataTemplate>
|
||||||
</ItemsRepeater.ItemTemplate>
|
</ItemsRepeater.ItemTemplate>
|
||||||
</ItemsRepeater>
|
</ItemsRepeater>
|
||||||
|
|||||||
@ -1,25 +1,117 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
using System.Collections.ObjectModel;
|
using System.Collections.ObjectModel;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Reactive.Disposables;
|
using System.Reactive.Disposables;
|
||||||
|
using System.Reactive.Linq;
|
||||||
|
using Artemis.Core;
|
||||||
using Artemis.UI.Screens.Workshop.Layout;
|
using Artemis.UI.Screens.Workshop.Layout;
|
||||||
using Artemis.UI.Screens.Workshop.SubmissionWizard.Models;
|
using Artemis.UI.Screens.Workshop.SubmissionWizard.Models;
|
||||||
using DynamicData;
|
using PropertyChanged.SourceGenerator;
|
||||||
using ReactiveUI;
|
using ReactiveUI;
|
||||||
|
using ReactiveUI.Validation.Extensions;
|
||||||
|
using RGB.NET.Core;
|
||||||
|
using KeyboardLayoutType = Artemis.Core.KeyboardLayoutType;
|
||||||
|
|
||||||
namespace Artemis.UI.Screens.Workshop.SubmissionWizard.Steps.Layout;
|
namespace Artemis.UI.Screens.Workshop.SubmissionWizard.Steps.Layout;
|
||||||
|
|
||||||
public class LayoutInfoStepViewModel : SubmissionViewModel
|
public partial class LayoutInfoStepViewModel : SubmissionViewModel
|
||||||
{
|
{
|
||||||
public LayoutInfoStepViewModel()
|
private readonly Func<ArtemisLayout, LayoutInfoViewModel> _getLayoutInfoViewModel;
|
||||||
|
private ArtemisLayout? _layout;
|
||||||
|
[Notify(Setter.Private)] private bool _isKeyboardLayout;
|
||||||
|
[Notify] private ObservableCollection<LayoutInfoViewModel> _layoutInfo = new();
|
||||||
|
[Notify] private KeyboardLayoutType _physicalLayout;
|
||||||
|
|
||||||
|
public LayoutInfoStepViewModel(Func<ArtemisLayout, LayoutInfoViewModel> getLayoutInfoViewModel)
|
||||||
{
|
{
|
||||||
|
_getLayoutInfoViewModel = getLayoutInfoViewModel;
|
||||||
|
|
||||||
GoBack = ReactiveCommand.Create(() => State.ChangeScreen<LayoutSelectionStepViewModel>());
|
GoBack = ReactiveCommand.Create(() => State.ChangeScreen<LayoutSelectionStepViewModel>());
|
||||||
this.WhenActivated((CompositeDisposable _) =>
|
Continue = ReactiveCommand.Create(ExecuteContinue, ValidationContext.Valid);
|
||||||
|
Secondary = ReactiveCommand.Create(ExecuteAddLayoutInfo);
|
||||||
|
SecondaryText = "Add layout info";
|
||||||
|
|
||||||
|
this.WhenActivated(d =>
|
||||||
{
|
{
|
||||||
LayoutInfo.Clear();
|
if (State.EntrySource is not LayoutEntrySource layoutEntrySource)
|
||||||
if (State.EntrySource is LayoutEntrySource layoutEntrySource)
|
return;
|
||||||
LayoutInfo.AddRange(layoutEntrySource.LayoutInfo.Select(i => new LayoutInfoViewModel(layoutEntrySource.Layout, i)));
|
|
||||||
|
_layout = layoutEntrySource.Layout;
|
||||||
|
IsKeyboardLayout = _layout.RgbLayout.Type == RGBDeviceType.Keyboard;
|
||||||
|
PhysicalLayout = layoutEntrySource.PhysicalLayout;
|
||||||
|
LayoutInfo = layoutEntrySource.LayoutInfo;
|
||||||
|
|
||||||
|
if (!LayoutInfo.Any())
|
||||||
|
ExecuteAddLayoutInfo();
|
||||||
|
|
||||||
|
this.ValidationRule(
|
||||||
|
vm => vm.PhysicalLayout,
|
||||||
|
this.WhenAnyValue(vm => vm.IsKeyboardLayout, vm => vm.PhysicalLayout, (isKeyboard, layout) => !isKeyboard || layout != KeyboardLayoutType.Unknown),
|
||||||
|
"A keyboard layout is required"
|
||||||
|
).DisposeWith(d);
|
||||||
|
this.ValidationRule(
|
||||||
|
vm => vm.LayoutInfo,
|
||||||
|
this.WhenAnyValue(vm => vm.LayoutInfo.Count).Select(c => c != 0),
|
||||||
|
"At least one layout info is required"
|
||||||
|
).DisposeWith(d);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
public ObservableCollection<LayoutInfoViewModel> LayoutInfo { get; } = new();
|
private void ExecuteAddLayoutInfo()
|
||||||
|
{
|
||||||
|
if (_layout == null)
|
||||||
|
return;
|
||||||
|
|
||||||
|
LayoutInfoViewModel layoutInfo = _getLayoutInfoViewModel(_layout);
|
||||||
|
layoutInfo.Remove = ReactiveCommand.Create(() => LayoutInfo.Remove(layoutInfo));
|
||||||
|
LayoutInfo.Add(layoutInfo);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void ExecuteContinue()
|
||||||
|
{
|
||||||
|
if (State.EntrySource is not LayoutEntrySource layoutEntrySource)
|
||||||
|
return;
|
||||||
|
|
||||||
|
layoutEntrySource.PhysicalLayout = PhysicalLayout;
|
||||||
|
|
||||||
|
if (string.IsNullOrWhiteSpace(State.Name))
|
||||||
|
State.Name = layoutEntrySource.Layout.RgbLayout.Name ?? "";
|
||||||
|
if (string.IsNullOrWhiteSpace(State.Summary))
|
||||||
|
{
|
||||||
|
State.Summary = !string.IsNullOrWhiteSpace(layoutEntrySource.Layout.RgbLayout.Vendor)
|
||||||
|
? $"{layoutEntrySource.Layout.RgbLayout.Vendor} {layoutEntrySource.Layout.RgbLayout.Type} device layout"
|
||||||
|
: $"{layoutEntrySource.Layout.RgbLayout.Type} device layout";
|
||||||
|
}
|
||||||
|
|
||||||
|
if (string.IsNullOrWhiteSpace(State.Description))
|
||||||
|
{
|
||||||
|
State.Description = $@"### Layout properties
|
||||||
|
**Name**
|
||||||
|
{layoutEntrySource.Layout.RgbLayout.Name ?? "N/A"}
|
||||||
|
**Description**
|
||||||
|
{layoutEntrySource.Layout.RgbLayout.Description ?? "N/A"}
|
||||||
|
**Author**
|
||||||
|
{layoutEntrySource.Layout.RgbLayout.Author ?? "N/A"}
|
||||||
|
**Type**
|
||||||
|
{layoutEntrySource.Layout.RgbLayout.Type}
|
||||||
|
**Vendor**
|
||||||
|
{layoutEntrySource.Layout.RgbLayout.Vendor ?? "N/A"}
|
||||||
|
**Model**
|
||||||
|
{layoutEntrySource.Layout.RgbLayout.Model ?? "N/A"}
|
||||||
|
**Shape**
|
||||||
|
{layoutEntrySource.Layout.RgbLayout.Shape}
|
||||||
|
**Width**
|
||||||
|
{layoutEntrySource.Layout.RgbLayout.Width}mm
|
||||||
|
**Height**
|
||||||
|
{layoutEntrySource.Layout.RgbLayout.Height}mm";
|
||||||
|
}
|
||||||
|
|
||||||
|
State.Categories = new List<long> {8}; // Device category, yes this could change but why would it
|
||||||
|
|
||||||
|
if (State.EntryId == null)
|
||||||
|
State.ChangeScreen<SpecificationsStepViewModel>();
|
||||||
|
else
|
||||||
|
State.ChangeScreen<UploadStepViewModel>();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@ -5,7 +5,6 @@ using Artemis.Core.Services;
|
|||||||
using PropertyChanged.SourceGenerator;
|
using PropertyChanged.SourceGenerator;
|
||||||
using ReactiveUI;
|
using ReactiveUI;
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Reactive.Disposables;
|
using System.Reactive.Disposables;
|
||||||
using System.Reactive.Linq;
|
using System.Reactive.Linq;
|
||||||
@ -14,6 +13,7 @@ using Artemis.UI.Screens.Workshop.SubmissionWizard.Models;
|
|||||||
using Artemis.UI.Shared.Extensions;
|
using Artemis.UI.Shared.Extensions;
|
||||||
using Artemis.UI.Shared.Services;
|
using Artemis.UI.Shared.Services;
|
||||||
using Avalonia.Media.Imaging;
|
using Avalonia.Media.Imaging;
|
||||||
|
using Avalonia.Threading;
|
||||||
using SkiaSharp;
|
using SkiaSharp;
|
||||||
|
|
||||||
namespace Artemis.UI.Screens.Workshop.SubmissionWizard.Steps.Layout;
|
namespace Artemis.UI.Screens.Workshop.SubmissionWizard.Steps.Layout;
|
||||||
@ -36,7 +36,7 @@ public partial class LayoutSelectionStepViewModel : SubmissionViewModel
|
|||||||
);
|
);
|
||||||
|
|
||||||
GoBack = ReactiveCommand.Create(() => State.ChangeScreen<EntryTypeStepViewModel>());
|
GoBack = ReactiveCommand.Create(() => State.ChangeScreen<EntryTypeStepViewModel>());
|
||||||
Continue = ReactiveCommand.Create(ExecuteContinue, this.WhenAnyValue(vm => vm.Layout).Select(p => p != null));
|
Continue = ReactiveCommand.CreateFromTask(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.SelectedDevice).WhereNotNull().Subscribe(d => Layout = d.Layout);
|
||||||
this.WhenAnyValue(vm => vm.Layout).Subscribe(CreatePreviewDevice);
|
this.WhenAnyValue(vm => vm.Layout).Subscribe(CreatePreviewDevice);
|
||||||
@ -82,44 +82,64 @@ public partial class LayoutSelectionStepViewModel : SubmissionViewModel
|
|||||||
Layout = layout;
|
Layout = layout;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void ExecuteContinue()
|
private async Task ExecuteContinue()
|
||||||
{
|
{
|
||||||
if (Layout == null)
|
if (Layout == null)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
State.EntrySource = new LayoutEntrySource(Layout);
|
State.EntrySource = new LayoutEntrySource(Layout);
|
||||||
State.Name = Layout.RgbLayout.Name ?? "";
|
await Dispatcher.UIThread.InvokeAsync(SetDeviceImages, DispatcherPriority.Background);
|
||||||
State.Summary = !string.IsNullOrWhiteSpace(Layout.RgbLayout.Vendor)
|
|
||||||
? $"{Layout.RgbLayout.Vendor} {Layout.RgbLayout.Type} device layout"
|
|
||||||
: $"{Layout.RgbLayout.Type} device layout";
|
|
||||||
|
|
||||||
State.Categories = new List<long> {8}; // Device category, yes this could change but why would it
|
|
||||||
|
|
||||||
State.Icon?.Dispose();
|
|
||||||
State.Icon = GetDeviceIcon();
|
|
||||||
|
|
||||||
State.ChangeScreen<LayoutInfoStepViewModel>();
|
State.ChangeScreen<LayoutInfoStepViewModel>();
|
||||||
}
|
}
|
||||||
|
|
||||||
private Stream GetDeviceIcon()
|
private void SetDeviceImages()
|
||||||
{
|
{
|
||||||
// Go through the hassle of resizing the image to 128x128 without losing aspect ratio, padding is added for this
|
if (Layout == null)
|
||||||
using RenderTargetBitmap image = Layout.RenderLayout(false);
|
return;
|
||||||
using MemoryStream stream = new();
|
|
||||||
image.Save(stream);
|
|
||||||
stream.Seek(0, SeekOrigin.Begin);
|
|
||||||
|
|
||||||
|
MemoryStream deviceWithoutLeds = new();
|
||||||
|
MemoryStream deviceWithLeds = new();
|
||||||
|
|
||||||
|
using (RenderTargetBitmap image = Layout.RenderLayout(false))
|
||||||
|
{
|
||||||
|
image.Save(deviceWithoutLeds);
|
||||||
|
deviceWithoutLeds.Seek(0, SeekOrigin.Begin);
|
||||||
|
}
|
||||||
|
using (RenderTargetBitmap image = Layout.RenderLayout(true))
|
||||||
|
{
|
||||||
|
image.Save(deviceWithLeds);
|
||||||
|
deviceWithLeds.Seek(0, SeekOrigin.Begin);
|
||||||
|
}
|
||||||
|
|
||||||
|
State.Icon?.Dispose();
|
||||||
|
foreach (Stream stateImage in State.Images)
|
||||||
|
stateImage.Dispose();
|
||||||
|
State.Images.Clear();
|
||||||
|
|
||||||
|
// Go through the hassle of resizing the image to 128x128 without losing aspect ratio, padding is added for this
|
||||||
|
State.Icon = ResizeImage(deviceWithoutLeds, 128);
|
||||||
|
State.Images.Add(deviceWithoutLeds);
|
||||||
|
State.Images.Add(deviceWithLeds);
|
||||||
|
}
|
||||||
|
|
||||||
|
private Stream ResizeImage(Stream image, int size)
|
||||||
|
{
|
||||||
MemoryStream output = new();
|
MemoryStream output = new();
|
||||||
using SKBitmap? sourceBitmap = SKBitmap.Decode(stream);
|
using MemoryStream input = new();
|
||||||
|
|
||||||
|
image.CopyTo(input);
|
||||||
|
input.Seek(0, SeekOrigin.Begin);
|
||||||
|
|
||||||
|
using SKBitmap? sourceBitmap = SKBitmap.Decode(input);
|
||||||
int sourceWidth = sourceBitmap.Width;
|
int sourceWidth = sourceBitmap.Width;
|
||||||
int sourceHeight = sourceBitmap.Height;
|
int sourceHeight = sourceBitmap.Height;
|
||||||
float scale = Math.Min((float) 128 / sourceWidth, (float) 128 / sourceHeight);
|
float scale = Math.Min((float) size / sourceWidth, (float) size / sourceHeight);
|
||||||
|
|
||||||
SKSizeI scaledDimensions = new((int) Math.Floor(sourceWidth * scale), (int) Math.Floor(sourceHeight * scale));
|
SKSizeI scaledDimensions = new((int) Math.Floor(sourceWidth * scale), (int) Math.Floor(sourceHeight * scale));
|
||||||
SKPointI offset = new((128 - scaledDimensions.Width) / 2, (128 - scaledDimensions.Height) / 2);
|
SKPointI offset = new((size - scaledDimensions.Width) / 2, (size - scaledDimensions.Height) / 2);
|
||||||
|
|
||||||
using SKBitmap? scaleBitmap = sourceBitmap.Resize(scaledDimensions, SKFilterQuality.High);
|
using SKBitmap? scaleBitmap = sourceBitmap.Resize(scaledDimensions, SKFilterQuality.High);
|
||||||
using SKBitmap targetBitmap = new(128, 128);
|
using SKBitmap targetBitmap = new(size, size);
|
||||||
using SKCanvas canvas = new(targetBitmap);
|
using SKCanvas canvas = new(targetBitmap);
|
||||||
canvas.Clear(SKColors.Transparent);
|
canvas.Clear(SKColors.Transparent);
|
||||||
canvas.DrawBitmap(scaleBitmap, offset.X, offset.Y);
|
canvas.DrawBitmap(scaleBitmap, offset.X, offset.Y);
|
||||||
|
|||||||
@ -57,7 +57,7 @@ public partial class SpecificationsStepViewModel : SubmissionViewModel
|
|||||||
return;
|
return;
|
||||||
|
|
||||||
ApplyToState();
|
ApplyToState();
|
||||||
State.ChangeScreen<SubmitStepViewModel>();
|
State.ChangeScreen<ImagesStepViewModel>();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void ApplyFromState()
|
private void ApplyFromState()
|
||||||
|
|||||||
@ -8,9 +8,11 @@ namespace Artemis.UI.Screens.Workshop.SubmissionWizard;
|
|||||||
|
|
||||||
public abstract partial class SubmissionViewModel : ValidatableViewModelBase
|
public abstract partial class SubmissionViewModel : ValidatableViewModelBase
|
||||||
{
|
{
|
||||||
|
[Notify] private ReactiveCommand<Unit, Unit>? _secondary;
|
||||||
[Notify] private ReactiveCommand<Unit, Unit>? _continue;
|
[Notify] private ReactiveCommand<Unit, Unit>? _continue;
|
||||||
[Notify] private ReactiveCommand<Unit, Unit>? _goBack;
|
[Notify] private ReactiveCommand<Unit, Unit>? _goBack;
|
||||||
[Notify] private string _continueText = "Continue";
|
[Notify] private string _continueText = "Continue";
|
||||||
|
[Notify] private string? _secondaryText;
|
||||||
[Notify] private bool _showFinish;
|
[Notify] private bool _showFinish;
|
||||||
[Notify] private bool _showGoBack = true;
|
[Notify] private bool _showGoBack = true;
|
||||||
[Notify] private bool _showHeader = true;
|
[Notify] private bool _showHeader = true;
|
||||||
|
|||||||
@ -17,7 +17,8 @@
|
|||||||
<Grid Margin="15" RowDefinitions="Auto,*,Auto">
|
<Grid Margin="15" RowDefinitions="Auto,*,Auto">
|
||||||
<Grid RowDefinitions="*,*" ColumnDefinitions="Auto,*,Auto" Margin="0 0 0 15">
|
<Grid RowDefinitions="*,*" ColumnDefinitions="Auto,*,Auto" Margin="0 0 0 15">
|
||||||
<ContentControl Grid.Column="0" Grid.RowSpan="2" Width="65" Height="65" VerticalAlignment="Center" Margin="0 0 20 0" Content="{CompiledBinding CurrentUserViewModel}" />
|
<ContentControl Grid.Column="0" Grid.RowSpan="2" Width="65" Height="65" VerticalAlignment="Center" Margin="0 0 20 0" Content="{CompiledBinding CurrentUserViewModel}" />
|
||||||
<TextBlock Grid.Row="0" Grid.Column="1" FontSize="36" VerticalAlignment="Bottom" Text="{CompiledBinding CurrentUserViewModel.Name}" IsVisible="{CompiledBinding !CurrentUserViewModel.IsAnonymous}"/>
|
<TextBlock Grid.Row="0" Grid.Column="1" FontSize="36" VerticalAlignment="Bottom" Text="{CompiledBinding CurrentUserViewModel.Name}"
|
||||||
|
IsVisible="{CompiledBinding !CurrentUserViewModel.IsAnonymous}" />
|
||||||
<TextBlock Grid.Row="0" Grid.Column="1" FontSize="36" VerticalAlignment="Bottom" Text="Not logged in" IsVisible="{CompiledBinding CurrentUserViewModel.IsAnonymous}" />
|
<TextBlock Grid.Row="0" Grid.Column="1" FontSize="36" VerticalAlignment="Bottom" Text="Not logged in" IsVisible="{CompiledBinding CurrentUserViewModel.IsAnonymous}" />
|
||||||
|
|
||||||
<StackPanel Grid.Row="0" Grid.Column="2" HorizontalAlignment="Right" VerticalAlignment="Bottom" Orientation="Horizontal">
|
<StackPanel Grid.Row="0" Grid.Column="2" HorizontalAlignment="Right" VerticalAlignment="Bottom" Orientation="Horizontal">
|
||||||
@ -41,6 +42,13 @@
|
|||||||
</controls:Frame>
|
</controls:Frame>
|
||||||
</Border>
|
</Border>
|
||||||
|
|
||||||
|
<Button Grid.Row="2"
|
||||||
|
Grid.Column="0"
|
||||||
|
IsVisible="{CompiledBinding Screen.Secondary, Converter={x:Static ObjectConverters.IsNotNull}}"
|
||||||
|
Margin="0 15 0 0"
|
||||||
|
Content="{CompiledBinding Screen.SecondaryText}"
|
||||||
|
Command="{CompiledBinding Screen.Secondary}"/>
|
||||||
|
|
||||||
<StackPanel Grid.Row="2" Grid.Column="0" HorizontalAlignment="Right" Orientation="Horizontal" Spacing="5" Margin="0 15 0 0">
|
<StackPanel Grid.Row="2" Grid.Column="0" HorizontalAlignment="Right" Orientation="Horizontal" Spacing="5" Margin="0 15 0 0">
|
||||||
<Button Command="{CompiledBinding Screen.GoBack}" IsVisible="{CompiledBinding Screen.ShowGoBack}">
|
<Button Command="{CompiledBinding Screen.GoBack}" IsVisible="{CompiledBinding Screen.ShowGoBack}">
|
||||||
Back
|
Back
|
||||||
|
|||||||
@ -1,10 +1,12 @@
|
|||||||
using Artemis.UI.Screens.Workshop.CurrentUser;
|
using System.Reactive.Disposables;
|
||||||
|
using Artemis.UI.Screens.Workshop.CurrentUser;
|
||||||
using Artemis.UI.Screens.Workshop.SubmissionWizard.Models;
|
using Artemis.UI.Screens.Workshop.SubmissionWizard.Models;
|
||||||
using Artemis.UI.Screens.Workshop.SubmissionWizard.Steps;
|
using Artemis.UI.Screens.Workshop.SubmissionWizard.Steps;
|
||||||
using Artemis.UI.Shared;
|
using Artemis.UI.Shared;
|
||||||
using Artemis.UI.Shared.Services;
|
using Artemis.UI.Shared.Services;
|
||||||
using DryIoc;
|
using DryIoc;
|
||||||
using PropertyChanged.SourceGenerator;
|
using PropertyChanged.SourceGenerator;
|
||||||
|
using ReactiveUI;
|
||||||
|
|
||||||
namespace Artemis.UI.Screens.Workshop.SubmissionWizard;
|
namespace Artemis.UI.Screens.Workshop.SubmissionWizard;
|
||||||
|
|
||||||
@ -26,6 +28,8 @@ public partial class SubmissionWizardViewModel : ActivatableViewModelBase, IWork
|
|||||||
WindowService = windowService;
|
WindowService = windowService;
|
||||||
CurrentUserViewModel = currentUserViewModel;
|
CurrentUserViewModel = currentUserViewModel;
|
||||||
CurrentUserViewModel.AllowLogout = false;
|
CurrentUserViewModel.AllowLogout = false;
|
||||||
|
|
||||||
|
this.WhenActivated(d => _state.DisposeWith(d));
|
||||||
}
|
}
|
||||||
|
|
||||||
public IWindowService WindowService { get; }
|
public IWindowService WindowService { get; }
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user