1
0
mirror of https://github.com/Artemis-RGB/Artemis synced 2025-12-12 21:38:38 +00:00

Workshop - Layout info WIP

This commit is contained in:
Robert 2023-10-23 19:33:38 +02:00
parent c77d51fb58
commit f4b9b67f1a
25 changed files with 361 additions and 63 deletions

View File

@ -0,0 +1,10 @@
<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:layout="clr-namespace:Artemis.UI.Screens.Workshop.Layout"
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
x:Class="Artemis.UI.Screens.Workshop.Layout.LayoutInfoView"
x:DataType="layout:LayoutInfoViewModel">
Welcome to Avalonia!
</UserControl>

View File

@ -0,0 +1,13 @@
using Avalonia;
using Avalonia.Controls;
using Avalonia.Markup.Xaml;
namespace Artemis.UI.Screens.Workshop.Layout;
public partial class LayoutInfoView : UserControl
{
public LayoutInfoView()
{
InitializeComponent();
}
}

View File

@ -0,0 +1,32 @@
using System;
using Artemis.Core;
using Artemis.UI.Screens.Workshop.SubmissionWizard.Models;
using Artemis.UI.Shared;
using PropertyChanged.SourceGenerator;
using RGB.NET.Core;
using KeyboardLayoutType = Artemis.Core.KeyboardLayoutType;
namespace Artemis.UI.Screens.Workshop.Layout;
public partial class LayoutInfoViewModel : ViewModelBase
{
[Notify] private Guid _deviceProvider;
[Notify] private string? _vendor;
[Notify] private string? _model;
[Notify] private KeyboardLayoutType? _physicalLayout;
[Notify] private string? _logicalLayout;
/// <inheritdoc />
public LayoutInfoViewModel(ArtemisLayout layout)
{
DisplayKeyboardLayout = layout.RgbLayout.Type == RGBDeviceType.Keyboard;
}
public LayoutInfoViewModel(ArtemisLayout layout, LayoutInfo layoutInfo)
{
DisplayKeyboardLayout = layout.RgbLayout.Type == RGBDeviceType.Keyboard;
}
public bool DisplayKeyboardLayout { get; }
}

View File

@ -0,0 +1,6 @@
namespace Artemis.UI.Screens.Workshop.SubmissionWizard.Models;
public interface IEntrySource
{
}

View File

@ -0,0 +1,28 @@
using System;
using System.Collections.Generic;
using Artemis.Core;
using Artemis.WebClient.Workshop;
using KeyboardLayoutType = Artemis.WebClient.Workshop.KeyboardLayoutType;
namespace Artemis.UI.Screens.Workshop.SubmissionWizard.Models;
public class LayoutEntrySource : IEntrySource
{
public LayoutEntrySource(ArtemisLayout layout)
{
Layout = layout;
}
public ArtemisLayout Layout { get; set; }
public List<LayoutInfo> LayoutInfo { get; } = new();
}
public class LayoutInfo
{
public Guid DeviceProvider { get; set; }
public RGBDeviceType DeviceType { get; set; }
public string Model { get; set; }
public string Vendor { get; set; }
public string? LogicalLayout { get; set; }
public KeyboardLayoutType? PhysicalLayout { get; set; }
}

View File

@ -0,0 +1,13 @@
using Artemis.Core;
namespace Artemis.UI.Screens.Workshop.SubmissionWizard.Models;
public class ProfileEntrySource : IEntrySource
{
public ProfileEntrySource(ProfileConfiguration profileConfiguration)
{
ProfileConfiguration = profileConfiguration;
}
public ProfileConfiguration ProfileConfiguration { get; set; }
}

View File

@ -7,7 +7,7 @@ using Artemis.UI.Shared.Services;
using Artemis.WebClient.Workshop;
using DryIoc;
namespace Artemis.UI.Screens.Workshop.SubmissionWizard;
namespace Artemis.UI.Screens.Workshop.SubmissionWizard.Models;
public class SubmissionWizardState
{
@ -34,7 +34,7 @@ public class SubmissionWizardState
public List<string> Tags { get; set; } = new();
public List<Stream> Images { get; set; } = new();
public object? EntrySource { get; set; }
public IEntrySource? EntrySource { get; set; }
public void ChangeScreen<TSubmissionViewModel>() where TSubmissionViewModel : SubmissionViewModel
{

View File

@ -1,4 +1,5 @@
using Artemis.UI.Screens.Workshop.CurrentUser;
using Artemis.UI.Screens.Workshop.SubmissionWizard.Models;
using Artemis.UI.Shared;
using Artemis.UI.Shared.Services;
using Artemis.WebClient.Workshop;

View File

@ -0,0 +1,43 @@
<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:layout="clr-namespace:Artemis.UI.Screens.Workshop.SubmissionWizard.Steps.Layout"
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
x:Class="Artemis.UI.Screens.Workshop.SubmissionWizard.Steps.Layout.LayoutInfoStepView"
x:DataType="layout:LayoutInfoStepViewModel">
<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 information
</TextBlock>
<TextBlock TextWrapping="Wrap">
The information below is used by Artemis to automatically find your layout.
Some layouts can be shared across different devices and here you have a chance to set that up.
</TextBlock>
</StackPanel>
<ScrollViewer Grid.Row="1"
Grid.Column="0"
Margin="0 10 0 0"
Classes="with-padding"
HorizontalScrollBarVisibility="Disabled"
VerticalScrollBarVisibility="Auto"
VerticalAlignment="Top">
<ItemsRepeater ItemsSource="{CompiledBinding LayoutInfo}">
<ItemsRepeater.ItemTemplate>
<DataTemplate>
<ContentControl Content="{CompiledBinding}" />
</DataTemplate>
</ItemsRepeater.ItemTemplate>
</ItemsRepeater>
</ScrollViewer>
</Grid>
</UserControl>

View File

@ -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 LayoutInfoStepView : ReactiveUserControl<LayoutInfoStepViewModel>
{
public LayoutInfoStepView()
{
InitializeComponent();
}
}

View File

@ -0,0 +1,25 @@
using System.Collections.ObjectModel;
using System.Linq;
using System.Reactive.Disposables;
using Artemis.UI.Screens.Workshop.Layout;
using Artemis.UI.Screens.Workshop.SubmissionWizard.Models;
using DynamicData;
using ReactiveUI;
namespace Artemis.UI.Screens.Workshop.SubmissionWizard.Steps.Layout;
public class LayoutInfoStepViewModel : SubmissionViewModel
{
public LayoutInfoStepViewModel()
{
GoBack = ReactiveCommand.Create(() => State.ChangeScreen<LayoutSelectionStepViewModel>());
this.WhenActivated((CompositeDisposable _) =>
{
LayoutInfo.Clear();
if (State.EntrySource is LayoutEntrySource layoutEntrySource)
LayoutInfo.AddRange(layoutEntrySource.LayoutInfo.Select(i => new LayoutInfoViewModel(layoutEntrySource.Layout, i)));
});
}
public ObservableCollection<LayoutInfoViewModel> LayoutInfo { get; } = new();
}

View File

@ -4,22 +4,9 @@
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>

View File

@ -5,16 +5,15 @@ using Artemis.Core.Services;
using PropertyChanged.SourceGenerator;
using ReactiveUI;
using System;
using System.Collections.Generic;
using System.IO;
using System.Reactive.Disposables;
using System.Reactive.Linq;
using System.Threading.Tasks;
using Artemis.UI.Extensions;
using Artemis.UI.Screens.Workshop.SubmissionWizard.Models;
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;
@ -36,10 +35,20 @@ public partial class LayoutSelectionStepViewModel : SubmissionViewModel
.OrderBy(d => d.RgbDevice.DeviceInfo.Model)
);
GoBack = ReactiveCommand.Create(() => State.ChangeScreen<EntryTypeStepViewModel>());
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);
this.WhenActivated((CompositeDisposable _) =>
{
ShowGoBack = State.EntryId == null;
if (State.EntrySource is not LayoutEntrySource layoutEntrySource)
return;
Layout = layoutEntrySource.Layout;
SelectedDevice = Devices.FirstOrDefault(d => d.Layout == Layout);
});
}
public ObservableCollection<ArtemisDevice> Devices { get; }
@ -78,12 +87,22 @@ public partial class LayoutSelectionStepViewModel : SubmissionViewModel
if (Layout == null)
return;
State.EntrySource = Layout;
State.EntrySource = new LayoutEntrySource(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";
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>();
}
private Stream GetDeviceIcon()
{
// 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();
@ -99,18 +118,14 @@ public partial class LayoutSelectionStepViewModel : SubmissionViewModel
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);
}
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>();
return output;
}
}

View File

@ -7,6 +7,7 @@ using System.Threading.Tasks;
using Artemis.Core;
using Artemis.Core.Services;
using Artemis.UI.Screens.ProfileEditor.ProfileTree.Dialogs;
using Artemis.UI.Screens.Workshop.SubmissionWizard.Models;
using Artemis.UI.Shared.Services;
using DynamicData;
using DynamicData.Aggregation;
@ -34,12 +35,14 @@ public class ProfileAdaptionHintsStepViewModel : SubmissionViewModel
this.WhenActivated((CompositeDisposable _) =>
{
if (State.EntrySource is ProfileConfiguration profileConfiguration && profileConfiguration.Profile != null)
_layers.Edit(l =>
{
l.Clear();
l.AddRange(profileConfiguration.Profile.GetAllLayers().Select(getLayerViewModel));
});
if (State.EntrySource is not ProfileEntrySource profileEntrySource || profileEntrySource.ProfileConfiguration.Profile == null)
return;
_layers.Edit(l =>
{
l.Clear();
l.AddRange(profileEntrySource.ProfileConfiguration.Profile.GetAllLayers().Select(getLayerViewModel));
});
});
}

View File

@ -7,6 +7,7 @@ using Artemis.Core;
using Artemis.Core.Services;
using Artemis.UI.Extensions;
using Artemis.UI.Screens.Workshop.Profile;
using Artemis.UI.Screens.Workshop.SubmissionWizard.Models;
using Material.Icons;
using PropertyChanged.SourceGenerator;
using ReactiveUI;
@ -38,8 +39,8 @@ public partial class ProfileSelectionStepViewModel : SubmissionViewModel
this.WhenActivated((CompositeDisposable _) =>
{
ShowGoBack = State.EntryId == null;
if (State.EntrySource is ProfileConfiguration profileConfiguration)
SelectedProfile = Profiles.FirstOrDefault(p => p.ProfileId == profileConfiguration.ProfileId);
if (State.EntrySource is ProfileEntrySource profileEntrySource)
SelectedProfile = Profiles.FirstOrDefault(p => p.ProfileId == profileEntrySource.ProfileConfiguration.ProfileId);
});
}
@ -66,7 +67,7 @@ public partial class ProfileSelectionStepViewModel : SubmissionViewModel
if (SelectedProfile == null)
return;
State.EntrySource = SelectedProfile;
State.EntrySource = new ProfileEntrySource(SelectedProfile);
State.Name = SelectedProfile.Name;
State.Icon = SelectedProfile.Icon.GetIconStream();

View File

@ -39,7 +39,7 @@ public partial class SpecificationsStepViewModel : SubmissionViewModel
switch (State.EntryType)
{
case EntryType.Layout:
State.ChangeScreen<LayoutSelectionStepViewModel>();
State.ChangeScreen<LayoutInfoStepViewModel>();
break;
case EntryType.Plugin:
break;

View File

@ -1,4 +1,5 @@
using System.Reactive;
using Artemis.UI.Screens.Workshop.SubmissionWizard.Models;
using Artemis.UI.Shared;
using PropertyChanged.SourceGenerator;
using ReactiveUI;

View File

@ -1,4 +1,5 @@
using Artemis.UI.Screens.Workshop.CurrentUser;
using Artemis.UI.Screens.Workshop.SubmissionWizard.Models;
using Artemis.UI.Screens.Workshop.SubmissionWizard.Steps;
using Artemis.UI.Shared;
using Artemis.UI.Shared.Services;

View File

@ -0,0 +1,23 @@
fragment category on Category {
name
icon
}
fragment layoutInfo on LayoutInfo {
id
deviceProvider
deviceType
model
vendor
logicalLayout
physicalLayout
}
fragment submittedEntry on Entry {
id
name
summary
entryType
downloads
createdAt
}

View File

@ -10,8 +10,7 @@ query GetEntries($search: String $filter: EntryFilterInput $skip: Int $take: Int
downloads
createdAt
categories {
name
icon
...category
}
}
}

View File

@ -9,8 +9,7 @@ query GetEntryById($id: Long!) {
createdAt
description
categories {
name
icon
...category
}
latestRelease {
id

View File

@ -1,14 +1,8 @@
query GetSubmittedEntries($filter: EntryFilterInput) {
submittedEntries(where: $filter order: {createdAt: DESC}) {
id
name
summary
entryType
downloads
createdAt
...submittedEntry
categories {
name
icon
...category
}
}
}

View File

@ -1,11 +1,6 @@
query GetSubmittedEntryById($id: Long!) {
entry(id: $id) {
id
name
summary
entryType
downloads
createdAt
...submittedEntry
description
categories {
id
@ -13,5 +8,8 @@ query GetSubmittedEntryById($id: Long!) {
tags {
name
}
layoutInfo {
...layoutInfo
}
}
}

View File

@ -6,9 +6,7 @@ query SearchEntries($input: String! $type: EntryType) {
entryType
author
categories {
id
name
icon
...category
}
}
}

View File

@ -42,6 +42,7 @@ type Entry {
images: [Image!]!
latestRelease: Release
latestReleaseId: Long
layoutInfo: [LayoutInfo!]!
name: String!
releases: [Release!]!
summary: String!
@ -53,9 +54,23 @@ type Image {
mimeType: String!
}
type LayoutInfo {
deviceProvider: UUID!
deviceType: RGBDeviceType!
entry: Entry!
entryId: Long!
id: Long!
logicalLayout: String
model: String!
physicalLayout: KeyboardLayoutType
vendor: String!
}
type Mutation {
addEntry(input: CreateEntryInput!): Entry
addLayoutInfo(input: CreateLayoutInfoInput!): LayoutInfo
removeEntry(id: Long!): Entry
removeLayoutInfo(id: Long!): LayoutInfo!
updateEntry(input: UpdateEntryInput!): Entry
}
@ -64,6 +79,8 @@ type Query {
entries(order: [EntrySortInput!], search: String, skip: Int, take: Int, where: EntryFilterInput): EntriesCollectionSegment
entry(id: Long!): Entry
searchEntries(input: String!, order: [EntrySortInput!], type: EntryType, where: EntryFilterInput): [Entry!]!
searchKeyboardLayout(deviceProvider: UUID!, logicalLayout: String, model: String!, physicalLayout: KeyboardLayoutType!, vendor: String!): LayoutInfo
searchLayout(deviceProvider: UUID!, deviceType: RGBDeviceType!, model: String!, vendor: String!): LayoutInfo
submittedEntries(order: [EntrySortInput!], where: EntryFilterInput): [Entry!]!
}
@ -95,6 +112,37 @@ enum EntryType {
PROFILE
}
enum KeyboardLayoutType {
ABNT
ANSI
ISO
JIS
KS
UNKNOWN
}
enum RGBDeviceType {
ALL
COOLER
DRAM
FAN
GRAPHICS_CARD
HEADSET
HEADSET_STAND
KEYBOARD
KEYPAD
LED_CONTROLLER
LED_MATRIX
LED_STRIPE
MAINBOARD
MONITOR
MOUSE
MOUSEPAD
NONE
SPEAKER
UNKNOWN
}
enum SortEnumType {
ASC
DESC
@ -131,6 +179,16 @@ input CreateEntryInput {
tags: [String!]!
}
input CreateLayoutInfoInput {
deviceProvider: UUID!
deviceType: RGBDeviceType!
entryId: Long!
logicalLayout: String
model: String!
physicalLayout: KeyboardLayoutType
vendor: String!
}
input DateTimeOperationFilterInput {
eq: DateTime
gt: DateTime
@ -161,6 +219,7 @@ input EntryFilterInput {
images: ListFilterInputTypeOfImageFilterInput
latestRelease: ReleaseFilterInput
latestReleaseId: LongOperationFilterInput
layoutInfo: ListFilterInputTypeOfLayoutInfoFilterInput
name: StringOperationFilterInput
or: [EntryFilterInput!]
releases: ListFilterInputTypeOfReleaseFilterInput
@ -203,6 +262,20 @@ input ImageSortInput {
mimeType: SortEnumType
}
input LayoutInfoFilterInput {
and: [LayoutInfoFilterInput!]
deviceProvider: UuidOperationFilterInput
deviceType: RGBDeviceTypeOperationFilterInput
entry: EntryFilterInput
entryId: LongOperationFilterInput
id: LongOperationFilterInput
logicalLayout: StringOperationFilterInput
model: StringOperationFilterInput
or: [LayoutInfoFilterInput!]
physicalLayout: NullableOfKeyboardLayoutTypeOperationFilterInput
vendor: StringOperationFilterInput
}
input ListFilterInputTypeOfCategoryFilterInput {
all: CategoryFilterInput
any: Boolean
@ -217,6 +290,13 @@ input ListFilterInputTypeOfImageFilterInput {
some: ImageFilterInput
}
input ListFilterInputTypeOfLayoutInfoFilterInput {
all: LayoutInfoFilterInput
any: Boolean
none: LayoutInfoFilterInput
some: LayoutInfoFilterInput
}
input ListFilterInputTypeOfReleaseFilterInput {
all: ReleaseFilterInput
any: Boolean
@ -246,6 +326,20 @@ input LongOperationFilterInput {
nlte: Long
}
input NullableOfKeyboardLayoutTypeOperationFilterInput {
eq: KeyboardLayoutType
in: [KeyboardLayoutType]
neq: KeyboardLayoutType
nin: [KeyboardLayoutType]
}
input RGBDeviceTypeOperationFilterInput {
eq: RGBDeviceType
in: [RGBDeviceType!]
neq: RGBDeviceType
nin: [RGBDeviceType!]
}
input ReleaseFilterInput {
and: [ReleaseFilterInput!]
createdAt: DateTimeOperationFilterInput