1
0
mirror of https://github.com/Artemis-RGB/Artemis synced 2025-12-13 05:48:35 +00:00

Merge branch 'development'

This commit is contained in:
Robert 2024-01-16 08:39:51 +01:00
commit ff6efe9456
273 changed files with 4428 additions and 3050 deletions

View File

@ -1,6 +1,4 @@
<Project Sdk="Microsoft.NET.Sdk"> <Project Sdk="Microsoft.NET.Sdk">
<Import Project="..\Artemis.props" />
<PropertyGroup> <PropertyGroup>
<TargetFramework>net7.0</TargetFramework> <TargetFramework>net7.0</TargetFramework>
<PreserveCompilationContext>false</PreserveCompilationContext> <PreserveCompilationContext>false</PreserveCompilationContext>
@ -38,27 +36,20 @@
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="DryIoc.dll" Version="5.4.1" /> <PackageReference Include="DryIoc.dll" Version="5.4.3" />
<PackageReference Include="EmbedIO" Version="3.5.2" /> <PackageReference Include="EmbedIO" Version="3.5.2" />
<PackageReference Include="HidSharp" Version="2.1.0" /> <PackageReference Include="HidSharp" Version="2.1.0" />
<PackageReference Include="Humanizer.Core" Version="2.14.1" /> <PackageReference Include="Humanizer.Core" Version="2.14.1" />
<PackageReference Include="JetBrains.Annotations" Version="2023.2.0" /> <PackageReference Include="JetBrains.Annotations" Version="2023.3.0" />
<PackageReference Include="LiteDB" Version="5.0.17" />
<PackageReference Include="McMaster.NETCore.Plugins" Version="1.4.0" /> <PackageReference Include="McMaster.NETCore.Plugins" Version="1.4.0" />
<PackageReference Include="Newtonsoft.Json" Version="13.0.3" /> <PackageReference Include="Newtonsoft.Json" Version="13.0.3" />
<PackageReference Include="RGB.NET.Core" Version="$(RGBDotNetVersion)" /> <PackageReference Include="RGB.NET.Core" Version="2.0.4-prerelease.1" />
<PackageReference Include="RGB.NET.Layout" Version="$(RGBDotNetVersion)" /> <PackageReference Include="RGB.NET.Layout" Version="2.0.4-prerelease.1" />
<PackageReference Include="RGB.NET.Presets" Version="$(RGBDotNetVersion)" /> <PackageReference Include="RGB.NET.Presets" Version="2.0.4-prerelease.1" />
<PackageReference Include="Serilog" Version="3.0.1" /> <PackageReference Include="Serilog.Sinks.Console" Version="5.0.1" />
<PackageReference Include="Serilog.Sinks.Console" Version="4.1.0" />
<PackageReference Include="Serilog.Sinks.Debug" Version="2.0.0" /> <PackageReference Include="Serilog.Sinks.Debug" Version="2.0.0" />
<PackageReference Include="Serilog.Sinks.File" Version="5.0.0" /> <PackageReference Include="Serilog.Sinks.File" Version="5.0.0" />
<PackageReference Include="SkiaSharp" Version="$(SkiaSharpVersion)" /> <PackageReference Include="SkiaSharp" Version="2.88.7" />
<PackageReference Include="System.Buffers" Version="4.5.1" />
<PackageReference Include="System.IO.FileSystem.AccessControl" Version="5.0.0" />
<PackageReference Include="System.Numerics.Vectors" Version="4.5.0" />
<PackageReference Include="System.ValueTuple" Version="4.5.0" />
<PackageReference Include="Unosquare.Swan.Lite" Version="3.1.0" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>

View File

@ -47,6 +47,7 @@ public static class Constants
/// The full path to the Artemis logs folder /// The full path to the Artemis logs folder
/// </summary> /// </summary>
public static readonly string LogsFolder = Path.Combine(DataFolder, "Logs"); public static readonly string LogsFolder = Path.Combine(DataFolder, "Logs");
/// <summary> /// <summary>
/// The full path to the Artemis logs folder /// The full path to the Artemis logs folder
/// </summary> /// </summary>
@ -62,6 +63,11 @@ public static class Constants
/// </summary> /// </summary>
public static readonly string LayoutsFolder = Path.Combine(DataFolder, "User Layouts"); public static readonly string LayoutsFolder = Path.Combine(DataFolder, "User Layouts");
/// <summary>
/// The full path to the Artemis user layouts folder
/// </summary>
public static readonly string WorkshopFolder = Path.Combine(DataFolder, "workshop");
/// <summary> /// <summary>
/// The current API version for plugins /// The current API version for plugins
/// </summary> /// </summary>
@ -71,9 +77,9 @@ public static class Constants
/// <summary> /// <summary>
/// The current version of the application /// The current version of the application
/// </summary> /// </summary>
public static readonly string CurrentVersion = CoreAssembly.GetCustomAttribute<AssemblyInformationalVersionAttribute>()!.InformationalVersion != "1.0.0" public static readonly string CurrentVersion = CoreAssembly.GetCustomAttribute<AssemblyInformationalVersionAttribute>()!.InformationalVersion.StartsWith("1.0.0")
? CoreAssembly.GetCustomAttribute<AssemblyInformationalVersionAttribute>()!.InformationalVersion ? "local"
: "local"; : CoreAssembly.GetCustomAttribute<AssemblyInformationalVersionAttribute>()!.InformationalVersion;
/// <summary> /// <summary>
/// The plugin info used by core components of Artemis /// The plugin info used by core components of Artemis
@ -151,8 +157,8 @@ public static class Constants
public static ReadOnlyCollection<string> StartupArguments { get; set; } = null!; public static ReadOnlyCollection<string> StartupArguments { get; set; } = null!;
/// <summary> /// <summary>
/// Gets the graphics context to be used for rendering by SkiaSharp. Can be set via /// Gets the graphics context to be used for rendering by SkiaSharp.
/// <see cref="IRgbService.UpdateGraphicsContext" />.
/// </summary> /// </summary>
public static IManagedGraphicsContext? ManagedGraphicsContext { get; internal set; } public static IManagedGraphicsContext? ManagedGraphicsContext { get; internal set; }
} }

View File

@ -2,6 +2,7 @@ using System;
using System.Linq; using System.Linq;
using System.Reflection; using System.Reflection;
using Artemis.Core.DryIoc.Factories; using Artemis.Core.DryIoc.Factories;
using Artemis.Core.Providers;
using Artemis.Core.Services; using Artemis.Core.Services;
using Artemis.Storage; using Artemis.Storage;
using Artemis.Storage.Migrations.Interfaces; using Artemis.Storage.Migrations.Interfaces;
@ -36,6 +37,7 @@ public static class ContainerExtensions
// Bind migrations // Bind migrations
container.RegisterMany(storageAssembly, type => type.IsAssignableTo<IStorageMigration>(), Reuse.Singleton, nonPublicServiceTypes: true); container.RegisterMany(storageAssembly, type => type.IsAssignableTo<IStorageMigration>(), Reuse.Singleton, nonPublicServiceTypes: true);
container.RegisterMany(coreAssembly, type => type.IsAssignableTo<ILayoutProvider>(), Reuse.Singleton);
container.Register<IPluginSettingsFactory, PluginSettingsFactory>(Reuse.Singleton); container.Register<IPluginSettingsFactory, PluginSettingsFactory>(Reuse.Singleton);
container.Register(Made.Of(_ => ServiceInfo.Of<IPluginSettingsFactory>(), f => f.CreatePluginSettings(Arg.Index<Type>(0)), r => r.Parent.ImplementationType)); container.Register(Made.Of(_ => ServiceInfo.Of<IPluginSettingsFactory>(), f => f.CreatePluginSettings(Arg.Index<Type>(0)), r => r.Parent.ImplementationType));
container.Register<ILoggerFactory, LoggerFactory>(Reuse.Singleton); container.Register<ILoggerFactory, LoggerFactory>(Reuse.Singleton);

View File

@ -1,31 +0,0 @@
using System.IO;
namespace Artemis.Core;
internal static class DirectoryInfoExtensions
{
public static void CopyFilesRecursively(this DirectoryInfo source, DirectoryInfo target)
{
foreach (DirectoryInfo dir in source.GetDirectories())
CopyFilesRecursively(dir, target.CreateSubdirectory(dir.Name));
foreach (FileInfo file in source.GetFiles())
file.CopyTo(Path.Combine(target.FullName, file.Name));
}
public static void DeleteRecursively(this DirectoryInfo baseDir)
{
if (!baseDir.Exists)
return;
foreach (DirectoryInfo dir in baseDir.EnumerateDirectories())
DeleteRecursively(dir);
FileInfo[] files = baseDir.GetFiles();
foreach (FileInfo file in files)
{
file.IsReadOnly = false;
file.Delete();
}
baseDir.Delete();
}
}

View File

@ -23,7 +23,6 @@ public class ProfileConfiguration : BreakableModel, IStorageModel, IDisposable
private bool _disposed; private bool _disposed;
private Hotkey? _enableHotkey; private Hotkey? _enableHotkey;
private ProfileConfigurationHotkeyMode _hotkeyMode; private ProfileConfigurationHotkeyMode _hotkeyMode;
private bool _isBeingEdited;
private bool _isMissingModule; private bool _isMissingModule;
private bool _isSuspended; private bool _isSuspended;
private bool _fadeInAndOut; private bool _fadeInAndOut;

View File

@ -2,9 +2,10 @@
using System.Collections.Generic; using System.Collections.Generic;
using System.Collections.ObjectModel; using System.Collections.ObjectModel;
using System.ComponentModel; using System.ComponentModel;
using System.Diagnostics;
using System.Linq; using System.Linq;
using Artemis.Core.DeviceProviders; using Artemis.Core.DeviceProviders;
using Artemis.Core.Providers;
using Artemis.Core.Services;
using Artemis.Storage.Entities.Surface; using Artemis.Storage.Entities.Surface;
using RGB.NET.Core; using RGB.NET.Core;
using SkiaSharp; using SkiaSharp;
@ -45,6 +46,7 @@ public class ArtemisDevice : CorePropertyChanged
InputIdentifiers = new List<ArtemisDeviceInputIdentifier>(); InputIdentifiers = new List<ArtemisDeviceInputIdentifier>();
InputMappings = new Dictionary<ArtemisLed, ArtemisLed>(); InputMappings = new Dictionary<ArtemisLed, ArtemisLed>();
Categories = new HashSet<DeviceCategory>(); Categories = new HashSet<DeviceCategory>();
LayoutSelection = new LayoutSelection {Type = DefaultLayoutProvider.LayoutType};
RgbDevice.ColorCorrections.Clear(); RgbDevice.ColorCorrections.Clear();
RgbDevice.ColorCorrections.Add(new ScaleColorCorrection(this)); RgbDevice.ColorCorrections.Add(new ScaleColorCorrection(this));
@ -73,6 +75,7 @@ public class ArtemisDevice : CorePropertyChanged
InputIdentifiers = new List<ArtemisDeviceInputIdentifier>(); InputIdentifiers = new List<ArtemisDeviceInputIdentifier>();
InputMappings = new Dictionary<ArtemisLed, ArtemisLed>(); InputMappings = new Dictionary<ArtemisLed, ArtemisLed>();
Categories = new HashSet<DeviceCategory>(); Categories = new HashSet<DeviceCategory>();
LayoutSelection = new LayoutSelection {Type = DefaultLayoutProvider.LayoutType};
foreach (DeviceInputIdentifierEntity identifierEntity in DeviceEntity.InputIdentifiers) foreach (DeviceInputIdentifierEntity identifierEntity in DeviceEntity.InputIdentifiers)
InputIdentifiers.Add(new ArtemisDeviceInputIdentifier(identifierEntity.InputProvider, identifierEntity.Identifier)); InputIdentifiers.Add(new ArtemisDeviceInputIdentifier(identifierEntity.InputProvider, identifierEntity.Identifier));
@ -152,6 +155,8 @@ public class ArtemisDevice : CorePropertyChanged
/// </summary> /// </summary>
public HashSet<DeviceCategory> Categories { get; } public HashSet<DeviceCategory> Categories { get; }
public LayoutSelection LayoutSelection { get; }
/// <summary> /// <summary>
/// Gets or sets the X-position of the device /// Gets or sets the X-position of the device
/// </summary> /// </summary>
@ -266,7 +271,7 @@ public class ArtemisDevice : CorePropertyChanged
/// <summary> /// <summary>
/// Gets a boolean indicating whether this devices is enabled or not /// Gets a boolean indicating whether this devices is enabled or not
/// <para>Note: To enable/disable a device use the methods provided by <see cref="IRgbService" /></para> /// <para>Note: To enable/disable a device use the methods provided by <see cref="IDeviceService" /></para>
/// </summary> /// </summary>
public bool IsEnabled public bool IsEnabled
{ {
@ -292,19 +297,6 @@ public class ArtemisDevice : CorePropertyChanged
} }
} }
/// <summary>
/// Gets or sets a boolean indicating whether falling back to default layouts is enabled or not
/// </summary>
public bool DisableDefaultLayout
{
get => DeviceEntity.DisableDefaultLayout;
set
{
DeviceEntity.DisableDefaultLayout = value;
OnPropertyChanged(nameof(DisableDefaultLayout));
}
}
/// <summary> /// <summary>
/// Gets or sets the logical layout of the device e.g. DE, UK or US. /// Gets or sets the logical layout of the device e.g. DE, UK or US.
/// <para>Only applicable to keyboards</para> /// <para>Only applicable to keyboards</para>
@ -319,20 +311,6 @@ public class ArtemisDevice : CorePropertyChanged
} }
} }
/// <summary>
/// Gets or sets the path of the custom layout to load when calling <see cref="IRgbService.ApplyBestDeviceLayout" />
/// for this device
/// </summary>
public string? CustomLayoutPath
{
get => DeviceEntity.CustomLayoutPath;
set
{
DeviceEntity.CustomLayoutPath = value;
OnPropertyChanged(nameof(CustomLayoutPath));
}
}
/// <summary> /// <summary>
/// Gets the layout of the device expanded with Artemis-specific data /// Gets the layout of the device expanded with Artemis-specific data
/// </summary> /// </summary>
@ -380,40 +358,6 @@ public class ArtemisDevice : CorePropertyChanged
return artemisLed; return artemisLed;
} }
/// <summary>
/// Returns the most preferred device layout for this device.
/// </summary>
/// <returns>The most preferred device layout for this device.</returns>
public ArtemisLayout? GetBestDeviceLayout()
{
ArtemisLayout? layout;
// Configured layout path takes precedence over all other options
if (CustomLayoutPath != null)
{
layout = new ArtemisLayout(CustomLayoutPath, LayoutSource.Configured);
if (layout.IsValid)
return layout;
}
// Look for a layout provided by the user
layout = DeviceProvider.LoadUserLayout(this);
if (layout.IsValid)
return layout;
if (DisableDefaultLayout)
return null;
// Look for a layout provided by the plugin
layout = DeviceProvider.LoadLayout(this);
if (layout.IsValid)
return layout;
// Finally fall back to a default layout
layout = ArtemisLayout.GetDefaultLayout(this);
return layout;
}
/// <summary> /// <summary>
/// Occurs when the underlying RGB.NET device was updated /// Occurs when the underlying RGB.NET device was updated
/// </summary> /// </summary>
@ -454,14 +398,6 @@ public class ArtemisDevice : CorePropertyChanged
} }
} }
/// <summary>
/// Invokes the <see cref="DeviceUpdated" /> event
/// </summary>
protected virtual void OnDeviceUpdated()
{
DeviceUpdated?.Invoke(this, EventArgs.Empty);
}
/// <summary> /// <summary>
/// Applies the provided layout to the device /// Applies the provided layout to the device
/// </summary> /// </summary>
@ -474,7 +410,7 @@ public class ArtemisDevice : CorePropertyChanged
/// A boolean indicating whether to remove excess LEDs present in the device but missing /// A boolean indicating whether to remove excess LEDs present in the device but missing
/// in the layout /// in the layout
/// </param> /// </param>
internal void ApplyLayout(ArtemisLayout? layout, bool createMissingLeds, bool removeExcessiveLeds) public void ApplyLayout(ArtemisLayout? layout, bool createMissingLeds, bool removeExcessiveLeds)
{ {
if (layout != null && layout.IsValid && createMissingLeds && !DeviceProvider.CreateMissingLedsSupported) if (layout != null && layout.IsValid && createMissingLeds && !DeviceProvider.CreateMissingLedsSupported)
throw new ArtemisCoreException($"Cannot apply layout with {nameof(createMissingLeds)} set to true because the device provider does not support it"); throw new ArtemisCoreException($"Cannot apply layout with {nameof(createMissingLeds)} set to true because the device provider does not support it");
@ -501,6 +437,14 @@ public class ArtemisDevice : CorePropertyChanged
CalculateRenderProperties(); CalculateRenderProperties();
} }
/// <summary>
/// Invokes the <see cref="DeviceUpdated" /> event
/// </summary>
protected virtual void OnDeviceUpdated()
{
DeviceUpdated?.Invoke(this, EventArgs.Empty);
}
private void ClearLayout() private void ClearLayout()
{ {
if (Layout == null) if (Layout == null)
@ -533,6 +477,9 @@ public class ArtemisDevice : CorePropertyChanged
DeviceEntity.Categories.Clear(); DeviceEntity.Categories.Clear();
foreach (DeviceCategory deviceCategory in Categories) foreach (DeviceCategory deviceCategory in Categories)
DeviceEntity.Categories.Add((int) deviceCategory); DeviceEntity.Categories.Add((int) deviceCategory);
DeviceEntity.LayoutType = LayoutSelection.Type;
DeviceEntity.LayoutParameter = LayoutSelection.Parameter;
} }
internal void Load() internal void Load()
@ -547,6 +494,9 @@ public class ArtemisDevice : CorePropertyChanged
if (!Categories.Any()) if (!Categories.Any())
ApplyDefaultCategories(); ApplyDefaultCategories();
LayoutSelection.Type = DeviceEntity.LayoutType;
LayoutSelection.Parameter = DeviceEntity.LayoutParameter;
LoadInputMappings(); LoadInputMappings();
} }

View File

@ -12,16 +12,17 @@ namespace Artemis.Core;
/// </summary> /// </summary>
public class ArtemisLayout public class ArtemisLayout
{ {
private static readonly string DefaultLayoutPath = Path.Combine(Constants.ApplicationFolder, "DefaultLayouts", "Artemis");
/// <summary> /// <summary>
/// Creates a new instance of the <see cref="ArtemisLayout" /> class /// Creates a new instance of the <see cref="ArtemisLayout" /> class
/// </summary> /// </summary>
/// <param name="filePath">The path of the layout XML file</param> /// <param name="filePath">The path of the layout XML file</param>
/// <param name="source">The source from where this layout is being loaded</param> public ArtemisLayout(string filePath)
public ArtemisLayout(string filePath, LayoutSource source)
{ {
FilePath = filePath; FilePath = filePath;
Source = source;
Leds = new List<ArtemisLedLayout>(); Leds = new List<ArtemisLedLayout>();
IsDefaultLayout = filePath.StartsWith(DefaultLayoutPath);
LoadLayout(); LoadLayout();
} }
@ -31,11 +32,6 @@ public class ArtemisLayout
/// </summary> /// </summary>
public string FilePath { get; } public string FilePath { get; }
/// <summary>
/// Gets the source from where this layout was loaded
/// </summary>
public LayoutSource Source { get; }
/// <summary> /// <summary>
/// Gets a boolean indicating whether a valid layout was loaded /// Gets a boolean indicating whether a valid layout was loaded
/// </summary> /// </summary>
@ -61,6 +57,8 @@ public class ArtemisLayout
/// </summary> /// </summary>
public LayoutCustomDeviceData LayoutCustomDeviceData { get; private set; } = null!; public LayoutCustomDeviceData LayoutCustomDeviceData { get; private set; } = null!;
public bool IsDefaultLayout { get; private set; }
/// <summary> /// <summary>
/// Applies the layout to the provided device /// Applies the layout to the provided device
/// </summary> /// </summary>
@ -121,29 +119,28 @@ public class ArtemisLayout
internal static ArtemisLayout? GetDefaultLayout(ArtemisDevice device) internal static ArtemisLayout? GetDefaultLayout(ArtemisDevice device)
{ {
string layoutFolder = Path.Combine(Constants.ApplicationFolder, "DefaultLayouts", "Artemis");
if (device.DeviceType == RGBDeviceType.Keyboard) if (device.DeviceType == RGBDeviceType.Keyboard)
{ {
// XL layout is defined by its programmable macro keys // XL layout is defined by its programmable macro keys
if (device.Leds.Any(l => l.RgbLed.Id >= LedId.Keyboard_Programmable1 && l.RgbLed.Id <= LedId.Keyboard_Programmable32)) if (device.Leds.Any(l => l.RgbLed.Id >= LedId.Keyboard_Programmable1 && l.RgbLed.Id <= LedId.Keyboard_Programmable32))
{ {
if (device.PhysicalLayout == KeyboardLayoutType.ANSI) if (device.PhysicalLayout == KeyboardLayoutType.ANSI)
return new ArtemisLayout(Path.Combine(layoutFolder, "Keyboard", "Artemis XL keyboard-ANSI.xml"), LayoutSource.Default); return new ArtemisLayout(Path.Combine(DefaultLayoutPath, "Keyboard", "Artemis XL keyboard-ANSI.xml"));
return new ArtemisLayout(Path.Combine(layoutFolder, "Keyboard", "Artemis XL keyboard-ISO.xml"), LayoutSource.Default); return new ArtemisLayout(Path.Combine(DefaultLayoutPath, "Keyboard", "Artemis XL keyboard-ISO.xml"));
} }
// L layout is defined by its numpad // L layout is defined by its numpad
if (device.Leds.Any(l => l.RgbLed.Id >= LedId.Keyboard_NumLock && l.RgbLed.Id <= LedId.Keyboard_NumPeriodAndDelete)) if (device.Leds.Any(l => l.RgbLed.Id >= LedId.Keyboard_NumLock && l.RgbLed.Id <= LedId.Keyboard_NumPeriodAndDelete))
{ {
if (device.PhysicalLayout == KeyboardLayoutType.ANSI) if (device.PhysicalLayout == KeyboardLayoutType.ANSI)
return new ArtemisLayout(Path.Combine(layoutFolder + "Keyboard", "Artemis L keyboard-ANSI.xml"), LayoutSource.Default); return new ArtemisLayout(Path.Combine(DefaultLayoutPath, "Keyboard", "Artemis L keyboard-ANSI.xml"));
return new ArtemisLayout(Path.Combine(layoutFolder + "Keyboard", "Artemis L keyboard-ISO.xml"), LayoutSource.Default); return new ArtemisLayout(Path.Combine(DefaultLayoutPath, "Keyboard", "Artemis L keyboard-ISO.xml"));
} }
// No numpad will result in TKL // No numpad will result in TKL
if (device.PhysicalLayout == KeyboardLayoutType.ANSI) if (device.PhysicalLayout == KeyboardLayoutType.ANSI)
return new ArtemisLayout(Path.Combine(layoutFolder + "Keyboard", "Artemis TKL keyboard-ANSI.xml"), LayoutSource.Default); return new ArtemisLayout(Path.Combine(DefaultLayoutPath, "Keyboard", "Artemis TKL keyboard-ANSI.xml"));
return new ArtemisLayout(Path.Combine(layoutFolder + "Keyboard", "Artemis TKL keyboard-ISO.xml"), LayoutSource.Default); return new ArtemisLayout(Path.Combine(DefaultLayoutPath, "Keyboard", "Artemis TKL keyboard-ISO.xml"));
} }
// if (device.DeviceType == RGBDeviceType.Mouse) // if (device.DeviceType == RGBDeviceType.Mouse)
@ -151,21 +148,21 @@ public class ArtemisLayout
// if (device.Leds.Count == 1) // if (device.Leds.Count == 1)
// { // {
// if (device.Leds.Any(l => l.RgbLed.Id == LedId.Logo)) // if (device.Leds.Any(l => l.RgbLed.Id == LedId.Logo))
// return new ArtemisLayout(Path.Combine(layoutFolder + "Mouse", "1 LED mouse logo.xml"), LayoutSource.Default); // return new ArtemisLayout(Path.Combine(DefaultLayoutPath, "Mouse", "1 LED mouse logo.xml"), LayoutSource.Default);
// return new ArtemisLayout(Path.Combine(layoutFolder + "Mouse", "1 LED mouse.xml"), LayoutSource.Default); // return new ArtemisLayout(Path.Combine(DefaultLayoutPath, "Mouse", "1 LED mouse.xml"), LayoutSource.Default);
// } // }
// if (device.Leds.Any(l => l.RgbLed.Id == LedId.Logo)) // if (device.Leds.Any(l => l.RgbLed.Id == LedId.Logo))
// return new ArtemisLayout(Path.Combine(layoutFolder + "Mouse", "4 LED mouse logo.xml"), LayoutSource.Default); // return new ArtemisLayout(Path.Combine(DefaultLayoutPath, "Mouse", "4 LED mouse logo.xml"), LayoutSource.Default);
// return new ArtemisLayout(Path.Combine(layoutFolder + "Mouse", "4 LED mouse.xml"), LayoutSource.Default); // return new ArtemisLayout(Path.Combine(DefaultLayoutPath, "Mouse", "4 LED mouse.xml"), LayoutSource.Default);
// } // }
if (device.DeviceType == RGBDeviceType.Headset) if (device.DeviceType == RGBDeviceType.Headset)
{ {
if (device.Leds.Count == 1) if (device.Leds.Count == 1)
return new ArtemisLayout(Path.Combine(layoutFolder + "Headset", "Artemis 1 LED headset.xml"), LayoutSource.Default); return new ArtemisLayout(Path.Combine(DefaultLayoutPath, "Headset", "Artemis 1 LED headset.xml"));
if (device.Leds.Count == 2) if (device.Leds.Count == 2)
return new ArtemisLayout(Path.Combine(layoutFolder + "Headset", "Artemis 2 LED headset.xml"), LayoutSource.Default); return new ArtemisLayout(Path.Combine(DefaultLayoutPath, "Headset", "Artemis 2 LED headset.xml"));
return new ArtemisLayout(Path.Combine(layoutFolder + "Headset", "Artemis 4 LED headset.xml"), LayoutSource.Default); return new ArtemisLayout(Path.Combine(DefaultLayoutPath, "Headset", "Artemis 4 LED headset.xml"));
} }
return null; return null;
@ -206,30 +203,10 @@ public class ArtemisLayout
else else
Image = null; Image = null;
} }
}
/// <summary> /// <inheritdoc />
/// Represents a source from where a layout came public override string ToString()
/// </summary>
public enum LayoutSource
{ {
/// <summary> return FilePath;
/// A layout loaded from config }
/// </summary>
Configured,
/// <summary>
/// A layout loaded from the user layout folder
/// </summary>
User,
/// <summary>
/// A layout loaded from the plugin folder
/// </summary>
Plugin,
/// <summary>
/// A default layout loaded as a fallback option
/// </summary>
Default
} }

View File

@ -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,7 +59,17 @@ public class ArtemisLedLayout
.ThenBy(l => l.Name == null) .ThenBy(l => l.Name == null)
.First(); .First();
ApplyLogicalLayout(logicalLayout);
}
private void ApplyLogicalLayout(LayoutCustomLedDataLogicalLayout logicalLayout)
{
string? layoutDirectory = Path.GetDirectoryName(DeviceLayout.FilePath);
LogicalName = logicalLayout.Name; LogicalName = logicalLayout.Name;
Image = new Uri(Path.Combine(Path.GetDirectoryName(DeviceLayout.FilePath)!, logicalLayout.Image!), UriKind.Absolute); if (layoutDirectory != null && logicalLayout.Image != null)
Image = new Uri(Path.Combine(layoutDirectory, logicalLayout.Image!), UriKind.Absolute);
else
Image = null;
} }
} }

View File

@ -0,0 +1,28 @@
namespace Artemis.Core;
/// <summary>
/// Represents a reference to a layout for a device.
/// </summary>
public class LayoutSelection : CorePropertyChanged
{
private string? _type;
private string? _parameter;
/// <summary>
/// Gets or sets what kind of layout reference this is.
/// </summary>
public string? Type
{
get => _type;
set => SetAndNotify(ref _type, value);
}
/// <summary>
/// Gets or sets the parameter of the layout reference, such as a file path of workshop entry ID.
/// </summary>
public string? Parameter
{
get => _parameter;
set => SetAndNotify(ref _parameter, value);
}
}

View File

@ -62,7 +62,7 @@ public abstract class DeviceProvider : PluginFeature
device.DeviceType.ToString(), device.DeviceType.ToString(),
GetDeviceLayoutName(device) GetDeviceLayoutName(device)
); );
return new ArtemisLayout(filePath, LayoutSource.Plugin); return new ArtemisLayout(filePath);
} }
/// <summary> /// <summary>
@ -79,7 +79,7 @@ public abstract class DeviceProvider : PluginFeature
device.DeviceType.ToString(), device.DeviceType.ToString(),
GetDeviceLayoutName(device) GetDeviceLayoutName(device)
); );
return new ArtemisLayout(filePath, LayoutSource.User); return new ArtemisLayout(filePath);
} }
/// <summary> /// <summary>

View File

@ -0,0 +1,37 @@
namespace Artemis.Core.Providers;
public class CustomPathLayoutProvider : ILayoutProvider
{
public static string LayoutType = "CustomPath";
/// <inheritdoc />
public ArtemisLayout? GetDeviceLayout(ArtemisDevice device)
{
if (device.LayoutSelection.Parameter == null)
return null;
return new ArtemisLayout(device.LayoutSelection.Parameter);
}
/// <inheritdoc />
public void ApplyLayout(ArtemisDevice device, ArtemisLayout layout)
{
device.ApplyLayout(layout, device.DeviceProvider.CreateMissingLedsSupported, device.DeviceProvider.RemoveExcessiveLedsSupported);
}
/// <inheritdoc />
public bool IsMatch(ArtemisDevice device)
{
return device.LayoutSelection.Type == LayoutType;
}
/// <summary>
/// Configures the provided device to use this layout provider.
/// </summary>
/// <param name="device">The device to apply the provider to.</param>
/// <param name="path">The path to the custom layout.</param>
public void ConfigureDevice(ArtemisDevice device, string? path)
{
device.LayoutSelection.Type = LayoutType;
device.LayoutSelection.Parameter = path;
}
}

View File

@ -0,0 +1,41 @@
namespace Artemis.Core.Providers;
public class DefaultLayoutProvider : ILayoutProvider
{
public static string LayoutType = "Default";
/// <inheritdoc />
public ArtemisLayout? GetDeviceLayout(ArtemisDevice device)
{
// Look for a layout provided by the plugin
ArtemisLayout layout = device.DeviceProvider.LoadLayout(device);
// Finally fall back to a default layout
return layout.IsValid ? layout : ArtemisLayout.GetDefaultLayout(device);
}
/// <inheritdoc />
public void ApplyLayout(ArtemisDevice device, ArtemisLayout layout)
{
if (layout.IsDefaultLayout)
device.ApplyLayout(layout, false, false);
else
device.ApplyLayout(layout, device.DeviceProvider.CreateMissingLedsSupported, device.DeviceProvider.RemoveExcessiveLedsSupported);
}
/// <inheritdoc />
public bool IsMatch(ArtemisDevice device)
{
return device.LayoutSelection.Type == LayoutType;
}
/// <summary>
/// Configures the provided device to use this layout provider.
/// </summary>
/// <param name="device">The device to apply the provider to.</param>
public void ConfigureDevice(ArtemisDevice device)
{
device.LayoutSelection.Type = LayoutType;
device.LayoutSelection.Parameter = null;
}
}

View File

@ -0,0 +1,17 @@
namespace Artemis.Core.Providers;
/// <summary>
/// Represents a class that can provide Artemis layouts for devices.
/// </summary>
public interface ILayoutProvider
{
/// <summary>
/// If available, loads an Artemis layout for the provided device.
/// </summary>
/// <param name="device">The device to load the layout for.</param>
/// <returns>The resulting layout if one was available; otherwise <see langword="null" />.</returns>
ArtemisLayout? GetDeviceLayout(ArtemisDevice device);
void ApplyLayout(ArtemisDevice device, ArtemisLayout layout);
bool IsMatch(ArtemisDevice device);
}

View File

@ -0,0 +1,34 @@
namespace Artemis.Core.Providers;
public class NoneLayoutProvider : ILayoutProvider
{
public static string LayoutType = "None";
/// <inheritdoc />
public ArtemisLayout? GetDeviceLayout(ArtemisDevice device)
{
return null;
}
/// <inheritdoc />
public void ApplyLayout(ArtemisDevice device, ArtemisLayout layout)
{
device.ApplyLayout(null, false, false);
}
/// <inheritdoc />
public bool IsMatch(ArtemisDevice device)
{
return device.LayoutSelection.Type == LayoutType;
}
/// <summary>
/// Configures the provided device to use this layout provider.
/// </summary>
/// <param name="device">The device to apply the provider to.</param>
public void ConfigureDevice(ArtemisDevice device)
{
device.LayoutSelection.Type = LayoutType;
device.LayoutSelection.Parameter = null;
}
}

View File

@ -4,9 +4,11 @@ using System.Collections.ObjectModel;
using System.Linq; using System.Linq;
using System.Threading.Tasks; using System.Threading.Tasks;
using Artemis.Core.DeviceProviders; using Artemis.Core.DeviceProviders;
using Artemis.Core.Providers;
using Artemis.Core.Services.Models; using Artemis.Core.Services.Models;
using Artemis.Storage.Entities.Surface; using Artemis.Storage.Entities.Surface;
using Artemis.Storage.Repositories.Interfaces; using Artemis.Storage.Repositories.Interfaces;
using DryIoc;
using RGB.NET.Core; using RGB.NET.Core;
using Serilog; using Serilog;
@ -18,15 +20,21 @@ internal class DeviceService : IDeviceService
private readonly IPluginManagementService _pluginManagementService; private readonly IPluginManagementService _pluginManagementService;
private readonly IDeviceRepository _deviceRepository; private readonly IDeviceRepository _deviceRepository;
private readonly Lazy<IRenderService> _renderService; private readonly Lazy<IRenderService> _renderService;
private readonly Func<List<ILayoutProvider>> _getLayoutProviders;
private readonly List<ArtemisDevice> _enabledDevices = new(); private readonly List<ArtemisDevice> _enabledDevices = new();
private readonly List<ArtemisDevice> _devices = new(); private readonly List<ArtemisDevice> _devices = new();
public DeviceService(ILogger logger, IPluginManagementService pluginManagementService, IDeviceRepository deviceRepository, Lazy<IRenderService> renderService) public DeviceService(ILogger logger,
IPluginManagementService pluginManagementService,
IDeviceRepository deviceRepository,
Lazy<IRenderService> renderService,
Func<List<ILayoutProvider>> getLayoutProviders)
{ {
_logger = logger; _logger = logger;
_pluginManagementService = pluginManagementService; _pluginManagementService = pluginManagementService;
_deviceRepository = deviceRepository; _deviceRepository = deviceRepository;
_renderService = renderService; _renderService = renderService;
_getLayoutProviders = getLayoutProviders;
EnabledDevices = new ReadOnlyCollection<ArtemisDevice>(_enabledDevices); EnabledDevices = new ReadOnlyCollection<ArtemisDevice>(_enabledDevices);
Devices = new ReadOnlyCollection<ArtemisDevice>(_devices); Devices = new ReadOnlyCollection<ArtemisDevice>(_devices);
@ -157,12 +165,30 @@ internal class DeviceService : IDeviceService
} }
/// <inheritdoc /> /// <inheritdoc />
public void ApplyDeviceLayout(ArtemisDevice device, ArtemisLayout? layout) public void LoadDeviceLayout(ArtemisDevice device)
{ {
if (layout == null || layout.Source == LayoutSource.Default) ILayoutProvider? provider = _getLayoutProviders().FirstOrDefault(p => p.IsMatch(device));
device.ApplyLayout(layout, false, false); if (provider == null)
_logger.Warning("Could not find a layout provider for type {LayoutType} of device {Device}", device.LayoutSelection.Type, device);
ArtemisLayout? layout = provider?.GetDeviceLayout(device);
if (layout != null && !layout.IsValid)
{
_logger.Warning("Got an invalid layout {Layout} from {LayoutProvider}", layout, provider!.GetType().FullName);
layout = null;
}
try
{
if (layout == null)
device.ApplyLayout(null, false, false);
else else
device.ApplyLayout(layout, device.DeviceProvider.CreateMissingLedsSupported, device.DeviceProvider.RemoveExcessiveLedsSupported); provider?.ApplyLayout(device, layout);
}
catch (Exception e)
{
_logger.Error(e, "Failed to apply device layout");
}
UpdateLeds(); UpdateLeds();
} }
@ -230,7 +256,7 @@ internal class DeviceService : IDeviceService
device = new ArtemisDevice(rgbDevice, deviceProvider); device = new ArtemisDevice(rgbDevice, deviceProvider);
} }
ApplyDeviceLayout(device, device.GetBestDeviceLayout()); LoadDeviceLayout(device);
return device; return device;
} }

View File

@ -43,11 +43,10 @@ public interface IDeviceService : IArtemisService
void AutoArrangeDevices(); void AutoArrangeDevices();
/// <summary> /// <summary>
/// Apples the provided <see cref="ArtemisLayout" /> to the provided <see cref="ArtemisDevice" /> /// Apples the best available to the provided <see cref="ArtemisDevice" />
/// </summary> /// </summary>
/// <param name="device"></param> /// <param name="device"></param>
/// <param name="layout"></param> void LoadDeviceLayout(ArtemisDevice device);
void ApplyDeviceLayout(ArtemisDevice device, ArtemisLayout? layout);
/// <summary> /// <summary>
/// Enables the provided device /// Enables the provided device

View File

@ -31,7 +31,7 @@ internal class PluginManagementService : IPluginManagementService
private readonly IPluginRepository _pluginRepository; private readonly IPluginRepository _pluginRepository;
private readonly List<Plugin> _plugins; private readonly List<Plugin> _plugins;
private readonly IQueuedActionRepository _queuedActionRepository; private readonly IQueuedActionRepository _queuedActionRepository;
private FileSystemWatcher _hotReloadWatcher; private FileSystemWatcher? _hotReloadWatcher;
private bool _disposed; private bool _disposed;
private bool _isElevated; private bool _isElevated;
@ -57,7 +57,7 @@ internal class PluginManagementService : IPluginManagementService
// Remove the old directory if it exists // Remove the old directory if it exists
if (Directory.Exists(pluginDirectory.FullName)) if (Directory.Exists(pluginDirectory.FullName))
pluginDirectory.DeleteRecursively(); pluginDirectory.Delete(true);
// Extract everything in the same archive directory to the unique plugin directory // Extract everything in the same archive directory to the unique plugin directory
Utilities.CreateAccessibleDirectory(pluginDirectory.FullName); Utilities.CreateAccessibleDirectory(pluginDirectory.FullName);

View File

@ -1,24 +1,49 @@
namespace Artemis.Core.Services; namespace Artemis.Core.Services;
/// <summary>
/// This readonly struct provides information about a process.
/// </summary>
public readonly struct ProcessInfo public readonly struct ProcessInfo
{ {
#region Properties & Fields #region Properties & Fields
/// <summary>
/// Gets the Identifier for the process.
/// </summary>
public readonly int ProcessId; public readonly int ProcessId;
/// <summary>
/// Gets the name of the process.
/// </summary>
public readonly string ProcessName; public readonly string ProcessName;
/// <summary>
/// Gets the Image Name of the Process.
/// </summary>
public readonly string ImageName; // TODO DarthAffe 01.09.2023: Do we need this if we can't get it through Process.GetProcesses()? public readonly string ImageName; // TODO DarthAffe 01.09.2023: Do we need this if we can't get it through Process.GetProcesses()?
/// <summary>
/// Gets the Executable associated with the Process.
/// </summary>
public readonly string Executable; public readonly string Executable;
#endregion #endregion
#region Constructors #region Constructors
/// <summary>
/// Initializes a new instance of the <see cref="ProcessInfo"/> struct.
/// </summary>
/// <param name="processId">The identifier for the process.</param>
/// <param name="processName">The name of the process.</param>
/// <param name="imageName">The Image Name of the process.</param>
/// <param name="executable">The executable associated with the process.</param>
public ProcessInfo(int processId, string processName, string imageName, string executable) public ProcessInfo(int processId, string processName, string imageName, string executable)
{ {
this.ProcessId = processId; ProcessId = processId;
this.ProcessName = processName; ProcessName = processName;
this.ImageName = imageName; ImageName = imageName;
this.Executable = executable; Executable = executable;
} }
#endregion #endregion

View File

@ -75,8 +75,8 @@ internal class RenderService : IRenderService, IRenderer, IDisposable
_frameStopWatch.Restart(); _frameStopWatch.Restart();
try try
{ {
OnFrameRendering(new FrameRenderingEventArgs(canvas, delta, _surfaceManager.Surface));
_coreRenderer.Render(canvas, delta); _coreRenderer.Render(canvas, delta);
OnFrameRendering(new FrameRenderingEventArgs(canvas, delta, _surfaceManager.Surface));
} }
catch (Exception e) catch (Exception e)
{ {

View File

@ -1,6 +1,4 @@
<Project Sdk="Microsoft.NET.Sdk"> <Project Sdk="Microsoft.NET.Sdk">
<Import Project="..\Artemis.props" />
<PropertyGroup> <PropertyGroup>
<TargetFramework>net7.0</TargetFramework> <TargetFramework>net7.0</TargetFramework>
<PreserveCompilationContext>false</PreserveCompilationContext> <PreserveCompilationContext>false</PreserveCompilationContext>
@ -9,6 +7,6 @@
<ItemGroup> <ItemGroup>
<PackageReference Include="LiteDB" Version="5.0.17" /> <PackageReference Include="LiteDB" Version="5.0.17" />
<PackageReference Include="Serilog" Version="3.0.1" /> <PackageReference Include="Serilog" Version="3.1.1" />
</ItemGroup> </ItemGroup>
</Project> </Project>

View File

@ -23,10 +23,10 @@ public class DeviceEntity
public float BlueScale { get; set; } public float BlueScale { get; set; }
public bool IsEnabled { get; set; } public bool IsEnabled { get; set; }
public bool DisableDefaultLayout { get; set; }
public int PhysicalLayout { get; set; } public int PhysicalLayout { get; set; }
public string LogicalLayout { get; set; } public string LogicalLayout { get; set; }
public string CustomLayoutPath { get; set; } public string LayoutType { get; set; }
public string LayoutParameter { get; set; }
public List<DeviceInputIdentifierEntity> InputIdentifiers { get; set; } public List<DeviceInputIdentifierEntity> InputIdentifiers { get; set; }
public List<InputMappingEntity> InputMappings { get; set; } public List<InputMappingEntity> InputMappings { get; set; }

View File

@ -1,4 +1,5 @@
using System; using System;
using System.Collections.Generic;
namespace Artemis.Storage.Entities.Workshop; namespace Artemis.Storage.Entities.Workshop;
@ -17,5 +18,5 @@ public class EntryEntity
public string ReleaseVersion { get; set; } public string ReleaseVersion { get; set; }
public DateTimeOffset InstalledAt { get; set; } public DateTimeOffset InstalledAt { get; set; }
public string LocalReference { get; set; } public Dictionary<string,object> Metadata { get; set; }
} }

View File

@ -0,0 +1,35 @@
using System.Collections.Generic;
using Artemis.Storage.Migrations.Interfaces;
using LiteDB;
namespace Artemis.Storage.Migrations;
public class M0023LayoutProviders : IStorageMigration
{
public int UserVersion => 23;
public void Apply(LiteRepository repository)
{
ILiteCollection<BsonDocument> deviceEntities = repository.Database.GetCollection("DeviceEntity");
List<BsonDocument> toUpdate = new();
foreach (BsonDocument bsonDocument in deviceEntities.FindAll())
{
if (bsonDocument.TryGetValue("CustomLayoutPath", out BsonValue customLayoutPath) && customLayoutPath.IsString && !string.IsNullOrEmpty(customLayoutPath.AsString))
{
bsonDocument.Add("LayoutType", new BsonValue("CustomPath"));
bsonDocument.Add("LayoutParameter", new BsonValue(customLayoutPath.AsString));
}
else if (bsonDocument.TryGetValue("DisableDefaultLayout", out BsonValue disableDefaultLayout) && disableDefaultLayout.AsBoolean)
bsonDocument.Add("LayoutType", new BsonValue("None"));
else
bsonDocument.Add("LayoutType", new BsonValue("Default"));
bsonDocument.Remove("CustomLayoutPath");
bsonDocument.Remove("DisableDefaultLayout");
toUpdate.Add(bsonDocument);
}
deviceEntities.Update(toUpdate);
}
}

View File

@ -1,6 +1,4 @@
<Project Sdk="Microsoft.NET.Sdk"> <Project Sdk="Microsoft.NET.Sdk">
<Import Project="..\Artemis.props" />
<PropertyGroup> <PropertyGroup>
<OutputType>WinExe</OutputType> <OutputType>WinExe</OutputType>
<TargetFramework>net7.0</TargetFramework> <TargetFramework>net7.0</TargetFramework>
@ -17,14 +15,6 @@
<AvaloniaResource Include="Assets\**" /> <AvaloniaResource Include="Assets\**" />
<None Remove=".gitignore" /> <None Remove=".gitignore" />
</ItemGroup> </ItemGroup>
<ItemGroup>
<PackageReference Include="Avalonia" Version="$(AvaloniaVersion)" />
<PackageReference Include="Avalonia.Desktop" Version="$(AvaloniaVersion)" />
<!--Condition below is needed to remove Avalonia.Diagnostics package from build output in Release configuration.-->
<PackageReference Condition="'$(Configuration)' == 'Debug'" Include="Avalonia.Diagnostics" Version="$(AvaloniaVersion)" />
<PackageReference Include="Avalonia.ReactiveUI" Version="$(AvaloniaVersion)" />
<PackageReference Include="ReactiveUI" Version="19.4.1" />
</ItemGroup>
<ItemGroup> <ItemGroup>
<ProjectReference Include="..\Artemis.Core\Artemis.Core.csproj" /> <ProjectReference Include="..\Artemis.Core\Artemis.Core.csproj" />
<ProjectReference Include="..\Artemis.UI\Artemis.UI.csproj" /> <ProjectReference Include="..\Artemis.UI\Artemis.UI.csproj" />

View File

@ -1,6 +1,4 @@
<Project Sdk="Microsoft.NET.Sdk"> <Project Sdk="Microsoft.NET.Sdk">
<Import Project="..\Artemis.props" />
<PropertyGroup> <PropertyGroup>
<OutputType>WinExe</OutputType> <OutputType>WinExe</OutputType>
<TargetFramework>net7.0</TargetFramework> <TargetFramework>net7.0</TargetFramework>
@ -16,14 +14,6 @@
<AvaloniaResource Include="Assets\**" /> <AvaloniaResource Include="Assets\**" />
<None Remove=".gitignore" /> <None Remove=".gitignore" />
</ItemGroup> </ItemGroup>
<ItemGroup>
<PackageReference Include="Avalonia" Version="$(AvaloniaVersion)" />
<PackageReference Include="Avalonia.Desktop" Version="$(AvaloniaVersion)" />
<!--Condition below is needed to remove Avalonia.Diagnostics package from build output in Release configuration.-->
<PackageReference Condition="'$(Configuration)' == 'Debug'" Include="Avalonia.Diagnostics" Version="$(AvaloniaVersion)" />
<PackageReference Include="Avalonia.ReactiveUI" Version="$(AvaloniaVersion)" />
<PackageReference Include="ReactiveUI" Version="19.4.1" />
</ItemGroup>
<ItemGroup> <ItemGroup>
<ProjectReference Include="..\Artemis.Core\Artemis.Core.csproj" /> <ProjectReference Include="..\Artemis.Core\Artemis.Core.csproj" />
<ProjectReference Include="..\Artemis.UI\Artemis.UI.csproj" /> <ProjectReference Include="..\Artemis.UI\Artemis.UI.csproj" />

View File

@ -1,6 +1,4 @@
<Project Sdk="Microsoft.NET.Sdk"> <Project Sdk="Microsoft.NET.Sdk">
<Import Project="..\Artemis.props" />
<PropertyGroup> <PropertyGroup>
<OutputType>Library</OutputType> <OutputType>Library</OutputType>
<TargetFramework>net7.0</TargetFramework> <TargetFramework>net7.0</TargetFramework>
@ -12,19 +10,17 @@
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="Avalonia" Version="$(AvaloniaVersion)" /> <PackageReference Include="Avalonia" Version="11.0.6" />
<!--Condition below is needed to remove Avalonia.Diagnostics package from build output in Release configuration.--> <!--Condition below is needed to remove Avalonia.Diagnostics package from build output in Release configuration.-->
<PackageReference Condition="'$(Configuration)' == 'Debug'" Include="Avalonia.Diagnostics" Version="$(AvaloniaVersion)" /> <PackageReference Condition="'$(Configuration)' == 'Debug'" Include="Avalonia.Diagnostics" Version="11.0.6" />
<PackageReference Include="Avalonia.Controls.ItemsRepeater" Version="$(AvaloniaVersion)" /> <PackageReference Include="Avalonia.Controls.ItemsRepeater" Version="11.0.6" />
<PackageReference Include="Avalonia.ReactiveUI" Version="$(AvaloniaVersion)" /> <PackageReference Include="Avalonia.ReactiveUI" Version="11.0.6" />
<PackageReference Include="Avalonia.Xaml.Behaviors" Version="$(AvaloniaBehavioursVersion)" /> <PackageReference Include="Avalonia.Xaml.Behaviors" Version="11.0.6" />
<PackageReference Include="DynamicData" Version="7.14.2" /> <PackageReference Include="DynamicData" Version="8.3.27" />
<PackageReference Include="FluentAvaloniaUI" Version="$(FluentAvaloniaVersion)" /> <PackageReference Include="FluentAvaloniaUI" Version="2.0.5" />
<PackageReference Include="Material.Icons.Avalonia" Version="2.0.1" /> <PackageReference Include="Material.Icons.Avalonia" Version="2.1.0" />
<PackageReference Include="ReactiveUI" Version="19.4.1" /> <PackageReference Include="ReactiveUI" Version="19.5.39" />
<PackageReference Include="ReactiveUI.Validation" Version="3.1.7" /> <PackageReference Include="ReactiveUI.Validation" Version="3.1.7" />
<PackageReference Include="RGB.NET.Core" Version="$(RGBDotNetVersion)" />
<PackageReference Include="SkiaSharp" Version="$(SkiaSharpVersion)" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<ProjectReference Include="..\Artemis.Core\Artemis.Core.csproj" /> <ProjectReference Include="..\Artemis.Core\Artemis.Core.csproj" />

View File

@ -15,7 +15,7 @@ public class LostFocusTextBoxBindingBehavior : Behavior<TextBox>
/// <summary> /// <summary>
/// Gets or sets the value of the binding. /// Gets or sets the value of the binding.
/// </summary> /// </summary>
public static readonly StyledProperty<string> TextProperty = AvaloniaProperty.Register<LostFocusTextBoxBindingBehavior, string>( public static readonly StyledProperty<string?> TextProperty = AvaloniaProperty.Register<LostFocusTextBoxBindingBehavior, string?>(
"Text", defaultBindingMode: BindingMode.TwoWay); "Text", defaultBindingMode: BindingMode.TwoWay);
static LostFocusTextBoxBindingBehavior() static LostFocusTextBoxBindingBehavior()
@ -26,7 +26,7 @@ public class LostFocusTextBoxBindingBehavior : Behavior<TextBox>
/// <summary> /// <summary>
/// Gets or sets the value of the binding. /// Gets or sets the value of the binding.
/// </summary> /// </summary>
public string Text public string? Text
{ {
get => GetValue(TextProperty); get => GetValue(TextProperty);
set => SetValue(TextProperty, value); set => SetValue(TextProperty, value);

View File

@ -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;
@ -305,7 +300,7 @@ public class DeviceVisualizer : Control
{ {
_deviceImage = await Task.Run(() => GetDeviceImage(device)); _deviceImage = await Task.Run(() => GetDeviceImage(device));
} }
catch (Exception e) catch (Exception)
{ {
// ignored // ignored
} }
@ -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;
} }

View File

@ -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)

View File

@ -19,32 +19,32 @@ namespace Artemis.UI.Shared.Pagination;
[TemplatePart("PART_PagesView", typeof(StackPanel))] [TemplatePart("PART_PagesView", typeof(StackPanel))]
public partial class Pagination : TemplatedControl public partial class Pagination : TemplatedControl
{ {
private Button? _previousButton;
private Button? _nextButton;
private StackPanel? _pagesView;
/// <inheritdoc /> /// <inheritdoc />
public Pagination() public Pagination()
{ {
PropertyChanged += OnPropertyChanged; PropertyChanged += OnPropertyChanged;
} }
public Button? PreviousButton { get; set; }
public Button? NextButton { get; set; }
public StackPanel? PagesView { get; set; }
/// <inheritdoc /> /// <inheritdoc />
protected override void OnApplyTemplate(TemplateAppliedEventArgs e) protected override void OnApplyTemplate(TemplateAppliedEventArgs e)
{ {
if (PreviousButton != null) if (_previousButton != null)
PreviousButton.Click -= PreviousButtonOnClick; _previousButton.Click -= PreviousButtonOnClick;
if (NextButton != null) if (_nextButton != null)
NextButton.Click -= NextButtonOnClick; _nextButton.Click -= NextButtonOnClick;
PreviousButton = e.NameScope.Find<Button>("PART_PreviousButton"); _previousButton = e.NameScope.Find<Button>("PART_PreviousButton");
NextButton = e.NameScope.Find<Button>("PART_NextButton"); _nextButton = e.NameScope.Find<Button>("PART_NextButton");
PagesView = e.NameScope.Find<StackPanel>("PART_PagesView"); _pagesView = e.NameScope.Find<StackPanel>("PART_PagesView");
if (PreviousButton != null) if (_previousButton != null)
PreviousButton.Click += PreviousButtonOnClick; _previousButton.Click += PreviousButtonOnClick;
if (NextButton != null) if (_nextButton != null)
NextButton.Click += NextButtonOnClick; _nextButton.Click += NextButtonOnClick;
Update(); Update();
} }
@ -69,19 +69,19 @@ public partial class Pagination : TemplatedControl
private void Update() private void Update()
{ {
if (PagesView == null) if (_pagesView == null)
return; return;
List<int> pages = GetPages(Value, Maximum); List<int> pages = GetPages(Value, Maximum);
// Remove extra children // Remove extra children
while (PagesView.Children.Count > pages.Count) while (_pagesView.Children.Count > pages.Count)
{ {
PagesView.Children.RemoveAt(PagesView.Children.Count - 1); _pagesView.Children.RemoveAt(_pagesView.Children.Count - 1);
} }
if (PagesView.Children.Count > pages.Count) if (_pagesView.Children.Count > pages.Count)
PagesView.Children.RemoveRange(0, PagesView.Children.Count - pages.Count); _pagesView.Children.RemoveRange(0, _pagesView.Children.Count - pages.Count);
// Add/modify children // Add/modify children
for (int i = 0; i < pages.Count; i++) for (int i = 0; i < pages.Count; i++)
@ -91,18 +91,18 @@ public partial class Pagination : TemplatedControl
// -1 indicates an ellipsis (...) // -1 indicates an ellipsis (...)
if (page == -1) if (page == -1)
{ {
if (PagesView.Children.ElementAtOrDefault(i) is not PaginationEllipsis) if (_pagesView.Children.ElementAtOrDefault(i) is not PaginationEllipsis)
{ {
if (PagesView.Children.Count - 1 >= i) if (_pagesView.Children.Count - 1 >= i)
PagesView.Children[i] = new PaginationEllipsis(); _pagesView.Children[i] = new PaginationEllipsis();
else else
PagesView.Children.Add(new PaginationEllipsis()); _pagesView.Children.Add(new PaginationEllipsis());
} }
} }
// Anything else indicates a regular page // Anything else indicates a regular page
else else
{ {
if (PagesView.Children.ElementAtOrDefault(i) is PaginationPage paginationPage) if (_pagesView.Children.ElementAtOrDefault(i) is PaginationPage paginationPage)
{ {
paginationPage.Page = page; paginationPage.Page = page;
paginationPage.Command = ReactiveCommand.Create(() => Value = page); paginationPage.Command = ReactiveCommand.Create(() => Value = page);
@ -110,14 +110,14 @@ public partial class Pagination : TemplatedControl
} }
paginationPage = new PaginationPage {Page = page, Command = ReactiveCommand.Create(() => Value = page)}; paginationPage = new PaginationPage {Page = page, Command = ReactiveCommand.Create(() => Value = page)};
if (PagesView.Children.Count - 1 >= i) if (_pagesView.Children.Count - 1 >= i)
PagesView.Children[i] = paginationPage; _pagesView.Children[i] = paginationPage;
else else
PagesView.Children.Add(paginationPage); _pagesView.Children.Add(paginationPage);
} }
} }
foreach (Control child in PagesView.Children) foreach (Control child in _pagesView.Children)
{ {
if (child is PaginationPage paginationPage) if (child is PaginationPage paginationPage)
((IPseudoClasses) paginationPage.Classes).Set(":selected", paginationPage.Page == Value); ((IPseudoClasses) paginationPage.Classes).Set(":selected", paginationPage.Page == Value);

View File

@ -8,10 +8,17 @@ using ReactiveUI;
namespace Artemis.UI.Shared.TagsInput; namespace Artemis.UI.Shared.TagsInput;
/// <summary>
/// Represents an input for tags.
/// </summary>
[TemplatePart("PART_TagInputBox", typeof(TextBox))] [TemplatePart("PART_TagInputBox", typeof(TextBox))]
public partial class TagsInput : TemplatedControl public partial class TagsInput : TemplatedControl
{ {
public TextBox? TagInputBox { get; set; } private TextBox? _tagInputBox;
/// <summary>
/// Gets the command that is to be called when removing a tag
/// </summary>
public ICommand RemoveTag { get; } public ICommand RemoveTag { get; }
/// <inheritdoc /> /// <inheritdoc />
@ -23,18 +30,18 @@ public partial class TagsInput : TemplatedControl
/// <inheritdoc /> /// <inheritdoc />
protected override void OnApplyTemplate(TemplateAppliedEventArgs e) protected override void OnApplyTemplate(TemplateAppliedEventArgs e)
{ {
if (TagInputBox != null) if (_tagInputBox != null)
{ {
TagInputBox.KeyDown -= TagInputBoxOnKeyDown; _tagInputBox.KeyDown -= TagInputBoxOnKeyDown;
TagInputBox.TextChanging -= TagInputBoxOnTextChanging; _tagInputBox.TextChanging -= TagInputBoxOnTextChanging;
} }
TagInputBox = e.NameScope.Find<TextBox>("PART_TagInputBox"); _tagInputBox = e.NameScope.Find<TextBox>("PART_TagInputBox");
if (TagInputBox != null) if (_tagInputBox != null)
{ {
TagInputBox.KeyDown += TagInputBoxOnKeyDown; _tagInputBox.KeyDown += TagInputBoxOnKeyDown;
TagInputBox.TextChanging += TagInputBoxOnTextChanging; _tagInputBox.TextChanging += TagInputBoxOnTextChanging;
} }
} }
@ -42,21 +49,21 @@ public partial class TagsInput : TemplatedControl
{ {
Tags.Remove(t); Tags.Remove(t);
if (TagInputBox != null) if (_tagInputBox != null)
TagInputBox.IsEnabled = Tags.Count < MaxLength; _tagInputBox.IsEnabled = Tags.Count < MaxLength;
} }
private void TagInputBoxOnTextChanging(object? sender, TextChangingEventArgs e) private void TagInputBoxOnTextChanging(object? sender, TextChangingEventArgs e)
{ {
if (TagInputBox?.Text == null) if (_tagInputBox?.Text == null)
return; return;
TagInputBox.Text = CleanTagRegex().Replace(TagInputBox.Text.ToLower(), ""); _tagInputBox.Text = CleanTagRegex().Replace(_tagInputBox.Text.ToLower(), "");
} }
private void TagInputBoxOnKeyDown(object? sender, KeyEventArgs e) private void TagInputBoxOnKeyDown(object? sender, KeyEventArgs e)
{ {
if (TagInputBox == null) if (_tagInputBox == null)
return; return;
if (e.Key == Key.Space) if (e.Key == Key.Space)
@ -64,13 +71,13 @@ public partial class TagsInput : TemplatedControl
if (e.Key != Key.Enter) if (e.Key != Key.Enter)
return; return;
if (string.IsNullOrWhiteSpace(TagInputBox.Text) || Tags.Contains(TagInputBox.Text) || Tags.Count >= MaxLength) if (string.IsNullOrWhiteSpace(_tagInputBox.Text) || Tags.Contains(_tagInputBox.Text) || Tags.Count >= MaxLength)
return; return;
Tags.Add(CleanTagRegex().Replace(TagInputBox.Text.ToLower(), "")); Tags.Add(CleanTagRegex().Replace(_tagInputBox.Text.ToLower(), ""));
TagInputBox.Text = ""; _tagInputBox.Text = "";
TagInputBox.IsEnabled = Tags.Count < MaxLength; _tagInputBox.IsEnabled = Tags.Count < MaxLength;
} }
[GeneratedRegex("[\\s\\-]+")] [GeneratedRegex("[\\s\\-]+")]

View File

@ -2,6 +2,9 @@ using System;
namespace Artemis.UI.Shared; namespace Artemis.UI.Shared;
/// <summary>
/// Represents errors that occur within the Artemis router.
/// </summary>
public class ArtemisRoutingException : Exception public class ArtemisRoutingException : Exception
{ {
/// <inheritdoc /> /// <inheritdoc />

View 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);
}
}
}

View File

@ -5,10 +5,21 @@ using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using Artemis.UI.Shared.Utilities; using Artemis.UI.Shared.Utilities;
namespace Artemis.UI.Shared.Extensions namespace Artemis.UI.Shared.Extensions;
{
/// <summary>
/// Provides extension methods for the <see cref="HttpClient" /> type.
/// </summary>
public static class HttpClientProgressExtensions public static class HttpClientProgressExtensions
{ {
/// <summary>
/// Send a GET request to the specified Uri with an HTTP completion option and a cancellation token as an asynchronous operation.
/// </summary>
/// <param name="client">The HTTP client to use.</param>
/// <param name="requestUrl">The Uri the request is sent to.</param>
/// <param name="destination">The destination stream.</param>
/// <param name="progress">The progress instance to use for progress indication.</param>
/// <param name="cancellationToken">A cancellation token that can be used by other objects or threads to receive notice of cancellation.</param>
public static async Task DownloadDataAsync(this HttpClient client, string requestUrl, Stream destination, IProgress<StreamProgress>? progress, CancellationToken cancellationToken) public static async Task DownloadDataAsync(this HttpClient client, string requestUrl, Stream destination, IProgress<StreamProgress>? progress, CancellationToken cancellationToken)
{ {
using HttpResponseMessage response = await client.GetAsync(requestUrl, HttpCompletionOption.ResponseHeadersRead, cancellationToken); using HttpResponseMessage response = await client.GetAsync(requestUrl, HttpCompletionOption.ResponseHeadersRead, cancellationToken);
@ -27,7 +38,7 @@ namespace Artemis.UI.Shared.Extensions
await download.CopyToAsync(destination, 81920, progress, contentLength, cancellationToken); await download.CopyToAsync(destination, 81920, progress, contentLength, cancellationToken);
} }
static async Task CopyToAsync(this Stream source, Stream destination, int bufferSize, IProgress<StreamProgress> progress, long? contentLength, CancellationToken cancellationToken) private static async Task CopyToAsync(this Stream source, Stream destination, int bufferSize, IProgress<StreamProgress> progress, long? contentLength, CancellationToken cancellationToken)
{ {
if (bufferSize < 0) if (bufferSize < 0)
throw new ArgumentOutOfRangeException(nameof(bufferSize)); throw new ArgumentOutOfRangeException(nameof(bufferSize));
@ -51,4 +62,3 @@ namespace Artemis.UI.Shared.Extensions
} }
} }
} }
}

View File

@ -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>

View File

@ -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)

View File

@ -62,6 +62,9 @@ internal class WindowService : IWindowService
{ {
Window? parent = GetCurrentWindow(); Window? parent = GetCurrentWindow();
if (parent == null)
throw new ArtemisSharedUIException("Failed to get the current window.");
string name = viewModel.GetType().FullName!.Split('`')[0].Replace("ViewModel", "View"); string name = viewModel.GetType().FullName!.Split('`')[0].Replace("ViewModel", "View");
Type? type = viewModel.GetType().Assembly.GetType(name); Type? type = viewModel.GetType().Assembly.GetType(name);
@ -101,6 +104,9 @@ internal class WindowService : IWindowService
{ {
Window? parent = GetCurrentWindow(); Window? parent = GetCurrentWindow();
if (parent == null)
throw new ArtemisSharedUIException("Failed to get the current window.");
string name = viewModel.GetType().FullName!.Split('`')[0].Replace("ViewModel", "View"); string name = viewModel.GetType().FullName!.Split('`')[0].Replace("ViewModel", "View");
Type? type = viewModel.GetType().Assembly.GetType(name); Type? type = viewModel.GetType().Assembly.GetType(name);

View File

@ -27,22 +27,7 @@
<Setter Property="Maximum" Value="{x:Static system:Double.MaxValue}"></Setter> <Setter Property="Maximum" Value="{x:Static system:Double.MaxValue}"></Setter>
<Setter Property="Minimum" Value="{x:Static system:Double.MinValue}"></Setter> <Setter Property="Minimum" Value="{x:Static system:Double.MinValue}"></Setter>
</Style> </Style>
<Style Selector="controls|NumberBox /template/ TextBox.NumberBoxTextBoxStyle /template/ TextBlock#PART_Prefix"> <Style Selector="controls|NumberBox /template/ TextBox#InputBox">
<Setter Property="Foreground" Value="{DynamicResource TextControlForegroundDisabled}"></Setter>
<Setter Property="Margin" Value="-4 0 -12 0"></Setter>
</Style>
<Style Selector="controls|NumberBox /template/ TextBox.NumberBoxTextBoxStyle /template/ TextBlock#PART_Suffix">
<Setter Property="Foreground" Value="{DynamicResource TextControlForegroundDisabled}"></Setter>
<Setter Property="Margin" Value="-12 0 0 0"></Setter>
</Style>
<Style Selector="controls|NumberBox.condensed /template/ TextBox.NumberBoxTextBoxStyle /template/ TextBlock#PART_Prefix">
<Setter Property="Margin" Value="0 0 -4 0"></Setter>
</Style>
<Style Selector="controls|NumberBox.condensed /template/ TextBox.NumberBoxTextBoxStyle /template/ TextBlock#PART_Suffix">
<Setter Property="Margin" Value="-4 0 0 0"></Setter>
</Style>
<Style Selector="controls|NumberBox /template/ TextBox.NumberBoxTextBoxStyle">
<Setter Property="attachedProperties:TextBoxAssist.PrefixText" Value="{TemplateBinding attachedProperties:NumberBoxAssist.PrefixText}"></Setter> <Setter Property="attachedProperties:TextBoxAssist.PrefixText" Value="{TemplateBinding attachedProperties:NumberBoxAssist.PrefixText}"></Setter>
<Setter Property="attachedProperties:TextBoxAssist.SuffixText" Value="{TemplateBinding attachedProperties:NumberBoxAssist.SuffixText}"></Setter> <Setter Property="attachedProperties:TextBoxAssist.SuffixText" Value="{TemplateBinding attachedProperties:NumberBoxAssist.SuffixText}"></Setter>
</Style> </Style>

View File

@ -5,7 +5,21 @@
<Design.PreviewWith> <Design.PreviewWith>
<Border Padding="20"> <Border Padding="20">
<StackPanel Spacing="20"> <StackPanel Spacing="20">
<TextBox Width="200" Height="150" AcceptsReturn="True" xml:space="preserve">asdasdas asd
asdasd
asd
asd
asd
as
fdsfsdf
sdg
sdg
sdg
</TextBox>
<TextBox Width="200" Height="150" AcceptsReturn="True" xml:space="preserve">asdasdas asd
asdasd
asd
as</TextBox>
<!-- Add Controls for Previewer Here --> <!-- Add Controls for Previewer Here -->
<TextBox Text="99999999" <TextBox Text="99999999"
@ -46,12 +60,10 @@
<Border Margin="{TemplateBinding BorderThickness}"> <Border Margin="{TemplateBinding BorderThickness}">
<Grid ColumnDefinitions="Auto,*,Auto"> <Grid ColumnDefinitions="Auto,*,Auto">
<ContentPresenter Grid.Column="0" <ContentPresenter Grid.Column="0" Content="{TemplateBinding InnerLeftContent}" />
Grid.ColumnSpan="1"
Content="{TemplateBinding InnerLeftContent}" />
<Grid x:Name="PART_InnerGrid" <Grid x:Name="PART_InnerGrid"
Grid.Column="1" Grid.Column="1"
RowDefinitions="Auto,Auto" RowDefinitions="Auto,*"
ColumnDefinitions="Auto,*,Auto" ColumnDefinitions="Auto,*,Auto"
Cursor="IBeam" Cursor="IBeam"
Margin="{TemplateBinding Padding}"> Margin="{TemplateBinding Padding}">

View File

@ -18,6 +18,9 @@ public static class UI
{ {
private static readonly BehaviorSubject<bool> MicaEnabledSubject = new(false); private static readonly BehaviorSubject<bool> MicaEnabledSubject = new(false);
/// <summary>
/// Gets the background event loop scheduler.
/// </summary>
public static EventLoopScheduler BackgroundScheduler = new(ts => new Thread(ts)); public static EventLoopScheduler BackgroundScheduler = new(ts => new Thread(ts));
static UI() static UI()

View File

@ -1,119 +0,0 @@
// Heavily based on:
// SkyClip
// - ProgressableStreamContent.cs
// --------------------------------------------------------------------
// Author: Jeff Hansen <jeff@jeffijoe.com>
// Copyright (C) Jeff Hansen 2015. All rights reserved.
using System;
using System.IO;
using System.Net;
using System.Net.Http;
using System.Threading;
using System.Threading.Tasks;
namespace Artemis.UI.Shared.Utilities;
/// <summary>
/// Provides HTTP content based on a stream with support for IProgress.
/// </summary>
public class ProgressableStreamContent : StreamContent
{
private const int DEFAULT_BUFFER_SIZE = 4096;
private readonly int _bufferSize;
private readonly IProgress<StreamProgress> _progress;
private readonly Stream _streamToWrite;
private bool _contentConsumed;
/// <summary>
/// Initializes a new instance of the <see cref="ProgressableStreamContent" /> class.
/// </summary>
/// <param name="streamToWrite">The stream to write.</param>
/// <param name="progress">The downloader.</param>
public ProgressableStreamContent(Stream streamToWrite, IProgress<StreamProgress> progress) : this(streamToWrite, DEFAULT_BUFFER_SIZE, progress)
{
}
/// <summary>
/// Initializes a new instance of the <see cref="ProgressableStreamContent" /> class.
/// </summary>
/// <param name="streamToWrite">The stream to write.</param>
/// <param name="bufferSize">The buffer size.</param>
/// <param name="progress">The downloader.</param>
public ProgressableStreamContent(Stream streamToWrite, int bufferSize, IProgress<StreamProgress> progress) : base(streamToWrite, bufferSize)
{
if (bufferSize <= 0)
throw new ArgumentOutOfRangeException(nameof(bufferSize));
_streamToWrite = streamToWrite;
_bufferSize = bufferSize;
_progress = progress;
}
/// <inheritdoc />
protected override void Dispose(bool disposing)
{
if (disposing)
_streamToWrite.Dispose();
base.Dispose(disposing);
}
/// <inheritdoc />
protected override async Task SerializeToStreamAsync(Stream stream, TransportContext? context)
{
await SerializeToStreamAsync(stream, context, CancellationToken.None);
}
/// <inheritdoc />
protected override async Task SerializeToStreamAsync(Stream stream, TransportContext? context, CancellationToken cancellationToken)
{
PrepareContent();
byte[] buffer = new byte[_bufferSize];
long size = _streamToWrite.Length;
int uploaded = 0;
await using (_streamToWrite)
{
while (!cancellationToken.IsCancellationRequested)
{
int length = await _streamToWrite.ReadAsync(buffer, cancellationToken);
if (length <= 0)
break;
uploaded += length;
_progress.Report(new StreamProgress(uploaded, size));
await stream.WriteAsync(buffer, 0, length, cancellationToken);
}
}
}
/// <inheritdoc />
protected override bool TryComputeLength(out long length)
{
length = _streamToWrite.Length;
return true;
}
/// <summary>
/// Prepares the content.
/// </summary>
/// <exception cref="System.InvalidOperationException">The stream has already been read.</exception>
private void PrepareContent()
{
if (_contentConsumed)
{
// If the content needs to be written to a target stream a 2nd time, then the stream must support
// seeking (e.g. a FileStream), otherwise the stream can't be copied a second time to a target
// stream (e.g. a NetworkStream).
if (_streamToWrite.CanSeek)
_streamToWrite.Position = 0;
else
throw new InvalidOperationException("The stream has already been read.");
}
_contentConsumed = true;
}
}

View File

@ -1,5 +1,6 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.ComponentModel;
using System.Runtime.CompilerServices; using System.Runtime.CompilerServices;
using Artemis.UI.Shared.Events; using Artemis.UI.Shared.Events;
using FluentAvalonia.UI.Controls; using FluentAvalonia.UI.Controls;
@ -100,6 +101,24 @@ public abstract class ValidatableViewModelBase : ReactiveValidationObject, IActi
/// <inheritdoc /> /// <inheritdoc />
public ViewModelActivator Activator { get; } = new(); public ViewModelActivator Activator { get; } = new();
/// <summary>
/// Raises the property changed event for the provided property.
/// </summary>
/// <param name="args">The event arguments containing the name of the property that changed.</param>
protected virtual void OnPropertyChanged(PropertyChangedEventArgs args)
{
this.RaisePropertyChanged(args.PropertyName);
}
/// <summary>
/// Raises the property changing event for the provided property.
/// </summary>
/// <param name="args">The event arguments containing the name of the property that is changing.</param>
protected virtual void OnPropertyChanging(PropertyChangingEventArgs args)
{
this.RaisePropertyChanging(args.PropertyName);
}
} }
/// <summary> /// <summary>
@ -153,4 +172,22 @@ public abstract class ViewModelBase : ReactiveObject
this.RaisePropertyChanged(propertyName); this.RaisePropertyChanged(propertyName);
return newValue; return newValue;
} }
/// <summary>
/// Raises the property changed event for the provided property.
/// </summary>
/// <param name="args">The event arguments containing the name of the property that changed.</param>
protected virtual void OnPropertyChanged(PropertyChangedEventArgs args)
{
this.RaisePropertyChanged(args.PropertyName);
}
/// <summary>
/// Raises the property changing event for the provided property.
/// </summary>
/// <param name="args">The event arguments containing the name of the property that is changing.</param>
protected virtual void OnPropertyChanging(PropertyChangingEventArgs args)
{
this.RaisePropertyChanging(args.PropertyName);
}
} }

View File

@ -1,6 +1,4 @@
<Project Sdk="Microsoft.NET.Sdk"> <Project Sdk="Microsoft.NET.Sdk">
<Import Project="..\Artemis.props" />
<PropertyGroup> <PropertyGroup>
<OutputType>WinExe</OutputType> <OutputType>WinExe</OutputType>
<TargetFramework>net7.0-windows10.0.17763.0</TargetFramework> <TargetFramework>net7.0-windows10.0.17763.0</TargetFramework>
@ -23,18 +21,13 @@
</None> </None>
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="Avalonia" Version="$(AvaloniaVersion)" /> <PackageReference Include="Avalonia.Win32" Version="11.0.6" />
<PackageReference Include="Avalonia.Desktop" Version="$(AvaloniaVersion)" />
<!--Condition below is needed to remove Avalonia.Diagnostics package from build output in Release configuration.-->
<PackageReference Condition="'$(Configuration)' == 'Debug'" Include="Avalonia.Diagnostics" Version="$(AvaloniaVersion)" />
<PackageReference Include="Avalonia.ReactiveUI" Version="$(AvaloniaVersion)" />
<PackageReference Include="Avalonia.Win32" Version="$(AvaloniaVersion)" />
<PackageReference Include="Microsoft.Toolkit.Uwp.Notifications" Version="7.1.3" /> <PackageReference Include="Microsoft.Toolkit.Uwp.Notifications" Version="7.1.3" />
<PackageReference Include="Microsoft.Win32" Version="2.0.1" /> <PackageReference Include="Microsoft.Win32" Version="2.0.1" />
<!-- Note: Do NOT upgrade this compatibility package to 8.X before updating to net8, it WILL break -->
<PackageReference Include="Microsoft.Windows.Compatibility" Version="7.0.5" /> <PackageReference Include="Microsoft.Windows.Compatibility" Version="7.0.5" />
<PackageReference Include="RawInput.Sharp" Version="0.1.1" /> <PackageReference Include="RawInput.Sharp" Version="0.1.3" />
<PackageReference Include="ReactiveUI" Version="19.4.1" /> <PackageReference Include="SkiaSharp.Vulkan.SharpVk" Version="2.88.7" />
<PackageReference Include="SkiaSharp.Vulkan.SharpVk" Version="$(SkiaSharpVersion)" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<ProjectReference Include="..\Artemis.Core\Artemis.Core.csproj" /> <ProjectReference Include="..\Artemis.Core\Artemis.Core.csproj" />

View File

@ -9,17 +9,19 @@ namespace Artemis.UI.Windows.Providers;
public class ProtocolProvider : IProtocolProvider public class ProtocolProvider : IProtocolProvider
{ {
/// <inheritdoc /> /// <inheritdoc />
public async Task AssociateWithProtocol(string protocol) public Task AssociateWithProtocol(string protocol)
{ {
string key = $"HKEY_CURRENT_USER\\Software\\Classes\\{protocol}"; string key = $"HKEY_CURRENT_USER\\Software\\Classes\\{protocol}";
Registry.SetValue($"{key}", null, "URL:artemis protocol"); Registry.SetValue($"{key}", null, "URL:artemis protocol");
Registry.SetValue($"{key}", "URL Protocol", ""); Registry.SetValue($"{key}", "URL Protocol", "");
Registry.SetValue($"{key}\\DefaultIcon", null, $"\"{Constants.ExecutablePath}\",1"); Registry.SetValue($"{key}\\DefaultIcon", null, $"\"{Constants.ExecutablePath}\",1");
Registry.SetValue($"{key}\\shell\\open\\command", null, $"\"{Constants.ExecutablePath}\", \"--route=%1\""); Registry.SetValue($"{key}\\shell\\open\\command", null, $"\"{Constants.ExecutablePath}\", \"--route=%1\"");
return Task.CompletedTask;
} }
/// <inheritdoc /> /// <inheritdoc />
public async Task DisassociateWithProtocol(string protocol) public Task DisassociateWithProtocol(string protocol)
{ {
try try
{ {
@ -30,5 +32,7 @@ public class ProtocolProvider : IProtocolProvider
{ {
// Ignore errors (which means that the protocol wasn't associated before) // Ignore errors (which means that the protocol wasn't associated before)
} }
return Task.CompletedTask;
} }
} }

View File

@ -1,6 +1,4 @@
<Project Sdk="Microsoft.NET.Sdk"> <Project Sdk="Microsoft.NET.Sdk">
<Import Project="..\Artemis.props" />
<PropertyGroup> <PropertyGroup>
<OutputType>Library</OutputType> <OutputType>Library</OutputType>
<TargetFramework>net7.0</TargetFramework> <TargetFramework>net7.0</TargetFramework>
@ -18,31 +16,19 @@
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="AsyncImageLoader.Avalonia" Version="3.2.0" /> <PackageReference Include="AsyncImageLoader.Avalonia" Version="3.2.1" />
<PackageReference Include="Avalonia" Version="$(AvaloniaVersion)" /> <PackageReference Include="Avalonia.AvaloniaEdit" Version="11.0.6" />
<PackageReference Include="Avalonia.AvaloniaEdit" Version="11.0.1" /> <PackageReference Include="Avalonia.Controls.PanAndZoom" Version="11.0.0.2" />
<PackageReference Include="Avalonia.Controls.PanAndZoom" Version="11.0.0" /> <PackageReference Include="Avalonia.Desktop" Version="11.0.6" />
<PackageReference Include="Avalonia.Desktop" Version="$(AvaloniaVersion)" />
<!--Condition below is needed to remove Avalonia.Diagnostics package from build output in Release configuration.-->
<PackageReference Condition="'$(Configuration)' == 'Debug'" Include="Avalonia.Diagnostics" Version="$(AvaloniaVersion)" />
<PackageReference Include="Avalonia.Controls.ItemsRepeater" Version="$(AvaloniaVersion)" />
<PackageReference Include="Avalonia.ReactiveUI" Version="$(AvaloniaVersion)" />
<PackageReference Include="Avalonia.Xaml.Behaviors" Version="$(AvaloniaBehavioursVersion)" />
<PackageReference Include="Avalonia.Skia.Lottie" Version="11.0.0" /> <PackageReference Include="Avalonia.Skia.Lottie" Version="11.0.0" />
<PackageReference Include="AvaloniaEdit.TextMate" Version="11.0.1" /> <PackageReference Include="AvaloniaEdit.TextMate" Version="11.0.6" />
<PackageReference Include="DryIoc.dll" Version="5.4.1" />
<PackageReference Include="DynamicData" Version="7.14.2" />
<PackageReference Include="FluentAvaloniaUI" Version="$(FluentAvaloniaVersion)" />
<PackageReference Include="Flurl.Http" Version="3.2.4" />
<PackageReference Include="Markdown.Avalonia.Tight" Version="11.0.2" /> <PackageReference Include="Markdown.Avalonia.Tight" Version="11.0.2" />
<PackageReference Include="Material.Icons.Avalonia" Version="2.0.1" /> <PackageReference Include="Octopus.Octodiff" Version="2.0.544" />
<PackageReference Include="Octopus.Octodiff" Version="2.0.326" /> <PackageReference Include="PropertyChanged.SourceGenerator" Version="1.1.0">
<PackageReference Include="ReactiveUI" Version="19.4.1" /> <PrivateAssets>all</PrivateAssets>
<PackageReference Include="ReactiveUI.Validation" Version="3.1.7" /> <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
<PackageReference Include="RGB.NET.Core" Version="$(RGBDotNetVersion)" /> </PackageReference>
<PackageReference Include="RGB.NET.Layout" Version="$(RGBDotNetVersion)" /> <PackageReference Include="Splat.DryIoc" Version="14.8.12" />
<PackageReference Include="SkiaSharp" Version="$(SkiaSharpVersion)" />
<PackageReference Include="Splat.DryIoc" Version="14.7.1" />
<PackageReference Include="TextMateSharp.Grammars" Version="1.0.56" /> <PackageReference Include="TextMateSharp.Grammars" Version="1.0.56" />
</ItemGroup> </ItemGroup>

View File

@ -64,7 +64,7 @@ public static class ArtemisBootstrapper
if (_application.ApplicationLifetime is not IClassicDesktopStyleApplicationLifetime desktop) if (_application.ApplicationLifetime is not IClassicDesktopStyleApplicationLifetime desktop)
return; return;
Constants.StartupArguments = new ReadOnlyCollection<string>(new List<string>(desktop.Args)); Constants.StartupArguments = new ReadOnlyCollection<string>(desktop.Args != null ? new List<string>(desktop.Args) : new List<string>());
// Don't shut down when the last window closes, we might still be active in the tray // Don't shut down when the last window closes, we might still be active in the tray
desktop.ShutdownMode = ShutdownMode.OnExplicitShutdown; desktop.ShutdownMode = ShutdownMode.OnExplicitShutdown;

File diff suppressed because one or more lines are too long

View File

@ -1,6 +1,7 @@
using System.Reflection; using System.Reflection;
using Artemis.UI.DryIoc.Factories; using Artemis.UI.DryIoc.Factories;
using Artemis.UI.DryIoc.InstanceProviders; using Artemis.UI.DryIoc.InstanceProviders;
using Artemis.UI.Screens.Device.Layout.LayoutProviders;
using Artemis.UI.Screens.VisualScripting; using Artemis.UI.Screens.VisualScripting;
using Artemis.UI.Services.Interfaces; using Artemis.UI.Services.Interfaces;
using Artemis.UI.Services.Updating; using Artemis.UI.Services.Updating;
@ -26,6 +27,7 @@ public static class ContainerExtensions
container.RegisterMany(thisAssembly, type => type.IsAssignableTo<ViewModelBase>(), setup: Setup.With(preventDisposal: true)); container.RegisterMany(thisAssembly, type => type.IsAssignableTo<ViewModelBase>(), setup: Setup.With(preventDisposal: true));
container.RegisterMany(thisAssembly, type => type.IsAssignableTo<IToolViewModel>() && type.IsInterface, setup: Setup.With(preventDisposal: true)); container.RegisterMany(thisAssembly, type => type.IsAssignableTo<IToolViewModel>() && type.IsInterface, setup: Setup.With(preventDisposal: true));
container.RegisterMany(thisAssembly, type => type.IsAssignableTo<ILayoutProviderViewModel>() && type.IsInterface, setup: Setup.With(preventDisposal: true));
container.RegisterMany(thisAssembly, type => type.IsAssignableTo<IVmFactory>() && type != typeof(PropertyVmFactory)); container.RegisterMany(thisAssembly, type => type.IsAssignableTo<IVmFactory>() && type != typeof(PropertyVmFactory));
container.Register<NodeScriptWindowViewModelBase, NodeScriptWindowViewModel>(Reuse.Singleton); container.Register<NodeScriptWindowViewModelBase, NodeScriptWindowViewModel>(Reuse.Singleton);

View File

@ -1,13 +1,14 @@
using System; using System.Collections.ObjectModel;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Reactive; using System.Reactive;
using Artemis.Core; using Artemis.Core;
using Artemis.Core.LayerBrushes; using Artemis.Core.LayerBrushes;
using Artemis.Core.LayerEffects; using Artemis.Core.LayerEffects;
using Artemis.Core.ScriptingProviders; using Artemis.Core.ScriptingProviders;
using Artemis.UI.Routing;
using Artemis.UI.Screens.Device; using Artemis.UI.Screens.Device;
using Artemis.UI.Screens.Device.General;
using Artemis.UI.Screens.Device.InputMappings;
using Artemis.UI.Screens.Device.Layout;
using Artemis.UI.Screens.Device.Leds;
using Artemis.UI.Screens.Plugins; using Artemis.UI.Screens.Plugins;
using Artemis.UI.Screens.Plugins.Features; using Artemis.UI.Screens.Plugins.Features;
using Artemis.UI.Screens.Plugins.Prerequisites; using Artemis.UI.Screens.Plugins.Prerequisites;
@ -27,12 +28,8 @@ using Artemis.UI.Screens.Sidebar;
using Artemis.UI.Screens.SurfaceEditor; using Artemis.UI.Screens.SurfaceEditor;
using Artemis.UI.Screens.VisualScripting; using Artemis.UI.Screens.VisualScripting;
using Artemis.UI.Screens.VisualScripting.Pins; using Artemis.UI.Screens.VisualScripting.Pins;
using Artemis.UI.Shared;
using Artemis.UI.Shared.Routing;
using Artemis.WebClient.Updating; using Artemis.WebClient.Updating;
using DryIoc; using DryIoc;
using DynamicData;
using Material.Icons;
using ReactiveUI; using ReactiveUI;
namespace Artemis.UI.DryIoc.Factories; namespace Artemis.UI.DryIoc.Factories;
@ -51,6 +48,7 @@ public interface IDeviceVmFactory : IVmFactory
InputMappingsTabViewModel InputMappingsTabViewModel(ArtemisDevice device, ObservableCollection<ArtemisLed> selectedLeds); InputMappingsTabViewModel InputMappingsTabViewModel(ArtemisDevice device, ObservableCollection<ArtemisLed> selectedLeds);
DeviceGeneralTabViewModel DeviceGeneralTabViewModel(ArtemisDevice device); DeviceGeneralTabViewModel DeviceGeneralTabViewModel(ArtemisDevice device);
} }
public class DeviceFactory : IDeviceVmFactory public class DeviceFactory : IDeviceVmFactory
{ {
private readonly IContainer _container; private readonly IContainer _container;
@ -102,6 +100,7 @@ public interface ISettingsVmFactory : IVmFactory
PluginViewModel PluginViewModel(Plugin plugin, ReactiveCommand<Unit, Unit>? reload); PluginViewModel PluginViewModel(Plugin plugin, ReactiveCommand<Unit, Unit>? reload);
PluginFeatureViewModel PluginFeatureViewModel(PluginFeatureInfo pluginFeatureInfo, bool showShield); PluginFeatureViewModel PluginFeatureViewModel(PluginFeatureInfo pluginFeatureInfo, bool showShield);
} }
public class SettingsVmFactory : ISettingsVmFactory public class SettingsVmFactory : ISettingsVmFactory
{ {
private readonly IContainer _container; private readonly IContainer _container;
@ -132,6 +131,7 @@ public interface ISidebarVmFactory : IVmFactory
SidebarCategoryViewModel SidebarCategoryViewModel(ProfileCategory profileCategory); SidebarCategoryViewModel SidebarCategoryViewModel(ProfileCategory profileCategory);
SidebarProfileConfigurationViewModel SidebarProfileConfigurationViewModel(ProfileConfiguration profileConfiguration); SidebarProfileConfigurationViewModel SidebarProfileConfigurationViewModel(ProfileConfiguration profileConfiguration);
} }
public class SidebarVmFactory : ISidebarVmFactory public class SidebarVmFactory : ISidebarVmFactory
{ {
private readonly IContainer _container; private readonly IContainer _container;
@ -155,8 +155,9 @@ public class SidebarVmFactory : ISidebarVmFactory
public interface ISurfaceVmFactory : IVmFactory public interface ISurfaceVmFactory : IVmFactory
{ {
SurfaceDeviceViewModel SurfaceDeviceViewModel(ArtemisDevice device, SurfaceEditorViewModel surfaceEditorViewModel); SurfaceDeviceViewModel SurfaceDeviceViewModel(ArtemisDevice device, SurfaceEditorViewModel surfaceEditorViewModel);
ListDeviceViewModel ListDeviceViewModel(ArtemisDevice device, SurfaceEditorViewModel surfaceEditorViewModel); ListDeviceViewModel ListDeviceViewModel(ArtemisDevice device);
} }
public class SurfaceVmFactory : ISurfaceVmFactory public class SurfaceVmFactory : ISurfaceVmFactory
{ {
private readonly IContainer _container; private readonly IContainer _container;
@ -171,9 +172,9 @@ public class SurfaceVmFactory : ISurfaceVmFactory
return _container.Resolve<SurfaceDeviceViewModel>(new object[] {device, surfaceEditorViewModel}); return _container.Resolve<SurfaceDeviceViewModel>(new object[] {device, surfaceEditorViewModel});
} }
public ListDeviceViewModel ListDeviceViewModel(ArtemisDevice device, SurfaceEditorViewModel surfaceEditorViewModel) public ListDeviceViewModel ListDeviceViewModel(ArtemisDevice device)
{ {
return _container.Resolve<ListDeviceViewModel>(new object[] { device, surfaceEditorViewModel }); return _container.Resolve<ListDeviceViewModel>(new object[] {device});
} }
} }
@ -181,6 +182,7 @@ public interface IPrerequisitesVmFactory : IVmFactory
{ {
PluginPrerequisiteViewModel PluginPrerequisiteViewModel(PluginPrerequisite pluginPrerequisite, bool uninstall); PluginPrerequisiteViewModel PluginPrerequisiteViewModel(PluginPrerequisite pluginPrerequisite, bool uninstall);
} }
public class PrerequisitesVmFactory : IPrerequisitesVmFactory public class PrerequisitesVmFactory : IPrerequisitesVmFactory
{ {
private readonly IContainer _container; private readonly IContainer _container;
@ -204,6 +206,7 @@ public interface IProfileEditorVmFactory : IVmFactory
LayerShapeVisualizerViewModel LayerShapeVisualizerViewModel(Layer layer); LayerShapeVisualizerViewModel LayerShapeVisualizerViewModel(Layer layer);
LayerVisualizerViewModel LayerVisualizerViewModel(Layer layer); LayerVisualizerViewModel LayerVisualizerViewModel(Layer layer);
} }
public class ProfileEditorVmFactory : IProfileEditorVmFactory public class ProfileEditorVmFactory : IProfileEditorVmFactory
{ {
private readonly IContainer _container; private readonly IContainer _container;
@ -251,6 +254,7 @@ public interface ILayerPropertyVmFactory : IVmFactory
TimelineViewModel TimelineViewModel(ObservableCollection<PropertyGroupViewModel> propertyGroupViewModels); TimelineViewModel TimelineViewModel(ObservableCollection<PropertyGroupViewModel> propertyGroupViewModels);
TimelineGroupViewModel TimelineGroupViewModel(PropertyGroupViewModel propertyGroupViewModel); TimelineGroupViewModel TimelineGroupViewModel(PropertyGroupViewModel propertyGroupViewModel);
} }
public class LayerPropertyVmFactory : ILayerPropertyVmFactory public class LayerPropertyVmFactory : ILayerPropertyVmFactory
{ {
private readonly IContainer _container; private readonly IContainer _container;
@ -300,6 +304,7 @@ public interface IDataBindingVmFactory : IVmFactory
{ {
DataBindingViewModel DataBindingViewModel(); DataBindingViewModel DataBindingViewModel();
} }
public class DataBindingVmFactory : IDataBindingVmFactory public class DataBindingVmFactory : IDataBindingVmFactory
{ {
private readonly IContainer _container; private readonly IContainer _container;
@ -333,6 +338,7 @@ public interface INodeVmFactory : IVmFactory
InputPinCollectionViewModel InputPinCollectionViewModel(IPinCollection inputPinCollection, NodeScriptViewModel nodeScriptViewModel); InputPinCollectionViewModel InputPinCollectionViewModel(IPinCollection inputPinCollection, NodeScriptViewModel nodeScriptViewModel);
OutputPinCollectionViewModel OutputPinCollectionViewModel(IPinCollection outputPinCollection, NodeScriptViewModel nodeScriptViewModel); OutputPinCollectionViewModel OutputPinCollectionViewModel(IPinCollection outputPinCollection, NodeScriptViewModel nodeScriptViewModel);
} }
public class NodeVmFactory : INodeVmFactory public class NodeVmFactory : INodeVmFactory
{ {
private readonly IContainer _container; private readonly IContainer _container;
@ -395,6 +401,7 @@ public interface IConditionVmFactory : IVmFactory
StaticConditionViewModel StaticConditionViewModel(StaticCondition staticCondition); StaticConditionViewModel StaticConditionViewModel(StaticCondition staticCondition);
EventConditionViewModel EventConditionViewModel(EventCondition eventCondition); EventConditionViewModel EventConditionViewModel(EventCondition eventCondition);
} }
public class ConditionVmFactory : IConditionVmFactory public class ConditionVmFactory : IConditionVmFactory
{ {
private readonly IContainer _container; private readonly IContainer _container;
@ -432,6 +439,7 @@ public interface ILayerHintVmFactory : IVmFactory
KeyboardSectionAdaptionHintViewModel KeyboardSectionAdaptionHintViewModel(Layer layer, KeyboardSectionAdaptionHint adaptionHint); KeyboardSectionAdaptionHintViewModel KeyboardSectionAdaptionHintViewModel(Layer layer, KeyboardSectionAdaptionHint adaptionHint);
SingleLedAdaptionHintViewModel SingleLedAdaptionHintViewModel(Layer layer, SingleLedAdaptionHint adaptionHint); SingleLedAdaptionHintViewModel SingleLedAdaptionHintViewModel(Layer layer, SingleLedAdaptionHint adaptionHint);
} }
public class LayerHintVmFactory : ILayerHintVmFactory public class LayerHintVmFactory : ILayerHintVmFactory
{ {
private readonly IContainer _container; private readonly IContainer _container;
@ -467,6 +475,7 @@ public interface IScriptVmFactory : IVmFactory
ScriptConfigurationViewModel ScriptConfigurationViewModel(ScriptConfiguration scriptConfiguration); ScriptConfigurationViewModel ScriptConfigurationViewModel(ScriptConfiguration scriptConfiguration);
ScriptConfigurationViewModel ScriptConfigurationViewModel(Profile profile, ScriptConfiguration scriptConfiguration); ScriptConfigurationViewModel ScriptConfigurationViewModel(Profile profile, ScriptConfiguration scriptConfiguration);
} }
public class ScriptVmFactory : IScriptVmFactory public class ScriptVmFactory : IScriptVmFactory
{ {
private readonly IContainer _container; private readonly IContainer _container;
@ -491,6 +500,7 @@ public interface IReleaseVmFactory : IVmFactory
{ {
ReleaseViewModel ReleaseListViewModel(IGetReleases_PublishedReleases_Nodes release); ReleaseViewModel ReleaseListViewModel(IGetReleases_PublishedReleases_Nodes release);
} }
public class ReleaseVmFactory : IReleaseVmFactory public class ReleaseVmFactory : IReleaseVmFactory
{ {
private readonly IContainer _container; private readonly IContainer _container;

View File

@ -6,19 +6,21 @@ using Artemis.UI.Screens.Debugger.Logs;
using Artemis.UI.Screens.Debugger.Performance; using Artemis.UI.Screens.Debugger.Performance;
using Artemis.UI.Screens.Debugger.Render; using Artemis.UI.Screens.Debugger.Render;
using Artemis.UI.Screens.Debugger.Routing; using Artemis.UI.Screens.Debugger.Routing;
using Artemis.UI.Screens.Debugger.Workshop;
using Artemis.UI.Services.Interfaces; using Artemis.UI.Services.Interfaces;
using Artemis.UI.Shared; using Artemis.UI.Shared;
using PropertyChanged.SourceGenerator;
using ReactiveUI; using ReactiveUI;
namespace Artemis.UI.Screens.Debugger; namespace Artemis.UI.Screens.Debugger;
public class DebugViewModel : ActivatableViewModelBase, IScreen public partial class DebugViewModel : ActivatableViewModelBase, IScreen
{ {
private ViewModelBase _selectedItem; [Notify] private ViewModelBase _selectedItem;
public DebugViewModel(IDebugService debugService, RenderDebugViewModel render, DataModelDebugViewModel dataModel, PerformanceDebugViewModel performance, RoutingDebugViewModel routing, LogsDebugViewModel logs) public DebugViewModel(IDebugService debugService, RenderDebugViewModel render, DataModelDebugViewModel dataModel, PerformanceDebugViewModel performance, RoutingDebugViewModel routing, WorkshopDebugViewModel workshop, LogsDebugViewModel logs)
{ {
Items = new ObservableCollection<ViewModelBase> {render, dataModel, performance, routing, logs}; Items = new ObservableCollection<ViewModelBase> {render, dataModel, performance, routing, workshop, logs};
_selectedItem = render; _selectedItem = render;
this.WhenActivated(d => Disposable.Create(debugService.ClearDebugger).DisposeWith(d)); this.WhenActivated(d => Disposable.Create(debugService.ClearDebugger).DisposeWith(d));
@ -26,12 +28,6 @@ public class DebugViewModel : ActivatableViewModelBase, IScreen
public ObservableCollection<ViewModelBase> Items { get; } public ObservableCollection<ViewModelBase> Items { get; }
public ViewModelBase SelectedItem
{
get => _selectedItem;
set => RaiseAndSetIfChanged(ref _selectedItem, value);
}
public void Activate() public void Activate()
{ {
OnActivationRequested(); OnActivationRequested();

View File

@ -12,21 +12,21 @@ using Artemis.UI.Shared.DataModelVisualization.Shared;
using Artemis.UI.Shared.Services; using Artemis.UI.Shared.Services;
using Avalonia.Threading; using Avalonia.Threading;
using DynamicData; using DynamicData;
using PropertyChanged.SourceGenerator;
using ReactiveUI; using ReactiveUI;
namespace Artemis.UI.Screens.Debugger.DataModel; namespace Artemis.UI.Screens.Debugger.DataModel;
public class DataModelDebugViewModel : ActivatableViewModelBase public partial class DataModelDebugViewModel : ActivatableViewModelBase
{ {
private readonly IDataModelUIService _dataModelUIService; private readonly IDataModelUIService _dataModelUIService;
private readonly IPluginManagementService _pluginManagementService; private readonly IPluginManagementService _pluginManagementService;
private readonly DispatcherTimer _updateTimer; private readonly DispatcherTimer _updateTimer;
private bool _isModuleFilterEnabled; private bool _isModuleFilterEnabled;
private DataModelPropertiesViewModel? _mainDataModel;
private string? _propertySearch;
private Module? _selectedModule; private Module? _selectedModule;
private bool _slowUpdates; private bool _slowUpdates;
[Notify] private DataModelPropertiesViewModel? _mainDataModel;
[Notify] private string? _propertySearch;
public DataModelDebugViewModel(IDataModelUIService dataModelUIService, IPluginManagementService pluginManagementService) public DataModelDebugViewModel(IDataModelUIService dataModelUIService, IPluginManagementService pluginManagementService)
{ {
@ -57,18 +57,6 @@ public class DataModelDebugViewModel : ActivatableViewModelBase
}); });
} }
public DataModelPropertiesViewModel? MainDataModel
{
get => _mainDataModel;
set => RaiseAndSetIfChanged(ref _mainDataModel, value);
}
public string? PropertySearch
{
get => _propertySearch;
set => RaiseAndSetIfChanged(ref _propertySearch, value);
}
public bool SlowUpdates public bool SlowUpdates
{ {
get => _slowUpdates; get => _slowUpdates;

View File

@ -1,16 +1,17 @@
using Artemis.Core; using Artemis.Core;
using Artemis.UI.Shared; using Artemis.UI.Shared;
using PropertyChanged.SourceGenerator;
namespace Artemis.UI.Screens.Debugger.Performance; namespace Artemis.UI.Screens.Debugger.Performance;
public class PerformanceDebugMeasurementViewModel : ViewModelBase public partial class PerformanceDebugMeasurementViewModel : ViewModelBase
{ {
private string? _average; [Notify] private string? _average;
private string? _last; [Notify] private string? _last;
private string? _max; [Notify] private string? _max;
private string? _min; [Notify] private string? _min;
private string? _percentile; [Notify] private string? _percentile;
private string? _count; [Notify] private string? _count;
public PerformanceDebugMeasurementViewModel(ProfilingMeasurement measurement) public PerformanceDebugMeasurementViewModel(ProfilingMeasurement measurement)
{ {
@ -19,42 +20,6 @@ public class PerformanceDebugMeasurementViewModel : ViewModelBase
public ProfilingMeasurement Measurement { get; } public ProfilingMeasurement Measurement { get; }
public string? Last
{
get => _last;
set => RaiseAndSetIfChanged(ref _last, value);
}
public string? Average
{
get => _average;
set => RaiseAndSetIfChanged(ref _average, value);
}
public string? Min
{
get => _min;
set => RaiseAndSetIfChanged(ref _min, value);
}
public string? Max
{
get => _max;
set => RaiseAndSetIfChanged(ref _max, value);
}
public string? Percentile
{
get => _percentile;
set => RaiseAndSetIfChanged(ref _percentile, value);
}
public string? Count
{
get => _count;
set => RaiseAndSetIfChanged(ref _count, value);
}
public void Update() public void Update()
{ {
Last = Measurement.GetLast().TotalMilliseconds + " ms"; Last = Measurement.GetLast().TotalMilliseconds + " ms";

View File

@ -7,20 +7,21 @@ using Artemis.Core;
using Artemis.Core.Services; using Artemis.Core.Services;
using Artemis.UI.Shared; using Artemis.UI.Shared;
using Avalonia.Threading; using Avalonia.Threading;
using PropertyChanged.SourceGenerator;
using ReactiveUI; using ReactiveUI;
using SkiaSharp; using SkiaSharp;
namespace Artemis.UI.Screens.Debugger.Performance; namespace Artemis.UI.Screens.Debugger.Performance;
public class PerformanceDebugViewModel : ActivatableViewModelBase public partial class PerformanceDebugViewModel : ActivatableViewModelBase
{ {
private readonly IRenderService _renderService; private readonly IRenderService _renderService;
private readonly IPluginManagementService _pluginManagementService; private readonly IPluginManagementService _pluginManagementService;
private readonly DispatcherTimer _updateTimer; private readonly DispatcherTimer _updateTimer;
private double _currentFps; [Notify] private double _currentFps;
private string? _renderer; [Notify] private string? _renderer;
private int _renderHeight; [Notify] private int _renderHeight;
private int _renderWidth; [Notify] private int _renderWidth;
public PerformanceDebugViewModel(IRenderService renderService, IPluginManagementService pluginManagementService) public PerformanceDebugViewModel(IRenderService renderService, IPluginManagementService pluginManagementService)
{ {
@ -60,30 +61,6 @@ public class PerformanceDebugViewModel : ActivatableViewModelBase
public ObservableCollection<PerformanceDebugPluginViewModel> Items { get; } = new(); public ObservableCollection<PerformanceDebugPluginViewModel> Items { get; } = new();
public double CurrentFps
{
get => _currentFps;
set => RaiseAndSetIfChanged(ref _currentFps, value);
}
public int RenderWidth
{
get => _renderWidth;
set => RaiseAndSetIfChanged(ref _renderWidth, value);
}
public int RenderHeight
{
get => _renderHeight;
set => RaiseAndSetIfChanged(ref _renderHeight, value);
}
public string? Renderer
{
get => _renderer;
set => RaiseAndSetIfChanged(ref _renderer, value);
}
private void HandleActivation() private void HandleActivation()
{ {
Renderer = Constants.ManagedGraphicsContext != null ? Constants.ManagedGraphicsContext.GetType().Name : "Software"; Renderer = Constants.ManagedGraphicsContext != null ? Constants.ManagedGraphicsContext.GetType().Name : "Software";

View File

@ -4,21 +4,21 @@ using Artemis.Core;
using Artemis.Core.Services; using Artemis.Core.Services;
using Artemis.UI.Shared; using Artemis.UI.Shared;
using Avalonia.Media.Imaging; using Avalonia.Media.Imaging;
using PropertyChanged.SourceGenerator;
using ReactiveUI; using ReactiveUI;
using SkiaSharp; using SkiaSharp;
namespace Artemis.UI.Screens.Debugger.Render; namespace Artemis.UI.Screens.Debugger.Render;
public class RenderDebugViewModel : ActivatableViewModelBase public partial class RenderDebugViewModel : ActivatableViewModelBase
{ {
private readonly IRenderService _renderService; private readonly IRenderService _renderService;
private double _currentFps;
private Bitmap? _currentFrame;
private string? _frameTargetPath; private string? _frameTargetPath;
private string? _renderer; [Notify] private double _currentFps;
private int _renderHeight; [Notify] private Bitmap? _currentFrame;
private int _renderWidth; [Notify] private string? _renderer;
[Notify] private int _renderHeight;
[Notify] private int _renderWidth;
public RenderDebugViewModel(IRenderService renderService) public RenderDebugViewModel(IRenderService renderService)
{ {
@ -33,36 +33,6 @@ public class RenderDebugViewModel : ActivatableViewModelBase
}); });
} }
public Bitmap? CurrentFrame
{
get => _currentFrame;
set => RaiseAndSetIfChanged(ref _currentFrame, value);
}
public double CurrentFps
{
get => _currentFps;
set => RaiseAndSetIfChanged(ref _currentFps, value);
}
public int RenderWidth
{
get => _renderWidth;
set => RaiseAndSetIfChanged(ref _renderWidth, value);
}
public int RenderHeight
{
get => _renderHeight;
set => RaiseAndSetIfChanged(ref _renderHeight, value);
}
public string? Renderer
{
get => _renderer;
set => RaiseAndSetIfChanged(ref _renderer, value);
}
private void HandleActivation() private void HandleActivation()
{ {
Renderer = Constants.ManagedGraphicsContext != null ? Constants.ManagedGraphicsContext.GetType().Name : "Software"; Renderer = Constants.ManagedGraphicsContext != null ? Constants.ManagedGraphicsContext.GetType().Name : "Software";

View File

@ -11,18 +11,19 @@ using Artemis.UI.Shared.Routing;
using Avalonia.Controls.Documents; using Avalonia.Controls.Documents;
using Avalonia.Media; using Avalonia.Media;
using Avalonia.Threading; using Avalonia.Threading;
using PropertyChanged.SourceGenerator;
using ReactiveUI; using ReactiveUI;
using Serilog.Events; using Serilog.Events;
using Serilog.Formatting.Display; using Serilog.Formatting.Display;
namespace Artemis.UI.Screens.Debugger.Routing; namespace Artemis.UI.Screens.Debugger.Routing;
public class RoutingDebugViewModel : ActivatableViewModelBase public partial class RoutingDebugViewModel : ActivatableViewModelBase
{ {
private readonly IRouter _router; private readonly IRouter _router;
private const int MAX_ENTRIES = 1000; private const int MAX_ENTRIES = 1000;
private readonly MessageTemplateTextFormatter _formatter; private readonly MessageTemplateTextFormatter _formatter;
private string? _route; [Notify] private string? _route;
public RoutingDebugViewModel(IRouter router) public RoutingDebugViewModel(IRouter router)
{ {
@ -49,12 +50,6 @@ public class RoutingDebugViewModel : ActivatableViewModelBase
public InlineCollection Lines { get; } = new(); public InlineCollection Lines { get; } = new();
public ReactiveCommand<Unit, Unit> Navigate { get; } public ReactiveCommand<Unit, Unit> Navigate { get; }
public string? Route
{
get => _route;
set => RaiseAndSetIfChanged(ref _route, value);
}
private void OnLogEventAdded(object? sender, LogEventEventArgs e) private void OnLogEventAdded(object? sender, LogEventEventArgs e)
{ {
Dispatcher.UIThread.Post(() => AddLogEvent(e.LogEvent)); Dispatcher.UIThread.Post(() => AddLogEvent(e.LogEvent));

View File

@ -0,0 +1,32 @@
<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:workshop="clr-namespace:Artemis.UI.Screens.Debugger.Workshop"
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
x:Class="Artemis.UI.Screens.Debugger.Workshop.WorkshopDebugView"
x:DataType="workshop:WorkshopDebugViewModel">
<ScrollViewer Classes="with-padding">
<StackPanel>
<Label>Workshop Status</Label>
<Border Classes="card-condensed">
<SelectableTextBlock Text="{CompiledBinding WorkshopStatus}" FontFamily="{StaticResource RobotoMono}" FontSize="13" />
</Border>
<Label Margin="0 10 0 0">Auth token (DO NOT SHARE)</Label>
<Border Classes="card-condensed">
<SelectableTextBlock Text="{CompiledBinding Token}" FontFamily="{StaticResource RobotoMono}" FontSize="13" TextWrapping="Wrap" />
</Border>
<Label Margin="0 10 0 0">Email verified</Label>
<Border Classes="card-condensed">
<SelectableTextBlock Text="{CompiledBinding EmailVerified}" FontFamily="{StaticResource RobotoMono}" FontSize="13" />
</Border>
<Label Margin="0 10 0 0">Claims</Label>
<Border Classes="card-condensed">
<SelectableTextBlock Text="{CompiledBinding Claims}" FontFamily="{StaticResource RobotoMono}" FontSize="13" />
</Border>
</StackPanel>
</ScrollViewer>
</UserControl>

View File

@ -0,0 +1,14 @@
using Avalonia;
using Avalonia.Controls;
using Avalonia.Markup.Xaml;
using Avalonia.ReactiveUI;
namespace Artemis.UI.Screens.Debugger.Workshop;
public partial class WorkshopDebugView : ReactiveUserControl<WorkshopDebugViewModel>
{
public WorkshopDebugView()
{
InitializeComponent();
}
}

View File

@ -0,0 +1,30 @@
using System.Threading;
using Artemis.UI.Extensions;
using Artemis.UI.Shared;
using Artemis.WebClient.Workshop.Services;
using Newtonsoft.Json;
using PropertyChanged.SourceGenerator;
namespace Artemis.UI.Screens.Debugger.Workshop;
public partial class WorkshopDebugViewModel : ActivatableViewModelBase
{
[Notify] private string? _token;
[Notify] private bool _emailVerified;
[Notify] private string? _claims;
[Notify] private IWorkshopService.WorkshopStatus? _workshopStatus;
public WorkshopDebugViewModel(IWorkshopService workshopService, IAuthenticationService authenticationService)
{
DisplayName = "Workshop";
this.WhenActivatedAsync(async _ =>
{
Token = await authenticationService.GetBearer();
EmailVerified = authenticationService.GetIsEmailVerified();
Claims = JsonConvert.SerializeObject(authenticationService.Claims, Formatting.Indented);
WorkshopStatus = await workshopService.GetWorkshopStatus(CancellationToken.None);
});
}
}

View File

@ -6,16 +6,17 @@ using Artemis.Core;
using Artemis.Core.Services; using Artemis.Core.Services;
using Artemis.UI.DryIoc.Factories; using Artemis.UI.DryIoc.Factories;
using Artemis.UI.Shared; using Artemis.UI.Shared;
using PropertyChanged.SourceGenerator;
using ReactiveUI; using ReactiveUI;
using RGB.NET.Core; using RGB.NET.Core;
using SkiaSharp; using SkiaSharp;
namespace Artemis.UI.Screens.Device; namespace Artemis.UI.Screens.Device;
public class DevicePropertiesViewModel : DialogViewModelBase<object> public partial class DevicePropertiesViewModel : DialogViewModelBase<object>
{ {
private readonly IDeviceVmFactory _deviceVmFactory; private readonly IDeviceVmFactory _deviceVmFactory;
private ArtemisDevice _device; [Notify] private ArtemisDevice _device;
public DevicePropertiesViewModel(ArtemisDevice device, IRenderService renderService, IDeviceService deviceService, IDeviceVmFactory deviceVmFactory) public DevicePropertiesViewModel(ArtemisDevice device, IRenderService renderService, IDeviceService deviceService, IDeviceVmFactory deviceVmFactory)
{ {
@ -42,12 +43,6 @@ public class DevicePropertiesViewModel : DialogViewModelBase<object>
ClearSelectedLeds = ReactiveCommand.Create(ExecuteClearSelectedLeds); ClearSelectedLeds = ReactiveCommand.Create(ExecuteClearSelectedLeds);
} }
public ArtemisDevice Device
{
get => _device;
set => RaiseAndSetIfChanged(ref _device, value);
}
public ObservableCollection<ArtemisLed> SelectedLeds { get; } public ObservableCollection<ArtemisLed> SelectedLeds { get; }
public ObservableCollection<ActivatableViewModelBase> Tabs { get; } public ObservableCollection<ActivatableViewModelBase> Tabs { get; }
public ReactiveCommand<Unit, Unit> ClearSelectedLeds { get; } public ReactiveCommand<Unit, Unit> ClearSelectedLeds { get; }

View File

@ -8,18 +8,19 @@ using Artemis.UI.Shared;
using Artemis.UI.Shared.Services; using Artemis.UI.Shared.Services;
using Avalonia.Threading; using Avalonia.Threading;
using Humanizer; using Humanizer;
using PropertyChanged.SourceGenerator;
using ReactiveUI; using ReactiveUI;
using RGB.NET.Core; using RGB.NET.Core;
namespace Artemis.UI.Screens.Device; namespace Artemis.UI.Screens.Device;
public class DeviceSettingsViewModel : ActivatableViewModelBase public partial class DeviceSettingsViewModel : ActivatableViewModelBase
{ {
private readonly IDeviceService _deviceService; private readonly IDeviceService _deviceService;
private readonly DevicesTabViewModel _devicesTabViewModel; private readonly DevicesTabViewModel _devicesTabViewModel;
private readonly IDeviceVmFactory _deviceVmFactory; private readonly IDeviceVmFactory _deviceVmFactory;
private readonly IWindowService _windowService; private readonly IWindowService _windowService;
private bool _togglingDevice; [Notify] private bool _togglingDevice;
public DeviceSettingsViewModel(ArtemisDevice device, DevicesTabViewModel devicesTabViewModel, IDeviceService deviceService, IWindowService windowService, IDeviceVmFactory deviceVmFactory) public DeviceSettingsViewModel(ArtemisDevice device, DevicesTabViewModel devicesTabViewModel, IDeviceService deviceService, IWindowService windowService, IDeviceVmFactory deviceVmFactory)
{ {
@ -51,12 +52,6 @@ public class DeviceSettingsViewModel : ActivatableViewModelBase
set => Dispatcher.UIThread.InvokeAsync(async () => await UpdateIsDeviceEnabled(value)); set => Dispatcher.UIThread.InvokeAsync(async () => await UpdateIsDeviceEnabled(value));
} }
public bool TogglingDevice
{
get => _togglingDevice;
set => RaiseAndSetIfChanged(ref _togglingDevice, value);
}
public void IdentifyDevice() public void IdentifyDevice()
{ {
_deviceService.IdentifyDevice(Device); _deviceService.IdentifyDevice(Device);

View File

@ -5,9 +5,10 @@
xmlns:controls="clr-namespace:FluentAvalonia.UI.Controls;assembly=FluentAvalonia" xmlns:controls="clr-namespace:FluentAvalonia.UI.Controls;assembly=FluentAvalonia"
xmlns:converters="clr-namespace:Artemis.UI.Shared.Converters;assembly=Artemis.UI.Shared" xmlns:converters="clr-namespace:Artemis.UI.Shared.Converters;assembly=Artemis.UI.Shared"
xmlns:device="clr-namespace:Artemis.UI.Screens.Device" xmlns:device="clr-namespace:Artemis.UI.Screens.Device"
xmlns:general="clr-namespace:Artemis.UI.Screens.Device.General"
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="650" mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="650"
x:Class="Artemis.UI.Screens.Device.DeviceGeneralTabView" x:Class="Artemis.UI.Screens.Device.General.DeviceGeneralTabView"
x:DataType="device:DeviceGeneralTabViewModel"> x:DataType="general:DeviceGeneralTabViewModel">
<UserControl.Resources> <UserControl.Resources>
<converters:SKColorToColorConverter x:Key="SKColorToColorConverter" /> <converters:SKColorToColorConverter x:Key="SKColorToColorConverter" />
</UserControl.Resources> </UserControl.Resources>

View File

@ -1,7 +1,6 @@
using Avalonia.Controls;
using Avalonia.ReactiveUI; using Avalonia.ReactiveUI;
namespace Artemis.UI.Screens.Device; namespace Artemis.UI.Screens.Device.General;
public partial class DeviceGeneralTabView : ReactiveUserControl<DeviceGeneralTabViewModel> public partial class DeviceGeneralTabView : ReactiveUserControl<DeviceGeneralTabViewModel>
{ {
public DeviceGeneralTabView() public DeviceGeneralTabView()

View File

@ -1,48 +1,41 @@
using Artemis.UI.Shared; using System;
using Artemis.UI.Shared;
using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Reactive.Disposables; using System.Reactive.Disposables;
using System.Text;
using System.Threading;
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.Screens.Device.Layout;
using Artemis.UI.Shared;
using Artemis.UI.Shared.Services; using Artemis.UI.Shared.Services;
using PropertyChanged.SourceGenerator;
using ReactiveUI; using ReactiveUI;
using RGB.NET.Core; using RGB.NET.Core;
using SkiaSharp; using SkiaSharp;
namespace Artemis.UI.Screens.Device; namespace Artemis.UI.Screens.Device.General;
public class DeviceGeneralTabViewModel : ActivatableViewModelBase public partial class DeviceGeneralTabViewModel : ActivatableViewModelBase
{ {
private readonly ICoreService _coreService;
private readonly IDeviceService _deviceService; private readonly IDeviceService _deviceService;
private readonly IRenderService _renderService; private readonly IRenderService _renderService;
private readonly IWindowService _windowService; private readonly IWindowService _windowService;
private readonly List<DeviceCategory> _categories; private readonly List<DeviceCategory> _categories;
private readonly float _initialBlueScale; private readonly float _initialBlueScale;
private readonly float _initialGreenScale; private readonly float _initialGreenScale;
private readonly float _initialRedScale; private readonly float _initialRedScale;
private int _rotation; [Notify] private int _rotation;
private float _scale; [Notify] private float _scale;
private int _x; [Notify] private int _x;
private int _y; [Notify] private int _y;
[Notify] private float _redScale;
[Notify] private float _greenScale;
[Notify] private float _blueScale;
[Notify] private SKColor _currentColor;
[Notify] private bool _displayOnDevices;
private float _redScale; public DeviceGeneralTabViewModel(ArtemisDevice device, IDeviceService deviceService, IRenderService renderService, IWindowService windowService)
private float _greenScale;
private float _blueScale;
private SKColor _currentColor;
private bool _displayOnDevices;
public DeviceGeneralTabViewModel(ArtemisDevice device, ICoreService coreService, IDeviceService deviceService, IRenderService renderService, IWindowService windowService)
{ {
_coreService = coreService;
_deviceService = deviceService; _deviceService = deviceService;
_renderService = renderService; _renderService = renderService;
_windowService = windowService; _windowService = windowService;
@ -64,7 +57,7 @@ public class DeviceGeneralTabViewModel : ActivatableViewModelBase
_initialGreenScale = Device.GreenScale; _initialGreenScale = Device.GreenScale;
_initialBlueScale = Device.BlueScale; _initialBlueScale = Device.BlueScale;
this.WhenAnyValue(x => x.RedScale, x => x.GreenScale, x => x.BlueScale).Subscribe(_ => ApplyScaling()); this.WhenAnyValue<DeviceGeneralTabViewModel, float, float, float>(x => x.RedScale, x => x.GreenScale, x => x.BlueScale).Subscribe(_ => ApplyScaling());
this.WhenActivated(d => this.WhenActivated(d =>
{ {
@ -82,30 +75,6 @@ public class DeviceGeneralTabViewModel : ActivatableViewModelBase
public ArtemisDevice Device { get; } public ArtemisDevice Device { get; }
public int X
{
get => _x;
set => RaiseAndSetIfChanged(ref _x, value);
}
public int Y
{
get => _y;
set => RaiseAndSetIfChanged(ref _y, value);
}
public float Scale
{
get => _scale;
set => RaiseAndSetIfChanged(ref _scale, value);
}
public int Rotation
{
get => _rotation;
set => RaiseAndSetIfChanged(ref _rotation, value);
}
public bool IsKeyboard => Device.DeviceType == RGBDeviceType.Keyboard; public bool IsKeyboard => Device.DeviceType == RGBDeviceType.Keyboard;
public bool HasDeskCategory public bool HasDeskCategory
@ -138,36 +107,6 @@ public class DeviceGeneralTabViewModel : ActivatableViewModelBase
set => SetCategory(DeviceCategory.Peripherals, value); set => SetCategory(DeviceCategory.Peripherals, value);
} }
public float RedScale
{
get => _redScale;
set => RaiseAndSetIfChanged(ref _redScale, value);
}
public float GreenScale
{
get => _greenScale;
set => RaiseAndSetIfChanged(ref _greenScale, value);
}
public float BlueScale
{
get => _blueScale;
set => RaiseAndSetIfChanged(ref _blueScale, value);
}
public SKColor CurrentColor
{
get => _currentColor;
set => RaiseAndSetIfChanged(ref _currentColor, value);
}
public bool DisplayOnDevices
{
get => _displayOnDevices;
set => RaiseAndSetIfChanged(ref _displayOnDevices, value);
}
private bool GetCategory(DeviceCategory category) private bool GetCategory(DeviceCategory category)
{ {
return _categories.Contains(category); return _categories.Contains(category);
@ -189,12 +128,12 @@ public class DeviceGeneralTabViewModel : ActivatableViewModelBase
return; return;
if (!Device.DeviceProvider.CanDetectPhysicalLayout && !await DevicePhysicalLayoutDialogViewModel.SelectPhysicalLayout(_windowService, Device)) if (!Device.DeviceProvider.CanDetectPhysicalLayout && !await DevicePhysicalLayoutDialogViewModel.SelectPhysicalLayout(_windowService, Device))
return; return;
if (!Device.DeviceProvider.CanDetectLogicalLayout && !await DeviceLogicalLayoutDialogViewModel.SelectLogicalLayout(_windowService, Device)) if (!Device.DeviceProvider.CanDetectLogicalLayout && !await Layout.DeviceLogicalLayoutDialogViewModel.SelectLogicalLayout(_windowService, Device))
return; return;
await Task.Delay(400); await Task.Delay(400);
_deviceService.SaveDevice(Device); _deviceService.SaveDevice(Device);
_deviceService.ApplyDeviceLayout(Device, Device.GetBestDeviceLayout()); _deviceService.LoadDeviceLayout(Device);
} }
private void Apply() private void Apply()

View File

@ -4,9 +4,10 @@
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:device="clr-namespace:Artemis.UI.Screens.Device" xmlns:device="clr-namespace:Artemis.UI.Screens.Device"
xmlns:avalonia="clr-namespace:Material.Icons.Avalonia;assembly=Material.Icons.Avalonia" xmlns:avalonia="clr-namespace:Material.Icons.Avalonia;assembly=Material.Icons.Avalonia"
xmlns:inputMappings="clr-namespace:Artemis.UI.Screens.Device.InputMappings"
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450" mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
x:Class="Artemis.UI.Screens.Device.InputMappingsTabView" x:Class="Artemis.UI.Screens.Device.InputMappings.InputMappingsTabView"
x:DataType="device:InputMappingsTabViewModel"> x:DataType="inputMappings:InputMappingsTabViewModel">
<ScrollViewer HorizontalScrollBarVisibility="Disabled" VerticalScrollBarVisibility="Auto"> <ScrollViewer HorizontalScrollBarVisibility="Disabled" VerticalScrollBarVisibility="Auto">
<Grid RowDefinitions="Auto,Auto,*"> <Grid RowDefinitions="Auto,Auto,*">
<StackPanel Grid.Row="0"> <StackPanel Grid.Row="0">

View File

@ -1,7 +1,6 @@
using Avalonia.Markup.Xaml;
using Avalonia.ReactiveUI; using Avalonia.ReactiveUI;
namespace Artemis.UI.Screens.Device; namespace Artemis.UI.Screens.Device.InputMappings;
public partial class InputMappingsTabView : ReactiveUserControl<InputMappingsTabViewModel> public partial class InputMappingsTabView : ReactiveUserControl<InputMappingsTabViewModel>
{ {

View File

@ -6,19 +6,19 @@ using Artemis.Core;
using Artemis.Core.Services; using Artemis.Core.Services;
using Artemis.UI.Exceptions; using Artemis.UI.Exceptions;
using Artemis.UI.Shared; using Artemis.UI.Shared;
using HidSharp.Reports.Units; using PropertyChanged.SourceGenerator;
using ReactiveUI; using ReactiveUI;
using RGB.NET.Core; using RGB.NET.Core;
using Unit = System.Reactive.Unit; using Unit = System.Reactive.Unit;
namespace Artemis.UI.Screens.Device; namespace Artemis.UI.Screens.Device.InputMappings;
public class InputMappingsTabViewModel : ActivatableViewModelBase public partial class InputMappingsTabViewModel : ActivatableViewModelBase
{ {
private readonly IInputService _inputService; private readonly IInputService _inputService;
private readonly IDeviceService _deviceService; private readonly IDeviceService _deviceService;
private readonly ObservableCollection<ArtemisLed> _selectedLeds; private readonly ObservableCollection<ArtemisLed> _selectedLeds;
private ArtemisLed? _selectedLed; [Notify] private ArtemisLed? _selectedLed;
public InputMappingsTabViewModel(ArtemisDevice device, ObservableCollection<ArtemisLed> selectedLeds, IInputService inputService, IDeviceService deviceService) public InputMappingsTabViewModel(ArtemisDevice device, ObservableCollection<ArtemisLed> selectedLeds, IInputService inputService, IDeviceService deviceService)
{ {
@ -49,15 +49,7 @@ public class InputMappingsTabViewModel : ActivatableViewModelBase
} }
public ReactiveCommand<ArtemisInputMapping, Unit> DeleteMapping { get; } public ReactiveCommand<ArtemisInputMapping, Unit> DeleteMapping { get; }
public ArtemisDevice Device { get; } public ArtemisDevice Device { get; }
public ArtemisLed? SelectedLed
{
get => _selectedLed;
set => RaiseAndSetIfChanged(ref _selectedLed, value);
}
public ObservableCollection<ArtemisInputMapping> InputMappings { get; } public ObservableCollection<ArtemisInputMapping> InputMappings { get; }
private void ExecuteDeleteMapping(ArtemisInputMapping inputMapping) private void ExecuteDeleteMapping(ArtemisInputMapping inputMapping)

View File

@ -4,10 +4,11 @@
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:controls="clr-namespace:FluentAvalonia.UI.Controls;assembly=FluentAvalonia" xmlns:controls="clr-namespace:FluentAvalonia.UI.Controls;assembly=FluentAvalonia"
xmlns:avalonia="clr-namespace:Material.Icons.Avalonia;assembly=Material.Icons.Avalonia" xmlns:avalonia="clr-namespace:Material.Icons.Avalonia;assembly=Material.Icons.Avalonia"
xmlns:device="clr-namespace:Artemis.UI.Screens.Device" xmlns:layout="clr-namespace:Artemis.UI.Screens.Device.Layout"
xmlns:layoutProviders="clr-namespace:Artemis.UI.Screens.Device.Layout.LayoutProviders"
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="800" mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="800"
x:Class="Artemis.UI.Screens.Device.DeviceLayoutTabView" x:Class="Artemis.UI.Screens.Device.Layout.DeviceLayoutTabView"
x:DataType="device:DeviceLayoutTabViewModel"> x:DataType="layout:DeviceLayoutTabViewModel">
<ScrollViewer HorizontalScrollBarVisibility="Disabled" VerticalScrollBarVisibility="Auto"> <ScrollViewer HorizontalScrollBarVisibility="Disabled" VerticalScrollBarVisibility="Auto">
<Border Classes="card" Margin="5"> <Border Classes="card" Margin="5">
<Grid RowDefinitions="*,Auto"> <Grid RowDefinitions="*,Auto">
@ -28,6 +29,7 @@
</Button> </Button>
</StackPanel> </StackPanel>
</Grid> </Grid>
<Border Classes="card-separator" /> <Border Classes="card-separator" />
<Grid RowDefinitions="*,*" ColumnDefinitions="*,Auto"> <Grid RowDefinitions="*,*" ColumnDefinitions="*,Auto">
<StackPanel Grid.Row="1" Grid.Column="0"> <StackPanel Grid.Row="1" Grid.Column="0">
@ -45,28 +47,43 @@
</Button> </Button>
</StackPanel> </StackPanel>
</Grid> </Grid>
<Border Classes="card-separator" /> <Border Classes="card-separator" />
<Grid RowDefinitions="*,*" ColumnDefinitions="*,Auto"> <Grid RowDefinitions="*,*,*" ColumnDefinitions="*,Auto">
<StackPanel Grid.Row="1" Grid.Column="0"> <StackPanel Grid.Row="1" Grid.Column="0">
<TextBlock Text="Disable default layout" /> <TextBlock Text="Layout provider" />
<TextBlock Classes="subtitle" FontSize="12" Text="With this checked, Artemis will not load a layout for this device unless you specifically provide one." /> <TextBlock Classes="subtitle" FontSize="12" Text="Choose between different ways to load a layout for this device." />
</StackPanel> </StackPanel>
<StackPanel Grid.Row="1" Grid.Column="1" VerticalAlignment="Center"> <StackPanel Grid.Row="1" Grid.Column="1" VerticalAlignment="Center">
<CheckBox HorizontalAlignment="Right" Margin="0,0,-10,0" IsChecked="{CompiledBinding Device.DisableDefaultLayout}"/> <StackPanel.Styles>
</StackPanel> <Style Selector="ComboBox.layoutProvider /template/ ContentControl#ContentPresenter">
</Grid> <Setter Property="ContentTemplate">
<Border Classes="card-separator" /> <Setter.Value>
<Grid RowDefinitions="*,*" ColumnDefinitions="*,Auto"> <DataTemplate x:DataType="layoutProviders:ILayoutProviderViewModel">
<StackPanel Grid.Row="1" Grid.Column="0"> <TextBlock Text="{CompiledBinding Name}" TextWrapping="Wrap" MaxWidth="350" />
<TextBlock Text="Custom layout path" /> </DataTemplate>
<TextBlock Classes="subtitle" FontSize="12" Text="{CompiledBinding CustomLayoutPath, TargetNullValue=None}"/> </Setter.Value>
</StackPanel> </Setter>
<StackPanel Grid.Row="1" Grid.Column="1" VerticalAlignment="Center" Orientation="Horizontal"> </Style>
<Button Content="Clear" Command="{CompiledBinding ClearCustomLayout}" IsEnabled="{CompiledBinding HasCustomLayout}" /> </StackPanel.Styles>
<!-- 5 pixels of margin between the buttons --> <ComboBox Classes="layoutProvider"
<Button Margin="5,0,0,0" Content="Browse" Command="{CompiledBinding BrowseCustomLayout}" /> Width="150"
SelectedItem="{CompiledBinding SelectedLayoutProvider}"
ItemsSource="{CompiledBinding LayoutProviders}">
<ComboBox.ItemTemplate>
<DataTemplate x:DataType="layoutProviders:ILayoutProviderViewModel">
<StackPanel>
<TextBlock Text="{CompiledBinding Name}" TextWrapping="Wrap" MaxWidth="350" />
<TextBlock Classes="subtitle" Text="{CompiledBinding Description}" TextWrapping="Wrap" MaxWidth="350" />
</StackPanel>
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
</StackPanel> </StackPanel>
</Grid> </Grid>
<ContentControl Content="{CompiledBinding SelectedLayoutProvider}" ClipToBounds="False" />
<Border Classes="card-separator" /> <Border Classes="card-separator" />
<Grid RowDefinitions="*,*" ColumnDefinitions="*,Auto"> <Grid RowDefinitions="*,*" ColumnDefinitions="*,Auto">
<StackPanel Grid.Row="1" Grid.Column="0"> <StackPanel Grid.Row="1" Grid.Column="0">

View File

@ -1,11 +1,8 @@
using Avalonia; using Avalonia.Controls;
using Avalonia.Controls;
using Avalonia.Input;
using Avalonia.Interactivity; using Avalonia.Interactivity;
using Avalonia.Markup.Xaml;
using Avalonia.ReactiveUI; using Avalonia.ReactiveUI;
namespace Artemis.UI.Screens.Device; namespace Artemis.UI.Screens.Device.Layout;
public partial class DeviceLayoutTabView : ReactiveUserControl<DeviceLayoutTabViewModel> public partial class DeviceLayoutTabView : ReactiveUserControl<DeviceLayoutTabViewModel>
{ {
@ -19,16 +16,16 @@ public partial class DeviceLayoutTabView : ReactiveUserControl<DeviceLayoutTabVi
if (ViewModel?.DefaultLayoutPath is null) if (ViewModel?.DefaultLayoutPath is null)
return; return;
TopLevel.GetTopLevel(this).Clipboard.SetTextAsync(ViewModel.DefaultLayoutPath); TopLevel.GetTopLevel(this)?.Clipboard?.SetTextAsync(ViewModel.DefaultLayoutPath);
ViewModel.ShowCopiedNotification(); ViewModel.ShowCopiedNotification();
} }
private void ImagePathButton_OnClick(object? sender, RoutedEventArgs e) private void ImagePathButton_OnClick(object? sender, RoutedEventArgs e)
{ {
if (ViewModel?.Device?.Layout?.Image?.LocalPath is null) if (ViewModel?.Device.Layout?.Image?.LocalPath is null)
return; return;
TopLevel.GetTopLevel(this).Clipboard.SetTextAsync(ViewModel.Device.Layout.Image.LocalPath); TopLevel.GetTopLevel(this)?.Clipboard?.SetTextAsync(ViewModel.Device.Layout.Image.LocalPath);
ViewModel.ShowCopiedNotification(); ViewModel.ShowCopiedNotification();
} }
} }

View File

@ -1,81 +1,60 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.ComponentModel; using System.Collections.ObjectModel;
using System.IO; using System.IO;
using System.Linq; using System.Linq;
using System.Reactive.Disposables;
using System.Threading.Tasks; using System.Threading.Tasks;
using System.Xml.Serialization; using System.Xml.Serialization;
using Artemis.Core; using Artemis.Core;
using Artemis.Core.Services; using Artemis.UI.Screens.Device.Layout.LayoutProviders;
using Artemis.UI.Shared; using Artemis.UI.Shared;
using Artemis.UI.Shared.Services; using Artemis.UI.Shared.Services;
using Artemis.UI.Shared.Services.Builders; using Artemis.UI.Shared.Services.Builders;
using Avalonia.Controls; using PropertyChanged.SourceGenerator;
using ReactiveUI; using ReactiveUI;
using RGB.NET.Layout; using RGB.NET.Layout;
using SkiaSharp;
namespace Artemis.UI.Screens.Device; namespace Artemis.UI.Screens.Device.Layout;
public class DeviceLayoutTabViewModel : ActivatableViewModelBase public partial class DeviceLayoutTabViewModel : ActivatableViewModelBase
{ {
[Notify] private ILayoutProviderViewModel? _selectedLayoutProvider;
private readonly IWindowService _windowService; private readonly IWindowService _windowService;
private readonly INotificationService _notificationService; private readonly INotificationService _notificationService;
private readonly IDeviceService _deviceService;
public DeviceLayoutTabViewModel(IWindowService windowService, INotificationService notificationService, IDeviceService deviceService, ArtemisDevice device) public DeviceLayoutTabViewModel(IWindowService windowService, INotificationService notificationService, ArtemisDevice device, List<ILayoutProviderViewModel> layoutProviders)
{ {
_windowService = windowService; _windowService = windowService;
_notificationService = notificationService; _notificationService = notificationService;
_deviceService = deviceService;
Device = device; Device = device;
DisplayName = "Layout"; DisplayName = "Layout";
DefaultLayoutPath = Device.DeviceProvider.LoadLayout(Device).FilePath; DefaultLayoutPath = Device.DeviceProvider.LoadLayout(Device).FilePath;
this.WhenActivated(d => LayoutProviders = new ObservableCollection<ILayoutProviderViewModel>(layoutProviders);
foreach (ILayoutProviderViewModel layoutProviderViewModel in layoutProviders)
{ {
Device.PropertyChanged += DeviceOnPropertyChanged; layoutProviderViewModel.Device = Device;
Disposable.Create(() => Device.PropertyChanged -= DeviceOnPropertyChanged).DisposeWith(d); if (layoutProviderViewModel.Provider.IsMatch(Device))
SelectedLayoutProvider = layoutProviderViewModel;
}
// When changing device provider to one that isn't currently on the device, apply it to the device immediately
this.WhenAnyValue(vm => vm.SelectedLayoutProvider).Subscribe(l =>
{
if (l != null && !l.Provider.IsMatch(Device))
l.Apply();
}); });
} }
public ArtemisDevice Device { get; } public ArtemisDevice Device { get; }
public ObservableCollection<ILayoutProviderViewModel> LayoutProviders { get; set; }
public string DefaultLayoutPath { get; } public string DefaultLayoutPath { get; }
public string? ImagePath => Device.Layout?.Image?.LocalPath; public string? ImagePath => Device.Layout?.Image?.LocalPath;
public string? CustomLayoutPath => Device.CustomLayoutPath;
public bool HasCustomLayout => Device.CustomLayoutPath != null;
public void ClearCustomLayout()
{
Device.CustomLayoutPath = null;
_notificationService.CreateNotification()
.WithMessage("Cleared imported layout.")
.WithSeverity(NotificationSeverity.Informational);
}
public async Task BrowseCustomLayout()
{
string[]? files = await _windowService.CreateOpenFileDialog()
.WithTitle("Select device layout file")
.HavingFilter(f => f.WithName("Layout files").WithExtension("xml"))
.ShowAsync();
if (files?.Length > 0)
{
Device.CustomLayoutPath = files[0];
_notificationService.CreateNotification()
.WithTitle("Imported layout")
.WithMessage($"File loaded from {files[0]}")
.WithSeverity(NotificationSeverity.Informational);
}
}
public async Task ExportLayout() public async Task ExportLayout()
{ {
string fileName = Device.DeviceProvider.GetDeviceLayoutName(Device); string fileName = Device.DeviceProvider.GetDeviceLayoutName(Device);
@ -106,13 +85,13 @@ public class DeviceLayoutTabViewModel : ActivatableViewModelBase
} }
else else
{ {
List<LedLayout> ledLayouts = Device.Leds.Select(x => new LedLayout() List<LedLayout> ledLayouts = Device.Leds.Select(x => new LedLayout
{ {
Id = x.RgbLed.Id.ToString(), Id = x.RgbLed.Id.ToString(),
DescriptiveX = x.Rectangle.Left.ToString(), DescriptiveX = x.Rectangle.Left.ToString(),
DescriptiveY = x.Rectangle.Top.ToString(), DescriptiveY = x.Rectangle.Top.ToString(),
DescriptiveWidth = $"{x.Rectangle.Width}mm", DescriptiveWidth = $"{x.Rectangle.Width}mm",
DescriptiveHeight = $"{x.Rectangle.Height}mm", DescriptiveHeight = $"{x.Rectangle.Height}mm"
}).ToList(); }).ToList();
DeviceLayout emptyLayout = new() DeviceLayout emptyLayout = new()
@ -123,7 +102,7 @@ public class DeviceLayoutTabViewModel : ActivatableViewModelBase
Model = Device.RgbDevice.DeviceInfo.Model, Model = Device.RgbDevice.DeviceInfo.Model,
Width = Device.Rectangle.Width, Width = Device.Rectangle.Width,
Height = Device.Rectangle.Height, Height = Device.Rectangle.Height,
InternalLeds = ledLayouts, InternalLeds = ledLayouts
}; };
XmlSerializer serializer = new(typeof(DeviceLayout)); XmlSerializer serializer = new(typeof(DeviceLayout));
@ -145,19 +124,4 @@ public class DeviceLayoutTabViewModel : ActivatableViewModelBase
.WithSeverity(NotificationSeverity.Informational) .WithSeverity(NotificationSeverity.Informational)
.Show(); .Show();
} }
private void DeviceOnPropertyChanged(object? sender, PropertyChangedEventArgs e)
{
if (e.PropertyName is nameof(Device.CustomLayoutPath) or nameof(Device.DisableDefaultLayout))
{
Task.Run(() =>
{
_deviceService.ApplyDeviceLayout(Device, Device.GetBestDeviceLayout());
_deviceService.SaveDevice(Device);
});
this.RaisePropertyChanged(nameof(CustomLayoutPath));
this.RaisePropertyChanged(nameof(HasCustomLayout));
}
}
} }

View File

@ -2,11 +2,11 @@
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
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:device="clr-namespace:Artemis.UI.Screens.Device"
xmlns:globalization="clr-namespace:System.Globalization;assembly=System.Runtime" xmlns:globalization="clr-namespace:System.Globalization;assembly=System.Runtime"
xmlns:layout="clr-namespace:Artemis.UI.Screens.Device.Layout"
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450" mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
x:Class="Artemis.UI.Screens.Device.DeviceLogicalLayoutDialogView" x:Class="Artemis.UI.Screens.Device.Layout.DeviceLogicalLayoutDialogView"
x:DataType="device:DeviceLogicalLayoutDialogViewModel"> x:DataType="layout:DeviceLogicalLayoutDialogViewModel">
<StackPanel> <StackPanel>
<TextBlock TextWrapping="Wrap">Artemis couldn't automatically determine the logical layout of your</TextBlock> <TextBlock TextWrapping="Wrap">Artemis couldn't automatically determine the logical layout of your</TextBlock>
<TextBlock TextWrapping="Wrap" Text="{CompiledBinding Device.RgbDevice.DeviceInfo.DeviceName, Mode=OneWay}" /> <TextBlock TextWrapping="Wrap" Text="{CompiledBinding Device.RgbDevice.DeviceInfo.DeviceName, Mode=OneWay}" />
@ -26,12 +26,11 @@
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}" />
<TextBlock Text=" ("/> <Run Text="(" /><Run FontWeight="SemiBold" Text="{CompiledBinding TwoLetterISORegionName}" />
<TextBlock FontWeight="SemiBold" Text="{CompiledBinding TwoLetterISORegionName}"></TextBlock> <Run Text=")" />
<TextBlock Text=")"/> </TextBlock>
</StackPanel>
</DataTemplate> </DataTemplate>
</AutoCompleteBox.ItemTemplate> </AutoCompleteBox.ItemTemplate>
</AutoCompleteBox> </AutoCompleteBox>

View File

@ -1,12 +1,10 @@
using System; using System;
using System.Globalization; using System.Globalization;
using System.Threading.Tasks; using System.Threading.Tasks;
using Avalonia.Controls;
using Avalonia.Markup.Xaml;
using Avalonia.ReactiveUI; using Avalonia.ReactiveUI;
using Avalonia.Threading; using Avalonia.Threading;
namespace Artemis.UI.Screens.Device; namespace Artemis.UI.Screens.Device.Layout;
public partial class DeviceLogicalLayoutDialogView : ReactiveUserControl<DeviceLogicalLayoutDialogViewModel> public partial class DeviceLogicalLayoutDialogView : ReactiveUserControl<DeviceLogicalLayoutDialogViewModel>
{ {
@ -25,8 +23,10 @@ public partial class DeviceLogicalLayoutDialogView : ReactiveUserControl<DeviceL
RegionsAutoCompleteBox.PopulateComplete(); RegionsAutoCompleteBox.PopulateComplete();
} }
private bool SearchRegions(string search, object item) private bool SearchRegions(string? search, object? item)
{ {
if (search == null)
return true;
if (item is not RegionInfo regionInfo) if (item is not RegionInfo regionInfo)
return false; return false;
@ -34,5 +34,4 @@ public partial class DeviceLogicalLayoutDialogView : ReactiveUserControl<DeviceL
regionInfo.NativeName.Contains(search, StringComparison.OrdinalIgnoreCase) || regionInfo.NativeName.Contains(search, StringComparison.OrdinalIgnoreCase) ||
regionInfo.TwoLetterISORegionName.Contains(search, StringComparison.OrdinalIgnoreCase); regionInfo.TwoLetterISORegionName.Contains(search, StringComparison.OrdinalIgnoreCase);
} }
} }

View File

@ -8,23 +8,23 @@ using Artemis.Core;
using Artemis.UI.Shared; using Artemis.UI.Shared;
using Artemis.UI.Shared.Services; using Artemis.UI.Shared.Services;
using FluentAvalonia.UI.Controls; using FluentAvalonia.UI.Controls;
using PropertyChanged.SourceGenerator;
using ReactiveUI; using ReactiveUI;
using ContentDialogButton = Artemis.UI.Shared.Services.Builders.ContentDialogButton; using ContentDialogButton = Artemis.UI.Shared.Services.Builders.ContentDialogButton;
namespace Artemis.UI.Screens.Device; namespace Artemis.UI.Screens.Device.Layout;
public class DeviceLogicalLayoutDialogViewModel : ContentDialogViewModelBase public partial class DeviceLogicalLayoutDialogViewModel : ContentDialogViewModelBase
{ {
private const int LOCALE_NEUTRAL = 0x0000; private const int LOCALE_NEUTRAL = 0x0000;
private const int LOCALE_CUSTOM_DEFAULT = 0x0c00; private const int LOCALE_CUSTOM_DEFAULT = 0x0c00;
private const int LOCALE_INVARIANT = 0x007F; private const int LOCALE_INVARIANT = 0x007F;
[Notify] private RegionInfo? _selectedRegion;
private RegionInfo? _selectedRegion;
public DeviceLogicalLayoutDialogViewModel(ArtemisDevice device) public DeviceLogicalLayoutDialogViewModel(ArtemisDevice device)
{ {
Device = device; Device = device;
ApplyLogicalLayout = ReactiveCommand.Create(ExecuteApplyLogicalLayout, this.WhenAnyValue(vm => vm.SelectedRegion).Select(r => r != null)); ApplyLogicalLayout = ReactiveCommand.Create(ExecuteApplyLogicalLayout, this.WhenAnyValue<DeviceLogicalLayoutDialogViewModel, RegionInfo>(vm => vm.SelectedRegion).Select(r => r != null));
Regions = new ObservableCollection<RegionInfo>(CultureInfo.GetCultures(CultureTypes.SpecificCultures) Regions = new ObservableCollection<RegionInfo>(CultureInfo.GetCultures(CultureTypes.SpecificCultures)
.Where(c => c.LCID != LOCALE_INVARIANT && .Where(c => c.LCID != LOCALE_INVARIANT &&
c.LCID != LOCALE_NEUTRAL && c.LCID != LOCALE_NEUTRAL &&
@ -44,12 +44,6 @@ public class DeviceLogicalLayoutDialogViewModel : ContentDialogViewModelBase
public ObservableCollection<RegionInfo> Regions { get; } public ObservableCollection<RegionInfo> Regions { get; }
public bool Applied { get; set; } public bool Applied { get; set; }
public RegionInfo? SelectedRegion
{
get => _selectedRegion;
set => RaiseAndSetIfChanged(ref _selectedRegion, value);
}
public static async Task<bool> SelectLogicalLayout(IWindowService windowService, ArtemisDevice device) public static async Task<bool> SelectLogicalLayout(IWindowService windowService, ArtemisDevice device)
{ {
await windowService.CreateContentDialog() await windowService.CreateContentDialog()

View File

@ -2,10 +2,10 @@
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
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:device="clr-namespace:Artemis.UI.Screens.Device" xmlns:layout="clr-namespace:Artemis.UI.Screens.Device.Layout"
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450" mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
x:Class="Artemis.UI.Screens.Device.DevicePhysicalLayoutDialogView" x:Class="Artemis.UI.Screens.Device.Layout.DevicePhysicalLayoutDialogView"
x:DataType="device:DevicePhysicalLayoutDialogViewModel"> x:DataType="layout:DevicePhysicalLayoutDialogViewModel">
<Grid RowDefinitions="Auto,*"> <Grid RowDefinitions="Auto,*">
<StackPanel Grid.Row="0"> <StackPanel Grid.Row="0">
<StackPanel Orientation="Horizontal"> <StackPanel Orientation="Horizontal">

View File

@ -1,7 +1,6 @@
using Avalonia.Markup.Xaml; using Avalonia.ReactiveUI;
using Avalonia.ReactiveUI;
namespace Artemis.UI.Screens.Device; namespace Artemis.UI.Screens.Device.Layout;
public partial class DevicePhysicalLayoutDialogView : ReactiveUserControl<DevicePhysicalLayoutDialogViewModel> public partial class DevicePhysicalLayoutDialogView : ReactiveUserControl<DevicePhysicalLayoutDialogViewModel>
{ {
@ -9,5 +8,4 @@ public partial class DevicePhysicalLayoutDialogView : ReactiveUserControl<Device
{ {
InitializeComponent(); InitializeComponent();
} }
} }

View File

@ -7,7 +7,7 @@ using Artemis.UI.Shared.Services;
using FluentAvalonia.UI.Controls; using FluentAvalonia.UI.Controls;
using ReactiveUI; using ReactiveUI;
namespace Artemis.UI.Screens.Device; namespace Artemis.UI.Screens.Device.Layout;
public class DevicePhysicalLayoutDialogViewModel : ContentDialogViewModelBase public class DevicePhysicalLayoutDialogViewModel : ContentDialogViewModelBase
{ {

View File

@ -0,0 +1,24 @@
<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:layoutProviders="clr-namespace:Artemis.UI.Screens.Device.Layout.LayoutProviders"
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
x:Class="Artemis.UI.Screens.Device.Layout.LayoutProviders.CustomLayoutView"
x:DataType="layoutProviders:CustomLayoutViewModel"
ClipToBounds="False">
<StackPanel ClipToBounds="False">
<Border Classes="card-separator" />
<Grid RowDefinitions="*,*" ColumnDefinitions="*,Auto">
<StackPanel Grid.Row="1" Grid.Column="0">
<TextBlock Text="Current layout" />
<TextBlock Classes="subtitle" FontSize="12" Text="{CompiledBinding Device.LayoutSelection.Parameter, TargetNullValue=None}" TextWrapping="Wrap" />
</StackPanel>
<StackPanel Grid.Row="1" Grid.Column="1" VerticalAlignment="Center" Orientation="Horizontal">
<Button Content="Clear" Command="{CompiledBinding ClearCustomLayout}" IsEnabled="{CompiledBinding !!Device.LayoutSelection.Parameter}" />
<!-- 5 pixels of margin between the buttons -->
<Button Margin="5,0,0,0" Content="Browse" Command="{CompiledBinding BrowseCustomLayout}" />
</StackPanel>
</Grid>
</StackPanel>
</UserControl>

View File

@ -0,0 +1,11 @@
using Avalonia.Controls;
namespace Artemis.UI.Screens.Device.Layout.LayoutProviders;
public partial class CustomLayoutView : UserControl
{
public CustomLayoutView()
{
InitializeComponent();
}
}

View File

@ -0,0 +1,80 @@
using System.Threading.Tasks;
using Artemis.Core;
using Artemis.Core.Providers;
using Artemis.Core.Services;
using Artemis.UI.Shared;
using Artemis.UI.Shared.Services;
using Artemis.UI.Shared.Services.Builders;
namespace Artemis.UI.Screens.Device.Layout.LayoutProviders;
public class CustomLayoutViewModel : ViewModelBase, ILayoutProviderViewModel
{
private readonly CustomPathLayoutProvider _layoutProvider;
public CustomLayoutViewModel(IWindowService windowService, INotificationService notificationService, IDeviceService deviceService, CustomPathLayoutProvider layoutProvider)
{
_layoutProvider = layoutProvider;
_windowService = windowService;
_notificationService = notificationService;
_deviceService = deviceService;
}
/// <inheritdoc />
public string Name => "Custom";
/// <inheritdoc />
public string Description => "Select a layout file from a folder on your computer";
/// <inheritdoc />
public ILayoutProvider Provider => _layoutProvider;
public ArtemisDevice Device { get; set; } = null!;
private readonly IWindowService _windowService;
private readonly INotificationService _notificationService;
private readonly IDeviceService _deviceService;
public void ClearCustomLayout()
{
_layoutProvider.ConfigureDevice(Device, null);
Save();
_notificationService.CreateNotification()
.WithMessage("Cleared imported layout.")
.WithSeverity(NotificationSeverity.Informational);
}
public async Task BrowseCustomLayout()
{
string[]? files = await _windowService.CreateOpenFileDialog()
.WithTitle("Select device layout file")
.HavingFilter(f => f.WithName("Layout files").WithExtension("xml"))
.ShowAsync();
if (files?.Length > 0)
{
_layoutProvider.ConfigureDevice(Device, files[0]);
Save();
_notificationService.CreateNotification()
.WithTitle("Imported layout")
.WithMessage($"File loaded from {files[0]}")
.WithSeverity(NotificationSeverity.Informational);
}
}
/// <inheritdoc />
public void Apply()
{
_layoutProvider.ConfigureDevice(Device, null);
Save();
}
private void Save()
{
_deviceService.SaveDevice(Device);
_deviceService.LoadDeviceLayout(Device);
}
}

View File

@ -0,0 +1,14 @@
<UserControl xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
x:Class="Artemis.UI.Screens.Device.Layout.LayoutProviders.DefaultLayoutView">
<StackPanel ClipToBounds="False">
<Border Classes="card-separator" />
<StackPanel>
<TextBlock Text="Current layout" />
<TextBlock Classes="subtitle" FontSize="12" Text="Loading the default layout from the plugin or User Layouts folder." TextWrapping="Wrap" />
</StackPanel>
</StackPanel>
</UserControl>

View File

@ -0,0 +1,11 @@
using Avalonia.Controls;
namespace Artemis.UI.Screens.Device.Layout.LayoutProviders;
public partial class DefaultLayoutView : UserControl
{
public DefaultLayoutView()
{
InitializeComponent();
}
}

View File

@ -0,0 +1,42 @@
using Artemis.Core;
using Artemis.Core.Providers;
using Artemis.Core.Services;
using Artemis.UI.Shared;
namespace Artemis.UI.Screens.Device.Layout.LayoutProviders;
public class DefaultLayoutViewModel : ViewModelBase, ILayoutProviderViewModel
{
private readonly DefaultLayoutProvider _layoutProvider;
private readonly IDeviceService _deviceService;
public DefaultLayoutViewModel(DefaultLayoutProvider layoutProvider, IDeviceService deviceService)
{
_layoutProvider = layoutProvider;
_deviceService = deviceService;
}
/// <inheritdoc />
public ILayoutProvider Provider => _layoutProvider;
public ArtemisDevice Device { get; set; } = null!;
/// <inheritdoc />
public string Name => "Default";
/// <inheritdoc />
public string Description => "Attempts to load a layout from the default paths";
/// <inheritdoc />
public void Apply()
{
_layoutProvider.ConfigureDevice(Device);
Save();
}
private void Save()
{
_deviceService.SaveDevice(Device);
_deviceService.LoadDeviceLayout(Device);
}
}

View File

@ -0,0 +1,17 @@
using Artemis.Core;
using Artemis.Core.Providers;
namespace Artemis.UI.Screens.Device.Layout.LayoutProviders;
public interface ILayoutProviderViewModel
{
string Name { get; }
string Description { get; }
ILayoutProvider Provider { get; }
ArtemisDevice Device { get; set; }
void Apply();
}

View File

@ -0,0 +1,14 @@
<UserControl xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
x:Class="Artemis.UI.Screens.Device.Layout.LayoutProviders.NoneLayoutView">
<StackPanel ClipToBounds="False">
<Border Classes="card-separator" />
<StackPanel>
<TextBlock Text="Current layout" />
<TextBlock Classes="subtitle" FontSize="12" Text="Not loading any layout, leaving LEDs to be positioned by the device provider." TextWrapping="Wrap" />
</StackPanel>
</StackPanel>
</UserControl>

View File

@ -0,0 +1,11 @@
using Avalonia.Controls;
namespace Artemis.UI.Screens.Device.Layout.LayoutProviders;
public partial class NoneLayoutView : UserControl
{
public NoneLayoutView()
{
InitializeComponent();
}
}

View File

@ -0,0 +1,42 @@
using Artemis.Core;
using Artemis.Core.Providers;
using Artemis.Core.Services;
using Artemis.UI.Shared;
namespace Artemis.UI.Screens.Device.Layout.LayoutProviders;
public class NoneLayoutViewModel : ViewModelBase, ILayoutProviderViewModel
{
private readonly NoneLayoutProvider _layoutProvider;
private readonly IDeviceService _deviceService;
public NoneLayoutViewModel(NoneLayoutProvider layoutProvider, IDeviceService deviceService)
{
_layoutProvider = layoutProvider;
_deviceService = deviceService;
}
/// <inheritdoc />
public ILayoutProvider Provider => _layoutProvider;
public ArtemisDevice Device { get; set; } = null!;
/// <inheritdoc />
public string Name => "None";
/// <inheritdoc />
public string Description => "Do not load any layout";
/// <inheritdoc />
public void Apply()
{
_layoutProvider.ConfigureDevice(Device);
Save();
}
private void Save()
{
_deviceService.SaveDevice(Device);
_deviceService.LoadDeviceLayout(Device);
}
}

View File

@ -0,0 +1,47 @@
<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:services="clr-namespace:Artemis.WebClient.Workshop.Services;assembly=Artemis.WebClient.Workshop"
xmlns:layoutProviders="clr-namespace:Artemis.UI.Screens.Device.Layout.LayoutProviders"
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
x:Class="Artemis.UI.Screens.Device.Layout.LayoutProviders.WorkshopLayoutView"
x:DataType="layoutProviders:WorkshopLayoutViewModel">
<StackPanel ClipToBounds="False">
<Border Classes="card-separator" />
<Grid RowDefinitions="*,*" ColumnDefinitions="*,Auto">
<StackPanel Grid.Row="1" Grid.Column="0">
<TextBlock Text="Current layout" />
<TextBlock Classes="subtitle" FontSize="12" Text="Loading the layout from a workshop entry" TextWrapping="Wrap" />
</StackPanel>
<StackPanel Grid.Row="1" Grid.Column="1" VerticalAlignment="Center" Spacing="5">
<StackPanel.Styles>
<Style Selector="ComboBox.layoutProvider /template/ ContentControl#ContentPresenter">
<Setter Property="ContentTemplate">
<Setter.Value>
<DataTemplate x:DataType="services:InstalledEntry">
<TextBlock Text="{CompiledBinding Name}" TextWrapping="Wrap" MaxWidth="350" />
</DataTemplate>
</Setter.Value>
</Setter>
</Style>
</StackPanel.Styles>
<ComboBox Classes="layoutProvider"
Width="350"
SelectedItem="{CompiledBinding SelectedEntry}"
ItemsSource="{CompiledBinding Entries}"
PlaceholderText="Select an installed layout">
<ComboBox.ItemTemplate>
<DataTemplate x:DataType="services:InstalledEntry">
<StackPanel>
<TextBlock Text="{CompiledBinding Name}" TextWrapping="Wrap" MaxWidth="350" />
<TextBlock Classes="subtitle" Text="{CompiledBinding Author}" TextWrapping="Wrap" MaxWidth="350" />
</StackPanel>
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
<Button HorizontalAlignment="Right" Click="Button_OnClick">Browse workshop layouts</Button>
</StackPanel>
</Grid>
</StackPanel>
</UserControl>

View File

@ -0,0 +1,20 @@
using System.Threading.Tasks;
using Avalonia.Controls;
using Avalonia.Interactivity;
using Avalonia.ReactiveUI;
namespace Artemis.UI.Screens.Device.Layout.LayoutProviders;
public partial class WorkshopLayoutView : ReactiveUserControl<WorkshopLayoutViewModel>
{
public WorkshopLayoutView()
{
InitializeComponent();
}
private async void Button_OnClick(object? sender, RoutedEventArgs e)
{
if (ViewModel != null && await ViewModel.BrowseLayouts())
(VisualRoot as Window)?.Close();
}
}

View File

@ -0,0 +1,83 @@
using System.Collections.ObjectModel;
using System;
using System.Linq;
using System.Reactive.Disposables;
using System.Threading.Tasks;
using Artemis.Core;
using Artemis.Core.Providers;
using Artemis.Core.Services;
using Artemis.UI.Shared;
using Artemis.UI.Shared.Routing;
using Artemis.UI.Shared.Services;
using Artemis.WebClient.Workshop;
using Artemis.WebClient.Workshop.Providers;
using Artemis.WebClient.Workshop.Services;
using PropertyChanged.SourceGenerator;
using ReactiveUI;
namespace Artemis.UI.Screens.Device.Layout.LayoutProviders;
public partial class WorkshopLayoutViewModel : ActivatableViewModelBase, ILayoutProviderViewModel
{
[Notify] private InstalledEntry? _selectedEntry;
private readonly WorkshopLayoutProvider _layoutProvider;
private readonly IDeviceService _deviceService;
private readonly IWindowService _windowService;
private readonly IRouter _router;
public WorkshopLayoutViewModel(WorkshopLayoutProvider layoutProvider, IWorkshopService workshopService, IDeviceService deviceService, IWindowService windowService, IRouter router)
{
_layoutProvider = layoutProvider;
_deviceService = deviceService;
_windowService = windowService;
_router = router;
Entries = new ObservableCollection<InstalledEntry>(workshopService.GetInstalledEntries().Where(e => e.EntryType == EntryType.Layout));
this.WhenAnyValue(vm => vm.SelectedEntry).Subscribe(ApplyEntry);
this.WhenActivated((CompositeDisposable _) => SelectedEntry = Entries.FirstOrDefault(e => e.EntryId.ToString() == Device.LayoutSelection.Parameter));
}
/// <inheritdoc />
public ILayoutProvider Provider => _layoutProvider;
public ArtemisDevice Device { get; set; } = null!;
public ObservableCollection<InstalledEntry> Entries { get; }
/// <inheritdoc />
public string Name => "Workshop";
/// <inheritdoc />
public string Description => "Load a layout from the workshop";
/// <inheritdoc />
public void Apply()
{
_layoutProvider.ConfigureDevice(Device, null);
Save();
}
public async Task<bool> BrowseLayouts()
{
if (!await _windowService.ShowConfirmContentDialog("Open workshop", "Do you want to close this window and view the workshop?"))
return false;
await _router.Navigate("workshop/entries/layouts/1");
return true;
}
private void ApplyEntry(InstalledEntry? entry)
{
if (entry == null || Device.LayoutSelection.Parameter == entry.EntryId.ToString())
return;
_layoutProvider.ConfigureDevice(Device, entry);
Save();
}
private void Save()
{
_deviceService.SaveDevice(Device);
_deviceService.LoadDeviceLayout(Device);
}
}

View File

@ -2,7 +2,7 @@
using Artemis.Core; using Artemis.Core;
using Artemis.UI.Shared; using Artemis.UI.Shared;
namespace Artemis.UI.Screens.Device; namespace Artemis.UI.Screens.Device.Leds;
public class DeviceLedsTabLedViewModel : ViewModelBase public class DeviceLedsTabLedViewModel : ViewModelBase
{ {

View File

@ -5,9 +5,10 @@
xmlns:device="clr-namespace:Artemis.UI.Screens.Device" xmlns:device="clr-namespace:Artemis.UI.Screens.Device"
xmlns:converters="clr-namespace:Artemis.UI.Converters" xmlns:converters="clr-namespace:Artemis.UI.Converters"
xmlns:shared="clr-namespace:Artemis.UI.Shared.Converters;assembly=Artemis.UI.Shared" xmlns:shared="clr-namespace:Artemis.UI.Shared.Converters;assembly=Artemis.UI.Shared"
xmlns:leds="clr-namespace:Artemis.UI.Screens.Device.Leds"
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450" mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
x:Class="Artemis.UI.Screens.Device.DeviceLedsTabView" x:Class="Artemis.UI.Screens.Device.Leds.DeviceLedsTabView"
x:DataType="device:DeviceLedsTabViewModel"> x:DataType="leds:DeviceLedsTabViewModel">
<UserControl.Resources> <UserControl.Resources>
<converters:UriToFileNameConverter x:Key="UriToFileNameConverter" /> <converters:UriToFileNameConverter x:Key="UriToFileNameConverter" />
<converters:LedIdToStringConverter x:Key="LedIdToStringConverter" /> <converters:LedIdToStringConverter x:Key="LedIdToStringConverter" />

View File

@ -1,7 +1,6 @@
using Avalonia.Markup.Xaml;
using Avalonia.ReactiveUI; using Avalonia.ReactiveUI;
namespace Artemis.UI.Screens.Device; namespace Artemis.UI.Screens.Device.Leds;
public partial class DeviceLedsTabView : ReactiveUserControl<DeviceLedsTabViewModel> public partial class DeviceLedsTabView : ReactiveUserControl<DeviceLedsTabViewModel>
{ {

View File

@ -8,7 +8,7 @@ using Artemis.UI.Shared;
using DynamicData.Binding; using DynamicData.Binding;
using ReactiveUI; using ReactiveUI;
namespace Artemis.UI.Screens.Device; namespace Artemis.UI.Screens.Device.Leds;
public class DeviceLedsTabViewModel : ActivatableViewModelBase public class DeviceLedsTabViewModel : ActivatableViewModelBase
{ {

View File

@ -14,20 +14,21 @@ using Artemis.UI.Shared.Services;
using Avalonia.Threading; using Avalonia.Threading;
using FluentAvalonia.Core; using FluentAvalonia.Core;
using FluentAvalonia.UI.Controls; using FluentAvalonia.UI.Controls;
using PropertyChanged.SourceGenerator;
using ReactiveUI; using ReactiveUI;
using ContentDialogButton = Artemis.UI.Shared.Services.Builders.ContentDialogButton; using ContentDialogButton = Artemis.UI.Shared.Services.Builders.ContentDialogButton;
namespace Artemis.UI.Screens.Plugins; namespace Artemis.UI.Screens.Plugins;
public class PluginPrerequisitesInstallDialogViewModel : ContentDialogViewModelBase public partial class PluginPrerequisitesInstallDialogViewModel : ContentDialogViewModelBase
{ {
private PluginPrerequisiteViewModel? _activePrerequisite;
private bool _canInstall;
private bool _showFailed;
private bool _showInstall = true;
private bool _showIntro = true;
private bool _showProgress;
private CancellationTokenSource? _tokenSource; private CancellationTokenSource? _tokenSource;
[Notify] private PluginPrerequisiteViewModel? _activePrerequisite;
[Notify] private bool _canInstall;
[Notify] private bool _showFailed;
[Notify] private bool _showInstall = true;
[Notify] private bool _showIntro = true;
[Notify] private bool _showProgress;
public PluginPrerequisitesInstallDialogViewModel(List<IPrerequisitesSubject> subjects, IPrerequisitesVmFactory prerequisitesVmFactory) public PluginPrerequisitesInstallDialogViewModel(List<IPrerequisitesSubject> subjects, IPrerequisitesVmFactory prerequisitesVmFactory)
{ {
@ -51,42 +52,6 @@ public class PluginPrerequisitesInstallDialogViewModel : ContentDialogViewModelB
public ReactiveCommand<Unit, Unit> Install { get; } public ReactiveCommand<Unit, Unit> Install { get; }
public ObservableCollection<PluginPrerequisiteViewModel> Prerequisites { get; } public ObservableCollection<PluginPrerequisiteViewModel> Prerequisites { get; }
public PluginPrerequisiteViewModel? ActivePrerequisite
{
get => _activePrerequisite;
set => RaiseAndSetIfChanged(ref _activePrerequisite, value);
}
public bool ShowProgress
{
get => _showProgress;
set => RaiseAndSetIfChanged(ref _showProgress, value);
}
public bool ShowIntro
{
get => _showIntro;
set => RaiseAndSetIfChanged(ref _showIntro, value);
}
public bool ShowFailed
{
get => _showFailed;
set => RaiseAndSetIfChanged(ref _showFailed, value);
}
public bool ShowInstall
{
get => _showInstall;
set => RaiseAndSetIfChanged(ref _showInstall, value);
}
public bool CanInstall
{
get => _canInstall;
set => RaiseAndSetIfChanged(ref _canInstall, value);
}
public static async Task Show(IWindowService windowService, List<IPrerequisitesSubject> subjects) public static async Task Show(IWindowService windowService, List<IPrerequisitesSubject> subjects)
{ {
await windowService.CreateContentDialog() await windowService.CreateContentDialog()

View File

@ -15,19 +15,20 @@ using Artemis.UI.Shared.Services;
using Avalonia.Threading; using Avalonia.Threading;
using FluentAvalonia.Core; using FluentAvalonia.Core;
using FluentAvalonia.UI.Controls; using FluentAvalonia.UI.Controls;
using PropertyChanged.SourceGenerator;
using ReactiveUI; using ReactiveUI;
using ContentDialogButton = Artemis.UI.Shared.Services.Builders.ContentDialogButton; using ContentDialogButton = Artemis.UI.Shared.Services.Builders.ContentDialogButton;
namespace Artemis.UI.Screens.Plugins; namespace Artemis.UI.Screens.Plugins;
public class PluginPrerequisitesUninstallDialogViewModel : ContentDialogViewModelBase public partial class PluginPrerequisitesUninstallDialogViewModel : ContentDialogViewModelBase
{ {
private readonly IPluginManagementService _pluginManagementService; private readonly IPluginManagementService _pluginManagementService;
private readonly List<IPrerequisitesSubject> _subjects; private readonly List<IPrerequisitesSubject> _subjects;
private readonly IWindowService _windowService; private readonly IWindowService _windowService;
private PluginPrerequisiteViewModel? _activePrerequisite;
private bool _canUninstall;
private CancellationTokenSource? _tokenSource; private CancellationTokenSource? _tokenSource;
[Notify] private PluginPrerequisiteViewModel? _activePrerequisite;
[Notify] private bool _canUninstall;
public PluginPrerequisitesUninstallDialogViewModel(List<IPrerequisitesSubject> subjects, public PluginPrerequisitesUninstallDialogViewModel(List<IPrerequisitesSubject> subjects,
IPrerequisitesVmFactory prerequisitesVmFactory, IPrerequisitesVmFactory prerequisitesVmFactory,
@ -60,18 +61,6 @@ public class PluginPrerequisitesUninstallDialogViewModel : ContentDialogViewMode
public ReactiveCommand<Unit, Unit> Uninstall { get; } public ReactiveCommand<Unit, Unit> Uninstall { get; }
public ObservableCollection<PluginPrerequisiteViewModel> Prerequisites { get; } public ObservableCollection<PluginPrerequisiteViewModel> Prerequisites { get; }
public PluginPrerequisiteViewModel? ActivePrerequisite
{
get => _activePrerequisite;
set => RaiseAndSetIfChanged(ref _activePrerequisite, value);
}
public bool CanUninstall
{
get => _canUninstall;
set => RaiseAndSetIfChanged(ref _canUninstall, value);
}
public static async Task Show(IWindowService windowService, List<IPrerequisitesSubject> subjects, string cancelLabel = "Cancel") public static async Task Show(IWindowService windowService, List<IPrerequisitesSubject> subjects, string cancelLabel = "Cancel")
{ {
await windowService.CreateContentDialog() await windowService.CreateContentDialog()

View File

@ -16,16 +16,17 @@ using Artemis.UI.Shared.Services;
using Artemis.UI.Shared.Services.Builders; using Artemis.UI.Shared.Services.Builders;
using Avalonia.Threading; using Avalonia.Threading;
using Material.Icons; using Material.Icons;
using PropertyChanged.SourceGenerator;
using ReactiveUI; using ReactiveUI;
namespace Artemis.UI.Screens.Plugins.Features; namespace Artemis.UI.Screens.Plugins.Features;
public class PluginFeatureViewModel : ActivatableViewModelBase public partial class PluginFeatureViewModel : ActivatableViewModelBase
{ {
private readonly ICoreService _coreService; private readonly ICoreService _coreService;
private readonly IPluginManagementService _pluginManagementService; private readonly IPluginManagementService _pluginManagementService;
private readonly IWindowService _windowService; private readonly IWindowService _windowService;
private bool _enabling; [Notify] private bool _enabling;
public PluginFeatureViewModel(PluginFeatureInfo pluginFeatureInfo, public PluginFeatureViewModel(PluginFeatureInfo pluginFeatureInfo,
bool showShield, bool showShield,
@ -73,15 +74,8 @@ public class PluginFeatureViewModel : ActivatableViewModelBase
public PluginFeatureInfo FeatureInfo { get; } public PluginFeatureInfo FeatureInfo { get; }
public Exception? LoadException => FeatureInfo.LoadException; public Exception? LoadException => FeatureInfo.LoadException;
public bool ShowShield { get; } public bool ShowShield { get; }
public bool Enabling
{
get => _enabling;
set => RaiseAndSetIfChanged(ref _enabling, value);
}
public bool IsEnabled public bool IsEnabled
{ {
get => FeatureInfo.AlwaysEnabled || (FeatureInfo.Instance != null && FeatureInfo.Instance.IsEnabled); get => FeatureInfo.AlwaysEnabled || (FeatureInfo.Instance != null && FeatureInfo.Instance.IsEnabled);

View File

@ -14,21 +14,22 @@ using Artemis.UI.Shared.Services.Builders;
using Avalonia.Controls; using Avalonia.Controls;
using Avalonia.Threading; using Avalonia.Threading;
using Material.Icons; using Material.Icons;
using PropertyChanged.SourceGenerator;
using ReactiveUI; using ReactiveUI;
namespace Artemis.UI.Screens.Plugins; namespace Artemis.UI.Screens.Plugins;
public class PluginViewModel : ActivatableViewModelBase public partial class PluginViewModel : ActivatableViewModelBase
{ {
private readonly ICoreService _coreService; private readonly ICoreService _coreService;
private readonly INotificationService _notificationService; private readonly INotificationService _notificationService;
private readonly IPluginManagementService _pluginManagementService; private readonly IPluginManagementService _pluginManagementService;
private readonly IWindowService _windowService; private readonly IWindowService _windowService;
private bool _canInstallPrerequisites;
private bool _canRemovePrerequisites;
private bool _enabling;
private Plugin _plugin;
private Window? _settingsWindow; private Window? _settingsWindow;
[Notify] private bool _canInstallPrerequisites;
[Notify] private bool _canRemovePrerequisites;
[Notify] private bool _enabling;
[Notify] private Plugin _plugin;
public PluginViewModel(Plugin plugin, public PluginViewModel(Plugin plugin,
ReactiveCommand<Unit, Unit>? reload, ReactiveCommand<Unit, Unit>? reload,
@ -87,34 +88,9 @@ public class PluginViewModel : ActivatableViewModelBase
public ReactiveCommand<Unit, Unit> OpenPluginDirectory { get; } public ReactiveCommand<Unit, Unit> OpenPluginDirectory { get; }
public ObservableCollection<PluginPlatformViewModel> Platforms { get; } public ObservableCollection<PluginPlatformViewModel> Platforms { get; }
public Plugin Plugin
{
get => _plugin;
set => RaiseAndSetIfChanged(ref _plugin, value);
}
public bool Enabling
{
get => _enabling;
set => RaiseAndSetIfChanged(ref _enabling, value);
}
public string Type => Plugin.GetType().BaseType?.Name ?? Plugin.GetType().Name; public string Type => Plugin.GetType().BaseType?.Name ?? Plugin.GetType().Name;
public bool IsEnabled => Plugin.IsEnabled; public bool IsEnabled => Plugin.IsEnabled;
public bool CanInstallPrerequisites
{
get => _canInstallPrerequisites;
set => RaiseAndSetIfChanged(ref _canInstallPrerequisites, value);
}
public bool CanRemovePrerequisites
{
get => _canRemovePrerequisites;
set => RaiseAndSetIfChanged(ref _canRemovePrerequisites, value);
}
public async Task UpdateEnabled(bool enable) public async Task UpdateEnabled(bool enable)
{ {
if (Enabling) if (Enabling)

View File

@ -6,21 +6,20 @@ using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using Artemis.Core; using Artemis.Core;
using Artemis.UI.Shared; using Artemis.UI.Shared;
using PropertyChanged.SourceGenerator;
using ReactiveUI; using ReactiveUI;
namespace Artemis.UI.Screens.Plugins.Prerequisites; namespace Artemis.UI.Screens.Plugins.Prerequisites;
public class PluginPrerequisiteViewModel : ActivatableViewModelBase public partial class PluginPrerequisiteViewModel : ActivatableViewModelBase
{ {
private readonly ObservableAsPropertyHelper<int> _activeStepNumber; private readonly ObservableAsPropertyHelper<int> _activeStepNumber;
private readonly ObservableAsPropertyHelper<bool> _busy; private readonly ObservableAsPropertyHelper<bool> _busy;
private readonly bool _uninstall; private readonly bool _uninstall;
[Notify] private PluginPrerequisiteActionViewModel? _activeAction;
private PluginPrerequisiteActionViewModel? _activeAction; [Notify] private bool _installing;
[Notify] private bool _isMet;
private bool _installing; [Notify] private bool _uninstalling;
private bool _isMet;
private bool _uninstalling;
public PluginPrerequisiteViewModel(PluginPrerequisite pluginPrerequisite, bool uninstall) public PluginPrerequisiteViewModel(PluginPrerequisite pluginPrerequisite, bool uninstall)
{ {
@ -45,33 +44,7 @@ public class PluginPrerequisiteViewModel : ActivatableViewModelBase
} }
public ObservableCollection<PluginPrerequisiteActionViewModel> Actions { get; } public ObservableCollection<PluginPrerequisiteActionViewModel> Actions { get; }
public PluginPrerequisiteActionViewModel? ActiveAction
{
get => _activeAction;
set => RaiseAndSetIfChanged(ref _activeAction, value);
}
public PluginPrerequisite PluginPrerequisite { get; } public PluginPrerequisite PluginPrerequisite { get; }
public bool Installing
{
get => _installing;
set => RaiseAndSetIfChanged(ref _installing, value);
}
public bool Uninstalling
{
get => _uninstalling;
set => RaiseAndSetIfChanged(ref _uninstalling, value);
}
public bool IsMet
{
get => _isMet;
set => RaiseAndSetIfChanged(ref _isMet, value);
}
public bool Busy => _busy.Value; public bool Busy => _busy.Value;
public int ActiveStepNumber => _activeStepNumber.Value; public int ActiveStepNumber => _activeStepNumber.Value;

View File

@ -15,11 +15,12 @@ using Artemis.UI.Shared.Routing;
using Artemis.UI.Shared.Services; using Artemis.UI.Shared.Services;
using Artemis.UI.Shared.Services.ProfileEditor; using Artemis.UI.Shared.Services.ProfileEditor;
using Newtonsoft.Json; using Newtonsoft.Json;
using PropertyChanged.SourceGenerator;
using ReactiveUI; using ReactiveUI;
namespace Artemis.UI.Screens.ProfileEditor.MenuBar; namespace Artemis.UI.Screens.ProfileEditor.MenuBar;
public class MenuBarViewModel : ActivatableViewModelBase public partial class MenuBarViewModel : ActivatableViewModelBase
{ {
private readonly IRouter _router; private readonly IRouter _router;
private readonly IProfileEditorService _profileEditorService; private readonly IProfileEditorService _profileEditorService;
@ -29,12 +30,12 @@ public class MenuBarViewModel : ActivatableViewModelBase
private ObservableAsPropertyHelper<bool>? _focusFolder; private ObservableAsPropertyHelper<bool>? _focusFolder;
private ObservableAsPropertyHelper<bool>? _focusNone; private ObservableAsPropertyHelper<bool>? _focusNone;
private ObservableAsPropertyHelper<bool>? _focusSelection; private ObservableAsPropertyHelper<bool>? _focusSelection;
private ProfileEditorHistory? _history;
private ObservableAsPropertyHelper<bool>? _isSuspended; private ObservableAsPropertyHelper<bool>? _isSuspended;
private ObservableAsPropertyHelper<bool>? _keyBindingsEnabled; private ObservableAsPropertyHelper<bool>? _keyBindingsEnabled;
private ObservableAsPropertyHelper<ProfileConfiguration?>? _profileConfiguration; private ObservableAsPropertyHelper<ProfileConfiguration?>? _profileConfiguration;
private ObservableAsPropertyHelper<RenderProfileElement?>? _profileElement; private ObservableAsPropertyHelper<RenderProfileElement?>? _profileElement;
private ObservableAsPropertyHelper<bool>? _suspendedEditing; private ObservableAsPropertyHelper<bool>? _suspendedEditing;
[Notify] private ProfileEditorHistory? _history;
public MenuBarViewModel(IRouter router, IProfileEditorService profileEditorService, IProfileService profileService, ISettingsService settingsService, IWindowService windowService) public MenuBarViewModel(IRouter router, IProfileEditorService profileEditorService, IProfileService profileService, ISettingsService settingsService, IWindowService windowService)
{ {
@ -108,12 +109,6 @@ public class MenuBarViewModel : ActivatableViewModelBase
public PluginSetting<bool> AlwaysApplyDataBindings => _settingsService.GetSetting("ProfileEditor.AlwaysApplyDataBindings", false); public PluginSetting<bool> AlwaysApplyDataBindings => _settingsService.GetSetting("ProfileEditor.AlwaysApplyDataBindings", false);
public PluginSetting<ProfileEditorFocusMode> FocusMode => _settingsService.GetSetting("ProfileEditor.FocusMode", ProfileEditorFocusMode.Folder); public PluginSetting<ProfileEditorFocusMode> FocusMode => _settingsService.GetSetting("ProfileEditor.FocusMode", ProfileEditorFocusMode.Folder);
public ProfileEditorHistory? History
{
get => _history;
set => RaiseAndSetIfChanged(ref _history, value);
}
private void ExecuteAddFolder() private void ExecuteAddFolder()
{ {
if (ProfileConfiguration?.Profile == null) if (ProfileConfiguration?.Profile == null)

View File

@ -8,11 +8,12 @@ using Artemis.Core.Services;
using Artemis.UI.Shared; using Artemis.UI.Shared;
using Artemis.UI.Shared.Services.ProfileEditor; using Artemis.UI.Shared.Services.ProfileEditor;
using Avalonia.Threading; using Avalonia.Threading;
using PropertyChanged.SourceGenerator;
using ReactiveUI; using ReactiveUI;
namespace Artemis.UI.Screens.ProfileEditor.Playback; namespace Artemis.UI.Screens.ProfileEditor.Playback;
public class PlaybackViewModel : ActivatableViewModelBase public partial class PlaybackViewModel : ActivatableViewModelBase
{ {
private readonly IProfileEditorService _profileEditorService; private readonly IProfileEditorService _profileEditorService;
private readonly ISettingsService _settingsService; private readonly ISettingsService _settingsService;
@ -22,9 +23,9 @@ public class PlaybackViewModel : ActivatableViewModelBase
private DateTime _lastUpdate; private DateTime _lastUpdate;
private ObservableAsPropertyHelper<bool>? _playing; private ObservableAsPropertyHelper<bool>? _playing;
private RenderProfileElement? _profileElement; private RenderProfileElement? _profileElement;
private bool _repeating; [Notify] private bool _repeating;
private bool _repeatSegment; [Notify] private bool _repeatSegment;
private bool _repeatTimeline; [Notify] private bool _repeatTimeline;
public PlaybackViewModel(IProfileEditorService profileEditorService, ISettingsService settingsService) public PlaybackViewModel(IProfileEditorService profileEditorService, ISettingsService settingsService)
{ {
@ -84,24 +85,6 @@ public class PlaybackViewModel : ActivatableViewModelBase
public bool Playing => _playing?.Value ?? false; public bool Playing => _playing?.Value ?? false;
public bool KeyBindingsEnabled => _keyBindingsEnabled?.Value ?? false; public bool KeyBindingsEnabled => _keyBindingsEnabled?.Value ?? false;
public bool Repeating
{
get => _repeating;
set => RaiseAndSetIfChanged(ref _repeating, value);
}
public bool RepeatTimeline
{
get => _repeatTimeline;
set => RaiseAndSetIfChanged(ref _repeatTimeline, value);
}
public bool RepeatSegment
{
get => _repeatSegment;
set => RaiseAndSetIfChanged(ref _repeatSegment, value);
}
public ReactiveCommand<Unit, Unit> PlayFromStart { get; } public ReactiveCommand<Unit, Unit> PlayFromStart { get; }
public ReactiveCommand<Unit, Unit> TogglePlay { get; } public ReactiveCommand<Unit, Unit> TogglePlay { get; }
public ReactiveCommand<Unit, Unit> GoToStart { get; } public ReactiveCommand<Unit, Unit> GoToStart { get; }

View File

@ -4,16 +4,17 @@ using Artemis.UI.Shared;
using Artemis.UI.Shared.Services.ProfileEditor; using Artemis.UI.Shared.Services.ProfileEditor;
using Artemis.UI.Shared.Services.ProfileEditor.Commands; using Artemis.UI.Shared.Services.ProfileEditor.Commands;
using FluentAvalonia.UI.Controls; using FluentAvalonia.UI.Controls;
using PropertyChanged.SourceGenerator;
using ReactiveUI; using ReactiveUI;
using ReactiveUI.Validation.Extensions; using ReactiveUI.Validation.Extensions;
namespace Artemis.UI.Screens.ProfileEditor.ProfileTree.ContentDialogs; namespace Artemis.UI.Screens.ProfileEditor.ProfileTree.ContentDialogs;
public class ProfileElementRenameViewModel : ContentDialogViewModelBase public partial class ProfileElementRenameViewModel : ContentDialogViewModelBase
{ {
private readonly IProfileEditorService _profileEditorService; private readonly IProfileEditorService _profileEditorService;
private readonly ProfileElement _profileElement; private readonly ProfileElement _profileElement;
private string? _profileElementName; [Notify] private string? _profileElementName;
public ProfileElementRenameViewModel(IProfileEditorService profileEditorService, ProfileElement profileElement) public ProfileElementRenameViewModel(IProfileEditorService profileEditorService, ProfileElement profileElement)
{ {
@ -25,13 +26,6 @@ public class ProfileElementRenameViewModel : ContentDialogViewModelBase
this.ValidationRule(vm => vm.ProfileElementName, name => !string.IsNullOrWhiteSpace(name), "You must specify a valid name"); this.ValidationRule(vm => vm.ProfileElementName, name => !string.IsNullOrWhiteSpace(name), "You must specify a valid name");
} }
public string? ProfileElementName
{
get => _profileElementName;
set => RaiseAndSetIfChanged(ref _profileElementName, value);
}
public ReactiveCommand<Unit, Unit> Confirm { get; } public ReactiveCommand<Unit, Unit> Confirm { get; }
private void ExecuteConfirm() private void ExecuteConfirm()

Some files were not shown because too many files have changed in this diff Show More