Skip to content
Merged
Show file tree
Hide file tree
Changes from 3 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());
}
}
}
28 changes: 26 additions & 2 deletions src/Files.App/Views/ShellPanesPage.xaml.cs
Original file line number Diff line number Diff line change
Expand Up @@ -228,6 +228,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 @@ -378,6 +389,12 @@ public void FocusActivePane()
GetPane(1)?.Focus(FocusState.Programmatic);
}

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

/// <inheritdoc/>
public IEnumerable<ModernShellPage> GetPanes()
{
Expand Down Expand Up @@ -656,9 +673,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