diff --git a/doc/devdocs/modules/shortcut_guide.md b/doc/devdocs/modules/shortcut_guide.md index a22af52336..8cf9726cfc 100644 --- a/doc/devdocs/modules/shortcut_guide.md +++ b/doc/devdocs/modules/shortcut_guide.md @@ -25,6 +25,9 @@ The **Hold Windows key** setting is independent of the activation shortcut: - **Show taskbar indicators** is the default and always hides the indicators when the Windows key is released. - **Open Shortcut Guide** can close on Windows-key release or remain open. +- Use the title-bar search box to filter shortcuts on the selected application page +- Press Ctrl+F to focus search. Escape clears an active search before dismissing the overlay +- The hold duration accepts values from 100 through 5,000 milliseconds and defaults to 900 milliseconds. ## Build and Debug Instructions diff --git a/src/modules/ShortcutGuide/ShortcutGuide.Ui/Helpers/ShortcutSearchMatcher.cs b/src/modules/ShortcutGuide/ShortcutGuide.Ui/Helpers/ShortcutSearchMatcher.cs new file mode 100644 index 0000000000..2b39166d82 --- /dev/null +++ b/src/modules/ShortcutGuide/ShortcutGuide.Ui/Helpers/ShortcutSearchMatcher.cs @@ -0,0 +1,168 @@ +// Copyright (c) Microsoft Corporation +// The Microsoft Corporation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; +using System.Collections.Generic; +using System.Linq; +using Common.Search.FuzzSearch; +using ShortcutGuide.Models; + +namespace ShortcutGuide.Helpers +{ + public static class ShortcutSearchMatcher + { + public static bool Matches(ShortcutEntry shortcut, string? query) + { + string searchText = query?.Trim() ?? string.Empty; + if (searchText.Length == 0) + { + return true; + } + + if (MatchesText(shortcut.Name, searchText) || MatchesText(shortcut.Description, searchText)) + { + return true; + } + + foreach (var description in shortcut.Shortcut ?? []) + { + foreach (string chordLabel in GetChordSearchLabels(description)) + { + if (MatchesText(chordLabel, searchText)) + { + return true; + } + } + + foreach (string label in GetSearchLabels(description)) + { + if (MatchesText(label, searchText)) + { + return true; + } + } + } + + return false; + } + + private static IEnumerable GetChordSearchLabels(ShortcutDescription description) + { + var displayedLabels = new List(); + var semanticLabels = new List(); + + if (description.Win) + { + displayedLabels.Add("Win"); + semanticLabels.Add("Windows"); + } + + if (description.Ctrl) + { + displayedLabels.Add("Ctrl"); + semanticLabels.Add("Control"); + } + + if (description.Alt) + { + displayedLabels.Add("Alt"); + semanticLabels.Add("Alt"); + } + + if (description.Shift) + { + displayedLabels.Add("Shift"); + semanticLabels.Add("Shift"); + } + + var keyLabels = (description.Keys ?? []).Select(GetKeySearchLabel); + displayedLabels.AddRange(keyLabels); + semanticLabels.AddRange(keyLabels); + + yield return string.Join(' ', displayedLabels); + + if (!displayedLabels.SequenceEqual(semanticLabels, StringComparer.Ordinal)) + { + yield return string.Join(' ', semanticLabels); + } + } + + private static IEnumerable GetSearchLabels(ShortcutDescription description) + { + if (description.Win) + { + yield return "Win Windows"; + } + + if (description.Ctrl) + { + yield return "Ctrl Control"; + } + + if (description.Alt) + { + yield return "Alt"; + } + + if (description.Shift) + { + yield return "Shift"; + } + + foreach (string key in description.Keys ?? []) + { + yield return GetKeySearchLabel(key); + } + } + + private static string GetKeySearchLabel(string key) + { + if (int.TryParse(key, out int keyCode)) + { + return keyCode switch + { + 37 => "Left Left Arrow", + 38 => "Up Up Arrow", + 39 => "Right Right Arrow", + 40 => "Down Down Arrow", + _ => Microsoft.PowerToys.Settings.UI.Library.Utilities.Helper.GetKeyName((uint)keyCode), + }; + } + + return key switch + { + "Up" or "" => "Up Up Arrow", + "Down" or "" => "Down Down Arrow", + "Left" or "" => "Left Left Arrow", + "Right" or "" => "Right Right Arrow", + "Back" or "" => "Back Backspace", + "" => "Num", + "" => "Up Down Arrow", + "" => "Left Right Arrow", + "" => "Left Right Up Down Arrow", + "" => "Enter", + "" => "<", + "" => ">", + "" => "Esc Escape", + string value when value.StartsWith('<') && value.EndsWith('>') => value.Trim('<', '>'), + _ => key, + }; + } + + private static bool MatchesText(string? value, string searchText) + { + if (value is null) + { + return false; + } + + if (value.Contains(searchText, StringComparison.OrdinalIgnoreCase)) + { + return true; + } + + return StringMatcher.FuzzyMatch(searchText, value).IsSearchPrecisionScoreMet(); + } + } +} diff --git a/src/modules/ShortcutGuide/ShortcutGuide.Ui/ShortcutGuide.Ui.csproj b/src/modules/ShortcutGuide/ShortcutGuide.Ui/ShortcutGuide.Ui.csproj index 79709c7346..13d599a25f 100644 --- a/src/modules/ShortcutGuide/ShortcutGuide.Ui/ShortcutGuide.Ui.csproj +++ b/src/modules/ShortcutGuide/ShortcutGuide.Ui/ShortcutGuide.Ui.csproj @@ -100,6 +100,7 @@ + diff --git a/src/modules/ShortcutGuide/ShortcutGuide.Ui/ShortcutGuideXAML/App.xaml.cs b/src/modules/ShortcutGuide/ShortcutGuide.Ui/ShortcutGuideXAML/App.xaml.cs index c2c0795e7a..5ff74521a7 100644 --- a/src/modules/ShortcutGuide/ShortcutGuide.Ui/ShortcutGuideXAML/App.xaml.cs +++ b/src/modules/ShortcutGuide/ShortcutGuide.Ui/ShortcutGuideXAML/App.xaml.cs @@ -238,6 +238,7 @@ namespace ShortcutGuide await OverlayWindow.MainPaneControl.Open(); OverlayWindow.UpdateTaskbarPaneLayout(); OverlayWindow.MainPaneControl.Visibility = Visibility.Visible; + OverlayWindow.MainPaneControl.FocusSearch(); } }); } diff --git a/src/modules/ShortcutGuide/ShortcutGuide.Ui/ShortcutGuideXAML/Controls/MainPaneControl.xaml b/src/modules/ShortcutGuide/ShortcutGuide.Ui/ShortcutGuideXAML/Controls/MainPaneControl.xaml index 76e2fb49e0..3f2287d9e0 100644 --- a/src/modules/ShortcutGuide/ShortcutGuide.Ui/ShortcutGuideXAML/Controls/MainPaneControl.xaml +++ b/src/modules/ShortcutGuide/ShortcutGuide.Ui/ShortcutGuideXAML/Controls/MainPaneControl.xaml @@ -26,6 +26,7 @@ + @@ -40,10 +41,30 @@ Grid.Column="1" VerticalAlignment="Center" Style="{ThemeResource CaptionTextBlockStyle}" /> + + + + + + + +