From 5d3fdfdab3c9df54feb3c42c9715cef0256f1da5 Mon Sep 17 00:00:00 2001 From: Benjamin Popp Date: Sun, 9 May 2021 22:24:06 -0500 Subject: [PATCH] Add keyboard shortcuts for Display As options --- src/HexManiac.Core/HexManiac.Core.csproj | 1 + .../ViewModels/EditorViewModel.cs | 16 +++++ src/HexManiac.Core/ViewModels/Shortcuts.cs | 42 ++++++++++++ src/HexManiac.Core/ViewModels/ViewPort.cs | 3 + .../ViewModels/Visitors/ContextItemFactory.cs | 12 ++-- src/HexManiac.Tests/ImageTests.cs | 8 +-- src/HexManiac.WPF/HexManiac.WPF.csproj | 1 + .../Resources/MultiKeyGesture.cs | 65 +++++++++++++++++++ src/HexManiac.WPF/Windows/MainWindow.xaml | 15 ++++- 9 files changed, 149 insertions(+), 14 deletions(-) create mode 100644 src/HexManiac.Core/ViewModels/Shortcuts.cs create mode 100644 src/HexManiac.WPF/Resources/MultiKeyGesture.cs diff --git a/src/HexManiac.Core/HexManiac.Core.csproj b/src/HexManiac.Core/HexManiac.Core.csproj index e2facafe..305a9e33 100644 --- a/src/HexManiac.Core/HexManiac.Core.csproj +++ b/src/HexManiac.Core/HexManiac.Core.csproj @@ -107,6 +107,7 @@ + diff --git a/src/HexManiac.Core/ViewModels/EditorViewModel.cs b/src/HexManiac.Core/ViewModels/EditorViewModel.cs index f6a29417..174fda23 100644 --- a/src/HexManiac.Core/ViewModels/EditorViewModel.cs +++ b/src/HexManiac.Core/ViewModels/EditorViewModel.cs @@ -62,6 +62,10 @@ namespace HavenSoft.HexManiac.Core.ViewModels { resetTheme = new StubCommand(), clearError = new StubCommand(), clearMessage = new StubCommand(), + displayAsText = new StubCommand(), + displayAsEventScript = new StubCommand(), + displayAsSprite = new StubCommand(), + displayAsColorPalette = new StubCommand(), toggleMatrix = new StubCommand(), toggleScrollAnimation = new StubCommand(), toggleTableHeaders = new StubCommand(); @@ -104,6 +108,10 @@ namespace HavenSoft.HexManiac.Core.ViewModels { public ICommand ResetTheme => resetTheme; public ICommand ClearError => clearError; public ICommand ClearMessage => clearMessage; + public ICommand DisplayAsText => displayAsText; + public ICommand DisplayAsEventScript => displayAsEventScript; + public ICommand DisplayAsSprite => displayAsSprite; + public ICommand DisplayAsColorPalette => displayAsColorPalette; public ICommand ToggleMatrix => toggleMatrix; public ICommand ToggleScrollAnimation => toggleScrollAnimation; public ICommand ToggleTableHeaders => toggleTableHeaders; @@ -423,6 +431,10 @@ namespace HavenSoft.HexManiac.Core.ViewModels { back = CreateWrapperForSelected(tab => tab.Back); forward = CreateWrapperForSelected(tab => tab.Forward); resetAlignment = CreateWrapperForSelected(tab => tab.ResetAlignment); + displayAsText = CreateWrapperForSelected(tab => (tab as ViewPort)?.Shortcuts.DisplayAsText); + displayAsEventScript = CreateWrapperForSelected(tab => (tab as ViewPort)?.Shortcuts.DisplayAsEventScript); + displayAsSprite = CreateWrapperForSelected(tab => (tab as ViewPort)?.Shortcuts.DisplayAsSprite); + displayAsColorPalette = CreateWrapperForSelected(tab => (tab as ViewPort)?.Shortcuts.DisplayAsColorPalette); saveAll = CreateWrapperForAll(tab => tab.Save); closeAll = CreateWrapperForAll(tab => tab.Close); @@ -442,6 +454,10 @@ namespace HavenSoft.HexManiac.Core.ViewModels { { tab => tab.Back, (sender, e) => back.CanExecuteChanged.Invoke(this, e) }, { tab => tab.Forward, (sender, e) => forward.CanExecuteChanged.Invoke(this, e) }, { tab => tab.ResetAlignment, (sender, e) => resetAlignment.CanExecuteChanged.Invoke(this, e) }, + { tab => (tab as ViewPort)?.Shortcuts.DisplayAsText, (sender, e) => displayAsText.RaiseCanExecuteChanged() }, + { tab => (tab as ViewPort)?.Shortcuts.DisplayAsEventScript, (sender, e) => displayAsEventScript.RaiseCanExecuteChanged() }, + { tab => (tab as ViewPort)?.Shortcuts.DisplayAsSprite, (sender, e) => displayAsSprite.RaiseCanExecuteChanged() }, + { tab => (tab as ViewPort)?.Shortcuts.DisplayAsColorPalette, (sender, e) => displayAsColorPalette.RaiseCanExecuteChanged() }, }; var metadata = fileSystem.MetadataFor(ApplicationName) ?? new string[0]; diff --git a/src/HexManiac.Core/ViewModels/Shortcuts.cs b/src/HexManiac.Core/ViewModels/Shortcuts.cs new file mode 100644 index 00000000..56b12909 --- /dev/null +++ b/src/HexManiac.Core/ViewModels/Shortcuts.cs @@ -0,0 +1,42 @@ +using HavenSoft.HexManiac.Core.Models.Runs; +using System.Windows.Input; +using static HavenSoft.HexManiac.Core.ICommandExtensions; + +namespace HavenSoft.HexManiac.Core.ViewModels { + public class Shortcuts : ViewModelCore { + private StubCommand displayAsEventScript, displayAsText, displayAsSprite, displayAsColorPalette; + + public ViewPort ViewPort { get; } + + public ICommand DisplayAsEventScript => StubCommand(ref displayAsEventScript, ExecuteDisplayAsEventScript, CanExecuteDisplayAs); + public ICommand DisplayAsText=> StubCommand(ref displayAsText, ExecuteDisplayAsText, CanExecuteDisplayAs); + public ICommand DisplayAsSprite=> StubCommand(ref displayAsSprite, ExecuteDisplayAsSprite, CanExecuteDisplayAs); + public ICommand DisplayAsColorPalette=> StubCommand(ref displayAsColorPalette, ExecuteDisplayAsColorPalette, CanExecuteDisplayAs); + + public Shortcuts(ViewPort viewPort) => ViewPort = viewPort; + + private bool CanExecuteDisplayAs() { + var spot = ViewPort.ConvertViewPointToAddress(ViewPort.SelectionStart); + var nextRun = ViewPort.Model.GetNextRun(spot); + return nextRun.Start > spot || nextRun is NoInfoRun; + } + + private void ExecuteDisplayAsEventScript() { + ViewPort.Tools.CodeTool.IsEventScript.Execute(); + ViewPort.Refresh(); + ViewPort.UpdateToolsFromSelection(ViewPort.ConvertViewPointToAddress(ViewPort.SelectionStart)); + } + + private void ExecuteDisplayAsText() { + ViewPort.IsText.Execute(); + } + + private void ExecuteDisplayAsSprite() { + ViewPort.Tools.SpriteTool.IsSprite.Execute(); + } + + private void ExecuteDisplayAsColorPalette() { + ViewPort.Tools.SpriteTool.IsPalette.Execute(); + } + } +} diff --git a/src/HexManiac.Core/ViewModels/ViewPort.cs b/src/HexManiac.Core/ViewModels/ViewPort.cs index ce9d47b5..e5e6105d 100644 --- a/src/HexManiac.Core/ViewModels/ViewPort.cs +++ b/src/HexManiac.Core/ViewModels/ViewPort.cs @@ -675,6 +675,8 @@ namespace HavenSoft.HexManiac.Core.ViewModels { public event EventHandler RequestMenuClose; #pragma warning restore 0067 + public Shortcuts Shortcuts { get; } + #region Constructors public ViewPort() : this(new LoadedFile(string.Empty, new byte[0])) { } @@ -712,6 +714,7 @@ namespace HavenSoft.HexManiac.Core.ViewModels { ImplementCommands(); if (changeHistory == null) CascadeScripts(); // if we're sharing history with another viewmodel, our model has already been updated like this. RefreshBackingData(); + Shortcuts = new Shortcuts(this); } public ViewPort(LoadedFile file) : this(file.Name, new BasicModel(file.Contents), InstantDispatch.Instance) { } diff --git a/src/HexManiac.Core/ViewModels/Visitors/ContextItemFactory.cs b/src/HexManiac.Core/ViewModels/Visitors/ContextItemFactory.cs index 2cd384ad..c86eeae9 100644 --- a/src/HexManiac.Core/ViewModels/Visitors/ContextItemFactory.cs +++ b/src/HexManiac.Core/ViewModels/Visitors/ContextItemFactory.cs @@ -68,14 +68,10 @@ namespace HavenSoft.HexManiac.Core.ViewModels.Visitors { public void Visit(None dataFormat, byte data) { Results.Add(new ContextItemGroup("Display As...") { - new ContextItem("Text", ViewPort.IsText.Execute), - new ContextItem("Sprite", ViewPort.Tools.SpriteTool.IsSprite.Execute), - new ContextItem("Palette", ViewPort.Tools.SpriteTool.IsPalette.Execute), - new ContextItem("Event Script", arg => { - ViewPort.Tools.CodeTool.IsEventScript.Execute(); - ViewPort.Refresh(); - ViewPort.UpdateToolsFromSelection(ViewPort.ConvertViewPointToAddress(ViewPort.SelectionStart)); - }), + new ContextItem("Text", ViewPort.Shortcuts.DisplayAsText.Execute) { ShortcutText = "Ctrl+D, T" }, + new ContextItem("Sprite", ViewPort.Shortcuts.DisplayAsSprite.Execute) { ShortcutText = "Ctrl+D, S" }, + new ContextItem("Color Palette", ViewPort.Shortcuts.DisplayAsColorPalette.Execute) { ShortcutText = "Ctrl+D, C" }, + new ContextItem("Event Script", ViewPort.Shortcuts.DisplayAsEventScript.Execute) { ShortcutText = "Ctrl+D, E" }, }); if (data == 0xFF && ViewPort.SelectionEnd == ViewPort.SelectionStart && ViewPort[ViewPort.SelectionStart].Format is None) Results.Add(new ContextItemGroup("Create New...") { diff --git a/src/HexManiac.Tests/ImageTests.cs b/src/HexManiac.Tests/ImageTests.cs index f4fef0fe..a20700a9 100644 --- a/src/HexManiac.Tests/ImageTests.cs +++ b/src/HexManiac.Tests/ImageTests.cs @@ -363,7 +363,7 @@ namespace HavenSoft.HexManiac.Tests { ViewPort.Edit("^bob @20 ^tom @bob "); var items = ViewPort.GetContextMenuItems(new Point(0, 0)); var item = items.Single(element => element.Text == "Display As..."); - item = ((ContextItemGroup)item).Single(element => element.Text == "Palette"); + item = ((ContextItemGroup)item).Single(element => element.Text == "Color Palette"); item.Command.Execute(); var anchor = Model.GetAnchorFromAddress(-1, 0); @@ -485,7 +485,7 @@ namespace HavenSoft.HexManiac.Tests { var items = ViewPort.GetContextMenuItems(new Point(0, 0)); var item = items.Single(element => element.Text == "Display As..."); - item = ((ContextItemGroup)item).Single(element => element.Text == "Palette"); + item = ((ContextItemGroup)item).Single(element => element.Text == "Color Palette"); var makePalette = item.Command; makePalette.Execute(); @@ -498,7 +498,7 @@ namespace HavenSoft.HexManiac.Tests { var items = ViewPort.GetContextMenuItems(new Point(0, 0)); var item = items.Single(element => element.Text == "Display As..."); - item = ((ContextItemGroup)item).Single(element => element.Text == "Palette"); + item = ((ContextItemGroup)item).Single(element => element.Text == "Color Palette"); var makePalette = item.Command; makePalette.Execute(); @@ -654,7 +654,7 @@ namespace HavenSoft.HexManiac.Tests { ViewPort.SelectionStart = new Point(1, 0); var group = (ContextItemGroup)ViewPort.GetContextMenuItems(new Point(1, 0)).Single(item => item.Text == "Display As..."); - var contextItem = group.Single(item => item.Text == "Palette"); + var contextItem = group.Single(item => item.Text == "Color Palette"); contextItem.Command.Execute(); Assert.Equal(ViewPort.Tools.SpriteTool, ViewPort.Tools.SelectedTool); diff --git a/src/HexManiac.WPF/HexManiac.WPF.csproj b/src/HexManiac.WPF/HexManiac.WPF.csproj index e66a18b6..35fcd233 100644 --- a/src/HexManiac.WPF/HexManiac.WPF.csproj +++ b/src/HexManiac.WPF/HexManiac.WPF.csproj @@ -91,6 +91,7 @@ TextBoxLookAlike.xaml + OptionDialog.xaml diff --git a/src/HexManiac.WPF/Resources/MultiKeyGesture.cs b/src/HexManiac.WPF/Resources/MultiKeyGesture.cs new file mode 100644 index 00000000..7f60eff4 --- /dev/null +++ b/src/HexManiac.WPF/Resources/MultiKeyGesture.cs @@ -0,0 +1,65 @@ +using HavenSoft.HexManiac.Core; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Windows.Input; +using System.Windows.Markup; + +namespace HavenSoft.HexManiac.WPF.Resources { + public class MultiKeyGesture : KeyGesture { + + private readonly ModifierKeys mods; + private readonly IReadOnlyList keys; + private int nextKey; + + public MultiKeyGesture(ModifierKeys mods, params Key[] keys) : base(keys[0], mods) { + this.mods = mods; + this.keys = keys; + } + + public override bool Matches(object targetElement, InputEventArgs inputEventArgs) { + if (!(inputEventArgs is KeyEventArgs keyArgs)) return false; + if (keyArgs.IsRepeat) return false; + + if (nextKey == 0) { + if (new KeyGesture(keys[0], mods).Matches(targetElement, keyArgs)) { + nextKey += 1; + } else { + nextKey = 0; + } + return false; + } else if (nextKey == keys.Count - 1) { + nextKey = 0; + return keyArgs.Key == keys[keys.Count - 1]; + } else { + nextKey = keyArgs.Key == keys[nextKey] ? nextKey + 1 : 0; + return false; + } + } + } + + public class MultiKeyGestureExtension : MarkupExtension { + private static readonly KeyConverter keyConverter = new KeyConverter(); + private static readonly ModifierKeysConverter modifierConverter = new ModifierKeysConverter(); + private readonly ModifierKeys mods; + private readonly Key[] keys; + + public MultiKeyGestureExtension(string first, string second) : this(new[] { first, second }) { } + public MultiKeyGestureExtension(params string[] textPieces) { + var text = ", ".Join(textPieces); + if (text.Contains("+")) { + var parts = text.Split('+'); + text = parts[1]; + mods = (ModifierKeys)modifierConverter.ConvertFromString(parts[0]); + } else { + mods = ModifierKeys.None; + } + + keys = text.Split(',').Select(k => (Key)keyConverter.ConvertFromString(k.Trim())).ToArray(); + } + + public override object ProvideValue(IServiceProvider serviceProvider) { + return new MultiKeyGesture(mods, keys); + } + } +} diff --git a/src/HexManiac.WPF/Windows/MainWindow.xaml b/src/HexManiac.WPF/Windows/MainWindow.xaml index 3fd9a158..87ad3daa 100644 --- a/src/HexManiac.WPF/Windows/MainWindow.xaml +++ b/src/HexManiac.WPF/Windows/MainWindow.xaml @@ -12,6 +12,11 @@ + + + + + @@ -29,7 +34,7 @@ - + @@ -112,6 +117,12 @@ + + + + + + @@ -141,7 +152,7 @@ - +