diff --git a/src/Artemis.Core/Plugins/DeviceProviders/DeviceProvider.cs b/src/Artemis.Core/Plugins/DeviceProviders/DeviceProvider.cs
index ee9f256a9..263748f0d 100644
--- a/src/Artemis.Core/Plugins/DeviceProviders/DeviceProvider.cs
+++ b/src/Artemis.Core/Plugins/DeviceProviders/DeviceProvider.cs
@@ -1,6 +1,7 @@
using System;
using System.IO;
using System.Linq;
+using System.Threading.Tasks;
using RGB.NET.Core;
namespace Artemis.Core.DeviceProviders;
@@ -15,7 +16,7 @@ public abstract class DeviceProvider : PluginFeature
/// The RGB.NET device provider backing this Artemis device provider
///
public abstract IRGBDeviceProvider RgbDeviceProvider { get; }
-
+
///
/// A boolean indicating whether this device provider detects the physical layout of connected keyboards.
///
@@ -48,6 +49,11 @@ public abstract class DeviceProvider : PluginFeature
///
public bool RemoveExcessiveLedsSupported { get; protected set; } = true;
+ ///
+ /// Gets or sets a boolean indicating whether suspending the device provider is supported
+ ///
+ public bool SuspendSupported { get; protected set; }
+
///
/// Loads a layout for the specified device and wraps it in an
///
@@ -109,4 +115,12 @@ public virtual string GetDeviceLayoutName(ArtemisDevice device)
return fileName + ".xml";
}
+
+ ///
+ /// Called when the device provider is being suspended, like when the system is going to sleep.
+ /// Note: This will be called while the plugin is disabled.
+ ///
+ public virtual void Suspend()
+ {
+ }
}
\ No newline at end of file
diff --git a/src/Artemis.Core/Services/DeviceService.cs b/src/Artemis.Core/Services/DeviceService.cs
index 111594c4a..5d2da4db4 100644
--- a/src/Artemis.Core/Services/DeviceService.cs
+++ b/src/Artemis.Core/Services/DeviceService.cs
@@ -2,13 +2,13 @@
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq;
+using System.Threading;
using System.Threading.Tasks;
using Artemis.Core.DeviceProviders;
using Artemis.Core.Providers;
using Artemis.Core.Services.Models;
using Artemis.Storage.Entities.Surface;
using Artemis.Storage.Repositories.Interfaces;
-using DryIoc;
using RGB.NET.Core;
using Serilog;
@@ -21,8 +21,10 @@ internal class DeviceService : IDeviceService
private readonly IDeviceRepository _deviceRepository;
private readonly Lazy _renderService;
private readonly Func> _getLayoutProviders;
- private readonly List _enabledDevices = new();
- private readonly List _devices = new();
+ private readonly List _enabledDevices = [];
+ private readonly List _devices = [];
+ private readonly List _suspendedDeviceProviders = [];
+ private readonly object _suspensionLock = new();
public DeviceService(ILogger logger,
IPluginManagementService pluginManagementService,
@@ -69,7 +71,7 @@ public void AddDeviceProvider(DeviceProvider deviceProvider)
OnDeviceRemoved(new DeviceEventArgs(device));
}
- List providerExceptions = new();
+ List providerExceptions = [];
void DeviceProviderOnException(object? sender, ExceptionEventArgs e)
{
@@ -95,7 +97,7 @@ void DeviceProviderOnException(object? sender, ExceptionEventArgs e)
return;
}
- List addedDevices = new();
+ List addedDevices = [];
foreach (IRGBDevice rgbDevice in rgbDeviceProvider.Devices)
{
ArtemisDevice artemisDevice = GetArtemisDevice(rgbDevice);
@@ -184,7 +186,7 @@ public void LoadDeviceLayout(ArtemisDevice device)
device.ApplyLayout(null, false, false);
else
provider?.ApplyLayout(device, layout);
-
+
UpdateLeds();
}
catch (Exception e)
@@ -241,6 +243,83 @@ public void SaveDevices()
UpdateLeds();
}
+ ///
+ public void SuspendDeviceProviders()
+ {
+ lock (_suspensionLock)
+ {
+ _logger.Information("Suspending all device providers");
+
+ bool wasPaused = _renderService.Value.IsPaused;
+ try
+ {
+ _renderService.Value.IsPaused = true;
+ foreach (DeviceProvider deviceProvider in _pluginManagementService.GetFeaturesOfType().Where(d => d.SuspendSupported))
+ SuspendDeviceProvider(deviceProvider);
+ }
+ finally
+ {
+ _renderService.Value.IsPaused = wasPaused;
+ }
+ }
+ }
+
+ ///
+ public void ResumeDeviceProviders()
+ {
+ lock (_suspensionLock)
+ {
+ _logger.Information("Resuming all device providers");
+
+ bool wasPaused = _renderService.Value.IsPaused;
+ try
+ {
+ _renderService.Value.IsPaused = true;
+ foreach (DeviceProvider deviceProvider in _suspendedDeviceProviders.ToList())
+ ResumeDeviceProvider(deviceProvider);
+ }
+ finally
+ {
+ _renderService.Value.IsPaused = wasPaused;
+ }
+ }
+ }
+
+ private void SuspendDeviceProvider(DeviceProvider deviceProvider)
+ {
+ if (_suspendedDeviceProviders.Contains(deviceProvider))
+ {
+ _logger.Warning("Device provider {DeviceProvider} is already suspended", deviceProvider.Info.Name);
+ return;
+ }
+
+ try
+ {
+ _pluginManagementService.DisablePluginFeature(deviceProvider, false);
+ deviceProvider.Suspend();
+ _suspendedDeviceProviders.Add(deviceProvider);
+ _logger.Information("Device provider {DeviceProvider} suspended", deviceProvider.Info.Name);
+ }
+ catch (Exception e)
+ {
+ _logger.Error(e, "Device provider {DeviceProvider} failed to suspend", deviceProvider.Info.Name);
+ }
+ }
+
+ private void ResumeDeviceProvider(DeviceProvider deviceProvider)
+ {
+ try
+ {
+ _pluginManagementService.EnablePluginFeature(deviceProvider, false, true);
+ _suspendedDeviceProviders.Remove(deviceProvider);
+ _logger.Information("Device provider {DeviceProvider} resumed", deviceProvider.Info.Name);
+ }
+ catch (Exception e)
+ {
+ _logger.Error(e, "Device provider {DeviceProvider} failed to resume", deviceProvider.Info.Name);
+ }
+ }
+
private ArtemisDevice GetArtemisDevice(IRGBDevice rgbDevice)
{
string deviceIdentifier = rgbDevice.GetDeviceIdentifier();
diff --git a/src/Artemis.Core/Services/Interfaces/IDeviceService.cs b/src/Artemis.Core/Services/Interfaces/IDeviceService.cs
index 51b19d937..5c5c60ad7 100644
--- a/src/Artemis.Core/Services/Interfaces/IDeviceService.cs
+++ b/src/Artemis.Core/Services/Interfaces/IDeviceService.cs
@@ -1,5 +1,6 @@
using System;
using System.Collections.Generic;
+using System.Threading.Tasks;
using Artemis.Core.DeviceProviders;
namespace Artemis.Core.Services;
@@ -71,6 +72,16 @@ public interface IDeviceService : IArtemisService
///
void SaveDevices();
+ ///
+ /// Suspends all active device providers
+ ///
+ void SuspendDeviceProviders();
+
+ ///
+ /// Resumes all previously active device providers
+ ///
+ void ResumeDeviceProviders();
+
///
/// Occurs when a single device was added.
///
diff --git a/src/Artemis.UI.Shared/Controls/DataModelPicker/DataModelPicker.cs b/src/Artemis.UI.Shared/Controls/DataModelPicker/DataModelPicker.cs
index e3168eaba..996dc842c 100644
--- a/src/Artemis.UI.Shared/Controls/DataModelPicker/DataModelPicker.cs
+++ b/src/Artemis.UI.Shared/Controls/DataModelPicker/DataModelPicker.cs
@@ -1,4 +1,5 @@
using System;
+using System.Collections;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq;
@@ -69,12 +70,19 @@ public class DataModelPicker : TemplatedControl
public static readonly StyledProperty IsEventPickerProperty =
AvaloniaProperty.Register(nameof(IsEventPicker));
+ private readonly ObservableCollection _searchResults = [];
private TextBlock? _currentPathDescription;
private TextBlock? _currentPathDisplay;
private MaterialIcon? _currentPathIcon;
private TreeView? _dataModelTreeView;
+ private ListBox? _searchListBox;
+ private TextBox? _searchBox;
+ private Panel? _searchContainer;
+ private StackPanel? _searchEmpty;
private DispatcherTimer? _updateTimer;
+ private string? _lastSearch;
+ private bool _firstUpdate;
static DataModelPicker()
{
@@ -200,7 +208,14 @@ private void Update(object? sender, EventArgs e)
if (DataModelUIService == null)
return;
- DataModelViewModel?.Update(DataModelUIService, new DataModelUpdateConfiguration(!IsEventPicker));
+ DataModelViewModel?.Update(DataModelUIService, new DataModelUpdateConfiguration(!IsEventPicker, !string.IsNullOrEmpty(_searchBox?.Text)));
+ ApplySearch();
+
+ if (_firstUpdate)
+ {
+ _firstUpdate = false;
+ UpdateCurrentPath(true);
+ }
}
private void GetDataModel()
@@ -229,14 +244,15 @@ private void DataModelOnUpdateRequested(object? sender, EventArgs e)
if (DataModelUIService == null || DataModelViewModel == null)
return;
- DataModelViewModel.Update(DataModelUIService, new DataModelUpdateConfiguration(IsEventPicker));
+ DataModelViewModel.Update(DataModelUIService, new DataModelUpdateConfiguration(!IsEventPicker, !string.IsNullOrEmpty(_searchBox?.Text)));
DataModelViewModel.ApplyTypeFilter(true, FilterTypes?.ToArray() ?? Type.EmptyTypes);
+ ApplySearch();
}
- private void DataModelTreeViewOnSelectionChanged(object? sender, SelectionChangedEventArgs e)
+ private void TreeViewOnSelectionChanged(object? sender, SelectionChangedEventArgs e)
{
- // Multi-select isn't a think so grab the first one
- object? selected = _dataModelTreeView?.SelectedItems[0];
+ // Multi-select isn't a thing so grab the first one
+ object? selected = e.AddedItems[0];
if (selected == null)
return;
@@ -255,12 +271,48 @@ private void DataModelTreeViewOnDoubleTapped(object? sender, RoutedEventArgs e)
treeViewItem.IsExpanded = !treeViewItem.IsExpanded;
}
+ private void ApplySearch()
+ {
+ if (DataModelViewModel == null || string.IsNullOrWhiteSpace(_searchBox?.Text))
+ {
+ if (_dataModelTreeView != null)
+ _dataModelTreeView.IsVisible = true;
+ if (_searchContainer != null)
+ _searchContainer.IsVisible = false;
+ return;
+ }
+
+ if (_dataModelTreeView != null)
+ _dataModelTreeView.IsVisible = false;
+ if (_searchContainer != null)
+ _searchContainer.IsVisible = true;
+
+ if (_searchBox.Text != _lastSearch)
+ {
+ _searchResults.Clear();
+ foreach (DataModelVisualizationViewModel searchResult in DataModelViewModel.GetSearchResults(_searchBox.Text).DistinctBy(r => r.Path).Take(20))
+ _searchResults.Add(searchResult);
+ _lastSearch = _searchBox.Text;
+ }
+
+ if (_searchListBox != null)
+ {
+ _searchListBox.IsVisible = _searchResults.Count > 0;
+ _searchListBox.SelectedItem = _searchResults.FirstOrDefault(r => r.DataModelPath?.Path == DataModelPath?.Path);
+ }
+
+ if (_searchEmpty != null)
+ _searchEmpty.IsVisible = _searchResults.Count == 0;
+
+
+ }
+
private void UpdateCurrentPath(bool selectCurrentPath)
{
- if (DataModelPath == null)
+ if (DataModelPath == null || _dataModelTreeView == null)
return;
- if (_dataModelTreeView != null && selectCurrentPath)
+ if (selectCurrentPath)
{
// Expand the path
DataModel? start = DataModelPath.Target;
@@ -286,7 +338,7 @@ private void UpdateCurrentPath(bool selectCurrentPath)
protected override void OnApplyTemplate(TemplateAppliedEventArgs e)
{
if (_dataModelTreeView != null)
- _dataModelTreeView.SelectionChanged -= DataModelTreeViewOnSelectionChanged;
+ _dataModelTreeView.SelectionChanged -= TreeViewOnSelectionChanged;
if (_dataModelTreeView != null)
_dataModelTreeView.DoubleTapped -= DataModelTreeViewOnDoubleTapped;
@@ -294,11 +346,22 @@ protected override void OnApplyTemplate(TemplateAppliedEventArgs e)
_currentPathDisplay = e.NameScope.Find("CurrentPathDisplay");
_currentPathDescription = e.NameScope.Find("CurrentPathDescription");
_dataModelTreeView = e.NameScope.Find("DataModelTreeView");
+ _searchBox = e.NameScope.Find("SearchBox");
+ _searchContainer = e.NameScope.Find("SearchContainer");
+ _searchListBox = e.NameScope.Find("SearchListBox");
+ _searchEmpty = e.NameScope.Find("SearchEmpty");
if (_dataModelTreeView != null)
- _dataModelTreeView.SelectionChanged += DataModelTreeViewOnSelectionChanged;
- if (_dataModelTreeView != null)
+ {
+ _dataModelTreeView.SelectionChanged += TreeViewOnSelectionChanged;
_dataModelTreeView.DoubleTapped += DataModelTreeViewOnDoubleTapped;
+ }
+
+ if (_searchListBox != null)
+ {
+ _searchListBox.ItemsSource = _searchResults;
+ _searchListBox.SelectionChanged += TreeViewOnSelectionChanged;
+ }
}
#region Overrides of Visual
@@ -307,17 +370,23 @@ protected override void OnApplyTemplate(TemplateAppliedEventArgs e)
protected override void OnAttachedToVisualTree(VisualTreeAttachmentEventArgs e)
{
GetDataModel();
- UpdateCurrentPath(true);
+
_updateTimer = new DispatcherTimer(TimeSpan.FromMilliseconds(200), DispatcherPriority.Background, Update);
_updateTimer.Start();
+ _firstUpdate = true;
}
///
protected override void OnDetachedFromVisualTree(VisualTreeAttachmentEventArgs e)
{
DataModelViewModel?.Dispose();
+
_updateTimer?.Stop();
_updateTimer = null;
+
+ _lastSearch = null;
+ if (_searchBox != null)
+ _searchBox.Text = null;
}
#endregion
diff --git a/src/Artemis.UI.Shared/DataModelVisualization/Shared/DataModelEventViewModel.cs b/src/Artemis.UI.Shared/DataModelVisualization/Shared/DataModelEventViewModel.cs
index b3c16c860..312d95a36 100644
--- a/src/Artemis.UI.Shared/DataModelVisualization/Shared/DataModelEventViewModel.cs
+++ b/src/Artemis.UI.Shared/DataModelVisualization/Shared/DataModelEventViewModel.cs
@@ -1,4 +1,5 @@
using System;
+using System.Collections.Generic;
using System.Linq;
using Artemis.Core;
using Artemis.Core.Modules;
@@ -41,13 +42,21 @@ public override void Update(IDataModelUIService dataModelUIService, DataModelUpd
}
// Only update children if the parent is expanded
- if (Parent != null && !Parent.IsRootViewModel && !Parent.IsVisualizationExpanded)
+ if (Parent != null && !Parent.IsRootViewModel && !Parent.IsVisualizationExpanded && (configuration == null || !configuration.UpdateAllChildren))
return;
foreach (DataModelVisualizationViewModel dataModelVisualizationViewModel in Children)
dataModelVisualizationViewModel.Update(dataModelUIService, configuration);
}
+ ///
+ public override IEnumerable GetSearchResults(string search)
+ {
+ if (PropertyDescription?.Name != null && PropertyDescription.Name.Contains(search, StringComparison.OrdinalIgnoreCase))
+ return [this];
+ return [];
+ }
+
///
/// Always returns for data model events
///
diff --git a/src/Artemis.UI.Shared/DataModelVisualization/Shared/DataModelListItemViewModel.cs b/src/Artemis.UI.Shared/DataModelVisualization/Shared/DataModelListItemViewModel.cs
index 0a553339c..fd7a7a74f 100644
--- a/src/Artemis.UI.Shared/DataModelVisualization/Shared/DataModelListItemViewModel.cs
+++ b/src/Artemis.UI.Shared/DataModelVisualization/Shared/DataModelListItemViewModel.cs
@@ -1,4 +1,5 @@
using System;
+using System.Collections.Generic;
using Artemis.UI.Shared.Services;
using ReactiveUI;
@@ -62,6 +63,12 @@ public object? DisplayValue
internal set => this.RaiseAndSetIfChanged(ref _displayValue, value);
}
+ ///
+ public override IEnumerable GetSearchResults(string search)
+ {
+ return [];
+ }
+
///
public override object? GetCurrentValue()
{
diff --git a/src/Artemis.UI.Shared/DataModelVisualization/Shared/DataModelListViewModel.cs b/src/Artemis.UI.Shared/DataModelVisualization/Shared/DataModelListViewModel.cs
index 0d0ca66dd..b04d61d2b 100644
--- a/src/Artemis.UI.Shared/DataModelVisualization/Shared/DataModelListViewModel.cs
+++ b/src/Artemis.UI.Shared/DataModelVisualization/Shared/DataModelListViewModel.cs
@@ -1,5 +1,6 @@
using System;
using System.Collections;
+using System.Collections.Generic;
using System.Collections.ObjectModel;
using Artemis.Core;
using Artemis.Core.Modules;
@@ -119,6 +120,17 @@ public override void Update(IDataModelUIService dataModelUIService, DataModelUpd
CountDisplay = $"{ListChildren.Count} {(ListChildren.Count == 1 ? "item" : "items")}";
}
+ ///
+ public override IEnumerable GetSearchResults(string search)
+ {
+ if (PropertyDescription?.Name != null && PropertyDescription.Name.Contains(search, StringComparison.OrdinalIgnoreCase))
+ return [this];
+ if (PropertyDescription?.Description != null && PropertyDescription.Description.Contains(search, StringComparison.OrdinalIgnoreCase))
+ return [this];
+
+ return [];
+ }
+
///
public override string ToString()
{
diff --git a/src/Artemis.UI.Shared/DataModelVisualization/Shared/DataModelPropertiesViewModel.cs b/src/Artemis.UI.Shared/DataModelVisualization/Shared/DataModelPropertiesViewModel.cs
index fe1d226c4..ca03c5c26 100644
--- a/src/Artemis.UI.Shared/DataModelVisualization/Shared/DataModelPropertiesViewModel.cs
+++ b/src/Artemis.UI.Shared/DataModelVisualization/Shared/DataModelPropertiesViewModel.cs
@@ -1,4 +1,6 @@
using System;
+using System.Collections.Generic;
+using System.Linq;
using Artemis.Core;
using Artemis.Core.Modules;
using Artemis.UI.Shared.Services;
@@ -52,14 +54,20 @@ public override void Update(IDataModelUIService dataModelUIService, DataModelUpd
// Always populate properties
PopulateProperties(dataModelUIService, configuration);
- // Only update children if the parent is expanded
- if (Parent != null && !Parent.IsRootViewModel && !Parent.IsVisualizationExpanded)
+ // Only update children if the parent is expanded or when searching
+ if (Parent != null && !Parent.IsRootViewModel && !Parent.IsVisualizationExpanded && (configuration == null || !configuration.UpdateAllChildren))
return;
-
+
foreach (DataModelVisualizationViewModel dataModelVisualizationViewModel in Children)
dataModelVisualizationViewModel.Update(dataModelUIService, configuration);
}
+ ///
+ public override IEnumerable GetSearchResults(string search)
+ {
+ return Children.SelectMany(c => c.GetSearchResults(search));
+ }
+
///
public override object? GetCurrentValue()
{
diff --git a/src/Artemis.UI.Shared/DataModelVisualization/Shared/DataModelPropertyViewModel.cs b/src/Artemis.UI.Shared/DataModelVisualization/Shared/DataModelPropertyViewModel.cs
index a68ee78a2..69e60578d 100644
--- a/src/Artemis.UI.Shared/DataModelVisualization/Shared/DataModelPropertyViewModel.cs
+++ b/src/Artemis.UI.Shared/DataModelVisualization/Shared/DataModelPropertyViewModel.cs
@@ -1,4 +1,5 @@
using System;
+using System.Collections.Generic;
using Artemis.Core;
using Artemis.Core.Modules;
using Artemis.UI.Shared.Services;
@@ -71,6 +72,17 @@ public override void Update(IDataModelUIService dataModelUIService, DataModelUpd
DisplayViewModel?.UpdateValue(DisplayValue);
}
+ ///
+ public override IEnumerable GetSearchResults(string search)
+ {
+ if (PropertyDescription?.Name != null && PropertyDescription.Name.Contains(search, StringComparison.OrdinalIgnoreCase))
+ return [this];
+ if (DisplayValue != null && DisplayValue.ToString()?.Contains(search, StringComparison.OrdinalIgnoreCase) == true)
+ return [this];
+
+ return [];
+ }
+
///
public override string ToString()
{
diff --git a/src/Artemis.UI.Shared/DataModelVisualization/Shared/DataModelUpdateConfiguration.cs b/src/Artemis.UI.Shared/DataModelVisualization/Shared/DataModelUpdateConfiguration.cs
index 1cf9dcce7..f4f30e082 100644
--- a/src/Artemis.UI.Shared/DataModelVisualization/Shared/DataModelUpdateConfiguration.cs
+++ b/src/Artemis.UI.Shared/DataModelVisualization/Shared/DataModelUpdateConfiguration.cs
@@ -8,14 +8,21 @@ public class DataModelUpdateConfiguration
///
/// Creates a new instance of the class
///
- /// A boolean indicating whether or not event children should be created
- public DataModelUpdateConfiguration(bool createEventChildren)
+ /// A boolean indicating whether event children should be created
+ /// A boolean indicating whether all children should be updated
+ public DataModelUpdateConfiguration(bool createEventChildren, bool updateAllChildren)
{
CreateEventChildren = createEventChildren;
+ UpdateAllChildren = updateAllChildren;
}
///
- /// Gets a boolean indicating whether or not event children should be created
+ /// Gets a boolean indicating whether event children should be created
///
public bool CreateEventChildren { get; }
+
+ ///
+ /// Gets a boolean indicating whether all children should be updated
+ ///
+ public bool UpdateAllChildren { get; }
}
\ No newline at end of file
diff --git a/src/Artemis.UI.Shared/DataModelVisualization/Shared/DataModelVisualizationViewModel.cs b/src/Artemis.UI.Shared/DataModelVisualization/Shared/DataModelVisualizationViewModel.cs
index 77f429408..95d07a35b 100644
--- a/src/Artemis.UI.Shared/DataModelVisualization/Shared/DataModelVisualizationViewModel.cs
+++ b/src/Artemis.UI.Shared/DataModelVisualization/Shared/DataModelVisualizationViewModel.cs
@@ -144,6 +144,13 @@ public bool IsVisualizationExpanded
/// The configuration to apply while updating
public abstract void Update(IDataModelUIService dataModelUIService, DataModelUpdateConfiguration? configuration);
+ ///
+ /// Gets the search results for the provided search string
+ ///
+ /// The search string
+ /// The search results
+ public abstract IEnumerable GetSearchResults(string search);
+
///
/// Gets the current value of the property being visualized
///
@@ -281,7 +288,7 @@ internal void PopulateProperties(IDataModelUIService dataModelUIService, DataMod
foreach (PropertyInfo propertyInfo in modelType.GetProperties(BindingFlags.Public | BindingFlags.Instance).OrderBy(t => t.MetadataToken))
{
string childPath = AppendToPath(propertyInfo.Name);
-
+
if (!ShouldIncludePath(childPath, propertyInfo))
continue;
diff --git a/src/Artemis.UI.Shared/Styles/Controls/DataModelPicker.axaml b/src/Artemis.UI.Shared/Styles/Controls/DataModelPicker.axaml
index 0175ae880..18df7201a 100644
--- a/src/Artemis.UI.Shared/Styles/Controls/DataModelPicker.axaml
+++ b/src/Artemis.UI.Shared/Styles/Controls/DataModelPicker.axaml
@@ -11,38 +11,43 @@