mirror of
https://github.com/microsoft/PowerToys.git
synced 2026-04-06 03:07:04 +02:00
CmdPal: Refactoring ContextMenu adding separators, IsCritical styling, and right-click context menus for list items (#40189)
Refactored ContextMenu into it's own control to allow displaying in CommandBar and in response to right click on list items. - Adds "critical" styling to context menu items flagged as `IsCritical`. This will use the theme to style with correct color. - Added `SeparatorContextItem` and modified `MoreCommands` to allow for both `CommandContextItem`s and `SeparatorContextItem`s. - Right clicking a list item with a context menu will open the context menu at the position of the click and position the filter box at the top of the context menu.   This PR covers: - closes #38308 - closes #39211 - closes #38307 - closes #38261
This commit is contained in:
@@ -0,0 +1,231 @@
|
||||
// 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 CommunityToolkit.Mvvm.ComponentModel;
|
||||
using CommunityToolkit.Mvvm.Messaging;
|
||||
using CommunityToolkit.WinUI;
|
||||
using Microsoft.CmdPal.Ext.System;
|
||||
using Microsoft.CmdPal.UI.ViewModels;
|
||||
using Microsoft.CmdPal.UI.ViewModels.Messages;
|
||||
using Microsoft.CmdPal.UI.Views;
|
||||
using Microsoft.UI.Input;
|
||||
using Microsoft.UI.Xaml;
|
||||
using Microsoft.UI.Xaml.Controls;
|
||||
using Microsoft.UI.Xaml.Controls.Primitives;
|
||||
using Microsoft.UI.Xaml.Input;
|
||||
using Windows.System;
|
||||
using Windows.UI.Core;
|
||||
|
||||
namespace Microsoft.CmdPal.UI.Controls;
|
||||
|
||||
public sealed partial class ContextMenu : UserControl,
|
||||
IRecipient<OpenContextMenuMessage>,
|
||||
IRecipient<UpdateCommandBarMessage>,
|
||||
IRecipient<TryCommandKeybindingMessage>
|
||||
{
|
||||
public ContextMenuViewModel ViewModel { get; } = new();
|
||||
|
||||
public ContextMenu()
|
||||
{
|
||||
this.InitializeComponent();
|
||||
|
||||
// RegisterAll isn't AOT compatible
|
||||
WeakReferenceMessenger.Default.Register<OpenContextMenuMessage>(this);
|
||||
WeakReferenceMessenger.Default.Register<UpdateCommandBarMessage>(this);
|
||||
WeakReferenceMessenger.Default.Register<TryCommandKeybindingMessage>(this);
|
||||
|
||||
if (ViewModel != null)
|
||||
{
|
||||
ViewModel.PropertyChanged += ViewModel_PropertyChanged;
|
||||
}
|
||||
}
|
||||
|
||||
public void Receive(OpenContextMenuMessage message)
|
||||
{
|
||||
UpdateUiForStackChange();
|
||||
}
|
||||
|
||||
public void Receive(UpdateCommandBarMessage message)
|
||||
{
|
||||
UpdateUiForStackChange();
|
||||
}
|
||||
|
||||
public void Receive(TryCommandKeybindingMessage msg)
|
||||
{
|
||||
var result = ViewModel?.CheckKeybinding(msg.Ctrl, msg.Alt, msg.Shift, msg.Win, msg.Key);
|
||||
|
||||
if (result == ContextKeybindingResult.Hide)
|
||||
{
|
||||
msg.Handled = true;
|
||||
WeakReferenceMessenger.Default.Send<CloseContextMenuMessage>();
|
||||
UpdateUiForStackChange();
|
||||
}
|
||||
else if (result == ContextKeybindingResult.KeepOpen)
|
||||
{
|
||||
UpdateUiForStackChange();
|
||||
msg.Handled = true;
|
||||
}
|
||||
else if (result == ContextKeybindingResult.Unhandled)
|
||||
{
|
||||
msg.Handled = false;
|
||||
}
|
||||
}
|
||||
|
||||
private void CommandsDropdown_ItemClick(object sender, ItemClickEventArgs e)
|
||||
{
|
||||
if (e.ClickedItem is CommandContextItemViewModel item)
|
||||
{
|
||||
if (InvokeCommand(item) == ContextKeybindingResult.Hide)
|
||||
{
|
||||
WeakReferenceMessenger.Default.Send<CloseContextMenuMessage>();
|
||||
}
|
||||
|
||||
UpdateUiForStackChange();
|
||||
}
|
||||
}
|
||||
|
||||
private void CommandsDropdown_KeyDown(object sender, KeyRoutedEventArgs e)
|
||||
{
|
||||
if (e.Handled)
|
||||
{
|
||||
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 result = ViewModel?.CheckKeybinding(ctrlPressed, altPressed, shiftPressed, winPressed, e.Key);
|
||||
|
||||
if (result == ContextKeybindingResult.Hide)
|
||||
{
|
||||
e.Handled = true;
|
||||
WeakReferenceMessenger.Default.Send<CloseContextMenuMessage>();
|
||||
UpdateUiForStackChange();
|
||||
}
|
||||
else if (result == ContextKeybindingResult.KeepOpen)
|
||||
{
|
||||
e.Handled = true;
|
||||
}
|
||||
else if (result == ContextKeybindingResult.Unhandled)
|
||||
{
|
||||
e.Handled = false;
|
||||
}
|
||||
}
|
||||
|
||||
private void ViewModel_PropertyChanged(object? sender, System.ComponentModel.PropertyChangedEventArgs e)
|
||||
{
|
||||
var prop = e.PropertyName;
|
||||
|
||||
if (prop == nameof(ContextMenuViewModel.FilteredItems))
|
||||
{
|
||||
UpdateUiForStackChange();
|
||||
}
|
||||
}
|
||||
|
||||
private void ContextFilterBox_TextChanged(object sender, TextChangedEventArgs e)
|
||||
{
|
||||
ViewModel?.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 (InvokeCommand(item) == ContextKeybindingResult.Hide)
|
||||
{
|
||||
WeakReferenceMessenger.Default.Send<CloseContextMenuMessage>();
|
||||
}
|
||||
|
||||
UpdateUiForStackChange();
|
||||
|
||||
e.Handled = true;
|
||||
}
|
||||
}
|
||||
else if (e.Key == VirtualKey.Escape ||
|
||||
(e.Key == VirtualKey.Left && altPressed))
|
||||
{
|
||||
if (ViewModel.CanPopContextStack())
|
||||
{
|
||||
ViewModel.PopContextStack();
|
||||
UpdateUiForStackChange();
|
||||
}
|
||||
else
|
||||
{
|
||||
WeakReferenceMessenger.Default.Send<CloseContextMenuMessage>();
|
||||
WeakReferenceMessenger.Default.Send<FocusSearchBoxMessage>();
|
||||
UpdateUiForStackChange();
|
||||
}
|
||||
|
||||
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?.SetSearchText(string.Empty);
|
||||
CommandsDropdown.SelectedIndex = 0;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Manually focuses our search box. This needs to be called after we're actually
|
||||
/// In the UI tree - if we're in a Flyout, that's not until Opened()
|
||||
/// </summary>
|
||||
internal void FocusSearchBox()
|
||||
{
|
||||
ContextFilterBox.Focus(FocusState.Programmatic);
|
||||
}
|
||||
|
||||
private ContextKeybindingResult InvokeCommand(CommandItemViewModel command) => ViewModel.InvokeCommand(command);
|
||||
}
|
||||
Reference in New Issue
Block a user