diff --git a/src/Files.App.Controls/Omnibar/Omnibar.Events.cs b/src/Files.App.Controls/Omnibar/Omnibar.Events.cs index ebd4f5bcb457..9f33112c8ee0 100644 --- a/src/Files.App.Controls/Omnibar/Omnibar.Events.cs +++ b/src/Files.App.Controls/Omnibar/Omnibar.Events.cs @@ -14,14 +14,18 @@ private void Omnibar_SizeChanged(object sender, SizeChangedEventArgs e) _textBoxSuggestionsContainerBorder.Width = ActualWidth; } - private void AutoSuggestBox_GotFocus(object sender, RoutedEventArgs e) + private void AutoSuggestBox_GettingFocus(UIElement sender, GettingFocusEventArgs args) { - IsFocused = true; + if (args.OldFocusedElement is null) + return; - VisualStateManager.GoToState(CurrentSelectedMode, "Focused", true); - VisualStateManager.GoToState(_textBox, "InputAreaVisible", true); + _previouslyFocusedElement = new(args.OldFocusedElement as UIElement); + } - TryToggleIsSuggestionsPopupOpen(true); + private void AutoSuggestBox_GotFocus(object sender, RoutedEventArgs e) + { + IsFocused = true; + _textBox.SelectAll(); } private void AutoSuggestBox_LostFocus(object sender, RoutedEventArgs e) @@ -31,14 +35,6 @@ private void AutoSuggestBox_LostFocus(object sender, RoutedEventArgs e) return; IsFocused = false; - - if (CurrentSelectedMode?.ContentOnInactive is not null) - { - VisualStateManager.GoToState(CurrentSelectedMode, "CurrentUnfocused", true); - VisualStateManager.GoToState(_textBox, "InputAreaCollapsed", true); - } - - TryToggleIsSuggestionsPopupOpen(false); } private void AutoSuggestBox_KeyDown(object sender, KeyRoutedEventArgs e) @@ -77,12 +73,20 @@ private void AutoSuggestBox_KeyDown(object sender, KeyRoutedEventArgs e) ChooseSuggestionItem(_textBoxSuggestionsListView.SelectedItem); } } - else if (e.Key == VirtualKey.Escape && _textBoxSuggestionsPopup.IsOpen) + else if (e.Key == VirtualKey.Escape) { e.Handled = true; - RevertTextToUserInput(); - _textBoxSuggestionsPopup.IsOpen = false; + if (_textBoxSuggestionsPopup.IsOpen) + { + RevertTextToUserInput(); + _textBoxSuggestionsPopup.IsOpen = false; + } + else + { + _previouslyFocusedElement.TryGetTarget(out var previouslyFocusedElement); + previouslyFocusedElement?.Focus(FocusState.Programmatic); + } } else { diff --git a/src/Files.App.Controls/Omnibar/Omnibar.Properties.cs b/src/Files.App.Controls/Omnibar/Omnibar.Properties.cs index d4d8ff449cab..fb3cc94db2a5 100644 --- a/src/Files.App.Controls/Omnibar/Omnibar.Properties.cs +++ b/src/Files.App.Controls/Omnibar/Omnibar.Properties.cs @@ -22,14 +22,50 @@ public partial class Omnibar [GeneratedDependencyProperty] public partial bool IsFocused { get; set; } - partial void OnCurrentSelectedModeChanged(OmnibarMode? newValue) + partial void OnCurrentSelectedModePropertyChanged(DependencyPropertyChangedEventArgs e) { - CurrentSelectedModeName = newValue?.ModeName; + if (e.NewValue is not OmnibarMode newMode) + return; + + ChangeMode(e.OldValue as OmnibarMode, newMode); + CurrentSelectedModeName = newMode.ModeName; + } + + partial void OnCurrentSelectedModeNameChanged(string? newValue) + { + if (string.IsNullOrEmpty(newValue) || + string.IsNullOrEmpty(CurrentSelectedMode?.Name) || + CurrentSelectedMode.Name.Equals(newValue) || + Modes is null) + return; + + var newMode = Modes.Where(x => x.Name?.Equals(newValue) ?? false).FirstOrDefault(); + if (newMode is null) + return; + + CurrentSelectedMode = newMode; } partial void OnIsFocusedChanged(bool newValue) { - //_textBox?.Focus(newValue ? FocusState.Programmatic : FocusState.Unfocused); + if (CurrentSelectedMode is null || _textBox is null) + return; + + if (newValue) + { + VisualStateManager.GoToState(CurrentSelectedMode, "Focused", true); + VisualStateManager.GoToState(_textBox, "InputAreaVisible", true); + } + else + { + if (CurrentSelectedMode?.ContentOnInactive is not null) + { + VisualStateManager.GoToState(CurrentSelectedMode, "CurrentUnfocused", true); + VisualStateManager.GoToState(_textBox, "InputAreaCollapsed", true); + } + } + + TryToggleIsSuggestionsPopupOpen(newValue); } } } diff --git a/src/Files.App.Controls/Omnibar/Omnibar.cs b/src/Files.App.Controls/Omnibar/Omnibar.cs index fa780da79423..f4d29468e1ff 100644 --- a/src/Files.App.Controls/Omnibar/Omnibar.cs +++ b/src/Files.App.Controls/Omnibar/Omnibar.cs @@ -31,6 +31,8 @@ public partial class Omnibar : Control private string _userInput = string.Empty; private OmnibarTextChangeReason _textChangeReason = OmnibarTextChangeReason.None; + private WeakReference _previouslyFocusedElement = new(null); + // Events public event TypedEventHandler? QuerySubmitted; @@ -67,6 +69,7 @@ protected override void OnApplyTemplate() PopulateModes(); SizeChanged += Omnibar_SizeChanged; + _textBox.GettingFocus += AutoSuggestBox_GettingFocus; _textBox.GotFocus += AutoSuggestBox_GotFocus; _textBox.LostFocus += AutoSuggestBox_LostFocus; _textBox.KeyDown += AutoSuggestBox_KeyDown; @@ -104,27 +107,23 @@ public void PopulateModes() } } - public void ChangeMode(OmnibarMode modeToExpand, bool shouldFocus = false, bool useTransition = true) + protected void ChangeMode(OmnibarMode? oldMode, OmnibarMode newMode) { - if (_modesHostGrid is null || Modes is null) + if (_modesHostGrid is null || Modes is null || CurrentSelectedMode is null) return; foreach (var mode in Modes) { // Add the reposition transition to the all modes - if (useTransition) - { - mode.Transitions = [new RepositionThemeTransition()]; - mode.UpdateLayout(); - } - - mode.OnChangingCurrentMode(false); + mode.Transitions = [new RepositionThemeTransition()]; + mode.UpdateLayout(); + mode.IsTabStop = true; } - var index = _modesHostGrid.Children.IndexOf(modeToExpand); + var index = _modesHostGrid.Children.IndexOf(newMode); - if (CurrentSelectedMode is not null) - VisualStateManager.GoToState(CurrentSelectedMode, "Unfocused", true); + if (oldMode is not null) + VisualStateManager.GoToState(oldMode, "Unfocused", true); // Reset foreach (var column in _modesHostGrid.ColumnDefinitions) @@ -134,8 +133,8 @@ public void ChangeMode(OmnibarMode modeToExpand, bool shouldFocus = false, bool _modesHostGrid.ColumnDefinitions[index].Width = new(1, GridUnitType.Star); var itemCount = Modes.Count; - var itemIndex = Modes.IndexOf(modeToExpand); - var modeButtonWidth = modeToExpand.ActualWidth; + var itemIndex = Modes.IndexOf(newMode); + var modeButtonWidth = newMode.ActualWidth; var modeSeparatorWidth = itemCount is not 0 or 1 ? _modesHostGrid.Children[1] is FrameworkElement frameworkElement ? frameworkElement.ActualWidth : 0 : 0; var leftPadding = (itemIndex + 1) * modeButtonWidth + modeSeparatorWidth * itemIndex; @@ -144,51 +143,52 @@ public void ChangeMode(OmnibarMode modeToExpand, bool shouldFocus = false, bool // Set the correct AutoSuggestBox cursor position AutoSuggestBoxPadding = new(leftPadding, 0, rightPadding, 0); - CurrentSelectedMode = modeToExpand; - _textChangeReason = OmnibarTextChangeReason.ProgrammaticChange; - ChangeTextBoxText(CurrentSelectedMode.Text ?? string.Empty); - - // Move cursor of the TextBox to the tail - _textBox.Select(_textBox.Text.Length, 0); + ChangeTextBoxText(newMode.Text ?? string.Empty); - VisualStateManager.GoToState(CurrentSelectedMode, "Focused", true); - CurrentSelectedMode.OnChangingCurrentMode(true); - - if (IsFocused) - { - VisualStateManager.GoToState(CurrentSelectedMode, "Focused", true); - VisualStateManager.GoToState(_textBox, "InputAreaVisible", true); - } - else if (CurrentSelectedMode?.ContentOnInactive is not null) + VisualStateManager.GoToState(newMode, "Focused", true); + newMode.IsTabStop = false; + if (newMode.IsAutoFocusEnabled) { - VisualStateManager.GoToState(CurrentSelectedMode, "CurrentUnfocused", true); - VisualStateManager.GoToState(_textBox, "InputAreaCollapsed", true); + _textBox.Focus(FocusState.Pointer); } else { - VisualStateManager.GoToState(_textBox, "InputAreaVisible", true); - } - - if (shouldFocus) - _textBox.Focus(FocusState.Keyboard); + if (IsFocused) + { + VisualStateManager.GoToState(newMode, "Focused", true); + VisualStateManager.GoToState(_textBox, "InputAreaVisible", true); + } + else if (newMode?.ContentOnInactive is not null) + { + VisualStateManager.GoToState(newMode, "CurrentUnfocused", true); + VisualStateManager.GoToState(_textBox, "InputAreaCollapsed", true); + } + else + { + VisualStateManager.GoToState(_textBox, "InputAreaVisible", true); + } - TryToggleIsSuggestionsPopupOpen(IsFocused && CurrentSelectedMode?.SuggestionItemsSource is not null); + TryToggleIsSuggestionsPopupOpen(true); + } // Remove the reposition transition from the all modes - if (useTransition) + foreach (var mode in Modes) { - foreach (var mode in Modes) - { - mode.Transitions.Clear(); - mode.UpdateLayout(); - } + mode.Transitions.Clear(); + mode.UpdateLayout(); } } + internal protected void FocusTextBox() + { + _textBox.Focus(FocusState.Keyboard); + } + public bool TryToggleIsSuggestionsPopupOpen(bool wantToOpen) { - if (wantToOpen && (!IsFocused || CurrentSelectedMode?.SuggestionItemsSource is null || (CurrentSelectedMode?.SuggestionItemsSource is IList collection && collection.Count is 0))) + if (wantToOpen && (!IsFocused || CurrentSelectedMode?.SuggestionItemsSource is null || (CurrentSelectedMode?.SuggestionItemsSource is IList collection && collection.Count is 0)) || + _textBoxSuggestionsPopup is null) return false; _textBoxSuggestionsPopup.IsOpen = wantToOpen; diff --git a/src/Files.App.Controls/Omnibar/OmnibarMode.Events.cs b/src/Files.App.Controls/Omnibar/OmnibarMode.Events.cs index cf0be5627114..e7f73fd5262c 100644 --- a/src/Files.App.Controls/Omnibar/OmnibarMode.Events.cs +++ b/src/Files.App.Controls/Omnibar/OmnibarMode.Events.cs @@ -31,7 +31,7 @@ private void ModeButton_PointerReleased(object sender, PointerRoutedEventArgs e) VisualStateManager.GoToState(this, "PointerOver", true); // Change the current mode - owner.ChangeMode(this); + owner.CurrentSelectedMode = this; } private void ModeButton_PointerExited(object sender, PointerRoutedEventArgs e) diff --git a/src/Files.App.Controls/Omnibar/OmnibarMode.Properties.cs b/src/Files.App.Controls/Omnibar/OmnibarMode.Properties.cs index 9a7a234e4472..dbac55f6ca1f 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] + public partial bool IsAutoFocusEnabled { get; set; } + partial void OnTextChanged(string? newValue) { if (_ownerRef is null || _ownerRef.TryGetTarget(out var owner) is false) diff --git a/src/Files.App.Controls/Omnibar/OmnibarMode.cs b/src/Files.App.Controls/Omnibar/OmnibarMode.cs index 3fa09c416018..b4d5927383a9 100644 --- a/src/Files.App.Controls/Omnibar/OmnibarMode.cs +++ b/src/Files.App.Controls/Omnibar/OmnibarMode.cs @@ -54,7 +54,8 @@ protected override void OnKeyUp(KeyRoutedEventArgs args) VisualStateManager.GoToState(this, "PointerPressed", true); // Change the current mode - owner.ChangeMode(this, true); + owner.CurrentSelectedMode = this; + owner.FocusTextBox(); VisualStateManager.GoToState(this, "PointerNormal", true); } @@ -69,7 +70,7 @@ private void OmnibarMode_Loaded(object sender, RoutedEventArgs e) { // Set this mode as the current mode if it is the default mode if (IsDefault && _ownerRef is not null && _ownerRef.TryGetTarget(out var owner)) - owner.ChangeMode(this); + owner.CurrentSelectedMode = this; } public void SetOwner(Omnibar owner) @@ -77,11 +78,6 @@ public void SetOwner(Omnibar owner) _ownerRef = new(owner); } - public void OnChangingCurrentMode(bool isCurrentMode) - { - _modeButton.IsTabStop = !isCurrentMode; - } - public override string ToString() { return ModeName ?? string.Empty; diff --git a/src/Files.App/Actions/Global/EditPathAction.cs b/src/Files.App/Actions/Global/EditPathAction.cs index 47584a594d34..1f4ce2013182 100644 --- a/src/Files.App/Actions/Global/EditPathAction.cs +++ b/src/Files.App/Actions/Global/EditPathAction.cs @@ -5,7 +5,8 @@ namespace Files.App.Actions { internal sealed class EditPathAction : IAction { - private readonly IContentPageContext context; + private readonly IContentPageContext context = Ioc.Default.GetRequiredService(); + private readonly IGeneralSettingsService GeneralSettingsService = Ioc.Default.GetRequiredService(); public string Label => Strings.EditPath.GetLocalizedResource(); @@ -21,13 +22,18 @@ public HotKey SecondHotKey public EditPathAction() { - context = Ioc.Default.GetRequiredService(); + } public Task ExecuteAsync(object? parameter = null) { if (context.ShellPage is not null) - context.ShellPage.ToolbarViewModel.IsEditModeEnabled = true; + { + if (GeneralSettingsService.EnableOmnibar) + context.ShellPage!.ToolbarViewModel.SwitchToPathMode(); + else + context.ShellPage.ToolbarViewModel.IsEditModeEnabled = true; + } return Task.CompletedTask; } diff --git a/src/Files.App/Actions/Global/SearchAction.cs b/src/Files.App/Actions/Global/SearchAction.cs index 0a47ed957d0a..77ad67222e59 100644 --- a/src/Files.App/Actions/Global/SearchAction.cs +++ b/src/Files.App/Actions/Global/SearchAction.cs @@ -34,7 +34,7 @@ public SearchAction() public Task ExecuteAsync(object? parameter = null) { - context.ShellPage!.ToolbarViewModel.SwitchSearchBoxVisibility(); + context.ShellPage!.ToolbarViewModel.SwitchToSearchMode(); return Task.CompletedTask; } diff --git a/src/Files.App/Actions/Open/OpenCommandPaletteAction.cs b/src/Files.App/Actions/Open/OpenCommandPaletteAction.cs index 723f4c8b21cd..e39b27161a7a 100644 --- a/src/Files.App/Actions/Open/OpenCommandPaletteAction.cs +++ b/src/Files.App/Actions/Open/OpenCommandPaletteAction.cs @@ -23,7 +23,7 @@ public OpenCommandPaletteAction() public Task ExecuteAsync(object? parameter = null) { - _context.ShellPage?.ToolbarViewModel.OpenCommandPalette(); + _context.ShellPage?.ToolbarViewModel.SwitchToCommandPaletteMode(); return Task.CompletedTask; } diff --git a/src/Files.App/Data/Contracts/IAddressToolbarViewModel.cs b/src/Files.App/Data/Contracts/IAddressToolbarViewModel.cs index 051a93829511..42a5f0b7637c 100644 --- a/src/Files.App/Data/Contracts/IAddressToolbarViewModel.cs +++ b/src/Files.App/Data/Contracts/IAddressToolbarViewModel.cs @@ -42,7 +42,7 @@ public interface IAddressToolbarViewModel public event EventHandler RefreshWidgetsRequested; - public void SwitchSearchBoxVisibility(); + public void SwitchToSearchMode(); public ISearchBoxViewModel SearchBox { get; } } diff --git a/src/Files.App/UserControls/NavigationToolbar.xaml b/src/Files.App/UserControls/NavigationToolbar.xaml index 93bacf529eea..47bd988b4cfa 100644 --- a/src/Files.App/UserControls/NavigationToolbar.xaml +++ b/src/Files.App/UserControls/NavigationToolbar.xaml @@ -321,9 +321,9 @@ Grid.Column="1" x:Load="{x:Bind ViewModel.EnableOmnibar, Mode=OneWay}" CurrentSelectedMode="{x:Bind ViewModel.OmnibarCurrentSelectedMode, Mode=TwoWay}" + CurrentSelectedModeName="{x:Bind ViewModel.OmnibarCurrentSelectedModeName, Mode=TwoWay}" IsFocused="{x:Bind ViewModel.IsOmnibarFocused, Mode=TwoWay}" QuerySubmitted="Omnibar_QuerySubmitted" - SuggestionChosen="Omnibar_SuggestionChosen" TextChanged="Omnibar_TextChanged"> diff --git a/src/Files.App/UserControls/NavigationToolbar.xaml.cs b/src/Files.App/UserControls/NavigationToolbar.xaml.cs index 76d3b353459a..1a42b1d0c61f 100644 --- a/src/Files.App/UserControls/NavigationToolbar.xaml.cs +++ b/src/Files.App/UserControls/NavigationToolbar.xaml.cs @@ -262,23 +262,7 @@ private async void Omnibar_QuerySubmitted(Omnibar sender, OmnibarQuerySubmittedE } else if (Omnibar.CurrentSelectedMode == OmnibarCommandPaletteMode) { - } - else if (Omnibar.CurrentSelectedMode == OmnibarSearchMode) - { - } - } - - private async void Omnibar_SuggestionChosen(Omnibar sender, OmnibarSuggestionChosenEventArgs args) - { - if (Omnibar.CurrentSelectedMode == OmnibarPathMode) - { - if (args.SelectedItem is OmnibarPathModeSuggestionModel item && - !string.IsNullOrEmpty(item.Path)) - await ViewModel.HandleItemNavigationAsync(item.Path); - } - else if (Omnibar.CurrentSelectedMode == OmnibarCommandPaletteMode) - { - if (args.SelectedItem is not NavigationBarSuggestionItem item || item.Text is not { } commandText) + if (args.Item is not NavigationBarSuggestionItem item || item.Text is not { } commandText) return; var command = Commands[commandText]; @@ -291,7 +275,7 @@ await DialogDisplayHelper.ShowDialogAsync(Strings.CommandNotExecutable.GetLocali else await command.ExecuteAsync(); - Omnibar.ChangeMode(OmnibarPathMode); + ViewModel.OmnibarCurrentSelectedMode = OmnibarPathMode; } else if (Omnibar.CurrentSelectedMode == OmnibarSearchMode) { @@ -300,6 +284,9 @@ await DialogDisplayHelper.ShowDialogAsync(Strings.CommandNotExecutable.GetLocali private async void Omnibar_TextChanged(Omnibar sender, OmnibarTextChangedEventArgs args) { + if (args.Reason is not OmnibarTextChangeReason.UserInput) + return; + if (Omnibar.CurrentSelectedMode == OmnibarPathMode) { await ViewModel.PopulateOmnibarSuggestionsForPathMode(); diff --git a/src/Files.App/ViewModels/UserControls/NavigationToolbarViewModel.cs b/src/Files.App/ViewModels/UserControls/NavigationToolbarViewModel.cs index 798c63b0cd23..b596888f2c54 100644 --- a/src/Files.App/ViewModels/UserControls/NavigationToolbarViewModel.cs +++ b/src/Files.App/ViewModels/UserControls/NavigationToolbarViewModel.cs @@ -2,7 +2,6 @@ // Licensed under the MIT License. using CommunityToolkit.WinUI; -using Files.App.Actions; using Files.App.Controls; using Files.Shared.Helpers; using Microsoft.UI.Dispatching; @@ -155,7 +154,7 @@ public sealed partial class NavigationToolbarViewModel : ObservableObject, IAddr private bool _IsDynamicOverflowEnabled; public bool IsDynamicOverflowEnabled { get => _IsDynamicOverflowEnabled; set => SetProperty(ref _IsDynamicOverflowEnabled, value); } - + private bool _IsUpdating; public bool IsUpdating { get => _IsUpdating; set => SetProperty(ref _IsUpdating, value); } @@ -228,34 +227,34 @@ public string? PathText public string? OmnibarCommandPaletteModeText { get => _OmnibarCommandPaletteModeText; set => SetProperty(ref _OmnibarCommandPaletteModeText, value); } private bool _IsOmnibarFocused; - public bool IsOmnibarFocused + public bool IsOmnibarFocused { get => _IsOmnibarFocused; set { - if (SetProperty(ref _IsOmnibarFocused, value)) + // NOTE: Don't call ObservableObject.SetProperty() here since we don't want to change focus logic outside of the control. + + _IsOmnibarFocused = value; + + if (value) { - if (value) + switch (OmnibarCurrentSelectedMode.Name) { - switch(OmnibarCurrentSelectedMode.Name) - { - case OmnibarPathModeName: - PathText = - string.IsNullOrEmpty(ContentPageContext.ShellPage?.ShellViewModel?.WorkingDirectory) - ? Constants.UserEnvironmentPaths.HomePath - : ContentPageContext.ShellPage.ShellViewModel.WorkingDirectory; - _ = PopulateOmnibarSuggestionsForPathMode(); - break; - case OmnibarPaletteModeName: - if (OmnibarCommandPaletteModeSuggestionItems.Count is 0) - PopulateOmnibarSuggestionsForCommandPaletteMode(); - break; - case OmnibarSearchModeName: - break; - default: - break; - } - + case OmnibarPathModeName: + PathText = + string.IsNullOrEmpty(ContentPageContext.ShellPage?.ShellViewModel?.WorkingDirectory) + ? Constants.UserEnvironmentPaths.HomePath + : ContentPageContext.ShellPage.ShellViewModel.WorkingDirectory; + _ = PopulateOmnibarSuggestionsForPathMode(); + break; + case OmnibarPaletteModeName: + if (OmnibarCommandPaletteModeSuggestionItems.Count is 0) + PopulateOmnibarSuggestionsForCommandPaletteMode(); + break; + case OmnibarSearchModeName: + break; + default: + break; } } } @@ -264,6 +263,9 @@ public bool IsOmnibarFocused private OmnibarMode _OmnibarCurrentSelectedMode; public OmnibarMode OmnibarCurrentSelectedMode { get => _OmnibarCurrentSelectedMode; set => SetProperty(ref _OmnibarCurrentSelectedMode, value); } + private string _OmnibarCurrentSelectedModeName; + public string OmnibarCurrentSelectedModeName { get => _OmnibarCurrentSelectedModeName; set => SetProperty(ref _OmnibarCurrentSelectedModeName, value); } + private CurrentInstanceViewModel _InstanceViewModel; public CurrentInstanceViewModel InstanceViewModel { @@ -741,59 +743,76 @@ public void PathBoxItem_PreviewKeyDown(object sender, KeyRoutedEventArgs e) switch (e.Key) { case Windows.System.VirtualKey.Down: - { - var item = e.OriginalSource as ListViewItem; - var button = item?.FindDescendant