mirror of
https://github.com/microsoft/PowerToys.git
synced 2026-04-05 10:46:33 +02:00
Add support for filterable, nested context menus (#38776)
_targets #38573_ At first I just wanted to add support for nested context menus. But then I also had to add a search box, so the focus wouldn't get weird. End result:  This gets rid of the need to have the search box and the command bar both track item keybindings - now it's just in the command bar. Closes #38299 Closes #38442
This commit is contained in:
@@ -18,9 +18,10 @@ namespace Microsoft.CmdPal.UI.Controls;
|
||||
|
||||
public sealed partial class CommandBar : UserControl,
|
||||
IRecipient<OpenContextMenuMessage>,
|
||||
IRecipient<TryCommandKeybindingMessage>,
|
||||
ICurrentPageAware
|
||||
{
|
||||
public CommandBarViewModel ViewModel { get; set; } = new();
|
||||
public CommandBarViewModel ViewModel { get; } = new();
|
||||
|
||||
public PageViewModel? CurrentPageViewModel
|
||||
{
|
||||
@@ -38,6 +39,9 @@ public sealed partial class CommandBar : UserControl,
|
||||
|
||||
// RegisterAll isn't AOT compatible
|
||||
WeakReferenceMessenger.Default.Register<OpenContextMenuMessage>(this);
|
||||
WeakReferenceMessenger.Default.Register<TryCommandKeybindingMessage>(this);
|
||||
|
||||
ViewModel.PropertyChanged += ViewModel_PropertyChanged;
|
||||
}
|
||||
|
||||
public void Receive(OpenContextMenuMessage message)
|
||||
@@ -52,8 +56,41 @@ public sealed partial class CommandBar : UserControl,
|
||||
ShowMode = FlyoutShowMode.Standard,
|
||||
};
|
||||
MoreCommandsButton.Flyout.ShowAt(MoreCommandsButton, options);
|
||||
CommandsDropdown.SelectedIndex = 0;
|
||||
CommandsDropdown.Focus(FocusState.Programmatic);
|
||||
UpdateUiForStackChange();
|
||||
}
|
||||
|
||||
public void Receive(TryCommandKeybindingMessage msg)
|
||||
{
|
||||
if (!ViewModel.ShouldShowContextMenu)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var result = ViewModel?.CheckKeybinding(msg.Ctrl, msg.Alt, msg.Shift, msg.Win, msg.Key);
|
||||
|
||||
if (result == ContextKeybindingResult.Hide)
|
||||
{
|
||||
msg.Handled = true;
|
||||
}
|
||||
else if (result == ContextKeybindingResult.KeepOpen)
|
||||
{
|
||||
if (!MoreCommandsButton.Flyout.IsOpen)
|
||||
{
|
||||
var options = new FlyoutShowOptions
|
||||
{
|
||||
ShowMode = FlyoutShowMode.Standard,
|
||||
};
|
||||
MoreCommandsButton.Flyout.ShowAt(MoreCommandsButton, options);
|
||||
}
|
||||
|
||||
UpdateUiForStackChange();
|
||||
|
||||
msg.Handled = true;
|
||||
}
|
||||
else if (result == ContextKeybindingResult.Unhandled)
|
||||
{
|
||||
msg.Handled = false;
|
||||
}
|
||||
}
|
||||
|
||||
[System.Diagnostics.CodeAnalysis.SuppressMessage("CodeQuality", "IDE0051:Remove unused private members", Justification = "VS has a tendency to delete XAML bound methods over-aggressively")]
|
||||
@@ -88,8 +125,14 @@ public sealed partial class CommandBar : UserControl,
|
||||
{
|
||||
if (e.ClickedItem is CommandContextItemViewModel item)
|
||||
{
|
||||
ViewModel?.InvokeItemCommand.Execute(item);
|
||||
MoreCommandsButton.Flyout.Hide();
|
||||
if (ViewModel?.InvokeItem(item) == ContextKeybindingResult.Hide)
|
||||
{
|
||||
MoreCommandsButton.Flyout.Hide();
|
||||
}
|
||||
else
|
||||
{
|
||||
UpdateUiForStackChange();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -106,9 +149,136 @@ public sealed partial class CommandBar : UserControl,
|
||||
var winPressed = InputKeyboardSource.GetKeyStateForCurrentThread(VirtualKey.LeftWindows).HasFlag(CoreVirtualKeyStates.Down) ||
|
||||
InputKeyboardSource.GetKeyStateForCurrentThread(VirtualKey.RightWindows).HasFlag(CoreVirtualKeyStates.Down);
|
||||
|
||||
if (ViewModel?.CheckKeybinding(ctrlPressed, altPressed, shiftPressed, winPressed, e.Key) ?? false)
|
||||
var result = ViewModel?.CheckKeybinding(ctrlPressed, altPressed, shiftPressed, winPressed, e.Key);
|
||||
|
||||
if (result == ContextKeybindingResult.Hide)
|
||||
{
|
||||
e.Handled = true;
|
||||
MoreCommandsButton.Flyout.Hide();
|
||||
WeakReferenceMessenger.Default.Send<FocusSearchBoxMessage>();
|
||||
}
|
||||
else if (result == ContextKeybindingResult.KeepOpen)
|
||||
{
|
||||
e.Handled = true;
|
||||
}
|
||||
else if (result == ContextKeybindingResult.Unhandled)
|
||||
{
|
||||
e.Handled = false;
|
||||
}
|
||||
}
|
||||
|
||||
private void Flyout_Opened(object sender, object e)
|
||||
{
|
||||
UpdateUiForStackChange();
|
||||
}
|
||||
|
||||
private void Flyout_Closing(FlyoutBase sender, FlyoutBaseClosingEventArgs args)
|
||||
{
|
||||
ViewModel?.ClearContextStack();
|
||||
WeakReferenceMessenger.Default.Send<FocusSearchBoxMessage>();
|
||||
}
|
||||
|
||||
private void ViewModel_PropertyChanged(object? sender, System.ComponentModel.PropertyChangedEventArgs e)
|
||||
{
|
||||
var prop = e.PropertyName;
|
||||
if (prop == nameof(ViewModel.ContextMenu))
|
||||
{
|
||||
UpdateUiForStackChange();
|
||||
}
|
||||
}
|
||||
|
||||
private void ContextFilterBox_TextChanged(object sender, TextChangedEventArgs e)
|
||||
{
|
||||
ViewModel.ContextMenu?.SetSearchText(ContextFilterBox.Text);
|
||||
|
||||
if (CommandsDropdown.SelectedIndex == -1)
|
||||
{
|
||||
CommandsDropdown.SelectedIndex = 0;
|
||||
}
|
||||
}
|
||||
|
||||
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);
|
||||
|
||||
if (e.Key == VirtualKey.Enter)
|
||||
{
|
||||
if (CommandsDropdown.SelectedItem is CommandContextItemViewModel item)
|
||||
{
|
||||
if (ViewModel?.InvokeItem(item) == ContextKeybindingResult.Hide)
|
||||
{
|
||||
MoreCommandsButton.Flyout.Hide();
|
||||
WeakReferenceMessenger.Default.Send<FocusSearchBoxMessage>();
|
||||
}
|
||||
else
|
||||
{
|
||||
UpdateUiForStackChange();
|
||||
}
|
||||
|
||||
e.Handled = true;
|
||||
}
|
||||
}
|
||||
else if (e.Key == VirtualKey.Escape ||
|
||||
(e.Key == VirtualKey.Left && altPressed))
|
||||
{
|
||||
if (ViewModel.CanPopContextStack())
|
||||
{
|
||||
ViewModel.PopContextStack();
|
||||
UpdateUiForStackChange();
|
||||
}
|
||||
else
|
||||
{
|
||||
MoreCommandsButton.Flyout.Hide();
|
||||
WeakReferenceMessenger.Default.Send<FocusSearchBoxMessage>();
|
||||
}
|
||||
|
||||
e.Handled = true;
|
||||
}
|
||||
|
||||
CommandsDropdown_KeyDown(sender, e);
|
||||
}
|
||||
|
||||
private void ContextFilterBox_PreviewKeyDown(object sender, KeyRoutedEventArgs e)
|
||||
{
|
||||
if (e.Key == VirtualKey.Up)
|
||||
{
|
||||
// navigate previous
|
||||
if (CommandsDropdown.SelectedIndex > 0)
|
||||
{
|
||||
CommandsDropdown.SelectedIndex--;
|
||||
}
|
||||
else
|
||||
{
|
||||
CommandsDropdown.SelectedIndex = CommandsDropdown.Items.Count - 1;
|
||||
}
|
||||
|
||||
e.Handled = true;
|
||||
}
|
||||
else if (e.Key == VirtualKey.Down)
|
||||
{
|
||||
// navigate next
|
||||
if (CommandsDropdown.SelectedIndex < CommandsDropdown.Items.Count - 1)
|
||||
{
|
||||
CommandsDropdown.SelectedIndex++;
|
||||
}
|
||||
else
|
||||
{
|
||||
CommandsDropdown.SelectedIndex = 0;
|
||||
}
|
||||
|
||||
e.Handled = true;
|
||||
}
|
||||
}
|
||||
|
||||
private void UpdateUiForStackChange()
|
||||
{
|
||||
ContextFilterBox.Text = string.Empty;
|
||||
ViewModel.ContextMenu?.SetSearchText(string.Empty);
|
||||
CommandsDropdown.SelectedIndex = 0;
|
||||
ContextFilterBox.Focus(FocusState.Programmatic);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user