Device dialog - Logical layout selection

This commit is contained in:
Robert committed 2022-07-02 22:53:12 +02:00
1 parent a735568c8f
commit b3b324697a
25 files changed
+467 -176

No files matched your search

@@ -0,0 +1,39 @@
<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:device="clr-namespace:Artemis.UI.Screens.Device"
xmlns:globalization="clr-namespace:System.Globalization;assembly=System.Runtime"
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
x:Class="Artemis.UI.Screens.Device.DeviceLogicalLayoutDialogView"
x:DataType="device:DeviceLogicalLayoutDialogViewModel">
<StackPanel>
<TextBlock TextWrapping="Wrap">Artemis couldn't automatically determine the logical layout of your</TextBlock>
<TextBlock TextWrapping="Wrap" Text="{CompiledBinding Device.RgbDevice.DeviceInfo.DeviceName, Mode=OneWay}" />
<TextBlock Margin="0 10" TextWrapping="Wrap">
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)
</TextBlock>
<AutoCompleteBox HorizontalAlignment="Stretch"
Items="{CompiledBinding Regions}"
SelectedItem="{CompiledBinding SelectedRegion}"
ValueMemberBinding="{Binding EnglishName}"
Watermark="Enter keyboard country name"
FilterMode="Custom"
MaxDropDownHeight="200"
MinimumPrefixLength="0"
Name="RegionsAutoCompleteBox">
<AutoCompleteBox.ItemTemplate>
<DataTemplate DataType="{x:Type globalization:RegionInfo}">
<StackPanel Orientation="Horizontal">
<TextBlock Text="{CompiledBinding EnglishName}"></TextBlock>
<TextBlock Text=" ("/>
<TextBlock FontWeight="SemiBold" Text="{CompiledBinding TwoLetterISORegionName}"></TextBlock>
<TextBlock Text=")"/>
</StackPanel>
</DataTemplate>
</AutoCompleteBox.ItemTemplate>
</AutoCompleteBox>
</StackPanel>
</UserControl>
@@ -0,0 +1,47 @@
using System;
using System.Globalization;
using System.Threading.Tasks;
using Avalonia.Controls;
using Avalonia.Markup.Xaml;
using Avalonia.ReactiveUI;
using Avalonia.Threading;
using ReactiveUI;
namespace Artemis.UI.Screens.Device;
public partial class DeviceLogicalLayoutDialogView : ReactiveUserControl<DeviceLogicalLayoutDialogViewModel>
{
private readonly AutoCompleteBox _autoCompleteBox;
public DeviceLogicalLayoutDialogView()
{
InitializeComponent();
_autoCompleteBox = this.Get<AutoCompleteBox>("RegionsAutoCompleteBox");
_autoCompleteBox.ItemFilter += SearchRegions;
Dispatcher.UIThread.InvokeAsync(DelayedAutoFocus);
}
private async Task DelayedAutoFocus()
{
await Task.Delay(200);
_autoCompleteBox.Focus();
_autoCompleteBox.PopulateComplete();
}
private bool SearchRegions(string search, object item)
{
if (item is not RegionInfo regionInfo)
return false;
return regionInfo.EnglishName.Contains(search, StringComparison.OrdinalIgnoreCase) ||
regionInfo.NativeName.Contains(search, StringComparison.OrdinalIgnoreCase) ||
regionInfo.TwoLetterISORegionName.Contains(search, StringComparison.OrdinalIgnoreCase);
}
private void InitializeComponent()
{
AvaloniaXamlLoader.Load(this);
}
}
@@ -0,0 +1,72 @@
using System.Collections.ObjectModel;
using System.Globalization;
using System.Linq;
using System.Reactive;
using System.Reactive.Linq;
using System.Threading.Tasks;
using Artemis.Core;
using Artemis.UI.Shared;
using Artemis.UI.Shared.Services;
using FluentAvalonia.UI.Controls;
using ReactiveUI;
namespace Artemis.UI.Screens.Device;
public class DeviceLogicalLayoutDialogViewModel : ContentDialogViewModelBase
{
private const int LOCALE_NEUTRAL = 0x0000;
private const int LOCALE_CUSTOM_DEFAULT = 0x0c00;
private const int LOCALE_INVARIANT = 0x007F;
private RegionInfo? _selectedRegion;
public DeviceLogicalLayoutDialogViewModel(ArtemisDevice device)
{
Device = device;
ApplyLogicalLayout = ReactiveCommand.Create(ExecuteApplyLogicalLayout, this.WhenAnyValue(vm => vm.SelectedRegion).Select(r => r != null));
Regions = new ObservableCollection<RegionInfo>(CultureInfo.GetCultures(CultureTypes.SpecificCultures)
.Where(c => c.LCID != LOCALE_INVARIANT && c.LCID != LOCALE_NEUTRAL && c.LCID != LOCALE_CUSTOM_DEFAULT)
.Select(c => new RegionInfo(c.LCID))
.GroupBy(r => r.EnglishName)
.Select(g => g.First())
.OrderBy(r => r.EnglishName));
// Default to US/international
SelectedRegion = Regions.FirstOrDefault(r => r.TwoLetterISORegionName == "US");
}
public ArtemisDevice Device { get; }
public ReactiveCommand<Unit, Unit> ApplyLogicalLayout { get; }
public ObservableCollection<RegionInfo> Regions { get; }
public bool Applied { get; set; }
public RegionInfo? SelectedRegion
{
get => _selectedRegion;
set => RaiseAndSetIfChanged(ref _selectedRegion, value);
}
private void ExecuteApplyLogicalLayout()
{
if (SelectedRegion == null)
return;
Device.LogicalLayout = SelectedRegion.TwoLetterISORegionName;
Applied = true;
ContentDialog?.Hide(ContentDialogResult.Primary);
}
public static async Task<bool> SelectLogicalLayout(IWindowService windowService, ArtemisDevice device)
{
await windowService.CreateContentDialog()
.WithTitle("Select logical layout")
.WithViewModel(out DeviceLogicalLayoutDialogViewModel vm, ("device", device))
.WithCloseButtonText("Cancel")
.WithDefaultButton(Shared.Services.Builders.ContentDialogButton.Primary)
.HavingPrimaryButton(b => b.WithText("Select").WithCommand(vm.ApplyLogicalLayout))
.ShowAsync();
return vm.Applied;
}
}
@@ -0,0 +1,104 @@
<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:device="clr-namespace:Artemis.UI.Screens.Device"
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
x:Class="Artemis.UI.Screens.Device.DevicePhysicalLayoutDialogView"
x:DataType="device:DevicePhysicalLayoutDialogViewModel">
<Grid RowDefinitions="Auto,*">
<StackPanel Grid.Row="0">
<StackPanel Orientation="Horizontal">
<TextBlock> Artemis couldn't automatically determine the physical layout of your </TextBlock>
<TextBlock Text="{CompiledBinding Device.RgbDevice.DeviceInfo.DeviceName, Mode=OneWay}" />
</StackPanel>
<TextBlock>In order for Artemis to know which keys are on your keyboard and where they're located, select the matching layout below.</TextBlock>
<TextBlock Margin="0 15 0 0">P.S. Don't worry about missing special keys like num keys/function keys or macro keys, they aren't important here.</TextBlock>
</StackPanel>
<ScrollViewer Grid.Row="1" Margin="0 25" MaxHeight="425">
<StackPanel HorizontalAlignment="Center">
<Button Classes="AppBarButton"
Command="{CompiledBinding ApplyPhysicalLayout}"
CommandParameter="ISO"
Width="400"
Height="225">
<StackPanel>
<Image Source="/Assets/Images/PhysicalLayouts/iso.png" />
<TextBlock TextAlignment="Center" Classes="h4" Margin="0 10">
ISO
</TextBlock>
<TextBlock TextAlignment="Center" Classes="subtitle" TextWrapping="Wrap">
Most commonly used in the EU (tall enter)
</TextBlock>
</StackPanel>
</Button>
<Button Classes="AppBarButton"
Command="{CompiledBinding ApplyPhysicalLayout}"
CommandParameter="ANSI"
Width="400"
Height="225">
<StackPanel>
<Image Source="/Assets/Images/PhysicalLayouts/ansi.png" />
<TextBlock TextAlignment="Center" Classes="h4" Margin="0 10">
ANSI
</TextBlock>
<TextBlock TextAlignment="Center" Classes="subtitle" TextWrapping="Wrap">
Most commonly used in the US (short enter)
</TextBlock>
</StackPanel>
</Button>
<Button Classes="AppBarButton"
Command="{CompiledBinding ApplyPhysicalLayout}"
CommandParameter="ABNT"
Width="400"
Height="225">
<StackPanel>
<Image Source="/Assets/Images/PhysicalLayouts/abnt.png" />
<TextBlock TextAlignment="Center" Classes="h4" Margin="0 10">
ABNT
</TextBlock>
<TextBlock TextAlignment="Center" Classes="subtitle" TextWrapping="Wrap">
Most commonly used in Brazil/Portugal (based on ISO)
</TextBlock>
</StackPanel>
</Button>
<Button Classes="AppBarButton"
Command="{CompiledBinding ApplyPhysicalLayout}"
CommandParameter="KS"
Width="400"
Height="225">
<StackPanel>
<Image Source="/Assets/Images/PhysicalLayouts/ks.png" />
<TextBlock TextAlignment="Center" Classes="h4" Margin="0 10">
KS
</TextBlock>
<TextBlock TextAlignment="Center" Classes="subtitle" TextWrapping="Wrap">
Most commonly used in South Korea
</TextBlock>
</StackPanel>
</Button>
<Button Classes="AppBarButton"
Command="{CompiledBinding ApplyPhysicalLayout}"
CommandParameter="JIS"
Width="400"
Height="225">
<StackPanel>
<Image Source="/Assets/Images/PhysicalLayouts/jis.png" />
<TextBlock TextAlignment="Center" Classes="h4" Margin="0 10">
JIS
</TextBlock>
<TextBlock TextAlignment="Center" Classes="subtitle" TextWrapping="Wrap">
Most commonly used in Japan (based on ISO)
</TextBlock>
</StackPanel>
</Button>
</StackPanel>
</ScrollViewer>
</Grid>
</UserControl>
@@ -0,0 +1,19 @@
using Avalonia;
using Avalonia.Controls;
using Avalonia.Markup.Xaml;
using Avalonia.ReactiveUI;
namespace Artemis.UI.Screens.Device;
public partial class DevicePhysicalLayoutDialogView : ReactiveUserControl<DevicePhysicalLayoutDialogViewModel>
{
public DevicePhysicalLayoutDialogView()
{
InitializeComponent();
}
private void InitializeComponent()
{
AvaloniaXamlLoader.Load(this);
}
}
@@ -0,0 +1,41 @@
using System;
using System.Reactive;
using System.Threading.Tasks;
using Artemis.Core;
using Artemis.UI.Shared;
using Artemis.UI.Shared.Services;
using FluentAvalonia.UI.Controls;
using ReactiveUI;
namespace Artemis.UI.Screens.Device;
public class DevicePhysicalLayoutDialogViewModel : ContentDialogViewModelBase
{
public DevicePhysicalLayoutDialogViewModel(ArtemisDevice device)
{
Device = device;
ApplyPhysicalLayout = ReactiveCommand.Create<string>(ExecuteApplyPhysicalLayout);
}
public ArtemisDevice Device { get; }
public ReactiveCommand<string, Unit> ApplyPhysicalLayout { get; }
public bool Applied { get; set; }
private void ExecuteApplyPhysicalLayout(string physicalLayout)
{
Device.PhysicalLayout = Enum.Parse<KeyboardLayoutType>(physicalLayout);
Applied = true;
ContentDialog?.Hide(ContentDialogResult.Primary);
}
public static async Task<bool> SelectPhysicalLayout(IWindowService windowService, ArtemisDevice device)
{
await windowService.CreateContentDialog()
.WithTitle("Select physical layout")
.WithViewModel(out DevicePhysicalLayoutDialogViewModel vm, ("device", device))
.WithCloseButtonText("Cancel")
.ShowAsync();
return vm.Applied;
}
}
@@ -7,13 +7,11 @@
xmlns:converters="clr-namespace:Artemis.UI.Shared.Converters;assembly=Artemis.UI.Shared"
xmlns:device="clr-namespace:Artemis.UI.Screens.Device"
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="1200"
x:Class="Artemis.UI.Screens.Device.DevicePropertiesTabView">
x:Class="Artemis.UI.Screens.Device.DevicePropertiesTabView"
x:DataType="device:DevicePropertiesTabViewModel">
<UserControl.Resources>
<converters:SKColorToColorConverter x:Key="SKColorToColorConverter" />
</UserControl.Resources>
<Design.DataContext>
<device:DevicePropertiesTabViewModel />
</Design.DataContext>
<ScrollViewer HorizontalScrollBarVisibility="Disabled" VerticalScrollBarVisibility="Auto">
<!-- Body -->
@@ -30,19 +28,19 @@
You can hover over a category for a more detailed description.
</TextBlock>
<StackPanel Orientation="Horizontal">
<CheckBox IsChecked="{Binding HasDeskCategory}"
<CheckBox IsChecked="{CompiledBinding HasDeskCategory}"
ToolTip.Tip="A device acting as desk ornamentation such as a LED strip"
Content="Desk" />
<CheckBox IsChecked="{Binding HasMonitorCategory}"
<CheckBox IsChecked="{CompiledBinding HasMonitorCategory}"
ToolTip.Tip="A device attached to the monitor such as ambilight LEDs"
Content="Monitor" />
<CheckBox ToolTip.Tip="A device inside your computer case"
IsChecked="{Binding HasCaseCategory}"
IsChecked="{CompiledBinding HasCaseCategory}"
Content="Case" />
<CheckBox IsChecked="{Binding HasRoomCategory}"
<CheckBox IsChecked="{CompiledBinding HasRoomCategory}"
ToolTip.Tip="A device elsewhere in the room"
Content="Room" />
<CheckBox IsChecked="{Binding HasPeripheralsCategory}"
<CheckBox IsChecked="{CompiledBinding HasPeripheralsCategory}"
ToolTip.Tip="A peripheral such as a mouse or keyboard"
Content="Peripheral" />
</StackPanel>
@@ -58,7 +56,7 @@
Grid.Column="1"
Margin="10 0"
VerticalAlignment="Center"
Value="{Binding X}" />
Value="{CompiledBinding X}" />
<TextBlock Grid.Row="1" Grid.Column="2" VerticalAlignment="Center">mm</TextBlock>
<TextBlock Grid.Row="2" Grid.Column="0" VerticalAlignment="Center">Y-coordinate</TextBlock>
@@ -66,7 +64,7 @@
Grid.Column="1"
Margin="10 0"
VerticalAlignment="Center"
Value="{Binding Y}" />
Value="{CompiledBinding Y}" />
<TextBlock Grid.Row="2" Grid.Column="2" VerticalAlignment="Center">mm</TextBlock>
<TextBlock Grid.Row="3" Grid.Column="0" VerticalAlignment="Center">Scale</TextBlock>
@@ -74,7 +72,7 @@
Grid.Column="1"
Margin="10 0"
VerticalAlignment="Center"
Value="{Binding Scale}" />
Value="{CompiledBinding Scale}" />
<TextBlock Grid.Row="3" Grid.Column="2" VerticalAlignment="Center">times</TextBlock>
<TextBlock Grid.Row="4" Grid.Column="0" VerticalAlignment="Center">Rotation</TextBlock>
@@ -82,7 +80,7 @@
Grid.Column="1"
Margin="10 0"
VerticalAlignment="Center"
Value="{Binding Rotation}" />
Value="{CompiledBinding Rotation}" />
<TextBlock Grid.Row="4" Grid.Column="2" VerticalAlignment="Center">deg</TextBlock>
</Grid>
@@ -100,12 +98,12 @@
<Grid ColumnDefinitions="Auto,*,Auto" RowDefinitions="*,*,*,*">
<Label Grid.Row="0" Grid.Column="0" Content="R" VerticalAlignment="Center" />
<Slider Grid.Row="0" Grid.Column="1" Minimum="0" Maximum="200" Value="{Binding RedScale}" Margin="10 0" VerticalAlignment="Center" />
<Slider Grid.Row="0" Grid.Column="1" Minimum="0" Maximum="200" Value="{CompiledBinding RedScale}" Margin="10 0" VerticalAlignment="Center" />
<NumericUpDown Grid.Row="0"
Grid.Column="2"
VerticalAlignment="Center"
Width="65"
Value="{Binding RedScale}"
Value="{CompiledBinding RedScale}"
ShowButtonSpinner="False"
FormatString="{}{0:0.0}"
Minimum="0"
@@ -113,12 +111,12 @@
ClipValueToMinMax="True" />
<Label Grid.Row="1" Grid.Column="0" Content="G" VerticalAlignment="Center" />
<Slider Grid.Row="1" Grid.Column="1" Minimum="0" Maximum="200" Value="{Binding GreenScale}" Margin="10 0" VerticalAlignment="Center" />
<Slider Grid.Row="1" Grid.Column="1" Minimum="0" Maximum="200" Value="{CompiledBinding GreenScale}" Margin="10 0" VerticalAlignment="Center" />
<NumericUpDown Grid.Row="1"
Grid.Column="2"
VerticalAlignment="Center"
Width="65"
Value="{Binding GreenScale}"
Value="{CompiledBinding GreenScale}"
ShowButtonSpinner="False"
FormatString="{}{0:0.0}"
Minimum="0"
@@ -126,24 +124,24 @@
ClipValueToMinMax="True" />
<Label Grid.Row="2" Grid.Column="0" Content="B" VerticalAlignment="Center" />
<Slider Grid.Row="2" Grid.Column="1" Minimum="0" Maximum="200" Value="{Binding BlueScale}" Margin="10 0" Ticks="100" VerticalAlignment="Center" />
<Slider Grid.Row="2" Grid.Column="1" Minimum="0" Maximum="200" Value="{CompiledBinding BlueScale}" Margin="10 0" Ticks="100" VerticalAlignment="Center" />
<NumericUpDown Grid.Row="2"
Grid.Column="2"
VerticalAlignment="Center"
Width="65"
Value="{Binding BlueScale}"
Value="{CompiledBinding BlueScale}"
ShowButtonSpinner="False"
FormatString="{}{0:0.0}"
Minimum="0"
Maximum="200"
ClipValueToMinMax="True" />
<CheckBox Grid.Row="3" Grid.Column="0" Grid.ColumnSpan="2" IsChecked="{Binding DisplayOnDevices}" Content="Show preview" VerticalAlignment="Center" />
<CheckBox Grid.Row="3" Grid.Column="0" Grid.ColumnSpan="2" IsChecked="{CompiledBinding DisplayOnDevices}" Content="Show preview" VerticalAlignment="Center" />
<controls:ColorPickerButton Grid.Row="3"
Grid.Column="2"
VerticalAlignment="Center"
HorizontalAlignment="Right"
Color="{Binding CurrentColor, Converter={StaticResource SKColorToColorConverter}}"
Color="{CompiledBinding CurrentColor, Converter={StaticResource SKColorToColorConverter}}"
ShowAcceptDismissButtons="False" />
</Grid>
</StackPanel>
@@ -187,7 +185,10 @@
<!-- Buttons -->
<Grid Grid.Row="1" ColumnDefinitions="Auto,*">
<Button Grid.Column="0" Command="{Binding SelectPhysicalLayout}" ToolTip.Tip="Restart device setup, allowing you to select a new physical and logical layout">
<Button Grid.Column="0"
IsVisible="{CompiledBinding RequiresManualSetup}"
Command="{Binding RestartSetup}"
ToolTip.Tip="Restart device setup, allowing you to select a new physical and logical layout">
Restart setup
</Button>
@@ -33,12 +33,6 @@ namespace Artemis.UI.Screens.Device
private int _x;
private int _y;
#pragma warning disable CS8618 // Design-time constructor
public DevicePropertiesTabViewModel()
{
}
#pragma warning restore CS8618
public DevicePropertiesTabViewModel(ArtemisDevice device, ICoreService coreService, IRgbService rgbService, IWindowService windowService, INotificationService notificationService)
{
_coreService = coreService;
@@ -167,6 +161,8 @@ namespace Artemis.UI.Screens.Device
set => SetCategory(DeviceCategory.Peripherals, value);
}
public bool RequiresManualSetup => !Device.DeviceProvider.CanDetectPhysicalLayout || !Device.DeviceProvider.CanDetectLogicalLayout;
public void ApplyScaling()
{
Device.RedScale = RedScale / 100f;
@@ -201,12 +197,18 @@ namespace Artemis.UI.Screens.Device
}
}
public async Task SelectPhysicalLayout()
public async Task RestartSetup()
{
await _windowService.CreateContentDialog()
.WithTitle("Select layout")
.WithViewModel<DeviceLayoutDialogViewModel>(("device", Device))
.ShowAsync();
if (!RequiresManualSetup)
return;
if (!Device.DeviceProvider.CanDetectPhysicalLayout && !await DevicePhysicalLayoutDialogViewModel.SelectPhysicalLayout(_windowService, Device))
return;
if (!Device.DeviceProvider.CanDetectLogicalLayout && !await DeviceLogicalLayoutDialogViewModel.SelectLogicalLayout(_windowService, Device))
return;
await Task.Delay(400);
_rgbService.SaveDevice(Device);
_rgbService.ApplyBestDeviceLayout(Device);
}
public async Task Apply()
@@ -47,7 +47,6 @@ namespace Artemis.UI.Screens.Device
});
}
public ArtemisDevice Device { get; }
public ArtemisLed? SelectedLed