diff --git a/src/Files.App.Controls/BreadcrumbBar/BreadcrumbBar.Properties.cs b/src/Files.App.Controls/BreadcrumbBar/BreadcrumbBar.Properties.cs index 36dca955a6c1..a02306d2a95c 100644 --- a/src/Files.App.Controls/BreadcrumbBar/BreadcrumbBar.Properties.cs +++ b/src/Files.App.Controls/BreadcrumbBar/BreadcrumbBar.Properties.cs @@ -15,5 +15,11 @@ public partial class BreadcrumbBar : Control [GeneratedDependencyProperty] public partial object? ItemTemplate { get; set; } + + [GeneratedDependencyProperty] + public partial string? EllipsisButtonToolTip { get; set; } + + [GeneratedDependencyProperty] + public partial string? RootItemToolTip { get; set; } } } diff --git a/src/Files.App.Controls/BreadcrumbBar/BreadcrumbBar.xaml b/src/Files.App.Controls/BreadcrumbBar/BreadcrumbBar.xaml index 213ab4cd8340..071c43d19e5a 100644 --- a/src/Files.App.Controls/BreadcrumbBar/BreadcrumbBar.xaml +++ b/src/Files.App.Controls/BreadcrumbBar/BreadcrumbBar.xaml @@ -13,6 +13,7 @@ 8,0 16,0,8,0 2,0,0,0 + 32 2,2,2,2 2,2,2,2 @@ -55,8 +56,8 @@ x:Name="PART_RootBreadcrumbBarItem" Grid.Column="0" Padding="{StaticResource BreadcrumbBarRootItemPadding}" - AutomationProperties.AccessibilityView="Content" - CornerRadius="{StaticResource BreadcrumbBarRootItemCornerRadius}"> + CornerRadius="{StaticResource BreadcrumbBarRootItemCornerRadius}" + ItemToolTip="{TemplateBinding RootItemToolTip}"> @@ -64,8 +65,8 @@ x:Name="PART_EllipsisBreadcrumbBarItem" Grid.Column="1" Margin="{StaticResource BreadcrumbBarItemMargin}" - AutomationProperties.AccessibilityView="Content" IsEllipsis="True" + ToolTipService.ToolTip="{TemplateBinding EllipsisButtonToolTip}" Visibility="Collapsed"> @@ -96,7 +97,7 @@ - + @@ -114,120 +115,116 @@ x:Name="PART_LayoutRoot" TabFocusNavigation="Once" XYFocusKeyboardNavigation="Enabled"> - - - - - - - - - - + + + + AutomationProperties.Name="Chevron" + Background="{TemplateBinding Background}" + BorderBrush="{TemplateBinding BorderBrush}" + BorderThickness="{TemplateBinding BorderThickness}" + CornerRadius="{StaticResource BreadcrumbBarChevronCornerRaduis}" + Style="{StaticResource BreadcrumbBarItemChevronButtonStyle}" + ToolTipService.ToolTip="{TemplateBinding ChevronToolTip}" + UseSystemFocusVisuals="True"> + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/Files.App.Controls/BreadcrumbBar/BreadcrumbBarItem.Properties.cs b/src/Files.App.Controls/BreadcrumbBar/BreadcrumbBarItem.Properties.cs index 7b1709d84b26..47cae0a537a4 100644 --- a/src/Files.App.Controls/BreadcrumbBar/BreadcrumbBarItem.Properties.cs +++ b/src/Files.App.Controls/BreadcrumbBar/BreadcrumbBarItem.Properties.cs @@ -13,6 +13,12 @@ public partial class BreadcrumbBarItem [GeneratedDependencyProperty] public partial bool IsLastItem { get; set; } + [GeneratedDependencyProperty] + public partial string ItemToolTip { get; set; } + + [GeneratedDependencyProperty] + public partial string ChevronToolTip { get; set; } + partial void OnIsEllipsisChanged(bool newValue) { VisualStateManager.GoToState(this, newValue ? "ChevronCollapsed" : "ChevronVisible", true); diff --git a/src/Files.App.Controls/BreadcrumbBar/BreadcrumbBarItemAutomationPeer.cs b/src/Files.App.Controls/BreadcrumbBar/BreadcrumbBarItemAutomationPeer.cs index 42d81617f28c..bddb74a7bf8f 100644 --- a/src/Files.App.Controls/BreadcrumbBar/BreadcrumbBarItemAutomationPeer.cs +++ b/src/Files.App.Controls/BreadcrumbBar/BreadcrumbBarItemAutomationPeer.cs @@ -24,7 +24,7 @@ protected override string GetLocalizedControlTypeCore() protected override object GetPatternCore(PatternInterface patternInterface) { - if (patternInterface is PatternInterface.Invoke) + if (patternInterface is PatternInterface.ExpandCollapse or PatternInterface.Invoke) return this; return base.GetPatternCore(patternInterface); @@ -37,12 +37,15 @@ protected override string GetClassNameCore() protected override AutomationControlType GetAutomationControlTypeCore() { - return AutomationControlType.Button; + return AutomationControlType.SplitButton; } - /// - /// Sends a request to invoke the item associated with the automation peer. - /// + protected override bool IsControlElementCore() + { + return true; + } + + /// public void Invoke() { if (Owner is not BreadcrumbBarItem item) diff --git a/src/Files.App.Controls/BreadcrumbBar/BreadcrumbBarLayout.cs b/src/Files.App.Controls/BreadcrumbBar/BreadcrumbBarLayout.cs index da0313988121..f1a0d2efcc25 100644 --- a/src/Files.App.Controls/BreadcrumbBar/BreadcrumbBarLayout.cs +++ b/src/Files.App.Controls/BreadcrumbBar/BreadcrumbBarLayout.cs @@ -33,13 +33,15 @@ protected override Size MeasureOverride(NonVirtualizingLayoutContext context, Si var accumulatedSize = new Size(0, 0); _availableSize = availableSize; + var indexAfterEllipsis = GetFirstIndexToRender(context); + // Go through all items and measure them - foreach (var item in context.Children) + for (int index = 0; index < context.Children.Count; index++) { - if (item is BreadcrumbBarItem breadcrumbItem) + if (context.Children[index] is BreadcrumbBarItem breadcrumbItem) { breadcrumbItem.Measure(availableSize); - accumulatedSize.Width += breadcrumbItem.DesiredSize.Width; + accumulatedSize.Width += index < indexAfterEllipsis ? 0: breadcrumbItem.DesiredSize.Width; accumulatedSize.Height = Math.Max(accumulatedSize.Height, breadcrumbItem.DesiredSize.Height); } } @@ -49,7 +51,7 @@ protected override Size MeasureOverride(NonVirtualizingLayoutContext context, Si _ellipsisButton ??= context.Children[0] as BreadcrumbBarItem; // Sets the ellipsis item's visibility based on whether the items are overflowing - EllipsisIsRendered = accumulatedSize.Width > availableSize.Width; + EllipsisIsRendered = indexAfterEllipsis is not 0; return accumulatedSize; } diff --git a/src/Files.App.Controls/Omnibar/Omnibar.Events.cs b/src/Files.App.Controls/Omnibar/Omnibar.Events.cs index 9f33112c8ee0..4499c41ab5d4 100644 --- a/src/Files.App.Controls/Omnibar/Omnibar.Events.cs +++ b/src/Files.App.Controls/Omnibar/Omnibar.Events.cs @@ -22,6 +22,16 @@ private void AutoSuggestBox_GettingFocus(UIElement sender, GettingFocusEventArgs _previouslyFocusedElement = new(args.OldFocusedElement as UIElement); } + private void AutoSuggestBox_LosingFocus(UIElement sender, LosingFocusEventArgs args) + { + if (IsModeButtonPressed) + { + IsModeButtonPressed = false; + args.TryCancel(); + return; + } + } + private void AutoSuggestBox_GotFocus(object sender, RoutedEventArgs e) { IsFocused = true; @@ -70,7 +80,7 @@ private void AutoSuggestBox_KeyDown(object sender, KeyRoutedEventArgs e) { _textBoxSuggestionsListView.SelectedIndex = nextIndex; - ChooseSuggestionItem(_textBoxSuggestionsListView.SelectedItem); + ChooseSuggestionItem(_textBoxSuggestionsListView.SelectedItem, true); } } else if (e.Key == VirtualKey.Escape) @@ -127,5 +137,10 @@ private void AutoSuggestBoxSuggestionsListView_ItemClick(object sender, ItemClic ChooseSuggestionItem(e.ClickedItem); SubmitQuery(e.ClickedItem); } + + private void AutoSuggestBoxSuggestionsListView_SelectionChanged(object sender, SelectionChangedEventArgs e) + { + _textBoxSuggestionsListView.ScrollIntoView(_textBoxSuggestionsListView.SelectedItem); + } } } diff --git a/src/Files.App.Controls/Omnibar/Omnibar.cs b/src/Files.App.Controls/Omnibar/Omnibar.cs index f4d29468e1ff..50c4eecacd43 100644 --- a/src/Files.App.Controls/Omnibar/Omnibar.cs +++ b/src/Files.App.Controls/Omnibar/Omnibar.cs @@ -33,6 +33,9 @@ public partial class Omnibar : Control private WeakReference _previouslyFocusedElement = new(null); + // NOTE: This is a workaround to keep Omnibar's focus on a mode button being clicked + internal bool IsModeButtonPressed { get; set; } + // Events public event TypedEventHandler? QuerySubmitted; @@ -71,11 +74,13 @@ protected override void OnApplyTemplate() SizeChanged += Omnibar_SizeChanged; _textBox.GettingFocus += AutoSuggestBox_GettingFocus; _textBox.GotFocus += AutoSuggestBox_GotFocus; + _textBox.LosingFocus += AutoSuggestBox_LosingFocus; _textBox.LostFocus += AutoSuggestBox_LostFocus; _textBox.KeyDown += AutoSuggestBox_KeyDown; _textBox.TextChanged += AutoSuggestBox_TextChanged; _textBoxSuggestionsPopup.GettingFocus += AutoSuggestBoxSuggestionsPopup_GettingFocus; _textBoxSuggestionsListView.ItemClick += AutoSuggestBoxSuggestionsListView_ItemClick; + _textBoxSuggestionsListView.SelectionChanged += AutoSuggestBoxSuggestionsListView_SelectionChanged; // Set the default width _textBoxSuggestionsContainerBorder.Width = ActualWidth; @@ -148,6 +153,11 @@ protected void ChangeMode(OmnibarMode? oldMode, OmnibarMode newMode) VisualStateManager.GoToState(newMode, "Focused", true); newMode.IsTabStop = false; + + _textBox.PlaceholderText = newMode.PlaceholderText ?? string.Empty; + _textBoxSuggestionsListView.ItemTemplate = newMode.SuggestionItemTemplate; + _textBoxSuggestionsListView.ItemsSource = newMode.SuggestionItemsSource; + if (newMode.IsAutoFocusEnabled) { _textBox.Focus(FocusState.Pointer); @@ -196,12 +206,13 @@ public bool TryToggleIsSuggestionsPopupOpen(bool wantToOpen) return false; } - public void ChooseSuggestionItem(object obj) + public void ChooseSuggestionItem(object obj, bool isOriginatedFromArrowKey = false) { if (CurrentSelectedMode is null) return; - if (CurrentSelectedMode.UpdateTextOnSelect) + if (CurrentSelectedMode.UpdateTextOnSelect || + (isOriginatedFromArrowKey && CurrentSelectedMode.UpdateTextOnArrowKeys)) { _textChangeReason = OmnibarTextChangeReason.SuggestionChosen; ChangeTextBoxText(GetObjectText(obj)); diff --git a/src/Files.App.Controls/Omnibar/Omnibar.xaml b/src/Files.App.Controls/Omnibar/Omnibar.xaml index 64eabed99947..26c90b65eb0f 100644 --- a/src/Files.App.Controls/Omnibar/Omnibar.xaml +++ b/src/Files.App.Controls/Omnibar/Omnibar.xaml @@ -67,7 +67,6 @@ FontStretch="{TemplateBinding FontStretch}" FontWeight="{TemplateBinding FontWeight}" Foreground="{TemplateBinding Foreground}" - PlaceholderText="{Binding CurrentSelectedMode.PlaceholderText, RelativeSource={RelativeSource TemplatedParent}, Mode=OneWay}" ScrollViewer.BringIntoViewOnFocusChange="False" Style="{StaticResource DefaultOmnibarTextBoxStyle}" UseSystemFocusVisuals="{TemplateBinding UseSystemFocusVisuals}" /> @@ -96,11 +95,7 @@ MaxHeight="{ThemeResource AutoSuggestListMaxHeight}" Margin="{ThemeResource AutoSuggestListPadding}" IsItemClickEnabled="True" - ItemTemplate="{Binding CurrentSelectedMode.SuggestionItemTemplate, RelativeSource={RelativeSource TemplatedParent}, Mode=OneWay}" - ItemsSource="{Binding CurrentSelectedMode.SuggestionItemsSource, RelativeSource={RelativeSource TemplatedParent}, Mode=OneWay}" - SelectionMode="Single"> - - + SelectionMode="Single" /> @@ -138,7 +133,7 @@ - + - + diff --git a/src/Files.App.Controls/Omnibar/OmnibarMode.Events.cs b/src/Files.App.Controls/Omnibar/OmnibarMode.Events.cs index e7f73fd5262c..fdc6fa6b3c7f 100644 --- a/src/Files.App.Controls/Omnibar/OmnibarMode.Events.cs +++ b/src/Files.App.Controls/Omnibar/OmnibarMode.Events.cs @@ -30,6 +30,8 @@ private void ModeButton_PointerReleased(object sender, PointerRoutedEventArgs e) VisualStateManager.GoToState(this, "PointerOver", true); + owner.IsModeButtonPressed = true; + // Change the current mode owner.CurrentSelectedMode = this; } diff --git a/src/Files.App.Controls/Omnibar/OmnibarMode.Properties.cs b/src/Files.App.Controls/Omnibar/OmnibarMode.Properties.cs index dbac55f6ca1f..6245ebbca02f 100644 --- a/src/Files.App.Controls/Omnibar/OmnibarMode.Properties.cs +++ b/src/Files.App.Controls/Omnibar/OmnibarMode.Properties.cs @@ -43,6 +43,9 @@ public partial class OmnibarMode [GeneratedDependencyProperty(DefaultValue = true)] public partial bool UpdateTextOnSelect { get; set; } + [GeneratedDependencyProperty(DefaultValue = true)] + public partial bool UpdateTextOnArrowKeys { get; set; } + [GeneratedDependencyProperty] public partial bool IsAutoFocusEnabled { get; set; } diff --git a/src/Files.App/Strings/en-US/Resources.resw b/src/Files.App/Strings/en-US/Resources.resw index 8a5b39ff452e..7040ba60c3f2 100644 --- a/src/Files.App/Strings/en-US/Resources.resw +++ b/src/Files.App/Strings/en-US/Resources.resw @@ -4240,4 +4240,10 @@ Icon files This is the friendly name for a variety of different icon files. + + Show collapsed path breadcrumbs + + + Show child folders + diff --git a/src/Files.App/UserControls/NavigationToolbar.xaml b/src/Files.App/UserControls/NavigationToolbar.xaml index 30f1f6f82475..e1056c2544ed 100644 --- a/src/Files.App/UserControls/NavigationToolbar.xaml +++ b/src/Files.App/UserControls/NavigationToolbar.xaml @@ -324,6 +324,8 @@ CurrentSelectedMode="{x:Bind ViewModel.OmnibarCurrentSelectedMode, Mode=TwoWay}" CurrentSelectedModeName="{x:Bind ViewModel.OmnibarCurrentSelectedModeName, Mode=TwoWay}" IsFocused="{x:Bind ViewModel.IsOmnibarFocused, Mode=TwoWay}" + LostFocus="Omnibar_LostFocus" + PreviewKeyDown="Omnibar_PreviewKeyDown" QuerySubmitted="Omnibar_QuerySubmitted" TextChanged="Omnibar_TextChanged"> @@ -340,10 +342,12 @@ + ItemsSource="{x:Bind ViewModel.PathComponents, Mode=OneWay}" + RootItemToolTip="{helpers:ResourceString Name=Home}"> - + diff --git a/src/Files.App/UserControls/NavigationToolbar.xaml.cs b/src/Files.App/UserControls/NavigationToolbar.xaml.cs index 9d7751f1b88e..e2b093d36d22 100644 --- a/src/Files.App/UserControls/NavigationToolbar.xaml.cs +++ b/src/Files.App/UserControls/NavigationToolbar.xaml.cs @@ -260,6 +260,7 @@ private async void Omnibar_QuerySubmitted(Omnibar sender, OmnibarQuerySubmittedE if (Omnibar.CurrentSelectedMode == OmnibarPathMode) { await ViewModel.HandleItemNavigationAsync(args.Text); + (MainPageViewModel.SelectedTabItem?.TabItemContent as Control)?.Focus(FocusState.Programmatic); } else if (Omnibar.CurrentSelectedMode == OmnibarCommandPaletteMode) { @@ -278,13 +279,10 @@ await DialogDisplayHelper.ShowDialogAsync(Strings.CommandNotExecutable.GetLocali string.Format(Strings.CommandNotExecutableContent.GetLocalizedResource(), command.Code)); else await command.ExecuteAsync(); - - ViewModel.OmnibarCurrentSelectedMode = OmnibarPathMode; - return; } // Try invoking Windows app action - if (ActionManager.Instance.ActionRuntime is not null && item.ActionInstance is ActionInstance actionInstance) + else if (ActionManager.Instance.ActionRuntime is not null && item.ActionInstance is ActionInstance actionInstance) { // Workaround for https://github.com/microsoft/App-Actions-On-Windows-Samples/issues/7 var action = ActionManager.Instance.ActionRuntime.ActionCatalog.GetAllActions() @@ -295,9 +293,9 @@ await DialogDisplayHelper.ShowDialogAsync(Strings.CommandNotExecutable.GetLocali var overload = action.GetOverloads().FirstOrDefault(); await overload?.InvokeAsync(actionInstance.Context); } - - ViewModel.OmnibarCurrentSelectedMode = OmnibarPathMode; } + + (MainPageViewModel.SelectedTabItem?.TabItemContent as Control)?.Focus(FocusState.Programmatic); } else if (Omnibar.CurrentSelectedMode == OmnibarSearchMode) { @@ -412,5 +410,23 @@ private void BreadcrumbBar_ItemDropDownFlyoutClosed(object sender, BreadcrumbBar // Clear the flyout items to save memory e.Flyout.Items.Clear(); } + + private void Omnibar_LostFocus(object sender, RoutedEventArgs e) + { + if (ViewModel.OmnibarCurrentSelectedMode == OmnibarCommandPaletteMode) + { + ViewModel.OmnibarCurrentSelectedMode = OmnibarPathMode; + ViewModel.OmnibarCommandPaletteModeText = string.Empty; + } + } + + private void Omnibar_PreviewKeyDown(object sender, KeyRoutedEventArgs e) + { + if (e.Key is VirtualKey.Escape) + { + Omnibar.IsFocused = false; + (MainPageViewModel.SelectedTabItem?.TabItemContent as Control)?.Focus(FocusState.Programmatic); + } + } } }