mirror of
https://github.com/microsoft/PowerToys.git
synced 2025-12-16 11:48:06 +01:00
CmdPal: Hide search box on content pages [experiment] (#41479)
## Summary of the Pull Request CmdPal now displays the search box only on pages that derive from ListPage. On ContentPage (forms, etc.), the search box is hidden. - Moves keyboard shortcut handling from SearchBox to ShellPage so shortcuts are always handled. - Keeps the search box hidden/disabled to preserve layout metrics. - Refines focus management to prevent focus jumps during navigation. - For ContentPage page's content gains focus automatically (not just form inputs, but now markdown content as well - so you can scroll immediately, for example). - Adds accessibility (a11y) tweaks: when navigating to content pages without a visible search box, sets an explicit focus target so screen readers announce a meaningful element. Screen reader will now announce navigation to the page. - Adds a title to the main list page - "Home". https://github.com/user-attachments/assets/f60d0826-df1f-468e-8e41-0266cd27878b <!-- Please review the items on the PR checklist before submitting--> ## PR Checklist - [x] Closes: #38967 - [ ] **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:
@@ -68,6 +68,8 @@ public partial class PageViewModel : ExtensionObjectViewModel, IPageContext
|
|||||||
// `IsLoading` property as a combo of this value and `IsInitialized`
|
// `IsLoading` property as a combo of this value and `IsInitialized`
|
||||||
public bool ModelIsLoading { get; protected set; } = true;
|
public bool ModelIsLoading { get; protected set; } = true;
|
||||||
|
|
||||||
|
public bool HasSearchBox { get; protected set; }
|
||||||
|
|
||||||
public IconInfoViewModel Icon { get; protected set; }
|
public IconInfoViewModel Icon { get; protected set; }
|
||||||
|
|
||||||
public PageViewModel(IPage? model, TaskScheduler scheduler, AppExtensionHost extensionHost)
|
public PageViewModel(IPage? model, TaskScheduler scheduler, AppExtensionHost extensionHost)
|
||||||
@@ -144,12 +146,15 @@ public partial class PageViewModel : ExtensionObjectViewModel, IPageContext
|
|||||||
Icon = new(page.Icon);
|
Icon = new(page.Icon);
|
||||||
Icon.InitializeProperties();
|
Icon.InitializeProperties();
|
||||||
|
|
||||||
|
HasSearchBox = page is IListPage;
|
||||||
|
|
||||||
// Let the UI know about our initial properties too.
|
// Let the UI know about our initial properties too.
|
||||||
UpdateProperty(nameof(Name));
|
UpdateProperty(nameof(Name));
|
||||||
UpdateProperty(nameof(Title));
|
UpdateProperty(nameof(Title));
|
||||||
UpdateProperty(nameof(ModelIsLoading));
|
UpdateProperty(nameof(ModelIsLoading));
|
||||||
UpdateProperty(nameof(IsLoading));
|
UpdateProperty(nameof(IsLoading));
|
||||||
UpdateProperty(nameof(Icon));
|
UpdateProperty(nameof(Icon));
|
||||||
|
UpdateProperty(nameof(HasSearchBox));
|
||||||
|
|
||||||
page.PropChanged += Model_PropChanged;
|
page.PropChanged += Model_PropChanged;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -32,6 +32,9 @@ public partial class ShellViewModel : ObservableObject,
|
|||||||
[ObservableProperty]
|
[ObservableProperty]
|
||||||
public partial bool IsDetailsVisible { get; set; }
|
public partial bool IsDetailsVisible { get; set; }
|
||||||
|
|
||||||
|
[ObservableProperty]
|
||||||
|
public partial bool IsSearchBoxVisible { get; set; } = true;
|
||||||
|
|
||||||
private PageViewModel _currentPage;
|
private PageViewModel _currentPage;
|
||||||
|
|
||||||
public PageViewModel CurrentPage
|
public PageViewModel CurrentPage
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ using Microsoft.CmdPal.Core.Common.Helpers;
|
|||||||
using Microsoft.CmdPal.Core.ViewModels.Messages;
|
using Microsoft.CmdPal.Core.ViewModels.Messages;
|
||||||
using Microsoft.CmdPal.Ext.Apps;
|
using Microsoft.CmdPal.Ext.Apps;
|
||||||
using Microsoft.CmdPal.UI.ViewModels.Messages;
|
using Microsoft.CmdPal.UI.ViewModels.Messages;
|
||||||
|
using Microsoft.CmdPal.UI.ViewModels.Properties;
|
||||||
using Microsoft.CommandPalette.Extensions;
|
using Microsoft.CommandPalette.Extensions;
|
||||||
using Microsoft.CommandPalette.Extensions.Toolkit;
|
using Microsoft.CommandPalette.Extensions.Toolkit;
|
||||||
using Microsoft.Extensions.DependencyInjection;
|
using Microsoft.Extensions.DependencyInjection;
|
||||||
@@ -46,6 +47,7 @@ public partial class MainListPage : DynamicListPage,
|
|||||||
|
|
||||||
public MainListPage(IServiceProvider serviceProvider)
|
public MainListPage(IServiceProvider serviceProvider)
|
||||||
{
|
{
|
||||||
|
Title = Resources.builtin_home_name;
|
||||||
Icon = IconHelpers.FromRelativePath("Assets\\StoreLogo.scale-200.png");
|
Icon = IconHelpers.FromRelativePath("Assets\\StoreLogo.scale-200.png");
|
||||||
PlaceholderText = Properties.Resources.builtin_main_list_page_searchbar_placeholder;
|
PlaceholderText = Properties.Resources.builtin_main_list_page_searchbar_placeholder;
|
||||||
_serviceProvider = serviceProvider;
|
_serviceProvider = serviceProvider;
|
||||||
|
|||||||
@@ -285,6 +285,15 @@ namespace Microsoft.CmdPal.UI.ViewModels.Properties {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Looks up a localized string similar to Home.
|
||||||
|
/// </summary>
|
||||||
|
public static string builtin_home_name {
|
||||||
|
get {
|
||||||
|
return ResourceManager.GetString("builtin_home_name", resourceCulture);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Looks up a localized string similar to View log folder.
|
/// Looks up a localized string similar to View log folder.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|||||||
@@ -233,4 +233,7 @@
|
|||||||
<data name="builtin_main_list_page_searchbar_placeholder" xml:space="preserve">
|
<data name="builtin_main_list_page_searchbar_placeholder" xml:space="preserve">
|
||||||
<value>Search for apps, files and commands...</value>
|
<value>Search for apps, files and commands...</value>
|
||||||
</data>
|
</data>
|
||||||
|
<data name="builtin_home_name" xml:space="preserve">
|
||||||
|
<value>Home</value>
|
||||||
|
</data>
|
||||||
</root>
|
</root>
|
||||||
@@ -122,31 +122,11 @@ public sealed partial class SearchBar : UserControl,
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
var ctrlPressed = InputKeyboardSource.GetKeyStateForCurrentThread(VirtualKey.Control).HasFlag(CoreVirtualKeyStates.Down);
|
var ctrlPressed = (InputKeyboardSource.GetKeyStateForCurrentThread(VirtualKey.Control) & CoreVirtualKeyStates.Down) == CoreVirtualKeyStates.Down;
|
||||||
var altPressed = InputKeyboardSource.GetKeyStateForCurrentThread(VirtualKey.Menu).HasFlag(CoreVirtualKeyStates.Down);
|
if (ctrlPressed && e.Key == VirtualKey.I)
|
||||||
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 (ctrlPressed && e.Key == VirtualKey.Enter)
|
|
||||||
{
|
|
||||||
// ctrl+enter
|
|
||||||
WeakReferenceMessenger.Default.Send<ActivateSecondaryCommandMessage>();
|
|
||||||
e.Handled = true;
|
|
||||||
}
|
|
||||||
else if (e.Key == VirtualKey.Enter)
|
|
||||||
{
|
|
||||||
WeakReferenceMessenger.Default.Send<ActivateSelectedListItemMessage>();
|
|
||||||
e.Handled = true;
|
|
||||||
}
|
|
||||||
else if (ctrlPressed && e.Key == VirtualKey.K)
|
|
||||||
{
|
|
||||||
// ctrl+k
|
|
||||||
WeakReferenceMessenger.Default.Send<OpenContextMenuMessage>(new OpenContextMenuMessage(null, null, null, ContextMenuFilterLocation.Bottom));
|
|
||||||
e.Handled = true;
|
|
||||||
}
|
|
||||||
else if (ctrlPressed && e.Key == VirtualKey.I)
|
|
||||||
{
|
{
|
||||||
// Today you learned that Ctrl+I in a TextBox will insert a tab
|
// 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
|
||||||
e.Handled = true;
|
e.Handled = true;
|
||||||
}
|
}
|
||||||
else if (e.Key == VirtualKey.Escape)
|
else if (e.Key == VirtualKey.Escape)
|
||||||
@@ -280,22 +260,6 @@ public sealed partial class SearchBar : UserControl,
|
|||||||
_inSuggestion = false;
|
_inSuggestion = false;
|
||||||
_lastText = null;
|
_lastText = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!e.Handled)
|
|
||||||
{
|
|
||||||
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);
|
|
||||||
|
|
||||||
// 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);
|
|
||||||
WeakReferenceMessenger.Default.Send(msg);
|
|
||||||
e.Handled = msg.Handled;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void FilterBox_PreviewKeyUp(object sender, KeyRoutedEventArgs e)
|
private void FilterBox_PreviewKeyUp(object sender, KeyRoutedEventArgs e)
|
||||||
|
|||||||
@@ -30,7 +30,7 @@ public sealed partial class ListPage : Page,
|
|||||||
{
|
{
|
||||||
private InputSource _lastInputSource;
|
private InputSource _lastInputSource;
|
||||||
|
|
||||||
private ListViewModel? ViewModel
|
internal ListViewModel? ViewModel
|
||||||
{
|
{
|
||||||
get => (ListViewModel?)GetValue(ViewModelProperty);
|
get => (ListViewModel?)GetValue(ViewModelProperty);
|
||||||
set => SetValue(ViewModelProperty, value);
|
set => SetValue(ViewModelProperty, value);
|
||||||
|
|||||||
@@ -0,0 +1,10 @@
|
|||||||
|
// 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.
|
||||||
|
|
||||||
|
namespace Microsoft.CmdPal.UI.Helpers;
|
||||||
|
|
||||||
|
internal static class BindTransformers
|
||||||
|
{
|
||||||
|
public static bool Negate(bool value) => !value;
|
||||||
|
}
|
||||||
@@ -9,6 +9,7 @@
|
|||||||
xmlns:coreViewModels="using:Microsoft.CmdPal.Core.ViewModels"
|
xmlns:coreViewModels="using:Microsoft.CmdPal.Core.ViewModels"
|
||||||
xmlns:cpcontrols="using:Microsoft.CmdPal.UI.Controls"
|
xmlns:cpcontrols="using:Microsoft.CmdPal.UI.Controls"
|
||||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||||
|
xmlns:h="using:Microsoft.CmdPal.UI.Helpers"
|
||||||
xmlns:help="using:Microsoft.CmdPal.UI.Helpers"
|
xmlns:help="using:Microsoft.CmdPal.UI.Helpers"
|
||||||
xmlns:labToolkit="using:CommunityToolkit.Labs.WinUI.MarkdownTextBlock"
|
xmlns:labToolkit="using:CommunityToolkit.Labs.WinUI.MarkdownTextBlock"
|
||||||
xmlns:markdownImageProviders="using:Microsoft.CmdPal.UI.Helpers.MarkdownImageProviders"
|
xmlns:markdownImageProviders="using:Microsoft.CmdPal.UI.Helpers.MarkdownImageProviders"
|
||||||
@@ -16,7 +17,6 @@
|
|||||||
xmlns:tkcontrols="using:CommunityToolkit.WinUI.Controls"
|
xmlns:tkcontrols="using:CommunityToolkit.WinUI.Controls"
|
||||||
xmlns:toolkit="using:CommunityToolkit.WinUI.Controls"
|
xmlns:toolkit="using:CommunityToolkit.WinUI.Controls"
|
||||||
xmlns:ui="using:CommunityToolkit.WinUI"
|
xmlns:ui="using:CommunityToolkit.WinUI"
|
||||||
xmlns:viewModels="using:Microsoft.CmdPal.UI.ViewModels"
|
|
||||||
Background="Transparent"
|
Background="Transparent"
|
||||||
mc:Ignorable="d">
|
mc:Ignorable="d">
|
||||||
|
|
||||||
@@ -27,8 +27,6 @@
|
|||||||
EmptyValue="Collapsed"
|
EmptyValue="Collapsed"
|
||||||
NotEmptyValue="Visible" />
|
NotEmptyValue="Visible" />
|
||||||
|
|
||||||
<converters:BoolNegationConverter x:Key="BoolNegationConverter" />
|
|
||||||
|
|
||||||
<cmdpalUI:MessageStateToSeverityConverter x:Key="MessageStateToSeverityConverter" />
|
<cmdpalUI:MessageStateToSeverityConverter x:Key="MessageStateToSeverityConverter" />
|
||||||
|
|
||||||
<cmdpalUI:DetailsDataTemplateSelector
|
<cmdpalUI:DetailsDataTemplateSelector
|
||||||
@@ -175,9 +173,12 @@
|
|||||||
|
|
||||||
<!-- Back button and search box -->
|
<!-- Back button and search box -->
|
||||||
<Grid
|
<Grid
|
||||||
|
x:Name="TopBarGrid"
|
||||||
Padding="0,12,0,12"
|
Padding="0,12,0,12"
|
||||||
HorizontalAlignment="Stretch"
|
HorizontalAlignment="Stretch"
|
||||||
VerticalAlignment="Stretch">
|
VerticalAlignment="Stretch"
|
||||||
|
BorderBrush="{ThemeResource DividerStrokeColorDefaultBrush}"
|
||||||
|
BorderThickness="0,0,0,1">
|
||||||
<Grid.ColumnDefinitions>
|
<Grid.ColumnDefinitions>
|
||||||
<ColumnDefinition Width="Auto" />
|
<ColumnDefinition Width="Auto" />
|
||||||
<ColumnDefinition Width="*" />
|
<ColumnDefinition Width="*" />
|
||||||
@@ -354,11 +355,7 @@
|
|||||||
</animations:Implicit.HideAnimations>
|
</animations:Implicit.HideAnimations>
|
||||||
</ProgressBar>
|
</ProgressBar>
|
||||||
|
|
||||||
<Grid
|
<Grid x:Name="ContentGrid" Grid.Row="1">
|
||||||
x:Name="ContentGrid"
|
|
||||||
Grid.Row="1"
|
|
||||||
BorderBrush="{ThemeResource DividerStrokeColorDefaultBrush}"
|
|
||||||
BorderThickness="0,1,0,1">
|
|
||||||
|
|
||||||
<Grid.ColumnDefinitions>
|
<Grid.ColumnDefinitions>
|
||||||
<ColumnDefinition Width="3*" />
|
<ColumnDefinition Width="3*" />
|
||||||
@@ -367,6 +364,9 @@
|
|||||||
|
|
||||||
<Frame
|
<Frame
|
||||||
Name="RootFrame"
|
Name="RootFrame"
|
||||||
|
AutomationProperties.AutomationControlType="Pane"
|
||||||
|
AutomationProperties.LandmarkType="Navigation"
|
||||||
|
AutomationProperties.LiveSetting="Assertive"
|
||||||
IsNavigationStackEnabled="True"
|
IsNavigationStackEnabled="True"
|
||||||
Navigated="RootFrame_Navigated" />
|
Navigated="RootFrame_Navigated" />
|
||||||
|
|
||||||
@@ -502,7 +502,11 @@
|
|||||||
</InfoBar>
|
</InfoBar>
|
||||||
</StackPanel>
|
</StackPanel>
|
||||||
|
|
||||||
<Grid Grid.Row="1" Background="{ThemeResource LayerOnAcrylicSecondaryBackgroundBrush}">
|
<Grid
|
||||||
|
Grid.Row="1"
|
||||||
|
Background="{ThemeResource LayerOnAcrylicSecondaryBackgroundBrush}"
|
||||||
|
BorderBrush="{ThemeResource DividerStrokeColorDefaultBrush}"
|
||||||
|
BorderThickness="0,1,0,0">
|
||||||
<cpcontrols:CommandBar CurrentPageViewModel="{x:Bind ViewModel.CurrentPage, Mode=OneWay}" />
|
<cpcontrols:CommandBar CurrentPageViewModel="{x:Bind ViewModel.CurrentPage, Mode=OneWay}" />
|
||||||
</Grid>
|
</Grid>
|
||||||
|
|
||||||
@@ -519,6 +523,26 @@
|
|||||||
</VisualState.Setters>
|
</VisualState.Setters>
|
||||||
</VisualState>
|
</VisualState>
|
||||||
</VisualStateGroup>
|
</VisualStateGroup>
|
||||||
|
<VisualStateGroup>
|
||||||
|
<!--
|
||||||
|
Why disable it and make it transparent?
|
||||||
|
To prevent the container from collapsing, ensuring the layout metrics remain unchanged.
|
||||||
|
-->
|
||||||
|
<VisualStateGroup.Transitions>
|
||||||
|
<VisualTransition GeneratedDuration="0:0:0.187" To="SearchBarCollapsed" />
|
||||||
|
</VisualStateGroup.Transitions>
|
||||||
|
<VisualState x:Name="SearchBarVisible" />
|
||||||
|
<VisualState x:Name="SearchBarCollapsed">
|
||||||
|
<VisualState.StateTriggers>
|
||||||
|
<StateTrigger IsActive="{x:Bind h:BindTransformers.Negate(ViewModel.IsSearchBoxVisible), Mode=OneWay}" />
|
||||||
|
</VisualState.StateTriggers>
|
||||||
|
<VisualState.Setters>
|
||||||
|
<Setter Target="SearchBox.IsEnabled" Value="False" />
|
||||||
|
<Setter Target="SearchBox.Opacity" Value="0" />
|
||||||
|
<Setter Target="TopBarGrid.BorderBrush" Value="Transparent" />
|
||||||
|
</VisualState.Setters>
|
||||||
|
</VisualState>
|
||||||
|
</VisualStateGroup>
|
||||||
</VisualStateManager.VisualStateGroups>
|
</VisualStateManager.VisualStateGroups>
|
||||||
</Grid>
|
</Grid>
|
||||||
</Page>
|
</Page>
|
||||||
@@ -3,12 +3,15 @@
|
|||||||
// See the LICENSE file in the project root for more information.
|
// See the LICENSE file in the project root for more information.
|
||||||
|
|
||||||
using System.ComponentModel;
|
using System.ComponentModel;
|
||||||
|
using System.Globalization;
|
||||||
|
using System.Text;
|
||||||
using CommunityToolkit.Mvvm.Messaging;
|
using CommunityToolkit.Mvvm.Messaging;
|
||||||
using CommunityToolkit.WinUI;
|
using CommunityToolkit.WinUI;
|
||||||
using ManagedCommon;
|
using ManagedCommon;
|
||||||
using Microsoft.CmdPal.Core.ViewModels;
|
using Microsoft.CmdPal.Core.ViewModels;
|
||||||
using Microsoft.CmdPal.Core.ViewModels.Messages;
|
using Microsoft.CmdPal.Core.ViewModels.Messages;
|
||||||
using Microsoft.CmdPal.UI.Events;
|
using Microsoft.CmdPal.UI.Events;
|
||||||
|
using Microsoft.CmdPal.UI.Helpers;
|
||||||
using Microsoft.CmdPal.UI.Messages;
|
using Microsoft.CmdPal.UI.Messages;
|
||||||
using Microsoft.CmdPal.UI.Settings;
|
using Microsoft.CmdPal.UI.Settings;
|
||||||
using Microsoft.CmdPal.UI.ViewModels;
|
using Microsoft.CmdPal.UI.ViewModels;
|
||||||
@@ -17,9 +20,12 @@ using Microsoft.Extensions.DependencyInjection;
|
|||||||
using Microsoft.PowerToys.Telemetry;
|
using Microsoft.PowerToys.Telemetry;
|
||||||
using Microsoft.UI.Dispatching;
|
using Microsoft.UI.Dispatching;
|
||||||
using Microsoft.UI.Input;
|
using Microsoft.UI.Input;
|
||||||
|
using Microsoft.UI.Xaml;
|
||||||
|
using Microsoft.UI.Xaml.Automation.Peers;
|
||||||
using Microsoft.UI.Xaml.Controls;
|
using Microsoft.UI.Xaml.Controls;
|
||||||
using Microsoft.UI.Xaml.Input;
|
using Microsoft.UI.Xaml.Input;
|
||||||
using Microsoft.UI.Xaml.Media.Animation;
|
using Microsoft.UI.Xaml.Media.Animation;
|
||||||
|
using Windows.UI.Core;
|
||||||
using DispatcherQueue = Microsoft.UI.Dispatching.DispatcherQueue;
|
using DispatcherQueue = Microsoft.UI.Dispatching.DispatcherQueue;
|
||||||
using VirtualKey = Windows.System.VirtualKey;
|
using VirtualKey = Windows.System.VirtualKey;
|
||||||
|
|
||||||
@@ -55,6 +61,8 @@ public sealed partial class ShellPage : Microsoft.UI.Xaml.Controls.Page,
|
|||||||
|
|
||||||
private readonly ToastWindow _toast = new();
|
private readonly ToastWindow _toast = new();
|
||||||
|
|
||||||
|
private readonly CompositeFormat _pageNavigatedAnnouncement;
|
||||||
|
|
||||||
private SettingsWindow? _settingsWindow;
|
private SettingsWindow? _settingsWindow;
|
||||||
|
|
||||||
public ShellViewModel ViewModel { get; private set; } = App.Current.Services.GetService<ShellViewModel>()!;
|
public ShellViewModel ViewModel { get; private set; } = App.Current.Services.GetService<ShellViewModel>()!;
|
||||||
@@ -84,9 +92,13 @@ public sealed partial class ShellPage : Microsoft.UI.Xaml.Controls.Page,
|
|||||||
WeakReferenceMessenger.Default.Register<NavigateToPageMessage>(this);
|
WeakReferenceMessenger.Default.Register<NavigateToPageMessage>(this);
|
||||||
|
|
||||||
AddHandler(PreviewKeyDownEvent, new KeyEventHandler(ShellPage_OnPreviewKeyDown), true);
|
AddHandler(PreviewKeyDownEvent, new KeyEventHandler(ShellPage_OnPreviewKeyDown), true);
|
||||||
|
AddHandler(KeyDownEvent, new KeyEventHandler(ShellPage_OnKeyDown), false);
|
||||||
AddHandler(PointerPressedEvent, new PointerEventHandler(ShellPage_OnPointerPressed), true);
|
AddHandler(PointerPressedEvent, new PointerEventHandler(ShellPage_OnPointerPressed), true);
|
||||||
|
|
||||||
RootFrame.Navigate(typeof(LoadingPage), ViewModel);
|
RootFrame.Navigate(typeof(LoadingPage), ViewModel);
|
||||||
|
|
||||||
|
var pageAnnouncementFormat = ResourceLoaderInstance.GetString("ScreenReader_Announcement_NavigatedToPage0");
|
||||||
|
_pageNavigatedAnnouncement = CompositeFormat.Parse(pageAnnouncementFormat);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Receive(NavigateBackMessage message)
|
public void Receive(NavigateBackMessage message)
|
||||||
@@ -134,9 +146,6 @@ public sealed partial class ShellPage : Microsoft.UI.Xaml.Controls.Page,
|
|||||||
|
|
||||||
PowerToysTelemetry.Log.WriteEvent(new OpenPage(RootFrame.BackStackDepth));
|
PowerToysTelemetry.Log.WriteEvent(new OpenPage(RootFrame.BackStackDepth));
|
||||||
|
|
||||||
// Refocus on the Search for continual typing on the next search request
|
|
||||||
SearchBox.Focus(Microsoft.UI.Xaml.FocusState.Programmatic);
|
|
||||||
|
|
||||||
if (!ViewModel.IsNested)
|
if (!ViewModel.IsNested)
|
||||||
{
|
{
|
||||||
// todo BODGY
|
// todo BODGY
|
||||||
@@ -441,6 +450,78 @@ public sealed partial class ShellPage : Microsoft.UI.Xaml.Controls.Page,
|
|||||||
// We just need to reconcile our loading systems a bit more in the future.
|
// We just need to reconcile our loading systems a bit more in the future.
|
||||||
ViewModel.CurrentPage = page;
|
ViewModel.CurrentPage = page;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (e.Content is Page element)
|
||||||
|
{
|
||||||
|
element.Loaded += FocusAfterLoaded;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void FocusAfterLoaded(object sender, RoutedEventArgs e)
|
||||||
|
{
|
||||||
|
var page = (Page)sender;
|
||||||
|
page.Loaded -= FocusAfterLoaded;
|
||||||
|
|
||||||
|
AnnounceNavigationToPage(page);
|
||||||
|
|
||||||
|
var shouldSearchBoxBeVisible = ViewModel.CurrentPage?.HasSearchBox ?? false;
|
||||||
|
|
||||||
|
if (shouldSearchBoxBeVisible || page is not ContentPage)
|
||||||
|
{
|
||||||
|
ViewModel.IsSearchBoxVisible = shouldSearchBoxBeVisible;
|
||||||
|
SearchBox.Focus(FocusState.Programmatic);
|
||||||
|
SearchBox.SelectSearch();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
_ = Task.Run(async () =>
|
||||||
|
{
|
||||||
|
await page.DispatcherQueue.EnqueueAsync(async () =>
|
||||||
|
{
|
||||||
|
// I hate this so much, but it can take a while for the page to be ready to accept focus;
|
||||||
|
// focusing page with MarkdownTextBlock takes up to 5 attempts (* 100ms delay between attempts)
|
||||||
|
for (var i = 0; i < 10; i++)
|
||||||
|
{
|
||||||
|
if (FocusManager.FindFirstFocusableElement(page) is FrameworkElement frameworkElement)
|
||||||
|
{
|
||||||
|
var set = frameworkElement.Focus(FocusState.Programmatic);
|
||||||
|
if (set)
|
||||||
|
{
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
await Task.Delay(100);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update the search box visibility based on the current page:
|
||||||
|
// - We do this here after navigation so the focus is not jumping around too much,
|
||||||
|
// it messes with screen readers if we do it too early
|
||||||
|
// - Since this should hide the search box on content pages, it's not a problem if we
|
||||||
|
// wait for the code above to finish trying to focus the content
|
||||||
|
ViewModel.IsSearchBoxVisible = ViewModel.CurrentPage?.HasSearchBox ?? false;
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void AnnounceNavigationToPage(Page page)
|
||||||
|
{
|
||||||
|
var pageTitle = page switch
|
||||||
|
{
|
||||||
|
ListPage listPage => listPage.ViewModel?.Title,
|
||||||
|
ContentPage contentPage => contentPage.ViewModel?.Title,
|
||||||
|
_ => null,
|
||||||
|
};
|
||||||
|
|
||||||
|
if (string.IsNullOrEmpty(pageTitle))
|
||||||
|
{
|
||||||
|
pageTitle = ResourceLoaderInstance.GetString("UntitledPageTitle");
|
||||||
|
}
|
||||||
|
|
||||||
|
var announcement = string.Format(CultureInfo.CurrentCulture, _pageNavigatedAnnouncement.Format, pageTitle);
|
||||||
|
|
||||||
|
UIHelper.AnnounceActionForAccessibility(RootFrame, announcement, "CommandPalettePageNavigatedTo");
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -466,11 +547,54 @@ public sealed partial class ShellPage : Microsoft.UI.Xaml.Controls.Page,
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void ShellPage_OnPreviewKeyDown(object sender, KeyRoutedEventArgs e)
|
private static void ShellPage_OnPreviewKeyDown(object sender, KeyRoutedEventArgs e)
|
||||||
{
|
{
|
||||||
if (e.Key == VirtualKey.Left && e.KeyStatus.IsMenuKeyDown)
|
if (e.Key == VirtualKey.Left && e.KeyStatus.IsMenuKeyDown)
|
||||||
{
|
{
|
||||||
WeakReferenceMessenger.Default.Send<NavigateBackMessage>(new());
|
WeakReferenceMessenger.Default.Send<NavigateBackMessage>(new());
|
||||||
|
e.Handled = true;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
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);
|
||||||
|
|
||||||
|
// 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);
|
||||||
|
WeakReferenceMessenger.Default.Send(msg);
|
||||||
|
e.Handled = msg.Handled;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void ShellPage_OnKeyDown(object sender, KeyRoutedEventArgs e)
|
||||||
|
{
|
||||||
|
var ctrlPressed = InputKeyboardSource.GetKeyStateForCurrentThread(VirtualKey.Control).HasFlag(CoreVirtualKeyStates.Down);
|
||||||
|
if (ctrlPressed && e.Key == VirtualKey.Enter)
|
||||||
|
{
|
||||||
|
// ctrl+enter
|
||||||
|
WeakReferenceMessenger.Default.Send<ActivateSecondaryCommandMessage>();
|
||||||
|
e.Handled = true;
|
||||||
|
}
|
||||||
|
else if (e.Key == VirtualKey.Enter)
|
||||||
|
{
|
||||||
|
WeakReferenceMessenger.Default.Send<ActivateSelectedListItemMessage>();
|
||||||
|
e.Handled = true;
|
||||||
|
}
|
||||||
|
else if (ctrlPressed && e.Key == VirtualKey.K)
|
||||||
|
{
|
||||||
|
// ctrl+k
|
||||||
|
WeakReferenceMessenger.Default.Send<OpenContextMenuMessage>(new OpenContextMenuMessage(null, null, null, ContextMenuFilterLocation.Bottom));
|
||||||
|
e.Handled = true;
|
||||||
|
}
|
||||||
|
else if (e.Key == VirtualKey.Escape)
|
||||||
|
{
|
||||||
|
WeakReferenceMessenger.Default.Send<NavigateBackMessage>(new());
|
||||||
|
e.Handled = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -450,4 +450,10 @@ Right-click to remove the key combination, thereby deactivating the shortcut.</v
|
|||||||
<data name="ForDevelopersSettingsHeader.Text" xml:space="preserve">
|
<data name="ForDevelopersSettingsHeader.Text" xml:space="preserve">
|
||||||
<value>For Developers</value>
|
<value>For Developers</value>
|
||||||
</data>
|
</data>
|
||||||
|
<data name="UntitledPageTitle" xml:space="preserve">
|
||||||
|
<value>an untitled</value>
|
||||||
|
</data>
|
||||||
|
<data name="ScreenReader_Announcement_NavigatedToPage0" xml:space="preserve">
|
||||||
|
<value>Navigated to {0} page</value>
|
||||||
|
</data>
|
||||||
</root>
|
</root>
|
||||||
Reference in New Issue
Block a user