diff --git a/src/Artemis.Core/Models/ProfileConfiguration/Hotkey.cs b/src/Artemis.Core/Models/ProfileConfiguration/Hotkey.cs index ac2c9ed24..b7dc91525 100644 --- a/src/Artemis.Core/Models/ProfileConfiguration/Hotkey.cs +++ b/src/Artemis.Core/Models/ProfileConfiguration/Hotkey.cs @@ -1,4 +1,5 @@ -using Artemis.Core.Services; +using System; +using Artemis.Core.Services; using Artemis.Storage.Entities.Profile; namespace Artemis.Core; @@ -16,6 +17,14 @@ public class Hotkey : CorePropertyChanged, IStorageModel Entity = new ProfileConfigurationHotkeyEntity(); } + /// + public Hotkey(KeyboardKey? key, KeyboardModifierKey? modifiers) + { + Key = key; + Modifiers = modifiers; + Entity = new ProfileConfigurationHotkeyEntity(); + } + /// /// Creates a new instance of based on the provided entity /// @@ -46,7 +55,7 @@ public class Hotkey : CorePropertyChanged, IStorageModel /// if the event args match the hotkey; otherwise public bool MatchesEventArgs(ArtemisKeyboardKeyEventArgs eventArgs) { - return eventArgs.Key == Key && eventArgs.Modifiers == Modifiers; + return eventArgs.Key == Key && (eventArgs.Modifiers == Modifiers || (eventArgs.Modifiers == KeyboardModifierKey.None && Modifiers == null)); } #region Implementation of IStorageModel diff --git a/src/Artemis.Core/Services/Input/Events/ArtemisKeyboardKeyEventArgs.cs b/src/Artemis.Core/Services/Input/Events/ArtemisKeyboardKeyEventArgs.cs index e6193c515..4738bab71 100644 --- a/src/Artemis.Core/Services/Input/Events/ArtemisKeyboardKeyEventArgs.cs +++ b/src/Artemis.Core/Services/Input/Events/ArtemisKeyboardKeyEventArgs.cs @@ -34,4 +34,13 @@ public class ArtemisKeyboardKeyEventArgs : EventArgs /// Gets the modifiers that are pressed /// public KeyboardModifierKey Modifiers { get; } + + /// + /// Creates a hotkey matching the event. + /// + /// The resulting hotkey. + public Hotkey ToHotkey() + { + return new Hotkey {Key = Key, Modifiers = Modifiers}; + } } \ No newline at end of file diff --git a/src/Artemis.UI.Shared/Services/MainWindow/IMainWindowService.cs b/src/Artemis.UI.Shared/Services/MainWindow/IMainWindowService.cs index d85066451..0e18f6c68 100644 --- a/src/Artemis.UI.Shared/Services/MainWindow/IMainWindowService.cs +++ b/src/Artemis.UI.Shared/Services/MainWindow/IMainWindowService.cs @@ -11,7 +11,12 @@ public interface IMainWindowService : IArtemisSharedUIService /// Gets a boolean indicating whether the main window is currently open /// bool IsMainWindowOpen { get; } - + + /// + /// Gets a boolean indicating whether the main window is currently focused + /// + bool IsMainWindowFocused { get; } + /// /// Sets up the main window provider that controls the state of the main window /// diff --git a/src/Artemis.UI.Shared/Services/MainWindow/MainWindowService.cs b/src/Artemis.UI.Shared/Services/MainWindow/MainWindowService.cs index a95126c66..0a95a0087 100644 --- a/src/Artemis.UI.Shared/Services/MainWindow/MainWindowService.cs +++ b/src/Artemis.UI.Shared/Services/MainWindow/MainWindowService.cs @@ -10,6 +10,9 @@ internal class MainWindowService : IMainWindowService /// public bool IsMainWindowOpen { get; private set; } + /// + public bool IsMainWindowFocused { get; private set; } + protected virtual void OnMainWindowOpened() { MainWindowOpened?.Invoke(this, EventArgs.Empty); @@ -24,11 +27,13 @@ internal class MainWindowService : IMainWindowService protected virtual void OnMainWindowFocused() { MainWindowFocused?.Invoke(this, EventArgs.Empty); + IsMainWindowFocused = true; } protected virtual void OnMainWindowUnfocused() { MainWindowUnfocused?.Invoke(this, EventArgs.Empty); + IsMainWindowFocused = false; } private void SyncWithManager() diff --git a/src/Artemis.UI.Shared/Services/ProfileEditor/IToolViewModel.cs b/src/Artemis.UI.Shared/Services/ProfileEditor/IToolViewModel.cs index e34febdfe..7b1bf30d8 100644 --- a/src/Artemis.UI.Shared/Services/ProfileEditor/IToolViewModel.cs +++ b/src/Artemis.UI.Shared/Services/ProfileEditor/IToolViewModel.cs @@ -1,4 +1,6 @@ using System; +using Artemis.Core; +using Avalonia.Input; using Material.Icons; namespace Artemis.UI.Shared.Services.ProfileEditor; @@ -43,6 +45,11 @@ public interface IToolViewModel : IDisposable /// Gets the tooltip which this tool should show in the toolbar. /// public string ToolTip { get; } + + /// + /// Gets the keyboard hotkey that activates the tool. + /// + Hotkey? Hotkey { get; } } /// @@ -98,5 +105,8 @@ public abstract class ToolViewModel : ActivatableViewModelBase, IToolViewModel /// public abstract string ToolTip { get; } + /// + public abstract Hotkey? Hotkey { get; } + #endregion } \ No newline at end of file diff --git a/src/Artemis.UI.Shared/Utilities.cs b/src/Artemis.UI.Shared/Utilities.cs index 3b1c58495..48e0ae2b3 100644 --- a/src/Artemis.UI.Shared/Utilities.cs +++ b/src/Artemis.UI.Shared/Utilities.cs @@ -22,10 +22,11 @@ public static class UI static UI() { - KeyBindingsEnabled = InputElement.GotFocusEvent.Raised.Select(e => e.Item2.Source is not TextBox).StartWith(true); + CurrentKeyBindingsEnabled = InputElement.GotFocusEvent.Raised.Select(e => e.Item2.Source is not TextBox).StartWith(true); + CurrentKeyBindingsEnabled.Subscribe(b => KeyBindingsEnabled = b); MicaEnabled = MicaEnabledSubject.AsObservable(); } - + /// /// Gets the current IoC locator. /// @@ -36,10 +37,15 @@ public static class UI /// public static IClipboard Clipboard { get; set; } = null!; + /// + /// Gets an observable boolean indicating whether hotkeys are to be disabled. + /// + public static IObservable CurrentKeyBindingsEnabled { get; } + /// /// Gets a boolean indicating whether hotkeys are to be disabled. /// - public static IObservable KeyBindingsEnabled { get; } + public static bool KeyBindingsEnabled { get; private set; } /// /// Gets a boolean indicating whether the Mica effect should be enabled. diff --git a/src/Artemis.UI/Screens/ProfileEditor/Panels/MenuBar/MenuBarViewModel.cs b/src/Artemis.UI/Screens/ProfileEditor/Panels/MenuBar/MenuBarViewModel.cs index 9209789a1..7b323ab6e 100644 --- a/src/Artemis.UI/Screens/ProfileEditor/Panels/MenuBar/MenuBarViewModel.cs +++ b/src/Artemis.UI/Screens/ProfileEditor/Panels/MenuBar/MenuBarViewModel.cs @@ -58,7 +58,7 @@ public class MenuBarViewModel : ActivatableViewModelBase _focusNone = profileEditorService.FocusMode.Select(f => f == ProfileEditorFocusMode.None).ToProperty(this, vm => vm.FocusNone).DisposeWith(d); _focusFolder = profileEditorService.FocusMode.Select(f => f == ProfileEditorFocusMode.Folder).ToProperty(this, vm => vm.FocusFolder).DisposeWith(d); _focusSelection = profileEditorService.FocusMode.Select(f => f == ProfileEditorFocusMode.Selection).ToProperty(this, vm => vm.FocusSelection).DisposeWith(d); - _keyBindingsEnabled = Shared.UI.KeyBindingsEnabled.ToProperty(this, vm => vm.KeyBindingsEnabled).DisposeWith(d); + _keyBindingsEnabled = Shared.UI.CurrentKeyBindingsEnabled.ToProperty(this, vm => vm.KeyBindingsEnabled).DisposeWith(d); }); AddFolder = ReactiveCommand.Create(ExecuteAddFolder); diff --git a/src/Artemis.UI/Screens/ProfileEditor/Panels/Playback/PlaybackViewModel.cs b/src/Artemis.UI/Screens/ProfileEditor/Panels/Playback/PlaybackViewModel.cs index 9f6009934..d7fc9516e 100644 --- a/src/Artemis.UI/Screens/ProfileEditor/Panels/Playback/PlaybackViewModel.cs +++ b/src/Artemis.UI/Screens/ProfileEditor/Panels/Playback/PlaybackViewModel.cs @@ -53,7 +53,7 @@ public class PlaybackViewModel : ActivatableViewModelBase _currentTime = _profileEditorService.Time.ToProperty(this, vm => vm.CurrentTime).DisposeWith(d); _formattedCurrentTime = _profileEditorService.Time.Select(t => $"{Math.Floor(t.TotalSeconds):00}.{t.Milliseconds:000}").ToProperty(this, vm => vm.FormattedCurrentTime).DisposeWith(d); _playing = _profileEditorService.Playing.ToProperty(this, vm => vm.Playing).DisposeWith(d); - _keyBindingsEnabled = Shared.UI.KeyBindingsEnabled.ToProperty(this, vm => vm.KeyBindingsEnabled).DisposeWith(d); + _keyBindingsEnabled = Shared.UI.CurrentKeyBindingsEnabled.ToProperty(this, vm => vm.KeyBindingsEnabled).DisposeWith(d); // Update timer Timer updateTimer = new(TimeSpan.FromMilliseconds(60.0 / 1000)); diff --git a/src/Artemis.UI/Screens/ProfileEditor/Panels/ProfileTree/ProfileTreeViewModel.cs b/src/Artemis.UI/Screens/ProfileEditor/Panels/ProfileTree/ProfileTreeViewModel.cs index 4dcb93b82..82c559e98 100644 --- a/src/Artemis.UI/Screens/ProfileEditor/Panels/ProfileTree/ProfileTreeViewModel.cs +++ b/src/Artemis.UI/Screens/ProfileEditor/Panels/ProfileTree/ProfileTreeViewModel.cs @@ -46,7 +46,7 @@ public class ProfileTreeViewModel : TreeItemViewModel _focusNone = profileEditorService.FocusMode.Select(f => f == ProfileEditorFocusMode.None).ToProperty(this, vm => vm.FocusNone).DisposeWith(d); _focusFolder = profileEditorService.FocusMode.Select(f => f == ProfileEditorFocusMode.Folder).ToProperty(this, vm => vm.FocusFolder).DisposeWith(d); _focusSelection = profileEditorService.FocusMode.Select(f => f == ProfileEditorFocusMode.Selection).ToProperty(this, vm => vm.FocusSelection).DisposeWith(d); - _keyBindingsEnabled = Shared.UI.KeyBindingsEnabled.ToProperty(this, vm => vm.KeyBindingsEnabled).DisposeWith(d); + _keyBindingsEnabled = Shared.UI.CurrentKeyBindingsEnabled.ToProperty(this, vm => vm.KeyBindingsEnabled).DisposeWith(d); }); ClearSelection = ReactiveCommand.Create(() => profileEditorService.ChangeCurrentProfileElement(null), this.WhenAnyValue(vm => vm.KeyBindingsEnabled)); diff --git a/src/Artemis.UI/Screens/ProfileEditor/Panels/VisualEditor/Tools/SelectionAddToolViewModel.cs b/src/Artemis.UI/Screens/ProfileEditor/Panels/VisualEditor/Tools/SelectionAddToolViewModel.cs index 39be6c84f..484bca7f4 100644 --- a/src/Artemis.UI/Screens/ProfileEditor/Panels/VisualEditor/Tools/SelectionAddToolViewModel.cs +++ b/src/Artemis.UI/Screens/ProfileEditor/Panels/VisualEditor/Tools/SelectionAddToolViewModel.cs @@ -7,6 +7,7 @@ using Artemis.Core; using Artemis.Core.Services; using Artemis.UI.Shared.Services.ProfileEditor; using Artemis.UI.Shared.Services.ProfileEditor.Commands; +using Avalonia.Input; using Material.Icons; using ReactiveUI; using SkiaSharp; @@ -45,9 +46,12 @@ public class SelectionAddToolViewModel : ToolViewModel /// public override MaterialIconKind Icon => MaterialIconKind.SelectionDrag; + + /// + public override Hotkey? Hotkey { get; } = new(KeyboardKey.OemPlus, KeyboardModifierKey.Control); /// - public override string ToolTip => "Add LEDs to the current layer"; + public override string ToolTip => "Add LEDs to the current layer (Ctrl + +)"; public void AddLedsInRectangle(SKRect rect, bool expand, bool inverse) { diff --git a/src/Artemis.UI/Screens/ProfileEditor/Panels/VisualEditor/Tools/SelectionRemoveToolViewModel.cs b/src/Artemis.UI/Screens/ProfileEditor/Panels/VisualEditor/Tools/SelectionRemoveToolViewModel.cs index 2c7d0a995..806b9279a 100644 --- a/src/Artemis.UI/Screens/ProfileEditor/Panels/VisualEditor/Tools/SelectionRemoveToolViewModel.cs +++ b/src/Artemis.UI/Screens/ProfileEditor/Panels/VisualEditor/Tools/SelectionRemoveToolViewModel.cs @@ -4,8 +4,10 @@ using System.Linq; using System.Reactive.Disposables; using System.Reactive.Linq; using Artemis.Core; +using Artemis.Core.Services; using Artemis.UI.Shared.Services.ProfileEditor; using Artemis.UI.Shared.Services.ProfileEditor.Commands; +using Avalonia.Input; using Material.Icons; using ReactiveUI; using SkiaSharp; @@ -38,12 +40,15 @@ public class SelectionRemoveToolViewModel : ToolViewModel /// public override int Order => 3; + + /// + public override Hotkey? Hotkey { get; } = new(KeyboardKey.OemMinus, KeyboardModifierKey.Control); /// public override MaterialIconKind Icon => MaterialIconKind.SelectOff; /// - public override string ToolTip => "Remove LEDs from the current layer"; + public override string ToolTip => "Remove LEDs from the current layer (Ctrl + -)"; public void RemoveLedsInRectangle(SKRect rect) { diff --git a/src/Artemis.UI/Screens/ProfileEditor/Panels/VisualEditor/Tools/TransformToolViewModel.cs b/src/Artemis.UI/Screens/ProfileEditor/Panels/VisualEditor/Tools/TransformToolViewModel.cs index 7475f15eb..dbb5c75bf 100644 --- a/src/Artemis.UI/Screens/ProfileEditor/Panels/VisualEditor/Tools/TransformToolViewModel.cs +++ b/src/Artemis.UI/Screens/ProfileEditor/Panels/VisualEditor/Tools/TransformToolViewModel.cs @@ -3,11 +3,13 @@ using System.Reactive; using System.Reactive.Disposables; using System.Reactive.Linq; using Artemis.Core; +using Artemis.Core.Services; using Artemis.UI.Exceptions; using Artemis.UI.Shared.Extensions; using Artemis.UI.Shared.Services.ProfileEditor; using Artemis.UI.Shared.Services.ProfileEditor.Commands; using Avalonia; +using Avalonia.Input; using Material.Icons; using ReactiveUI; using SkiaSharp; @@ -95,12 +97,15 @@ public class TransformToolViewModel : ToolViewModel /// public override int Order => 3; + + /// + public override Hotkey? Hotkey { get; } = new(KeyboardKey.T, KeyboardModifierKey.Control); /// public override MaterialIconKind Icon => MaterialIconKind.TransitConnectionVariant; /// - public override string ToolTip => "Transform the shape of the current layer"; + public override string ToolTip => "Transform the shape of the current layer (Ctrl+T)"; public Rect ShapeBounds { diff --git a/src/Artemis.UI/Screens/ProfileEditor/ProfileEditorView.axaml b/src/Artemis.UI/Screens/ProfileEditor/ProfileEditorView.axaml index 1939fbbae..0ea17649e 100644 --- a/src/Artemis.UI/Screens/ProfileEditor/ProfileEditorView.axaml +++ b/src/Artemis.UI/Screens/ProfileEditor/ProfileEditorView.axaml @@ -8,19 +8,11 @@ xmlns:shared="clr-namespace:Artemis.UI.Shared.Services.ProfileEditor;assembly=Artemis.UI.Shared" mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450" x:Class="Artemis.UI.Screens.ProfileEditor.ProfileEditorView" - x:DataType="profileEditor:ProfileEditorViewModel"> + x:DataType="profileEditor:ProfileEditorViewModel" + Focusable="True"> - - - - - - - - -