diff --git a/src/modules/cmdpal/Microsoft.CmdPal.UI/Controls/ContextMenu.xaml.cs b/src/modules/cmdpal/Microsoft.CmdPal.UI/Controls/ContextMenu.xaml.cs
index 29839eb514..817734f134 100644
--- a/src/modules/cmdpal/Microsoft.CmdPal.UI/Controls/ContextMenu.xaml.cs
+++ b/src/modules/cmdpal/Microsoft.CmdPal.UI/Controls/ContextMenu.xaml.cs
@@ -5,16 +5,15 @@
using CommunityToolkit.Mvvm.Messaging;
using CommunityToolkit.WinUI;
using Microsoft.CmdPal.Common.Text;
+using Microsoft.CmdPal.UI.Helpers;
using Microsoft.CmdPal.UI.Messages;
using Microsoft.CmdPal.UI.ViewModels;
using Microsoft.CmdPal.UI.ViewModels.Messages;
using Microsoft.Extensions.DependencyInjection;
-using Microsoft.UI.Input;
using Microsoft.UI.Xaml;
using Microsoft.UI.Xaml.Controls;
using Microsoft.UI.Xaml.Input;
using Windows.System;
-using Windows.UI.Core;
namespace Microsoft.CmdPal.UI.Controls;
@@ -101,13 +100,9 @@ public sealed partial class ContextMenu : UserControl,
return;
}
- var ctrlPressed = InputKeyboardSource.GetKeyStateForCurrentThread(VirtualKey.Control).HasFlag(CoreVirtualKeyStates.Down);
- var altPressed = InputKeyboardSource.GetKeyStateForCurrentThread(VirtualKey.Menu).HasFlag(CoreVirtualKeyStates.Down);
- var shiftPressed = InputKeyboardSource.GetKeyStateForCurrentThread(VirtualKey.Shift).HasFlag(CoreVirtualKeyStates.Down);
- var winPressed = InputKeyboardSource.GetKeyStateForCurrentThread(VirtualKey.LeftWindows).HasFlag(CoreVirtualKeyStates.Down) ||
- InputKeyboardSource.GetKeyStateForCurrentThread(VirtualKey.RightWindows).HasFlag(CoreVirtualKeyStates.Down);
+ var mods = KeyModifiers.GetCurrent();
- var result = ViewModel?.CheckKeybinding(ctrlPressed, altPressed, shiftPressed, winPressed, e.Key);
+ var result = ViewModel?.CheckKeybinding(mods.Ctrl, mods.Alt, mods.Shift, mods.Win, e.Key);
if (result == ContextKeybindingResult.Hide)
{
@@ -165,11 +160,7 @@ public sealed partial class ContextMenu : UserControl,
private void ContextFilterBox_KeyDown(object sender, KeyRoutedEventArgs e)
{
- var ctrlPressed = InputKeyboardSource.GetKeyStateForCurrentThread(VirtualKey.Control).HasFlag(CoreVirtualKeyStates.Down);
- var altPressed = InputKeyboardSource.GetKeyStateForCurrentThread(VirtualKey.Menu).HasFlag(CoreVirtualKeyStates.Down);
- var shiftPressed = InputKeyboardSource.GetKeyStateForCurrentThread(VirtualKey.Shift).HasFlag(CoreVirtualKeyStates.Down);
- var winPressed = InputKeyboardSource.GetKeyStateForCurrentThread(VirtualKey.LeftWindows).HasFlag(CoreVirtualKeyStates.Down) ||
- InputKeyboardSource.GetKeyStateForCurrentThread(VirtualKey.RightWindows).HasFlag(CoreVirtualKeyStates.Down);
+ var modifiers = KeyModifiers.GetCurrent();
if (e.Key == VirtualKey.Enter)
{
@@ -186,7 +177,7 @@ public sealed partial class ContextMenu : UserControl,
}
}
else if (e.Key == VirtualKey.Escape ||
- (e.Key == VirtualKey.Left && altPressed))
+ (e.Key == VirtualKey.Left && modifiers.Alt))
{
if (ViewModel.CanPopContextStack())
{
diff --git a/src/modules/cmdpal/Microsoft.CmdPal.UI/Controls/FiltersDropDown.xaml b/src/modules/cmdpal/Microsoft.CmdPal.UI/Controls/FiltersDropDown.xaml
index be8579f082..fb9f0c6cd9 100644
--- a/src/modules/cmdpal/Microsoft.CmdPal.UI/Controls/FiltersDropDown.xaml
+++ b/src/modules/cmdpal/Microsoft.CmdPal.UI/Controls/FiltersDropDown.xaml
@@ -22,20 +22,9 @@
Default="{StaticResource FilterItemViewModelTemplate}"
Separator="{StaticResource SeparatorViewModelTemplate}" />
-
-
-
+
@@ -58,34 +47,100 @@
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/modules/cmdpal/Microsoft.CmdPal.UI/Controls/FiltersDropDown.xaml.cs b/src/modules/cmdpal/Microsoft.CmdPal.UI/Controls/FiltersDropDown.xaml.cs
index 78566b5273..9ec6dd9ddb 100644
--- a/src/modules/cmdpal/Microsoft.CmdPal.UI/Controls/FiltersDropDown.xaml.cs
+++ b/src/modules/cmdpal/Microsoft.CmdPal.UI/Controls/FiltersDropDown.xaml.cs
@@ -2,11 +2,17 @@
// 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 CommunityToolkit.Mvvm.Messaging;
+using Microsoft.CmdPal.UI.Helpers;
using Microsoft.CmdPal.UI.ViewModels;
+using Microsoft.CmdPal.UI.ViewModels.Messages;
using Microsoft.CmdPal.UI.Views;
using Microsoft.UI.Xaml;
using Microsoft.UI.Xaml.Controls;
using Microsoft.UI.Xaml.Input;
+using Windows.Foundation;
using Windows.System;
namespace Microsoft.CmdPal.UI.Controls;
@@ -14,6 +20,11 @@ namespace Microsoft.CmdPal.UI.Controls;
public sealed partial class FiltersDropDown : UserControl,
ICurrentPageAware
{
+ private bool _isDropDownOpen;
+ private string? _pendingSearchText;
+ private IFilterItemViewModel[] _allItems = [];
+ private FilterItemViewModel? _lastSelectedFilter;
+
public PageViewModel? CurrentPageViewModel
{
get => (PageViewModel?)GetValue(CurrentPageViewModelProperty);
@@ -62,11 +73,146 @@ public sealed partial class FiltersDropDown : UserControl,
}
public static readonly DependencyProperty ViewModelProperty =
- DependencyProperty.Register(nameof(ViewModel), typeof(FiltersViewModel), typeof(FiltersDropDown), new PropertyMetadata(null));
+ DependencyProperty.Register(nameof(ViewModel), typeof(FiltersViewModel), typeof(FiltersDropDown), new PropertyMetadata(null, OnViewModelChanged));
+
+ ///
+ /// Gets a value indicating whether the dropdown is currently open or the button has keyboard focus.
+ ///
+ public bool IsActive => _isDropDownOpen ||
+ FilterDropDownButton.FocusState != FocusState.Unfocused;
+
+ ///
+ /// Gets a value indicating whether the filter control is visible (has filters to show).
+ ///
+ public bool IsFilterVisible => ViewModel?.ShouldShowFilters ?? false;
+
+ private static readonly string _defaultFilterText = ResourceLoaderInstance.GetString("FiltersDropDown_DefaultText");
public FiltersDropDown()
{
this.InitializeComponent();
+ SelectedFilterText.Text = _defaultFilterText;
+ FilterDropDownButton.AddHandler(
+ CharacterReceivedEvent,
+ new TypedEventHandler(FilterDropDownButton_CharacterReceived),
+ true);
+ }
+
+ private static void OnViewModelChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
+ {
+ if (d is not FiltersDropDown @this)
+ {
+ return;
+ }
+
+ if (e.OldValue is FiltersViewModel oldVm)
+ {
+ oldVm.PropertyChanged -= @this.ViewModel_PropertyChanged;
+ }
+
+ if (e.NewValue is FiltersViewModel newVm)
+ {
+ newVm.PropertyChanged += @this.ViewModel_PropertyChanged;
+ }
+
+ @this.OnFiltersChanged();
+ }
+
+ private void ViewModel_PropertyChanged(object? sender, System.ComponentModel.PropertyChangedEventArgs e)
+ {
+ if (e.PropertyName is nameof(FiltersViewModel.Filters)
+ or nameof(FiltersViewModel.CurrentFilter)
+ or nameof(FiltersViewModel.ShouldShowFilters))
+ {
+ OnFiltersChanged();
+ }
+ }
+
+ private void OnFiltersChanged()
+ {
+ _allItems = ViewModel?.Filters ?? [];
+ UpdateFilteredList();
+ UpdateSelectedFilterDisplay();
+ }
+
+ private void UpdateSelectedFilterDisplay()
+ {
+ if (ViewModel?.CurrentFilter is FilterItemViewModel filter)
+ {
+ SelectedFilterText.Text = filter.Name;
+ SelectedFilterIcon.SourceKey = filter.Icon;
+ SelectedFilterIcon.Visibility = Visibility.Visible;
+ }
+ else
+ {
+ SelectedFilterText.Text = _defaultFilterText;
+ SelectedFilterIcon.SourceKey = null;
+ SelectedFilterIcon.Visibility = Visibility.Collapsed;
+ }
+ }
+
+ private void UpdateFilteredList()
+ {
+ if (FilterListView == null)
+ {
+ return;
+ }
+
+ var searchText = FilterSearchBox?.Text?.Trim() ?? string.Empty;
+
+ IFilterItemViewModel[] filtered;
+ if (string.IsNullOrEmpty(searchText))
+ {
+ filtered = _allItems;
+ }
+ else
+ {
+ var list = new List();
+ foreach (var item in _allItems)
+ {
+ if (item is FilterItemViewModel filterItem &&
+ filterItem.Name.IndexOf(searchText, StringComparison.OrdinalIgnoreCase) > -1)
+ {
+ list.Add(item);
+ }
+ }
+
+ filtered = list.ToArray();
+ }
+
+ FilterListView.ItemsSource = filtered;
+
+ var hasResults = filtered.Length > 0;
+ FilterListView.Visibility = hasResults ? Visibility.Visible : Visibility.Collapsed;
+ NoResultsText.Visibility = hasResults ? Visibility.Collapsed : Visibility.Visible;
+
+ // Restore selection to current filter if present
+ if (_lastSelectedFilter != null && Array.IndexOf(filtered, _lastSelectedFilter) >= 0)
+ {
+ FilterListView.SelectedItem = _lastSelectedFilter;
+ }
+ else if (ViewModel?.CurrentFilter != null && Array.IndexOf(filtered, ViewModel.CurrentFilter) >= 0)
+ {
+ FilterListView.SelectedItem = ViewModel.CurrentFilter;
+ }
+ else if (hasResults)
+ {
+ // Select the first non-separator item
+ IFilterItemViewModel? first = null;
+ foreach (var item in filtered)
+ {
+ if (item is not SeparatorViewModel)
+ {
+ first = item;
+ break;
+ }
+ }
+
+ if (first != null)
+ {
+ FilterListView.SelectedItem = first;
+ }
+ }
}
// Used to handle the case when a ListPage's `Filters` may have changed
@@ -83,55 +229,239 @@ public sealed partial class FiltersDropDown : UserControl,
}
}
- private void FiltersComboBox_SelectionChanged(object sender, SelectionChangedEventArgs e)
+ private void FilterDropDownButton_CharacterReceived(UIElement sender, CharacterReceivedRoutedEventArgs args)
{
- if (CurrentPageViewModel is ListViewModel listViewModel &&
- FiltersComboBox.SelectedItem is FilterItemViewModel filterItem)
+ // Redirect printable (non-space) characters to open flyout and type into search
+ if (!char.IsControl(args.Character) && args.Character != ' ')
{
- listViewModel.UpdateCurrentFilter(filterItem.Id);
+ OpenFlyoutAndType(args.Character.ToString());
+ args.Handled = true;
}
}
- private void FiltersComboBox_PreviewKeyDown(object sender, KeyRoutedEventArgs e)
+ private void FilterDropDownButton_PreviewKeyDown(object sender, KeyRoutedEventArgs e)
{
- if (e.Key == VirtualKey.Up)
- {
- NavigateUp();
+ var modifiers = KeyModifiers.GetCurrent();
- e.Handled = true;
- }
- else if (e.Key == VirtualKey.Down)
+ switch (e.Key)
{
- NavigateDown();
+ case VirtualKey.Down when modifiers.OnlyAlt:
+ goto case VirtualKey.F4;
- e.Handled = true;
+ case VirtualKey.Down or VirtualKey.Up:
+ {
+ if (!_isDropDownOpen)
+ {
+ FilterFlyout.ShowAt(FilterDropDownButton);
+ }
+
+ if (e.Key == VirtualKey.Down)
+ {
+ NavigateDown();
+ }
+ else
+ {
+ NavigateUp();
+ }
+
+ e.Handled = true;
+ break;
+ }
+
+ case VirtualKey.F4:
+ {
+ if (!_isDropDownOpen)
+ {
+ FilterFlyout.ShowAt(FilterDropDownButton);
+ }
+
+ e.Handled = true;
+ break;
+ }
}
}
+ private void FilterSearchBox_PreviewKeyDown(object sender, KeyRoutedEventArgs e)
+ {
+ var modifiers = KeyModifiers.GetCurrent();
+
+ switch (e.Key)
+ {
+ case VirtualKey.Down:
+ NavigateDown();
+ e.Handled = true;
+ break;
+ case VirtualKey.Up:
+ NavigateUp();
+ e.Handled = true;
+ break;
+ case VirtualKey.Enter:
+ SelectCurrentAndClose();
+ e.Handled = true;
+ break;
+ case VirtualKey.Escape:
+ if (!string.IsNullOrEmpty(FilterSearchBox.Text))
+ {
+ FilterSearchBox.Text = string.Empty;
+ }
+ else
+ {
+ CloseDropDownAndFocusSearch();
+ }
+
+ e.Handled = true;
+ break;
+ case VirtualKey.F when modifiers.Alt:
+ CloseDropDownAndFocusSearch();
+ e.Handled = true;
+ break;
+ }
+ }
+
+ private void FilterSearchBox_TextChanged(object sender, TextChangedEventArgs e) =>
+ UpdateFilteredList();
+
+ private void FilterListView_ItemClick(object sender, ItemClickEventArgs e)
+ {
+ if (e.ClickedItem is FilterItemViewModel filterItem)
+ {
+ SelectFilter(filterItem);
+ CloseDropDownAndFocusSearch();
+ }
+ }
+
+ private void FilterFlyout_Opened(object sender, object e)
+ {
+ _isDropDownOpen = true;
+
+ FilterSearchBox.Text = _pendingSearchText ?? string.Empty;
+ FilterSearchBox.SelectionStart = FilterSearchBox.Text.Length;
+ _pendingSearchText = null;
+
+ UpdateFilteredList();
+ FilterSearchBox.Focus(FocusState.Programmatic);
+ }
+
+ private void FilterFlyout_Closed(object sender, object e)
+ {
+ _isDropDownOpen = false;
+ _pendingSearchText = null;
+ FilterSearchBox.Text = string.Empty;
+ }
+
+ private void OpenFlyoutAndType(string text)
+ {
+ _pendingSearchText = (_pendingSearchText ?? string.Empty) + text;
+ if (!_isDropDownOpen)
+ {
+ FilterFlyout.ShowAt(FilterDropDownButton);
+ }
+ else
+ {
+ FilterSearchBox.Text = _pendingSearchText;
+ FilterSearchBox.SelectionStart = FilterSearchBox.Text.Length;
+ FilterSearchBox.Focus(FocusState.Programmatic);
+ _pendingSearchText = null;
+ }
+ }
+
+ ///
+ /// Opens the filter dropdown flyout.
+ ///
+ public void OpenDropDown()
+ {
+ if (!_isDropDownOpen)
+ {
+ FilterFlyout.ShowAt(FilterDropDownButton);
+ }
+ }
+
+ ///
+ /// Closes the filter dropdown flyout and returns focus to the main search box.
+ ///
+ public void CloseDropDownAndFocusSearch()
+ {
+ if (_isDropDownOpen)
+ {
+ FilterFlyout.Hide();
+ }
+
+ WeakReferenceMessenger.Default.Send();
+ }
+
+ ///
+ /// Closes the filter dropdown flyout.
+ ///
+ public void CloseDropDown()
+ {
+ if (_isDropDownOpen)
+ {
+ FilterFlyout.Hide();
+ }
+
+ FilterDropDownButton.Focus(FocusState.Programmatic);
+ }
+
+ ///
+ /// Moves focus to this control (the dropdown button).
+ ///
+ public void FocusControl()
+ {
+ FilterDropDownButton.Focus(FocusState.Programmatic);
+ }
+
+ private void SelectCurrentAndClose()
+ {
+ if (FilterListView.SelectedItem is FilterItemViewModel filterItem)
+ {
+ SelectFilter(filterItem);
+ }
+
+ CloseDropDownAndFocusSearch();
+ }
+
+ private void SelectFilter(FilterItemViewModel filterItem)
+ {
+ _lastSelectedFilter = filterItem;
+
+ if (CurrentPageViewModel is ListViewModel listViewModel)
+ {
+ listViewModel.UpdateCurrentFilter(filterItem.Id);
+ }
+
+ // Update display immediately (UpdateCurrentFilter is async)
+ SelectedFilterText.Text = filterItem.Name;
+ SelectedFilterIcon.SourceKey = filterItem.Icon;
+ SelectedFilterIcon.Visibility = Visibility.Visible;
+ }
+
private void NavigateUp()
{
- var newIndex = FiltersComboBox.SelectedIndex;
+ if (FilterListView.ItemsSource is not IFilterItemViewModel[] items || items.Length == 0)
+ {
+ return;
+ }
- if (FiltersComboBox.SelectedIndex > 0)
+ if (!HasSelectableItem(items))
+ {
+ return;
+ }
+
+ var newIndex = FilterListView.SelectedIndex;
+
+ if (newIndex > 0)
{
newIndex--;
- while (
- newIndex >= 0 &&
- IsSeparator(FiltersComboBox.Items[newIndex]) &&
- newIndex != FiltersComboBox.SelectedIndex)
+ while (newIndex >= 0 && IsSeparator(items[newIndex]))
{
newIndex--;
}
if (newIndex < 0)
{
- newIndex = FiltersComboBox.Items.Count - 1;
-
- while (
- newIndex >= 0 &&
- IsSeparator(FiltersComboBox.Items[newIndex]) &&
- newIndex != FiltersComboBox.SelectedIndex)
+ newIndex = items.Length - 1;
+ while (newIndex >= 0 && IsSeparator(items[newIndex]))
{
newIndex--;
}
@@ -139,17 +469,35 @@ public sealed partial class FiltersDropDown : UserControl,
}
else
{
- newIndex = FiltersComboBox.Items.Count - 1;
+ newIndex = items.Length - 1;
+ while (newIndex >= 0 && IsSeparator(items[newIndex]))
+ {
+ newIndex--;
+ }
}
- FiltersComboBox.SelectedIndex = newIndex;
+ if (newIndex >= 0)
+ {
+ FilterListView.SelectedIndex = newIndex;
+ FilterListView.ScrollIntoView(FilterListView.SelectedItem);
+ }
}
private void NavigateDown()
{
- var newIndex = FiltersComboBox.SelectedIndex;
+ if (FilterListView.ItemsSource is not IFilterItemViewModel[] items || items.Length == 0)
+ {
+ return;
+ }
- if (FiltersComboBox.SelectedIndex == FiltersComboBox.Items.Count - 1)
+ if (!HasSelectableItem(items))
+ {
+ return;
+ }
+
+ var newIndex = FilterListView.SelectedIndex;
+
+ if (newIndex >= items.Length - 1)
{
newIndex = 0;
}
@@ -157,33 +505,40 @@ public sealed partial class FiltersDropDown : UserControl,
{
newIndex++;
- while (
- newIndex < FiltersComboBox.Items.Count &&
- IsSeparator(FiltersComboBox.Items[newIndex]) &&
- newIndex != FiltersComboBox.SelectedIndex)
+ while (newIndex < items.Length && IsSeparator(items[newIndex]))
{
newIndex++;
}
- if (newIndex >= FiltersComboBox.Items.Count)
+ if (newIndex >= items.Length)
{
newIndex = 0;
-
- while (
- newIndex < FiltersComboBox.Items.Count &&
- IsSeparator(FiltersComboBox.Items[newIndex]) &&
- newIndex != FiltersComboBox.SelectedIndex)
+ while (newIndex < items.Length && IsSeparator(items[newIndex]))
{
newIndex++;
}
}
}
- FiltersComboBox.SelectedIndex = newIndex;
+ if (newIndex < items.Length)
+ {
+ FilterListView.SelectedIndex = newIndex;
+ FilterListView.ScrollIntoView(FilterListView.SelectedItem);
+ }
}
- private bool IsSeparator(object item)
+ private static bool IsSeparator(object item) => item is SeparatorViewModel;
+
+ private static bool HasSelectableItem(IFilterItemViewModel[] items)
{
- return item is SeparatorViewModel;
+ foreach (var item in items)
+ {
+ if (!IsSeparator(item))
+ {
+ return true;
+ }
+ }
+
+ return false;
}
}
diff --git a/src/modules/cmdpal/Microsoft.CmdPal.UI/Controls/SearchBar.xaml.cs b/src/modules/cmdpal/Microsoft.CmdPal.UI/Controls/SearchBar.xaml.cs
index 6ad173af35..ea9d362285 100644
--- a/src/modules/cmdpal/Microsoft.CmdPal.UI/Controls/SearchBar.xaml.cs
+++ b/src/modules/cmdpal/Microsoft.CmdPal.UI/Controls/SearchBar.xaml.cs
@@ -5,17 +5,16 @@
using CommunityToolkit.Mvvm.Messaging;
using CommunityToolkit.WinUI;
using Microsoft.CmdPal.Ext.ClipboardHistory.Messages;
+using Microsoft.CmdPal.UI.Helpers;
using Microsoft.CmdPal.UI.ViewModels;
using Microsoft.CmdPal.UI.ViewModels.Commands;
using Microsoft.CmdPal.UI.ViewModels.Messages;
using Microsoft.CmdPal.UI.Views;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.UI.Dispatching;
-using Microsoft.UI.Input;
using Microsoft.UI.Xaml;
using Microsoft.UI.Xaml.Controls;
using Microsoft.UI.Xaml.Input;
-using CoreVirtualKeyStates = Windows.UI.Core.CoreVirtualKeyStates;
using VirtualKey = Windows.System.VirtualKey;
namespace Microsoft.CmdPal.UI.Controls;
@@ -125,8 +124,7 @@ public sealed partial class SearchBar : UserControl,
return;
}
- var ctrlPressed = (InputKeyboardSource.GetKeyStateForCurrentThread(VirtualKey.Control) & CoreVirtualKeyStates.Down) == CoreVirtualKeyStates.Down;
- if (ctrlPressed && e.Key == VirtualKey.I)
+ if (KeyModifiers.GetCurrent().Ctrl && e.Key == VirtualKey.I)
{
// Today you learned that Ctrl+I in a TextBox will insert a tab
// We don't want that, so we'll suppress it, this way it can be used for other purposes
diff --git a/src/modules/cmdpal/Microsoft.CmdPal.UI/Converters/FilterTemplateSelector.cs b/src/modules/cmdpal/Microsoft.CmdPal.UI/Converters/FilterTemplateSelector.cs
index cf71d3f3af..fcc3c8de58 100644
--- a/src/modules/cmdpal/Microsoft.CmdPal.UI/Converters/FilterTemplateSelector.cs
+++ b/src/modules/cmdpal/Microsoft.CmdPal.UI/Converters/FilterTemplateSelector.cs
@@ -16,21 +16,38 @@ internal sealed partial class FilterTemplateSelector : DataTemplateSelector
public DataTemplate? Separator { get; set; }
[DynamicDependency(DynamicallyAccessedMemberTypes.All, "Microsoft.UI.Xaml.Controls.ComboBoxItem", "Microsoft.WinUI")]
+ [DynamicDependency(DynamicallyAccessedMemberTypes.All, "Microsoft.UI.Xaml.Controls.ListViewItem", "Microsoft.WinUI")]
protected override DataTemplate? SelectTemplateCore(object item, DependencyObject dependencyObject)
{
DataTemplate? dataTemplate = Default;
- if (dependencyObject is ComboBoxItem comboBoxItem)
- {
- comboBoxItem.IsEnabled = true;
+ var isSeparator = item is SeparatorViewModel;
- if (item is SeparatorViewModel)
- {
- comboBoxItem.IsEnabled = false;
- comboBoxItem.AllowFocusWhenDisabled = false;
- comboBoxItem.AllowFocusOnInteraction = false;
- dataTemplate = Separator;
- }
+ switch (dependencyObject)
+ {
+ case ComboBoxItem comboBoxItem:
+ comboBoxItem.IsEnabled = !isSeparator;
+ if (isSeparator)
+ {
+ comboBoxItem.AllowFocusWhenDisabled = false;
+ comboBoxItem.AllowFocusOnInteraction = false;
+ }
+
+ break;
+ case ListViewItem listViewItem:
+ listViewItem.IsEnabled = !isSeparator;
+ if (isSeparator)
+ {
+ listViewItem.MinHeight = 0;
+ listViewItem.IsHitTestVisible = false;
+ }
+
+ break;
+ }
+
+ if (isSeparator)
+ {
+ dataTemplate = Separator;
}
return dataTemplate;
diff --git a/src/modules/cmdpal/Microsoft.CmdPal.UI/Helpers/KeyModifiers.cs b/src/modules/cmdpal/Microsoft.CmdPal.UI/Helpers/KeyModifiers.cs
new file mode 100644
index 0000000000..707ff03aa4
--- /dev/null
+++ b/src/modules/cmdpal/Microsoft.CmdPal.UI/Helpers/KeyModifiers.cs
@@ -0,0 +1,50 @@
+// 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 Microsoft.UI.Input;
+using Windows.System;
+using Windows.UI.Core;
+
+namespace Microsoft.CmdPal.UI.Helpers;
+
+///
+/// Snapshot of the current keyboard modifier state (Ctrl, Alt, Shift, Win).
+///
+internal readonly struct KeyModifiers
+{
+ public bool Ctrl { get; }
+
+ public bool Alt { get; }
+
+ public bool Shift { get; }
+
+ public bool Win { get; }
+
+ private KeyModifiers(bool ctrl, bool alt, bool shift, bool win)
+ {
+ Ctrl = ctrl;
+ Alt = alt;
+ Shift = shift;
+ Win = win;
+ }
+
+ ///
+ /// Gets a snapshot of the modifier keys currently held down.
+ ///
+ public static KeyModifiers GetCurrent()
+ {
+ var ctrl = InputKeyboardSource.GetKeyStateForCurrentThread(VirtualKey.Control).HasFlag(CoreVirtualKeyStates.Down);
+ var alt = InputKeyboardSource.GetKeyStateForCurrentThread(VirtualKey.Menu).HasFlag(CoreVirtualKeyStates.Down);
+ var shift = InputKeyboardSource.GetKeyStateForCurrentThread(VirtualKey.Shift).HasFlag(CoreVirtualKeyStates.Down);
+ var win = InputKeyboardSource.GetKeyStateForCurrentThread(VirtualKey.LeftWindows).HasFlag(CoreVirtualKeyStates.Down) ||
+ InputKeyboardSource.GetKeyStateForCurrentThread(VirtualKey.RightWindows).HasFlag(CoreVirtualKeyStates.Down);
+ return new KeyModifiers(ctrl, alt, shift, win);
+ }
+
+ public bool OnlyAlt => Alt && !Ctrl && !Shift && !Win;
+
+ public bool OnlyCtrl => Ctrl && !Alt && !Shift && !Win;
+
+ public bool None => !Ctrl && !Alt && !Shift && !Win;
+}
diff --git a/src/modules/cmdpal/Microsoft.CmdPal.UI/Pages/ShellPage.xaml.cs b/src/modules/cmdpal/Microsoft.CmdPal.UI/Pages/ShellPage.xaml.cs
index bf89f1e056..0c821dda5c 100644
--- a/src/modules/cmdpal/Microsoft.CmdPal.UI/Pages/ShellPage.xaml.cs
+++ b/src/modules/cmdpal/Microsoft.CmdPal.UI/Pages/ShellPage.xaml.cs
@@ -504,6 +504,23 @@ public sealed partial class ShellPage : Microsoft.UI.Xaml.Controls.Page,
});
}
+ private void ToggleFilterFocus()
+ {
+ if (!FiltersDropDown.IsFilterVisible)
+ {
+ return;
+ }
+
+ if (FiltersDropDown.IsActive)
+ {
+ FiltersDropDown.CloseDropDownAndFocusSearch();
+ }
+ else
+ {
+ FiltersDropDown.OpenDropDown();
+ }
+ }
+
private void BackButton_Clicked(object sender, Microsoft.UI.Xaml.RoutedEventArgs e) => WeakReferenceMessenger.Default.Send(new());
private void RootFrame_Navigated(object sender, Microsoft.UI.Xaml.Navigation.NavigationEventArgs e)
@@ -688,34 +705,32 @@ public sealed partial class ShellPage : Microsoft.UI.Xaml.Controls.Page,
private static void ShellPage_OnPreviewKeyDown(object sender, KeyRoutedEventArgs e)
{
- var ctrlPressed = InputKeyboardSource.GetKeyStateForCurrentThread(VirtualKey.Control).HasFlag(CoreVirtualKeyStates.Down);
- var altPressed = InputKeyboardSource.GetKeyStateForCurrentThread(VirtualKey.Menu).HasFlag(CoreVirtualKeyStates.Down);
- var shiftPressed = InputKeyboardSource.GetKeyStateForCurrentThread(VirtualKey.Shift).HasFlag(CoreVirtualKeyStates.Down);
- var winPressed = InputKeyboardSource.GetKeyStateForCurrentThread(VirtualKey.LeftWindows).HasFlag(CoreVirtualKeyStates.Down) ||
- InputKeyboardSource.GetKeyStateForCurrentThread(VirtualKey.RightWindows).HasFlag(CoreVirtualKeyStates.Down);
+ var modifiers = KeyModifiers.GetCurrent();
- var onlyAlt = altPressed && !ctrlPressed && !shiftPressed && !winPressed;
- var onlyCtrl = !altPressed && ctrlPressed && !shiftPressed && !winPressed;
switch (e.Key)
{
- case VirtualKey.Left when onlyAlt: // Alt+Left arrow
+ case VirtualKey.Left when modifiers.OnlyAlt: // Alt+Left arrow
WeakReferenceMessenger.Default.Send(new());
e.Handled = true;
break;
- case VirtualKey.Home when onlyAlt: // Alt+Home
+ case VirtualKey.Home when modifiers.OnlyAlt: // Alt+Home
WeakReferenceMessenger.Default.Send(new(WithAnimation: false));
e.Handled = true;
break;
- case (VirtualKey)188 when onlyCtrl: // Ctrl+,
+ case (VirtualKey)188 when modifiers.OnlyCtrl: // Ctrl+,
WeakReferenceMessenger.Default.Send(new());
e.Handled = true;
break;
+ case VirtualKey.F when modifiers.OnlyAlt: // Alt+F: toggle filter focus
+ ((ShellPage)sender).ToggleFilterFocus();
+ e.Handled = true;
+ break;
default:
{
// The CommandBar is responsible for handling all the item keybindings,
// since the bound context item may need to then show another
// context menu
- TryCommandKeybindingMessage msg = new(ctrlPressed, altPressed, shiftPressed, winPressed, e.Key);
+ TryCommandKeybindingMessage msg = new(modifiers.Ctrl, modifiers.Alt, modifiers.Shift, modifiers.Win, e.Key);
WeakReferenceMessenger.Default.Send(msg);
e.Handled = msg.Handled;
break;
@@ -725,8 +740,8 @@ public sealed partial class ShellPage : Microsoft.UI.Xaml.Controls.Page,
private static void ShellPage_OnKeyDown(object sender, KeyRoutedEventArgs e)
{
- var ctrlPressed = InputKeyboardSource.GetKeyStateForCurrentThread(VirtualKey.Control).HasFlag(CoreVirtualKeyStates.Down);
- if (ctrlPressed && e.Key == VirtualKey.Enter)
+ var mods = KeyModifiers.GetCurrent();
+ if (mods.Ctrl && e.Key == VirtualKey.Enter)
{
// ctrl+enter
WeakReferenceMessenger.Default.Send();
@@ -737,7 +752,7 @@ public sealed partial class ShellPage : Microsoft.UI.Xaml.Controls.Page,
WeakReferenceMessenger.Default.Send();
e.Handled = true;
}
- else if (ctrlPressed && e.Key == VirtualKey.K)
+ else if (mods.Ctrl && e.Key == VirtualKey.K)
{
// ctrl+k
WeakReferenceMessenger.Default.Send(new OpenContextMenuMessage(null, null, null, ContextMenuFilterLocation.Bottom));
diff --git a/src/modules/cmdpal/Microsoft.CmdPal.UI/Settings/SettingsWindow.xaml.cs b/src/modules/cmdpal/Microsoft.CmdPal.UI/Settings/SettingsWindow.xaml.cs
index feae8902b6..de3664641d 100644
--- a/src/modules/cmdpal/Microsoft.CmdPal.UI/Settings/SettingsWindow.xaml.cs
+++ b/src/modules/cmdpal/Microsoft.CmdPal.UI/Settings/SettingsWindow.xaml.cs
@@ -16,7 +16,6 @@ using Microsoft.UI.Xaml.Controls;
using Microsoft.UI.Xaml.Input;
using Microsoft.UI.Xaml.Navigation;
using Windows.System;
-using Windows.UI.Core;
using WinUIEx;
using RS_ = Microsoft.CmdPal.UI.Helpers.ResourceLoaderInstance;
using TitleBar = Microsoft.UI.Xaml.Controls.TitleBar;
@@ -251,8 +250,7 @@ public sealed partial class SettingsWindow : WindowEx,
break;
case VirtualKey.Left:
- var altPressed = InputKeyboardSource.GetKeyStateForCurrentThread(VirtualKey.Menu).HasFlag(CoreVirtualKeyStates.Down);
- if (altPressed)
+ if (KeyModifiers.GetCurrent().Alt)
{
TryGoBack();
}
diff --git a/src/modules/cmdpal/Microsoft.CmdPal.UI/Strings/en-us/Resources.resw b/src/modules/cmdpal/Microsoft.CmdPal.UI/Strings/en-us/Resources.resw
index 262d16a483..879504bb97 100644
--- a/src/modules/cmdpal/Microsoft.CmdPal.UI/Strings/en-us/Resources.resw
+++ b/src/modules/cmdpal/Microsoft.CmdPal.UI/Strings/en-us/Resources.resw
@@ -928,4 +928,25 @@ Right-click to remove the key combination, thereby deactivating the shortcut.Unpin from dock
Command name for unpinning an item from the dock
+
+ Filters
+
+
+ Filters (Alt+F)
+
+
+ Filters
+
+
+ Search filters
+
+
+ Search filters...
+
+
+ Filter options
+
+
+ No results
+
\ No newline at end of file