CmdPal: Adds a back button to Settings window (#44013)

## Summary of the Pull Request

This PR introduces proper navigation support in the Settings window.
- Enables the Go back button in the title bar.
- Adds support for Alt + Left Arrow, mouse X1 button, and the Go back
button.
- Fixes breadcrumb updates and localization issues to prevent them from
breaking during navigation.

<!-- Please review the items on the PR checklist before submitting-->
## PR Checklist

- [x] Closes: #44011
<!-- - [ ] Closes: #yyy (add separate lines for additional resolved
issues) -->
- [ ] **Communication:** I've discussed this with core contributors
already. If the work hasn't been agreed, this work might be rejected
- [ ] **Tests:** Added/updated and all pass
- [ ] **Localization:** All end-user-facing strings can be localized
- [ ] **Dev docs:** Added/updated
- [ ] **New binaries:** Added on the required places
- [ ] [JSON for
signing](https://github.com/microsoft/PowerToys/blob/main/.pipelines/ESRPSigning_core.json)
for new binaries
- [ ] [WXS for
installer](https://github.com/microsoft/PowerToys/blob/main/installer/PowerToysSetup/Product.wxs)
for new binaries and localization folder
- [ ] [YML for CI
pipeline](https://github.com/microsoft/PowerToys/blob/main/.pipelines/ci/templates/build-powertoys-steps.yml)
for new test projects
- [ ] [YML for signed
pipeline](https://github.com/microsoft/PowerToys/blob/main/.pipelines/release.yml)
- [ ] **Documentation updated:** If checked, please file a pull request
on [our docs
repo](https://github.com/MicrosoftDocs/windows-uwp/tree/docs/hub/powertoys)
and link it here: #xxx

<!-- Provide a more detailed description of the PR, other things fixed,
or any additional comments/features here -->
## Detailed Description of the Pull Request / Additional comments

<!-- Describe how you validated the behavior. Add automated tests
wherever possible, but list manual validation steps taken as well -->
## Validation Steps Performed
This commit is contained in:
Jiří Polášek
2025-12-02 03:25:38 +01:00
committed by GitHub
parent 32c13cead4
commit bece9c9217
3 changed files with 127 additions and 10 deletions

View File

@@ -20,13 +20,15 @@
<winuiex:WindowEx.SystemBackdrop>
<MicaBackdrop />
</winuiex:WindowEx.SystemBackdrop>
<Grid>
<Grid x:Name="RootElement">
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<TitleBar
x:Name="AppTitleBar"
BackRequested="TitleBar_BackRequested"
IsBackButtonVisible="{x:Bind NavFrame.CanGoBack, Mode=OneWay}"
IsTabStop="False"
PaneToggleRequested="AppTitleBar_PaneToggleRequested">
<!-- This is a workaround for https://github.com/microsoft/microsoft-ui-xaml/issues/10374, once fixed we should just be using IconSource -->
@@ -56,10 +58,12 @@
</NavigationView.Resources>
<NavigationView.MenuItems>
<NavigationViewItem
x:Name="GeneralPageNavItem"
x:Uid="Settings_GeneralPage_NavigationViewItem_General"
Icon="{ui:FontIcon Glyph=&#xE80F;}"
Tag="General" />
<NavigationViewItem
x:Name="ExtensionPageNavItem"
x:Uid="Settings_GeneralPage_NavigationViewItem_Extensions"
Icon="{ui:FontIcon Glyph=&#xEA86;}"
Tag="Extensions" />
@@ -90,7 +94,10 @@
</BreadcrumbBar.Resources>
</BreadcrumbBar>
</Grid>
<Frame x:Name="NavFrame" Grid.Row="1" />
<Frame
x:Name="NavFrame"
Grid.Row="1"
Navigated="NavFrame_OnNavigated" />
</Grid>
</NavigationView>
</Grid>

View File

@@ -4,14 +4,19 @@
using System.Collections.ObjectModel;
using CommunityToolkit.Mvvm.Messaging;
using ManagedCommon;
using Microsoft.CmdPal.UI.Helpers;
using Microsoft.CmdPal.UI.Messages;
using Microsoft.CmdPal.UI.ViewModels;
using Microsoft.CmdPal.UI.ViewModels.Messages;
using Microsoft.UI.Input;
using Microsoft.UI.Windowing;
using Microsoft.UI.Xaml;
using Microsoft.UI.Xaml.Automation.Peers;
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;
@@ -19,9 +24,12 @@ using TitleBar = Microsoft.UI.Xaml.Controls.TitleBar;
namespace Microsoft.CmdPal.UI.Settings;
public sealed partial class SettingsWindow : WindowEx,
IDisposable,
IRecipient<NavigateToExtensionSettingsMessage>,
IRecipient<QuitMessage>
{
private readonly LocalKeyboardListener _localKeyboardListener;
public ObservableCollection<Crumb> BreadCrumbs { get; } = [];
// Gets or sets optional action invoked after NavigationView is loaded.
@@ -40,6 +48,17 @@ public sealed partial class SettingsWindow : WindowEx,
WeakReferenceMessenger.Default.Register<NavigateToExtensionSettingsMessage>(this);
WeakReferenceMessenger.Default.Register<QuitMessage>(this);
_localKeyboardListener = new LocalKeyboardListener();
_localKeyboardListener.KeyPressed += LocalKeyboardListener_OnKeyPressed;
_localKeyboardListener.Start();
Closed += SettingsWindow_Closed;
RootElement.AddHandler(UIElement.PointerPressedEvent, new PointerEventHandler(RootElement_OnPointerPressed), true);
}
private void SettingsWindow_Closed(object sender, WindowEventArgs args)
{
Dispose();
}
// Handles NavigationView loaded event.
@@ -85,10 +104,9 @@ public sealed partial class SettingsWindow : WindowEx,
"Extensions" => typeof(ExtensionsPage),
_ => null,
};
if (pageType is not null)
{
BreadCrumbs.Clear();
BreadCrumbs.Add(new(page, page));
NavFrame.Navigate(pageType);
}
}
@@ -96,7 +114,6 @@ public sealed partial class SettingsWindow : WindowEx,
private void Navigate(ProviderSettingsViewModel extension)
{
NavFrame.Navigate(typeof(ExtensionPage), extension);
BreadCrumbs.Add(new(extension.DisplayName, string.Empty));
}
private void PositionCentered()
@@ -127,9 +144,9 @@ public sealed partial class SettingsWindow : WindowEx,
}
}
private void Window_Activated(object sender, WindowActivatedEventArgs args)
private void Window_Activated(object sender, Microsoft.UI.Xaml.WindowActivatedEventArgs args)
{
WeakReferenceMessenger.Default.Send<WindowActivatedEventArgs>(args);
WeakReferenceMessenger.Default.Send<Microsoft.UI.Xaml.WindowActivatedEventArgs>(args);
}
private void Window_Closed(object sender, WindowEventArgs args)
@@ -141,7 +158,7 @@ public sealed partial class SettingsWindow : WindowEx,
private void NavView_DisplayModeChanged(NavigationView sender, NavigationViewDisplayModeChangedEventArgs args)
{
if (args.DisplayMode == NavigationViewDisplayMode.Compact || args.DisplayMode == NavigationViewDisplayMode.Minimal)
if (args.DisplayMode is NavigationViewDisplayMode.Compact or NavigationViewDisplayMode.Minimal)
{
AppTitleBar.IsPaneToggleButtonVisible = true;
WorkAroundIcon.Margin = new Thickness(8, 0, 16, 0); // Required for workaround, see XAML comment
@@ -149,7 +166,7 @@ public sealed partial class SettingsWindow : WindowEx,
else
{
AppTitleBar.IsPaneToggleButtonVisible = false;
WorkAroundIcon.Margin = new Thickness(16, 0, 0, 0); // Required for workaround, see XAML comment
WorkAroundIcon.Margin = new Thickness(16, 0, 8, 0); // Required for workaround, see XAML comment
}
}
@@ -163,6 +180,93 @@ public sealed partial class SettingsWindow : WindowEx,
{
NavView.IsPaneOpen = !NavView.IsPaneOpen;
}
private void TryGoBack()
{
if (NavFrame.CanGoBack)
{
NavFrame.GoBack();
}
}
private void TitleBar_BackRequested(TitleBar sender, object args)
{
TryGoBack();
}
private void LocalKeyboardListener_OnKeyPressed(object? sender, LocalKeyboardListenerKeyPressedEventArgs e)
{
switch (e.Key)
{
case VirtualKey.GoBack:
case VirtualKey.XButton1:
TryGoBack();
break;
case VirtualKey.Left:
var altPressed = InputKeyboardSource.GetKeyStateForCurrentThread(VirtualKey.Menu).HasFlag(CoreVirtualKeyStates.Down);
if (altPressed)
{
TryGoBack();
}
break;
}
}
private void RootElement_OnPointerPressed(object sender, PointerRoutedEventArgs e)
{
try
{
if (e.Pointer.PointerDeviceType == PointerDeviceType.Mouse)
{
var ptrPt = e.GetCurrentPoint(RootElement);
if (ptrPt.Properties.IsXButton1Pressed)
{
TryGoBack();
}
}
}
catch (Exception ex)
{
Logger.LogError("Error handling mouse button press event", ex);
}
}
public void Dispose()
{
_localKeyboardListener?.Dispose();
}
private void NavFrame_OnNavigated(object sender, NavigationEventArgs e)
{
BreadCrumbs.Clear();
if (e.SourcePageType == typeof(GeneralPage))
{
NavView.SelectedItem = GeneralPageNavItem;
var pageType = RS_.GetString("Settings_PageTitles_GeneralPage");
BreadCrumbs.Add(new(pageType, pageType));
}
else if (e.SourcePageType == typeof(ExtensionsPage))
{
NavView.SelectedItem = ExtensionPageNavItem;
var pageType = RS_.GetString("Settings_PageTitles_ExtensionsPage");
BreadCrumbs.Add(new(pageType, pageType));
}
else if (e.SourcePageType == typeof(ExtensionPage) && e.Parameter is ProviderSettingsViewModel vm)
{
NavView.SelectedItem = ExtensionPageNavItem;
var extensionsPageType = RS_.GetString("Settings_PageTitles_ExtensionsPage");
BreadCrumbs.Add(new(extensionsPageType, extensionsPageType));
BreadCrumbs.Add(new(vm.DisplayName, vm));
}
else
{
BreadCrumbs.Add(new($"[{e.SourcePageType?.Name}]", string.Empty));
Logger.LogError($"Unknown breadcrumb for page type '{e.SourcePageType}'");
}
}
}
public readonly struct Crumb

View File

@@ -550,4 +550,10 @@ Right-click to remove the key combination, thereby deactivating the shortcut.</v
<data name="Settings_GeneralPage_AutoGoHome_SettingsCard.Description" xml:space="preserve">
<value>Automatically returns to home page after a period of inactivity when Command Palette is closed</value>
</data>
<data name="Settings_PageTitles_GeneralPage" xml:space="preserve">
<value>General</value>
</data>
<data name="Settings_PageTitles_ExtensionsPage" xml:space="preserve">
<value>Extensions</value>
</data>
</root>