diff --git a/src/Artemis.Core/Models/Profile/Conditions/DataModelConditionEvent.cs b/src/Artemis.Core/Models/Profile/Conditions/DataModelConditionEvent.cs index 8ae6d5411..64529c531 100644 --- a/src/Artemis.Core/Models/Profile/Conditions/DataModelConditionEvent.cs +++ b/src/Artemis.Core/Models/Profile/Conditions/DataModelConditionEvent.cs @@ -12,7 +12,6 @@ namespace Artemis.Core { private bool _disposed; private bool _reinitializing; - private DateTime _lastTrigger; /// /// Creates a new instance of the class @@ -39,6 +38,8 @@ namespace Artemis.Core /// public DataModelPath? EventPath { get; private set; } + public DateTime LastTrigger { get; private set; } + /// /// Gets or sets the type of argument the event provides /// @@ -55,10 +56,10 @@ namespace Artemis.Core if (EventPath?.GetValue() is not IDataModelEvent dataModelEvent) return false; // Only evaluate to true once every time the event has been triggered since the last evaluation - if (dataModelEvent.LastTrigger <= _lastTrigger) + if (dataModelEvent.LastTrigger <= LastTrigger) return false; - _lastTrigger = DateTime.Now; + LastTrigger = DateTime.Now; // If there is a child (root group), it must evaluate to true whenever the event triggered if (Children.Any()) @@ -171,8 +172,8 @@ namespace Artemis.Core AddChild(new DataModelConditionGroup(this)); } - if (EventPath?.GetValue() is IDataModelEvent dataModelEvent) - _lastTrigger = dataModelEvent.LastTrigger; + if (EventPath?.GetValue() is IDataModelEvent dataModelEvent) + LastTrigger = dataModelEvent.LastTrigger; } private Type? GetEventArgumentType() diff --git a/src/Artemis.Core/Models/Profile/Layer.cs b/src/Artemis.Core/Models/Profile/Layer.cs index ace2734a9..9a98dfcc2 100644 --- a/src/Artemis.Core/Models/Profile/Layer.cs +++ b/src/Artemis.Core/Models/Profile/Layer.cs @@ -287,7 +287,7 @@ namespace Artemis.Core if (!Enabled || Path == null || LayerShape?.Path == null || !General.PropertiesInitialized || !Transform.PropertiesInitialized) return; // Ensure the brush is ready - if (LayerBrush?.BaseProperties?.PropertiesInitialized == false) + if (LayerBrush == null || LayerBrush?.BaseProperties?.PropertiesInitialized == false) return; RenderTimeline(Timeline, canvas); diff --git a/src/Artemis.Core/Models/Surface/ArtemisDevice.cs b/src/Artemis.Core/Models/Surface/ArtemisDevice.cs index 661130160..9146097e2 100644 --- a/src/Artemis.Core/Models/Surface/ArtemisDevice.cs +++ b/src/Artemis.Core/Models/Surface/ArtemisDevice.cs @@ -306,7 +306,7 @@ namespace Artemis.Core { // Take out invalid file name chars, may not be perfect but neither are you string fileName = System.IO.Path.GetInvalidFileNameChars().Aggregate(RgbDevice.DeviceInfo.Model, (current, c) => current.Replace(c, '-')); - if (RgbDevice is IKeyboard) + if (RgbDevice.DeviceInfo.DeviceType == RGBDeviceType.Keyboard) fileName = $"{fileName}-{PhysicalLayout.ToString().ToUpper()}"; if (includeExtension) fileName = $"{fileName}.xml"; @@ -353,7 +353,7 @@ namespace Artemis.Core { RgbDevice.Rotation = DeviceEntity.Rotation; RgbDevice.Scale = DeviceEntity.Scale; - + // Workaround for device rotation not applying if (DeviceEntity.X == 0 && DeviceEntity.Y == 0) RgbDevice.Location = new Point(1, 1); @@ -388,14 +388,19 @@ namespace Artemis.Core private void ApplyKeyboardLayout() { - if (!(RgbDevice is IKeyboard keyboard)) + if (RgbDevice.DeviceInfo.DeviceType != RGBDeviceType.Keyboard) return; + IKeyboard? keyboard = RgbDevice as IKeyboard; // If supported, detect the device layout so that we can load the correct one - if (DeviceProvider.CanDetectLogicalLayout) - LogicalLayout = DeviceProvider.GetLogicalLayout(keyboard); - if (DeviceProvider.CanDetectPhysicalLayout) + if (DeviceProvider.CanDetectPhysicalLayout && keyboard != null) PhysicalLayout = (KeyboardLayoutType) keyboard.DeviceInfo.Layout; + else + PhysicalLayout = (KeyboardLayoutType) DeviceEntity.PhysicalLayout; + if (DeviceProvider.CanDetectLogicalLayout && keyboard != null) + LogicalLayout = DeviceProvider.GetLogicalLayout(keyboard); + else + LogicalLayout = DeviceEntity.LogicalLayout; } #region Events diff --git a/src/Artemis.Core/Models/Surface/Layout/ArtemisLayout.cs b/src/Artemis.Core/Models/Surface/Layout/ArtemisLayout.cs index cc837ca3b..7ff43d6ef 100644 --- a/src/Artemis.Core/Models/Surface/Layout/ArtemisLayout.cs +++ b/src/Artemis.Core/Models/Surface/Layout/ArtemisLayout.cs @@ -65,12 +65,6 @@ namespace Artemis.Core /// public LayoutCustomDeviceData LayoutCustomDeviceData { get; private set; } = null!; - public void ReloadFromDisk() - { - Leds.Clear(); - LoadLayout(); - } - internal void ApplyDevice(ArtemisDevice artemisDevice) { Device = artemisDevice; diff --git a/src/Artemis.Core/Services/DeviceService.cs b/src/Artemis.Core/Services/DeviceService.cs index 68329ac77..e39616844 100644 --- a/src/Artemis.Core/Services/DeviceService.cs +++ b/src/Artemis.Core/Services/DeviceService.cs @@ -1,4 +1,5 @@ -using System.Linq; +using System.Collections.Generic; +using System.Linq; using System.Threading.Tasks; using RGB.NET.Core; diff --git a/src/Artemis.Core/Services/WebServer/EndPoints/DataModelJsonPluginEndPoint.cs b/src/Artemis.Core/Services/WebServer/EndPoints/DataModelJsonPluginEndPoint.cs index 30532aa2b..052c9c5a3 100644 --- a/src/Artemis.Core/Services/WebServer/EndPoints/DataModelJsonPluginEndPoint.cs +++ b/src/Artemis.Core/Services/WebServer/EndPoints/DataModelJsonPluginEndPoint.cs @@ -15,9 +15,18 @@ namespace Artemis.Core.Services /// public class DataModelJsonPluginEndPoint : PluginEndPoint where T : DataModel { + private readonly ProfileModule? _profileModule; private readonly Module? _module; private readonly DataModelExpansion? _dataModelExpansion; + internal DataModelJsonPluginEndPoint(ProfileModule profileModule, string name, PluginsModule pluginsModule) : base(profileModule, name, pluginsModule) + { + _profileModule = profileModule ?? throw new ArgumentNullException(nameof(profileModule)); + + ThrowOnFail = true; + Accepts = MimeType.Json; + } + internal DataModelJsonPluginEndPoint(Module module, string name, PluginsModule pluginsModule) : base(module, name, pluginsModule) { _module = module ?? throw new ArgumentNullException(nameof(module)); @@ -54,7 +63,9 @@ namespace Artemis.Core.Services using TextReader reader = context.OpenRequestText(); try { - if (_module != null) + if (_profileModule != null) + JsonConvert.PopulateObject(await reader.ReadToEndAsync(), _profileModule.DataModel); + else if (_module != null) JsonConvert.PopulateObject(await reader.ReadToEndAsync(), _module.DataModel); else JsonConvert.PopulateObject(await reader.ReadToEndAsync(), _dataModelExpansion!.DataModel); diff --git a/src/Artemis.Core/Services/WebServer/Interfaces/IWebServerService.cs b/src/Artemis.Core/Services/WebServer/Interfaces/IWebServerService.cs index ef98d3fa0..d88e3da3b 100644 --- a/src/Artemis.Core/Services/WebServer/Interfaces/IWebServerService.cs +++ b/src/Artemis.Core/Services/WebServer/Interfaces/IWebServerService.cs @@ -54,6 +54,15 @@ namespace Artemis.Core.Services /// The resulting end point DataModelJsonPluginEndPoint AddDataModelJsonEndPoint(Module module, string endPointName) where T : DataModel; + /// + /// Adds a new endpoint that directly maps received JSON to the data model of the provided . + /// + /// The data model type of the module + /// The module whose datamodel to apply the received JSON to + /// The name of the end point, must be unique + /// The resulting end point + DataModelJsonPluginEndPoint AddDataModelJsonEndPoint(ProfileModule profileModule, string endPointName) where T : DataModel; + /// /// Adds a new endpoint that directly maps received JSON to the data model of the provided . /// diff --git a/src/Artemis.Core/Services/WebServer/WebServerService.cs b/src/Artemis.Core/Services/WebServer/WebServerService.cs index 8de5ce9e4..d74035da2 100644 --- a/src/Artemis.Core/Services/WebServer/WebServerService.cs +++ b/src/Artemis.Core/Services/WebServer/WebServerService.cs @@ -136,6 +136,15 @@ namespace Artemis.Core.Services return endPoint; } + public DataModelJsonPluginEndPoint AddDataModelJsonEndPoint(ProfileModule profileModule, string endPointName) where T : DataModel + { + if (profileModule == null) throw new ArgumentNullException(nameof(profileModule)); + if (endPointName == null) throw new ArgumentNullException(nameof(endPointName)); + DataModelJsonPluginEndPoint endPoint = new(profileModule, endPointName, PluginsModule); + PluginsModule.AddPluginEndPoint(endPoint); + return endPoint; + } + public DataModelJsonPluginEndPoint AddDataModelJsonEndPoint(DataModelExpansion dataModelExpansion, string endPointName) where T : DataModel { if (dataModelExpansion == null) throw new ArgumentNullException(nameof(dataModelExpansion)); diff --git a/src/Artemis.UI.Shared/Controls/DeviceVisualizer.cs b/src/Artemis.UI.Shared/Controls/DeviceVisualizer.cs index 397dd2dae..21e82e6b1 100644 --- a/src/Artemis.UI.Shared/Controls/DeviceVisualizer.cs +++ b/src/Artemis.UI.Shared/Controls/DeviceVisualizer.cs @@ -288,12 +288,12 @@ namespace Artemis.UI.Shared private void DeviceUpdated(object? sender, EventArgs e) { - SetupForDevice(); + Execute.PostToUIThread(SetupForDevice); } private void DevicePropertyChanged(object? sender, PropertyChangedEventArgs e) { - SetupForDevice(); + Execute.PostToUIThread(SetupForDevice); } private void Render() diff --git a/src/Artemis.UI.Shared/Controls/DeviceVisualizerLed.cs b/src/Artemis.UI.Shared/Controls/DeviceVisualizerLed.cs index 1e96549a9..35adaf092 100644 --- a/src/Artemis.UI.Shared/Controls/DeviceVisualizerLed.cs +++ b/src/Artemis.UI.Shared/Controls/DeviceVisualizerLed.cs @@ -12,9 +12,12 @@ namespace Artemis.UI.Shared { internal class DeviceVisualizerLed { + private const byte Dimmed = 100; + private const byte NonDimmed = 255; + private SolidColorBrush? _renderColorBrush; private Color _renderColor; - + public DeviceVisualizerLed(ArtemisLed led) { Led = led; @@ -48,7 +51,8 @@ namespace Artemis.UI.Shared byte g = Led.RgbLed.Color.GetG(); byte b = Led.RgbLed.Color.GetB(); - _renderColor.A = isDimmed ? 100 : 255; + _renderColor.A = (byte)(isDimmed ? 100 : 255); + _renderColor.A = isDimmed ? Dimmed : NonDimmed; _renderColor.R = r; _renderColor.G = g; _renderColor.B = b; diff --git a/src/Artemis.UI/Artemis.UI.csproj b/src/Artemis.UI/Artemis.UI.csproj index 362b05fa9..451ff0b03 100644 --- a/src/Artemis.UI/Artemis.UI.csproj +++ b/src/Artemis.UI/Artemis.UI.csproj @@ -126,6 +126,11 @@ + + + + + @@ -306,6 +311,11 @@ + + + + + diff --git a/src/Artemis.UI/Behaviors/HighlightTermBehavior.cs b/src/Artemis.UI/Behaviors/HighlightTermBehavior.cs new file mode 100644 index 000000000..2d30ae887 --- /dev/null +++ b/src/Artemis.UI/Behaviors/HighlightTermBehavior.cs @@ -0,0 +1,169 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text.RegularExpressions; +using System.Windows; +using System.Windows.Controls; +using System.Windows.Documents; +using System.Windows.Media; + +namespace Artemis.UI.Behaviors +{ + // Source: https://stackoverflow.com/a/60474831/5015269 + // Made some changes to add a foreground and background property + public static class HighlightTermBehavior + { + public static readonly DependencyProperty TextProperty = DependencyProperty.RegisterAttached( + "Text", + typeof(string), + typeof(HighlightTermBehavior), + new FrameworkPropertyMetadata("", OnTextChanged)); + + public static readonly DependencyProperty TermToBeHighlightedProperty = DependencyProperty.RegisterAttached( + "TermToBeHighlighted", + typeof(string), + typeof(HighlightTermBehavior), + new FrameworkPropertyMetadata("", OnTextChanged)); + + public static readonly DependencyProperty HighlightForegroundProperty = DependencyProperty.RegisterAttached( + "HighlightForeground", + typeof(Color?), + typeof(HighlightTermBehavior), + new FrameworkPropertyMetadata(null, OnTextChanged)); + + public static readonly DependencyProperty HighlightBackgroundProperty = DependencyProperty.RegisterAttached( + "HighlightBackground", + typeof(Color?), + typeof(HighlightTermBehavior), + new FrameworkPropertyMetadata(null, OnTextChanged)); + + public static string GetText(FrameworkElement frameworkElement) + { + return (string) frameworkElement.GetValue(TextProperty); + } + + public static void SetText(FrameworkElement frameworkElement, string value) + { + frameworkElement.SetValue(TextProperty, value); + } + + public static string GetTermToBeHighlighted(FrameworkElement frameworkElement) + { + return (string) frameworkElement.GetValue(TermToBeHighlightedProperty); + } + + public static void SetTermToBeHighlighted(FrameworkElement frameworkElement, string value) + { + frameworkElement.SetValue(TermToBeHighlightedProperty, value); + } + + public static void SetHighlightForeground(FrameworkElement frameworkElement, Color? value) + { + frameworkElement.SetValue(HighlightForegroundProperty, value); + } + + public static Color? GetHighlightForeground(FrameworkElement frameworkElement) + { + return (Color?) frameworkElement.GetValue(HighlightForegroundProperty); + } + + public static void SetHighlightBackground(FrameworkElement frameworkElement, Color? value) + { + frameworkElement.SetValue(HighlightBackgroundProperty, value); + } + + public static Color? GetHighlightBackground(FrameworkElement frameworkElement) + { + return (Color?) frameworkElement.GetValue(HighlightBackgroundProperty); + } + + public static List SplitTextIntoTermAndNotTermParts(string text, string term) + { + if (string.IsNullOrEmpty(text)) + return new List {string.Empty}; + + return Regex.Split(text, $@"({Regex.Escape(term)})", RegexOptions.IgnoreCase) + .Where(p => p != string.Empty) + .ToList(); + } + + + private static void OnTextChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) + { + if (d is TextBlock textBlock) + SetTextBlockTextAndHighlightTerm(textBlock, GetText(textBlock), GetTermToBeHighlighted(textBlock)); + } + + private static void SetTextBlockTextAndHighlightTerm(TextBlock textBlock, string text, string termToBeHighlighted) + { + textBlock.Text = string.Empty; + + if (TextIsEmpty(text)) + return; + + if (TextIsNotContainingTermToBeHighlighted(text, termToBeHighlighted)) + { + AddPartToTextBlock(textBlock, text); + return; + } + + List textParts = SplitTextIntoTermAndNotTermParts(text, termToBeHighlighted); + + foreach (string textPart in textParts) + AddPartToTextBlockAndHighlightIfNecessary(textBlock, termToBeHighlighted, textPart); + } + + private static bool TextIsEmpty(string text) + { + return text.Length == 0; + } + + private static bool TextIsNotContainingTermToBeHighlighted(string text, string termToBeHighlighted) + { + if (text == null || termToBeHighlighted == null) + return true; + return text.Contains(termToBeHighlighted, StringComparison.OrdinalIgnoreCase) == false; + } + + private static void AddPartToTextBlockAndHighlightIfNecessary(TextBlock textBlock, string termToBeHighlighted, string textPart) + { + if (textPart.Equals(termToBeHighlighted, StringComparison.OrdinalIgnoreCase)) + AddHighlightedPartToTextBlock(textBlock, textPart); + else + AddPartToTextBlock(textBlock, textPart); + } + + private static void AddPartToTextBlock(TextBlock textBlock, string part) + { + textBlock.Inlines.Add(new Run {Text = part}); + } + + private static void AddHighlightedPartToTextBlock(TextBlock textBlock, string part) + { + Color? foreground = GetHighlightForeground(textBlock); + Color? background = GetHighlightBackground(textBlock); + + if (background == null) + { + Run run = new() {Text = part, FontWeight = FontWeights.ExtraBold}; + if (foreground != null) + run.Foreground = new SolidColorBrush(foreground.Value); + textBlock.Inlines.Add(run); + return; + } + + Border border = new() + { + Background = new SolidColorBrush(background.Value), + BorderThickness = new Thickness(0), + CornerRadius = new CornerRadius(2), + Child = new TextBlock {Text = part, FontWeight = FontWeights.Bold}, + Padding = new Thickness(1), + Margin = new Thickness(-1, -5, -1, -5) + }; + if (foreground != null) + ((TextBlock) border.Child).Foreground = new SolidColorBrush(foreground.Value); + textBlock.Inlines.Add(border); + } + } +} \ No newline at end of file diff --git a/src/Artemis.UI/Bootstrapper.cs b/src/Artemis.UI/Bootstrapper.cs index 5544c2307..585b1b1a4 100644 --- a/src/Artemis.UI/Bootstrapper.cs +++ b/src/Artemis.UI/Bootstrapper.cs @@ -90,6 +90,9 @@ namespace Artemis.UI IRegistrationService registrationService = Kernel.Get(); registrationService.RegisterInputProvider(); registrationService.RegisterControllers(); + + // Initialize background services + Kernel.Get(); } protected override void ConfigureIoC(IKernel kernel) diff --git a/src/Artemis.UI/Resources/Images/PhysicalLayouts/abnt.png b/src/Artemis.UI/Resources/Images/PhysicalLayouts/abnt.png new file mode 100644 index 000000000..dc7593429 Binary files /dev/null and b/src/Artemis.UI/Resources/Images/PhysicalLayouts/abnt.png differ diff --git a/src/Artemis.UI/Resources/Images/PhysicalLayouts/ansi.png b/src/Artemis.UI/Resources/Images/PhysicalLayouts/ansi.png new file mode 100644 index 000000000..940a8f233 Binary files /dev/null and b/src/Artemis.UI/Resources/Images/PhysicalLayouts/ansi.png differ diff --git a/src/Artemis.UI/Resources/Images/PhysicalLayouts/iso.png b/src/Artemis.UI/Resources/Images/PhysicalLayouts/iso.png new file mode 100644 index 000000000..ee4272e81 Binary files /dev/null and b/src/Artemis.UI/Resources/Images/PhysicalLayouts/iso.png differ diff --git a/src/Artemis.UI/Resources/Images/PhysicalLayouts/jis.png b/src/Artemis.UI/Resources/Images/PhysicalLayouts/jis.png new file mode 100644 index 000000000..d872ddab3 Binary files /dev/null and b/src/Artemis.UI/Resources/Images/PhysicalLayouts/jis.png differ diff --git a/src/Artemis.UI/Resources/Images/PhysicalLayouts/ks.png b/src/Artemis.UI/Resources/Images/PhysicalLayouts/ks.png new file mode 100644 index 000000000..898f8a85e Binary files /dev/null and b/src/Artemis.UI/Resources/Images/PhysicalLayouts/ks.png differ diff --git a/src/Artemis.UI/Screens/ProfileEditor/Conditions/Abstract/DataModelConditionPredicateViewModel.cs b/src/Artemis.UI/Screens/ProfileEditor/Conditions/Abstract/DataModelConditionPredicateViewModel.cs index f0a4a4ba5..6efb8a33a 100644 --- a/src/Artemis.UI/Screens/ProfileEditor/Conditions/Abstract/DataModelConditionPredicateViewModel.cs +++ b/src/Artemis.UI/Screens/ProfileEditor/Conditions/Abstract/DataModelConditionPredicateViewModel.cs @@ -67,6 +67,11 @@ namespace Artemis.UI.Screens.ProfileEditor.Conditions.Abstract protected SolidColorBrush LeftSideColor { get; set; } + public override void Evaluate() + { + IsConditionMet = DataModelConditionPredicate.Evaluate(); + } + public override void Delete() { base.Delete(); diff --git a/src/Artemis.UI/Screens/ProfileEditor/Conditions/Abstract/DataModelConditionViewModel.cs b/src/Artemis.UI/Screens/ProfileEditor/Conditions/Abstract/DataModelConditionViewModel.cs index 0cae105e4..b2f94a711 100644 --- a/src/Artemis.UI/Screens/ProfileEditor/Conditions/Abstract/DataModelConditionViewModel.cs +++ b/src/Artemis.UI/Screens/ProfileEditor/Conditions/Abstract/DataModelConditionViewModel.cs @@ -8,6 +8,7 @@ namespace Artemis.UI.Screens.ProfileEditor.Conditions.Abstract public abstract class DataModelConditionViewModel : Conductor.Collection.AllActive { private DataModelDynamicViewModel _leftSideSelectionViewModel; + private bool _isConditionMet; protected DataModelConditionViewModel(DataModelConditionPart model) { @@ -22,8 +23,17 @@ namespace Artemis.UI.Screens.ProfileEditor.Conditions.Abstract set => SetAndNotify(ref _leftSideSelectionViewModel, value); } + public bool IsConditionMet + { + get => _isConditionMet; + set => SetAndNotify(ref _isConditionMet, value); + } + + public abstract void Update(); + public abstract void Evaluate(); + public virtual void Delete() { Model.Parent.RemoveChild(Model); diff --git a/src/Artemis.UI/Screens/ProfileEditor/Conditions/DataModelConditionEventView.xaml b/src/Artemis.UI/Screens/ProfileEditor/Conditions/DataModelConditionEventView.xaml index f71fb5e79..8b9a2b81b 100644 --- a/src/Artemis.UI/Screens/ProfileEditor/Conditions/DataModelConditionEventView.xaml +++ b/src/Artemis.UI/Screens/ProfileEditor/Conditions/DataModelConditionEventView.xaml @@ -13,7 +13,14 @@ + + + + + + + @@ -21,6 +28,7 @@ + @@ -52,7 +60,37 @@ triggered - + + + + + + + + + + + + + + + + + + + + + diff --git a/src/Artemis.UI/Screens/ProfileEditor/Conditions/DataModelConditionEventViewModel.cs b/src/Artemis.UI/Screens/ProfileEditor/Conditions/DataModelConditionEventViewModel.cs index b22a5fe17..e864ce342 100644 --- a/src/Artemis.UI/Screens/ProfileEditor/Conditions/DataModelConditionEventViewModel.cs +++ b/src/Artemis.UI/Screens/ProfileEditor/Conditions/DataModelConditionEventViewModel.cs @@ -15,6 +15,7 @@ namespace Artemis.UI.Screens.ProfileEditor.Conditions private readonly IDataModelConditionsVmFactory _dataModelConditionsVmFactory; private readonly IDataModelUIService _dataModelUIService; private readonly IProfileEditorService _profileEditorService; + private DateTime _lastTrigger; public DataModelConditionEventViewModel(DataModelConditionEvent dataModelConditionEvent, IProfileEditorService profileEditorService, @@ -24,10 +25,18 @@ namespace Artemis.UI.Screens.ProfileEditor.Conditions _profileEditorService = profileEditorService; _dataModelUIService = dataModelUIService; _dataModelConditionsVmFactory = dataModelConditionsVmFactory; + + _lastTrigger = DataModelConditionEvent.LastTrigger; } public DataModelConditionEvent DataModelConditionEvent => (DataModelConditionEvent) Model; + public DateTime LastTrigger + { + get => _lastTrigger; + set => SetAndNotify(ref _lastTrigger, value); + } + public void Initialize() { LeftSideSelectionViewModel = _dataModelUIService.GetDynamicSelectionViewModel(_profileEditorService.GetCurrentModule()); @@ -36,7 +45,7 @@ namespace Artemis.UI.Screens.ProfileEditor.Conditions IReadOnlyCollection editors = _dataModelUIService.RegisteredDataModelEditors; List supportedInputTypes = new() {typeof(DataModelEvent), typeof(DataModelEvent<>)}; - + LeftSideSelectionViewModel.FilterTypes = supportedInputTypes.ToArray(); LeftSideSelectionViewModel.ButtonBrush = new SolidColorBrush(Color.FromRgb(185, 164, 10)); LeftSideSelectionViewModel.Placeholder = "Select an event"; @@ -74,6 +83,12 @@ namespace Artemis.UI.Screens.ProfileEditor.Conditions childViewModel.Update(); } + public override void Evaluate() + { + LastTrigger = DataModelConditionEvent.LastTrigger; + IsConditionMet = DataModelConditionEvent.Evaluate(); + } + public void ApplyEvent() { DataModelConditionEvent.UpdateEvent(LeftSideSelectionViewModel.DataModelPath); @@ -104,7 +119,7 @@ namespace Artemis.UI.Screens.ProfileEditor.Conditions LeftSideSelectionViewModel.Dispose(); LeftSideSelectionViewModel.PropertySelected -= LeftSideSelectionViewModelOnPropertySelected; } - + #endregion } } \ No newline at end of file diff --git a/src/Artemis.UI/Screens/ProfileEditor/Conditions/DataModelConditionGroupView.xaml b/src/Artemis.UI/Screens/ProfileEditor/Conditions/DataModelConditionGroupView.xaml index 0e4f1b687..421cf840d 100644 --- a/src/Artemis.UI/Screens/ProfileEditor/Conditions/DataModelConditionGroupView.xaml +++ b/src/Artemis.UI/Screens/ProfileEditor/Conditions/DataModelConditionGroupView.xaml @@ -28,6 +28,7 @@ + @@ -115,9 +116,9 @@ - @@ -133,7 +134,27 @@ - + + + + + + + + + + + + diff --git a/src/Artemis.UI/Screens/ProfileEditor/Conditions/DataModelConditionGroupViewModel.cs b/src/Artemis.UI/Screens/ProfileEditor/Conditions/DataModelConditionGroupViewModel.cs index adc568df4..437fe3252 100644 --- a/src/Artemis.UI/Screens/ProfileEditor/Conditions/DataModelConditionGroupViewModel.cs +++ b/src/Artemis.UI/Screens/ProfileEditor/Conditions/DataModelConditionGroupViewModel.cs @@ -1,12 +1,10 @@ using System; -using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; using Artemis.Core; using Artemis.UI.Extensions; using Artemis.UI.Ninject.Factories; using Artemis.UI.Screens.ProfileEditor.Conditions.Abstract; -using Artemis.UI.Screens.ProfileEditor.DisplayConditions; using Artemis.UI.Shared.Services; using Humanizer; using Stylet; @@ -17,9 +15,9 @@ namespace Artemis.UI.Screens.ProfileEditor.Conditions { private readonly IDataModelConditionsVmFactory _dataModelConditionsVmFactory; private readonly IProfileEditorService _profileEditorService; + private bool _isEventGroup; private bool _isInitialized; private bool _isRootGroup; - private bool _isEventGroup; public DataModelConditionGroupViewModel(DataModelConditionGroup dataModelConditionGroup, ConditionGroupType groupType, @@ -31,7 +29,7 @@ namespace Artemis.UI.Screens.ProfileEditor.Conditions _profileEditorService = profileEditorService; _dataModelConditionsVmFactory = dataModelConditionsVmFactory; - Items.CollectionChanged += (sender, args) => NotifyOfPropertyChange(nameof(DisplayBooleanOperator)); + Items.CollectionChanged += (_, _) => NotifyOfPropertyChange(nameof(DisplayBooleanOperator)); Execute.PostToUIThread(async () => { @@ -58,7 +56,11 @@ namespace Artemis.UI.Screens.ProfileEditor.Conditions public bool IsEventGroup { get => _isEventGroup; - set => SetAndNotify(ref _isEventGroup, value); + set + { + SetAndNotify(ref _isEventGroup, value); + NotifyOfPropertyChange(nameof(DisplayEvaluationResult)); + } } public bool IsInitialized @@ -68,6 +70,7 @@ namespace Artemis.UI.Screens.ProfileEditor.Conditions } public bool DisplayBooleanOperator => Items.Count > 1; + public bool DisplayEvaluationResult => GroupType == ConditionGroupType.General && !IsEventGroup; public string SelectedBooleanOperator => DataModelConditionGroup.BooleanOperator.Humanize(); public void SelectBooleanOperator(string type) @@ -166,14 +169,19 @@ namespace Artemis.UI.Screens.ProfileEditor.Conditions IsEventGroup = Items.Any(i => i is DataModelConditionEventViewModel); if (IsEventGroup) - { if (DataModelConditionGroup.BooleanOperator != BooleanOperator.And) SelectBooleanOperator("And"); - } OnUpdated(); } + public override void Evaluate() + { + IsConditionMet = DataModelConditionGroup.Evaluate(); + foreach (DataModelConditionViewModel dataModelConditionViewModel in Items) + dataModelConditionViewModel.Evaluate(); + } + public void ConvertToConditionList(DataModelConditionViewModel predicateViewModel) { // Store the old index and remove the old predicate diff --git a/src/Artemis.UI/Screens/ProfileEditor/Conditions/DataModelConditionListView.xaml b/src/Artemis.UI/Screens/ProfileEditor/Conditions/DataModelConditionListView.xaml index f19dd3a0c..ac405354f 100644 --- a/src/Artemis.UI/Screens/ProfileEditor/Conditions/DataModelConditionListView.xaml +++ b/src/Artemis.UI/Screens/ProfileEditor/Conditions/DataModelConditionListView.xaml @@ -22,6 +22,7 @@ + @@ -79,7 +80,26 @@ - + + + + + + + + + + + + diff --git a/src/Artemis.UI/Screens/ProfileEditor/Conditions/DataModelConditionListViewModel.cs b/src/Artemis.UI/Screens/ProfileEditor/Conditions/DataModelConditionListViewModel.cs index ef1b68d2d..746955bba 100644 --- a/src/Artemis.UI/Screens/ProfileEditor/Conditions/DataModelConditionListViewModel.cs +++ b/src/Artemis.UI/Screens/ProfileEditor/Conditions/DataModelConditionListViewModel.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using System.Linq; +using System.Windows.Forms.VisualStyles; using System.Windows.Media; using Artemis.Core; using Artemis.UI.Ninject.Factories; @@ -57,6 +58,13 @@ namespace Artemis.UI.Screens.ProfileEditor.Conditions _profileEditorService.UpdateSelectedProfileElement(); } + public override void Evaluate() + { + IsConditionMet = DataModelConditionList.Evaluate(); + foreach (DataModelConditionViewModel dataModelConditionViewModel in Items) + dataModelConditionViewModel.Evaluate(); + } + public override void Delete() { base.Delete(); diff --git a/src/Artemis.UI/Screens/ProfileEditor/Conditions/Predicate/DataModelConditionEventPredicateViewModel.cs b/src/Artemis.UI/Screens/ProfileEditor/Conditions/Predicate/DataModelConditionEventPredicateViewModel.cs index 71e30559e..185ec12c0 100644 --- a/src/Artemis.UI/Screens/ProfileEditor/Conditions/Predicate/DataModelConditionEventPredicateViewModel.cs +++ b/src/Artemis.UI/Screens/ProfileEditor/Conditions/Predicate/DataModelConditionEventPredicateViewModel.cs @@ -71,5 +71,10 @@ namespace Artemis.UI.Screens.ProfileEditor.Conditions return wrapper.CreateViewModel(_dataModelUIService, new DataModelUpdateConfiguration(false)); } + + public override void Evaluate() + { + throw new NotImplementedException(); + } } } \ No newline at end of file diff --git a/src/Artemis.UI/Screens/ProfileEditor/Conditions/Predicate/DataModelConditionGeneralPredicateView.xaml b/src/Artemis.UI/Screens/ProfileEditor/Conditions/Predicate/DataModelConditionGeneralPredicateView.xaml index dfcac88d1..6bd65d3e7 100644 --- a/src/Artemis.UI/Screens/ProfileEditor/Conditions/Predicate/DataModelConditionGeneralPredicateView.xaml +++ b/src/Artemis.UI/Screens/ProfileEditor/Conditions/Predicate/DataModelConditionGeneralPredicateView.xaml @@ -27,6 +27,7 @@ + + + + + + + + + + + + + + + + + + Select a logical layout + + + Artemis couldn't automatically determine the logical layout of your . + While not as important as the physical layout, setting the correct logical layout will allow Artemis to show the right keycaps (if a matching layout file is present) + + + + + + + + + () + + + + + + + + diff --git a/src/Artemis.UI/Screens/Settings/Device/Tabs/DevicePropertiesTabViewModel.cs b/src/Artemis.UI/Screens/Settings/Device/Tabs/DevicePropertiesTabViewModel.cs index 4de139698..19f425e70 100644 --- a/src/Artemis.UI/Screens/Settings/Device/Tabs/DevicePropertiesTabViewModel.cs +++ b/src/Artemis.UI/Screens/Settings/Device/Tabs/DevicePropertiesTabViewModel.cs @@ -1,4 +1,5 @@ -using System.ComponentModel; +using System.Collections.Generic; +using System.ComponentModel; using System.Threading.Tasks; using System.Windows.Controls; using System.Windows.Input; @@ -15,6 +16,7 @@ namespace Artemis.UI.Screens.Settings.Device.Tabs { private readonly ICoreService _coreService; private readonly IMessageService _messageService; + private readonly IDialogService _dialogService; private readonly IRgbService _rgbService; private float _blueScale; private SKColor _currentColor; @@ -33,11 +35,13 @@ namespace Artemis.UI.Screens.Settings.Device.Tabs ICoreService coreService, IRgbService rgbService, IMessageService messageService, + IDialogService dialogService, IModelValidator validator) : base(validator) { _coreService = coreService; _rgbService = rgbService; _messageService = messageService; + _dialogService = dialogService; Device = device; DisplayName = "PROPERTIES"; @@ -126,6 +130,11 @@ namespace Artemis.UI.Screens.Settings.Device.Tabs } } + public async Task SelectPhysicalLayout() + { + await _dialogService.ShowDialog(new Dictionary {{"device", Device}}); + } + public async Task Apply() { await ValidateAsync(); @@ -167,12 +176,21 @@ namespace Artemis.UI.Screens.Settings.Device.Tabs _initialGreenScale = Device.GreenScale; _initialBlueScale = Device.BlueScale; CurrentColor = SKColors.White; + _coreService.FrameRendering += OnFrameRendering; Device.PropertyChanged += DeviceOnPropertyChanged; base.OnActivate(); } + protected override void OnDeactivate() + { + _coreService.FrameRendering -= OnFrameRendering; + Device.PropertyChanged -= DeviceOnPropertyChanged; + + base.OnDeactivate(); + } + #region Event handlers private void DeviceOnPropertyChanged(object sender, PropertyChangedEventArgs e) diff --git a/src/Artemis.UI/Screens/Settings/Tabs/Devices/DeviceSettingsTabViewModel.cs b/src/Artemis.UI/Screens/Settings/Tabs/Devices/DeviceSettingsTabViewModel.cs index 6c13a7266..e3cb174a6 100644 --- a/src/Artemis.UI/Screens/Settings/Tabs/Devices/DeviceSettingsTabViewModel.cs +++ b/src/Artemis.UI/Screens/Settings/Tabs/Devices/DeviceSettingsTabViewModel.cs @@ -24,12 +24,11 @@ namespace Artemis.UI.Screens.Settings.Tabs.Devices _settingsVmFactory = settingsVmFactory; } - protected override void OnActivate() + protected override void OnInitialActivate() { // Take it off the UI thread to avoid freezing on tab change Task.Run(async () => { - Items.Clear(); await Task.Delay(200); List instances = _rgbService.Devices.Select(d => _settingsVmFactory.CreateDeviceSettingsViewModel(d)).ToList(); @@ -37,7 +36,7 @@ namespace Artemis.UI.Screens.Settings.Tabs.Devices Items.Add(deviceSettingsViewModel); }); - base.OnActivate(); + base.OnInitialActivate(); } public async Task ShowDeviceDisableDialog() diff --git a/src/Artemis.UI/Screens/Settings/Tabs/Devices/DeviceSettingsViewModel.cs b/src/Artemis.UI/Screens/Settings/Tabs/Devices/DeviceSettingsViewModel.cs index c8cb5d0cd..2005b6753 100644 --- a/src/Artemis.UI/Screens/Settings/Tabs/Devices/DeviceSettingsViewModel.cs +++ b/src/Artemis.UI/Screens/Settings/Tabs/Devices/DeviceSettingsViewModel.cs @@ -84,7 +84,7 @@ namespace Artemis.UI.Screens.Settings.Tabs.Devices public void ViewProperties() { - _windowManager.ShowDialog(_deviceDebugVmFactory.DeviceDialogViewModel(Device)); + _windowManager.ShowWindow(_deviceDebugVmFactory.DeviceDialogViewModel(Device)); } private async Task UpdateIsDeviceEnabled(bool value) { diff --git a/src/Artemis.UI/Screens/Settings/Tabs/Plugins/PluginFeatureView.xaml b/src/Artemis.UI/Screens/Settings/Tabs/Plugins/PluginFeatureView.xaml index eb8f06d5f..bd8bc03ff 100644 --- a/src/Artemis.UI/Screens/Settings/Tabs/Plugins/PluginFeatureView.xaml +++ b/src/Artemis.UI/Screens/Settings/Tabs/Plugins/PluginFeatureView.xaml @@ -27,13 +27,13 @@ Width="20" VerticalAlignment="Center" HorizontalAlignment="Center" - Visibility="{Binding LoadException, Converter={StaticResource NullToVisibilityConverter}, ConverterParameter=Inverted}" /> + Visibility="{Binding LoadException, Converter={StaticResource NullToVisibilityConverter}, ConverterParameter=Inverted, FallbackValue=Collapsed}" /> - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + - \ No newline at end of file diff --git a/src/Artemis.UI/Screens/Settings/Tabs/Plugins/PluginSettingsTabViewModel.cs b/src/Artemis.UI/Screens/Settings/Tabs/Plugins/PluginSettingsTabViewModel.cs index 787ab9c83..fa9632187 100644 --- a/src/Artemis.UI/Screens/Settings/Tabs/Plugins/PluginSettingsTabViewModel.cs +++ b/src/Artemis.UI/Screens/Settings/Tabs/Plugins/PluginSettingsTabViewModel.cs @@ -4,6 +4,7 @@ using System.Linq; using System.Threading.Tasks; using Artemis.Core; using Artemis.Core.Services; +using Artemis.UI.Extensions; using Artemis.UI.Ninject.Factories; using Artemis.UI.Shared.Services; using Ookii.Dialogs.Wpf; @@ -43,26 +44,36 @@ namespace Artemis.UI.Screens.Settings.Tabs.Plugins if (_instances == null) return; - Items.Clear(); + List instances = _instances; + string search = SearchPluginInput?.ToLower(); + if (!string.IsNullOrWhiteSpace(search)) + instances = instances.Where(i => i.Plugin.Info.Name.ToLower().Contains(search) || + i.Plugin.Info.Description != null && i.Plugin.Info.Description.ToLower().Contains(search)).ToList(); - if (string.IsNullOrWhiteSpace(SearchPluginInput)) - Items.AddRange(_instances); - else - Items.AddRange(_instances.Where(i => i.Plugin.Info.Name.Contains(SearchPluginInput, StringComparison.OrdinalIgnoreCase) || - i.Plugin.Info.Description.Contains(SearchPluginInput, StringComparison.OrdinalIgnoreCase))); + foreach (PluginSettingsViewModel pluginSettingsViewModel in instances) + { + if (!Items.Contains(pluginSettingsViewModel)) + Items.Add(pluginSettingsViewModel); + } + foreach (PluginSettingsViewModel pluginSettingsViewModel in Items.ToList()) + { + if (!instances.Contains(pluginSettingsViewModel)) + Items.Remove(pluginSettingsViewModel); + } + + ((BindableCollection) Items).Sort(i => i.Plugin.Info.Name); } - protected override void OnActivate() + protected override void OnInitialActivate() { // Take it off the UI thread to avoid freezing on tab change Task.Run(async () => { - Items.Clear(); await Task.Delay(200); GetPluginInstances(); }); - base.OnActivate(); + base.OnInitialActivate(); } public void ImportPlugin() diff --git a/src/Artemis.UI/Screens/Settings/Tabs/Plugins/PluginSettingsView.xaml b/src/Artemis.UI/Screens/Settings/Tabs/Plugins/PluginSettingsView.xaml index 34f106e9f..746ef06fc 100644 --- a/src/Artemis.UI/Screens/Settings/Tabs/Plugins/PluginSettingsView.xaml +++ b/src/Artemis.UI/Screens/Settings/Tabs/Plugins/PluginSettingsView.xaml @@ -8,6 +8,7 @@ xmlns:devices="clr-namespace:Artemis.UI.Screens.Settings.Tabs.Plugins" xmlns:b="http://schemas.microsoft.com/xaml/behaviors" xmlns:shared="clr-namespace:Artemis.UI.Shared;assembly=Artemis.UI.Shared" + xmlns:behaviors="clr-namespace:Artemis.UI.Behaviors" d:DataContext="{d:DesignInstance devices:PluginSettingsViewModel}" mc:Ignorable="d" d:DesignHeight="450" d:DesignWidth="800"> @@ -34,20 +35,27 @@ + Width="48" + Height="48" + Margin="0 5 0 0" + Grid.Row="0" + Grid.RowSpan="2" + HorizontalAlignment="Center" + VerticalAlignment="Top" /> - + @@ -55,9 +63,9 @@ - - - + Plugin enabled - + Visibility="{Binding Plugin.Info.RequiresAdmin, Converter={x:Static s:BoolToVisibilityConverter.Instance}, Mode=OneWay}" /> @@ -128,7 +136,7 @@ - + diff --git a/src/Artemis.UI/Screens/Settings/Tabs/Plugins/PluginSettingsViewModel.cs b/src/Artemis.UI/Screens/Settings/Tabs/Plugins/PluginSettingsViewModel.cs index 06fa111be..f8394b325 100644 --- a/src/Artemis.UI/Screens/Settings/Tabs/Plugins/PluginSettingsViewModel.cs +++ b/src/Artemis.UI/Screens/Settings/Tabs/Plugins/PluginSettingsViewModel.cs @@ -76,7 +76,7 @@ namespace Artemis.UI.Screens.Settings.Tabs.Plugins try { PluginConfigurationViewModel viewModel = (PluginConfigurationViewModel) Plugin.Kernel.Get(configurationViewModel.Type); - _windowManager.ShowDialog(new PluginSettingsWindowViewModel(viewModel, Icon)); + _windowManager.ShowWindow(new PluginSettingsWindowViewModel(viewModel, Icon)); } catch (Exception e) { diff --git a/src/Artemis.UI/Screens/SurfaceEditor/SurfaceEditorViewModel.cs b/src/Artemis.UI/Screens/SurfaceEditor/SurfaceEditorViewModel.cs index 1700dc144..917024ec6 100644 --- a/src/Artemis.UI/Screens/SurfaceEditor/SurfaceEditorViewModel.cs +++ b/src/Artemis.UI/Screens/SurfaceEditor/SurfaceEditorViewModel.cs @@ -241,7 +241,7 @@ namespace Artemis.UI.Screens.SurfaceEditor public void ViewProperties(ArtemisDevice device) { - _windowManager.ShowDialog(_deviceDebugVmFactory.DeviceDialogViewModel(device)); + _windowManager.ShowWindow(_deviceDebugVmFactory.DeviceDialogViewModel(device)); } public async Task DetectInput(ArtemisDevice device) diff --git a/src/Artemis.UI/Services/DeviceLayoutService.cs b/src/Artemis.UI/Services/DeviceLayoutService.cs new file mode 100644 index 000000000..dd7a6a83d --- /dev/null +++ b/src/Artemis.UI/Services/DeviceLayoutService.cs @@ -0,0 +1,89 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using Artemis.Core; +using Artemis.Core.Services; +using Artemis.UI.Screens.Settings.Device; +using Artemis.UI.Shared.Services; +using MaterialDesignThemes.Wpf; +using RGB.NET.Core; +using KeyboardLayoutType = Artemis.Core.KeyboardLayoutType; + +namespace Artemis.UI.Services +{ + public class DeviceLayoutService : IDeviceLayoutService + { + private readonly IDialogService _dialogService; + private readonly List _ignoredDevices; + private readonly IMessageService _messageService; + private readonly IRgbService _rgbService; + private readonly IWindowService _windowService; + + public DeviceLayoutService(IDialogService dialogService, IRgbService rgbService, IWindowService windowService, IMessageService messageService) + { + _dialogService = dialogService; + _rgbService = rgbService; + _windowService = windowService; + _messageService = messageService; + _ignoredDevices = new List(); + + rgbService.DeviceAdded += RgbServiceOnDeviceAdded; + windowService.MainWindowOpened += WindowServiceOnMainWindowOpened; + } + + private async Task RequestLayoutInput(ArtemisDevice artemisDevice) + { + bool configure = await _dialogService.ShowConfirmDialog( + "Device requires layout info", + $"Artemis could not detect the layout of your {artemisDevice.RgbDevice.DeviceInfo.DeviceName}. Please configure out manually", + "Configure", + "Ignore for now" + ); + + if (!configure) + { + _ignoredDevices.Add(artemisDevice); + return; + } + + await _dialogService.ShowDialog(new Dictionary {{"device", artemisDevice}}); + } + + private bool DeviceNeedsLayout(ArtemisDevice d) + { + return d.RgbDevice.DeviceInfo.DeviceType == RGBDeviceType.Keyboard && + (d.LogicalLayout == null || d.PhysicalLayout == KeyboardLayoutType.Unknown) && + (!d.DeviceProvider.CanDetectLogicalLayout || !d.DeviceProvider.CanDetectPhysicalLayout); + } + + #region Event handlers + + private async void WindowServiceOnMainWindowOpened(object? sender, EventArgs e) + { + List devices = _rgbService.Devices.Where(device => DeviceNeedsLayout(device) && !_ignoredDevices.Contains(device)).ToList(); + foreach (ArtemisDevice artemisDevice in devices) + await RequestLayoutInput(artemisDevice); + } + + private async void RgbServiceOnDeviceAdded(object sender, DeviceEventArgs e) + { + if (_ignoredDevices.Contains(e.Device) || !DeviceNeedsLayout(e.Device)) + return; + + if (!_windowService.IsMainWindowOpen) + { + _messageService.ShowNotification("New device detected", "Detected a new device that needs layout setup", PackIconKind.Keyboard); + return; + } + + await RequestLayoutInput(e.Device); + } + + #endregion + } + + public interface IDeviceLayoutService : IArtemisUIService + { + } +} \ No newline at end of file