// 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, IRecipient, IRecipient { public ContextMenuViewModel ViewModel { get; } = new(); public ContextMenu() { this.InitializeComponent(); // RegisterAll isn't AOT compatible WeakReferenceMessenger.Default.Register(this); WeakReferenceMessenger.Default.Register(this); WeakReferenceMessenger.Default.Register(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(); 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(); } 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(); 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(); } 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(); WeakReferenceMessenger.Default.Send(); 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; } /// /// 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() /// internal void FocusSearchBox() { ContextFilterBox.Focus(FocusState.Programmatic); } private ContextKeybindingResult InvokeCommand(CommandItemViewModel command) => ViewModel.InvokeCommand(command); }