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

Merge branch 'development'

This commit is contained in:
Robert 2022-10-12 22:50:46 +02:00
commit 937fe1fb74
15 changed files with 235 additions and 173 deletions

View File

@ -6,8 +6,7 @@
[![Discord](https://img.shields.io/discord/392093058352676874?logo=discord&logoColor=white)](https://discord.gg/S3MVaC9) [![Discord](https://img.shields.io/discord/392093058352676874?logo=discord&logoColor=white)](https://discord.gg/S3MVaC9)
[![Donate](https://img.shields.io/badge/Donate-PayPal-green.svg)](https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=VQBAEJYUFLU4J) [![Donate](https://img.shields.io/badge/Donate-PayPal-green.svg)](https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=VQBAEJYUFLU4J)
Artemis 2 adds highly configurable support for several games to a range of RGB keyboards, mice and headsets. Artemis adds highly configurable support for several games to a range of RGB keyboards, mice and headsets.
Artemis 1 is no longer supported and Artemis 2 is in active development. This entire readme and all websites/documents refer to Artemis 2.
### Check out our [Wiki](https://wiki.artemis-rgb.com) and more specifically, the [getting started guide](https://wiki.artemis-rgb.com/en/guides/user). ### Check out our [Wiki](https://wiki.artemis-rgb.com) and more specifically, the [getting started guide](https://wiki.artemis-rgb.com/en/guides/user).
**Pre-release download**: https://github.com/SpoinkyNL/Artemis/releases (pre-release means your profiles may break at any given time!) **Pre-release download**: https://github.com/SpoinkyNL/Artemis/releases (pre-release means your profiles may break at any given time!)
@ -31,23 +30,17 @@ dotnet build .\Artemis\src\Artemis.sln
dotnet build .\Artemis.Plugins\src\Artemis.Plugins.sln dotnet build .\Artemis.Plugins\src\Artemis.Plugins.sln
``` ```
For an up-to-date overview of what's currently being worked on, see the [Projects](https://github.com/SpoinkyNL/Artemis/projects) page For an up-to-date overview of what's currently being worked on, see the [Projects](https://github.com/Artemis-RGB/Artemis/projects?type=classic) page
## Plugin development ## Plugin development
While Artemis 2 is still in development, the plugin API is pretty far along. While Artemis 2 is still in development, the plugin API is pretty far along.
To get started, you can download the Visual Studio extension in the [Visual Studio Marketplace](https://marketplace.visualstudio.com/items?itemName=SpoinkyNL.ArtemisTemplates). To get started, you can download the .NET templates, check out the [Artemis.Plugins.Templates](https://github.com/Artemis-RGB/Artemis.Plugins.Templates) repository.
This extension provides you with interactive template projects for each type of Artemis 2 plugin.
To use the templates you must have Artemis 2 extracted somewhere on your drive. The plugin wizard will ask you to locate the exectuable in order to setup all the required project references for you.
Due to the volatine nature of the project right now, there is no documentation yet. The templates provide some commentary to get you going and feel free to ask for more help in Discord. Due to the volatine nature of the project right now, there is no documentation yet. The templates provide some commentary to get you going and feel free to ask for more help in Discord.
### Third party plugins ### Third party plugins
A few people have already started working on plugins! If you want your plugins to be added to this list feel free to PR or ask on Discord. A few people have already started working on plugins! We're keeping a list on our wiki
- https://github.com/diogotr7/Artemis.Plugins https://wiki.artemis-rgb.com/en/guides/user/plugins
- https://github.com/Wibble199/Artemis.Plugins
- https://github.com/F-Lehmann/MyArtemisPlugins
- https://github.com/Cheerpipe/Artemis.Plugins.Public
## Work in progress screenshots ## Work in progress screenshots
**Note:** Video tutorials and written guides on many of the features displayed below are planned for when Artemis 2 nears feature-completion. **Note:** Video tutorials and written guides on many of the features displayed below are planned for when Artemis 2 nears feature-completion.
@ -55,7 +48,8 @@ A few people have already started working on plugins! If you want your plugins t
![Surface editor](https://wiki.artemis-rgb.com/screenshots/surface-editor.png) ![Surface editor](https://wiki.artemis-rgb.com/screenshots/surface-editor.png)
_The surface editor allows you to recreate your desktop in 2D space, this provides Artemis with spatial awareness and ensures effects scale properly over your different devices. Right clicking a device lets you change its properties such as rotation and scale._ _The surface editor allows you to recreate your desktop in 2D space, this provides Artemis with spatial awareness and ensures effects scale properly over your different devices. Right clicking a device lets you change its properties such as rotation and scale._
![Keyframes](http://artemis-rgb.com/github/sSEvdAXeTQ.gif) [profile-editor.webm](https://user-images.githubusercontent.com/8858506/194389752-7cf1b69a-8ee5-478a-a404-0ef08532c28e.webm)
_With the keyframe engine you can animate almost any property of the layer. In the example above the position and scale of the shape have been animated using keyframes._ _With the keyframe engine you can animate almost any property of the layer. In the example above the position and scale of the shape have been animated using keyframes._
For more screenshots check out the wiki: https://wiki.artemis-rgb.com/en/screenshots For more screenshots check out the wiki: https://wiki.artemis-rgb.com/en/screenshots
@ -63,5 +57,4 @@ For more screenshots check out the wiki: https://wiki.artemis-rgb.com/en/screens
### Special thanks ### Special thanks
Over the years several companies have supported Artemis by providing both hardware and software, thank you! Over the years several companies have supported Artemis by providing both hardware and software, thank you!
[![Corsair](https://i.imgur.com/UKUdDOy.png)](https://www.corsair.com/) [![Corsair](https://i.imgur.com/UKUdDOy.png)](https://www.corsair.com/)
[![JetBrains](https://i.imgur.com/JYfXjjB.png)](https://www.jetbrains.com/?from=ArtemisRGB)
[![Wooting](https://i.imgur.com/Zh3bVza.png)](https://wooting.io/) [![Wooting](https://i.imgur.com/Zh3bVza.png)](https://wooting.io/)

View File

@ -35,7 +35,7 @@
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="EmbedIO" Version="3.4.3" /> <PackageReference Include="EmbedIO" Version="3.5.0" />
<PackageReference Include="HidSharp" Version="2.1.0" /> <PackageReference Include="HidSharp" Version="2.1.0" />
<PackageReference Include="Humanizer.Core" Version="2.14.1" /> <PackageReference Include="Humanizer.Core" Version="2.14.1" />
<PackageReference Include="LiteDB" Version="5.0.12" /> <PackageReference Include="LiteDB" Version="5.0.12" />
@ -56,6 +56,7 @@
<PackageReference Include="System.IO.FileSystem.AccessControl" Version="5.0.0" /> <PackageReference Include="System.IO.FileSystem.AccessControl" Version="5.0.0" />
<PackageReference Include="System.Numerics.Vectors" Version="4.5.0" /> <PackageReference Include="System.Numerics.Vectors" Version="4.5.0" />
<PackageReference Include="System.ValueTuple" Version="4.5.0" /> <PackageReference Include="System.ValueTuple" Version="4.5.0" />
<PackageReference Include="Unosquare.Swan.Lite" Version="3.1.0" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>

View File

@ -0,0 +1,19 @@
using System;
namespace Artemis.Core;
/// <summary>
/// Provides data for profile configuration events.
/// </summary>
public class ProfileCategoryEventArgs : EventArgs
{
internal ProfileCategoryEventArgs(ProfileCategory profileCategory)
{
ProfileCategory = profileCategory;
}
/// <summary>
/// Gets the profile category this event is related to
/// </summary>
public ProfileCategory ProfileCategory { get; }
}

View File

@ -20,9 +20,11 @@ public class ProfileCategory : CorePropertyChanged, IStorageModel
/// Creates a new instance of the <see cref="ProfileCategory" /> class /// Creates a new instance of the <see cref="ProfileCategory" /> class
/// </summary> /// </summary>
/// <param name="name">The name of the category</param> /// <param name="name">The name of the category</param>
internal ProfileCategory(string name) /// <param name="order">The order of the category</param>
internal ProfileCategory(string name, int order)
{ {
_name = name; _name = name;
_order = order;
Entity = new ProfileCategoryEntity(); Entity = new ProfileCategoryEntity();
ProfileConfigurations = new ReadOnlyCollection<ProfileConfiguration>(_profileConfigurations); ProfileConfigurations = new ReadOnlyCollection<ProfileConfiguration>(_profileConfigurations);
} }

View File

@ -6,135 +6,135 @@ using SkiaSharp;
namespace Artemis.Core.Services; namespace Artemis.Core.Services;
/// <summary> /// <summary>
/// Provides access to profile storage and is responsible for activating default profiles /// Provides access to profile storage and is responsible for activating default profiles.
/// </summary> /// </summary>
public interface IProfileService : IArtemisService public interface IProfileService : IArtemisService
{ {
/// <summary> /// <summary>
/// Gets the JSON serializer settings used to import/export profiles /// Gets the JSON serializer settings used to import/export profiles.
/// </summary> /// </summary>
public static JsonSerializerSettings ExportSettings { get; } = new() {TypeNameHandling = TypeNameHandling.All, Formatting = Formatting.Indented}; public static JsonSerializerSettings ExportSettings { get; } = new() {TypeNameHandling = TypeNameHandling.All, Formatting = Formatting.Indented};
/// <summary> /// <summary>
/// Gets a read only collection containing all the profile categories /// Gets a read only collection containing all the profile categories.
/// </summary> /// </summary>
ReadOnlyCollection<ProfileCategory> ProfileCategories { get; } ReadOnlyCollection<ProfileCategory> ProfileCategories { get; }
/// <summary> /// <summary>
/// Gets a read only collection containing all the profile configurations /// Gets a read only collection containing all the profile configurations.
/// </summary> /// </summary>
ReadOnlyCollection<ProfileConfiguration> ProfileConfigurations { get; } ReadOnlyCollection<ProfileConfiguration> ProfileConfigurations { get; }
/// <summary> /// <summary>
/// Gets or sets a boolean indicating whether hotkeys are enabled /// Gets or sets a boolean indicating whether hotkeys are enabled.
/// </summary> /// </summary>
bool HotkeysEnabled { get; set; } bool HotkeysEnabled { get; set; }
/// <summary> /// <summary>
/// Gets or sets a boolean indicating whether rendering should only be done for profiles being edited /// Gets or sets a boolean indicating whether rendering should only be done for profiles being edited.
/// </summary> /// </summary>
bool RenderForEditor { get; set; } bool RenderForEditor { get; set; }
/// <summary> /// <summary>
/// Gets or sets the profile element to focus on while rendering for the editor /// Gets or sets the profile element to focus on while rendering for the editor.
/// </summary> /// </summary>
ProfileElement? EditorFocus { get; set; } ProfileElement? EditorFocus { get; set; }
/// <summary> /// <summary>
/// Activates the profile of the given <see cref="ProfileConfiguration" /> with the currently active surface /// Activates the profile of the given <see cref="ProfileConfiguration" /> with the currently active surface.
/// </summary> /// </summary>
/// <param name="profileConfiguration">The profile configuration of the profile to activate</param> /// <param name="profileConfiguration">The profile configuration of the profile to activate.</param>
Profile ActivateProfile(ProfileConfiguration profileConfiguration); Profile ActivateProfile(ProfileConfiguration profileConfiguration);
/// <summary> /// <summary>
/// Deactivates the profile of the given <see cref="ProfileConfiguration" /> with the currently active surface /// Deactivates the profile of the given <see cref="ProfileConfiguration" /> with the currently active surface.
/// </summary> /// </summary>
/// <param name="profileConfiguration">The profile configuration of the profile to activate</param> /// <param name="profileConfiguration">The profile configuration of the profile to activate.</param>
void DeactivateProfile(ProfileConfiguration profileConfiguration); void DeactivateProfile(ProfileConfiguration profileConfiguration);
/// <summary> /// <summary>
/// Permanently deletes the profile of the given <see cref="ProfileConfiguration" /> /// Permanently deletes the profile of the given <see cref="ProfileConfiguration" />.
/// </summary> /// </summary>
/// <param name="profileConfiguration">The profile configuration of the profile to delete</param> /// <param name="profileConfiguration">The profile configuration of the profile to delete.</param>
void DeleteProfile(ProfileConfiguration profileConfiguration); void DeleteProfile(ProfileConfiguration profileConfiguration);
/// <summary> /// <summary>
/// Saves the provided <see cref="ProfileCategory" /> and it's <see cref="ProfileConfiguration" />s but not the /// Saves the provided <see cref="ProfileCategory" /> and it's <see cref="ProfileConfiguration" />s but not the
/// <see cref="Profile" />s themselves /// <see cref="Profile" />s themselves.
/// </summary> /// </summary>
/// <param name="profileCategory">The profile category to update</param> /// <param name="profileCategory">The profile category to update.</param>
void SaveProfileCategory(ProfileCategory profileCategory); void SaveProfileCategory(ProfileCategory profileCategory);
/// <summary> /// <summary>
/// Creates a new profile category and saves it to persistent storage /// Creates a new profile category and saves it to persistent storage.
/// </summary> /// </summary>
/// <param name="name">The name of the new profile category, must be unique</param> /// <param name="name">The name of the new profile category, must be unique.</param>
/// <returns>The newly created profile category</returns> /// <returns>The newly created profile category.</returns>
ProfileCategory CreateProfileCategory(string name); ProfileCategory CreateProfileCategory(string name);
/// <summary> /// <summary>
/// Permanently deletes the provided profile category /// Permanently deletes the provided profile category.
/// </summary> /// </summary>
void DeleteProfileCategory(ProfileCategory profileCategory); void DeleteProfileCategory(ProfileCategory profileCategory);
/// <summary> /// <summary>
/// Creates a new profile configuration and adds it to the provided <see cref="ProfileCategory" /> /// Creates a new profile configuration and adds it to the provided <see cref="ProfileCategory" />.
/// </summary> /// </summary>
/// <param name="category">The profile category to add the profile to</param> /// <param name="category">The profile category to add the profile to.</param>
/// <param name="name">The name of the new profile configuration</param> /// <param name="name">The name of the new profile configuration.</param>
/// <param name="icon">The icon of the new profile configuration</param> /// <param name="icon">The icon of the new profile configuration.</param>
/// <returns>The newly created profile configuration</returns> /// <returns>The newly created profile configuration.</returns>
ProfileConfiguration CreateProfileConfiguration(ProfileCategory category, string name, string icon); ProfileConfiguration CreateProfileConfiguration(ProfileCategory category, string name, string icon);
/// <summary> /// <summary>
/// Removes the provided profile configuration from the <see cref="ProfileCategory" /> /// Removes the provided profile configuration from the <see cref="ProfileCategory" />.
/// </summary> /// </summary>
/// <param name="profileConfiguration"></param> /// <param name="profileConfiguration"></param>
void RemoveProfileConfiguration(ProfileConfiguration profileConfiguration); void RemoveProfileConfiguration(ProfileConfiguration profileConfiguration);
/// <summary> /// <summary>
/// Loads the icon of this profile configuration if needed and puts it into <c>ProfileConfiguration.Icon.FileIcon</c> /// Loads the icon of this profile configuration if needed and puts it into <c>ProfileConfiguration.Icon.FileIcon</c>.
/// </summary> /// </summary>
void LoadProfileConfigurationIcon(ProfileConfiguration profileConfiguration); void LoadProfileConfigurationIcon(ProfileConfiguration profileConfiguration);
/// <summary> /// <summary>
/// Saves the current icon of this profile /// Saves the current icon of this profile.
/// </summary> /// </summary>
void SaveProfileConfigurationIcon(ProfileConfiguration profileConfiguration); void SaveProfileConfigurationIcon(ProfileConfiguration profileConfiguration);
/// <summary> /// <summary>
/// Writes the profile to persistent storage /// Writes the profile to persistent storage.
/// </summary> /// </summary>
/// <param name="profile"></param> /// <param name="profile"></param>
/// <param name="includeChildren"></param> /// <param name="includeChildren"></param>
void SaveProfile(Profile profile, bool includeChildren); void SaveProfile(Profile profile, bool includeChildren);
/// <summary> /// <summary>
/// Exports the profile described in the given <see cref="ProfileConfiguration" /> into an export model /// Exports the profile described in the given <see cref="ProfileConfiguration" /> into an export model.
/// </summary> /// </summary>
/// <param name="profileConfiguration">The profile configuration of the profile to export</param> /// <param name="profileConfiguration">The profile configuration of the profile to export.</param>
/// <returns>The resulting export model</returns> /// <returns>The resulting export model.</returns>
ProfileConfigurationExportModel ExportProfile(ProfileConfiguration profileConfiguration); ProfileConfigurationExportModel ExportProfile(ProfileConfiguration profileConfiguration);
/// <summary> /// <summary>
/// Imports the provided base64 encoded GZIPed JSON as a profile configuration /// Imports the provided base64 encoded GZIPed JSON as a profile configuration.
/// </summary> /// </summary>
/// <param name="category">The <see cref="ProfileCategory" /> in which to import the profile</param> /// <param name="category">The <see cref="ProfileCategory" /> in which to import the profile.</param>
/// <param name="exportModel">The model containing the profile to import</param> /// <param name="exportModel">The model containing the profile to import.</param>
/// <param name="makeUnique">Whether or not to give the profile a new GUID, making it unique</param> /// <param name="makeUnique">Whether or not to give the profile a new GUID, making it unique.</param>
/// <param name="markAsFreshImport"> /// <param name="markAsFreshImport">
/// Whether or not to mark the profile as a fresh import, causing it to be adapted until /// Whether or not to mark the profile as a fresh import, causing it to be adapted until
/// any changes are made to it /// any changes are made to it.
/// </param> /// </param>
/// <param name="nameAffix">Text to add after the name of the profile (separated by a dash)</param> /// <param name="nameAffix">Text to add after the name of the profile (separated by a dash).</param>
/// <returns>The resulting profile configuration</returns> /// <returns>The resulting profile configuration.</returns>
ProfileConfiguration ImportProfile(ProfileCategory category, ProfileConfigurationExportModel exportModel, bool makeUnique = true, bool markAsFreshImport = true, ProfileConfiguration ImportProfile(ProfileCategory category, ProfileConfigurationExportModel exportModel, bool makeUnique = true, bool markAsFreshImport = true,
string? nameAffix = "imported"); string? nameAffix = "imported");
/// <summary> /// <summary>
/// Adapts a given profile to the currently active devices /// Adapts a given profile to the currently active devices.
/// </summary> /// </summary>
/// <param name="profile">The profile to adapt</param> /// <param name="profile">The profile to adapt.</param>
void AdaptProfile(Profile profile); void AdaptProfile(Profile profile);
/// <summary> /// <summary>
@ -143,18 +143,28 @@ public interface IProfileService : IArtemisService
void UpdateProfiles(double deltaTime); void UpdateProfiles(double deltaTime);
/// <summary> /// <summary>
/// Renders all currently active profiles /// Renders all currently active profiles.
/// </summary> /// </summary>
/// <param name="canvas"></param> /// <param name="canvas"></param>
void RenderProfiles(SKCanvas canvas); void RenderProfiles(SKCanvas canvas);
/// <summary> /// <summary>
/// Occurs whenever a profile has been activated /// Occurs whenever a profile has been activated.
/// </summary> /// </summary>
public event EventHandler<ProfileConfigurationEventArgs>? ProfileActivated; public event EventHandler<ProfileConfigurationEventArgs>? ProfileActivated;
/// <summary> /// <summary>
/// Occurs whenever a profile has been deactivated /// Occurs whenever a profile has been deactivated.
/// </summary> /// </summary>
public event EventHandler<ProfileConfigurationEventArgs>? ProfileDeactivated; public event EventHandler<ProfileConfigurationEventArgs>? ProfileDeactivated;
/// <summary>
/// Occurs whenever a profile category is added.
/// </summary>
public event EventHandler<ProfileCategoryEventArgs>? ProfileCategoryAdded;
/// <summary>
/// Occurs whenever a profile category is removed.
/// </summary>
public event EventHandler<ProfileCategoryEventArgs>? ProfileCategoryRemoved;
} }

View File

@ -53,16 +53,6 @@ internal class ProfileService : IProfileService
UpdateModules(); UpdateModules();
} }
protected virtual void OnProfileActivated(ProfileConfigurationEventArgs e)
{
ProfileActivated?.Invoke(this, e);
}
protected virtual void OnProfileDeactivated(ProfileConfigurationEventArgs e)
{
ProfileDeactivated?.Invoke(this, e);
}
private void InputServiceOnKeyboardKeyUp(object? sender, ArtemisKeyboardKeyEventArgs e) private void InputServiceOnKeyboardKeyUp(object? sender, ArtemisKeyboardKeyEventArgs e)
{ {
if (!HotkeysEnabled) if (!HotkeysEnabled)
@ -386,13 +376,16 @@ internal class ProfileService : IProfileService
public ProfileCategory CreateProfileCategory(string name) public ProfileCategory CreateProfileCategory(string name)
{ {
ProfileCategory profileCategory;
lock (_profileRepository) lock (_profileRepository)
{ {
ProfileCategory profileCategory = new(name); profileCategory = new ProfileCategory(name, _profileCategories.Count + 1);
_profileCategories.Add(profileCategory); _profileCategories.Add(profileCategory);
SaveProfileCategory(profileCategory); SaveProfileCategory(profileCategory);
return profileCategory;
} }
OnProfileCategoryAdded(new ProfileCategoryEventArgs(profileCategory));
return profileCategory;
} }
public void DeleteProfileCategory(ProfileCategory profileCategory) public void DeleteProfileCategory(ProfileCategory profileCategory)
@ -406,6 +399,8 @@ internal class ProfileService : IProfileService
_profileCategories.Remove(profileCategory); _profileCategories.Remove(profileCategory);
_profileCategoryRepository.Remove(profileCategory.Entity); _profileCategoryRepository.Remove(profileCategory.Entity);
} }
OnProfileCategoryRemoved(new ProfileCategoryEventArgs(profileCategory));
} }
public ProfileConfiguration CreateProfileConfiguration(ProfileCategory category, string name, string icon) public ProfileConfiguration CreateProfileConfiguration(ProfileCategory category, string name, string icon)
@ -543,6 +538,32 @@ internal class ProfileService : IProfileService
_profileRepository.Save(profile.ProfileEntity); _profileRepository.Save(profile.ProfileEntity);
} }
#region Events
public event EventHandler<ProfileConfigurationEventArgs>? ProfileActivated; public event EventHandler<ProfileConfigurationEventArgs>? ProfileActivated;
public event EventHandler<ProfileConfigurationEventArgs>? ProfileDeactivated; public event EventHandler<ProfileConfigurationEventArgs>? ProfileDeactivated;
public event EventHandler<ProfileCategoryEventArgs>? ProfileCategoryAdded;
public event EventHandler<ProfileCategoryEventArgs>? ProfileCategoryRemoved;
protected virtual void OnProfileActivated(ProfileConfigurationEventArgs e)
{
ProfileActivated?.Invoke(this, e);
}
protected virtual void OnProfileDeactivated(ProfileConfigurationEventArgs e)
{
ProfileDeactivated?.Invoke(this, e);
}
protected virtual void OnProfileCategoryAdded(ProfileCategoryEventArgs e)
{
ProfileCategoryAdded?.Invoke(this, e);
}
protected virtual void OnProfileCategoryRemoved(ProfileCategoryEventArgs e)
{
ProfileCategoryRemoved?.Invoke(this, e);
}
#endregion
} }

View File

@ -1,4 +1,4 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Diagnostics; using System.Diagnostics;
using System.IO; using System.IO;
@ -16,6 +16,8 @@ namespace Artemis.UI.Windows;
public class ApplicationStateManager public class ApplicationStateManager
{ {
private const int SM_SHUTTINGDOWN = 0x2000;
public ApplicationStateManager(IKernel kernel, string[] startupArguments) public ApplicationStateManager(IKernel kernel, string[] startupArguments)
{ {
StartupArguments = startupArguments; StartupArguments = startupArguments;
@ -100,7 +102,8 @@ public class ApplicationStateManager
private void RunForcedShutdownIfEnabled() private void RunForcedShutdownIfEnabled()
{ {
if (StartupArguments.Contains("--disable-forced-shutdown")) // Don't run a forced shutdown if Windows itself is shutting down, the new PowerShell process will fail
if (GetSystemMetrics(SM_SHUTTINGDOWN) != 0 || StartupArguments.Contains("--disable-forced-shutdown"))
return; return;
ProcessStartInfo info = new() ProcessStartInfo info = new()
@ -112,4 +115,7 @@ public class ApplicationStateManager
}; };
Process.Start(info); Process.Start(info);
} }
[System.Runtime.InteropServices.DllImport("user32.dll")]
private static extern int GetSystemMetrics(int nIndex);
} }

View File

@ -14,6 +14,8 @@ namespace Artemis.UI.Windows.Providers;
public class AutoRunProvider : IAutoRunProvider public class AutoRunProvider : IAutoRunProvider
{ {
private readonly string _autorunName = $"Artemis 2 autorun {Environment.UserName}";
private readonly string _oldAutorunName = "Artemis 2 autorun";
private readonly IAssetLoader _assetLoader; private readonly IAssetLoader _assetLoader;
public AutoRunProvider(IAssetLoader assetLoader) public AutoRunProvider(IAssetLoader assetLoader)
@ -21,7 +23,7 @@ public class AutoRunProvider : IAutoRunProvider
_assetLoader = assetLoader; _assetLoader = assetLoader;
} }
private async Task<bool> IsAutoRunTaskCreated() private async Task<bool> IsAutoRunTaskCreated(string autorunName)
{ {
Process schtasks = new() Process schtasks = new()
{ {
@ -30,7 +32,7 @@ public class AutoRunProvider : IAutoRunProvider
WindowStyle = ProcessWindowStyle.Hidden, WindowStyle = ProcessWindowStyle.Hidden,
UseShellExecute = true, UseShellExecute = true,
FileName = Path.Combine(Environment.SystemDirectory, "schtasks.exe"), FileName = Path.Combine(Environment.SystemDirectory, "schtasks.exe"),
Arguments = "/TN \"Artemis 2 autorun\"" Arguments = $"/TN \"{autorunName}\""
} }
}; };
@ -39,7 +41,7 @@ public class AutoRunProvider : IAutoRunProvider
return schtasks.ExitCode == 0; return schtasks.ExitCode == 0;
} }
private async Task CreateAutoRunTask(TimeSpan autoRunDelay) private async Task CreateAutoRunTask(TimeSpan autoRunDelay, string autorunName)
{ {
await using Stream taskFile = _assetLoader.Open(new Uri("avares://Artemis.UI.Windows/Assets/autorun.xml")); await using Stream taskFile = _assetLoader.Open(new Uri("avares://Artemis.UI.Windows/Assets/autorun.xml"));
@ -53,6 +55,8 @@ public class AutoRunProvider : IAutoRunProvider
task.Descendants().First(d => d.Name.LocalName == "Triggers").Descendants().First(d => d.Name.LocalName == "LogonTrigger").Descendants().First(d => d.Name.LocalName == "Delay") task.Descendants().First(d => d.Name.LocalName == "Triggers").Descendants().First(d => d.Name.LocalName == "LogonTrigger").Descendants().First(d => d.Name.LocalName == "Delay")
.SetValue(autoRunDelay); .SetValue(autoRunDelay);
task.Descendants().First(d => d.Name.LocalName == "Triggers").Descendants().First(d => d.Name.LocalName == "LogonTrigger").Descendants().First(d => d.Name.LocalName == "UserId")
.SetValue(WindowsIdentity.GetCurrent().Name);
task.Descendants().First(d => d.Name.LocalName == "Principals").Descendants().First(d => d.Name.LocalName == "Principal").Descendants().First(d => d.Name.LocalName == "UserId") task.Descendants().First(d => d.Name.LocalName == "Principals").Descendants().First(d => d.Name.LocalName == "Principal").Descendants().First(d => d.Name.LocalName == "UserId")
.SetValue(WindowsIdentity.GetCurrent().User!.Value); .SetValue(WindowsIdentity.GetCurrent().User!.Value);
@ -76,7 +80,7 @@ public class AutoRunProvider : IAutoRunProvider
UseShellExecute = true, UseShellExecute = true,
Verb = "runas", Verb = "runas",
FileName = Path.Combine(Environment.SystemDirectory, "schtasks.exe"), FileName = Path.Combine(Environment.SystemDirectory, "schtasks.exe"),
Arguments = $"/Create /XML \"{xmlPath}\" /tn \"Artemis 2 autorun\" /F" Arguments = $"/Create /XML \"{xmlPath}\" /tn \"{autorunName}\" /F"
} }
}; };
@ -86,7 +90,7 @@ public class AutoRunProvider : IAutoRunProvider
File.Delete(xmlPath); File.Delete(xmlPath);
} }
private async Task RemoveAutoRunTask() private async Task RemoveAutoRunTask(string autorunName)
{ {
Process schtasks = new() Process schtasks = new()
{ {
@ -96,7 +100,7 @@ public class AutoRunProvider : IAutoRunProvider
UseShellExecute = true, UseShellExecute = true,
Verb = "runas", Verb = "runas",
FileName = Path.Combine(Environment.SystemDirectory, "schtasks.exe"), FileName = Path.Combine(Environment.SystemDirectory, "schtasks.exe"),
Arguments = "/Delete /TN \"Artemis 2 autorun\" /f" Arguments = $"/Delete /TN \"{autorunName}\" /f"
} }
}; };
@ -107,22 +111,31 @@ public class AutoRunProvider : IAutoRunProvider
/// <inheritdoc /> /// <inheritdoc />
public async Task EnableAutoRun(bool recreate, int autoRunDelay) public async Task EnableAutoRun(bool recreate, int autoRunDelay)
{ {
// if (Constants.BuildInfo.IsLocalBuild) if (Constants.BuildInfo.IsLocalBuild)
// return; return;
await CleanupOldAutorun();
// Create or remove the task if necessary // Create or remove the task if necessary
bool taskCreated = false; bool taskCreated = false;
if (!recreate) if (!recreate)
taskCreated = await IsAutoRunTaskCreated(); taskCreated = await IsAutoRunTaskCreated(_autorunName);
if (!taskCreated) if (!taskCreated)
await CreateAutoRunTask(TimeSpan.FromSeconds(autoRunDelay)); await CreateAutoRunTask(TimeSpan.FromSeconds(autoRunDelay), _autorunName);
} }
/// <inheritdoc /> /// <inheritdoc />
public async Task DisableAutoRun() public async Task DisableAutoRun()
{ {
bool taskCreated = await IsAutoRunTaskCreated(); bool taskCreated = await IsAutoRunTaskCreated(_autorunName);
if (taskCreated) if (taskCreated)
await RemoveAutoRunTask(); await RemoveAutoRunTask(_autorunName);
}
private async Task CleanupOldAutorun()
{
bool taskCreated = await IsAutoRunTaskCreated(_oldAutorunName);
if (taskCreated)
await RemoveAutoRunTask(_oldAutorunName);
} }
} }

View File

@ -50,8 +50,8 @@ public interface ISettingsVmFactory : IVmFactory
public interface ISidebarVmFactory : IVmFactory public interface ISidebarVmFactory : IVmFactory
{ {
SidebarViewModel? SidebarViewModel(IScreen hostScreen); SidebarViewModel? SidebarViewModel(IScreen hostScreen);
SidebarCategoryViewModel SidebarCategoryViewModel(SidebarViewModel sidebarViewModel, ProfileCategory profileCategory); SidebarCategoryViewModel SidebarCategoryViewModel(ProfileCategory profileCategory);
SidebarProfileConfigurationViewModel SidebarProfileConfigurationViewModel(SidebarViewModel sidebarViewModel, ProfileConfiguration profileConfiguration); SidebarProfileConfigurationViewModel SidebarProfileConfigurationViewModel(ProfileConfiguration profileConfiguration);
} }
public interface ISurfaceVmFactory : IVmFactory public interface ISurfaceVmFactory : IVmFactory

View File

@ -1,4 +1,5 @@
using System.Reactive; using System.Linq;
using System.Reactive;
using Artemis.Core; using Artemis.Core;
using Artemis.Core.Services; using Artemis.Core.Services;
using Artemis.UI.Shared; using Artemis.UI.Shared;
@ -23,7 +24,8 @@ public class SidebarCategoryEditViewModel : ContentDialogViewModelBase
_categoryName = _category.Name; _categoryName = _category.Name;
Confirm = ReactiveCommand.Create(ExecuteConfirm, ValidationContext.Valid); Confirm = ReactiveCommand.Create(ExecuteConfirm, ValidationContext.Valid);
this.ValidationRule(vm => vm.CategoryName, categoryName => !string.IsNullOrWhiteSpace(categoryName), "You must specify a valid name"); this.ValidationRule(vm => vm.CategoryName, categoryName => !string.IsNullOrWhiteSpace(categoryName?.Trim()), "You must specify a valid name");
this.ValidationRule(vm => vm.CategoryName, categoryName => profileService.ProfileCategories.All(c => c.Name != categoryName?.Trim()), "You must specify a unique name");
} }
public string? CategoryName public string? CategoryName
@ -38,12 +40,12 @@ public class SidebarCategoryEditViewModel : ContentDialogViewModelBase
{ {
if (_category != null) if (_category != null)
{ {
_category.Name = CategoryName!; _category.Name = CategoryName!.Trim();
_profileService.SaveProfileCategory(_category); _profileService.SaveProfileCategory(_category);
} }
else else
{ {
_profileService.CreateProfileCategory(CategoryName!); _profileService.CreateProfileCategory(CategoryName!.Trim());
} }
ContentDialog?.Hide(ContentDialogResult.Primary); ContentDialog?.Hide(ContentDialogResult.Primary);

View File

@ -24,17 +24,18 @@ namespace Artemis.UI.Screens.Sidebar;
public class SidebarCategoryViewModel : ActivatableViewModelBase public class SidebarCategoryViewModel : ActivatableViewModelBase
{ {
private readonly IProfileService _profileService; private readonly IProfileService _profileService;
private readonly SidebarViewModel _sidebarViewModel;
private readonly ISidebarVmFactory _vmFactory; private readonly ISidebarVmFactory _vmFactory;
private readonly IWindowService _windowService; private readonly IWindowService _windowService;
private ObservableAsPropertyHelper<bool>? _isCollapsed; private ObservableAsPropertyHelper<bool>? _isCollapsed;
private ObservableAsPropertyHelper<bool>? _isSuspended; private ObservableAsPropertyHelper<bool>? _isSuspended;
private SidebarProfileConfigurationViewModel? _selectedProfileConfiguration; private SidebarProfileConfigurationViewModel? _selectedProfileConfiguration;
public SidebarCategoryViewModel(SidebarViewModel sidebarViewModel, ProfileCategory profileCategory, IProfileService profileService, IWindowService windowService, public SidebarCategoryViewModel(ProfileCategory profileCategory,
IProfileEditorService profileEditorService, ISidebarVmFactory vmFactory) IProfileService profileService,
IWindowService windowService,
IProfileEditorService profileEditorService,
ISidebarVmFactory vmFactory)
{ {
_sidebarViewModel = sidebarViewModel;
_profileService = profileService; _profileService = profileService;
_windowService = windowService; _windowService = windowService;
_vmFactory = vmFactory; _vmFactory = vmFactory;
@ -47,7 +48,7 @@ public class SidebarCategoryViewModel : ActivatableViewModelBase
profileConfigurations.Connect() profileConfigurations.Connect()
.Filter(profileConfigurationsFilter) .Filter(profileConfigurationsFilter)
.Sort(SortExpressionComparer<ProfileConfiguration>.Ascending(c => c.Order)) .Sort(SortExpressionComparer<ProfileConfiguration>.Ascending(c => c.Order))
.Transform(c => _vmFactory.SidebarProfileConfigurationViewModel(_sidebarViewModel, c)) .Transform(c => _vmFactory.SidebarProfileConfigurationViewModel(c))
.Bind(out ReadOnlyObservableCollection<SidebarProfileConfigurationViewModel> profileConfigurationViewModels) .Bind(out ReadOnlyObservableCollection<SidebarProfileConfigurationViewModel> profileConfigurationViewModels)
.Subscribe(); .Subscribe();
ProfileConfigurations = profileConfigurationViewModels; ProfileConfigurations = profileConfigurationViewModels;
@ -93,7 +94,7 @@ public class SidebarCategoryViewModel : ActivatableViewModelBase
_windowService.ShowExceptionDialog(s.ProfileConfiguration.BrokenState, s.ProfileConfiguration.BrokenStateException); _windowService.ShowExceptionDialog(s.ProfileConfiguration.BrokenState, s.ProfileConfiguration.BrokenStateException);
else else
_windowService.ShowExceptionDialog(e.Message, e); _windowService.ShowExceptionDialog(e.Message, e);
profileEditorService.ChangeCurrentProfileConfiguration(null); profileEditorService.ChangeCurrentProfileConfiguration(null);
SelectedProfileConfiguration = null; SelectedProfileConfiguration = null;
} }
@ -148,8 +149,6 @@ public class SidebarCategoryViewModel : ActivatableViewModelBase
.WithCloseButtonText("Cancel") .WithCloseButtonText("Cancel")
.WithDefaultButton(ContentDialogButton.Primary) .WithDefaultButton(ContentDialogButton.Primary)
.ShowAsync(); .ShowAsync();
_sidebarViewModel.UpdateProfileCategories();
} }
private async Task ExecuteDeleteCategory() private async Task ExecuteDeleteCategory()
@ -166,7 +165,7 @@ public class SidebarCategoryViewModel : ActivatableViewModelBase
); );
if (result != null) if (result != null)
{ {
SidebarProfileConfigurationViewModel viewModel = _vmFactory.SidebarProfileConfigurationViewModel(_sidebarViewModel, result); SidebarProfileConfigurationViewModel viewModel = _vmFactory.SidebarProfileConfigurationViewModel(result);
SelectedProfileConfiguration = viewModel; SelectedProfileConfiguration = viewModel;
} }
} }
@ -230,8 +229,6 @@ public class SidebarCategoryViewModel : ActivatableViewModelBase
ProfileCategory.Order--; ProfileCategory.Order--;
_profileService.SaveProfileCategory(categories[index - 1]); _profileService.SaveProfileCategory(categories[index - 1]);
_profileService.SaveProfileCategory(ProfileCategory); _profileService.SaveProfileCategory(ProfileCategory);
_sidebarViewModel.UpdateProfileCategories();
} }
private void ExecuteMoveDown() private void ExecuteMoveDown()
@ -245,7 +242,5 @@ public class SidebarCategoryViewModel : ActivatableViewModelBase
ProfileCategory.Order++; ProfileCategory.Order++;
_profileService.SaveProfileCategory(categories[index + 1]); _profileService.SaveProfileCategory(categories[index + 1]);
_profileService.SaveProfileCategory(ProfileCategory); _profileService.SaveProfileCategory(ProfileCategory);
_sidebarViewModel.UpdateProfileCategories();
} }
} }

View File

@ -19,17 +19,11 @@ public class SidebarProfileConfigurationViewModel : ActivatableViewModelBase
{ {
private readonly IProfileEditorService _profileEditorService; private readonly IProfileEditorService _profileEditorService;
private readonly IProfileService _profileService; private readonly IProfileService _profileService;
private readonly SidebarViewModel _sidebarViewModel;
private readonly IWindowService _windowService; private readonly IWindowService _windowService;
private ObservableAsPropertyHelper<bool>? _isDisabled; private ObservableAsPropertyHelper<bool>? _isDisabled;
public SidebarProfileConfigurationViewModel(SidebarViewModel sidebarViewModel, public SidebarProfileConfigurationViewModel(ProfileConfiguration profileConfiguration, IProfileService profileService, IProfileEditorService profileEditorService, IWindowService windowService)
ProfileConfiguration profileConfiguration,
IProfileService profileService,
IProfileEditorService profileEditorService,
IWindowService windowService)
{ {
_sidebarViewModel = sidebarViewModel;
_profileService = profileService; _profileService = profileService;
_profileEditorService = profileEditorService; _profileEditorService = profileEditorService;
_windowService = windowService; _windowService = windowService;
@ -63,13 +57,10 @@ public class SidebarProfileConfigurationViewModel : ActivatableViewModelBase
private async Task ExecuteEditProfile() private async Task ExecuteEditProfile()
{ {
ProfileConfiguration? edited = await _windowService.ShowDialogAsync<ProfileConfigurationEditViewModel, ProfileConfiguration?>( await _windowService.ShowDialogAsync<ProfileConfigurationEditViewModel, ProfileConfiguration?>(
("profileCategory", ProfileConfiguration.Category), ("profileCategory", ProfileConfiguration.Category),
("profileConfiguration", ProfileConfiguration) ("profileConfiguration", ProfileConfiguration)
); );
if (edited != null)
_sidebarViewModel.UpdateProfileCategories();
} }
private void ExecuteToggleSuspended() private void ExecuteToggleSuspended()

View File

@ -4,8 +4,10 @@
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:avalonia="clr-namespace:Material.Icons.Avalonia;assembly=Material.Icons.Avalonia" xmlns:avalonia="clr-namespace:Material.Icons.Avalonia;assembly=Material.Icons.Avalonia"
xmlns:controls="clr-namespace:FluentAvalonia.UI.Controls;assembly=FluentAvalonia" xmlns:controls="clr-namespace:FluentAvalonia.UI.Controls;assembly=FluentAvalonia"
xmlns:sidebar="clr-namespace:Artemis.UI.Screens.Sidebar"
mc:Ignorable="d" d:DesignWidth="240" d:DesignHeight="450" mc:Ignorable="d" d:DesignWidth="240" d:DesignHeight="450"
x:Class="Artemis.UI.Screens.Sidebar.SidebarView"> x:Class="Artemis.UI.Screens.Sidebar.SidebarView"
x:DataType="sidebar:SidebarViewModel">
<UserControl.Styles> <UserControl.Styles>
<StyleInclude Source="avares://Avalonia.Xaml.Interactions/Draggable/Styles.axaml" /> <StyleInclude Source="avares://Avalonia.Xaml.Interactions/Draggable/Styles.axaml" />
</UserControl.Styles> </UserControl.Styles>
@ -24,18 +26,18 @@
<ListBox Classes="sidebar-listbox" <ListBox Classes="sidebar-listbox"
Grid.Row="1" Grid.Row="1"
Margin="10 2" Margin="10 2"
Items="{Binding SidebarScreens}" Items="{CompiledBinding SidebarScreens}"
SelectedItem="{Binding SelectedSidebarScreen}" /> SelectedItem="{CompiledBinding SelectedSidebarScreen}" />
<Separator Grid.Row="2" Margin="8" Height="1" Background="#FF6c6c6c" /> <Separator Grid.Row="2" Margin="8" Height="1" Background="#FF6c6c6c" />
<!-- Categories --> <!-- Categories -->
<ScrollViewer Grid.Row="3" VerticalScrollBarVisibility="Auto"> <ScrollViewer Grid.Row="3" VerticalScrollBarVisibility="Auto">
<StackPanel> <StackPanel>
<ItemsControl Margin="10 2" Items="{Binding SidebarCategories}" Classes="profile-categories" /> <ItemsControl Margin="10 2" Items="{CompiledBinding SidebarCategories}" Classes="profile-categories" />
<Button Content="Add new category" <Button Content="Add new category"
Margin="10" Margin="10"
HorizontalAlignment="Stretch" HorizontalAlignment="Stretch"
Command="{Binding AddCategory}" /> Command="{CompiledBinding AddCategory}" />
</StackPanel> </StackPanel>
</ScrollViewer> </ScrollViewer>

View File

@ -1,7 +1,9 @@
using System; using System;
using System.Collections.ObjectModel; using System.Collections.ObjectModel;
using System.Linq; using System.Linq;
using System.Reactive;
using System.Reactive.Disposables; using System.Reactive.Disposables;
using System.Reactive.Linq;
using System.Threading.Tasks; using System.Threading.Tasks;
using Artemis.Core; using Artemis.Core;
using Artemis.Core.Services; using Artemis.Core.Services;
@ -15,35 +17,38 @@ using Artemis.UI.Shared;
using Artemis.UI.Shared.Services; using Artemis.UI.Shared.Services;
using Artemis.UI.Shared.Services.Builders; using Artemis.UI.Shared.Services.Builders;
using Artemis.UI.Shared.Services.ProfileEditor; using Artemis.UI.Shared.Services.ProfileEditor;
using Avalonia.Threading;
using DynamicData;
using DynamicData.Binding;
using Material.Icons; using Material.Icons;
using Ninject; using Ninject;
using ReactiveUI; using ReactiveUI;
using RGB.NET.Core;
namespace Artemis.UI.Screens.Sidebar; namespace Artemis.UI.Screens.Sidebar;
public class SidebarViewModel : ActivatableViewModelBase public class SidebarViewModel : ActivatableViewModelBase
{ {
private readonly IScreen _hostScreen; private readonly IScreen _hostScreen;
private readonly IKernel _kernel;
private readonly IProfileEditorService _profileEditorService; private readonly IProfileEditorService _profileEditorService;
private readonly IProfileService _profileService; private readonly IProfileEditorVmFactory _profileEditorVmFactory;
private readonly IRgbService _rgbService;
private readonly ISidebarVmFactory _sidebarVmFactory;
private readonly IWindowService _windowService; private readonly IWindowService _windowService;
private ArtemisDevice? _headerDevice;
private SidebarScreenViewModel? _selectedSidebarScreen; private SidebarScreenViewModel? _selectedSidebarScreen;
private ReadOnlyObservableCollection<SidebarCategoryViewModel> _sidebarCategories = new(new ObservableCollection<SidebarCategoryViewModel>());
public SidebarViewModel(IScreen hostScreen,
public SidebarViewModel(IScreen hostScreen, IKernel kernel, IProfileService profileService, IRgbService rgbService, IWindowService windowService, IKernel kernel,
IProfileEditorService profileEditorService, ISidebarVmFactory sidebarVmFactory, IProfileEditorVmFactory profileEditorVmFactory) IProfileService profileService,
IWindowService windowService,
IProfileEditorService profileEditorService,
ISidebarVmFactory sidebarVmFactory,
IProfileEditorVmFactory profileEditorVmFactory)
{ {
_hostScreen = hostScreen; _hostScreen = hostScreen;
_profileService = profileService; _kernel = kernel;
_rgbService = rgbService;
_windowService = windowService; _windowService = windowService;
_profileEditorService = profileEditorService; _profileEditorService = profileEditorService;
_sidebarVmFactory = sidebarVmFactory; _profileEditorVmFactory = profileEditorVmFactory;
SidebarScreens = new ObservableCollection<SidebarScreenViewModel> SidebarScreens = new ObservableCollection<SidebarScreenViewModel>
{ {
@ -55,45 +60,52 @@ public class SidebarViewModel : ActivatableViewModelBase
new SidebarScreenViewModel<SettingsViewModel>(MaterialIconKind.Cog, "Settings") new SidebarScreenViewModel<SettingsViewModel>(MaterialIconKind.Cog, "Settings")
}; };
UpdateProfileCategories(); AddCategory = ReactiveCommand.CreateFromTask(ExecuteAddCategory);
UpdateHeaderDevice();
this.WhenActivated(disposables => SourceList<ProfileCategory> profileCategories = new();
this.WhenActivated(d =>
{ {
this.WhenAnyObservable(vm => vm._hostScreen.Router.CurrentViewModel) this.WhenAnyObservable(vm => vm._hostScreen.Router.CurrentViewModel).WhereNotNull()
.WhereNotNull()
.Subscribe(c => SelectedSidebarScreen = SidebarScreens.FirstOrDefault(s => s.ScreenType == c.GetType())) .Subscribe(c => SelectedSidebarScreen = SidebarScreens.FirstOrDefault(s => s.ScreenType == c.GetType()))
.DisposeWith(disposables); .DisposeWith(d);
this.WhenAnyValue(vm => vm.SelectedSidebarScreen) this.WhenAnyValue(vm => vm.SelectedSidebarScreen).WhereNotNull().Subscribe(NavigateToScreen);
.WhereNotNull() this.WhenAnyObservable(vm => vm._profileEditorService.ProfileConfiguration).Subscribe(NavigateToProfile).DisposeWith(d);
.Subscribe(s =>
{
_hostScreen.Router.Navigate.Execute(s.CreateInstance(kernel, _hostScreen));
profileEditorService.ChangeCurrentProfileConfiguration(null);
});
this.WhenAnyObservable(vm => vm._profileEditorService.ProfileConfiguration) Observable.FromEventPattern<ProfileCategoryEventArgs>(x => profileService.ProfileCategoryAdded += x, x => profileService.ProfileCategoryAdded -= x)
.Subscribe(profile => .Subscribe(e => profileCategories.Add(e.EventArgs.ProfileCategory))
{ .DisposeWith(d);
if (profile == null && _hostScreen.Router.GetCurrentViewModel() is ProfileEditorViewModel) Observable.FromEventPattern<ProfileCategoryEventArgs>(x => profileService.ProfileCategoryRemoved += x, x => profileService.ProfileCategoryRemoved -= x)
SelectedSidebarScreen = SidebarScreens.FirstOrDefault(); .Subscribe(e => profileCategories.Remove(e.EventArgs.ProfileCategory))
else if (profile != null && _hostScreen.Router.GetCurrentViewModel() is not ProfileEditorViewModel) .DisposeWith(d);
_hostScreen.Router.Navigate.Execute(profileEditorVmFactory.ProfileEditorViewModel(_hostScreen));
})
.DisposeWith(disposables);
profileCategories.Edit(c =>
{
c.Clear();
c.AddRange(profileService.ProfileCategories);
});
profileCategories.Connect()
.AutoRefresh(p => p.Order)
.Sort(SortExpressionComparer<ProfileCategory>.Ascending(p => p.Order))
.Transform(sidebarVmFactory.SidebarCategoryViewModel)
.ObserveOn(AvaloniaScheduler.Instance)
.Bind(out ReadOnlyObservableCollection<SidebarCategoryViewModel> categoryViewModels)
.Subscribe()
.DisposeWith(d);
SidebarCategories = categoryViewModels;
SelectedSidebarScreen = SidebarScreens.First(); SelectedSidebarScreen = SidebarScreens.First();
}); });
} }
public ObservableCollection<SidebarScreenViewModel> SidebarScreens { get; } public ObservableCollection<SidebarScreenViewModel> SidebarScreens { get; }
public ObservableCollection<SidebarCategoryViewModel> SidebarCategories { get; } = new();
public ArtemisDevice? HeaderDevice public ReadOnlyObservableCollection<SidebarCategoryViewModel> SidebarCategories
{ {
get => _headerDevice; get => _sidebarCategories;
set => RaiseAndSetIfChanged(ref _headerDevice, value); set => RaiseAndSetIfChanged(ref _sidebarCategories, value);
} }
public SidebarScreenViewModel? SelectedSidebarScreen public SidebarScreenViewModel? SelectedSidebarScreen
@ -102,14 +114,9 @@ public class SidebarViewModel : ActivatableViewModelBase
set => RaiseAndSetIfChanged(ref _selectedSidebarScreen, value); set => RaiseAndSetIfChanged(ref _selectedSidebarScreen, value);
} }
public SidebarCategoryViewModel AddProfileCategoryViewModel(ProfileCategory profileCategory) public ReactiveCommand<Unit, Unit> AddCategory { get; }
{
SidebarCategoryViewModel viewModel = _sidebarVmFactory.SidebarCategoryViewModel(this, profileCategory);
SidebarCategories.Add(viewModel);
return viewModel;
}
public async Task AddCategory() private async Task ExecuteAddCategory()
{ {
await _windowService.CreateContentDialog() await _windowService.CreateContentDialog()
.WithTitle("Add new category") .WithTitle("Add new category")
@ -118,19 +125,19 @@ public class SidebarViewModel : ActivatableViewModelBase
.WithCloseButtonText("Cancel") .WithCloseButtonText("Cancel")
.WithDefaultButton(ContentDialogButton.Primary) .WithDefaultButton(ContentDialogButton.Primary)
.ShowAsync(); .ShowAsync();
UpdateProfileCategories();
} }
public void UpdateProfileCategories() private void NavigateToProfile(ProfileConfiguration? profile)
{ {
SidebarCategories.Clear(); if (profile == null && _hostScreen.Router.GetCurrentViewModel() is ProfileEditorViewModel)
foreach (ProfileCategory profileCategory in _profileService.ProfileCategories.OrderBy(p => p.Order)) SelectedSidebarScreen = SidebarScreens.FirstOrDefault();
AddProfileCategoryViewModel(profileCategory); else if (profile != null && _hostScreen.Router.GetCurrentViewModel() is not ProfileEditorViewModel)
_hostScreen.Router.Navigate.Execute(_profileEditorVmFactory.ProfileEditorViewModel(_hostScreen));
} }
private void UpdateHeaderDevice() private void NavigateToScreen(SidebarScreenViewModel sidebarScreenViewModel)
{ {
HeaderDevice = _rgbService.Devices.FirstOrDefault(d => d.DeviceType == RGBDeviceType.Keyboard && d.Layout is {IsValid: true}); _hostScreen.Router.Navigate.Execute(sidebarScreenViewModel.CreateInstance(_kernel, _hostScreen));
_profileEditorService.ChangeCurrentProfileConfiguration(null);
} }
} }