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

UI - Added devices settings tab

This commit is contained in:
Robert 2021-11-15 21:30:25 +01:00
parent 63eb0ca9b3
commit 09d202cf24
18 changed files with 386 additions and 41 deletions

View File

@ -4,6 +4,7 @@ using System.Collections.ObjectModel;
using System.ComponentModel;
using System.IO;
using System.Linq;
using System.Threading.Tasks;
using Artemis.Core;
using Artemis.UI.Avalonia.Shared.Events;
using Avalonia;
@ -51,7 +52,7 @@ namespace Artemis.UI.Avalonia.Shared.Controls
// Determine the scale required to fit the desired size of the control
double scale = Math.Min(Bounds.Width / _deviceBounds.Width, Bounds.Height / _deviceBounds.Height);
DrawingContext.PushedState? boundsPush = null;
try
{
@ -70,6 +71,9 @@ namespace Artemis.UI.Avalonia.Shared.Controls
if (_deviceImage != null)
drawingContext.DrawImage(_deviceImage, new Rect(0, 0, Device.RgbDevice.ActualSize.Width, Device.RgbDevice.ActualSize.Height));
if (!ShowColors)
return;
foreach (DeviceVisualizerLed deviceVisualizerLed in _deviceVisualizerLeds)
deviceVisualizerLed.RenderGeometry(drawingContext, false);
}
@ -229,6 +233,7 @@ namespace Artemis.UI.Avalonia.Shared.Controls
private void SetupForDevice()
{
_deviceImage?.Dispose();
_deviceImage = null;
_deviceVisualizerLeds.Clear();
_highlightedLeds = new List<DeviceVisualizerLed>();
@ -254,40 +259,44 @@ namespace Artemis.UI.Avalonia.Shared.Controls
foreach (ArtemisLed artemisLed in Device.Leds)
_deviceVisualizerLeds.Add(new DeviceVisualizerLed(artemisLed));
// Load the device main image
if (Device.Layout?.Image != null && File.Exists(Device.Layout.Image.LocalPath))
// Load the device main image on a background thread
ArtemisDevice? device = Device;
Task.Run(() =>
{
try
if (device.Layout?.Image != null && File.Exists(device.Layout.Image.LocalPath))
{
// Create a bitmap that'll be used to render the device and LED images just once
RenderTargetBitmap renderTargetBitmap = new(new PixelSize((int) Device.RgbDevice.Size.Width * 4, (int) Device.RgbDevice.Size.Height * 4));
try
{
// Create a bitmap that'll be used to render the device and LED images just once
RenderTargetBitmap renderTargetBitmap = new(new PixelSize((int) device.RgbDevice.Size.Width * 4, (int) device.RgbDevice.Size.Height * 4));
using IDrawingContextImpl context = renderTargetBitmap.CreateDrawingContext(new ImmediateRenderer(this));
using Bitmap bitmap = new(Device.Layout.Image.LocalPath);
context.DrawBitmap(bitmap.PlatformImpl, 1, new Rect(bitmap.Size), new Rect(renderTargetBitmap.Size), BitmapInterpolationMode.HighQuality);
foreach (DeviceVisualizerLed deviceVisualizerLed in _deviceVisualizerLeds)
deviceVisualizerLed.DrawBitmap(context);
using IDrawingContextImpl context = renderTargetBitmap.CreateDrawingContext(new ImmediateRenderer(this));
using Bitmap bitmap = new(device.Layout.Image.LocalPath);
context.DrawBitmap(bitmap.PlatformImpl, 1, new Rect(bitmap.Size), new Rect(renderTargetBitmap.Size), BitmapInterpolationMode.HighQuality);
foreach (DeviceVisualizerLed deviceVisualizerLed in _deviceVisualizerLeds)
deviceVisualizerLed.DrawBitmap(context);
_deviceImage = renderTargetBitmap;
_deviceImage = renderTargetBitmap;
Dispatcher.UIThread.Post(InvalidateMeasure);
}
catch
{
// ignored
}
}
catch
{
// ignored
}
}
InvalidateMeasure();
});
}
#region Overrides of Layoutable
/// <inheritdoc />
protected override Size MeasureOverride(Size availableSize)
{
return new Size(Math.Min(availableSize.Width, _deviceBounds.Width), Math.Min(availableSize.Height, _deviceBounds.Height));
}
double availableWidth = double.IsInfinity(availableSize.Width) ? _deviceBounds.Width : availableSize.Width;
double availableHeight = double.IsInfinity(availableSize.Height) ? _deviceBounds.Height : availableSize.Height;
double bestRatio = Math.Min(availableWidth / _deviceBounds.Width, availableHeight / _deviceBounds.Height);
#endregion
return new Size(_deviceBounds.Width * bestRatio, _deviceBounds.Height * bestRatio);
}
#endregion
}

View File

@ -76,7 +76,7 @@ namespace Artemis.UI.Avalonia.Shared.Services.Builders
return this;
}
public ContentDialogBuilder WithViewModel<T>(ref T viewModel, params (string name, object value)[] parameters)
public ContentDialogBuilder WithViewModel<T>(out T viewModel, params (string name, object value)[] parameters)
{
IParameter[] paramsArray = parameters.Select(kv => new ConstructorArgument(kv.name, kv.value)).Cast<IParameter>().ToArray();
viewModel = _kernel.Get<T>(paramsArray);

View File

@ -3,8 +3,12 @@
xmlns:controls="clr-namespace:FluentAvalonia.UI.Controls;assembly=FluentAvalonia">
<Design.PreviewWith>
<Border Padding="20">
<!-- Add Controls for Previewer Here -->
<controls:InfoBar Classes="notification-info-bar" Title="Test title" Message="Test message" IsOpen="True"/>
<StackPanel >
<controls:InfoBar Classes="notification-info-bar" Title="Test title" Message="Test message" IsOpen="True" Severity="Informational"/>
<controls:InfoBar Classes="notification-info-bar" Title="Test title" Message="Test message" IsOpen="True" Severity="Success"/>
<controls:InfoBar Classes="notification-info-bar" Title="Test title" Message="Test message" IsOpen="True" Severity="Warning"/>
<controls:InfoBar Classes="notification-info-bar" Title="Test title" Message="Test message" IsOpen="True" Severity="Error"/>
</StackPanel>
</Border>
</Design.PreviewWith>

View File

@ -2,8 +2,10 @@
using Artemis.Core;
using Artemis.UI.Avalonia.Screens.Device;
using Artemis.UI.Avalonia.Screens.Device.Tabs.ViewModels;
using Artemis.UI.Avalonia.Screens.Device.ViewModels;
using Artemis.UI.Avalonia.Screens.Plugins.ViewModels;
using Artemis.UI.Avalonia.Screens.Root.ViewModels;
using Artemis.UI.Avalonia.Screens.Settings.Tabs.ViewModels;
using Artemis.UI.Avalonia.Screens.SurfaceEditor.ViewModels;
using ReactiveUI;
@ -16,6 +18,8 @@ namespace Artemis.UI.Avalonia.Ninject.Factories
public interface IDeviceVmFactory : IVmFactory
{
DevicePropertiesViewModel DevicePropertiesViewModel(ArtemisDevice device);
DeviceSettingsViewModel DeviceSettingsViewModel(ArtemisDevice device, DevicesTabViewModel devicesTabViewModel);
DeviceDetectInputViewModel DeviceDetectInputViewModel(ArtemisDevice device);
DevicePropertiesTabViewModel DevicePropertiesTabViewModel(ArtemisDevice device);
DeviceInfoTabViewModel DeviceInfoTabViewModel(ArtemisDevice device);
DeviceLedsTabViewModel DeviceLedsTabViewModel(ArtemisDevice device, ObservableCollection<ArtemisLed> selectedLeds);

View File

@ -0,0 +1,15 @@
using Artemis.Core;
namespace Artemis.UI.Avalonia.Screens.Device.ViewModels
{
public class DeviceDetectInputViewModel
{
public DeviceDetectInputViewModel(ArtemisDevice device)
{
Device = device;
}
public ArtemisDevice Device { get; }
public bool MadeChanges { get; set; }
}
}

View File

@ -0,0 +1,98 @@
using System.Threading.Tasks;
using Artemis.Core;
using Artemis.Core.Services;
using Artemis.UI.Avalonia.Ninject.Factories;
using Artemis.UI.Avalonia.Screens.Settings.Tabs.ViewModels;
using Artemis.UI.Avalonia.Shared;
using Artemis.UI.Avalonia.Shared.Services.Interfaces;
using Humanizer;
using ReactiveUI;
using RGB.NET.Core;
namespace Artemis.UI.Avalonia.Screens.Device.ViewModels
{
public class DeviceSettingsViewModel : ActivatableViewModelBase
{
private readonly IDeviceService _deviceService;
private readonly DevicesTabViewModel _devicesTabViewModel;
private readonly IDeviceVmFactory _deviceVmFactory;
private readonly IRgbService _rgbService;
private readonly IWindowService _windowService;
public DeviceSettingsViewModel(ArtemisDevice device, DevicesTabViewModel devicesTabViewModel, IDeviceService deviceService, IWindowService windowService, IDeviceVmFactory deviceVmFactory,
IRgbService rgbService)
{
_devicesTabViewModel = devicesTabViewModel;
_deviceService = deviceService;
_windowService = windowService;
_deviceVmFactory = deviceVmFactory;
_rgbService = rgbService;
Device = device;
Type = Device.DeviceType.ToString().Humanize();
Name = Device.RgbDevice.DeviceInfo.Model;
Manufacturer = Device.RgbDevice.DeviceInfo.Manufacturer;
}
public ArtemisDevice Device { get; }
public string Type { get; }
public string Name { get; }
public string Manufacturer { get; }
public bool CanDetectInput => Device.DeviceType is RGBDeviceType.Keyboard or RGBDeviceType.Mouse;
public bool IsDeviceEnabled
{
get => Device.IsEnabled;
set => Task.Run(() => UpdateIsDeviceEnabled(value));
}
public void IdentifyDevice()
{
_deviceService.IdentifyDevice(Device);
}
public void OpenPluginDirectory()
{
Utilities.OpenFolder(Device.DeviceProvider.Plugin.Directory.FullName);
}
public async Task DetectInput()
{
if (!CanDetectInput)
return;
await _windowService.CreateContentDialog()
.WithViewModel<DeviceDetectInputViewModel>(out var viewModel, ("device", Device))
.ShowAsync();
if (viewModel.MadeChanges)
_rgbService.SaveDevice(Device);
}
public async Task ViewProperties()
{
await _windowService.ShowDialogAsync(_deviceVmFactory.DevicePropertiesViewModel(Device));
}
private async Task UpdateIsDeviceEnabled(bool value)
{
if (!value)
value = !await _devicesTabViewModel.ShowDeviceDisableDialog();
if (value)
_rgbService.EnableDevice(Device);
else
_rgbService.DisableDevice(Device);
this.RaisePropertyChanged(nameof(IsDeviceEnabled));
SaveDevice();
}
private void SaveDevice()
{
_rgbService.SaveDevice(Device);
}
}
}

View File

@ -0,0 +1,8 @@
<UserControl xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
x:Class="Artemis.UI.Avalonia.Screens.Device.Views.DeviceDetectInputView">
Welcome to Avalonia!
</UserControl>

View File

@ -0,0 +1,19 @@
using Avalonia;
using Avalonia.Controls;
using Avalonia.Markup.Xaml;
namespace Artemis.UI.Avalonia.Screens.Device.Views
{
public partial class DeviceDetectInputView : UserControl
{
public DeviceDetectInputView()
{
InitializeComponent();
}
private void InitializeComponent()
{
AvaloniaXamlLoader.Load(this);
}
}
}

View File

@ -0,0 +1,71 @@
<UserControl xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:shared="clr-namespace:Artemis.UI.Avalonia.Shared.Controls;assembly=Artemis.UI.Avalonia.Shared"
xmlns:avalonia="clr-namespace:Material.Icons.Avalonia;assembly=Material.Icons.Avalonia"
xmlns:controls="clr-namespace:FluentAvalonia.UI.Controls;assembly=FluentAvalonia"
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
x:Class="Artemis.UI.Avalonia.Screens.Device.Views.DeviceSettingsView">
<Border Classes="card" Padding="0" Width="200" ClipToBounds="True" Margin="5">
<Grid RowDefinitions="140,*,Auto">
<Rectangle Grid.Row="0">
<Rectangle.Fill>
<VisualBrush TileMode="Tile" Stretch="Uniform" DestinationRect="0,0,25,25">
<VisualBrush.Visual>
<Grid Width="25" Height="25" RowDefinitions="*,*" ColumnDefinitions="*,*">
<Rectangle Grid.Row="0" Grid.Column="0" Fill="Black" Opacity="0.15" />
<Rectangle Grid.Row="0" Grid.Column="1" />
<Rectangle Grid.Row="1" Grid.Column="0" />
<Rectangle Grid.Row="1" Grid.Column="1" Fill="Black" Opacity="0.15" />
</Grid>
</VisualBrush.Visual>
</VisualBrush>
</Rectangle.Fill>
</Rectangle>
<shared:DeviceVisualizer VerticalAlignment="Center"
HorizontalAlignment="Center"
Margin="5"
ShowColors="False"
Device="{Binding Device}"
Grid.Row="0" />
<Button Grid.Row="0"
Classes="icon-button icon-button-large"
VerticalAlignment="Bottom"
HorizontalAlignment="Right"
Margin="5"
ToolTip.Tip="Identify"
Command="{Binding IdentifyDevice}">
<avalonia:MaterialIcon Kind="AlarmLight" />
</Button>
<StackPanel Grid.Row="1" Margin="8 24 8 0">
<TextBlock Text="{Binding Device.RgbDevice.DeviceInfo.Model}" />
<StackPanel>
<TextBlock TextWrapping="Wrap" Classes="subtitle" Text="{Binding Device.RgbDevice.DeviceInfo.Manufacturer, Mode=OneWay}" />
<TextBlock TextWrapping="Wrap" Classes="subtitle" Text="{Binding Device.DeviceType, Mode=OneWay}" />
</StackPanel>
</StackPanel>
<Grid Grid.Row="2" Margin="10" ColumnDefinitions="25,*">
<CheckBox IsChecked="{Binding IsDeviceEnabled}" />
<controls:SplitButton Grid.Column="1" Content="Properties" Command="{Binding OpenSettings}" HorizontalAlignment="Right">
<controls:SplitButton.Flyout>
<MenuFlyout Placement="Bottom">
<MenuItem Header="Open plugin directory" Command="{Binding OpenPluginDirectory}">
<MenuItem.Icon>
<avalonia:MaterialIcon Kind="FolderOpen" />
</MenuItem.Icon>
</MenuItem>
<MenuItem Header="Identity input" Command="{Binding DetectInput}">
<MenuItem.Icon>
<avalonia:MaterialIcon Kind="GestureDoubleTap" />
</MenuItem.Icon>
</MenuItem>
</MenuFlyout>
</controls:SplitButton.Flyout>
</controls:SplitButton>
</Grid>
</Grid>
</Border>
</UserControl>

View File

@ -0,0 +1,19 @@
using Avalonia;
using Avalonia.Controls;
using Avalonia.Markup.Xaml;
namespace Artemis.UI.Avalonia.Screens.Device.Views
{
public partial class DeviceSettingsView : UserControl
{
public DeviceSettingsView()
{
InitializeComponent();
}
private void InitializeComponent()
{
AvaloniaXamlLoader.Load(this);
}
}
}

View File

@ -12,7 +12,7 @@
</TabControl.ItemTemplate>
<TabControl.ContentTemplate>
<DataTemplate>
<ScrollViewer HorizontalScrollBarVisibility="Hidden" VerticalScrollBarVisibility="Auto">
<ScrollViewer HorizontalScrollBarVisibility="Disabled" VerticalScrollBarVisibility="Auto">
<ContentControl Content="{Binding}" />
</ScrollViewer>
</DataTemplate>

View File

@ -1,12 +1,87 @@
using Artemis.UI.Avalonia.Shared;
using System;
using System.Collections.ObjectModel;
using System.Linq;
using System.Reactive.Disposables;
using System.Reactive.Linq;
using System.Threading.Tasks;
using Artemis.Core;
using Artemis.Core.Services;
using Artemis.UI.Avalonia.Ninject.Factories;
using Artemis.UI.Avalonia.Screens.Device.ViewModels;
using Artemis.UI.Avalonia.Shared;
using Artemis.UI.Avalonia.Shared.Services.Interfaces;
using Avalonia.Threading;
using DynamicData;
using ReactiveUI;
namespace Artemis.UI.Avalonia.Screens.Settings.Tabs.ViewModels
{
public class DevicesTabViewModel : ActivatableViewModelBase
{
public DevicesTabViewModel()
private readonly IDeviceVmFactory _deviceVmFactory;
private readonly IRgbService _rgbService;
private readonly IWindowService _windowService;
private bool _confirmedDisable;
public DevicesTabViewModel(IRgbService rgbService, IWindowService windowService, IDeviceVmFactory deviceVmFactory)
{
DisplayName = "Devices";
_rgbService = rgbService;
_windowService = windowService;
_deviceVmFactory = deviceVmFactory;
Devices = new ObservableCollection<DeviceSettingsViewModel>();
this.WhenActivated(disposables =>
{
GetDevices();
Observable.FromEventPattern<DeviceEventArgs>(x => rgbService.DeviceAdded += x, x => rgbService.DeviceAdded -= x)
.Subscribe(d => AddDevice(d.EventArgs.Device))
.DisposeWith(disposables);
Observable.FromEventPattern<DeviceEventArgs>(x => rgbService.DeviceRemoved += x, x => rgbService.DeviceRemoved -= x)
.Subscribe(d => RemoveDevice(d.EventArgs.Device))
.DisposeWith(disposables);
});
}
private void GetDevices()
{
Devices.Clear();
Dispatcher.UIThread.InvokeAsync(() =>
{
Devices.AddRange(_rgbService.Devices.Select(d => _deviceVmFactory.DeviceSettingsViewModel(d, this)));
}, DispatcherPriority.Background);
}
public ObservableCollection<DeviceSettingsViewModel> Devices { get; }
public async Task<bool> ShowDeviceDisableDialog()
{
if (_confirmedDisable)
return true;
bool confirmed = await _windowService.ShowConfirmContentDialog(
"Disabling device",
"Disabling a device will cause it to stop updating. " +
"\r\nSome SDKs will even go back to using manufacturer lighting (Artemis restart may be required)."
);
if (confirmed)
_confirmedDisable = true;
return confirmed;
}
private void AddDevice(ArtemisDevice device)
{
Devices.Add(_deviceVmFactory.DeviceSettingsViewModel(device, this));
}
private void RemoveDevice(ArtemisDevice device)
{
DeviceSettingsViewModel? viewModel = Devices.FirstOrDefault(i => i.Device == device);
if (viewModel != null)
Devices.Remove(viewModel);
}
}
}
}

View File

@ -13,6 +13,7 @@ using Artemis.UI.Avalonia.Screens.Plugins.ViewModels;
using Artemis.UI.Avalonia.Shared;
using Artemis.UI.Avalonia.Shared.Services.Builders;
using Artemis.UI.Avalonia.Shared.Services.Interfaces;
using Avalonia.Threading;
using ReactiveUI;
namespace Artemis.UI.Avalonia.Screens.Settings.Tabs.ViewModels
@ -82,12 +83,16 @@ namespace Artemis.UI.Avalonia.Screens.Settings.Tabs.ViewModels
public void GetPluginInstances()
{
_instances = _pluginManagementService.GetAllPlugins()
.Select(p => _settingsVmFactory.CreatePluginSettingsViewModel(p))
.OrderBy(i => i.Plugin.Info.Name)
.ToList();
Plugins.Clear();
Dispatcher.UIThread.InvokeAsync(() =>
{
_instances = _pluginManagementService.GetAllPlugins()
.Select(p => _settingsVmFactory.CreatePluginSettingsViewModel(p))
.OrderBy(i => i.Plugin.Info.Name)
.ToList();
SearchPlugins(SearchPluginInput);
SearchPlugins(SearchPluginInput);
}, DispatcherPriority.Background);
}
private void SearchPlugins(string? searchPluginInput)

View File

@ -4,5 +4,22 @@
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
x:Class="Artemis.UI.Avalonia.Screens.Settings.Tabs.Views.DevicesTabView">
Welcome to Avalonia!
</UserControl>
<StackPanel MaxWidth="1050">
<TextBlock Classes="h4">Device management</TextBlock>
<TextBlock>
Below you view and manage the devices that were detected by Artemis.
</TextBlock>
<TextBlock>
Disabling a device will cause it to stop updating. Some SDKs will even go back to using manufacturer lighting (Artemis restart may be required).
</TextBlock>
<ItemsControl Items="{Binding Devices}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<WrapPanel />
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
</ItemsControl>
</StackPanel>
</UserControl>

View File

@ -1,9 +1,10 @@
using Avalonia.Controls;
using Artemis.UI.Avalonia.Screens.Settings.Tabs.ViewModels;
using Avalonia.Markup.Xaml;
using Avalonia.ReactiveUI;
namespace Artemis.UI.Avalonia.Screens.Settings.Tabs.Views
{
public partial class DevicesTabView : UserControl
public class DevicesTabView : ReactiveUserControl<DevicesTabViewModel>
{
public DevicesTabView()
{
@ -15,4 +16,4 @@ namespace Artemis.UI.Avalonia.Screens.Settings.Tabs.Views
AvaloniaXamlLoader.Load(this);
}
}
}
}