1
0
mirror of https://github.com/Artemis-RGB/Artemis synced 2025-12-12 21:38:38 +00:00

Device dialog - Logical layout selection

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

2
.gitignore vendored
View File

@ -196,3 +196,5 @@ FakesAssemblies/
src/Artemis\.Storage/Storage\.db
!src/Artemis.UI/screens/Settings/Debug
docfx/docfx_project/_site/
src/.idea/

View File

@ -1,13 +0,0 @@
# Default ignored files
/shelf/
/workspace.xml
# Rider ignored files
/modules.xml
/contentModel.xml
/.idea.Artemis.iml
/projectSettingsUpdater.xml
# Editor-based HTTP Client requests
/httpRequests/
# Datasource local storage ignored files
/dataSources/
/dataSources.local.xml

View File

@ -1 +0,0 @@
Artemis

View File

@ -1,102 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="AvaloniaProject">
<option name="projectPerEditor">
<map>
<entry key="Artemis.UI.Avalonia/App.axaml" value="Artemis.UI.Avalonia/Artemis.UI.Avalonia.csproj" />
<entry key="Artemis.UI.Avalonia/Screens/Home/Views/HomeView.axaml" value="Artemis.UI.Avalonia/Artemis.UI.Avalonia.csproj" />
<entry key="Artemis.UI.Avalonia/Screens/Main/Sidebar/Views/SidebarView.axaml" value="Artemis.UI.Avalonia/Artemis.UI.Avalonia.csproj" />
<entry key="Artemis.UI.Avalonia/Screens/Main/Views/MainWindow.axaml" value="Artemis.UI.Avalonia/Artemis.UI.Avalonia.csproj" />
<entry key="Artemis.UI.Avalonia/Screens/Root/Views/RootView.axaml" value="Artemis.UI.Avalonia/Artemis.UI.Avalonia.csproj" />
<entry key="Artemis.UI.Avalonia/Screens/Root/Views/SidebarCategoryView.axaml" value="Artemis.UI.Avalonia/Artemis.UI.Avalonia.csproj" />
<entry key="Artemis.UI.Avalonia/Screens/Sidebar/SidebarView.axaml" value="Artemis.UI.Avalonia/Artemis.UI.Avalonia.csproj" />
<entry key="Artemis.UI.Avalonia/Screens/Sidebar/Views/SidebarCategoryView.axaml" value="Artemis.UI.Avalonia/Artemis.UI.Avalonia.csproj" />
<entry key="Artemis.UI.Avalonia/Screens/Sidebar/Views/SidebarProfileConfigurationView.axaml" value="Artemis.UI.Avalonia/Artemis.UI.Avalonia.csproj" />
<entry key="Artemis.UI.Avalonia/Screens/SidebarView.axaml" value="Artemis.UI.Avalonia/Artemis.UI.Avalonia.csproj" />
<entry key="Artemis.UI.Avalonia/Views/MainWindow.axaml" value="Artemis.UI.Avalonia/Artemis.UI.Avalonia.csproj" />
<entry key="Artemis.UI.Shared/Controls/ArtemisIcon.axaml" value="Artemis.UI.Linux/Artemis.UI.Linux.csproj" />
<entry key="Artemis.UI.Shared/Controls/DraggableNumberBox.axaml" value="Artemis.UI.Linux/Artemis.UI.Linux.csproj" />
<entry key="Artemis.UI.Shared/Controls/EnumComboBox.axaml" value="Artemis.UI.Linux/Artemis.UI.Linux.csproj" />
<entry key="Artemis.UI.Shared/Controls/HotkeyBox.axaml" value="Artemis.UI.Linux/Artemis.UI.Linux.csproj" />
<entry key="Artemis.UI.Shared/Controls/ProfileConfigurationIcon.axaml" value="Artemis.UI.Linux/Artemis.UI.Linux.csproj" />
<entry key="Artemis.UI.Shared/DefaultTypes/DataModel/Display/DefaultDataModelDisplayView.axaml" value="Artemis.UI.Linux/Artemis.UI.Linux.csproj" />
<entry key="Artemis.UI.Shared/DefaultTypes/DataModel/Display/SKColorDataModelDisplayView.axaml" value="Artemis.UI.Linux/Artemis.UI.Linux.csproj" />
<entry key="Artemis.UI.Shared/Styles/Border.axaml" value="Artemis.UI.Linux/Artemis.UI.Linux.csproj" />
<entry key="Artemis.UI.Shared/Styles/Condensed.axaml" value="Artemis.UI.Windows/Artemis.UI.Windows.csproj" />
<entry key="Artemis.UI.Shared/Styles/TextBlock.axaml" value="Artemis.UI.Linux/Artemis.UI.Linux.csproj" />
<entry key="Artemis.UI.Windows/App.axaml" value="Artemis.UI.Windows/Artemis.UI.Windows.csproj" />
<entry key="Artemis.UI/DefaultTypes/PropertyInput/BoolPropertyInputView.axaml" value="Artemis.UI.Linux/Artemis.UI.Linux.csproj" />
<entry key="Artemis.UI/DefaultTypes/PropertyInput/BrushPropertyInputView.axaml" value="Artemis.UI.Linux/Artemis.UI.Linux.csproj" />
<entry key="Artemis.UI/DefaultTypes/PropertyInput/ColorGradientPropertyInputView.axaml" value="Artemis.UI.Windows/Artemis.UI.Windows.csproj" />
<entry key="Artemis.UI/DefaultTypes/PropertyInput/EnumPropertyInputView.axaml" value="Artemis.UI.Linux/Artemis.UI.Linux.csproj" />
<entry key="Artemis.UI/DefaultTypes/PropertyInput/FloatPropertyInputView.axaml" value="Artemis.UI.Windows/Artemis.UI.Windows.csproj" />
<entry key="Artemis.UI/DefaultTypes/PropertyInput/FloatRangePropertyInputView.axaml" value="Artemis.UI.Linux/Artemis.UI.Linux.csproj" />
<entry key="Artemis.UI/DefaultTypes/PropertyInput/IntPropertyInputView.axaml" value="Artemis.UI.Linux/Artemis.UI.Linux.csproj" />
<entry key="Artemis.UI/DefaultTypes/PropertyInput/IntRangePropertyInputView.axaml" value="Artemis.UI.Linux/Artemis.UI.Linux.csproj" />
<entry key="Artemis.UI/DefaultTypes/PropertyInput/PropertyInputStyles.axaml" value="Artemis.UI.Linux/Artemis.UI.Linux.csproj" />
<entry key="Artemis.UI/DefaultTypes/PropertyInput/SKColorPropertyInputView.axaml" value="Artemis.UI.Linux/Artemis.UI.Linux.csproj" />
<entry key="Artemis.UI/DefaultTypes/PropertyInput/SKPointPropertyInputView.axaml" value="Artemis.UI.Linux/Artemis.UI.Linux.csproj" />
<entry key="Artemis.UI/DefaultTypes/PropertyInput/SKSizePropertyInputView.axaml" value="Artemis.UI.Linux/Artemis.UI.Linux.csproj" />
<entry key="Artemis.UI/DefaultTypes/PropertyInput/StringPropertyInputView.axaml" value="Artemis.UI.Linux/Artemis.UI.Linux.csproj" />
<entry key="Artemis.UI/MainWindow.axaml" value="Artemis.UI.Linux/Artemis.UI.Linux.csproj" />
<entry key="Artemis.UI/Screens/Device/DevicePropertiesView.axaml" value="Artemis.UI.Linux/Artemis.UI.Linux.csproj" />
<entry key="Artemis.UI/Screens/Device/DeviceSettingsView.axaml" value="Artemis.UI.Linux/Artemis.UI.Linux.csproj" />
<entry key="Artemis.UI/Screens/Device/Tabs/DeviceInfoTabView.axaml" value="Artemis.UI.Linux/Artemis.UI.Linux.csproj" />
<entry key="Artemis.UI/Screens/Device/Tabs/DeviceLedsTabView.axaml" value="Artemis.UI.Linux/Artemis.UI.Linux.csproj" />
<entry key="Artemis.UI/Screens/Device/Tabs/DevicePropertiesTabView.axaml" value="Artemis.UI.Linux/Artemis.UI.Linux.csproj" />
<entry key="Artemis.UI/Screens/Device/Tabs/InputMappingsTabView.axaml" value="Artemis.UI.Linux/Artemis.UI.Linux.csproj" />
<entry key="Artemis.UI/Screens/Plugins/PluginFeatureView.axaml" value="Artemis.UI.Linux/Artemis.UI.Linux.csproj" />
<entry key="Artemis.UI/Screens/Plugins/PluginSettingsView.axaml" value="Artemis.UI.Linux/Artemis.UI.Linux.csproj" />
<entry key="Artemis.UI/Screens/ProfileEditor/Panels/DisplayCondition/ConditionTypes/StaticConditionView.axaml" value="Artemis.UI.Linux/Artemis.UI.Linux.csproj" />
<entry key="Artemis.UI/Screens/ProfileEditor/Panels/MenuBar/MenuBarView.axaml" value="Artemis.UI.Linux/Artemis.UI.Linux.csproj" />
<entry key="Artemis.UI/Screens/ProfileEditor/Panels/Playback/PlaybackView.axaml" value="Artemis.UI.Linux/Artemis.UI.Linux.csproj" />
<entry key="Artemis.UI/Screens/ProfileEditor/Panels/ProfileTree/Dialogs/AdaptionHints/CategoryAdaptionHintView.axaml" value="Artemis.UI.Linux/Artemis.UI.Linux.csproj" />
<entry key="Artemis.UI/Screens/ProfileEditor/Panels/ProfileTree/Dialogs/AdaptionHints/DeviceAdaptionHintView.axaml" value="Artemis.UI.Linux/Artemis.UI.Linux.csproj" />
<entry key="Artemis.UI/Screens/ProfileEditor/Panels/ProfileTree/Dialogs/AdaptionHints/KeyboardSectionAdaptionHintView.axaml" value="Artemis.UI.Linux/Artemis.UI.Linux.csproj" />
<entry key="Artemis.UI/Screens/ProfileEditor/Panels/ProfileTree/Dialogs/LayerHintsDialogView.axaml" value="Artemis.UI.Linux/Artemis.UI.Linux.csproj" />
<entry key="Artemis.UI/Screens/ProfileEditor/Panels/ProfileTree/FolderTreeItemView.axaml" value="Artemis.UI.Linux/Artemis.UI.Linux.csproj" />
<entry key="Artemis.UI/Screens/ProfileEditor/Panels/ProfileTree/LayerTreeItemView.axaml" value="Artemis.UI.Linux/Artemis.UI.Linux.csproj" />
<entry key="Artemis.UI/Screens/ProfileEditor/Panels/ProfileTree/ProfileTreeView.axaml" value="Artemis.UI.Linux/Artemis.UI.Linux.csproj" />
<entry key="Artemis.UI/Screens/ProfileEditor/Panels/Properties/Dialogs/AddEffectView.axaml" value="Artemis.UI.Windows/Artemis.UI.Windows.csproj" />
<entry key="Artemis.UI/Screens/ProfileEditor/Panels/Properties/PropertiesView.axaml" value="Artemis.UI.Linux/Artemis.UI.Linux.csproj" />
<entry key="Artemis.UI/Screens/ProfileEditor/Panels/Properties/Timeline/Keyframes/TimelineKeyframeView.axaml" value="Artemis.UI.Linux/Artemis.UI.Linux.csproj" />
<entry key="Artemis.UI/Screens/ProfileEditor/Panels/Properties/Timeline/TimelineGroupView.axaml" value="Artemis.UI.Linux/Artemis.UI.Linux.csproj" />
<entry key="Artemis.UI/Screens/ProfileEditor/Panels/Properties/Timeline/TimelinePropertyView.axaml" value="Artemis.UI.Linux/Artemis.UI.Linux.csproj" />
<entry key="Artemis.UI/Screens/ProfileEditor/Panels/Properties/Timeline/TimelineView.axaml" value="Artemis.UI.Linux/Artemis.UI.Linux.csproj" />
<entry key="Artemis.UI/Screens/ProfileEditor/Panels/Properties/Tree/ContentDialogs/LayerEffectRenameView.axaml" value="Artemis.UI.Linux/Artemis.UI.Linux.csproj" />
<entry key="Artemis.UI/Screens/ProfileEditor/Panels/Properties/Tree/ContentDialogs/SidebarCategoryEditView.axaml" value="Artemis.UI.Linux/Artemis.UI.Linux.csproj" />
<entry key="Artemis.UI/Screens/ProfileEditor/Panels/Properties/Tree/TreeGroupView.axaml" value="Artemis.UI.Windows/Artemis.UI.Windows.csproj" />
<entry key="Artemis.UI/Screens/ProfileEditor/Panels/Properties/Tree/TreePropertyView.axaml" value="Artemis.UI.Linux/Artemis.UI.Linux.csproj" />
<entry key="Artemis.UI/Screens/ProfileEditor/Panels/Properties/Windows/EffectConfigurationWindowView.axaml" value="Artemis.UI.Linux/Artemis.UI.Linux.csproj" />
<entry key="Artemis.UI/Screens/ProfileEditor/Panels/StatusBar/StatusBarView.axaml" value="Artemis.UI.Linux/Artemis.UI.Linux.csproj" />
<entry key="Artemis.UI/Screens/ProfileEditor/Panels/VisualEditor/Tools/SelectionAddToolView.axaml" value="Artemis.UI.Linux/Artemis.UI.Linux.csproj" />
<entry key="Artemis.UI/Screens/ProfileEditor/Panels/VisualEditor/Tools/SelectionRemoveToolView.axaml" value="Artemis.UI.Linux/Artemis.UI.Linux.csproj" />
<entry key="Artemis.UI/Screens/ProfileEditor/Panels/VisualEditor/Tools/TransformToolView.axaml" value="Artemis.UI.Linux/Artemis.UI.Linux.csproj" />
<entry key="Artemis.UI/Screens/ProfileEditor/Panels/VisualEditor/VisualEditorView.axaml" value="Artemis.UI.Windows/Artemis.UI.Windows.csproj" />
<entry key="Artemis.UI/Screens/ProfileEditor/ProfileEditorTitleBarView.axaml" value="Artemis.UI.Linux/Artemis.UI.Linux.csproj" />
<entry key="Artemis.UI/Screens/ProfileEditor/ProfileEditorView.axaml" value="Artemis.UI.Linux/Artemis.UI.Linux.csproj" />
<entry key="Artemis.UI/Screens/Root/SplashView.axaml" value="Artemis.UI.Windows/Artemis.UI.Windows.csproj" />
<entry key="Artemis.UI/Screens/Settings/Tabs/AboutTabView.axaml" value="Artemis.UI.Linux/Artemis.UI.Linux.csproj" />
<entry key="Artemis.UI/Screens/Settings/Tabs/GeneralTabView.axaml" value="Artemis.UI.Linux/Artemis.UI.Linux.csproj" />
<entry key="Artemis.UI/Screens/Settings/Tabs/PluginsTabView.axaml" value="Artemis.UI.Linux/Artemis.UI.Linux.csproj" />
<entry key="Artemis.UI/Screens/Sidebar/ContentDialogs/SidebarCategoryEditView.axaml" value="Artemis.UI.Linux/Artemis.UI.Linux.csproj" />
<entry key="Artemis.UI/Screens/Sidebar/Dialogs/ModuleActivationRequirementView.axaml" value="Artemis.UI.Linux/Artemis.UI.Linux.csproj" />
<entry key="Artemis.UI/Screens/Sidebar/Dialogs/ModuleActivationRequirementsView.axaml" value="Artemis.UI.Linux/Artemis.UI.Linux.csproj" />
<entry key="Artemis.UI/Screens/Sidebar/Dialogs/ProfileConfigurationEditView.axaml" value="Artemis.UI.Windows/Artemis.UI.Windows.csproj" />
<entry key="Artemis.UI/Screens/Sidebar/SidebarCategoryView.axaml" value="Artemis.UI.Windows/Artemis.UI.Windows.csproj" />
<entry key="Artemis.UI/Screens/Sidebar/SidebarProfileConfigurationView.axaml" value="Artemis.UI.Linux/Artemis.UI.Linux.csproj" />
<entry key="Artemis.UI/Screens/Sidebar/SidebarView.axaml" value="Artemis.UI.Linux/Artemis.UI.Linux.csproj" />
<entry key="Artemis.UI/Screens/SurfaceEditor/ListDeviceView.axaml" value="Artemis.UI.Linux/Artemis.UI.Linux.csproj" />
<entry key="Artemis.UI/Screens/SurfaceEditor/SurfaceDeviceView.axaml" value="Artemis.UI.Linux/Artemis.UI.Linux.csproj" />
<entry key="Artemis.UI/Screens/SurfaceEditor/SurfaceEditorView.axaml" value="Artemis.UI.Linux/Artemis.UI.Linux.csproj" />
<entry key="Artemis.UI/Screens/VisualScripting/NodePickerView.axaml" value="Artemis.UI.Linux/Artemis.UI.Linux.csproj" />
<entry key="Artemis.UI/Screens/VisualScripting/NodeScriptView.axaml" value="Artemis.UI.Linux/Artemis.UI.Linux.csproj" />
<entry key="Artemis.UI/Screens/VisualScripting/NodeScriptWindowView.axaml" value="Artemis.UI.Windows/Artemis.UI.Windows.csproj" />
<entry key="Artemis.UI/Screens/VisualScripting/NodeView.axaml" value="Artemis.UI.Linux/Artemis.UI.Linux.csproj" />
<entry key="Artemis.UI/Screens/Workshop/WorkshopView.axaml" value="Artemis.UI.Windows/Artemis.UI.Windows.csproj" />
<entry key="Avalonia/Artemis.UI/Screens/Debugger/Tabs/Render/RenderDebugView.axaml" value="Avalonia/Artemis.UI.Linux/Artemis.UI.Linux.csproj" />
<entry key="Avalonia/Artemis.UI/Styles/Artemis.axaml" value="Avalonia/Artemis.UI.Linux/Artemis.UI.Linux.csproj" />
</map>
</option>
</component>
</project>

View File

@ -1,4 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="Encoding" addBOMForNewFiles="with BOM under Windows, with no BOM otherwise" />
</project>

View File

@ -1,8 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="UserContentModel">
<attachedFolders />
<explicitIncludes />
<explicitExcludes />
</component>
</project>

View File

@ -1,8 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="SwUserDefinedSpecifications">
<option name="specTypeByUrl">
<map />
</option>
</component>
</project>

View File

@ -1,6 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="VcsDirectoryMappings">
<mapping directory="$PROJECT_DIR$/.." vcs="Git" />
</component>
</project>

View File

@ -142,6 +142,16 @@ namespace Artemis.UI.Shared.Services.Builders
return this;
}
/// <summary>
/// Changes the dialog to take the full height of the window it's being hosted in.
/// </summary>
/// <returns>The builder that can be used to further build the dialog.</returns>
public ContentDialogBuilder WithFullSize()
{
_contentDialog.FullSizeDesired = true;
return this;
}
/// <summary>
/// Asynchronously shows the content dialog.
/// </summary>

Binary file not shown.

After

Width:  |  Height:  |  Size: 27 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 26 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 27 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 28 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 27 KiB

View File

@ -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>

View File

@ -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);
}
}

View File

@ -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;
}
}

View File

@ -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>

View File

@ -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);
}
}

View File

@ -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;
}
}

View File

@ -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>

View File

@ -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()

View File

@ -47,7 +47,6 @@ namespace Artemis.UI.Screens.Device
});
}
public ArtemisDevice Device { get; }
public ArtemisLed? SelectedLed

View File

@ -0,0 +1,96 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Artemis.Core;
using Artemis.Core.Services;
using Artemis.UI.Screens.Device;
using Artemis.UI.Services.Interfaces;
using Artemis.UI.Shared.Services;
using Artemis.UI.Shared.Services.Builders;
using Artemis.UI.Shared.Services.MainWindow;
using Avalonia.Threading;
using RGB.NET.Core;
using KeyboardLayoutType = Artemis.Core.KeyboardLayoutType;
namespace Artemis.UI.Services;
public class DeviceLayoutService : IDeviceLayoutService
{
private readonly List<ArtemisDevice> _ignoredDevices;
private readonly IMainWindowService _mainWindowService;
private readonly IRgbService _rgbService;
private readonly IWindowService _windowService;
public DeviceLayoutService(IRgbService rgbService, IMainWindowService mainWindowService, IWindowService windowService)
{
_rgbService = rgbService;
_mainWindowService = mainWindowService;
_windowService = windowService;
_ignoredDevices = new List<ArtemisDevice>();
rgbService.DeviceAdded += RgbServiceOnDeviceAdded;
mainWindowService.MainWindowOpened += WindowServiceOnMainWindowOpened;
}
private async Task RequestLayoutInput(ArtemisDevice artemisDevice)
{
bool configure = await _windowService.ShowConfirmContentDialog(
"Device requires layout info",
$"Artemis could not detect the layout of your {artemisDevice.RgbDevice.DeviceInfo.DeviceName}. Please configure it manually",
"Configure",
"Ignore for now"
);
if (!configure)
{
_ignoredDevices.Add(artemisDevice);
return;
}
if (!artemisDevice.DeviceProvider.CanDetectPhysicalLayout && !await DevicePhysicalLayoutDialogViewModel.SelectPhysicalLayout(_windowService, artemisDevice))
{
_ignoredDevices.Add(artemisDevice);
return;
}
if (!artemisDevice.DeviceProvider.CanDetectLogicalLayout && !await DeviceLogicalLayoutDialogViewModel.SelectLogicalLayout(_windowService, artemisDevice))
{
_ignoredDevices.Add(artemisDevice);
return;
}
await Task.Delay(400);
_rgbService.SaveDevice(artemisDevice);
_rgbService.ApplyBestDeviceLayout(artemisDevice);
}
private bool DeviceNeedsLayout(ArtemisDevice d)
{
return d.DeviceType == RGBDeviceType.Keyboard &&
(d.LogicalLayout == null || d.PhysicalLayout == KeyboardLayoutType.Unknown) &&
(!d.DeviceProvider.CanDetectLogicalLayout || !d.DeviceProvider.CanDetectPhysicalLayout);
}
private async void WindowServiceOnMainWindowOpened(object? sender, EventArgs e)
{
List<ArtemisDevice> devices = _rgbService.Devices.Where(device => DeviceNeedsLayout(device) && !_ignoredDevices.Contains(device)).ToList();
await Dispatcher.UIThread.InvokeAsync(async () =>
{
foreach (ArtemisDevice artemisDevice in devices)
await RequestLayoutInput(artemisDevice);
});
}
private async void RgbServiceOnDeviceAdded(object? sender, DeviceEventArgs e)
{
if (_ignoredDevices.Contains(e.Device) || !DeviceNeedsLayout(e.Device) || !_mainWindowService.IsMainWindowOpen)
return;
await Dispatcher.UIThread.InvokeAsync(async () => await RequestLayoutInput(e.Device));
}
}
public interface IDeviceLayoutService : IArtemisUIService
{
}

View File

@ -33,6 +33,7 @@ public class RegistrationService : IRegistrationService
IProfileEditorService profileEditorService,
INodeService nodeService,
IDataModelUIService dataModelUIService,
IDeviceLayoutService deviceLayoutService, // here to make sure it is instantiated
IEnumerable<IToolViewModel> toolViewModels)
{
_kernel = kernel;