mirror of
https://github.com/Artemis-RGB/Artemis
synced 2025-12-13 05:48:35 +00:00
Compare commits
13 Commits
afa4c508b3
...
3ca3b6a18a
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
3ca3b6a18a | ||
|
|
50b5f13051 | ||
|
|
b3dc12790c | ||
|
|
0c107410d5 | ||
|
|
b01c5b16fc | ||
|
|
8b4b5d8810 | ||
|
|
28edabae89 | ||
|
|
dad6a56238 | ||
|
|
e8590abd61 | ||
|
|
9393bf2b68 | ||
|
|
e304d67035 | ||
|
|
e33ae8a066 | ||
|
|
0667d58ed8 |
@ -1,6 +1,4 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<Import Project="..\Artemis.props" />
|
||||
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net7.0</TargetFramework>
|
||||
<PreserveCompilationContext>false</PreserveCompilationContext>
|
||||
@ -38,21 +36,20 @@
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="DryIoc.dll" Version="5.4.2" />
|
||||
<PackageReference Include="DryIoc.dll" Version="5.4.3" />
|
||||
<PackageReference Include="EmbedIO" Version="3.5.2" />
|
||||
<PackageReference Include="HidSharp" Version="2.1.0" />
|
||||
<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="McMaster.NETCore.Plugins" Version="1.4.0" />
|
||||
<PackageReference Include="Newtonsoft.Json" Version="13.0.3" />
|
||||
<PackageReference Include="RGB.NET.Core" Version="$(RGBDotNetVersion)" />
|
||||
<PackageReference Include="RGB.NET.Presets" Version="$(RGBDotNetVersion)" />
|
||||
<PackageReference Include="RGB.NET.Layout" Version="$(RGBDotNetVersion)" />
|
||||
<PackageReference Include="Serilog" Version="3.0.1" />
|
||||
<PackageReference Include="Serilog.Sinks.Console" Version="4.1.0" />
|
||||
<PackageReference Include="RGB.NET.Core" Version="2.0.4-prerelease.1" />
|
||||
<PackageReference Include="RGB.NET.Layout" Version="2.0.4-prerelease.1" />
|
||||
<PackageReference Include="RGB.NET.Presets" Version="2.0.4-prerelease.1" />
|
||||
<PackageReference Include="Serilog.Sinks.Console" Version="5.0.1" />
|
||||
<PackageReference Include="Serilog.Sinks.Debug" Version="2.0.0" />
|
||||
<PackageReference Include="Serilog.Sinks.File" Version="5.0.0" />
|
||||
<PackageReference Include="SkiaSharp" Version="$(SkiaSharpVersion)" />
|
||||
<PackageReference Include="SkiaSharp" Version="2.88.7" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
||||
@ -47,6 +47,7 @@ public static class Constants
|
||||
/// The full path to the Artemis logs folder
|
||||
/// </summary>
|
||||
public static readonly string LogsFolder = Path.Combine(DataFolder, "Logs");
|
||||
|
||||
/// <summary>
|
||||
/// The full path to the Artemis logs folder
|
||||
/// </summary>
|
||||
@ -61,6 +62,11 @@ public static class Constants
|
||||
/// The full path to the Artemis user layouts folder
|
||||
/// </summary>
|
||||
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>
|
||||
/// The current API version for plugins
|
||||
@ -71,9 +77,9 @@ public static class Constants
|
||||
/// <summary>
|
||||
/// The current version of the application
|
||||
/// </summary>
|
||||
public static readonly string CurrentVersion = CoreAssembly.GetCustomAttribute<AssemblyInformationalVersionAttribute>()!.InformationalVersion != "1.0.0"
|
||||
? CoreAssembly.GetCustomAttribute<AssemblyInformationalVersionAttribute>()!.InformationalVersion
|
||||
: "local";
|
||||
public static readonly string CurrentVersion = CoreAssembly.GetCustomAttribute<AssemblyInformationalVersionAttribute>()!.InformationalVersion.StartsWith("1.0.0")
|
||||
? "local"
|
||||
: CoreAssembly.GetCustomAttribute<AssemblyInformationalVersionAttribute>()!.InformationalVersion;
|
||||
|
||||
/// <summary>
|
||||
/// The plugin info used by core components of Artemis
|
||||
@ -154,4 +160,5 @@ public static class Constants
|
||||
/// Gets the graphics context to be used for rendering by SkiaSharp.
|
||||
/// </summary>
|
||||
public static IManagedGraphicsContext? ManagedGraphicsContext { get; internal set; }
|
||||
|
||||
}
|
||||
@ -2,6 +2,7 @@ using System;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using Artemis.Core.DryIoc.Factories;
|
||||
using Artemis.Core.Providers;
|
||||
using Artemis.Core.Services;
|
||||
using Artemis.Storage;
|
||||
using Artemis.Storage.Migrations.Interfaces;
|
||||
@ -35,7 +36,8 @@ public static class ContainerExtensions
|
||||
|
||||
// Bind migrations
|
||||
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(Made.Of(_ => ServiceInfo.Of<IPluginSettingsFactory>(), f => f.CreatePluginSettings(Arg.Index<Type>(0)), r => r.Parent.ImplementationType));
|
||||
container.Register<ILoggerFactory, LoggerFactory>(Reuse.Singleton);
|
||||
|
||||
@ -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();
|
||||
}
|
||||
}
|
||||
@ -2,9 +2,9 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.ComponentModel;
|
||||
using System.Diagnostics;
|
||||
using System.Linq;
|
||||
using Artemis.Core.DeviceProviders;
|
||||
using Artemis.Core.Providers;
|
||||
using Artemis.Core.Services;
|
||||
using Artemis.Storage.Entities.Surface;
|
||||
using RGB.NET.Core;
|
||||
@ -46,6 +46,7 @@ public class ArtemisDevice : CorePropertyChanged
|
||||
InputIdentifiers = new List<ArtemisDeviceInputIdentifier>();
|
||||
InputMappings = new Dictionary<ArtemisLed, ArtemisLed>();
|
||||
Categories = new HashSet<DeviceCategory>();
|
||||
LayoutSelection = new LayoutSelection {Type = DefaultLayoutProvider.LayoutType};
|
||||
|
||||
RgbDevice.ColorCorrections.Clear();
|
||||
RgbDevice.ColorCorrections.Add(new ScaleColorCorrection(this));
|
||||
@ -74,6 +75,7 @@ public class ArtemisDevice : CorePropertyChanged
|
||||
InputIdentifiers = new List<ArtemisDeviceInputIdentifier>();
|
||||
InputMappings = new Dictionary<ArtemisLed, ArtemisLed>();
|
||||
Categories = new HashSet<DeviceCategory>();
|
||||
LayoutSelection = new LayoutSelection {Type = DefaultLayoutProvider.LayoutType};
|
||||
|
||||
foreach (DeviceInputIdentifierEntity identifierEntity in DeviceEntity.InputIdentifiers)
|
||||
InputIdentifiers.Add(new ArtemisDeviceInputIdentifier(identifierEntity.InputProvider, identifierEntity.Identifier));
|
||||
@ -153,6 +155,8 @@ public class ArtemisDevice : CorePropertyChanged
|
||||
/// </summary>
|
||||
public HashSet<DeviceCategory> Categories { get; }
|
||||
|
||||
public LayoutSelection LayoutSelection { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the X-position of the device
|
||||
/// </summary>
|
||||
@ -293,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>
|
||||
/// Gets or sets the logical layout of the device e.g. DE, UK or US.
|
||||
/// <para>Only applicable to keyboards</para>
|
||||
@ -320,20 +311,6 @@ public class ArtemisDevice : CorePropertyChanged
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the path of the custom layout to load when calling <see cref="GetBestDeviceLayout" />
|
||||
/// for this device
|
||||
/// </summary>
|
||||
public string? CustomLayoutPath
|
||||
{
|
||||
get => DeviceEntity.CustomLayoutPath;
|
||||
set
|
||||
{
|
||||
DeviceEntity.CustomLayoutPath = value;
|
||||
OnPropertyChanged(nameof(CustomLayoutPath));
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the layout of the device expanded with Artemis-specific data
|
||||
/// </summary>
|
||||
@ -381,40 +358,6 @@ public class ArtemisDevice : CorePropertyChanged
|
||||
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>
|
||||
/// Occurs when the underlying RGB.NET device was updated
|
||||
/// </summary>
|
||||
@ -455,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>
|
||||
/// Applies the provided layout to the device
|
||||
/// </summary>
|
||||
@ -475,13 +410,13 @@ public class ArtemisDevice : CorePropertyChanged
|
||||
/// A boolean indicating whether to remove excess LEDs present in the device but missing
|
||||
/// in the layout
|
||||
/// </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)
|
||||
throw new ArtemisCoreException($"Cannot apply layout with {nameof(createMissingLeds)} set to true because the device provider does not support it");
|
||||
if (layout != null && layout.IsValid && removeExcessiveLeds && !DeviceProvider.RemoveExcessiveLedsSupported)
|
||||
throw new ArtemisCoreException($"Cannot apply layout with {nameof(removeExcessiveLeds)} set to true because the device provider does not support it");
|
||||
|
||||
|
||||
// Always clear the current layout
|
||||
ClearLayout();
|
||||
|
||||
@ -502,6 +437,14 @@ public class ArtemisDevice : CorePropertyChanged
|
||||
CalculateRenderProperties();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Invokes the <see cref="DeviceUpdated" /> event
|
||||
/// </summary>
|
||||
protected virtual void OnDeviceUpdated()
|
||||
{
|
||||
DeviceUpdated?.Invoke(this, EventArgs.Empty);
|
||||
}
|
||||
|
||||
private void ClearLayout()
|
||||
{
|
||||
if (Layout == null)
|
||||
@ -534,6 +477,9 @@ public class ArtemisDevice : CorePropertyChanged
|
||||
DeviceEntity.Categories.Clear();
|
||||
foreach (DeviceCategory deviceCategory in Categories)
|
||||
DeviceEntity.Categories.Add((int) deviceCategory);
|
||||
|
||||
DeviceEntity.LayoutType = LayoutSelection.Type;
|
||||
DeviceEntity.LayoutParameter = LayoutSelection.Parameter;
|
||||
}
|
||||
|
||||
internal void Load()
|
||||
@ -548,6 +494,9 @@ public class ArtemisDevice : CorePropertyChanged
|
||||
if (!Categories.Any())
|
||||
ApplyDefaultCategories();
|
||||
|
||||
LayoutSelection.Type = DeviceEntity.LayoutType;
|
||||
LayoutSelection.Parameter = DeviceEntity.LayoutParameter;
|
||||
|
||||
LoadInputMappings();
|
||||
}
|
||||
|
||||
@ -573,7 +522,7 @@ public class ArtemisDevice : CorePropertyChanged
|
||||
{
|
||||
Leds = RgbDevice.Select(l => new ArtemisLed(l, this)).ToList().AsReadOnly();
|
||||
LedIds = new ReadOnlyDictionary<LedId, ArtemisLed>(Leds.ToDictionary(l => l.RgbLed.Id, l => l));
|
||||
|
||||
|
||||
if (loadInputMappings)
|
||||
LoadInputMappings();
|
||||
}
|
||||
|
||||
@ -12,17 +12,18 @@ namespace Artemis.Core;
|
||||
/// </summary>
|
||||
public class ArtemisLayout
|
||||
{
|
||||
private static readonly string DefaultLayoutPath = Path.Combine(Constants.ApplicationFolder, "DefaultLayouts", "Artemis");
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new instance of the <see cref="ArtemisLayout" /> class
|
||||
/// </summary>
|
||||
/// <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, LayoutSource source)
|
||||
public ArtemisLayout(string filePath)
|
||||
{
|
||||
FilePath = filePath;
|
||||
Source = source;
|
||||
Leds = new List<ArtemisLedLayout>();
|
||||
|
||||
IsDefaultLayout = filePath.StartsWith(DefaultLayoutPath);
|
||||
|
||||
LoadLayout();
|
||||
}
|
||||
|
||||
@ -31,11 +32,6 @@ public class ArtemisLayout
|
||||
/// </summary>
|
||||
public string FilePath { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the source from where this layout was loaded
|
||||
/// </summary>
|
||||
public LayoutSource Source { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets a boolean indicating whether a valid layout was loaded
|
||||
/// </summary>
|
||||
@ -61,6 +57,8 @@ public class ArtemisLayout
|
||||
/// </summary>
|
||||
public LayoutCustomDeviceData LayoutCustomDeviceData { get; private set; } = null!;
|
||||
|
||||
public bool IsDefaultLayout { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Applies the layout to the provided device
|
||||
/// </summary>
|
||||
@ -121,29 +119,28 @@ public class ArtemisLayout
|
||||
|
||||
internal static ArtemisLayout? GetDefaultLayout(ArtemisDevice device)
|
||||
{
|
||||
string layoutFolder = Path.Combine(Constants.ApplicationFolder, "DefaultLayouts", "Artemis");
|
||||
if (device.DeviceType == RGBDeviceType.Keyboard)
|
||||
{
|
||||
// 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.PhysicalLayout == KeyboardLayoutType.ANSI)
|
||||
return new ArtemisLayout(Path.Combine(layoutFolder, "Keyboard", "Artemis XL keyboard-ANSI.xml"), LayoutSource.Default);
|
||||
return new ArtemisLayout(Path.Combine(layoutFolder, "Keyboard", "Artemis XL keyboard-ISO.xml"), LayoutSource.Default);
|
||||
return new ArtemisLayout(Path.Combine(DefaultLayoutPath, "Keyboard", "Artemis XL keyboard-ANSI.xml"));
|
||||
return new ArtemisLayout(Path.Combine(DefaultLayoutPath, "Keyboard", "Artemis XL keyboard-ISO.xml"));
|
||||
}
|
||||
|
||||
// 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.PhysicalLayout == KeyboardLayoutType.ANSI)
|
||||
return new ArtemisLayout(Path.Combine(layoutFolder + "Keyboard", "Artemis L keyboard-ANSI.xml"), LayoutSource.Default);
|
||||
return new ArtemisLayout(Path.Combine(layoutFolder + "Keyboard", "Artemis L keyboard-ISO.xml"), LayoutSource.Default);
|
||||
return new ArtemisLayout(Path.Combine(DefaultLayoutPath, "Keyboard", "Artemis L keyboard-ANSI.xml"));
|
||||
return new ArtemisLayout(Path.Combine(DefaultLayoutPath, "Keyboard", "Artemis L keyboard-ISO.xml"));
|
||||
}
|
||||
|
||||
// No numpad will result in TKL
|
||||
if (device.PhysicalLayout == KeyboardLayoutType.ANSI)
|
||||
return new ArtemisLayout(Path.Combine(layoutFolder + "Keyboard", "Artemis TKL keyboard-ANSI.xml"), LayoutSource.Default);
|
||||
return new ArtemisLayout(Path.Combine(layoutFolder + "Keyboard", "Artemis TKL keyboard-ISO.xml"), LayoutSource.Default);
|
||||
return new ArtemisLayout(Path.Combine(DefaultLayoutPath, "Keyboard", "Artemis TKL keyboard-ANSI.xml"));
|
||||
return new ArtemisLayout(Path.Combine(DefaultLayoutPath, "Keyboard", "Artemis TKL keyboard-ISO.xml"));
|
||||
}
|
||||
|
||||
// if (device.DeviceType == RGBDeviceType.Mouse)
|
||||
@ -151,21 +148,21 @@ public class ArtemisLayout
|
||||
// if (device.Leds.Count == 1)
|
||||
// {
|
||||
// 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(layoutFolder + "Mouse", "1 LED mouse.xml"), LayoutSource.Default);
|
||||
// return new ArtemisLayout(Path.Combine(DefaultLayoutPath, "Mouse", "1 LED mouse logo.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))
|
||||
// return new ArtemisLayout(Path.Combine(layoutFolder + "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 logo.xml"), LayoutSource.Default);
|
||||
// return new ArtemisLayout(Path.Combine(DefaultLayoutPath, "Mouse", "4 LED mouse.xml"), LayoutSource.Default);
|
||||
// }
|
||||
|
||||
if (device.DeviceType == RGBDeviceType.Headset)
|
||||
{
|
||||
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)
|
||||
return new ArtemisLayout(Path.Combine(layoutFolder + "Headset", "Artemis 2 LED headset.xml"), LayoutSource.Default);
|
||||
return new ArtemisLayout(Path.Combine(layoutFolder + "Headset", "Artemis 4 LED headset.xml"), LayoutSource.Default);
|
||||
return new ArtemisLayout(Path.Combine(DefaultLayoutPath, "Headset", "Artemis 2 LED headset.xml"));
|
||||
return new ArtemisLayout(Path.Combine(DefaultLayoutPath, "Headset", "Artemis 4 LED headset.xml"));
|
||||
}
|
||||
|
||||
return null;
|
||||
@ -206,30 +203,10 @@ public class ArtemisLayout
|
||||
else
|
||||
Image = null;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Represents a source from where a layout came
|
||||
/// </summary>
|
||||
public enum LayoutSource
|
||||
{
|
||||
/// <summary>
|
||||
/// 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
|
||||
/// <inheritdoc />
|
||||
public override string ToString()
|
||||
{
|
||||
return FilePath;
|
||||
}
|
||||
}
|
||||
28
src/Artemis.Core/Models/Surface/LayoutSelection.cs
Normal file
28
src/Artemis.Core/Models/Surface/LayoutSelection.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
@ -62,7 +62,7 @@ public abstract class DeviceProvider : PluginFeature
|
||||
device.DeviceType.ToString(),
|
||||
GetDeviceLayoutName(device)
|
||||
);
|
||||
return new ArtemisLayout(filePath, LayoutSource.Plugin);
|
||||
return new ArtemisLayout(filePath);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@ -79,7 +79,7 @@ public abstract class DeviceProvider : PluginFeature
|
||||
device.DeviceType.ToString(),
|
||||
GetDeviceLayoutName(device)
|
||||
);
|
||||
return new ArtemisLayout(filePath, LayoutSource.User);
|
||||
return new ArtemisLayout(filePath);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
||||
37
src/Artemis.Core/Providers/CustomPathLayoutProvider.cs
Normal file
37
src/Artemis.Core/Providers/CustomPathLayoutProvider.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
41
src/Artemis.Core/Providers/DefaultLayoutProvider.cs
Normal file
41
src/Artemis.Core/Providers/DefaultLayoutProvider.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
17
src/Artemis.Core/Providers/Interfaces/ILayoutProvider.cs
Normal file
17
src/Artemis.Core/Providers/Interfaces/ILayoutProvider.cs
Normal 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);
|
||||
}
|
||||
34
src/Artemis.Core/Providers/NoneLayoutProvider.cs
Normal file
34
src/Artemis.Core/Providers/NoneLayoutProvider.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
@ -4,9 +4,11 @@ using System.Collections.ObjectModel;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using Artemis.Core.DeviceProviders;
|
||||
using Artemis.Core.Providers;
|
||||
using Artemis.Core.Services.Models;
|
||||
using Artemis.Storage.Entities.Surface;
|
||||
using Artemis.Storage.Repositories.Interfaces;
|
||||
using DryIoc;
|
||||
using RGB.NET.Core;
|
||||
using Serilog;
|
||||
|
||||
@ -18,15 +20,21 @@ internal class DeviceService : IDeviceService
|
||||
private readonly IPluginManagementService _pluginManagementService;
|
||||
private readonly IDeviceRepository _deviceRepository;
|
||||
private readonly Lazy<IRenderService> _renderService;
|
||||
private readonly Func<List<ILayoutProvider>> _getLayoutProviders;
|
||||
private readonly List<ArtemisDevice> _enabledDevices = 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;
|
||||
_pluginManagementService = pluginManagementService;
|
||||
_deviceRepository = deviceRepository;
|
||||
_renderService = renderService;
|
||||
_getLayoutProviders = getLayoutProviders;
|
||||
|
||||
EnabledDevices = new ReadOnlyCollection<ArtemisDevice>(_enabledDevices);
|
||||
Devices = new ReadOnlyCollection<ArtemisDevice>(_devices);
|
||||
@ -157,12 +165,30 @@ internal class DeviceService : IDeviceService
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public void ApplyDeviceLayout(ArtemisDevice device, ArtemisLayout? layout)
|
||||
public void LoadDeviceLayout(ArtemisDevice device)
|
||||
{
|
||||
if (layout == null || layout.Source == LayoutSource.Default)
|
||||
device.ApplyLayout(layout, false, false);
|
||||
else
|
||||
device.ApplyLayout(layout, device.DeviceProvider.CreateMissingLedsSupported, device.DeviceProvider.RemoveExcessiveLedsSupported);
|
||||
ILayoutProvider? provider = _getLayoutProviders().FirstOrDefault(p => p.IsMatch(device));
|
||||
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
|
||||
provider?.ApplyLayout(device, layout);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
_logger.Error(e, "Failed to apply device layout");
|
||||
}
|
||||
|
||||
UpdateLeds();
|
||||
}
|
||||
@ -230,7 +256,7 @@ internal class DeviceService : IDeviceService
|
||||
device = new ArtemisDevice(rgbDevice, deviceProvider);
|
||||
}
|
||||
|
||||
ApplyDeviceLayout(device, device.GetBestDeviceLayout());
|
||||
LoadDeviceLayout(device);
|
||||
return device;
|
||||
}
|
||||
|
||||
|
||||
@ -43,11 +43,10 @@ public interface IDeviceService : IArtemisService
|
||||
void AutoArrangeDevices();
|
||||
|
||||
/// <summary>
|
||||
/// Apples the provided <see cref="ArtemisLayout" /> to the provided <see cref="ArtemisDevice" />
|
||||
/// Apples the best available to the provided <see cref="ArtemisDevice" />
|
||||
/// </summary>
|
||||
/// <param name="device"></param>
|
||||
/// <param name="layout"></param>
|
||||
void ApplyDeviceLayout(ArtemisDevice device, ArtemisLayout? layout);
|
||||
void LoadDeviceLayout(ArtemisDevice device);
|
||||
|
||||
/// <summary>
|
||||
/// Enables the provided device
|
||||
|
||||
@ -57,7 +57,7 @@ internal class PluginManagementService : IPluginManagementService
|
||||
|
||||
// Remove the old directory if it exists
|
||||
if (Directory.Exists(pluginDirectory.FullName))
|
||||
pluginDirectory.DeleteRecursively();
|
||||
pluginDirectory.Delete(true);
|
||||
|
||||
// Extract everything in the same archive directory to the unique plugin directory
|
||||
Utilities.CreateAccessibleDirectory(pluginDirectory.FullName);
|
||||
|
||||
@ -1,6 +1,4 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<Import Project="..\Artemis.props" />
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net7.0</TargetFramework>
|
||||
<PreserveCompilationContext>false</PreserveCompilationContext>
|
||||
@ -9,6 +7,6 @@
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="LiteDB" Version="5.0.17" />
|
||||
<PackageReference Include="Serilog" Version="3.0.1" />
|
||||
<PackageReference Include="Serilog" Version="3.1.1" />
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
@ -22,11 +22,11 @@ public class DeviceEntity
|
||||
public float GreenScale { get; set; }
|
||||
public float BlueScale { get; set; }
|
||||
public bool IsEnabled { get; set; }
|
||||
|
||||
public bool DisableDefaultLayout { get; set; }
|
||||
|
||||
public int PhysicalLayout { 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<InputMappingEntity> InputMappings { get; set; }
|
||||
|
||||
@ -1,4 +1,5 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Artemis.Storage.Entities.Workshop;
|
||||
|
||||
@ -16,6 +17,6 @@ public class EntryEntity
|
||||
public long ReleaseId { get; set; }
|
||||
public string ReleaseVersion { get; set; }
|
||||
public DateTimeOffset InstalledAt { get; set; }
|
||||
|
||||
public string LocalReference { get; set; }
|
||||
|
||||
public Dictionary<string,object> Metadata { get; set; }
|
||||
}
|
||||
35
src/Artemis.Storage/Migrations/M0023LayoutProviders.cs
Normal file
35
src/Artemis.Storage/Migrations/M0023LayoutProviders.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
@ -1,6 +1,4 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<Import Project="..\Artemis.props" />
|
||||
|
||||
<PropertyGroup>
|
||||
<OutputType>WinExe</OutputType>
|
||||
<TargetFramework>net7.0</TargetFramework>
|
||||
|
||||
@ -1,6 +1,4 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<Import Project="..\Artemis.props" />
|
||||
|
||||
<PropertyGroup>
|
||||
<OutputType>WinExe</OutputType>
|
||||
<TargetFramework>net7.0</TargetFramework>
|
||||
|
||||
@ -1,6 +1,4 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<Import Project="..\Artemis.props" />
|
||||
|
||||
<PropertyGroup>
|
||||
<OutputType>Library</OutputType>
|
||||
<TargetFramework>net7.0</TargetFramework>
|
||||
@ -12,16 +10,16 @@
|
||||
</PropertyGroup>
|
||||
|
||||
<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.-->
|
||||
<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="11.0.2" />
|
||||
<PackageReference Include="DynamicData" Version="8.0.2" />
|
||||
<PackageReference Include="FluentAvaloniaUI" Version="2.0.4" />
|
||||
<PackageReference Include="Material.Icons.Avalonia" Version="2.0.1" />
|
||||
<PackageReference Include="ReactiveUI" Version="19.5.1" />
|
||||
<PackageReference Condition="'$(Configuration)' == 'Debug'" Include="Avalonia.Diagnostics" Version="11.0.6" />
|
||||
<PackageReference Include="Avalonia.Controls.ItemsRepeater" Version="11.0.6" />
|
||||
<PackageReference Include="Avalonia.ReactiveUI" Version="11.0.6" />
|
||||
<PackageReference Include="Avalonia.Xaml.Behaviors" Version="11.0.6" />
|
||||
<PackageReference Include="DynamicData" Version="8.3.27" />
|
||||
<PackageReference Include="FluentAvaloniaUI" Version="2.0.5" />
|
||||
<PackageReference Include="Material.Icons.Avalonia" Version="2.1.0" />
|
||||
<PackageReference Include="ReactiveUI" Version="19.5.39" />
|
||||
<PackageReference Include="ReactiveUI.Validation" Version="3.1.7" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
|
||||
@ -107,7 +107,7 @@ public class ContentDialogBuilder
|
||||
_contentDialog.IsSecondaryButtonEnabled = builder.Command.CanExecute(builder.CommandParameter);
|
||||
builder.Command.CanExecuteChanged += (_, _) => _contentDialog.IsSecondaryButtonEnabled = builder.Command.CanExecute(builder.CommandParameter);
|
||||
}
|
||||
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
|
||||
@ -4,7 +4,7 @@
|
||||
<StackPanel>
|
||||
<Border Classes="card" Margin="20">
|
||||
<TextBlock>I'm in a panel yo!</TextBlock>
|
||||
</Border>
|
||||
</Border>
|
||||
<Border Classes="card" Margin="20">
|
||||
<StackPanel>
|
||||
<TextBlock>I'm in a panel yo!</TextBlock>
|
||||
|
||||
@ -27,22 +27,7 @@
|
||||
<Setter Property="Maximum" Value="{x:Static system:Double.MaxValue}"></Setter>
|
||||
<Setter Property="Minimum" Value="{x:Static system:Double.MinValue}"></Setter>
|
||||
</Style>
|
||||
<Style Selector="controls|NumberBox /template/ TextBox.NumberBoxTextBoxStyle /template/ TextBlock#PART_Prefix">
|
||||
<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">
|
||||
<Style Selector="controls|NumberBox /template/ TextBox#InputBox">
|
||||
<Setter Property="attachedProperties:TextBoxAssist.PrefixText" Value="{TemplateBinding attachedProperties:NumberBoxAssist.PrefixText}"></Setter>
|
||||
<Setter Property="attachedProperties:TextBoxAssist.SuffixText" Value="{TemplateBinding attachedProperties:NumberBoxAssist.SuffixText}"></Setter>
|
||||
</Style>
|
||||
|
||||
@ -5,7 +5,21 @@
|
||||
<Design.PreviewWith>
|
||||
<Border Padding="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 -->
|
||||
<TextBox Text="99999999"
|
||||
@ -46,12 +60,10 @@
|
||||
|
||||
<Border Margin="{TemplateBinding BorderThickness}">
|
||||
<Grid ColumnDefinitions="Auto,*,Auto">
|
||||
<ContentPresenter Grid.Column="0"
|
||||
Grid.ColumnSpan="1"
|
||||
Content="{TemplateBinding InnerLeftContent}" />
|
||||
<ContentPresenter Grid.Column="0" Content="{TemplateBinding InnerLeftContent}" />
|
||||
<Grid x:Name="PART_InnerGrid"
|
||||
Grid.Column="1"
|
||||
RowDefinitions="Auto,Auto"
|
||||
RowDefinitions="Auto,*"
|
||||
ColumnDefinitions="Auto,*,Auto"
|
||||
Cursor="IBeam"
|
||||
Margin="{TemplateBinding Padding}">
|
||||
|
||||
@ -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;
|
||||
}
|
||||
}
|
||||
@ -1,6 +1,4 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<Import Project="..\Artemis.props" />
|
||||
|
||||
<PropertyGroup>
|
||||
<OutputType>WinExe</OutputType>
|
||||
<TargetFramework>net7.0-windows10.0.17763.0</TargetFramework>
|
||||
@ -23,12 +21,13 @@
|
||||
</None>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Avalonia.Win32" Version="$(AvaloniaVersion)" />
|
||||
<PackageReference Include="Avalonia.Win32" Version="11.0.6" />
|
||||
<PackageReference Include="Microsoft.Toolkit.Uwp.Notifications" Version="7.1.3" />
|
||||
<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="RawInput.Sharp" Version="0.1.1" />
|
||||
<PackageReference Include="SkiaSharp.Vulkan.SharpVk" Version="$(SkiaSharpVersion)" />
|
||||
<PackageReference Include="RawInput.Sharp" Version="0.1.3" />
|
||||
<PackageReference Include="SkiaSharp.Vulkan.SharpVk" Version="2.88.7" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\Artemis.Core\Artemis.Core.csproj" />
|
||||
|
||||
@ -1,6 +1,4 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<Import Project="..\Artemis.props" />
|
||||
|
||||
<PropertyGroup>
|
||||
<OutputType>Library</OutputType>
|
||||
<TargetFramework>net7.0</TargetFramework>
|
||||
@ -19,18 +17,18 @@
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="AsyncImageLoader.Avalonia" Version="3.2.1" />
|
||||
<PackageReference Include="Avalonia.AvaloniaEdit" Version="11.0.1" />
|
||||
<PackageReference Include="Avalonia.Controls.PanAndZoom" Version="11.0.0" />
|
||||
<PackageReference Include="Avalonia.Desktop" Version="$(AvaloniaVersion)" />
|
||||
<PackageReference Include="Avalonia.AvaloniaEdit" Version="11.0.6" />
|
||||
<PackageReference Include="Avalonia.Controls.PanAndZoom" Version="11.0.0.2" />
|
||||
<PackageReference Include="Avalonia.Desktop" Version="11.0.6" />
|
||||
<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="Markdown.Avalonia.Tight" Version="11.0.2" />
|
||||
<PackageReference Include="Octopus.Octodiff" Version="2.0.468" />
|
||||
<PackageReference Include="PropertyChanged.SourceGenerator" Version="1.0.8">
|
||||
<PackageReference Include="Octopus.Octodiff" Version="2.0.544" />
|
||||
<PackageReference Include="PropertyChanged.SourceGenerator" Version="1.1.0">
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
</PackageReference>
|
||||
<PackageReference Include="Splat.DryIoc" Version="14.7.1" />
|
||||
<PackageReference Include="Splat.DryIoc" Version="14.8.12" />
|
||||
<PackageReference Include="TextMateSharp.Grammars" Version="1.0.56" />
|
||||
</ItemGroup>
|
||||
|
||||
|
||||
1
src/Artemis.UI/Assets/Animations/busy.json
Normal file
1
src/Artemis.UI/Assets/Animations/busy.json
Normal file
File diff suppressed because one or more lines are too long
@ -1,6 +1,7 @@
|
||||
using System.Reflection;
|
||||
using Artemis.UI.DryIoc.Factories;
|
||||
using Artemis.UI.DryIoc.InstanceProviders;
|
||||
using Artemis.UI.Screens.Device.Layout.LayoutProviders;
|
||||
using Artemis.UI.Screens.VisualScripting;
|
||||
using Artemis.UI.Services.Interfaces;
|
||||
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<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.Register<NodeScriptWindowViewModelBase, NodeScriptWindowViewModel>(Reuse.Singleton);
|
||||
|
||||
@ -1,13 +1,14 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Reactive;
|
||||
using Artemis.Core;
|
||||
using Artemis.Core.LayerBrushes;
|
||||
using Artemis.Core.LayerEffects;
|
||||
using Artemis.Core.ScriptingProviders;
|
||||
using Artemis.UI.Routing;
|
||||
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.Features;
|
||||
using Artemis.UI.Screens.Plugins.Prerequisites;
|
||||
@ -27,12 +28,8 @@ using Artemis.UI.Screens.Sidebar;
|
||||
using Artemis.UI.Screens.SurfaceEditor;
|
||||
using Artemis.UI.Screens.VisualScripting;
|
||||
using Artemis.UI.Screens.VisualScripting.Pins;
|
||||
using Artemis.UI.Shared;
|
||||
using Artemis.UI.Shared.Routing;
|
||||
using Artemis.WebClient.Updating;
|
||||
using DryIoc;
|
||||
using DynamicData;
|
||||
using Material.Icons;
|
||||
using ReactiveUI;
|
||||
|
||||
namespace Artemis.UI.DryIoc.Factories;
|
||||
@ -51,48 +48,49 @@ public interface IDeviceVmFactory : IVmFactory
|
||||
InputMappingsTabViewModel InputMappingsTabViewModel(ArtemisDevice device, ObservableCollection<ArtemisLed> selectedLeds);
|
||||
DeviceGeneralTabViewModel DeviceGeneralTabViewModel(ArtemisDevice device);
|
||||
}
|
||||
|
||||
public class DeviceFactory : IDeviceVmFactory
|
||||
{
|
||||
private readonly IContainer _container;
|
||||
|
||||
|
||||
public DeviceFactory(IContainer container)
|
||||
{
|
||||
_container = container;
|
||||
}
|
||||
|
||||
|
||||
public DevicePropertiesViewModel DevicePropertiesViewModel(ArtemisDevice device)
|
||||
{
|
||||
return _container.Resolve<DevicePropertiesViewModel>(new object[] { device });
|
||||
return _container.Resolve<DevicePropertiesViewModel>(new object[] {device});
|
||||
}
|
||||
|
||||
|
||||
public DeviceSettingsViewModel DeviceSettingsViewModel(ArtemisDevice device, DevicesTabViewModel devicesTabViewModel)
|
||||
{
|
||||
return _container.Resolve<DeviceSettingsViewModel>(new object[] { device, devicesTabViewModel });
|
||||
return _container.Resolve<DeviceSettingsViewModel>(new object[] {device, devicesTabViewModel});
|
||||
}
|
||||
|
||||
|
||||
public DeviceDetectInputViewModel DeviceDetectInputViewModel(ArtemisDevice device)
|
||||
{
|
||||
return _container.Resolve<DeviceDetectInputViewModel>(new object[] { device });
|
||||
return _container.Resolve<DeviceDetectInputViewModel>(new object[] {device});
|
||||
}
|
||||
|
||||
|
||||
public DeviceLayoutTabViewModel DeviceLayoutTabViewModel(ArtemisDevice device)
|
||||
{
|
||||
return _container.Resolve<DeviceLayoutTabViewModel>(new object[] { device });
|
||||
return _container.Resolve<DeviceLayoutTabViewModel>(new object[] {device});
|
||||
}
|
||||
|
||||
public DeviceLedsTabViewModel DeviceLedsTabViewModel(ArtemisDevice device, ObservableCollection<ArtemisLed> selectedLeds)
|
||||
{
|
||||
return _container.Resolve<DeviceLedsTabViewModel>(new object[] { device, selectedLeds });
|
||||
return _container.Resolve<DeviceLedsTabViewModel>(new object[] {device, selectedLeds});
|
||||
}
|
||||
|
||||
|
||||
public InputMappingsTabViewModel InputMappingsTabViewModel(ArtemisDevice device, ObservableCollection<ArtemisLed> selectedLeds)
|
||||
{
|
||||
return _container.Resolve<InputMappingsTabViewModel>(new object[] { device, selectedLeds });
|
||||
return _container.Resolve<InputMappingsTabViewModel>(new object[] {device, selectedLeds});
|
||||
}
|
||||
|
||||
public DeviceGeneralTabViewModel DeviceGeneralTabViewModel(ArtemisDevice device)
|
||||
{
|
||||
return _container.Resolve<DeviceGeneralTabViewModel>(new object[] { device });
|
||||
return _container.Resolve<DeviceGeneralTabViewModel>(new object[] {device});
|
||||
}
|
||||
}
|
||||
|
||||
@ -102,28 +100,29 @@ public interface ISettingsVmFactory : IVmFactory
|
||||
PluginViewModel PluginViewModel(Plugin plugin, ReactiveCommand<Unit, Unit>? reload);
|
||||
PluginFeatureViewModel PluginFeatureViewModel(PluginFeatureInfo pluginFeatureInfo, bool showShield);
|
||||
}
|
||||
|
||||
public class SettingsVmFactory : ISettingsVmFactory
|
||||
{
|
||||
private readonly IContainer _container;
|
||||
|
||||
|
||||
public SettingsVmFactory(IContainer container)
|
||||
{
|
||||
_container = container;
|
||||
}
|
||||
|
||||
|
||||
public PluginSettingsViewModel PluginSettingsViewModel(Plugin plugin)
|
||||
{
|
||||
return _container.Resolve<PluginSettingsViewModel>(new object[] { plugin });
|
||||
return _container.Resolve<PluginSettingsViewModel>(new object[] {plugin});
|
||||
}
|
||||
|
||||
|
||||
public PluginViewModel PluginViewModel(Plugin plugin, ReactiveCommand<Unit, Unit>? reload)
|
||||
{
|
||||
return _container.Resolve<PluginViewModel>(new object?[] { plugin, reload });
|
||||
return _container.Resolve<PluginViewModel>(new object?[] {plugin, reload});
|
||||
}
|
||||
|
||||
|
||||
public PluginFeatureViewModel PluginFeatureViewModel(PluginFeatureInfo pluginFeatureInfo, bool showShield)
|
||||
{
|
||||
return _container.Resolve<PluginFeatureViewModel>(new object[] { pluginFeatureInfo, showShield });
|
||||
return _container.Resolve<PluginFeatureViewModel>(new object[] {pluginFeatureInfo, showShield});
|
||||
}
|
||||
}
|
||||
|
||||
@ -132,23 +131,24 @@ public interface ISidebarVmFactory : IVmFactory
|
||||
SidebarCategoryViewModel SidebarCategoryViewModel(ProfileCategory profileCategory);
|
||||
SidebarProfileConfigurationViewModel SidebarProfileConfigurationViewModel(ProfileConfiguration profileConfiguration);
|
||||
}
|
||||
|
||||
public class SidebarVmFactory : ISidebarVmFactory
|
||||
{
|
||||
private readonly IContainer _container;
|
||||
|
||||
|
||||
public SidebarVmFactory(IContainer container)
|
||||
{
|
||||
_container = container;
|
||||
}
|
||||
|
||||
|
||||
public SidebarCategoryViewModel SidebarCategoryViewModel(ProfileCategory profileCategory)
|
||||
{
|
||||
return _container.Resolve<SidebarCategoryViewModel>(new object[] { profileCategory });
|
||||
return _container.Resolve<SidebarCategoryViewModel>(new object[] {profileCategory});
|
||||
}
|
||||
|
||||
|
||||
public SidebarProfileConfigurationViewModel SidebarProfileConfigurationViewModel(ProfileConfiguration profileConfiguration)
|
||||
{
|
||||
return _container.Resolve<SidebarProfileConfigurationViewModel>(new object[] { profileConfiguration });
|
||||
return _container.Resolve<SidebarProfileConfigurationViewModel>(new object[] {profileConfiguration});
|
||||
}
|
||||
}
|
||||
|
||||
@ -157,6 +157,7 @@ public interface ISurfaceVmFactory : IVmFactory
|
||||
SurfaceDeviceViewModel SurfaceDeviceViewModel(ArtemisDevice device, SurfaceEditorViewModel surfaceEditorViewModel);
|
||||
ListDeviceViewModel ListDeviceViewModel(ArtemisDevice device, SurfaceEditorViewModel surfaceEditorViewModel);
|
||||
}
|
||||
|
||||
public class SurfaceVmFactory : ISurfaceVmFactory
|
||||
{
|
||||
private readonly IContainer _container;
|
||||
@ -168,12 +169,12 @@ public class SurfaceVmFactory : ISurfaceVmFactory
|
||||
|
||||
public SurfaceDeviceViewModel SurfaceDeviceViewModel(ArtemisDevice device, SurfaceEditorViewModel surfaceEditorViewModel)
|
||||
{
|
||||
return _container.Resolve<SurfaceDeviceViewModel>(new object[] { device, surfaceEditorViewModel });
|
||||
return _container.Resolve<SurfaceDeviceViewModel>(new object[] {device, surfaceEditorViewModel});
|
||||
}
|
||||
|
||||
public ListDeviceViewModel ListDeviceViewModel(ArtemisDevice device, SurfaceEditorViewModel surfaceEditorViewModel)
|
||||
{
|
||||
return _container.Resolve<ListDeviceViewModel>(new object[] { device, surfaceEditorViewModel });
|
||||
return _container.Resolve<ListDeviceViewModel>(new object[] {device, surfaceEditorViewModel});
|
||||
}
|
||||
}
|
||||
|
||||
@ -181,6 +182,7 @@ public interface IPrerequisitesVmFactory : IVmFactory
|
||||
{
|
||||
PluginPrerequisiteViewModel PluginPrerequisiteViewModel(PluginPrerequisite pluginPrerequisite, bool uninstall);
|
||||
}
|
||||
|
||||
public class PrerequisitesVmFactory : IPrerequisitesVmFactory
|
||||
{
|
||||
private readonly IContainer _container;
|
||||
@ -192,7 +194,7 @@ public class PrerequisitesVmFactory : IPrerequisitesVmFactory
|
||||
|
||||
public PluginPrerequisiteViewModel PluginPrerequisiteViewModel(PluginPrerequisite pluginPrerequisite, bool uninstall)
|
||||
{
|
||||
return _container.Resolve<PluginPrerequisiteViewModel>(new object[] { pluginPrerequisite, uninstall });
|
||||
return _container.Resolve<PluginPrerequisiteViewModel>(new object[] {pluginPrerequisite, uninstall});
|
||||
}
|
||||
}
|
||||
|
||||
@ -204,6 +206,7 @@ public interface IProfileEditorVmFactory : IVmFactory
|
||||
LayerShapeVisualizerViewModel LayerShapeVisualizerViewModel(Layer layer);
|
||||
LayerVisualizerViewModel LayerVisualizerViewModel(Layer layer);
|
||||
}
|
||||
|
||||
public class ProfileEditorVmFactory : IProfileEditorVmFactory
|
||||
{
|
||||
private readonly IContainer _container;
|
||||
@ -215,27 +218,27 @@ public class ProfileEditorVmFactory : IProfileEditorVmFactory
|
||||
|
||||
public FolderTreeItemViewModel FolderTreeItemViewModel(TreeItemViewModel? parent, Folder folder)
|
||||
{
|
||||
return _container.Resolve<FolderTreeItemViewModel>(new object?[] { parent, folder });
|
||||
return _container.Resolve<FolderTreeItemViewModel>(new object?[] {parent, folder});
|
||||
}
|
||||
|
||||
public LayerShapeVisualizerViewModel LayerShapeVisualizerViewModel(Layer layer)
|
||||
{
|
||||
return _container.Resolve<LayerShapeVisualizerViewModel>(new object[] { layer });
|
||||
return _container.Resolve<LayerShapeVisualizerViewModel>(new object[] {layer});
|
||||
}
|
||||
|
||||
public LayerTreeItemViewModel LayerTreeItemViewModel(TreeItemViewModel? parent, Layer layer)
|
||||
{
|
||||
return _container.Resolve<LayerTreeItemViewModel>(new object?[] { parent, layer });
|
||||
return _container.Resolve<LayerTreeItemViewModel>(new object?[] {parent, layer});
|
||||
}
|
||||
|
||||
public LayerVisualizerViewModel LayerVisualizerViewModel(Layer layer)
|
||||
{
|
||||
return _container.Resolve<LayerVisualizerViewModel>(new object[] { layer });
|
||||
return _container.Resolve<LayerVisualizerViewModel>(new object[] {layer});
|
||||
}
|
||||
|
||||
public ProfileEditorViewModel ProfileEditorViewModel(IScreen hostScreen)
|
||||
{
|
||||
return _container.Resolve<ProfileEditorViewModel>(new object[] { hostScreen });
|
||||
return _container.Resolve<ProfileEditorViewModel>(new object[] {hostScreen});
|
||||
}
|
||||
}
|
||||
|
||||
@ -251,6 +254,7 @@ public interface ILayerPropertyVmFactory : IVmFactory
|
||||
TimelineViewModel TimelineViewModel(ObservableCollection<PropertyGroupViewModel> propertyGroupViewModels);
|
||||
TimelineGroupViewModel TimelineGroupViewModel(PropertyGroupViewModel propertyGroupViewModel);
|
||||
}
|
||||
|
||||
public class LayerPropertyVmFactory : ILayerPropertyVmFactory
|
||||
{
|
||||
private readonly IContainer _container;
|
||||
@ -262,37 +266,37 @@ public class LayerPropertyVmFactory : ILayerPropertyVmFactory
|
||||
|
||||
public PropertyViewModel PropertyViewModel(ILayerProperty layerProperty)
|
||||
{
|
||||
return _container.Resolve<PropertyViewModel>(new object[] { layerProperty });
|
||||
return _container.Resolve<PropertyViewModel>(new object[] {layerProperty});
|
||||
}
|
||||
|
||||
public PropertyGroupViewModel PropertyGroupViewModel(LayerPropertyGroup layerPropertyGroup)
|
||||
{
|
||||
return _container.Resolve<PropertyGroupViewModel>(new object[] { layerPropertyGroup });
|
||||
return _container.Resolve<PropertyGroupViewModel>(new object[] {layerPropertyGroup});
|
||||
}
|
||||
|
||||
public PropertyGroupViewModel PropertyGroupViewModel(LayerPropertyGroup layerPropertyGroup, BaseLayerBrush layerBrush)
|
||||
{
|
||||
return _container.Resolve<PropertyGroupViewModel>(new object[] { layerPropertyGroup, layerBrush });
|
||||
return _container.Resolve<PropertyGroupViewModel>(new object[] {layerPropertyGroup, layerBrush});
|
||||
}
|
||||
|
||||
public PropertyGroupViewModel PropertyGroupViewModel(LayerPropertyGroup layerPropertyGroup, BaseLayerEffect layerEffect)
|
||||
{
|
||||
return _container.Resolve<PropertyGroupViewModel>(new object[] { layerPropertyGroup, layerEffect });
|
||||
return _container.Resolve<PropertyGroupViewModel>(new object[] {layerPropertyGroup, layerEffect});
|
||||
}
|
||||
|
||||
public TreeGroupViewModel TreeGroupViewModel(PropertyGroupViewModel propertyGroupViewModel)
|
||||
{
|
||||
return _container.Resolve<TreeGroupViewModel>(new object[] { propertyGroupViewModel });
|
||||
return _container.Resolve<TreeGroupViewModel>(new object[] {propertyGroupViewModel});
|
||||
}
|
||||
|
||||
public TimelineViewModel TimelineViewModel(ObservableCollection<PropertyGroupViewModel> propertyGroupViewModels)
|
||||
{
|
||||
return _container.Resolve<TimelineViewModel>(new object[] { propertyGroupViewModels });
|
||||
return _container.Resolve<TimelineViewModel>(new object[] {propertyGroupViewModels});
|
||||
}
|
||||
|
||||
public TimelineGroupViewModel TimelineGroupViewModel(PropertyGroupViewModel propertyGroupViewModel)
|
||||
{
|
||||
return _container.Resolve<TimelineGroupViewModel>(new object[] { propertyGroupViewModel });
|
||||
return _container.Resolve<TimelineGroupViewModel>(new object[] {propertyGroupViewModel});
|
||||
}
|
||||
}
|
||||
|
||||
@ -300,6 +304,7 @@ public interface IDataBindingVmFactory : IVmFactory
|
||||
{
|
||||
DataBindingViewModel DataBindingViewModel();
|
||||
}
|
||||
|
||||
public class DataBindingVmFactory : IDataBindingVmFactory
|
||||
{
|
||||
private readonly IContainer _container;
|
||||
@ -333,6 +338,7 @@ public interface INodeVmFactory : IVmFactory
|
||||
InputPinCollectionViewModel InputPinCollectionViewModel(IPinCollection inputPinCollection, NodeScriptViewModel nodeScriptViewModel);
|
||||
OutputPinCollectionViewModel OutputPinCollectionViewModel(IPinCollection outputPinCollection, NodeScriptViewModel nodeScriptViewModel);
|
||||
}
|
||||
|
||||
public class NodeVmFactory : INodeVmFactory
|
||||
{
|
||||
private readonly IContainer _container;
|
||||
@ -344,47 +350,47 @@ public class NodeVmFactory : INodeVmFactory
|
||||
|
||||
public NodeScriptViewModel NodeScriptViewModel(NodeScript nodeScript, bool isPreview)
|
||||
{
|
||||
return _container.Resolve<NodeScriptViewModel>(new object[] { nodeScript, isPreview });
|
||||
return _container.Resolve<NodeScriptViewModel>(new object[] {nodeScript, isPreview});
|
||||
}
|
||||
|
||||
public NodePickerViewModel NodePickerViewModel(NodeScript nodeScript)
|
||||
{
|
||||
return _container.Resolve<NodePickerViewModel>(new object[] { nodeScript });
|
||||
return _container.Resolve<NodePickerViewModel>(new object[] {nodeScript});
|
||||
}
|
||||
|
||||
public NodeViewModel NodeViewModel(NodeScriptViewModel nodeScriptViewModel, INode node)
|
||||
{
|
||||
return _container.Resolve<NodeViewModel>(new object[] { nodeScriptViewModel, node });
|
||||
return _container.Resolve<NodeViewModel>(new object[] {nodeScriptViewModel, node});
|
||||
}
|
||||
|
||||
public CableViewModel CableViewModel(NodeScriptViewModel nodeScriptViewModel, IPin from, IPin to)
|
||||
{
|
||||
return _container.Resolve<CableViewModel>(new object[] { nodeScriptViewModel, from, to });
|
||||
return _container.Resolve<CableViewModel>(new object[] {nodeScriptViewModel, from, to});
|
||||
}
|
||||
|
||||
public DragCableViewModel DragCableViewModel(PinViewModel pinViewModel)
|
||||
{
|
||||
return _container.Resolve<DragCableViewModel>(new object[] { pinViewModel });
|
||||
return _container.Resolve<DragCableViewModel>(new object[] {pinViewModel});
|
||||
}
|
||||
|
||||
public InputPinViewModel InputPinViewModel(IPin inputPin, NodeScriptViewModel nodeScriptViewModel)
|
||||
{
|
||||
return _container.Resolve<InputPinViewModel>(new object[] { inputPin, nodeScriptViewModel });
|
||||
return _container.Resolve<InputPinViewModel>(new object[] {inputPin, nodeScriptViewModel});
|
||||
}
|
||||
|
||||
public OutputPinViewModel OutputPinViewModel(IPin outputPin, NodeScriptViewModel nodeScriptViewModel)
|
||||
{
|
||||
return _container.Resolve<OutputPinViewModel>(new object[] { outputPin, nodeScriptViewModel });
|
||||
return _container.Resolve<OutputPinViewModel>(new object[] {outputPin, nodeScriptViewModel});
|
||||
}
|
||||
|
||||
public InputPinCollectionViewModel InputPinCollectionViewModel(IPinCollection inputPinCollection, NodeScriptViewModel nodeScriptViewModel)
|
||||
{
|
||||
return _container.Resolve<InputPinCollectionViewModel>(new object[] { inputPinCollection, nodeScriptViewModel });
|
||||
return _container.Resolve<InputPinCollectionViewModel>(new object[] {inputPinCollection, nodeScriptViewModel});
|
||||
}
|
||||
|
||||
public OutputPinCollectionViewModel OutputPinCollectionViewModel(IPinCollection outputPinCollection, NodeScriptViewModel nodeScriptViewModel)
|
||||
{
|
||||
return _container.Resolve<OutputPinCollectionViewModel>(new object[] { outputPinCollection, nodeScriptViewModel });
|
||||
return _container.Resolve<OutputPinCollectionViewModel>(new object[] {outputPinCollection, nodeScriptViewModel});
|
||||
}
|
||||
}
|
||||
|
||||
@ -395,6 +401,7 @@ public interface IConditionVmFactory : IVmFactory
|
||||
StaticConditionViewModel StaticConditionViewModel(StaticCondition staticCondition);
|
||||
EventConditionViewModel EventConditionViewModel(EventCondition eventCondition);
|
||||
}
|
||||
|
||||
public class ConditionVmFactory : IConditionVmFactory
|
||||
{
|
||||
private readonly IContainer _container;
|
||||
@ -406,22 +413,22 @@ public class ConditionVmFactory : IConditionVmFactory
|
||||
|
||||
public AlwaysOnConditionViewModel AlwaysOnConditionViewModel(AlwaysOnCondition alwaysOnCondition)
|
||||
{
|
||||
return _container.Resolve<AlwaysOnConditionViewModel>(new object[] { alwaysOnCondition });
|
||||
return _container.Resolve<AlwaysOnConditionViewModel>(new object[] {alwaysOnCondition});
|
||||
}
|
||||
|
||||
public PlayOnceConditionViewModel PlayOnceConditionViewModel(PlayOnceCondition playOnceCondition)
|
||||
{
|
||||
return _container.Resolve<PlayOnceConditionViewModel>(new object[] { playOnceCondition });
|
||||
return _container.Resolve<PlayOnceConditionViewModel>(new object[] {playOnceCondition});
|
||||
}
|
||||
|
||||
public StaticConditionViewModel StaticConditionViewModel(StaticCondition staticCondition)
|
||||
{
|
||||
return _container.Resolve<StaticConditionViewModel>(new object[] { staticCondition });
|
||||
return _container.Resolve<StaticConditionViewModel>(new object[] {staticCondition});
|
||||
}
|
||||
|
||||
public EventConditionViewModel EventConditionViewModel(EventCondition eventCondition)
|
||||
{
|
||||
return _container.Resolve<EventConditionViewModel>(new object[] { eventCondition });
|
||||
return _container.Resolve<EventConditionViewModel>(new object[] {eventCondition});
|
||||
}
|
||||
}
|
||||
|
||||
@ -432,6 +439,7 @@ public interface ILayerHintVmFactory : IVmFactory
|
||||
KeyboardSectionAdaptionHintViewModel KeyboardSectionAdaptionHintViewModel(Layer layer, KeyboardSectionAdaptionHint adaptionHint);
|
||||
SingleLedAdaptionHintViewModel SingleLedAdaptionHintViewModel(Layer layer, SingleLedAdaptionHint adaptionHint);
|
||||
}
|
||||
|
||||
public class LayerHintVmFactory : ILayerHintVmFactory
|
||||
{
|
||||
private readonly IContainer _container;
|
||||
@ -443,22 +451,22 @@ public class LayerHintVmFactory : ILayerHintVmFactory
|
||||
|
||||
public CategoryAdaptionHintViewModel CategoryAdaptionHintViewModel(Layer layer, CategoryAdaptionHint adaptionHint)
|
||||
{
|
||||
return _container.Resolve<CategoryAdaptionHintViewModel>(new object[] { layer, adaptionHint });
|
||||
return _container.Resolve<CategoryAdaptionHintViewModel>(new object[] {layer, adaptionHint});
|
||||
}
|
||||
|
||||
public DeviceAdaptionHintViewModel DeviceAdaptionHintViewModel(Layer layer, DeviceAdaptionHint adaptionHint)
|
||||
{
|
||||
return _container.Resolve<DeviceAdaptionHintViewModel>(new object[] { layer, adaptionHint });
|
||||
return _container.Resolve<DeviceAdaptionHintViewModel>(new object[] {layer, adaptionHint});
|
||||
}
|
||||
|
||||
public KeyboardSectionAdaptionHintViewModel KeyboardSectionAdaptionHintViewModel(Layer layer, KeyboardSectionAdaptionHint adaptionHint)
|
||||
{
|
||||
return _container.Resolve<KeyboardSectionAdaptionHintViewModel>(new object[] { layer, adaptionHint });
|
||||
return _container.Resolve<KeyboardSectionAdaptionHintViewModel>(new object[] {layer, adaptionHint});
|
||||
}
|
||||
|
||||
|
||||
public SingleLedAdaptionHintViewModel SingleLedAdaptionHintViewModel(Layer layer, SingleLedAdaptionHint adaptionHint)
|
||||
{
|
||||
return _container.Resolve<SingleLedAdaptionHintViewModel>(new object[] { layer, adaptionHint });
|
||||
return _container.Resolve<SingleLedAdaptionHintViewModel>(new object[] {layer, adaptionHint});
|
||||
}
|
||||
}
|
||||
|
||||
@ -467,6 +475,7 @@ public interface IScriptVmFactory : IVmFactory
|
||||
ScriptConfigurationViewModel ScriptConfigurationViewModel(ScriptConfiguration scriptConfiguration);
|
||||
ScriptConfigurationViewModel ScriptConfigurationViewModel(Profile profile, ScriptConfiguration scriptConfiguration);
|
||||
}
|
||||
|
||||
public class ScriptVmFactory : IScriptVmFactory
|
||||
{
|
||||
private readonly IContainer _container;
|
||||
@ -478,12 +487,12 @@ public class ScriptVmFactory : IScriptVmFactory
|
||||
|
||||
public ScriptConfigurationViewModel ScriptConfigurationViewModel(ScriptConfiguration scriptConfiguration)
|
||||
{
|
||||
return _container.Resolve<ScriptConfigurationViewModel>(new object[] { scriptConfiguration });
|
||||
return _container.Resolve<ScriptConfigurationViewModel>(new object[] {scriptConfiguration});
|
||||
}
|
||||
|
||||
public ScriptConfigurationViewModel ScriptConfigurationViewModel(Profile profile, ScriptConfiguration scriptConfiguration)
|
||||
{
|
||||
return _container.Resolve<ScriptConfigurationViewModel>(new object[] { profile, scriptConfiguration });
|
||||
return _container.Resolve<ScriptConfigurationViewModel>(new object[] {profile, scriptConfiguration});
|
||||
}
|
||||
}
|
||||
|
||||
@ -491,6 +500,7 @@ public interface IReleaseVmFactory : IVmFactory
|
||||
{
|
||||
ReleaseViewModel ReleaseListViewModel(IGetReleases_PublishedReleases_Nodes release);
|
||||
}
|
||||
|
||||
public class ReleaseVmFactory : IReleaseVmFactory
|
||||
{
|
||||
private readonly IContainer _container;
|
||||
@ -499,9 +509,9 @@ public class ReleaseVmFactory : IReleaseVmFactory
|
||||
{
|
||||
_container = container;
|
||||
}
|
||||
|
||||
|
||||
public ReleaseViewModel ReleaseListViewModel(IGetReleases_PublishedReleases_Nodes release)
|
||||
{
|
||||
return _container.Resolve<ReleaseViewModel>(new object[] { release });
|
||||
return _container.Resolve<ReleaseViewModel>(new object[] {release});
|
||||
}
|
||||
}
|
||||
@ -6,6 +6,7 @@ using Artemis.UI.Screens.Debugger.Logs;
|
||||
using Artemis.UI.Screens.Debugger.Performance;
|
||||
using Artemis.UI.Screens.Debugger.Render;
|
||||
using Artemis.UI.Screens.Debugger.Routing;
|
||||
using Artemis.UI.Screens.Debugger.Workshop;
|
||||
using Artemis.UI.Services.Interfaces;
|
||||
using Artemis.UI.Shared;
|
||||
using PropertyChanged.SourceGenerator;
|
||||
@ -17,9 +18,9 @@ public partial class DebugViewModel : ActivatableViewModelBase, IScreen
|
||||
{
|
||||
[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;
|
||||
|
||||
this.WhenActivated(d => Disposable.Create(debugService.ClearDebugger).DisposeWith(d));
|
||||
|
||||
@ -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>
|
||||
@ -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();
|
||||
}
|
||||
}
|
||||
@ -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);
|
||||
});
|
||||
}
|
||||
}
|
||||
@ -5,9 +5,10 @@
|
||||
xmlns:controls="clr-namespace:FluentAvalonia.UI.Controls;assembly=FluentAvalonia"
|
||||
xmlns:converters="clr-namespace:Artemis.UI.Shared.Converters;assembly=Artemis.UI.Shared"
|
||||
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"
|
||||
x:Class="Artemis.UI.Screens.Device.DeviceGeneralTabView"
|
||||
x:DataType="device:DeviceGeneralTabViewModel">
|
||||
x:Class="Artemis.UI.Screens.Device.General.DeviceGeneralTabView"
|
||||
x:DataType="general:DeviceGeneralTabViewModel">
|
||||
<UserControl.Resources>
|
||||
<converters:SKColorToColorConverter x:Key="SKColorToColorConverter" />
|
||||
</UserControl.Resources>
|
||||
@ -1,7 +1,6 @@
|
||||
using Avalonia.Controls;
|
||||
using Avalonia.ReactiveUI;
|
||||
|
||||
namespace Artemis.UI.Screens.Device;
|
||||
namespace Artemis.UI.Screens.Device.General;
|
||||
public partial class DeviceGeneralTabView : ReactiveUserControl<DeviceGeneralTabViewModel>
|
||||
{
|
||||
public DeviceGeneralTabView()
|
||||
@ -4,6 +4,7 @@ using System.Reactive.Disposables;
|
||||
using System.Threading.Tasks;
|
||||
using Artemis.Core;
|
||||
using Artemis.Core.Services;
|
||||
using Artemis.UI.Screens.Device.Layout;
|
||||
using Artemis.UI.Shared;
|
||||
using Artemis.UI.Shared.Services;
|
||||
using PropertyChanged.SourceGenerator;
|
||||
@ -11,7 +12,7 @@ using ReactiveUI;
|
||||
using RGB.NET.Core;
|
||||
using SkiaSharp;
|
||||
|
||||
namespace Artemis.UI.Screens.Device;
|
||||
namespace Artemis.UI.Screens.Device.General;
|
||||
|
||||
public partial class DeviceGeneralTabViewModel : ActivatableViewModelBase
|
||||
{
|
||||
@ -56,7 +57,7 @@ public partial class DeviceGeneralTabViewModel : ActivatableViewModelBase
|
||||
_initialGreenScale = Device.GreenScale;
|
||||
_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 =>
|
||||
{
|
||||
@ -127,12 +128,12 @@ public partial class DeviceGeneralTabViewModel : ActivatableViewModelBase
|
||||
return;
|
||||
if (!Device.DeviceProvider.CanDetectPhysicalLayout && !await DevicePhysicalLayoutDialogViewModel.SelectPhysicalLayout(_windowService, Device))
|
||||
return;
|
||||
if (!Device.DeviceProvider.CanDetectLogicalLayout && !await DeviceLogicalLayoutDialogViewModel.SelectLogicalLayout(_windowService, Device))
|
||||
if (!Device.DeviceProvider.CanDetectLogicalLayout && !await Layout.DeviceLogicalLayoutDialogViewModel.SelectLogicalLayout(_windowService, Device))
|
||||
return;
|
||||
|
||||
await Task.Delay(400);
|
||||
_deviceService.SaveDevice(Device);
|
||||
_deviceService.ApplyDeviceLayout(Device, Device.GetBestDeviceLayout());
|
||||
_deviceService.LoadDeviceLayout(Device);
|
||||
}
|
||||
|
||||
private void Apply()
|
||||
@ -4,9 +4,10 @@
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
xmlns:device="clr-namespace:Artemis.UI.Screens.Device"
|
||||
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"
|
||||
x:Class="Artemis.UI.Screens.Device.InputMappingsTabView"
|
||||
x:DataType="device:InputMappingsTabViewModel">
|
||||
x:Class="Artemis.UI.Screens.Device.InputMappings.InputMappingsTabView"
|
||||
x:DataType="inputMappings:InputMappingsTabViewModel">
|
||||
<ScrollViewer HorizontalScrollBarVisibility="Disabled" VerticalScrollBarVisibility="Auto">
|
||||
<Grid RowDefinitions="Auto,Auto,*">
|
||||
<StackPanel Grid.Row="0">
|
||||
@ -1,7 +1,6 @@
|
||||
using Avalonia.Markup.Xaml;
|
||||
using Avalonia.ReactiveUI;
|
||||
|
||||
namespace Artemis.UI.Screens.Device;
|
||||
namespace Artemis.UI.Screens.Device.InputMappings;
|
||||
|
||||
public partial class InputMappingsTabView : ReactiveUserControl<InputMappingsTabViewModel>
|
||||
{
|
||||
@ -6,13 +6,12 @@ using Artemis.Core;
|
||||
using Artemis.Core.Services;
|
||||
using Artemis.UI.Exceptions;
|
||||
using Artemis.UI.Shared;
|
||||
using HidSharp.Reports.Units;
|
||||
using PropertyChanged.SourceGenerator;
|
||||
using ReactiveUI;
|
||||
using RGB.NET.Core;
|
||||
using Unit = System.Reactive.Unit;
|
||||
|
||||
namespace Artemis.UI.Screens.Device;
|
||||
namespace Artemis.UI.Screens.Device.InputMappings;
|
||||
|
||||
public partial class InputMappingsTabViewModel : ActivatableViewModelBase
|
||||
{
|
||||
@ -4,10 +4,11 @@
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
xmlns:controls="clr-namespace:FluentAvalonia.UI.Controls;assembly=FluentAvalonia"
|
||||
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"
|
||||
x:Class="Artemis.UI.Screens.Device.DeviceLayoutTabView"
|
||||
x:DataType="device:DeviceLayoutTabViewModel">
|
||||
x:Class="Artemis.UI.Screens.Device.Layout.DeviceLayoutTabView"
|
||||
x:DataType="layout:DeviceLayoutTabViewModel">
|
||||
<ScrollViewer HorizontalScrollBarVisibility="Disabled" VerticalScrollBarVisibility="Auto">
|
||||
<Border Classes="card" Margin="5">
|
||||
<Grid RowDefinitions="*,Auto">
|
||||
@ -28,6 +29,7 @@
|
||||
</Button>
|
||||
</StackPanel>
|
||||
</Grid>
|
||||
|
||||
<Border Classes="card-separator" />
|
||||
<Grid RowDefinitions="*,*" ColumnDefinitions="*,Auto">
|
||||
<StackPanel Grid.Row="1" Grid.Column="0">
|
||||
@ -45,28 +47,43 @@
|
||||
</Button>
|
||||
</StackPanel>
|
||||
</Grid>
|
||||
|
||||
<Border Classes="card-separator" />
|
||||
<Grid RowDefinitions="*,*" ColumnDefinitions="*,Auto">
|
||||
<Grid RowDefinitions="*,*,*" ColumnDefinitions="*,Auto">
|
||||
<StackPanel Grid.Row="1" Grid.Column="0">
|
||||
<TextBlock Text="Disable default layout" />
|
||||
<TextBlock Classes="subtitle" FontSize="12" Text="With this checked, Artemis will not load a layout for this device unless you specifically provide one." />
|
||||
<TextBlock Text="Layout provider" />
|
||||
<TextBlock Classes="subtitle" FontSize="12" Text="Choose between different ways to load a layout for this device." />
|
||||
</StackPanel>
|
||||
<StackPanel Grid.Row="1" Grid.Column="1" VerticalAlignment="Center">
|
||||
<CheckBox HorizontalAlignment="Right" Margin="0,0,-10,0" IsChecked="{CompiledBinding Device.DisableDefaultLayout}"/>
|
||||
</StackPanel>
|
||||
</Grid>
|
||||
<Border Classes="card-separator" />
|
||||
<Grid RowDefinitions="*,*" ColumnDefinitions="*,Auto">
|
||||
<StackPanel Grid.Row="1" Grid.Column="0">
|
||||
<TextBlock Text="Custom layout path" />
|
||||
<TextBlock Classes="subtitle" FontSize="12" Text="{CompiledBinding CustomLayoutPath, TargetNullValue=None}"/>
|
||||
</StackPanel>
|
||||
<StackPanel Grid.Row="1" Grid.Column="1" VerticalAlignment="Center" Orientation="Horizontal">
|
||||
<Button Content="Clear" Command="{CompiledBinding ClearCustomLayout}" IsEnabled="{CompiledBinding HasCustomLayout}" />
|
||||
<!-- 5 pixels of margin between the buttons -->
|
||||
<Button Margin="5,0,0,0" Content="Browse" Command="{CompiledBinding BrowseCustomLayout}" />
|
||||
<StackPanel.Styles>
|
||||
<Style Selector="ComboBox.layoutProvider /template/ ContentControl#ContentPresenter">
|
||||
<Setter Property="ContentTemplate">
|
||||
<Setter.Value>
|
||||
<DataTemplate x:DataType="layoutProviders:ILayoutProviderViewModel">
|
||||
<TextBlock Text="{CompiledBinding Name}" TextWrapping="Wrap" MaxWidth="350" />
|
||||
</DataTemplate>
|
||||
</Setter.Value>
|
||||
</Setter>
|
||||
</Style>
|
||||
</StackPanel.Styles>
|
||||
<ComboBox Classes="layoutProvider"
|
||||
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>
|
||||
</Grid>
|
||||
|
||||
<ContentControl Content="{CompiledBinding SelectedLayoutProvider}" ClipToBounds="False" />
|
||||
|
||||
<Border Classes="card-separator" />
|
||||
<Grid RowDefinitions="*,*" ColumnDefinitions="*,Auto">
|
||||
<StackPanel Grid.Row="1" Grid.Column="0">
|
||||
@ -2,7 +2,7 @@
|
||||
using Avalonia.Interactivity;
|
||||
using Avalonia.ReactiveUI;
|
||||
|
||||
namespace Artemis.UI.Screens.Device;
|
||||
namespace Artemis.UI.Screens.Device.Layout;
|
||||
|
||||
public partial class DeviceLayoutTabView : ReactiveUserControl<DeviceLayoutTabViewModel>
|
||||
{
|
||||
@ -1,81 +1,60 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Reactive.Disposables;
|
||||
using System.Threading.Tasks;
|
||||
using System.Xml.Serialization;
|
||||
using Artemis.Core;
|
||||
using Artemis.Core.Services;
|
||||
using Artemis.UI.Screens.Device.Layout.LayoutProviders;
|
||||
using Artemis.UI.Shared;
|
||||
using Artemis.UI.Shared.Services;
|
||||
using Artemis.UI.Shared.Services.Builders;
|
||||
using Avalonia.Controls;
|
||||
using PropertyChanged.SourceGenerator;
|
||||
using ReactiveUI;
|
||||
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 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;
|
||||
_notificationService = notificationService;
|
||||
_deviceService = deviceService;
|
||||
|
||||
Device = device;
|
||||
DisplayName = "Layout";
|
||||
DefaultLayoutPath = Device.DeviceProvider.LoadLayout(Device).FilePath;
|
||||
|
||||
this.WhenActivated(d =>
|
||||
LayoutProviders = new ObservableCollection<ILayoutProviderViewModel>(layoutProviders);
|
||||
foreach (ILayoutProviderViewModel layoutProviderViewModel in layoutProviders)
|
||||
{
|
||||
Device.PropertyChanged += DeviceOnPropertyChanged;
|
||||
Disposable.Create(() => Device.PropertyChanged -= DeviceOnPropertyChanged).DisposeWith(d);
|
||||
layoutProviderViewModel.Device = Device;
|
||||
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 ObservableCollection<ILayoutProviderViewModel> LayoutProviders { get; set; }
|
||||
|
||||
public string DefaultLayoutPath { get; }
|
||||
|
||||
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()
|
||||
{
|
||||
string fileName = Device.DeviceProvider.GetDeviceLayoutName(Device);
|
||||
@ -106,13 +85,13 @@ public class DeviceLayoutTabViewModel : ActivatableViewModelBase
|
||||
}
|
||||
else
|
||||
{
|
||||
List<LedLayout> ledLayouts = Device.Leds.Select(x => new LedLayout()
|
||||
List<LedLayout> ledLayouts = Device.Leds.Select(x => new LedLayout
|
||||
{
|
||||
Id = x.RgbLed.Id.ToString(),
|
||||
DescriptiveX = x.Rectangle.Left.ToString(),
|
||||
DescriptiveY = x.Rectangle.Top.ToString(),
|
||||
DescriptiveWidth = $"{x.Rectangle.Width}mm",
|
||||
DescriptiveHeight = $"{x.Rectangle.Height}mm",
|
||||
DescriptiveHeight = $"{x.Rectangle.Height}mm"
|
||||
}).ToList();
|
||||
|
||||
DeviceLayout emptyLayout = new()
|
||||
@ -123,7 +102,7 @@ public class DeviceLayoutTabViewModel : ActivatableViewModelBase
|
||||
Model = Device.RgbDevice.DeviceInfo.Model,
|
||||
Width = Device.Rectangle.Width,
|
||||
Height = Device.Rectangle.Height,
|
||||
InternalLeds = ledLayouts,
|
||||
InternalLeds = ledLayouts
|
||||
};
|
||||
|
||||
XmlSerializer serializer = new(typeof(DeviceLayout));
|
||||
@ -145,19 +124,4 @@ public class DeviceLayoutTabViewModel : ActivatableViewModelBase
|
||||
.WithSeverity(NotificationSeverity.Informational)
|
||||
.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));
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -2,11 +2,11 @@
|
||||
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:device="clr-namespace:Artemis.UI.Screens.Device"
|
||||
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"
|
||||
x:Class="Artemis.UI.Screens.Device.DeviceLogicalLayoutDialogView"
|
||||
x:DataType="device:DeviceLogicalLayoutDialogViewModel">
|
||||
x:Class="Artemis.UI.Screens.Device.Layout.DeviceLogicalLayoutDialogView"
|
||||
x:DataType="layout:DeviceLogicalLayoutDialogViewModel">
|
||||
<StackPanel>
|
||||
<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}" />
|
||||
@ -27,8 +27,9 @@
|
||||
<AutoCompleteBox.ItemTemplate>
|
||||
<DataTemplate DataType="{x:Type globalization:RegionInfo}">
|
||||
<TextBlock>
|
||||
<Run Text="{CompiledBinding EnglishName}"></Run>
|
||||
<Run Text="(" /><Run FontWeight="SemiBold" Text="{CompiledBinding TwoLetterISORegionName}"></Run><Run Text=")" />
|
||||
<Run Text="{CompiledBinding EnglishName}" />
|
||||
<Run Text="(" /><Run FontWeight="SemiBold" Text="{CompiledBinding TwoLetterISORegionName}" />
|
||||
<Run Text=")" />
|
||||
</TextBlock>
|
||||
</DataTemplate>
|
||||
</AutoCompleteBox.ItemTemplate>
|
||||
@ -1,19 +1,17 @@
|
||||
using System;
|
||||
using System.Globalization;
|
||||
using System.Threading.Tasks;
|
||||
using Avalonia.Controls;
|
||||
using Avalonia.Markup.Xaml;
|
||||
using Avalonia.ReactiveUI;
|
||||
using Avalonia.Threading;
|
||||
|
||||
namespace Artemis.UI.Screens.Device;
|
||||
namespace Artemis.UI.Screens.Device.Layout;
|
||||
|
||||
public partial class DeviceLogicalLayoutDialogView : ReactiveUserControl<DeviceLogicalLayoutDialogViewModel>
|
||||
{
|
||||
public DeviceLogicalLayoutDialogView()
|
||||
{
|
||||
InitializeComponent();
|
||||
|
||||
|
||||
RegionsAutoCompleteBox.ItemFilter += SearchRegions;
|
||||
Dispatcher.UIThread.InvokeAsync(DelayedAutoFocus);
|
||||
}
|
||||
@ -36,5 +34,4 @@ public partial class DeviceLogicalLayoutDialogView : ReactiveUserControl<DeviceL
|
||||
regionInfo.NativeName.Contains(search, StringComparison.OrdinalIgnoreCase) ||
|
||||
regionInfo.TwoLetterISORegionName.Contains(search, StringComparison.OrdinalIgnoreCase);
|
||||
}
|
||||
|
||||
}
|
||||
@ -12,7 +12,7 @@ using PropertyChanged.SourceGenerator;
|
||||
using ReactiveUI;
|
||||
using ContentDialogButton = Artemis.UI.Shared.Services.Builders.ContentDialogButton;
|
||||
|
||||
namespace Artemis.UI.Screens.Device;
|
||||
namespace Artemis.UI.Screens.Device.Layout;
|
||||
|
||||
public partial class DeviceLogicalLayoutDialogViewModel : ContentDialogViewModelBase
|
||||
{
|
||||
@ -24,7 +24,7 @@ public partial class DeviceLogicalLayoutDialogViewModel : ContentDialogViewModel
|
||||
public DeviceLogicalLayoutDialogViewModel(ArtemisDevice 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)
|
||||
.Where(c => c.LCID != LOCALE_INVARIANT &&
|
||||
c.LCID != LOCALE_NEUTRAL &&
|
||||
@ -2,10 +2,10 @@
|
||||
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: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"
|
||||
x:Class="Artemis.UI.Screens.Device.DevicePhysicalLayoutDialogView"
|
||||
x:DataType="device:DevicePhysicalLayoutDialogViewModel">
|
||||
x:Class="Artemis.UI.Screens.Device.Layout.DevicePhysicalLayoutDialogView"
|
||||
x:DataType="layout:DevicePhysicalLayoutDialogViewModel">
|
||||
<Grid RowDefinitions="Auto,*">
|
||||
<StackPanel Grid.Row="0">
|
||||
<StackPanel Orientation="Horizontal">
|
||||
@ -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>
|
||||
{
|
||||
@ -9,5 +8,4 @@ public partial class DevicePhysicalLayoutDialogView : ReactiveUserControl<Device
|
||||
{
|
||||
InitializeComponent();
|
||||
}
|
||||
|
||||
}
|
||||
@ -7,7 +7,7 @@ using Artemis.UI.Shared.Services;
|
||||
using FluentAvalonia.UI.Controls;
|
||||
using ReactiveUI;
|
||||
|
||||
namespace Artemis.UI.Screens.Device;
|
||||
namespace Artemis.UI.Screens.Device.Layout;
|
||||
|
||||
public class DevicePhysicalLayoutDialogViewModel : ContentDialogViewModelBase
|
||||
{
|
||||
@ -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>
|
||||
@ -0,0 +1,11 @@
|
||||
using Avalonia.Controls;
|
||||
|
||||
namespace Artemis.UI.Screens.Device.Layout.LayoutProviders;
|
||||
|
||||
public partial class CustomLayoutView : UserControl
|
||||
{
|
||||
public CustomLayoutView()
|
||||
{
|
||||
InitializeComponent();
|
||||
}
|
||||
}
|
||||
@ -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);
|
||||
}
|
||||
}
|
||||
@ -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>
|
||||
@ -0,0 +1,11 @@
|
||||
using Avalonia.Controls;
|
||||
|
||||
namespace Artemis.UI.Screens.Device.Layout.LayoutProviders;
|
||||
|
||||
public partial class DefaultLayoutView : UserControl
|
||||
{
|
||||
public DefaultLayoutView()
|
||||
{
|
||||
InitializeComponent();
|
||||
}
|
||||
}
|
||||
@ -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);
|
||||
}
|
||||
}
|
||||
@ -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();
|
||||
}
|
||||
@ -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>
|
||||
@ -0,0 +1,11 @@
|
||||
using Avalonia.Controls;
|
||||
|
||||
namespace Artemis.UI.Screens.Device.Layout.LayoutProviders;
|
||||
|
||||
public partial class NoneLayoutView : UserControl
|
||||
{
|
||||
public NoneLayoutView()
|
||||
{
|
||||
InitializeComponent();
|
||||
}
|
||||
}
|
||||
@ -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);
|
||||
}
|
||||
}
|
||||
@ -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>
|
||||
@ -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();
|
||||
}
|
||||
}
|
||||
@ -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);
|
||||
}
|
||||
}
|
||||
@ -2,7 +2,7 @@
|
||||
using Artemis.Core;
|
||||
using Artemis.UI.Shared;
|
||||
|
||||
namespace Artemis.UI.Screens.Device;
|
||||
namespace Artemis.UI.Screens.Device.Leds;
|
||||
|
||||
public class DeviceLedsTabLedViewModel : ViewModelBase
|
||||
{
|
||||
@ -5,9 +5,10 @@
|
||||
xmlns:device="clr-namespace:Artemis.UI.Screens.Device"
|
||||
xmlns:converters="clr-namespace:Artemis.UI.Converters"
|
||||
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"
|
||||
x:Class="Artemis.UI.Screens.Device.DeviceLedsTabView"
|
||||
x:DataType="device:DeviceLedsTabViewModel">
|
||||
x:Class="Artemis.UI.Screens.Device.Leds.DeviceLedsTabView"
|
||||
x:DataType="leds:DeviceLedsTabViewModel">
|
||||
<UserControl.Resources>
|
||||
<converters:UriToFileNameConverter x:Key="UriToFileNameConverter" />
|
||||
<converters:LedIdToStringConverter x:Key="LedIdToStringConverter" />
|
||||
@ -1,7 +1,6 @@
|
||||
using Avalonia.Markup.Xaml;
|
||||
using Avalonia.ReactiveUI;
|
||||
|
||||
namespace Artemis.UI.Screens.Device;
|
||||
namespace Artemis.UI.Screens.Device.Leds;
|
||||
|
||||
public partial class DeviceLedsTabView : ReactiveUserControl<DeviceLedsTabViewModel>
|
||||
{
|
||||
@ -8,7 +8,7 @@ using Artemis.UI.Shared;
|
||||
using DynamicData.Binding;
|
||||
using ReactiveUI;
|
||||
|
||||
namespace Artemis.UI.Screens.Device;
|
||||
namespace Artemis.UI.Screens.Device.Leds;
|
||||
|
||||
public class DeviceLedsTabViewModel : ActivatableViewModelBase
|
||||
{
|
||||
@ -0,0 +1,27 @@
|
||||
<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:asyncImageLoader="clr-namespace:AsyncImageLoader;assembly=AsyncImageLoader.Avalonia"
|
||||
xmlns:details="clr-namespace:Artemis.UI.Screens.Workshop.Entries.Details"
|
||||
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
|
||||
x:Class="Artemis.UI.Screens.Workshop.Entries.Details.EntryImageView"
|
||||
x:DataType="details:EntryImageViewModel">
|
||||
<Border Classes="card" Padding="0">
|
||||
<Grid RowDefinitions="Auto,*">
|
||||
<Border Grid.Row="0" ClipToBounds="True" CornerRadius="4 4 0 0" Padding="0">
|
||||
<Image asyncImageLoader:ImageLoader.Source="{CompiledBinding ThumbnailUrl}" Stretch="Uniform" HorizontalAlignment="Stretch" MaxHeight="250" />
|
||||
</Border>
|
||||
<Border Grid.Row="1" ClipToBounds="True" CornerRadius="0 0 4 4" Background="{DynamicResource ControlFillColorDefaultBrush}">
|
||||
<StackPanel Margin="16">
|
||||
<TextBlock Text="{CompiledBinding Image.Name}" TextTrimming="CharacterEllipsis" />
|
||||
<TextBlock Classes="subtitle"
|
||||
Text="{CompiledBinding Image.Description}"
|
||||
TextWrapping="Wrap"
|
||||
IsVisible="{CompiledBinding Image.Description, Converter={x:Static StringConverters.IsNotNullOrEmpty}}" />
|
||||
</StackPanel>
|
||||
</Border>
|
||||
|
||||
</Grid>
|
||||
</Border>
|
||||
</UserControl>
|
||||
@ -0,0 +1,13 @@
|
||||
using Avalonia;
|
||||
using Avalonia.Controls;
|
||||
using Avalonia.Markup.Xaml;
|
||||
|
||||
namespace Artemis.UI.Screens.Workshop.Entries.Details;
|
||||
|
||||
public partial class EntryImageView : UserControl
|
||||
{
|
||||
public EntryImageView()
|
||||
{
|
||||
InitializeComponent();
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,18 @@
|
||||
using Artemis.UI.Shared;
|
||||
using Artemis.WebClient.Workshop;
|
||||
|
||||
namespace Artemis.UI.Screens.Workshop.Entries.Details;
|
||||
|
||||
public class EntryImageViewModel : ViewModelBase
|
||||
{
|
||||
public EntryImageViewModel(IImage image)
|
||||
{
|
||||
Image = image;
|
||||
Url = $"{WorkshopConstants.WORKSHOP_URL}/images/{image.Id}.png";
|
||||
ThumbnailUrl = $"{WorkshopConstants.WORKSHOP_URL}/images/{image.Id}-thumb.png";
|
||||
}
|
||||
|
||||
public IImage Image { get; }
|
||||
public string Url { get; }
|
||||
public string ThumbnailUrl { get; }
|
||||
}
|
||||
@ -0,0 +1,12 @@
|
||||
<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:asyncImageLoader="clr-namespace:AsyncImageLoader;assembly=AsyncImageLoader.Avalonia"
|
||||
xmlns:details="clr-namespace:Artemis.UI.Screens.Workshop.Entries.Details"
|
||||
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
|
||||
x:Class="Artemis.UI.Screens.Workshop.Entries.Details.EntryImagesDialogView"
|
||||
x:DataType="details:EntryImagesDialogViewModel"
|
||||
Margin="-25 -63 -25 -25">
|
||||
<Image asyncImageLoader:ImageLoader.Source="{CompiledBinding CurrentImage.Url}" Stretch="Uniform"/>
|
||||
</UserControl>
|
||||
@ -0,0 +1,14 @@
|
||||
using Avalonia;
|
||||
using Avalonia.Controls;
|
||||
using Avalonia.Markup.Xaml;
|
||||
using Avalonia.ReactiveUI;
|
||||
|
||||
namespace Artemis.UI.Screens.Workshop.Entries.Details;
|
||||
|
||||
public partial class EntryImagesDialogView : ReactiveUserControl<EntryImagesDialogViewModel>
|
||||
{
|
||||
public EntryImagesDialogView()
|
||||
{
|
||||
InitializeComponent();
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,61 @@
|
||||
using System;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Reactive;
|
||||
using System.Reactive.Disposables;
|
||||
using Artemis.Core.Services;
|
||||
using Artemis.UI.Shared;
|
||||
using FluentAvalonia.UI.Controls;
|
||||
using PropertyChanged.SourceGenerator;
|
||||
using ReactiveUI;
|
||||
|
||||
namespace Artemis.UI.Screens.Workshop.Entries.Details;
|
||||
|
||||
public partial class EntryImagesDialogViewModel : ContentDialogViewModelBase
|
||||
{
|
||||
[Notify] private EntryImageViewModel _currentImage;
|
||||
private readonly IInputService _inputService;
|
||||
|
||||
public EntryImagesDialogViewModel(ObservableCollection<EntryImageViewModel> images, EntryImageViewModel startImage, IInputService inputService)
|
||||
{
|
||||
_currentImage = startImage;
|
||||
_inputService = inputService;
|
||||
|
||||
Images = images;
|
||||
Previous = ReactiveCommand.Create(() => CurrentImage = Images[(Images.IndexOf(CurrentImage) - 1 + Images.Count) % Images.Count]);
|
||||
Next = ReactiveCommand.Create(() => CurrentImage = Images[(Images.IndexOf(CurrentImage) + 1) % Images.Count]);
|
||||
|
||||
this.WhenActivated(d =>
|
||||
{
|
||||
if (ContentDialog == null)
|
||||
return;
|
||||
|
||||
_inputService.KeyboardKeyDown += InputServiceOnKeyboardKeyDown;
|
||||
ContentDialog.Closing += ContentDialogOnClosing;
|
||||
Disposable.Create(() =>
|
||||
{
|
||||
_inputService.KeyboardKeyDown -= InputServiceOnKeyboardKeyDown;
|
||||
ContentDialog.Closing -= ContentDialogOnClosing;
|
||||
}).DisposeWith(d);
|
||||
});
|
||||
}
|
||||
|
||||
private void InputServiceOnKeyboardKeyDown(object? sender, ArtemisKeyboardKeyEventArgs e)
|
||||
{
|
||||
// Leveraging InputService to avoid issues with which control has focus
|
||||
if (e.Key == KeyboardKey.ArrowRight)
|
||||
Next.Execute().Subscribe();
|
||||
else if (e.Key == KeyboardKey.ArrowLeft)
|
||||
Previous.Execute().Subscribe();
|
||||
else if (e.Key == KeyboardKey.Escape)
|
||||
ContentDialog?.Hide(ContentDialogResult.None);
|
||||
}
|
||||
|
||||
private void ContentDialogOnClosing(ContentDialog sender, ContentDialogClosingEventArgs args)
|
||||
{
|
||||
args.Cancel = args.Result != ContentDialogResult.None;
|
||||
}
|
||||
|
||||
public ObservableCollection<EntryImageViewModel> Images { get; }
|
||||
public ReactiveCommand<Unit, EntryImageViewModel> Previous { get; }
|
||||
public ReactiveCommand<Unit, EntryImageViewModel> Next { get; }
|
||||
}
|
||||
@ -0,0 +1,33 @@
|
||||
<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:details="clr-namespace:Artemis.UI.Screens.Workshop.Entries.Details"
|
||||
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
|
||||
x:Class="Artemis.UI.Screens.Workshop.Entries.Details.EntryImagesView"
|
||||
x:DataType="details:EntryImagesViewModel">
|
||||
<ScrollViewer Classes="with-padding" HorizontalScrollBarVisibility="Disabled" VerticalScrollBarVisibility="Auto">
|
||||
<ItemsControl ItemsSource="{CompiledBinding Images}">
|
||||
<ItemsControl.Styles>
|
||||
<Styles>
|
||||
<Style Selector="ItemsControl > ContentPresenter">
|
||||
<Setter Property="Margin" Value="0 0 0 10"></Setter>
|
||||
</Style>
|
||||
<Style Selector="ItemsControl > ContentPresenter:nth-last-child(1)">
|
||||
<Setter Property="Margin" Value="0 0 0 0"></Setter>
|
||||
</Style>
|
||||
</Styles>
|
||||
</ItemsControl.Styles>
|
||||
<ItemsControl.ItemTemplate>
|
||||
<DataTemplate>
|
||||
<ContentPresenter Content="{CompiledBinding}" Cursor="Hand" PointerPressed="InputElement_OnPointerPressed"></ContentPresenter>
|
||||
</DataTemplate>
|
||||
</ItemsControl.ItemTemplate>
|
||||
<ItemsControl.ItemsPanel>
|
||||
<ItemsPanelTemplate>
|
||||
<VirtualizingStackPanel />
|
||||
</ItemsPanelTemplate>
|
||||
</ItemsControl.ItemsPanel>
|
||||
</ItemsControl>
|
||||
</ScrollViewer>
|
||||
</UserControl>
|
||||
@ -0,0 +1,23 @@
|
||||
using Avalonia;
|
||||
using Avalonia.Input;
|
||||
using Avalonia.ReactiveUI;
|
||||
|
||||
namespace Artemis.UI.Screens.Workshop.Entries.Details;
|
||||
|
||||
public partial class EntryImagesView : ReactiveUserControl<EntryImagesViewModel>
|
||||
{
|
||||
public EntryImagesView()
|
||||
{
|
||||
InitializeComponent();
|
||||
}
|
||||
|
||||
private void InputElement_OnPointerPressed(object? sender, PointerPressedEventArgs e)
|
||||
{
|
||||
if (sender is not IDataContextProvider contextProvider)
|
||||
return;
|
||||
if (contextProvider.DataContext is not EntryImageViewModel entryImageViewModel)
|
||||
return;
|
||||
|
||||
ViewModel?.ShowImages(entryImageViewModel);
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,30 @@
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using Artemis.UI.Shared;
|
||||
using Artemis.UI.Shared.Services;
|
||||
using Artemis.WebClient.Workshop;
|
||||
|
||||
namespace Artemis.UI.Screens.Workshop.Entries.Details;
|
||||
|
||||
public class EntryImagesViewModel : ViewModelBase
|
||||
{
|
||||
private readonly IWindowService _windowService;
|
||||
public ObservableCollection<EntryImageViewModel> Images { get; }
|
||||
|
||||
public EntryImagesViewModel(IEntryDetails entryDetails, IWindowService windowService)
|
||||
{
|
||||
_windowService = windowService;
|
||||
Images = new ObservableCollection<EntryImageViewModel>(entryDetails.Images.Select(i => new EntryImageViewModel(i)));
|
||||
}
|
||||
|
||||
public async Task ShowImages(EntryImageViewModel image)
|
||||
{
|
||||
await _windowService.CreateContentDialog()
|
||||
.WithViewModel(out EntryImagesDialogViewModel vm, Images, image)
|
||||
.HavingPrimaryButton(b => b.WithText("Previous").WithCommand(vm.Previous))
|
||||
.HavingSecondaryButton(b => b.WithText("Next").WithCommand(vm.Next))
|
||||
.WithFullScreen()
|
||||
.ShowAsync();
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,83 @@
|
||||
<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:details="clr-namespace:Artemis.UI.Screens.Workshop.Entries.Details"
|
||||
xmlns:il="clr-namespace:AsyncImageLoader;assembly=AsyncImageLoader.Avalonia"
|
||||
xmlns:avalonia="clr-namespace:Material.Icons.Avalonia;assembly=Material.Icons.Avalonia"
|
||||
xmlns:converters="clr-namespace:Artemis.UI.Converters"
|
||||
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
|
||||
x:Class="Artemis.UI.Screens.Workshop.Entries.Details.EntryInfoView"
|
||||
x:DataType="details:EntryInfoViewModel">
|
||||
<UserControl.Resources>
|
||||
<converters:EntryIconUriConverter x:Key="EntryIconUriConverter" />
|
||||
<converters:DateTimeConverter x:Key="DateTimeConverter" />
|
||||
</UserControl.Resources>
|
||||
<StackPanel>
|
||||
<Panel>
|
||||
<Border CornerRadius="6"
|
||||
HorizontalAlignment="Left"
|
||||
Margin="0 0 10 0"
|
||||
Width="80"
|
||||
Height="80"
|
||||
ClipToBounds="True">
|
||||
<Image Stretch="UniformToFill" il:ImageLoader.Source="{CompiledBinding Entry.Id, Converter={StaticResource EntryIconUriConverter}, Mode=OneWay}" />
|
||||
</Border>
|
||||
<Button Classes="icon-button"
|
||||
VerticalAlignment="Top"
|
||||
HorizontalAlignment="Right"
|
||||
Command="{CompiledBinding CopyShareLink}"
|
||||
ToolTip.Tip="Copy share link">
|
||||
<avalonia:MaterialIcon Kind="ShareVariant" />
|
||||
</Button>
|
||||
</Panel>
|
||||
|
||||
|
||||
<TextBlock Theme="{StaticResource TitleTextBlockStyle}"
|
||||
MaxLines="3"
|
||||
TextTrimming="CharacterEllipsis"
|
||||
Text="{CompiledBinding Entry.Name, FallbackValue=Title }" />
|
||||
|
||||
<TextBlock Classes="subtitle" TextTrimming="CharacterEllipsis" Text="{CompiledBinding Entry.Author, FallbackValue=Author}" />
|
||||
|
||||
<TextBlock Margin="0 8" TextWrapping="Wrap" Text="{CompiledBinding Entry.Summary, FallbackValue=Summary}" />
|
||||
|
||||
<!-- Categories -->
|
||||
<ItemsControl ItemsSource="{CompiledBinding Entry.Categories}" Margin="0 0 -8 0">
|
||||
<ItemsControl.ItemsPanel>
|
||||
<ItemsPanelTemplate>
|
||||
<WrapPanel Orientation="Horizontal"></WrapPanel>
|
||||
</ItemsPanelTemplate>
|
||||
</ItemsControl.ItemsPanel>
|
||||
<ItemsControl.ItemTemplate>
|
||||
<DataTemplate>
|
||||
<StackPanel Orientation="Horizontal" Margin="0 0 8 0">
|
||||
<avalonia:MaterialIcon Kind="{CompiledBinding Icon}" Margin="0 0 3 0"></avalonia:MaterialIcon>
|
||||
<TextBlock Text="{CompiledBinding Name}" TextTrimming="CharacterEllipsis" />
|
||||
</StackPanel>
|
||||
</DataTemplate>
|
||||
</ItemsControl.ItemTemplate>
|
||||
</ItemsControl>
|
||||
|
||||
<Border Classes="card-separator"></Border>
|
||||
|
||||
<TextBlock Margin="0 0 0 8">
|
||||
<avalonia:MaterialIcon Kind="Downloads" />
|
||||
<Run Classes="h5" Text="{CompiledBinding Entry.Downloads, FallbackValue=0}" />
|
||||
<Run>downloads</Run>
|
||||
</TextBlock>
|
||||
|
||||
<TextBlock Classes="subtitle"
|
||||
ToolTip.Tip="{CompiledBinding Entry.CreatedAt, Converter={StaticResource DateTimeConverter}}">
|
||||
<avalonia:MaterialIcon Kind="Calendar" />
|
||||
<Run>Created</Run>
|
||||
<Run Text="{CompiledBinding Entry.CreatedAt, Converter={StaticResource DateTimeConverter}, ConverterParameter='humanize'}"></Run>
|
||||
</TextBlock>
|
||||
<TextBlock Classes="subtitle"
|
||||
ToolTip.Tip="{CompiledBinding UpdatedAt, Converter={StaticResource DateTimeConverter}}">
|
||||
<avalonia:MaterialIcon Kind="Update" />
|
||||
<Run>Updated</Run>
|
||||
<Run Text="{CompiledBinding UpdatedAt, Converter={StaticResource DateTimeConverter}, ConverterParameter='humanize'}"></Run>
|
||||
</TextBlock>
|
||||
</StackPanel>
|
||||
</UserControl>
|
||||
@ -0,0 +1,13 @@
|
||||
using Avalonia;
|
||||
using Avalonia.Controls;
|
||||
using Avalonia.Markup.Xaml;
|
||||
|
||||
namespace Artemis.UI.Screens.Workshop.Entries.Details;
|
||||
|
||||
public partial class EntryInfoView : UserControl
|
||||
{
|
||||
public EntryInfoView()
|
||||
{
|
||||
InitializeComponent();
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,28 @@
|
||||
using System;
|
||||
using System.Threading.Tasks;
|
||||
using Artemis.Core;
|
||||
using Artemis.UI.Shared;
|
||||
using Artemis.UI.Shared.Services;
|
||||
using Artemis.WebClient.Workshop;
|
||||
|
||||
namespace Artemis.UI.Screens.Workshop.Entries.Details;
|
||||
|
||||
public class EntryInfoViewModel : ViewModelBase
|
||||
{
|
||||
private readonly INotificationService _notificationService;
|
||||
public IGetEntryById_Entry Entry { get; }
|
||||
public DateTimeOffset? UpdatedAt { get; }
|
||||
|
||||
public EntryInfoViewModel(IGetEntryById_Entry entry, INotificationService notificationService)
|
||||
{
|
||||
_notificationService = notificationService;
|
||||
Entry = entry;
|
||||
UpdatedAt = Entry.LatestRelease?.CreatedAt ?? Entry.CreatedAt;
|
||||
}
|
||||
|
||||
public async Task CopyShareLink()
|
||||
{
|
||||
await Shared.UI.Clipboard.SetTextAsync($"{WorkshopConstants.WORKSHOP_URL}/entries/{Entry.Id}/{StringUtilities.UrlFriendly(Entry.Name)}");
|
||||
_notificationService.CreateNotification().WithTitle("Copied share link to clipboard.").Show();
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,52 @@
|
||||
<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:details="clr-namespace:Artemis.UI.Screens.Workshop.Entries.Details"
|
||||
xmlns:converters="clr-namespace:Artemis.UI.Converters"
|
||||
xmlns:sharedConverters="clr-namespace:Artemis.UI.Shared.Converters;assembly=Artemis.UI.Shared"
|
||||
xmlns:avalonia="clr-namespace:Material.Icons.Avalonia;assembly=Material.Icons.Avalonia"
|
||||
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
|
||||
x:Class="Artemis.UI.Screens.Workshop.Entries.Details.EntryReleasesView"
|
||||
x:DataType="details:EntryReleasesViewModel">
|
||||
<UserControl.Resources>
|
||||
<converters:DateTimeConverter x:Key="DateTimeConverter" />
|
||||
<sharedConverters:BytesToStringConverter x:Key="BytesToStringConverter" />
|
||||
</UserControl.Resources>
|
||||
<StackPanel>
|
||||
<TextBlock Theme="{StaticResource SubtitleTextBlockStyle}">Latest release</TextBlock>
|
||||
<Border Classes="card-separator" />
|
||||
<Button HorizontalAlignment="Stretch"
|
||||
HorizontalContentAlignment="Stretch"
|
||||
Command="{CompiledBinding DownloadLatestRelease}">
|
||||
<Grid ColumnDefinitions="Auto,*">
|
||||
<!-- Icon -->
|
||||
<Border Grid.Column="0"
|
||||
CornerRadius="4"
|
||||
Background="{StaticResource SystemAccentColor}"
|
||||
VerticalAlignment="Center"
|
||||
Margin="0 6"
|
||||
Width="50"
|
||||
Height="50"
|
||||
ClipToBounds="True">
|
||||
<avalonia:MaterialIcon Kind="Download"></avalonia:MaterialIcon>
|
||||
</Border>
|
||||
|
||||
<!-- Body -->
|
||||
<StackPanel Grid.Column="1" Margin="10 0" VerticalAlignment="Center">
|
||||
<TextBlock Text="{CompiledBinding Entry.LatestRelease.Version, FallbackValue=Version}"></TextBlock>
|
||||
<TextBlock Classes="subtitle">
|
||||
<avalonia:MaterialIcon Kind="BoxOutline" />
|
||||
<Run Text="{CompiledBinding Entry.LatestRelease.DownloadSize, Converter={StaticResource BytesToStringConverter}, Mode=OneWay}"></Run>
|
||||
</TextBlock>
|
||||
<TextBlock Classes="subtitle"
|
||||
ToolTip.Tip="{CompiledBinding Entry.LatestRelease.CreatedAt, Converter={StaticResource DateTimeConverter}}">
|
||||
<avalonia:MaterialIcon Kind="Calendar" />
|
||||
<Run>Created</Run>
|
||||
<Run Text="{CompiledBinding Entry.LatestRelease.CreatedAt, Converter={StaticResource DateTimeConverter}, ConverterParameter='humanize'}"></Run>
|
||||
</TextBlock>
|
||||
</StackPanel>
|
||||
</Grid>
|
||||
</Button>
|
||||
</StackPanel>
|
||||
</UserControl>
|
||||
@ -0,0 +1,13 @@
|
||||
using Avalonia;
|
||||
using Avalonia.Controls;
|
||||
using Avalonia.Markup.Xaml;
|
||||
|
||||
namespace Artemis.UI.Screens.Workshop.Entries.Details;
|
||||
|
||||
public partial class EntryReleasesView : UserControl
|
||||
{
|
||||
public EntryReleasesView()
|
||||
{
|
||||
InitializeComponent();
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,66 @@
|
||||
using System;
|
||||
using System.Reactive;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Artemis.UI.Shared;
|
||||
using Artemis.UI.Shared.Services;
|
||||
using Artemis.UI.Shared.Services.Builders;
|
||||
using Artemis.UI.Shared.Utilities;
|
||||
using Artemis.WebClient.Workshop;
|
||||
using Artemis.WebClient.Workshop.Handlers.InstallationHandlers;
|
||||
using Artemis.WebClient.Workshop.Services;
|
||||
using Humanizer;
|
||||
using ReactiveUI;
|
||||
|
||||
namespace Artemis.UI.Screens.Workshop.Entries.Details;
|
||||
|
||||
public class EntryReleasesViewModel : ViewModelBase
|
||||
{
|
||||
private readonly EntryInstallationHandlerFactory _factory;
|
||||
private readonly IWindowService _windowService;
|
||||
private readonly INotificationService _notificationService;
|
||||
|
||||
public EntryReleasesViewModel(IGetEntryById_Entry entry, EntryInstallationHandlerFactory factory, IWindowService windowService, INotificationService notificationService)
|
||||
{
|
||||
_factory = factory;
|
||||
_windowService = windowService;
|
||||
_notificationService = notificationService;
|
||||
|
||||
Entry = entry;
|
||||
DownloadLatestRelease = ReactiveCommand.CreateFromTask(ExecuteDownloadLatestRelease);
|
||||
}
|
||||
|
||||
public IGetEntryById_Entry Entry { get; }
|
||||
public ReactiveCommand<Unit, Unit> DownloadLatestRelease { get; }
|
||||
|
||||
public Func<InstalledEntry, Task>? OnInstallationFinished { get; set; }
|
||||
|
||||
private async Task ExecuteDownloadLatestRelease(CancellationToken cancellationToken)
|
||||
{
|
||||
if (Entry.LatestRelease == null)
|
||||
return;
|
||||
|
||||
bool confirm = await _windowService.ShowConfirmContentDialog(
|
||||
"Install latest release",
|
||||
$"Are you sure you want to download and install version {Entry.LatestRelease.Version} of {Entry.Name}?"
|
||||
);
|
||||
if (!confirm)
|
||||
return;
|
||||
|
||||
IEntryInstallationHandler installationHandler = _factory.CreateHandler(Entry.EntryType);
|
||||
EntryInstallResult result = await installationHandler.InstallAsync(Entry, Entry.LatestRelease, new Progress<StreamProgress>(), cancellationToken);
|
||||
if (result.IsSuccess && result.Entry != null)
|
||||
{
|
||||
if (OnInstallationFinished != null)
|
||||
await OnInstallationFinished(result.Entry);
|
||||
_notificationService.CreateNotification().WithTitle($"{Entry.EntryType.Humanize(LetterCasing.Sentence)} installed").WithSeverity(NotificationSeverity.Success).Show();
|
||||
}
|
||||
else
|
||||
{
|
||||
_notificationService.CreateNotification()
|
||||
.WithTitle($"Failed to install {Entry.EntryType.Humanize(LetterCasing.LowerCase)}")
|
||||
.WithMessage(result.Message)
|
||||
.WithSeverity(NotificationSeverity.Error).Show();
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -9,9 +9,10 @@
|
||||
xmlns:avaloniaEdit="https://github.com/avaloniaui/avaloniaedit"
|
||||
xmlns:mdxaml="https://github.com/whistyun/Markdown.Avalonia.Tight"
|
||||
xmlns:controls="clr-namespace:FluentAvalonia.UI.Controls;assembly=FluentAvalonia"
|
||||
xmlns:details="clr-namespace:Artemis.UI.Screens.Workshop.Entries.Details"
|
||||
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="800"
|
||||
x:Class="Artemis.UI.Screens.Workshop.Entries.EntrySpecificationsView"
|
||||
x:DataType="entries:EntrySpecificationsViewModel">
|
||||
x:Class="Artemis.UI.Screens.Workshop.Entries.Details.EntrySpecificationsView"
|
||||
x:DataType="details:EntrySpecificationsViewModel">
|
||||
<Grid RowDefinitions="Auto,Auto,*,Auto">
|
||||
<StackPanel>
|
||||
<StackPanel.Styles>
|
||||
@ -1,5 +1,4 @@
|
||||
using System.Linq;
|
||||
using Artemis.UI.Shared.Extensions;
|
||||
using Avalonia;
|
||||
using Avalonia.Controls;
|
||||
using Avalonia.Media;
|
||||
@ -8,8 +7,9 @@ using Avalonia.ReactiveUI;
|
||||
using AvaloniaEdit.TextMate;
|
||||
using ReactiveUI;
|
||||
using TextMateSharp.Grammars;
|
||||
using VisualExtensions = Artemis.UI.Shared.Extensions.VisualExtensions;
|
||||
|
||||
namespace Artemis.UI.Screens.Workshop.Entries;
|
||||
namespace Artemis.UI.Screens.Workshop.Entries.Details;
|
||||
|
||||
public partial class EntrySpecificationsView : ReactiveUserControl<EntrySpecificationsViewModel>
|
||||
{
|
||||
@ -23,7 +23,7 @@ public partial class EntrySpecificationsView : ReactiveUserControl<EntrySpecific
|
||||
|
||||
DescriptionEditor.Options.AllowScrollBelowDocument = false;
|
||||
RegistryOptions options = new(ThemeName.Dark);
|
||||
TextMate.Installation? install = DescriptionEditor.InstallTextMate(options);
|
||||
TextMate.Installation? install = TextMate.InstallTextMate(DescriptionEditor, options);
|
||||
|
||||
install.SetGrammar(options.GetScopeByExtension(".md"));
|
||||
|
||||
@ -45,8 +45,8 @@ public partial class EntrySpecificationsView : ReactiveUserControl<EntrySpecific
|
||||
if (_previewScrollViewer != null)
|
||||
_previewScrollViewer.PropertyChanged -= PreviewScrollViewerOnPropertyChanged;
|
||||
|
||||
_editorScrollViewer = DescriptionEditor.GetVisualChildrenOfType<ScrollViewer>().FirstOrDefault();
|
||||
_previewScrollViewer = DescriptionPreview.GetVisualChildrenOfType<ScrollViewer>().FirstOrDefault();
|
||||
_editorScrollViewer = VisualExtensions.GetVisualChildrenOfType<ScrollViewer>(DescriptionEditor).FirstOrDefault();
|
||||
_previewScrollViewer = VisualExtensions.GetVisualChildrenOfType<ScrollViewer>(DescriptionPreview).FirstOrDefault();
|
||||
|
||||
if (_editorScrollViewer != null)
|
||||
_editorScrollViewer.PropertyChanged += EditorScrollViewerOnPropertyChanged;
|
||||
@ -22,7 +22,7 @@ using ReactiveUI.Validation.Extensions;
|
||||
using ReactiveUI.Validation.Helpers;
|
||||
using StrawberryShake;
|
||||
|
||||
namespace Artemis.UI.Screens.Workshop.Entries;
|
||||
namespace Artemis.UI.Screens.Workshop.Entries.Details;
|
||||
|
||||
public partial class EntrySpecificationsViewModel : ValidatableViewModelBase
|
||||
{
|
||||
@ -52,12 +52,12 @@ public partial class EntrySpecificationsViewModel : ValidatableViewModelBase
|
||||
.Subscribe();
|
||||
SelectedCategories = selectedCategories;
|
||||
|
||||
this.ValidationRule(vm => vm.Name, s => !string.IsNullOrWhiteSpace(s), "Name is required");
|
||||
this.ValidationRule(vm => vm.Summary, s => !string.IsNullOrWhiteSpace(s), "Summary is required");
|
||||
ValidationHelper descriptionRule = this.ValidationRule(vm => vm.Description, s => !string.IsNullOrWhiteSpace(s), "Description is required");
|
||||
this.ValidationRule<EntrySpecificationsViewModel, string>(vm => vm.Name, s => !string.IsNullOrWhiteSpace(s), "Name is required");
|
||||
this.ValidationRule<EntrySpecificationsViewModel, string>(vm => vm.Summary, s => !string.IsNullOrWhiteSpace(s), "Summary is required");
|
||||
ValidationHelper descriptionRule = this.ValidationRule<EntrySpecificationsViewModel, string>(vm => vm.Description, s => !string.IsNullOrWhiteSpace(s), "Description is required");
|
||||
|
||||
// These don't use inputs that support validation messages, do so manually
|
||||
ValidationHelper iconRule = this.ValidationRule(vm => vm.IconBitmap, s => s != null, "Icon required");
|
||||
ValidationHelper iconRule = this.ValidationRule<EntrySpecificationsViewModel, Bitmap>(vm => vm.IconBitmap, s => s != null, "Icon required");
|
||||
ValidationHelper categoriesRule = this.ValidationRule(vm => vm.Categories, Categories.ToObservableChangeSet().AutoRefresh(c => c.IsSelected).Filter(c => c.IsSelected).IsNotEmpty(),
|
||||
"At least one category must be selected"
|
||||
);
|
||||
@ -23,7 +23,7 @@
|
||||
</Styles>
|
||||
</controls:NavigationView.Styles>
|
||||
|
||||
<controls:Frame Name="TabFrame" IsNavigationStackEnabled="False" CacheSize="0" Padding="20">
|
||||
<controls:Frame Name="TabFrame" IsNavigationStackEnabled="False" CacheSize="0" Padding="20 20 10 20">
|
||||
<controls:Frame.NavigationPageFactory>
|
||||
<ui:PageFactory/>
|
||||
</controls:Frame.NavigationPageFactory>
|
||||
|
||||
@ -1,8 +0,0 @@
|
||||
<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.Workshop.Entries.EntryInstallationDialogView">
|
||||
Welcome to Avalonia!
|
||||
</UserControl>
|
||||
@ -1,13 +0,0 @@
|
||||
using Avalonia;
|
||||
using Avalonia.Controls;
|
||||
using Avalonia.Markup.Xaml;
|
||||
|
||||
namespace Artemis.UI.Screens.Workshop.Entries;
|
||||
|
||||
public partial class EntryInstallationDialogView : UserControl
|
||||
{
|
||||
public EntryInstallationDialogView()
|
||||
{
|
||||
InitializeComponent();
|
||||
}
|
||||
}
|
||||
@ -1,8 +0,0 @@
|
||||
using Artemis.UI.Shared;
|
||||
|
||||
namespace Artemis.UI.Screens.Workshop.Entries;
|
||||
|
||||
public class EntryInstallationDialogViewModel : ContentDialogViewModelBase
|
||||
{
|
||||
|
||||
}
|
||||
@ -4,9 +4,10 @@
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
xmlns:entries="clr-namespace:Artemis.UI.Screens.Workshop.Entries"
|
||||
xmlns:system="clr-namespace:System;assembly=System.Runtime"
|
||||
xmlns:list="clr-namespace:Artemis.UI.Screens.Workshop.Entries.List"
|
||||
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
|
||||
x:Class="Artemis.UI.Screens.Workshop.Entries.EntryListInputView"
|
||||
x:DataType="entries:EntryListInputViewModel">
|
||||
x:Class="Artemis.UI.Screens.Workshop.Entries.List.EntryListInputView"
|
||||
x:DataType="list:EntryListInputViewModel">
|
||||
<Grid>
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="*" MaxWidth="500" />
|
||||
@ -1,8 +1,6 @@
|
||||
using Avalonia;
|
||||
using Avalonia.Controls;
|
||||
using Avalonia.Markup.Xaml;
|
||||
|
||||
namespace Artemis.UI.Screens.Workshop.Entries;
|
||||
namespace Artemis.UI.Screens.Workshop.Entries.List;
|
||||
|
||||
public partial class EntryListInputView : UserControl
|
||||
{
|
||||
@ -4,7 +4,7 @@ using Artemis.UI.Shared;
|
||||
using PropertyChanged.SourceGenerator;
|
||||
using ReactiveUI;
|
||||
|
||||
namespace Artemis.UI.Screens.Workshop.Entries;
|
||||
namespace Artemis.UI.Screens.Workshop.Entries.List;
|
||||
|
||||
public partial class EntryListInputViewModel : ViewModelBase
|
||||
{
|
||||
@ -6,9 +6,10 @@
|
||||
xmlns:entries1="clr-namespace:Artemis.UI.Screens.Workshop.Entries"
|
||||
xmlns:il="clr-namespace:AsyncImageLoader;assembly=AsyncImageLoader.Avalonia"
|
||||
xmlns:converters="clr-namespace:Artemis.UI.Converters"
|
||||
xmlns:list="clr-namespace:Artemis.UI.Screens.Workshop.Entries.List"
|
||||
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="110"
|
||||
x:Class="Artemis.UI.Screens.Workshop.Entries.EntryListItemView"
|
||||
x:DataType="entries1:EntryListItemViewModel">
|
||||
x:Class="Artemis.UI.Screens.Workshop.Entries.List.EntryListItemView"
|
||||
x:DataType="list:EntryListItemViewModel">
|
||||
<UserControl.Resources>
|
||||
<converters:EntryIconUriConverter x:Key="EntryIconUriConverter" />
|
||||
<converters:DateTimeConverter x:Key="DateTimeConverter" />
|
||||
@ -1,6 +1,6 @@
|
||||
using Avalonia.ReactiveUI;
|
||||
|
||||
namespace Artemis.UI.Screens.Workshop.Entries;
|
||||
namespace Artemis.UI.Screens.Workshop.Entries.List;
|
||||
|
||||
public partial class EntryListItemView : ReactiveUserControl<EntryListItemViewModel>
|
||||
{
|
||||
@ -1,17 +1,12 @@
|
||||
using System;
|
||||
using System.Reactive;
|
||||
using System.Reactive.Disposables;
|
||||
using System.Reactive.Linq;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Artemis.UI.Shared;
|
||||
using Artemis.UI.Shared.Routing;
|
||||
using Artemis.WebClient.Workshop;
|
||||
using Artemis.WebClient.Workshop.Services;
|
||||
using Avalonia.Media.Imaging;
|
||||
using ReactiveUI;
|
||||
|
||||
namespace Artemis.UI.Screens.Workshop.Entries;
|
||||
namespace Artemis.UI.Screens.Workshop.Entries.List;
|
||||
|
||||
public class EntryListItemViewModel : ActivatableViewModelBase
|
||||
{
|
||||
@ -17,7 +17,7 @@ using PropertyChanged.SourceGenerator;
|
||||
using ReactiveUI;
|
||||
using StrawberryShake;
|
||||
|
||||
namespace Artemis.UI.Screens.Workshop.Entries;
|
||||
namespace Artemis.UI.Screens.Workshop.Entries.List;
|
||||
|
||||
public abstract partial class EntryListViewModel : RoutableScreen<WorkshopListParameters>
|
||||
{
|
||||
@ -42,8 +42,8 @@ public abstract partial class EntryListViewModel : RoutableScreen<WorkshopListPa
|
||||
_route = route;
|
||||
_workshopClient = workshopClient;
|
||||
_notificationService = notificationService;
|
||||
_showPagination = this.WhenAnyValue(vm => vm.TotalPages).Select(t => t > 1).ToProperty(this, vm => vm.ShowPagination);
|
||||
_isLoading = this.WhenAnyValue(vm => vm.Page, vm => vm.LoadedPage, (p, c) => p != c).ToProperty(this, vm => vm.IsLoading);
|
||||
_showPagination = this.WhenAnyValue<EntryListViewModel, int>(vm => vm.TotalPages).Select(t => t > 1).ToProperty(this, vm => vm.ShowPagination);
|
||||
_isLoading = this.WhenAnyValue<EntryListViewModel, bool, int, int>(vm => vm.Page, vm => vm.LoadedPage, (p, c) => p != c).ToProperty(this, vm => vm.IsLoading);
|
||||
|
||||
CategoriesViewModel = categoriesViewModel;
|
||||
InputViewModel = entryListInputViewModel;
|
||||
@ -56,7 +56,7 @@ public abstract partial class EntryListViewModel : RoutableScreen<WorkshopListPa
|
||||
Entries = entries;
|
||||
|
||||
// Respond to page changes
|
||||
this.WhenAnyValue(vm => vm.Page).Skip(1).Subscribe(p => Task.Run(() => router.Navigate($"{_route}/{p}")));
|
||||
this.WhenAnyValue<EntryListViewModel, int>(vm => vm.Page).Skip(1).Subscribe(p => Task.Run(() => router.Navigate($"{_route}/{p}")));
|
||||
|
||||
this.WhenActivated(d =>
|
||||
{
|
||||
@ -1,17 +1,18 @@
|
||||
using System;
|
||||
using Artemis.UI.Screens.Workshop.Categories;
|
||||
using Artemis.UI.Screens.Workshop.Entries.List;
|
||||
using Artemis.UI.Shared.Routing;
|
||||
using Artemis.UI.Shared.Services;
|
||||
using Artemis.WebClient.Workshop;
|
||||
|
||||
namespace Artemis.UI.Screens.Workshop.Entries.Tabs;
|
||||
|
||||
public class LayoutListViewModel : EntryListViewModel
|
||||
public class LayoutListViewModel : List.EntryListViewModel
|
||||
{
|
||||
public LayoutListViewModel(IWorkshopClient workshopClient,
|
||||
IRouter router,
|
||||
CategoriesViewModel categoriesViewModel,
|
||||
EntryListInputViewModel entryListInputViewModel,
|
||||
List.EntryListInputViewModel entryListInputViewModel,
|
||||
INotificationService notificationService,
|
||||
Func<IGetEntries_Entries_Items, EntryListItemViewModel> getEntryListViewModel)
|
||||
: base("workshop/entries/layout", workshopClient, router, categoriesViewModel, entryListInputViewModel, notificationService, getEntryListViewModel)
|
||||
|
||||
@ -1,17 +1,18 @@
|
||||
using System;
|
||||
using Artemis.UI.Screens.Workshop.Categories;
|
||||
using Artemis.UI.Screens.Workshop.Entries.List;
|
||||
using Artemis.UI.Shared.Routing;
|
||||
using Artemis.UI.Shared.Services;
|
||||
using Artemis.WebClient.Workshop;
|
||||
|
||||
namespace Artemis.UI.Screens.Workshop.Entries.Tabs;
|
||||
|
||||
public class ProfileListViewModel : EntryListViewModel
|
||||
public class ProfileListViewModel : List.EntryListViewModel
|
||||
{
|
||||
public ProfileListViewModel(IWorkshopClient workshopClient,
|
||||
IRouter router,
|
||||
CategoriesViewModel categoriesViewModel,
|
||||
EntryListInputViewModel entryListInputViewModel,
|
||||
List.EntryListInputViewModel entryListInputViewModel,
|
||||
INotificationService notificationService,
|
||||
Func<IGetEntries_Entries_Items, EntryListItemViewModel> getEntryListViewModel)
|
||||
: base("workshop/entries/profiles", workshopClient, router, categoriesViewModel, entryListInputViewModel, notificationService, getEntryListViewModel)
|
||||
|
||||
@ -0,0 +1,15 @@
|
||||
<UserControl xmlns="https://github.com/avaloniaui"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
xmlns:image="clr-namespace:Artemis.UI.Screens.Workshop.Image"
|
||||
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
|
||||
x:Class="Artemis.UI.Screens.Workshop.Image.ImagePropertiesDialogView"
|
||||
x:DataType="image:ImagePropertiesDialogViewModel">
|
||||
<StackPanel Width="400">
|
||||
<Label>Name</Label>
|
||||
<TextBox Text="{CompiledBinding Name}" Watermark="Name" MaxLength="50"/>
|
||||
<Label>Description</Label>
|
||||
<TextBox AcceptsReturn="True" Height="150" Text="{CompiledBinding Description}" Watermark="Description" MaxLength="150" MaxLines="7"/>
|
||||
</StackPanel>
|
||||
</UserControl>
|
||||
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user