Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion src/Files.App/Actions/Navigation/NextTabAction.cs
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ namespace Files.App.Actions
internal sealed partial class NextTabAction : ObservableObject, IAction
{
private readonly IMultitaskingContext multitaskingContext;
private readonly IContentPageContext contentPageContext = Ioc.Default.GetRequiredService<IContentPageContext>();

public string Label
=> Strings.NextTab.GetLocalizedResource();
Expand Down Expand Up @@ -37,7 +38,7 @@ public async Task ExecuteAsync(object? parameter = null)
await Task.Delay(500);

// Focus the content of the selected tab item (needed for keyboard navigation)
(multitaskingContext.CurrentTabItem.TabItemContent as Control)?.Focus(FocusState.Programmatic);
contentPageContext.ShellPage!.PaneHolder.FocusActivePane();
}

private void MultitaskingContext_PropertyChanged(object? sender, PropertyChangedEventArgs e)
Expand Down
3 changes: 2 additions & 1 deletion src/Files.App/Actions/Navigation/PreviousTabAction.cs
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ namespace Files.App.Actions
internal sealed partial class PreviousTabAction : ObservableObject, IAction
{
private readonly IMultitaskingContext multitaskingContext;
private readonly IContentPageContext contentPageContext = Ioc.Default.GetRequiredService<IContentPageContext>();

public string Label
=> Strings.PreviousTab.GetLocalizedResource();
Expand Down Expand Up @@ -40,7 +41,7 @@ public async Task ExecuteAsync(object? parameter = null)
await Task.Delay(500);

// Focus the content of the selected tab item (needed for keyboard navigation)
(multitaskingContext.CurrentTabItem.TabItemContent as Control)?.Focus(FocusState.Programmatic);
contentPageContext.ShellPage!.PaneHolder.FocusActivePane();
}

private void MultitaskingContext_PropertyChanged(object? sender, PropertyChangedEventArgs e)
Expand Down
5 changes: 5 additions & 0 deletions src/Files.App/Data/Contracts/IShellPanesPage.cs
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,11 @@ public interface IShellPanesPage : IDisposable, INotifyPropertyChanged
/// </summary>
public void FocusActivePane();

/// <summary>
/// Locks the active pane.
/// </summary>
public void LockActivePane();

/// <summary>
/// Gets open panes.
/// </summary>
Expand Down
2 changes: 1 addition & 1 deletion src/Files.App/UserControls/TabBar/TabBar.xaml
Original file line number Diff line number Diff line change
Expand Up @@ -145,7 +145,7 @@
x:Name="SplitPaneMenuItem"
x:Load="{x:Bind Commands.SplitPaneHorizontally.IsExecutable, Mode=OneWay}"
Text="{helpers:ResourceString Name=SplitPane}">
<MenuFlyoutSubItem.Items>
<MenuFlyoutSubItem.Items>
<!-- Vertical -->
<uc:MenuFlyoutItemWithThemedIcon
x:Name="AddVerticalPaneTabActionButton"
Expand Down
10 changes: 9 additions & 1 deletion src/Files.App/UserControls/Toolbar.xaml
Original file line number Diff line number Diff line change
Expand Up @@ -687,7 +687,10 @@
<controls:ThemedIcon Style="{x:Bind ViewModel.LayoutThemedIcon, Mode=OneWay}" />

<AppBarButton.Flyout>
<Flyout contract8Present:ShouldConstrainToRootBounds="False" Placement="Bottom">
<Flyout
x:Name="LayoutFlyout"
contract8Present:ShouldConstrainToRootBounds="False"
Placement="Bottom">
<StackPanel Spacing="12">

<!-- Header -->
Expand All @@ -699,6 +702,7 @@
<!-- Details -->
<RadioButton
AutomationProperties.Name="{x:Bind Commands.LayoutDetails.AutomationName}"
Click="LayoutButton_Click"
Command="{x:Bind Commands.LayoutDetails}"
GroupName="LayoutRadio"
IsChecked="{x:Bind ViewModel.IsDetailsLayout, Mode=OneWay}"
Expand All @@ -722,6 +726,7 @@
<!-- List -->
<RadioButton
AutomationProperties.Name="{x:Bind Commands.LayoutList.AutomationName}"
Click="LayoutButton_Click"
Command="{x:Bind Commands.LayoutList}"
GroupName="LayoutRadio"
IsChecked="{x:Bind ViewModel.IsListLayout, Mode=OneWay}"
Expand All @@ -746,6 +751,7 @@
<!-- Cards -->
<RadioButton
AutomationProperties.Name="{x:Bind Commands.LayoutCards.AutomationName}"
Click="LayoutButton_Click"
Command="{x:Bind Commands.LayoutCards}"
GroupName="LayoutRadio"
IsChecked="{x:Bind ViewModel.IsCardsLayout, Mode=OneWay}"
Expand All @@ -770,6 +776,7 @@
<!-- Grid -->
<RadioButton
AutomationProperties.Name="{x:Bind Commands.LayoutGrid.AutomationName}"
Click="LayoutButton_Click"
Command="{x:Bind Commands.LayoutGrid}"
GroupName="LayoutRadio"
IsChecked="{x:Bind ViewModel.IsGridLayout, Mode=OneWay}"
Expand All @@ -794,6 +801,7 @@
<!-- Columns -->
<RadioButton
AutomationProperties.Name="{x:Bind Commands.LayoutColumns.AutomationName}"
Click="LayoutButton_Click"
Command="{x:Bind Commands.LayoutColumns}"
GroupName="LayoutRadio"
IsChecked="{x:Bind ViewModel.IsColumnLayout, Mode=OneWay}"
Expand Down
6 changes: 6 additions & 0 deletions src/Files.App/UserControls/Toolbar.xaml.cs
Original file line number Diff line number Diff line change
Expand Up @@ -108,5 +108,11 @@ private void AppBarButton_AccessKeyInvoked(UIElement sender, AccessKeyInvokedEve
if (VisualTreeHelper.GetOpenPopupsForXamlRoot(MainWindow.Instance.Content.XamlRoot).Any())
args.Handled = true;
}

private void LayoutButton_Click(object sender, RoutedEventArgs e)
{
// Hide flyout after choosing a layout
LayoutFlyout.Hide();
}
}
}
2 changes: 1 addition & 1 deletion src/Files.App/ViewModels/MainPageViewModel.cs
Original file line number Diff line number Diff line change
Expand Up @@ -391,7 +391,7 @@ private async void ExecuteNavigateToNumberedTabKeyboardAcceleratorCommand(Keyboa
await Task.Delay(500);

// Focus the content of the selected tab item (needed for keyboard navigation)
(SelectedTabItem?.TabItemContent as Control)?.Focus(FocusState.Programmatic);
context.ShellPage!.PaneHolder.FocusActivePane();
}

e.Handled = true;
Expand Down
7 changes: 7 additions & 0 deletions src/Files.App/ViewModels/ShellViewModel.cs
Original file line number Diff line number Diff line change
Expand Up @@ -584,6 +584,13 @@ public ShellViewModel(LayoutPreferencesManager folderSettingsViewModel)

private async void LayoutModeChangeRequested(object? sender, LayoutModeEventArgs e)
{
// Layout changes can cause the active pane to lose focus. To prevent this,
// the pane is locked here and focus is restored when file loading completes
// in the RefreshItem() method in BaseLayoutPage.cs.
// See https://github.com/files-community/Files/issues/15397
// See https://github.com/files-community/Files/issues/16530
ContentPageContext.ShellPage!.PaneHolder.LockActivePane();

await dispatcherQueue.EnqueueOrInvokeAsync(CheckForBackgroundImage, Microsoft.UI.Dispatching.DispatcherQueuePriority.Low);
}

Expand Down
10 changes: 8 additions & 2 deletions src/Files.App/Views/Layouts/BaseLayoutPage.cs
Original file line number Diff line number Diff line change
Expand Up @@ -1240,8 +1240,14 @@ private void RefreshItem(SelectorItem container, object item, bool inRecycleQueu
if (ParentShellPageInstance.ShellViewModel.EnabledGitProperties is not GitProperties.None && listedItem is IGitItem gitItem)
await ParentShellPageInstance.ShellViewModel.LoadGitPropertiesAsync(gitItem);

// Focus file list when items finish loading (#16530)
ItemManipulationModel.FocusFileList();
// Layout changes can cause the active pane to lose focus. To prevent this,
// the pane is locked in LayoutModeChangeRequested() and focus is restored here
// when file loading completes.
// See https://github.com/files-community/Files/issues/15397
// See https://github.com/files-community/Files/issues/16530

if (ParentShellPageInstance.IsCurrentPane && !ParentShellPageInstance.IsColumnView)
ItemManipulationModel.FocusFileList();
});
}
}
Expand Down
1 change: 1 addition & 0 deletions src/Files.App/Views/MainPage.xaml
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
KeyboardAcceleratorPlacementMode="Hidden"
Loaded="Page_Loaded"
NavigationCacheMode="Required"
PointerReleased="Page_PointerReleased"
SizeChanged="Page_SizeChanged"
mc:Ignorable="d">

Expand Down
8 changes: 7 additions & 1 deletion src/Files.App/Views/MainPage.xaml.cs
Original file line number Diff line number Diff line change
Expand Up @@ -162,7 +162,6 @@ public async void MultitaskingControl_CurrentInstanceChanged(object? sender, Cur
// Focus the content of the selected tab item (this also avoids an issue where the Omnibar sometimes steals the focus)
await Task.Delay(100);
ContentPageContext.ShellPage!.PaneHolder.FocusActivePane();

}

private void PaneHolder_PropertyChanged(object? sender, PropertyChangedEventArgs e)
Expand Down Expand Up @@ -477,5 +476,12 @@ private void SettingsButton_AccessKeyInvoked(UIElement sender, AccessKeyInvokedE
if (VisualTreeHelper.GetOpenPopupsForXamlRoot(MainWindow.Instance.Content.XamlRoot).Any())
args.Handled = true;
}

private void Page_PointerReleased(object sender, PointerRoutedEventArgs e)
{
// Workaround for issue where clicking an empty area in the window (toolbar, title bar etc) prevents keyboard
// shortcuts from working properly, see https://github.com/microsoft/microsoft-ui-xaml/issues/6467
DispatcherQueue.TryEnqueue(() => ContentPageContext.ShellPage?.PaneHolder.FocusActivePane());
}
}
}
33 changes: 31 additions & 2 deletions src/Files.App/Views/ShellPanesPage.xaml.cs
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ public sealed partial class ShellPanesPage : Page, IShellPanesPage, ITabBarItemC
// Dependency injections

private IGeneralSettingsService GeneralSettingsService { get; } = Ioc.Default.GetRequiredService<IGeneralSettingsService>();
private IContentPageContext ContentPageContext { get; } = Ioc.Default.GetRequiredService<IContentPageContext>();
private AppModel AppModel { get; } = Ioc.Default.GetRequiredService<AppModel>();

// Constants
Expand Down Expand Up @@ -228,6 +229,17 @@ public bool IsCurrentInstance
}
}

private bool _IsActivePaneLocked;
public bool IsActivePaneLocked
{
get => _IsActivePaneLocked;
set
{
if (_IsActivePaneLocked != value)
_IsActivePaneLocked = value;
}
}

// Events

public static event EventHandler<ShellPanesPage>? CurrentInstanceChanged;
Expand Down Expand Up @@ -376,6 +388,16 @@ public void FocusActivePane()
GetPane(0)?.Focus(FocusState.Programmatic);
else
GetPane(1)?.Focus(FocusState.Programmatic);

// Focus file list
if (ActivePane is BaseShellPage baseShellPage)
baseShellPage.ContentPage?.ItemManipulationModel.FocusFileList();
}

/// <inheritdoc/>
public void LockActivePane()
{
IsActivePaneLocked = true;
}

/// <inheritdoc/>
Expand Down Expand Up @@ -656,9 +678,16 @@ private void Pane_Loaded(object sender, RoutedEventArgs e)

private void Pane_GettingFocus(UIElement sender, GettingFocusEventArgs args)
{
// Workaround for https://github.com/files-community/Files/issues/15397
if (args?.NewFocusedElement is not null && args.NewFocusedElement is not (ListViewItem or GridViewItem or ListView or GridView or TextBox))
// Cancel focus attempts while the active pane is locked during layout changes.
// Pane locking occurs in LayoutModeChangeRequested() in ShellViewModel.cs.
// Focus is restored in RefreshItem() in BaseLayoutPage.cs when file loading completes.
// See https://github.com/files-community/Files/issues/15397
// See https://github.com/files-community/Files/issues/16530
if (IsActivePaneLocked)
{
IsActivePaneLocked = false;
args.TryCancel();
}
}

private void Pane_ContentChanged(object? sender, TabBarItemParameter e)
Expand Down
Loading