Skip to content

Code Quality: Fixed a number of focus related issues in Omnibar #17239

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 11 commits into from
Jul 3, 2025
22 changes: 8 additions & 14 deletions src/Files.App.Controls/Omnibar/Omnibar.Events.cs
Original file line number Diff line number Diff line change
@@ -1,10 +1,8 @@
// Copyright (c) Files Community
// Licensed under the MIT License.

using Microsoft.UI.Input;
using Microsoft.UI.Xaml.Input;
using Windows.System;
using Windows.UI.Core;

namespace Files.App.Controls
{
Expand All @@ -28,7 +26,7 @@ private void AutoSuggestBox_GettingFocus(UIElement sender, GettingFocusEventArgs

private void AutoSuggestBox_LosingFocus(UIElement sender, LosingFocusEventArgs args)
{
if (IsModeButtonPressed)
if (args.NewFocusedElement is Button && IsModeButtonPressed)
{
IsModeButtonPressed = false;
args.TryCancel();
Expand All @@ -47,12 +45,16 @@ private void AutoSuggestBox_GotFocus(object sender, RoutedEventArgs e)
private void AutoSuggestBox_LostFocus(object sender, RoutedEventArgs e)
{
// TextBox still has focus if the context menu for selected text is open
if (_textBox.ContextFlyout.IsOpen)
var element = Microsoft.UI.Xaml.Input.FocusManager.GetFocusedElement(this.XamlRoot);
if (element is FlyoutBase or Popup)
return;

GlobalHelper.WriteDebugStringForOmnibar("The TextBox lost the focus.");

IsFocused = false;

// Reset to the default mode when Omnibar loses focus
CurrentSelectedMode = Modes?.FirstOrDefault();
}

private async void AutoSuggestBox_KeyDown(object sender, KeyRoutedEventArgs e)
Expand Down Expand Up @@ -112,16 +114,6 @@ private async void AutoSuggestBox_KeyDown(object sender, KeyRoutedEventArgs e)
previouslyFocusedElement?.Focus(FocusState.Programmatic);
}
}
else if (e.Key == VirtualKey.Tab && !InputKeyboardSource.GetKeyStateForCurrentThread(VirtualKey.Shift).HasFlag(CoreVirtualKeyStates.Down))
{
GlobalHelper.WriteDebugStringForOmnibar("The TextBox accepted the Tab key.");

// Focus on inactive content when pressing Tab instead of moving to the next control in the tab order
e.Handled = true;
IsFocused = false;
await Task.Delay(15);
CurrentSelectedMode?.ContentOnInactive?.Focus(FocusState.Keyboard);
}
else
{
_textChangeReason = OmnibarTextChangeReason.UserInput;
Expand All @@ -141,6 +133,8 @@ private void AutoSuggestBox_TextChanged(object sender, TextChangedEventArgs e)
_textChangeReason = OmnibarTextChangeReason.UserInput;
_userInput = _textBox.Text;
}
else if (_textChangeReason is OmnibarTextChangeReason.ProgrammaticChange)
_textBox.SelectAll();

TextChanged?.Invoke(this, new(CurrentSelectedMode, _textChangeReason));

Expand Down
2 changes: 1 addition & 1 deletion src/Files.App.Controls/Omnibar/Omnibar.cs
Original file line number Diff line number Diff line change
Expand Up @@ -128,7 +128,7 @@ protected void ChangeMode(OmnibarMode? oldMode, OmnibarMode newMode)
// Add the reposition transition to the all modes
mode.Transitions = [new RepositionThemeTransition()];
mode.UpdateLayout();
mode.IsTabStop = true;
mode.IsTabStop = false;
}

var index = _modesHostGrid.Children.IndexOf(newMode);
Expand Down
30 changes: 10 additions & 20 deletions src/Files.App.Controls/Omnibar/Omnibar.xaml
Original file line number Diff line number Diff line change
Expand Up @@ -117,33 +117,28 @@
<Setter Property="HorizontalContentAlignment" Value="Left" />
<Setter Property="VerticalAlignment" Value="Stretch" />

<Setter Property="IsTabStop" Value="True" />
<Setter Property="UseSystemFocusVisuals" Value="{StaticResource UseSystemFocusVisuals}" />

<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="local:OmnibarMode">
<Grid
x:Name="PART_RootGrid"
Height="{TemplateBinding Height}"
HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}"
Background="{TemplateBinding Background}"
TabFocusNavigation="Local">
Background="{TemplateBinding Background}">
<!-- Mode Button -->
<Border
<Button
x:Name="PART_ModeButton"
Width="{StaticResource OmnibarModeDefaultClickAreaWidth}"
Margin="1"
Background="{TemplateBinding Background}"
BorderBrush="{TemplateBinding BorderBrush}"
BorderThickness="{TemplateBinding BorderThickness}"
BorderThickness="0"
CornerRadius="{TemplateBinding CornerRadius}"
IsTabStop="True"
ToolTipService.ToolTip="{Binding ModeName, RelativeSource={RelativeSource TemplatedParent}, Mode=OneWay}"
UseSystemFocusVisuals="{StaticResource UseSystemFocusVisuals}">
<Border.BackgroundTransition>
<BrushTransition Duration="0:0:0.083" />
</Border.BackgroundTransition>
ToolTipService.ToolTip="{Binding ModeName, RelativeSource={RelativeSource TemplatedParent}, Mode=OneWay}">
<Button.Resources>
<SolidColorBrush x:Key="ButtonBackgroundPointerOver" Color="{ThemeResource SubtleFillColorSecondary}" />
<SolidColorBrush x:Key="ButtonBackgroundPressed" Color="{ThemeResource SubtleFillColorTertiary}" />
</Button.Resources>

<Grid>
<ContentPresenter
Expand All @@ -160,7 +155,7 @@
Content="{Binding IconOnActive, RelativeSource={RelativeSource TemplatedParent}, Mode=OneWay}"
Visibility="Collapsed" />
</Grid>
</Border>
</Button>

<ContentPresenter
x:Name="PART_InactiveContent"
Expand All @@ -174,14 +169,9 @@

<VisualStateGroup x:Name="PointerStates">
<VisualState x:Name="PointerNormal" />
<VisualState x:Name="PointerOver">
<VisualState.Setters>
<Setter Target="PART_ModeButton.Background" Value="{ThemeResource SubtleFillColorSecondaryBrush}" />
</VisualState.Setters>
</VisualState>
<VisualState x:Name="PointerOver" />
<VisualState x:Name="PointerPressed">
<VisualState.Setters>
<Setter Target="PART_ModeButton.Background" Value="{ThemeResource SubtleFillColorTertiaryBrush}" />
<Setter Target="PART_ModeButtonInactiveIconPresenter.Visibility" Value="Collapsed" />
<Setter Target="PART_ModeButtonActiveIconPresenter.Visibility" Value="Visible" />
</VisualState.Setters>
Expand Down
16 changes: 10 additions & 6 deletions src/Files.App.Controls/Omnibar/OmnibarMode.Events.cs
Original file line number Diff line number Diff line change
Expand Up @@ -35,11 +35,6 @@ private void ModeButton_PointerReleased(object sender, PointerRoutedEventArgs e)
GlobalHelper.WriteDebugStringForOmnibar($"The mouse pointer has been unpressed from the UI area of this Mode ({this})");

VisualStateManager.GoToState(this, "PointerOver", true);

owner.IsModeButtonPressed = true;

// Change the current mode
owner.CurrentSelectedMode = this;
}

private void ModeButton_PointerExited(object sender, PointerRoutedEventArgs e)
Expand All @@ -48,5 +43,14 @@ private void ModeButton_PointerExited(object sender, PointerRoutedEventArgs e)

VisualStateManager.GoToState(this, "PointerNormal", true);
}

private void ModeButton_Click(object sender, RoutedEventArgs e)
{
if (_ownerRef is null || _ownerRef.TryGetTarget(out var owner) is false || owner.CurrentSelectedMode == this)
return;

owner.IsModeButtonPressed = true;
owner.CurrentSelectedMode = this;
}
}
}
}
5 changes: 3 additions & 2 deletions src/Files.App.Controls/Omnibar/OmnibarMode.cs
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ public partial class OmnibarMode : ItemsControl

private WeakReference<Omnibar>? _ownerRef;

private Border _modeButton = null!;
private Button _modeButton = null!;

// Constructor

Expand All @@ -33,14 +33,15 @@ protected override void OnApplyTemplate()
{
base.OnApplyTemplate();

_modeButton = GetTemplateChild(TemplatePartName_ModeButton) as Border
_modeButton = GetTemplateChild(TemplatePartName_ModeButton) as Button
?? throw new MissingFieldException($"Could not find {TemplatePartName_ModeButton} in the given {nameof(OmnibarMode)}'s style.");

Loaded += OmnibarMode_Loaded;
_modeButton.PointerEntered += ModeButton_PointerEntered;
_modeButton.PointerPressed += ModeButton_PointerPressed;
_modeButton.PointerReleased += ModeButton_PointerReleased;
_modeButton.PointerExited += ModeButton_PointerExited;
_modeButton.Click += ModeButton_Click;

GlobalHelper.WriteDebugStringForOmnibar($"The template and the events of the Omnibar Mode ({this}) have been initialized.");
}
Expand Down
2 changes: 1 addition & 1 deletion src/Files.App/UserControls/NavigationToolbar.xaml
Original file line number Diff line number Diff line change
Expand Up @@ -351,7 +351,7 @@
x:Load="{x:Bind ViewModel.EnableOmnibar, Mode=OneWay}"
CurrentSelectedModeName="{x:Bind ViewModel.OmnibarCurrentSelectedModeName, Mode=TwoWay}"
IsFocused="{x:Bind ViewModel.IsOmnibarFocused, Mode=TwoWay}"
LostFocus="Omnibar_LostFocus"
ModeChanged="Omnibar_ModeChanged"
PreviewKeyDown="Omnibar_PreviewKeyDown"
QuerySubmitted="Omnibar_QuerySubmitted"
TextChanged="Omnibar_TextChanged">
Expand Down
20 changes: 15 additions & 5 deletions src/Files.App/UserControls/NavigationToolbar.xaml.cs
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
using Microsoft.UI.Xaml.Navigation;
using Windows.AI.Actions.Hosting;
using Windows.System;
using Windows.UI.Core;

namespace Files.App.UserControls
{
Expand Down Expand Up @@ -427,22 +428,31 @@ private void BreadcrumbBar_ItemDropDownFlyoutClosed(object sender, BreadcrumbBar
e.Flyout.Items.Clear();
}

private void Omnibar_LostFocus(object sender, RoutedEventArgs e)
private void Omnibar_ModeChanged(object sender, OmnibarModeChangedEventArgs e)
{
// Reset the command palette text when switching modes
if (Omnibar.CurrentSelectedMode == OmnibarCommandPaletteMode)
{
Omnibar.CurrentSelectedMode = OmnibarPathMode;
ViewModel.OmnibarCommandPaletteModeText = string.Empty;
}
}

private void Omnibar_PreviewKeyDown(object sender, KeyRoutedEventArgs e)
private async void Omnibar_PreviewKeyDown(object sender, KeyRoutedEventArgs e)
{
if (e.Key is VirtualKey.Escape)
{
Omnibar.IsFocused = false;
(MainPageViewModel.SelectedTabItem?.TabItemContent as Control)?.Focus(FocusState.Programmatic);
}
else if (e.Key is VirtualKey.Tab && Omnibar.IsFocused && !InputKeyboardSource.GetKeyStateForCurrentThread(VirtualKey.Shift).HasFlag(CoreVirtualKeyStates.Down))
{
var currentSelectedMode = Omnibar.CurrentSelectedMode;
Omnibar.IsFocused = false;
await Task.Delay(15);

if (currentSelectedMode == OmnibarPathMode)
BreadcrumbBar.Focus(FocusState.Keyboard);
else if (currentSelectedMode == OmnibarCommandPaletteMode)
OmnibarCommandPaletteMode.Focus(FocusState.Keyboard);
}
}

private void NavigationButtonOverflowFlyoutButton_LosingFocus(UIElement sender, LosingFocusEventArgs args)
Expand Down
Loading