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

Profiles - Added profile adaption based on configurable hints

Layers - Added hints UI accesible from profile tree
Surface - Added device categories used by profile adaption
This commit is contained in:
Robert Beekman 2021-05-13 19:55:10 +02:00 committed by Robert
parent 4865254ab6
commit 57193e739e
34 changed files with 1295 additions and 58 deletions

View File

@ -1,5 +1,8 @@
<wpf:ResourceDictionary xml:space="preserve" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:s="clr-namespace:System;assembly=mscorlib" xmlns:ss="urn:shemas-jetbrains-com:settings-storage-xaml" xmlns:wpf="http://schemas.microsoft.com/winfx/2006/xaml/presentation">
<s:Boolean x:Key="/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=defaulttypes/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=models_005Cprofile_005Cadaption/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=models_005Cprofile_005Cadaptionhints/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=models_005Cprofile_005Cadaption_005Chints/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=models_005Cprofile_005Cconditions_005Cwrappers/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=models_005Cprofile_005Cdatabindings_005Cmodes/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=models_005Cprofile_005Cdatabindings_005Cmodes_005Cconditional/@EntryIndexedValue">True</s:Boolean>

View File

@ -0,0 +1,75 @@
using System.Collections.Generic;
using System.Linq;
using Artemis.Storage.Entities.Profile.AdaptionHints;
namespace Artemis.Core
{
/// <summary>
/// Represents a hint that adapts layers to a certain category of devices
/// </summary>
public class CategoryAdaptionHint : IAdaptionHint
{
public CategoryAdaptionHint()
{
}
internal CategoryAdaptionHint(CategoryAdaptionHintEntity entity)
{
Category = (DeviceCategory) entity.Category;
Skip = entity.Skip;
LimitAmount = entity.LimitAmount;
Amount = entity.Amount;
}
/// <summary>
/// Gets or sets the category of devices LEDs will be applied to
/// </summary>
public DeviceCategory Category { get; set; }
/// <summary>
/// Gets or sets the amount of devices to skip
/// </summary>
public int Skip { get; set; }
/// <summary>
/// Gets or sets a boolean indicating whether a limited amount of devices should be used
/// </summary>
public bool LimitAmount { get; set; }
/// <summary>
/// Gets or sets the amount of devices to limit to if <see cref="LimitAmount" /> is <see langword="true" />
/// </summary>
public int Amount { get; set; }
#region Implementation of IAdaptionHint
/// <inheritdoc />
public void Apply(Layer layer, List<ArtemisDevice> devices)
{
IEnumerable<ArtemisDevice> matches = devices
.Where(d => d.Categories.Contains(Category))
.OrderBy(d => d.Rectangle.Top)
.ThenBy(d => d.Rectangle.Left)
.Skip(Skip);
if (LimitAmount)
matches = matches.Take(Amount);
foreach (ArtemisDevice artemisDevice in matches)
layer.AddLeds(artemisDevice.Leds);
}
/// <inheritdoc />
public IAdaptionHintEntity GetEntry()
{
return new CategoryAdaptionHintEntity {Amount = Amount, LimitAmount = LimitAmount, Category = (int) Category, Skip = Skip};
}
#endregion
/// <inheritdoc />
public override string ToString()
{
return $"Category adaption - {nameof(Category)}: {Category}, {nameof(Skip)}: {Skip}, {nameof(LimitAmount)}: {LimitAmount}, {nameof(Amount)}: {Amount}";
}
}
}

View File

@ -0,0 +1,76 @@
using System.Collections.Generic;
using System.Linq;
using Artemis.Storage.Entities.Profile.AdaptionHints;
using RGB.NET.Core;
namespace Artemis.Core
{
/// <summary>
/// Represents a hint that adapts layers to a certain type of devices
/// </summary>
public class DeviceAdaptionHint : IAdaptionHint
{
public DeviceAdaptionHint()
{
}
internal DeviceAdaptionHint(DeviceAdaptionHintEntity entity)
{
DeviceType = (RGBDeviceType) entity.DeviceType;
Skip = entity.Skip;
LimitAmount = entity.LimitAmount;
Amount = entity.Amount;
}
/// <summary>
/// Gets or sets the type of devices LEDs will be applied to
/// </summary>
public RGBDeviceType DeviceType { get; set; }
/// <summary>
/// Gets or sets the amount of devices to skip
/// </summary>
public int Skip { get; set; }
/// <summary>
/// Gets or sets a boolean indicating whether a limited amount of devices should be used
/// </summary>
public bool LimitAmount { get; set; }
/// <summary>
/// Gets or sets the amount of devices to limit to if <see cref="LimitAmount" /> is <see langword="true" />
/// </summary>
public int Amount { get; set; }
#region Implementation of IAdaptionHint
/// <inheritdoc />
public void Apply(Layer layer, List<ArtemisDevice> devices)
{
IEnumerable<ArtemisDevice> matches = devices
.Where(d => d.RgbDevice.DeviceInfo.DeviceType == DeviceType)
.OrderBy(d => d.Rectangle.Top)
.ThenBy(d => d.Rectangle.Left)
.Skip(Skip);
if (LimitAmount)
matches = matches.Take(Amount);
foreach (ArtemisDevice artemisDevice in matches)
layer.AddLeds(artemisDevice.Leds);
}
/// <inheritdoc />
public IAdaptionHintEntity GetEntry()
{
return new DeviceAdaptionHintEntity {Amount = Amount, LimitAmount = LimitAmount, DeviceType = (int) DeviceType, Skip = Skip};
}
/// <inheritdoc />
public override string ToString()
{
return $"Device adaption - {nameof(DeviceType)}: {DeviceType}, {nameof(Skip)}: {Skip}, {nameof(LimitAmount)}: {LimitAmount}, {nameof(Amount)}: {Amount}";
}
#endregion
}
}

View File

@ -0,0 +1,23 @@
using System.Collections.Generic;
using Artemis.Storage.Entities.Profile.AdaptionHints;
namespace Artemis.Core
{
/// <summary>
/// Represents an adaption hint that's used to adapt a layer to a set of devices
/// </summary>
public interface IAdaptionHint
{
/// <summary>
/// Applies the adaptive action to the provided layer
/// </summary>
/// <param name="layer">The layer to adapt</param>
/// <param name="devices">The devices to adapt the layer for</param>
void Apply(Layer layer, List<ArtemisDevice> devices);
/// <summary>
/// Returns an adaption hint entry for this adaption hint used for persistent storage
/// </summary>
IAdaptionHintEntity GetEntry();
}
}

View File

@ -0,0 +1,83 @@
using System;
using System.Collections.Generic;
using System.Linq;
using Artemis.Storage.Entities.Profile.AdaptionHints;
using RGB.NET.Core;
namespace Artemis.Core
{
/// <summary>
/// Represents a hint that adapts layers to a certain region of keyboards
/// </summary>
public class KeyboardSectionAdaptionHint : IAdaptionHint
{
private static readonly Dictionary<KeyboardSection, List<LedId>> RegionLedIds = new()
{
{KeyboardSection.MacroKeys, Enum.GetValues<LedId>().Where(l => l >= LedId.Keyboard_Programmable1 && l <= LedId.Keyboard_Programmable32).ToList()},
{KeyboardSection.LedStrips, Enum.GetValues<LedId>().Where(l => l >= LedId.LedStripe1 && l <= LedId.LedStripe128).ToList()},
{KeyboardSection.Extra, Enum.GetValues<LedId>().Where(l => l >= LedId.Keyboard_Custom1 && l <= LedId.Keyboard_Custom64).ToList()}
};
public KeyboardSectionAdaptionHint()
{
}
internal KeyboardSectionAdaptionHint(KeyboardSectionAdaptionHintEntity entity)
{
Section = (KeyboardSection) entity.Section;
}
/// <summary>
/// Gets or sets the section this hint will apply LEDs to
/// </summary>
public KeyboardSection Section { get; set; }
#region Implementation of IAdaptionHint
/// <inheritdoc />
public void Apply(Layer layer, List<ArtemisDevice> devices)
{
// Only keyboards should have the LEDs we care about
foreach (ArtemisDevice keyboard in devices.Where(d => d.RgbDevice.DeviceInfo.DeviceType == RGBDeviceType.Keyboard))
{
List<LedId> ledIds = RegionLedIds[Section];
layer.AddLeds(keyboard.Leds.Where(l => ledIds.Contains(l.RgbLed.Id)));
}
}
/// <inheritdoc />
public IAdaptionHintEntity GetEntry()
{
return new KeyboardSectionAdaptionHintEntity {Section = (int) Section};
}
/// <inheritdoc />
public override string ToString()
{
return $"Keyboard section adaption - {nameof(Section)}: {Section}";
}
#endregion
}
/// <summary>
/// Represents a section of LEDs on a keyboard
/// </summary>
public enum KeyboardSection
{
/// <summary>
/// A region containing the macro keys of a keyboard
/// </summary>
MacroKeys,
/// <summary>
/// A region containing the LED strips of a keyboard
/// </summary>
LedStrips,
/// <summary>
/// A region containing extra non-standard LEDs of a keyboard
/// </summary>
Extra
}
}

View File

@ -7,6 +7,7 @@ using Artemis.Core.LayerBrushes;
using Artemis.Core.LayerEffects;
using Artemis.Storage.Entities.Profile;
using Artemis.Storage.Entities.Profile.Abstract;
using RGB.NET.Core;
using SkiaSharp;
namespace Artemis.Core
@ -42,6 +43,7 @@ namespace Artemis.Core
_leds = new List<ArtemisLed>();
Adapter = new LayerAdapter(this);
Initialize();
Parent.AddChild(this, 0);
}
@ -65,6 +67,7 @@ namespace Artemis.Core
_leds = new List<ArtemisLed>();
Load();
Adapter = new LayerAdapter(this);
Initialize();
}
@ -121,6 +124,11 @@ namespace Artemis.Core
/// </summary>
public LayerEntity LayerEntity { get; internal set; }
/// <summary>
/// Gets the layer adapter that can be used to adapt this layer to a different set of devices
/// </summary>
public LayerAdapter Adapter { get; }
/// <inheritdoc />
public override bool ShouldBeEnabled => !Suspended && DisplayConditionMet;
@ -147,7 +155,15 @@ namespace Artemis.Core
return $"[Layer] {nameof(Name)}: {Name}, {nameof(Order)}: {Order}";
}
#region IDisposable
/// <summary>
/// Occurs when a property affecting the rendering properties of this layer has been updated
/// </summary>
public event EventHandler? RenderPropertiesUpdated;
/// <summary>
/// Occurs when the layer brush of this layer has been updated
/// </summary>
public event EventHandler? LayerBrushUpdated;
/// <inheritdoc />
protected override void Dispose(bool disposing)
@ -162,7 +178,10 @@ namespace Artemis.Core
base.Dispose(disposing);
}
#endregion
internal void OnLayerBrushUpdated()
{
LayerBrushUpdated?.Invoke(this, EventArgs.Empty);
}
private void Initialize()
{
@ -190,6 +209,28 @@ namespace Artemis.Core
Reset();
}
private void LayerBrushStoreOnLayerBrushRemoved(object? sender, LayerBrushStoreEvent e)
{
if (LayerBrush?.Descriptor == e.Registration.LayerBrushDescriptor)
DeactivateLayerBrush();
}
private void LayerBrushStoreOnLayerBrushAdded(object? sender, LayerBrushStoreEvent e)
{
if (LayerBrush != null || !General.PropertiesInitialized)
return;
LayerBrushReference? current = General.BrushReference.CurrentValue;
if (e.Registration.PluginFeature.Id == current?.LayerBrushProviderId &&
e.Registration.LayerBrushDescriptor.LayerBrushType.Name == current.BrushType)
ActivateLayerBrush();
}
private void OnRenderPropertiesUpdated()
{
RenderPropertiesUpdated?.Invoke(this, EventArgs.Empty);
}
#region Storage
internal override void Load()
@ -229,7 +270,8 @@ namespace Artemis.Core
LedEntity ledEntity = new()
{
DeviceIdentifier = artemisLed.Device.Identifier,
LedName = artemisLed.RgbLed.Id.ToString()
LedName = artemisLed.RgbLed.Id.ToString(),
PhysicalLayout = artemisLed.Device.RgbDevice.DeviceInfo.DeviceType == RGBDeviceType.Keyboard ? (int) artemisLed.Device.PhysicalLayout : null
};
LayerEntity.Leds.Add(ledEntity);
}
@ -575,7 +617,7 @@ namespace Artemis.Core
if (Disposed)
throw new ObjectDisposedException("Layer");
_leds.AddRange(leds);
_leds.AddRange(leds.Except(_leds));
CalculateRenderProperties();
}
@ -692,51 +734,6 @@ namespace Artemis.Core
}
#endregion
#region Event handlers
private void LayerBrushStoreOnLayerBrushRemoved(object? sender, LayerBrushStoreEvent e)
{
if (LayerBrush?.Descriptor == e.Registration.LayerBrushDescriptor)
DeactivateLayerBrush();
}
private void LayerBrushStoreOnLayerBrushAdded(object? sender, LayerBrushStoreEvent e)
{
if (LayerBrush != null || !General.PropertiesInitialized)
return;
LayerBrushReference? current = General.BrushReference.CurrentValue;
if (e.Registration.PluginFeature.Id == current?.LayerBrushProviderId &&
e.Registration.LayerBrushDescriptor.LayerBrushType.Name == current.BrushType)
ActivateLayerBrush();
}
#endregion
#region Events
/// <summary>
/// Occurs when a property affecting the rendering properties of this layer has been updated
/// </summary>
public event EventHandler? RenderPropertiesUpdated;
/// <summary>
/// Occurs when the layer brush of this layer has been updated
/// </summary>
public event EventHandler? LayerBrushUpdated;
private void OnRenderPropertiesUpdated()
{
RenderPropertiesUpdated?.Invoke(this, EventArgs.Empty);
}
internal void OnLayerBrushUpdated()
{
LayerBrushUpdated?.Invoke(this, EventArgs.Empty);
}
#endregion
}
/// <summary>

View File

@ -0,0 +1,144 @@
using System;
using System.Collections.Generic;
using System.Linq;
using Artemis.Storage.Entities.Profile;
using Artemis.Storage.Entities.Profile.AdaptionHints;
using RGB.NET.Core;
namespace Artemis.Core
{
/// <summary>
/// Represents an adapter that adapts a layer to a certain set of devices using <see cref="IAdaptionHint" />s
/// </summary>
public class LayerAdapter : IStorageModel
{
internal LayerAdapter(Layer layer)
{
Layer = layer;
AdaptionHints = new List<IAdaptionHint>();
}
/// <summary>
/// Gets the layer this adapter can adapt
/// </summary>
public Layer Layer { get; }
/// <summary>
/// Gets or sets a list containing the adaption hints used by this adapter
/// </summary>
public List<IAdaptionHint> AdaptionHints { get; set; }
/// <summary>
/// Modifies the layer, adapting it to the provided <paramref name="devices" />
/// </summary>
/// <param name="devices">The devices to adapt the layer to</param>
public void Adapt(List<ArtemisDevice> devices)
{
// Use adaption hints if provided
if (AdaptionHints.Any())
{
foreach (IAdaptionHint adaptionHint in AdaptionHints)
adaptionHint.Apply(Layer, devices);
}
// If there are no hints, try to find matching LEDs anyway
else
{
List<ArtemisLed> availableLeds = devices.SelectMany(d => d.Leds).ToList();
List<ArtemisLed> usedLeds = new();
foreach (LedEntity ledEntity in Layer.LayerEntity.Leds)
{
// TODO: If this is a keyboard LED and the layouts don't match, convert it before looking for it on the devices
LedId ledId = Enum.Parse<LedId>(ledEntity.LedName);
ArtemisLed? led = availableLeds.FirstOrDefault(l => l.RgbLed.Id == ledId);
if (led != null)
{
availableLeds.Remove(led);
usedLeds.Add(led);
}
}
Layer.AddLeds(usedLeds);
}
}
/// <summary>
/// Automatically determine hints for this layer
/// </summary>
public List<IAdaptionHint> DetermineHints(IEnumerable<ArtemisDevice> devices)
{
List<IAdaptionHint> newHints = new();
// Any fully covered device will add a device adaption hint for that type
foreach (IGrouping<ArtemisDevice, ArtemisLed> deviceLeds in Layer.Leds.GroupBy(l => l.Device))
{
ArtemisDevice device = deviceLeds.Key;
// If there is already an adaption hint for this type, don't add another
if (AdaptionHints.Any(h => h is DeviceAdaptionHint d && d.DeviceType == device.RgbDevice.DeviceInfo.DeviceType))
continue;
if (DoesLayerCoverDevice(device))
{
DeviceAdaptionHint hint = new() {DeviceType = device.RgbDevice.DeviceInfo.DeviceType};
AdaptionHints.Add(hint);
newHints.Add(hint);
}
}
// Any fully covered category will add a category adaption hint for its category
foreach (DeviceCategory deviceCategory in Enum.GetValues<DeviceCategory>())
{
if (AdaptionHints.Any(h => h is CategoryAdaptionHint c && c.Category == deviceCategory))
continue;
List<ArtemisDevice> categoryDevices = devices.Where(d => d.Categories.Contains(deviceCategory)).ToList();
if (categoryDevices.Any() && categoryDevices.All(DoesLayerCoverDevice))
{
CategoryAdaptionHint hint = new() {Category = deviceCategory};
AdaptionHints.Add(hint);
newHints.Add(hint);
}
}
return newHints;
}
private bool DoesLayerCoverDevice(ArtemisDevice device)
{
return device.Leds.All(l => Layer.Leds.Contains(l));
}
#region Implementation of IStorageModel
/// <inheritdoc />
public void Load()
{
AdaptionHints.Clear();
// Kind of meh.
// This leaves the adapter responsible for finding the right hint for the right entity, but it's gotta be done somewhere..
foreach (IAdaptionHintEntity hintEntity in Layer.LayerEntity.AdaptionHints)
switch (hintEntity)
{
case DeviceAdaptionHintEntity entity:
AdaptionHints.Add(new DeviceAdaptionHint(entity));
break;
case CategoryAdaptionHintEntity entity:
AdaptionHints.Add(new CategoryAdaptionHint(entity));
break;
case KeyboardSectionAdaptionHintEntity entity:
AdaptionHints.Add(new KeyboardSectionAdaptionHint(entity));
break;
}
}
/// <inheritdoc />
public void Save()
{
Layer.LayerEntity.AdaptionHints.Clear();
foreach (IAdaptionHint adaptionHint in AdaptionHints)
Layer.LayerEntity.AdaptionHints.Add(adaptionHint.GetEntry());
}
#endregion
}
}

View File

@ -36,10 +36,12 @@ namespace Artemis.Core
InputIdentifiers = new List<ArtemisDeviceInputIdentifier>();
InputMappings = new Dictionary<ArtemisLed, ArtemisLed>();
Categories = new HashSet<DeviceCategory>();
UpdateLeds();
ApplyKeyboardLayout();
ApplyToEntity();
ApplyDefaultCategories();
CalculateRenderProperties();
}
@ -52,6 +54,7 @@ namespace Artemis.Core
InputIdentifiers = new List<ArtemisDeviceInputIdentifier>();
InputMappings = new Dictionary<ArtemisLed, ArtemisLed>();
Categories = new HashSet<DeviceCategory>();
foreach (DeviceInputIdentifierEntity identifierEntity in DeviceEntity.InputIdentifiers)
InputIdentifiers.Add(new ArtemisDeviceInputIdentifier(identifierEntity.InputProvider, identifierEntity.Identifier));
@ -114,6 +117,11 @@ namespace Artemis.Core
/// </summary>
public Dictionary<ArtemisLed, ArtemisLed> InputMappings { get; }
/// <summary>
/// Gets a list containing the categories of this device
/// </summary>
public HashSet<DeviceCategory> Categories { get; }
/// <summary>
/// Gets or sets the X-position of the device
/// </summary>
@ -383,6 +391,46 @@ namespace Artemis.Core
OnDeviceUpdated();
}
/// <summary>
/// Applies the default categories for this device to the <see cref="Categories"/> list
/// </summary>
public void ApplyDefaultCategories()
{
switch (RgbDevice.DeviceInfo.DeviceType)
{
case RGBDeviceType.Keyboard:
case RGBDeviceType.Mouse:
case RGBDeviceType.Headset:
case RGBDeviceType.Mousepad:
case RGBDeviceType.HeadsetStand:
case RGBDeviceType.Keypad:
if (!Categories.Contains(DeviceCategory.Peripherals))
Categories.Add(DeviceCategory.Peripherals);
break;
case RGBDeviceType.Mainboard:
case RGBDeviceType.GraphicsCard:
case RGBDeviceType.DRAM:
case RGBDeviceType.Fan:
case RGBDeviceType.LedStripe:
case RGBDeviceType.Cooler:
if (!Categories.Contains(DeviceCategory.Case))
Categories.Add(DeviceCategory.Case);
break;
case RGBDeviceType.Speaker:
if (!Categories.Contains(DeviceCategory.Desk))
Categories.Add(DeviceCategory.Desk);
break;
case RGBDeviceType.Monitor:
if (!Categories.Contains(DeviceCategory.Monitor))
Categories.Add(DeviceCategory.Monitor);
break;
case RGBDeviceType.LedMatrix:
if (!Categories.Contains(DeviceCategory.Room))
Categories.Add(DeviceCategory.Room);
break;
}
}
internal void ApplyToEntity()
{
// Other properties are computed
@ -401,6 +449,10 @@ namespace Artemis.Core
DeviceEntity.InputMappings.Clear();
foreach (var (original, mapped) in InputMappings)
DeviceEntity.InputMappings.Add(new InputMappingEntity {OriginalLedId = (int) original.RgbLed.Id, MappedLedId = (int) mapped.RgbLed.Id});
DeviceEntity.Categories.Clear();
foreach (DeviceCategory deviceCategory in Categories)
DeviceEntity.Categories.Add((int) deviceCategory);
}
internal void ApplyToRgbDevice()
@ -419,6 +471,12 @@ namespace Artemis.Core
if (!RgbDevice.ColorCorrections.Any())
RgbDevice.ColorCorrections.Add(new ScaleColorCorrection(this));
Categories.Clear();
foreach (int deviceEntityCategory in DeviceEntity.Categories)
Categories.Add((DeviceCategory) deviceEntityCategory);
if (!Categories.Any())
ApplyDefaultCategories();
CalculateRenderProperties();
OnDeviceUpdated();
@ -472,4 +530,13 @@ namespace Artemis.Core
LogicalLayout = DeviceEntity.LogicalLayout;
}
}
public enum DeviceCategory
{
Desk,
Monitor,
Case,
Room,
Peripherals
}
}

View File

@ -314,6 +314,9 @@ namespace Artemis.Core.Services
{
SurfaceArrangement surfaceArrangement = SurfaceArrangement.GetDefaultArrangement();
surfaceArrangement.Arrange(_devices);
foreach (ArtemisDevice artemisDevice in _devices)
artemisDevice.ApplyDefaultCategories();
SaveDevices();
}

View File

@ -121,5 +121,11 @@ namespace Artemis.Core.Services
/// <param name="nameAffix">Text to add after the name of the profile (separated by a dash)</param>
/// <returns></returns>
ProfileDescriptor ImportProfile(string json, ProfileModule profileModule, string nameAffix = "imported");
/// <summary>
/// Adapts a given profile to the currently active devices
/// </summary>
/// <param name="profile">The profile to adapt</param>
void AdaptProfile(Profile profile);
}
}

View File

@ -122,7 +122,7 @@ namespace Artemis.Core.Services
ProfileEntity entity = _profileRepository.Get(module.ActiveProfile.EntityId);
Profile profile = new(module, entity);
InstantiateProfile(profile);
module.ChangeActiveProfile(null, _rgbService.EnabledDevices);
module.ChangeActiveProfile(profile, _rgbService.EnabledDevices);
}
@ -166,7 +166,6 @@ namespace Artemis.Core.Services
}
public void ClearActiveProfile(ProfileModule module)
{
module.ChangeActiveProfile(null, _rgbService.EnabledDevices);
@ -294,13 +293,21 @@ namespace Artemis.Core.Services
return new ProfileDescriptor(profileModule, profileEntity);
}
/// <inheritdoc />
public void AdaptProfile(Profile profile)
{
List<ArtemisDevice> devices = _rgbService.EnabledDevices.ToList();
foreach (Layer layer in profile.GetAllLayers())
layer.Adapter.Adapt(devices);
}
#region Event handlers
private void RgbServiceOnLedsChanged(object? sender, EventArgs e)
{
ActiveProfilesPopulateLeds();
}
#endregion
}
}

View File

@ -0,0 +1,13 @@
namespace Artemis.Storage.Entities.Profile.AdaptionHints
{
public class CategoryAdaptionHintEntity : IAdaptionHintEntity
{
public int Category { get; set; }
public bool LimitAmount { get; set; }
public int Skip { get; set; }
public int Amount { get; set; }
}
}

View File

@ -0,0 +1,11 @@
namespace Artemis.Storage.Entities.Profile.AdaptionHints
{
public class DeviceAdaptionHintEntity : IAdaptionHintEntity
{
public int DeviceType { get; set; }
public bool LimitAmount { get; set; }
public int Skip { get; set; }
public int Amount { get; set; }
}
}

View File

@ -0,0 +1,6 @@
namespace Artemis.Storage.Entities.Profile.AdaptionHints
{
public interface IAdaptionHintEntity
{
}
}

View File

@ -0,0 +1,7 @@
namespace Artemis.Storage.Entities.Profile.AdaptionHints
{
public class KeyboardSectionAdaptionHintEntity : IAdaptionHintEntity
{
public int Section { get; set; }
}
}

View File

@ -1,6 +1,7 @@
using System;
using System.Collections.Generic;
using Artemis.Storage.Entities.Profile.Abstract;
using Artemis.Storage.Entities.Profile.AdaptionHints;
using LiteDB;
namespace Artemis.Storage.Entities.Profile
@ -10,6 +11,7 @@ namespace Artemis.Storage.Entities.Profile
public LayerEntity()
{
Leds = new List<LedEntity>();
AdaptionHints = new List<IAdaptionHintEntity>();
PropertyEntities = new List<PropertyEntity>();
LayerEffects = new List<LayerEffectEntity>();
ExpandedPropertyGroups = new List<string>();
@ -20,6 +22,7 @@ namespace Artemis.Storage.Entities.Profile
public bool Suspended { get; set; }
public List<LedEntity> Leds { get; set; }
public List<IAdaptionHintEntity> AdaptionHints { get; set; }
[BsonRef("ProfileEntity")]
public ProfileEntity Profile { get; set; }

View File

@ -4,5 +4,7 @@
{
public string LedName { get; set; }
public string DeviceIdentifier { get; set; }
public int? PhysicalLayout { get; set; }
}
}

View File

@ -8,8 +8,9 @@ namespace Artemis.Storage.Entities.Surface
{
InputIdentifiers = new List<DeviceInputIdentifierEntity>();
InputMappings = new List<InputMappingEntity>();
Categories = new List<int>();
}
public string Id { get; set; }
public float X { get; set; }
public float Y { get; set; }
@ -27,7 +28,7 @@ namespace Artemis.Storage.Entities.Surface
public List<DeviceInputIdentifierEntity> InputIdentifiers { get; set; }
public List<InputMappingEntity> InputMappings { get; set; }
public List<int> Categories { get; set; }
}
public class InputMappingEntity

View File

@ -12,6 +12,8 @@ using Artemis.UI.Screens.ProfileEditor.LayerProperties.DataBindings.DirectDataBi
using Artemis.UI.Screens.ProfileEditor.LayerProperties.LayerEffects;
using Artemis.UI.Screens.ProfileEditor.LayerProperties.Timeline;
using Artemis.UI.Screens.ProfileEditor.LayerProperties.Tree;
using Artemis.UI.Screens.ProfileEditor.ProfileTree.Dialogs;
using Artemis.UI.Screens.ProfileEditor.ProfileTree.Dialogs.AdaptionHints;
using Artemis.UI.Screens.ProfileEditor.ProfileTree.TreeItem;
using Artemis.UI.Screens.ProfileEditor.Visualization;
using Artemis.UI.Screens.ProfileEditor.Visualization.Tools;
@ -59,6 +61,14 @@ namespace Artemis.UI.Ninject.Factories
LayerViewModel LayerViewModel(ProfileElement layer);
}
public interface ILayerHintVmFactory : IVmFactory
{
LayerHintsDialogViewModel LayerHintsDialogViewModel(Layer layer);
CategoryAdaptionHintViewModel CategoryAdaptionHintViewModel(CategoryAdaptionHint adaptionHint);
DeviceAdaptionHintViewModel DeviceAdaptionHintViewModel(DeviceAdaptionHint adaptionHint);
KeyboardSectionAdaptionHintViewModel KeyboardSectionAdaptionHintViewModel(KeyboardSectionAdaptionHint adaptionHint);
}
public interface IProfileLayerVmFactory : IVmFactory
{
ProfileLayerViewModel Create(Layer layer, PanZoomViewModel panZoomViewModel);

View File

@ -169,6 +169,12 @@
</Button>
<Separator />
<Button Command="{s:Action AdaptActiveProfile}" ToolTip="Adapt the profile to the current surface">
<StackPanel Orientation="Horizontal">
<materialDesign:PackIcon Kind="AutoFix" Margin="0 0 15 0" />
<TextBlock>Adapt profile</TextBlock>
</StackPanel>
</Button>
<Button Command="{s:Action ExportActiveProfile}">
<StackPanel Orientation="Horizontal">
<materialDesign:PackIcon Kind="Upload" Margin="0 0 15 0" />

View File

@ -209,6 +209,20 @@ namespace Artemis.UI.Screens.ProfileEditor
SelectedProfile = copy;
}
public async Task AdaptActiveProfile()
{
if (_profileEditorService.SelectedProfile == null)
return;
if (!await DialogService.ShowConfirmDialog(
"Adapt profile",
"Are you sure you want to adapt the profile to your current surface? Layer assignments may change."
))
return;
_profileService.AdaptProfile(_profileEditorService.SelectedProfile);
}
public async Task ExportActiveProfile()
{
await DialogService.ShowDialog<ProfileExportViewModel>(new Dictionary<string, object>

View File

@ -0,0 +1,20 @@
using Artemis.Core;
using Stylet;
namespace Artemis.UI.Screens.ProfileEditor.ProfileTree.Dialogs.AdaptionHints
{
public abstract class AdaptionHintViewModel : Screen
{
protected AdaptionHintViewModel(IAdaptionHint adaptionHint)
{
AdaptionHint = adaptionHint;
}
public IAdaptionHint AdaptionHint { get; }
public void Remove()
{
((LayerHintsDialogViewModel) Parent).RemoveAdaptionHint(this);
}
}
}

View File

@ -0,0 +1,78 @@
<UserControl x:Class="Artemis.UI.Screens.ProfileEditor.ProfileTree.Dialogs.AdaptionHints.CategoryAdaptionHintView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:local="clr-namespace:Artemis.UI.Screens.ProfileEditor.ProfileTree.Dialogs.AdaptionHints"
xmlns:materialDesign="http://materialdesigninxaml.net/winfx/xaml/themes"
xmlns:Converters="clr-namespace:Artemis.UI.Converters"
xmlns:s="https://github.com/canton7/Stylet"
mc:Ignorable="d"
d:DesignHeight="450" d:DesignWidth="800"
d:DataContext="{d:DesignInstance local:CategoryAdaptionHintViewModel}">
<UserControl.Resources>
<Converters:InverseBooleanConverter x:Key="InverseBooleanConverter" />
</UserControl.Resources>
<materialDesign:Card Margin="0 5" Padding="10">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition />
<ColumnDefinition />
<ColumnDefinition />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition />
<RowDefinition />
</Grid.RowDefinitions>
<StackPanel Grid.ColumnSpan="3" Margin="0 0 0 5">
<TextBlock Style="{StaticResource MaterialDesignSubtitle1TextBlock}" Text="Category hint" />
<TextBlock Style="{StaticResource MaterialDesignBody2TextBlock}" Text="Applies the layer to devices of a certain category" />
</StackPanel>
<Button Grid.Row="0"
Grid.Column="3"
VerticalAlignment="Top"
HorizontalAlignment="Right"
Style="{StaticResource MaterialDesignIconForegroundButton}"
ToolTip="Remove hint"
Command="{s:Action Remove}">
<materialDesign:PackIcon Kind="Delete" />
</Button>
<ComboBox Grid.Row="1"
Grid.Column="0"
Style="{StaticResource MaterialDesignFilledComboBox}"
materialDesign:HintAssist.Hint="Category"
Margin="0 0 5 0"
SelectedValue="{Binding CategoryAdaptionHint.Category}"
ItemsSource="{Binding Categories}"
SelectedValuePath="Value"
DisplayMemberPath="Description" />
<TextBox Grid.Row="1"
Grid.Column="1"
Style="{StaticResource MaterialDesignFilledTextBox}"
materialDesign:HintAssist.Hint="Skip"
materialDesign:TextFieldAssist.SuffixText="device(s)"
Margin="5 0"
Text="{Binding CategoryAdaptionHint.Skip}" />
<TextBox Grid.Row="1"
Grid.Column="2"
Style="{StaticResource MaterialDesignFilledTextBox}"
materialDesign:HintAssist.Hint="Take"
materialDesign:TextFieldAssist.SuffixText="device(s)"
IsEnabled="{Binding TakeAllDevices, Converter={StaticResource InverseBooleanConverter}}"
Margin="5 0"
Text="{Binding CategoryAdaptionHint.Amount}" />
<CheckBox Grid.Row="1"
Grid.Column="3"
Content="Take all devices"
VerticalAlignment="Top"
Margin="0 18 0 0"
IsChecked="{Binding TakeAllDevices}" />
</Grid>
</materialDesign:Card>
</UserControl>

View File

@ -0,0 +1,46 @@
using Artemis.Core;
using Artemis.UI.Shared;
using Stylet;
namespace Artemis.UI.Screens.ProfileEditor.ProfileTree.Dialogs.AdaptionHints
{
public class CategoryAdaptionHintViewModel : AdaptionHintViewModel
{
private bool _takeAllDevices;
/// <inheritdoc />
public CategoryAdaptionHintViewModel(CategoryAdaptionHint adaptionHint) : base(adaptionHint)
{
CategoryAdaptionHint = adaptionHint;
Categories = new BindableCollection<ValueDescription>(EnumUtilities.GetAllValuesAndDescriptions(typeof(DeviceCategory)));
}
public CategoryAdaptionHint CategoryAdaptionHint { get; }
public BindableCollection<ValueDescription> Categories { get; }
public bool TakeAllDevices
{
get => _takeAllDevices;
set => SetAndNotify(ref _takeAllDevices, value);
}
#region Overrides of Screen
/// <inheritdoc />
protected override void OnInitialActivate()
{
TakeAllDevices = !CategoryAdaptionHint.LimitAmount;
base.OnInitialActivate();
}
/// <inheritdoc />
protected override void OnClose()
{
CategoryAdaptionHint.LimitAmount = !TakeAllDevices;
base.OnClose();
}
#endregion
}
}

View File

@ -0,0 +1,79 @@
<UserControl x:Class="Artemis.UI.Screens.ProfileEditor.ProfileTree.Dialogs.AdaptionHints.DeviceAdaptionHintView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:local="clr-namespace:Artemis.UI.Screens.ProfileEditor.ProfileTree.Dialogs.AdaptionHints"
xmlns:materialDesign="http://materialdesigninxaml.net/winfx/xaml/themes"
xmlns:converters="clr-namespace:Artemis.UI.Converters"
xmlns:s="https://github.com/canton7/Stylet"
mc:Ignorable="d"
d:DesignHeight="450" d:DesignWidth="800"
d:DataContext="{d:DesignInstance local:DeviceAdaptionHintViewModel}">
<UserControl.Resources>
<converters:InverseBooleanConverter x:Key="InverseBooleanConverter" />
</UserControl.Resources>
<materialDesign:Card Margin="0 5" Padding="10">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition />
<ColumnDefinition />
<ColumnDefinition />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition />
<RowDefinition />
</Grid.RowDefinitions>
<StackPanel Grid.ColumnSpan="3" Margin="0 0 0 5">
<TextBlock Style="{StaticResource MaterialDesignSubtitle1TextBlock}" Text="Device type hint" />
<TextBlock Style="{StaticResource MaterialDesignBody2TextBlock}" Text="Applies the layer to devices of a certain type" />
</StackPanel>
<Button Grid.Row="0"
Grid.Column="3"
VerticalAlignment="Top"
HorizontalAlignment="Right"
Style="{StaticResource MaterialDesignIconForegroundButton}"
ToolTip="Remove hint"
Command="{s:Action Remove}">
<materialDesign:PackIcon Kind="Delete" />
</Button>
<ComboBox Grid.Row="1"
Grid.Column="0"
Style="{StaticResource MaterialDesignFilledComboBox}"
materialDesign:HintAssist.Hint="Device type"
Margin="0 0 5 0"
SelectedValue="{Binding DeviceAdaptionHint.DeviceType}"
ItemsSource="{Binding DeviceTypes}"
SelectedValuePath="Value"
DisplayMemberPath="Description">
</ComboBox>
<TextBox Grid.Row="1"
Grid.Column="1"
Style="{StaticResource MaterialDesignFilledTextBox}"
materialDesign:HintAssist.Hint="Skip"
materialDesign:TextFieldAssist.SuffixText="device(s)"
Margin="5 0"
Text="{Binding DeviceAdaptionHint.Skip}"/>
<TextBox Grid.Row="1"
Grid.Column="2"
Style="{StaticResource MaterialDesignFilledTextBox}"
materialDesign:HintAssist.Hint="Take"
materialDesign:TextFieldAssist.SuffixText="device(s)"
IsEnabled="{Binding TakeAllDevices, Converter={StaticResource InverseBooleanConverter}}"
Margin="5 0"
Text="{Binding DeviceAdaptionHint.Amount}"/>
<CheckBox Grid.Row="1"
Grid.Column="3"
Content="Take all devices"
VerticalAlignment="Top"
Margin="0 18 0 0"
IsChecked="{Binding TakeAllDevices}" />
</Grid>
</materialDesign:Card>
</UserControl>

View File

@ -0,0 +1,47 @@
using Artemis.Core;
using Artemis.UI.Shared;
using RGB.NET.Core;
using Stylet;
namespace Artemis.UI.Screens.ProfileEditor.ProfileTree.Dialogs.AdaptionHints
{
public class DeviceAdaptionHintViewModel : AdaptionHintViewModel
{
private bool _takeAllDevices;
/// <inheritdoc />
public DeviceAdaptionHintViewModel(DeviceAdaptionHint adaptionHint) : base(adaptionHint)
{
DeviceAdaptionHint = adaptionHint;
DeviceTypes = new BindableCollection<ValueDescription>(EnumUtilities.GetAllValuesAndDescriptions(typeof(RGBDeviceType)));
}
public DeviceAdaptionHint DeviceAdaptionHint { get; }
public BindableCollection<ValueDescription> DeviceTypes { get; }
public bool TakeAllDevices
{
get => _takeAllDevices;
set => SetAndNotify(ref _takeAllDevices, value);
}
#region Overrides of Screen
/// <inheritdoc />
protected override void OnInitialActivate()
{
TakeAllDevices = !DeviceAdaptionHint.LimitAmount;
base.OnInitialActivate();
}
/// <inheritdoc />
protected override void OnClose()
{
DeviceAdaptionHint.LimitAmount = !TakeAllDevices;
base.OnClose();
}
#endregion
}
}

View File

@ -0,0 +1,53 @@
<UserControl x:Class="Artemis.UI.Screens.ProfileEditor.ProfileTree.Dialogs.AdaptionHints.KeyboardSectionAdaptionHintView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:local="clr-namespace:Artemis.UI.Screens.ProfileEditor.ProfileTree.Dialogs.AdaptionHints"
xmlns:materialDesign="http://materialdesigninxaml.net/winfx/xaml/themes"
xmlns:s="https://github.com/canton7/Stylet"
mc:Ignorable="d"
d:DesignHeight="450" d:DesignWidth="800"
d:DataContext="{d:DesignInstance local:KeyboardSectionAdaptionHintViewModel}">
<materialDesign:Card Margin="0 5" Padding="10">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition />
<ColumnDefinition />
<ColumnDefinition />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition />
<RowDefinition />
</Grid.RowDefinitions>
<StackPanel Grid.ColumnSpan="3" Margin="0 0 0 5">
<TextBlock Style="{StaticResource MaterialDesignSubtitle1TextBlock}" Text="Keyboard section hint" />
<TextBlock Style="{StaticResource MaterialDesignBody2TextBlock}" Text="Applies the layer to a section of all keyboards" />
</StackPanel>
<Button Grid.Row="0"
Grid.Column="3"
VerticalAlignment="Top"
HorizontalAlignment="Right"
Style="{StaticResource MaterialDesignIconForegroundButton}"
ToolTip="Remove hint"
Command="{s:Action Remove}">
<materialDesign:PackIcon Kind="Delete" />
</Button>
<ComboBox Grid.Row="1"
Grid.Column="0"
Style="{StaticResource MaterialDesignFilledComboBox}"
materialDesign:HintAssist.Hint="Section"
Margin="0 0 5 0"
HorizontalAlignment="Left"
Width="209"
SelectedValue="{Binding KeyboardSectionAdaptionHint.Section}"
ItemsSource="{Binding Sections}"
SelectedValuePath="Value"
DisplayMemberPath="Description">
</ComboBox>
</Grid>
</materialDesign:Card>
</UserControl>

View File

@ -0,0 +1,19 @@
using Artemis.Core;
using Artemis.UI.Shared;
using Stylet;
namespace Artemis.UI.Screens.ProfileEditor.ProfileTree.Dialogs.AdaptionHints
{
public class KeyboardSectionAdaptionHintViewModel : AdaptionHintViewModel
{
/// <inheritdoc />
public KeyboardSectionAdaptionHintViewModel(KeyboardSectionAdaptionHint adaptionHint) : base(adaptionHint)
{
KeyboardSectionAdaptionHint = adaptionHint;
Sections = new BindableCollection<ValueDescription>(EnumUtilities.GetAllValuesAndDescriptions(typeof(KeyboardSection)));
}
public KeyboardSectionAdaptionHint KeyboardSectionAdaptionHint { get; }
public BindableCollection<ValueDescription> Sections { get; }
}
}

View File

@ -0,0 +1,112 @@
<mde:MaterialWindow x:Class="Artemis.UI.Screens.ProfileEditor.ProfileTree.Dialogs.LayerHintsDialogView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
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:materialDesign="http://materialdesigninxaml.net/winfx/xaml/themes"
xmlns:s="https://github.com/canton7/Stylet"
xmlns:mde="clr-namespace:MaterialDesignExtensions.Controls;assembly=MaterialDesignExtensions"
xmlns:shared="clr-namespace:Artemis.UI.Shared;assembly=Artemis.UI.Shared"
xmlns:svgc="http://sharpvectors.codeplex.com/svgc/"
xmlns:dialogs="clr-namespace:Artemis.UI.Screens.ProfileEditor.ProfileTree.Dialogs"
mc:Ignorable="d"
Title="{Binding DisplayName}"
TitleBarIcon="{svgc:SvgImage Source=/Resources/Images/Logo/bow-white.svg}"
Foreground="{DynamicResource MaterialDesignBody}"
Background="{DynamicResource MaterialDesignPaper}"
FontFamily="pack://application:,,,/MaterialDesignThemes.Wpf;component/Resources/Roboto/#Roboto"
UseLayoutRounding="True"
FadeContentIfInactive="False"
Width="800"
Height="800"
d:DesignHeight="800" d:DesignWidth="800"
d:DataContext="{d:DesignInstance dialogs:LayerHintsDialogViewModel}"
Icon="/Resources/Images/Logo/bow.ico">
<mde:MaterialWindow.Resources>
<ResourceDictionary>
<ResourceDictionary.MergedDictionaries>
<ResourceDictionary Source="pack://application:,,,/MaterialDesignThemes.Wpf;component/Themes/MaterialDesignTheme.PopupBox.xaml" />
</ResourceDictionary.MergedDictionaries>
</ResourceDictionary>
</mde:MaterialWindow.Resources>
<materialDesign:DialogHost IsTabStop="False"
Focusable="False"
Identifier="DeviceDialog"
DialogTheme="Inherit"
SnackbarMessageQueue="{Binding LayerHintsMessageQueue}">
<DockPanel>
<mde:AppBar Type="Dense"
Title="{Binding Layer.Name}"
ShowShadow="True"
DockPanel.Dock="Top"
Margin="-18 0 0 0">
<mde:AppBar.AppIcon>
<materialDesign:PackIcon Kind="AutoFix" Width="20" Height="28" />
</mde:AppBar.AppIcon>
</mde:AppBar>
<Grid Margin="15">
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="*" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<TextBlock Grid.Row="0" Style="{StaticResource MaterialDesignHeadline6TextBlock}">
Introduction
</TextBlock>
<TextBlock Grid.Row="1" Style="{StaticResource MaterialDesignTextBlock}" TextWrapping="Wrap">
In this window you can tell Artemis how this layer should be adapted when the profile is applied to a different set of devices by providing so-called adaption hints.
<LineBreak />
When sharing your profile with other people good hints help them import your profile without the need for manual adjustments.
</TextBlock>
<TextBlock Grid.Row="2" Style="{StaticResource MaterialDesignHeadline6TextBlock}" Margin="0 25 0 0">
Adaption hints
</TextBlock>
<ScrollViewer Grid.Row="3">
<ItemsControl ItemsSource="{Binding Items}" Margin="0 0 10 0">
<ItemsControl.ItemTemplate>
<DataTemplate>
<materialDesign:TransitioningContent OpeningEffect="{materialDesign:TransitionEffect SlideInFromLeft}"
OpeningEffectsOffset="{materialDesign:IndexedItemOffsetMultiplier 0:0:0.05}">
<ContentControl s:View.Model="{Binding}" VerticalContentAlignment="Stretch" HorizontalContentAlignment="Stretch" IsTabStop="False" />
</materialDesign:TransitioningContent>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</ScrollViewer>
<!-- Buttons -->
<Grid Grid.Row="4">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
<ColumnDefinition />
</Grid.ColumnDefinitions>
<Button Grid.Column="0"
Style="{StaticResource MaterialDesignOutlinedButton}"
Margin="0 8 8 0"
Command="{s:Action AutoDetermineHints}"
ToolTip="Attempt to automatically determine relevant hint(s) for this layer">
AUTO-DETERMINE HINTS
</Button>
<materialDesign:PopupBox Grid.Column="1"
Style="{StaticResource MaterialDesignMultiFloatingActionAccentPopupBox}"
ToolTip="Add a new hint"
HorizontalAlignment="Right"
Margin="0 5 0 0">
<StackPanel>
<Button ToolTip="Add a category hint" Content="{materialDesign:PackIcon Kind=Desk, Size=20}" Command="{s:Action AddCategoryHint}" />
<Button ToolTip="Add a device type hint" Content="{materialDesign:PackIcon Kind=Devices, Size=20}" Command="{s:Action AddDeviceHint}" />
<Button ToolTip="Add a keyboard-section hint" Content="{materialDesign:PackIcon Kind=Keyboard, Size=20}" Command="{s:Action AddKeyboardSectionHint}" />
</StackPanel>
</materialDesign:PopupBox>
</Grid>
</Grid>
</DockPanel>
</materialDesign:DialogHost>
</mde:MaterialWindow>

View File

@ -0,0 +1,114 @@
using System;
using System.Collections.Generic;
using Artemis.Core;
using Artemis.Core.Services;
using Artemis.UI.Ninject.Factories;
using Artemis.UI.Screens.ProfileEditor.ProfileTree.Dialogs.AdaptionHints;
using Artemis.UI.Shared.Services;
using MaterialDesignThemes.Wpf;
using Stylet;
namespace Artemis.UI.Screens.ProfileEditor.ProfileTree.Dialogs
{
public class LayerHintsDialogViewModel : Conductor<AdaptionHintViewModel>.Collection.AllActive
{
private readonly IRgbService _rgbService;
private readonly ILayerHintVmFactory _vmFactory;
private readonly IProfileEditorService _profileEditorService;
private SnackbarMessageQueue _layerHintsMessageQueue;
public LayerHintsDialogViewModel(Layer layer, IRgbService rgbService, ILayerHintVmFactory vmFactory, IProfileEditorService profileEditorService)
{
_rgbService = rgbService;
_vmFactory = vmFactory;
_profileEditorService = profileEditorService;
Layer = layer;
DisplayName = "Layer hints | Artemis";
}
public Layer Layer { get; }
public SnackbarMessageQueue LayerHintsMessageQueue
{
get => _layerHintsMessageQueue;
set => SetAndNotify(ref _layerHintsMessageQueue, value);
}
public void AutoDetermineHints()
{
List<IAdaptionHint> newHints = Layer.Adapter.DetermineHints(_rgbService.EnabledDevices);
CreateHintViewModels(newHints);
}
public void AddCategoryHint()
{
CategoryAdaptionHint hint = new();
Layer.Adapter.AdaptionHints.Add(hint);
Items.Add(_vmFactory.CategoryAdaptionHintViewModel(hint));
}
public void AddDeviceHint()
{
DeviceAdaptionHint hint = new();
Layer.Adapter.AdaptionHints.Add(hint);
Items.Add(_vmFactory.DeviceAdaptionHintViewModel(hint));
}
public void AddKeyboardSectionHint()
{
KeyboardSectionAdaptionHint hint = new();
Layer.Adapter.AdaptionHints.Add(hint);
Items.Add(_vmFactory.KeyboardSectionAdaptionHintViewModel(hint));
}
public void RemoveAdaptionHint(AdaptionHintViewModel adaptionHintViewModel)
{
Layer.Adapter.AdaptionHints.Remove(adaptionHintViewModel.AdaptionHint);
Items.Remove(adaptionHintViewModel);
}
#region Overrides of Screen
/// <inheritdoc />
protected override void OnInitialActivate()
{
LayerHintsMessageQueue = new SnackbarMessageQueue(TimeSpan.FromSeconds(5));
CreateHintViewModels(Layer.Adapter.AdaptionHints);
base.OnInitialActivate();
}
#region Overrides of AllActive
/// <inheritdoc />
protected override void OnClose()
{
_profileEditorService.UpdateSelectedProfileElement();
base.OnClose();
}
#endregion
#endregion
private void CreateHintViewModels(List<IAdaptionHint> hints)
{
foreach (IAdaptionHint adapterAdaptionHint in hints)
{
switch (adapterAdaptionHint)
{
case CategoryAdaptionHint categoryAdaptionHint:
Items.Add(_vmFactory.CategoryAdaptionHintViewModel(categoryAdaptionHint));
break;
case DeviceAdaptionHint deviceAdaptionHint:
Items.Add(_vmFactory.DeviceAdaptionHintViewModel(deviceAdaptionHint));
break;
case KeyboardSectionAdaptionHint keyboardSectionAdaptionHint:
Items.Add(_vmFactory.KeyboardSectionAdaptionHintViewModel(keyboardSectionAdaptionHint));
break;
}
}
}
}
}

View File

@ -35,6 +35,12 @@
</MenuItem.Icon>
</MenuItem>
<Separator />
<MenuItem Header="View adaption hints" Command="{s:Action OpenAdaptionHints}">
<MenuItem.Icon>
<materialDesign:PackIcon Kind="AutoFix" />
</MenuItem.Icon>
</MenuItem>
<Separator />
<MenuItem Header="Rename" Command="{s:Action RenameElement}" InputGestureText="F2">
<MenuItem.Icon>
<materialDesign:PackIcon Kind="RenameBox" />

View File

@ -2,19 +2,32 @@
using Artemis.Core.Services;
using Artemis.UI.Ninject.Factories;
using Artemis.UI.Shared.Services;
using Stylet;
namespace Artemis.UI.Screens.ProfileEditor.ProfileTree.TreeItem
{
public class LayerViewModel : TreeItemViewModel
{
private readonly IWindowManager _windowManager;
private ILayerHintVmFactory _vmFactory;
public LayerViewModel(ProfileElement layer,
IRgbService rgbService,
IProfileEditorService profileEditorService,
IDialogService dialogService,
IProfileTreeVmFactory profileTreeVmFactory,
ILayerBrushService layerBrushService) :
ILayerBrushService layerBrushService,
IWindowManager windowManager,
ILayerHintVmFactory vmFactory) :
base(layer, rgbService, profileEditorService, dialogService, profileTreeVmFactory, layerBrushService)
{
_windowManager = windowManager;
_vmFactory = vmFactory;
}
public void OpenAdaptionHints()
{
_windowManager.ShowDialog(_vmFactory.LayerHintsDialogViewModel(Layer));
}
public Layer Layer => ProfileElement as Layer;

View File

@ -21,7 +21,41 @@
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<StackPanel Grid.Row="0">
<Grid>
<!-- Layout -->
<TextBlock Style="{StaticResource MaterialDesignSubtitle1TextBlock}">
Categories
</TextBlock>
<TextBlock Style="{StaticResource MaterialDesignCaptionTextBlock}"
Foreground="{DynamicResource MaterialDesignBodyLight}"
TextWrapping="Wrap"
TextAlignment="Justify">
Artemis uses categories to determine where the layers of imported profiles are applied to. <LineBreak/>
You can hover over a category for a more detailed description.
</TextBlock>
<StackPanel Orientation="Horizontal">
<CheckBox Style="{StaticResource MaterialDesignFilterChipOutlineCheckBox}"
IsChecked="{Binding HasDeskCategory}"
ToolTip="A device acting as desk ornamentation such as a LED strip"
Content="Desk" />
<CheckBox Style="{StaticResource MaterialDesignFilterChipOutlineCheckBox}"
IsChecked="{Binding HasMonitorCategory}"
ToolTip="A device attached to the monitor such as ambilight LEDs"
Content="Monitor" />
<CheckBox Style="{StaticResource MaterialDesignFilterChipOutlineCheckBox}"
ToolTip="A device inside your computer case"
IsChecked="{Binding HasCaseCategory}"
Content="Case" />
<CheckBox Style="{StaticResource MaterialDesignFilterChipOutlineCheckBox}"
IsChecked="{Binding HasRoomCategory}"
ToolTip="A device elsewhere in the room"
Content="Room" />
<CheckBox Style="{StaticResource MaterialDesignFilterChipOutlineCheckBox}"
IsChecked="{Binding HasPeripheralsCategory}"
ToolTip="A peripheral such as a mouse or keyboard"
Content="Peripheral" />
</StackPanel>
<Grid Margin="0 25">
<Grid.ColumnDefinitions>
<ColumnDefinition />
<ColumnDefinition Width="40" />
@ -157,7 +191,7 @@
</Grid>
<!-- Layout -->
<TextBlock Style="{StaticResource MaterialDesignSubtitle1TextBlock}" Margin="0 25 0 0">
<TextBlock Style="{StaticResource MaterialDesignSubtitle1TextBlock}">
Custom layout
</TextBlock>
<TextBlock Style="{StaticResource MaterialDesignCaptionTextBlock}"

View File

@ -22,6 +22,7 @@ namespace Artemis.UI.Screens.Settings.Device.Tabs
private SKColor _currentColor;
private bool _displayOnDevices;
private float _greenScale;
private List<DeviceCategory> _categories;
private float _initialBlueScale;
private float _initialGreenScale;
private float _initialRedScale;
@ -100,13 +101,45 @@ namespace Artemis.UI.Screens.Settings.Device.Tabs
get => _displayOnDevices;
set => SetAndNotify(ref _displayOnDevices, value);
}
// This solution won't scale well but I don't expect there to be many more categories.
// If for some reason there will be, dynamically creating a view model per category may be more appropriate
public bool HasDeskCategory
{
get => GetCategory(DeviceCategory.Desk);
set => SetCategory(DeviceCategory.Desk, value);
}
public bool HasMonitorCategory
{
get => GetCategory(DeviceCategory.Monitor);
set => SetCategory(DeviceCategory.Monitor, value);
}
public bool HasCaseCategory
{
get => GetCategory(DeviceCategory.Case);
set => SetCategory(DeviceCategory.Case, value);
}
public bool HasRoomCategory
{
get => GetCategory(DeviceCategory.Room);
set => SetCategory(DeviceCategory.Room, value);
}
public bool HasPeripheralsCategory
{
get => GetCategory(DeviceCategory.Peripherals);
set => SetCategory(DeviceCategory.Peripherals, value);
}
public void ApplyScaling()
{
Device.RedScale = RedScale / 100f;
Device.GreenScale = GreenScale / 100f;
Device.BlueScale = BlueScale / 100f;
_rgbService.Surface.Update(true);
}
@ -151,6 +184,10 @@ namespace Artemis.UI.Screens.Settings.Device.Tabs
Device.RedScale = RedScale / 100f;
Device.GreenScale = GreenScale / 100f;
Device.BlueScale = BlueScale / 100f;
Device.Categories.Clear();
foreach (DeviceCategory deviceCategory in _categories)
Device.Categories.Add(deviceCategory);
_rgbService.SaveDevice(Device);
_coreService.ModuleRenderingDisabled = false;
@ -158,6 +195,12 @@ namespace Artemis.UI.Screens.Settings.Device.Tabs
public void Reset()
{
HasDeskCategory = Device.Categories.Contains(DeviceCategory.Desk);
HasMonitorCategory = Device.Categories.Contains(DeviceCategory.Monitor);
HasCaseCategory = Device.Categories.Contains(DeviceCategory.Case);
HasRoomCategory = Device.Categories.Contains(DeviceCategory.Room);
HasPeripheralsCategory = Device.Categories.Contains(DeviceCategory.Peripherals);
Device.RedScale = _initialRedScale;
Device.GreenScale = _initialGreenScale;
Device.BlueScale = _initialBlueScale;
@ -176,6 +219,7 @@ namespace Artemis.UI.Screens.Settings.Device.Tabs
_initialRedScale = Device.RedScale;
_initialGreenScale = Device.GreenScale;
_initialBlueScale = Device.BlueScale;
_categories = new List<DeviceCategory>(Device.Categories);
CurrentColor = SKColors.White;
_coreService.FrameRendering += OnFrameRendering;
@ -192,6 +236,21 @@ namespace Artemis.UI.Screens.Settings.Device.Tabs
base.OnDeactivate();
}
private bool GetCategory(DeviceCategory category)
{
return _categories.Contains(category);
}
private void SetCategory(DeviceCategory category, bool value)
{
if (value && !_categories.Contains(category))
_categories.Add(category);
else
_categories.Remove(category);
NotifyOfPropertyChange($"Has{category}Category");
}
#region Event handlers
private void DeviceOnPropertyChanged(object sender, PropertyChangedEventArgs e)