Skip to content

Commit 6946095

Browse files
committed
POC
1 parent 44b4ce5 commit 6946095

File tree

12 files changed

+189
-11
lines changed

12 files changed

+189
-11
lines changed

src/Files.App/Actions/Show/ToggleFilterHeaderAction.cs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,11 @@ public Task ExecuteAsync(object? parameter = null)
3131

3232
if (IsOn)
3333
ContentPageContext.ShellPage!.ShellViewModel.InvokeFocusFilterHeader();
34+
else
35+
{
36+
// Clear the filter query when the header is hidden
37+
ContentPageContext.ShellPage!.ShellViewModel.FilesAndFoldersFilter = string.Empty;
38+
}
3439

3540
return Task.CompletedTask;
3641
}

src/Files.App/Data/Contracts/IFoldersSettingsService.cs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -89,5 +89,10 @@ public interface IFoldersSettingsService : IBaseSettingsService, INotifyProperty
8989
/// Gets or sets a value indicating which format to use when displaying item sizes.
9090
/// </summary>
9191
SizeUnitTypes SizeUnitFormat { get; set; }
92+
93+
/// <summary>
94+
/// Gets or sets a value indicating the keyboard typing behavior.
95+
/// </summary>
96+
KeyboardTypingBehavior KeyboardTypingBehavior { get; set; }
9297
}
9398
}
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
// Copyright (c) Files Community
2+
// Licensed under the MIT License.
3+
4+
namespace Files.App.Data.Enums
5+
{
6+
public enum KeyboardTypingBehavior
7+
{
8+
/// <summary>
9+
/// Jump to matching item.
10+
/// </summary>
11+
JumpToFile,
12+
13+
/// <summary>
14+
/// Filter items.
15+
/// </summary>
16+
FilterItems
17+
}
18+
}

src/Files.App/Services/Settings/FoldersSettingsService.cs

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -114,6 +114,13 @@ public SizeUnitTypes SizeUnitFormat
114114
set => Set(value);
115115
}
116116

117+
/// <inheritdoc/>
118+
public KeyboardTypingBehavior KeyboardTypingBehavior
119+
{
120+
get => (KeyboardTypingBehavior)Get((long)KeyboardTypingBehavior.JumpToFile);
121+
set => Set((long)value);
122+
}
123+
117124
protected override void RaiseOnSettingChangedEvent(object sender, SettingChangedEventArgs e)
118125
{
119126
base.RaiseOnSettingChangedEvent(sender, e);

src/Files.App/Strings/en-US/Resources.resw

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4272,5 +4272,14 @@
42724272
</data>
42734273
<data name="Filename" xml:space="preserve">
42744274
<value>Filename</value>
4275+
</data>
4276+
<data name="KeyboardTypingBehavior" xml:space="preserve">
4277+
<value>Behavior when typing in the file area</value>
4278+
</data>
4279+
<data name="JumpToFile" xml:space="preserve">
4280+
<value>Jump to file</value>
4281+
</data>
4282+
<data name="FilterItems" xml:space="preserve">
4283+
<value>Filter items</value>
42754284
</data>
42764285
</root>

src/Files.App/ViewModels/Settings/AdvancedViewModel.cs

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ public sealed partial class AdvancedViewModel : ObservableObject
2828
public ICommand ImportSettingsCommand { get; }
2929
public AsyncRelayCommand OpenFilesOnWindowsStartupCommand { get; }
3030

31+
public Dictionary<KeyboardTypingBehavior, string> KeyboardTypingBehaviors { get; private set; } = [];
3132

3233
public AdvancedViewModel()
3334
{
@@ -40,6 +41,11 @@ public AdvancedViewModel()
4041
ImportSettingsCommand = new AsyncRelayCommand(ImportSettingsAsync);
4142
OpenFilesOnWindowsStartupCommand = new AsyncRelayCommand(OpenFilesOnWindowsStartupAsync);
4243

44+
// Keyboard typing behavior
45+
KeyboardTypingBehaviors.Add(Data.Enums.KeyboardTypingBehavior.JumpToFile, Strings.JumpToFile.GetLocalizedResource());
46+
KeyboardTypingBehaviors.Add(Data.Enums.KeyboardTypingBehavior.FilterItems, Strings.FilterItems.GetLocalizedResource());
47+
KeyboardTypingBehavior = KeyboardTypingBehaviors[UserSettingsService.FoldersSettingsService.KeyboardTypingBehavior];
48+
4349
_ = DetectOpenFilesAtStartupAsync();
4450
}
4551

@@ -354,6 +360,20 @@ public bool ShowFlattenOptions
354360
OnPropertyChanged();
355361
}
356362
}
363+
364+
private string keyboardTypingBehavior;
365+
public string KeyboardTypingBehavior
366+
{
367+
get => keyboardTypingBehavior;
368+
set
369+
{
370+
if (SetProperty(ref keyboardTypingBehavior, value))
371+
{
372+
UserSettingsService.FoldersSettingsService.KeyboardTypingBehavior = KeyboardTypingBehaviors.First(e => e.Value == value).Key;
373+
}
374+
}
375+
}
376+
357377
public async Task OpenFilesOnWindowsStartupAsync()
358378
{
359379
var stateMode = await ReadState();

src/Files.App/ViewModels/ShellViewModel.cs

Lines changed: 23 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -754,6 +754,7 @@ public string? FilesAndFoldersFilter
754754
{
755755
if (SetProperty(ref _filesAndFoldersFilter, value))
756756
{
757+
// Apply the updated filter to the files and folders list
757758
FilesAndFolderFilterUpdated();
758759
}
759760
}
@@ -764,6 +765,27 @@ private void FilesAndFolderFilterUpdated()
764765
_ = ApplyFilesAndFoldersChangesAsync();
765766
}
766767

768+
/// <summary>
769+
/// Clears the files and folder filter.
770+
/// This is used when the directory is changed or refreshed.
771+
/// </summary>
772+
private void ClearFilesAndFolderFilter()
773+
{
774+
// Hide the filter header if:
775+
// - Keyboard behavior is set to filter items
776+
// - A filter is currently applied
777+
//
778+
// Keep the header visible if:
779+
// - The filter is already empty (e.g. opened manually)
780+
if (UserSettingsService.FoldersSettingsService.KeyboardTypingBehavior == KeyboardTypingBehavior.FilterItems &&
781+
!string.IsNullOrEmpty(FilesAndFoldersFilter))
782+
{
783+
UserSettingsService.GeneralSettingsService.ShowFilterHeader = false;
784+
}
785+
786+
// Clear the filter
787+
FilesAndFoldersFilter = string.Empty;
788+
}
767789

768790
// Apply changes immediately after manipulating on filesAndFolders completed
769791
public async Task ApplyFilesAndFoldersChangesAsync()
@@ -1886,7 +1908,7 @@ await dispatcherQueue.EnqueueOrInvokeAsync(() =>
18861908
{
18871909
GetDesktopIniFileData();
18881910
CheckForBackgroundImage();
1889-
FilesAndFoldersFilter = null;
1911+
ClearFilesAndFolderFilter();
18901912
},
18911913
Microsoft.UI.Dispatching.DispatcherQueuePriority.Low);
18921914
});

src/Files.App/Views/Layouts/BaseGroupableLayoutPage.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -95,7 +95,7 @@ protected override void UnhookEvents()
9595
ItemManipulationModel.RefreshItemsThumbnailInvoked -= ItemManipulationModel_RefreshItemsThumbnail;
9696
}
9797

98-
protected override void Page_CharacterReceived(UIElement sender, CharacterReceivedRoutedEventArgs args)
98+
protected override void Page_PreviewKeyDown(object sender, KeyRoutedEventArgs e)
9999
{
100100
if (ParentShellPageInstance is null ||
101101
ParentShellPageInstance.CurrentPageType != this.GetType() ||
@@ -112,7 +112,7 @@ focusedElement is PasswordBox ||
112112
DependencyObjectHelpers.FindParent<ContentDialog>(focusedElement) is not null)
113113
return;
114114

115-
base.Page_CharacterReceived(sender, args);
115+
base.Page_PreviewKeyDown(sender, e);
116116
}
117117

118118
// Virtual methods

src/Files.App/Views/Layouts/BaseLayoutPage.cs

Lines changed: 80 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
using System.IO;
1616
using System.Runtime.CompilerServices;
1717
using System.Runtime.InteropServices.ComTypes;
18+
using System.Text;
1819
using Vanara.Extensions;
1920
using Vanara.PInvoke;
2021
using Windows.ApplicationModel.DataTransfer;
@@ -23,6 +24,7 @@
2324
using Windows.Foundation.Collections;
2425
using Windows.Storage;
2526
using Windows.System;
27+
using Windows.Win32;
2628
using static Files.App.Helpers.PathNormalization;
2729
using DispatcherQueueTimer = Microsoft.UI.Dispatching.DispatcherQueueTimer;
2830
using SortDirection = Files.App.Data.Enums.SortDirection;
@@ -40,6 +42,8 @@ public abstract class BaseLayoutPage : Page, IBaseLayoutPage, INotifyPropertyCha
4042
protected IFileTagsSettingsService FileTagsSettingsService { get; } = Ioc.Default.GetService<IFileTagsSettingsService>()!;
4143
protected IUserSettingsService UserSettingsService { get; } = Ioc.Default.GetService<IUserSettingsService>()!;
4244
protected ILayoutSettingsService LayoutSettingsService { get; } = Ioc.Default.GetService<ILayoutSettingsService>()!;
45+
protected IGeneralSettingsService GeneralSettingsService { get; } = Ioc.Default.GetService<IGeneralSettingsService>()!;
46+
protected IFoldersSettingsService FoldersSettingsService { get; } = Ioc.Default.GetService<IFoldersSettingsService>()!;
4347
protected ICommandManager Commands { get; } = Ioc.Default.GetRequiredService<ICommandManager>();
4448
public InfoPaneViewModel InfoPaneViewModel { get; } = Ioc.Default.GetRequiredService<InfoPaneViewModel>();
4549
protected readonly IWindowContext WindowContext = Ioc.Default.GetRequiredService<IWindowContext>();
@@ -401,7 +405,7 @@ protected override async void OnNavigatedTo(NavigationEventArgs e)
401405
base.OnNavigatedTo(e);
402406

403407
// Add item jumping handler
404-
CharacterReceived += Page_CharacterReceived;
408+
PreviewKeyDown += Page_PreviewKeyDown;
405409

406410
navigationArguments = (NavigationArguments)e.Parameter;
407411
ParentShellPageInstance = navigationArguments.AssociatedTabInstance;
@@ -565,7 +569,7 @@ protected override void OnNavigatingFrom(NavigatingCancelEventArgs e)
565569
base.OnNavigatingFrom(e);
566570

567571
// Remove item jumping handler
568-
CharacterReceived -= Page_CharacterReceived;
572+
PreviewKeyDown -= Page_PreviewKeyDown;
569573
FolderSettings!.LayoutModeChangeRequested -= BaseFolderSettings_LayoutModeChangeRequested;
570574
FolderSettings.GroupOptionPreferenceUpdated -= FolderSettings_GroupOptionPreferenceUpdated;
571575
FolderSettings.GroupDirectionPreferenceUpdated -= FolderSettings_GroupDirectionPreferenceUpdated;
@@ -996,12 +1000,82 @@ private void RemoveOverflow(CommandBarFlyout contextMenuFlyout)
9961000
overflowSeparator.Visibility = Visibility.Collapsed;
9971001
}
9981002

999-
protected virtual void Page_CharacterReceived(UIElement sender, CharacterReceivedRoutedEventArgs args)
1003+
protected virtual void Page_PreviewKeyDown(object sender, KeyRoutedEventArgs e)
10001004
{
1001-
if (ParentShellPageInstance!.IsCurrentInstance)
1005+
var shellPage = ParentShellPageInstance;
1006+
if (shellPage?.IsCurrentInstance != true)
1007+
return;
1008+
1009+
var pressedKey = e.Key;
1010+
var currentFilter = shellPage.ShellViewModel.FilesAndFoldersFilter ?? string.Empty;
1011+
var isFilterModeOn = FoldersSettingsService.KeyboardTypingBehavior == KeyboardTypingBehavior.FilterItems;
1012+
var buffer = new StringBuilder(4);
1013+
var state = new byte[256];
1014+
char? typedCharacter = null;
1015+
1016+
if (PInvoke.GetKeyboardState(state))
1017+
{
1018+
var virtualKey = (uint)pressedKey;
1019+
var scanCode = PInvoke.MapVirtualKey(virtualKey, 0);
1020+
var keyboardLayout = PInvoke.GetKeyboardLayout(0);
1021+
1022+
if (Win32PInvoke.ToUnicodeEx(virtualKey, scanCode, state, buffer, buffer.Capacity, 0, keyboardLayout) > 0)
1023+
{
1024+
var character = buffer[^1];
1025+
if (character != ' ')
1026+
typedCharacter = character;
1027+
}
1028+
}
1029+
1030+
if (!typedCharacter.HasValue)
1031+
return;
1032+
1033+
// Handle valid character input
1034+
if (!Path.GetInvalidFileNameChars().Contains(char.ToLowerInvariant(typedCharacter.Value)))
1035+
{
1036+
var lowerCharString = char.ToLowerInvariant(typedCharacter.Value).ToString();
1037+
1038+
if (isFilterModeOn)
1039+
{
1040+
if (!GeneralSettingsService.ShowFilterHeader)
1041+
GeneralSettingsService.ShowFilterHeader = true;
1042+
shellPage.ShellViewModel.FilesAndFoldersFilter += lowerCharString;
1043+
}
1044+
else
1045+
{
1046+
JumpString += lowerCharString;
1047+
}
1048+
}
1049+
// Handle special keys in filter mode
1050+
else if (isFilterModeOn && !string.IsNullOrEmpty(currentFilter))
1051+
{
1052+
switch (pressedKey)
1053+
{
1054+
case VirtualKey.Back when currentFilter.Length > 1:
1055+
shellPage.ShellViewModel.FilesAndFoldersFilter = currentFilter[..^1];
1056+
break;
1057+
1058+
case VirtualKey.Back when currentFilter.Length == 1:
1059+
shellPage.ShellViewModel.FilesAndFoldersFilter = string.Empty;
1060+
GeneralSettingsService.ShowFilterHeader = false;
1061+
break;
1062+
}
1063+
}
1064+
1065+
// Update selection in filter mode
1066+
if (isFilterModeOn)
10021067
{
1003-
char letter = args.Character;
1004-
JumpString += letter.ToString().ToLowerInvariant();
1068+
var filterText = shellPage.ShellViewModel.FilesAndFoldersFilter;
1069+
var matchedItem = shellPage.ShellViewModel.FilesAndFolders
1070+
.FirstOrDefault(item => !string.IsNullOrEmpty(filterText) &&
1071+
item.Name?.Contains(filterText, StringComparison.OrdinalIgnoreCase) == true);
1072+
1073+
if (matchedItem != null)
1074+
{
1075+
ItemManipulationModel.SetSelectedItem(matchedItem);
1076+
ItemManipulationModel.ScrollIntoView(matchedItem);
1077+
ItemManipulationModel.FocusSelectedItems();
1078+
}
10051079
}
10061080
}
10071081

src/Files.App/Views/MainPage.xaml.cs

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -216,6 +216,13 @@ private async Task OnPreviewKeyDownAsync(KeyRoutedEventArgs e)
216216
if (source?.FindAscendantOrSelf<TextBox>() is not null)
217217
break;
218218

219+
// Prevent the Back and Space keys from executing a command if the keyboard
220+
// typing behavior is set to filter items and a filter is currently applied.
221+
if ((e.Key is VirtualKey.Back or VirtualKey.Space) &&
222+
UserSettingsService.FoldersSettingsService.KeyboardTypingBehavior == KeyboardTypingBehavior.FilterItems &&
223+
!string.IsNullOrEmpty(SidebarAdaptiveViewModel.PaneHolder?.ActivePaneOrColumn!.ShellViewModel.FilesAndFoldersFilter))
224+
break;
225+
219226
// Execute command for hotkey
220227
var command = Commands[hotKey];
221228

0 commit comments

Comments
 (0)