diff --git a/.gitignore b/.gitignore
index 10351521f..f25a67c8b 100644
--- a/.gitignore
+++ b/.gitignore
@@ -196,3 +196,5 @@ FakesAssemblies/
src/Artemis\.Storage/Storage\.db
!src/Artemis.UI/screens/Settings/Debug
docfx/docfx_project/_site/
+
+src/.idea/
diff --git a/src/.idea/.idea.Artemis/.idea/.gitignore b/src/.idea/.idea.Artemis/.idea/.gitignore
deleted file mode 100644
index 0f04753a2..000000000
--- a/src/.idea/.idea.Artemis/.idea/.gitignore
+++ /dev/null
@@ -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
diff --git a/src/.idea/.idea.Artemis/.idea/.name b/src/.idea/.idea.Artemis/.idea/.name
deleted file mode 100644
index cc79fd55c..000000000
--- a/src/.idea/.idea.Artemis/.idea/.name
+++ /dev/null
@@ -1 +0,0 @@
-Artemis
\ No newline at end of file
diff --git a/src/.idea/.idea.Artemis/.idea/avalonia.xml b/src/.idea/.idea.Artemis/.idea/avalonia.xml
deleted file mode 100644
index c9701a626..000000000
--- a/src/.idea/.idea.Artemis/.idea/avalonia.xml
+++ /dev/null
@@ -1,102 +0,0 @@
-
-
-
-
-
-
\ No newline at end of file
diff --git a/src/.idea/.idea.Artemis/.idea/encodings.xml b/src/.idea/.idea.Artemis/.idea/encodings.xml
deleted file mode 100644
index df87cf951..000000000
--- a/src/.idea/.idea.Artemis/.idea/encodings.xml
+++ /dev/null
@@ -1,4 +0,0 @@
-
-
-
-
\ No newline at end of file
diff --git a/src/.idea/.idea.Artemis/.idea/indexLayout.xml b/src/.idea/.idea.Artemis/.idea/indexLayout.xml
deleted file mode 100644
index 7b08163ce..000000000
--- a/src/.idea/.idea.Artemis/.idea/indexLayout.xml
+++ /dev/null
@@ -1,8 +0,0 @@
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/src/.idea/.idea.Artemis/.idea/misc.xml b/src/.idea/.idea.Artemis/.idea/misc.xml
deleted file mode 100644
index 283b9b4d4..000000000
--- a/src/.idea/.idea.Artemis/.idea/misc.xml
+++ /dev/null
@@ -1,8 +0,0 @@
-
-
-
-
-
-
\ No newline at end of file
diff --git a/src/.idea/.idea.Artemis/.idea/vcs.xml b/src/.idea/.idea.Artemis/.idea/vcs.xml
deleted file mode 100644
index 6c0b86358..000000000
--- a/src/.idea/.idea.Artemis/.idea/vcs.xml
+++ /dev/null
@@ -1,6 +0,0 @@
-
-
-
-
-
-
\ No newline at end of file
diff --git a/src/Artemis.UI.Shared/Services/Builders/ContentDialogBuilder.cs b/src/Artemis.UI.Shared/Services/Builders/ContentDialogBuilder.cs
index b0a5c49d1..6825c2729 100644
--- a/src/Artemis.UI.Shared/Services/Builders/ContentDialogBuilder.cs
+++ b/src/Artemis.UI.Shared/Services/Builders/ContentDialogBuilder.cs
@@ -142,6 +142,16 @@ namespace Artemis.UI.Shared.Services.Builders
return this;
}
+ ///
+ /// Changes the dialog to take the full height of the window it's being hosted in.
+ ///
+ /// The builder that can be used to further build the dialog.
+ public ContentDialogBuilder WithFullSize()
+ {
+ _contentDialog.FullSizeDesired = true;
+ return this;
+ }
+
///
/// Asynchronously shows the content dialog.
///
diff --git a/src/Artemis.UI/Assets/Images/PhysicalLayouts/abnt.png b/src/Artemis.UI/Assets/Images/PhysicalLayouts/abnt.png
new file mode 100644
index 000000000..dc7593429
Binary files /dev/null and b/src/Artemis.UI/Assets/Images/PhysicalLayouts/abnt.png differ
diff --git a/src/Artemis.UI/Assets/Images/PhysicalLayouts/ansi.png b/src/Artemis.UI/Assets/Images/PhysicalLayouts/ansi.png
new file mode 100644
index 000000000..940a8f233
Binary files /dev/null and b/src/Artemis.UI/Assets/Images/PhysicalLayouts/ansi.png differ
diff --git a/src/Artemis.UI/Assets/Images/PhysicalLayouts/iso.png b/src/Artemis.UI/Assets/Images/PhysicalLayouts/iso.png
new file mode 100644
index 000000000..ee4272e81
Binary files /dev/null and b/src/Artemis.UI/Assets/Images/PhysicalLayouts/iso.png differ
diff --git a/src/Artemis.UI/Assets/Images/PhysicalLayouts/jis.png b/src/Artemis.UI/Assets/Images/PhysicalLayouts/jis.png
new file mode 100644
index 000000000..d872ddab3
Binary files /dev/null and b/src/Artemis.UI/Assets/Images/PhysicalLayouts/jis.png differ
diff --git a/src/Artemis.UI/Assets/Images/PhysicalLayouts/ks.png b/src/Artemis.UI/Assets/Images/PhysicalLayouts/ks.png
new file mode 100644
index 000000000..898f8a85e
Binary files /dev/null and b/src/Artemis.UI/Assets/Images/PhysicalLayouts/ks.png differ
diff --git a/src/Artemis.UI/Screens/Device/Tabs/DeviceLogicalLayoutDialogView.axaml b/src/Artemis.UI/Screens/Device/Tabs/DeviceLogicalLayoutDialogView.axaml
new file mode 100644
index 000000000..28514fcdc
--- /dev/null
+++ b/src/Artemis.UI/Screens/Device/Tabs/DeviceLogicalLayoutDialogView.axaml
@@ -0,0 +1,39 @@
+
+
+ Artemis couldn't automatically determine the logical layout of your
+
+
+
+ While not as important as the physical layout, setting the correct logical layout will allow Artemis to show the right keycaps (if a matching layout file is present)
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/Artemis.UI/Screens/Device/Tabs/DeviceLogicalLayoutDialogView.axaml.cs b/src/Artemis.UI/Screens/Device/Tabs/DeviceLogicalLayoutDialogView.axaml.cs
new file mode 100644
index 000000000..d3ebaf1ae
--- /dev/null
+++ b/src/Artemis.UI/Screens/Device/Tabs/DeviceLogicalLayoutDialogView.axaml.cs
@@ -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
+{
+ private readonly AutoCompleteBox _autoCompleteBox;
+
+ public DeviceLogicalLayoutDialogView()
+ {
+ InitializeComponent();
+
+ _autoCompleteBox = this.Get("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);
+ }
+}
\ No newline at end of file
diff --git a/src/Artemis.UI/Screens/Device/Tabs/DeviceLogicalLayoutDialogViewModel.cs b/src/Artemis.UI/Screens/Device/Tabs/DeviceLogicalLayoutDialogViewModel.cs
new file mode 100644
index 000000000..936b3968c
--- /dev/null
+++ b/src/Artemis.UI/Screens/Device/Tabs/DeviceLogicalLayoutDialogViewModel.cs
@@ -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(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 ApplyLogicalLayout { get; }
+ public ObservableCollection 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 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;
+ }
+
+}
\ No newline at end of file
diff --git a/src/Artemis.UI/Screens/Device/Tabs/DevicePhysicalLayoutDialogView.axaml b/src/Artemis.UI/Screens/Device/Tabs/DevicePhysicalLayoutDialogView.axaml
new file mode 100644
index 000000000..484a95b26
--- /dev/null
+++ b/src/Artemis.UI/Screens/Device/Tabs/DevicePhysicalLayoutDialogView.axaml
@@ -0,0 +1,104 @@
+
+
+
+
+ Artemis couldn't automatically determine the physical layout of your
+
+
+ In order for Artemis to know which keys are on your keyboard and where they're located, select the matching layout below.
+ P.S. Don't worry about missing special keys like num keys/function keys or macro keys, they aren't important here.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/Artemis.UI/Screens/Device/Tabs/DevicePhysicalLayoutDialogView.axaml.cs b/src/Artemis.UI/Screens/Device/Tabs/DevicePhysicalLayoutDialogView.axaml.cs
new file mode 100644
index 000000000..d2314c958
--- /dev/null
+++ b/src/Artemis.UI/Screens/Device/Tabs/DevicePhysicalLayoutDialogView.axaml.cs
@@ -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
+{
+ public DevicePhysicalLayoutDialogView()
+ {
+ InitializeComponent();
+ }
+
+ private void InitializeComponent()
+ {
+ AvaloniaXamlLoader.Load(this);
+ }
+}
\ No newline at end of file
diff --git a/src/Artemis.UI/Screens/Device/Tabs/DevicePhysicalLayoutDialogViewModel.cs b/src/Artemis.UI/Screens/Device/Tabs/DevicePhysicalLayoutDialogViewModel.cs
new file mode 100644
index 000000000..337277ed0
--- /dev/null
+++ b/src/Artemis.UI/Screens/Device/Tabs/DevicePhysicalLayoutDialogViewModel.cs
@@ -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(ExecuteApplyPhysicalLayout);
+ }
+
+ public ArtemisDevice Device { get; }
+ public ReactiveCommand ApplyPhysicalLayout { get; }
+ public bool Applied { get; set; }
+
+ private void ExecuteApplyPhysicalLayout(string physicalLayout)
+ {
+ Device.PhysicalLayout = Enum.Parse(physicalLayout);
+ Applied = true;
+ ContentDialog?.Hide(ContentDialogResult.Primary);
+ }
+
+ public static async Task SelectPhysicalLayout(IWindowService windowService, ArtemisDevice device)
+ {
+ await windowService.CreateContentDialog()
+ .WithTitle("Select physical layout")
+ .WithViewModel(out DevicePhysicalLayoutDialogViewModel vm, ("device", device))
+ .WithCloseButtonText("Cancel")
+ .ShowAsync();
+
+ return vm.Applied;
+ }
+}
\ No newline at end of file
diff --git a/src/Artemis.UI/Screens/Device/Tabs/DevicePropertiesTabView.axaml b/src/Artemis.UI/Screens/Device/Tabs/DevicePropertiesTabView.axaml
index 9ab42b07b..92cc7395e 100644
--- a/src/Artemis.UI/Screens/Device/Tabs/DevicePropertiesTabView.axaml
+++ b/src/Artemis.UI/Screens/Device/Tabs/DevicePropertiesTabView.axaml
@@ -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">
-
-
-
@@ -30,19 +28,19 @@
You can hover over a category for a more detailed description.
-
-
-
-
@@ -58,7 +56,7 @@
Grid.Column="1"
Margin="10 0"
VerticalAlignment="Center"
- Value="{Binding X}" />
+ Value="{CompiledBinding X}" />
mm
Y-coordinate
@@ -66,7 +64,7 @@
Grid.Column="1"
Margin="10 0"
VerticalAlignment="Center"
- Value="{Binding Y}" />
+ Value="{CompiledBinding Y}" />
mm
Scale
@@ -74,7 +72,7 @@
Grid.Column="1"
Margin="10 0"
VerticalAlignment="Center"
- Value="{Binding Scale}" />
+ Value="{CompiledBinding Scale}" />
times
Rotation
@@ -82,7 +80,7 @@
Grid.Column="1"
Margin="10 0"
VerticalAlignment="Center"
- Value="{Binding Rotation}" />
+ Value="{CompiledBinding Rotation}" />
deg
@@ -100,12 +98,12 @@
-
+
-
+
-
+
-
+
@@ -187,7 +185,10 @@
-