Skip to content

Commit 4fd6774

Browse files
authored
Performance: Optimized the recent files widget (#10867)
1 parent 6ea4a9c commit 4fd6774

File tree

3 files changed

+89
-31
lines changed

3 files changed

+89
-31
lines changed

src/Files.App/Filesystem/RecentItem.cs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -106,6 +106,9 @@ public bool Equals(RecentItem other)
106106
RecentPath == other.RecentPath;
107107
}
108108

109+
public override int GetHashCode() => (LinkPath, RecentPath).GetHashCode();
110+
public override bool Equals(object? o) => o is RecentItem other && Equals(other);
111+
109112
/**
110113
* Strips a name from an extension while aware of some edge cases.
111114
*

src/Files.App/Filesystem/RecentItems.cs

Lines changed: 53 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -65,16 +65,17 @@ public async Task UpdateRecentFilesAsync()
6565
List<RecentItem> enumeratedFiles = await ListRecentFilesAsync();
6666
if (enumeratedFiles is not null)
6767
{
68+
var recentFilesSnapshot = RecentFiles;
69+
6870
lock (recentFiles)
6971
{
7072
recentFiles.Clear();
7173
recentFiles.AddRange(enumeratedFiles);
7274
// do not sort here, enumeration order *is* the correct order since we get it from Quick Access
7375
}
7476

75-
// todo: potentially optimize this and figure out if list changed by either (1) Add (2) Remove (3) Move
76-
// this way the UI doesn't have to refresh the entire list everytime a change occurs
77-
RecentFilesChanged?.Invoke(this, new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset));
77+
var changedActionEventArgs = GetChangedActionEventArgs(recentFilesSnapshot, enumeratedFiles);
78+
RecentFilesChanged?.Invoke(this, changedActionEventArgs);
7879
}
7980
}
8081

@@ -205,6 +206,55 @@ public Task<bool> UnpinFromRecentFiles(RecentItem item)
205206
}));
206207
}
207208

209+
private NotifyCollectionChangedEventArgs GetChangedActionEventArgs(IReadOnlyList<RecentItem> oldItems, IList<RecentItem> newItems)
210+
{
211+
// a single item was added
212+
if (newItems.Count == oldItems.Count + 1)
213+
{
214+
var differences = newItems.Except(oldItems);
215+
if (differences.Take(2).Count() == 1)
216+
{
217+
return new(NotifyCollectionChangedAction.Add, newItems.First());
218+
}
219+
}
220+
// a single item was removed
221+
else if (newItems.Count == oldItems.Count - 1)
222+
{
223+
var differences = oldItems.Except(newItems);
224+
if (differences.Take(2).Count() == 1)
225+
{
226+
for (int i = 0; i < oldItems.Count; i++)
227+
{
228+
if (i >= newItems.Count || !newItems[i].Equals(oldItems[i]))
229+
{
230+
return new(NotifyCollectionChangedAction.Remove, oldItems[i], index: i);
231+
}
232+
}
233+
}
234+
}
235+
// a single item was moved
236+
else if (newItems.Count == oldItems.Count)
237+
{
238+
var differences = oldItems.Except(newItems);
239+
// desync due to skipped/batched calls, reset the list
240+
if (differences.Any())
241+
{
242+
return new(NotifyCollectionChangedAction.Reset);
243+
}
244+
245+
// first diff from reversed is the designated item
246+
for (int i = oldItems.Count - 1; i >= 0; i--)
247+
{
248+
if (!oldItems[i].Equals(newItems[i]))
249+
{
250+
return new(NotifyCollectionChangedAction.Move, oldItems[i], index: 0, oldIndex: i);
251+
}
252+
}
253+
}
254+
255+
return new(NotifyCollectionChangedAction.Reset);
256+
}
257+
208258
public bool CheckIsRecentFilesEnabled()
209259
{
210260
using var subkey = Registry.CurrentUser.OpenSubKey(@"SOFTWARE\Microsoft\Windows\CurrentVersion\Explorer");
@@ -246,15 +296,6 @@ public bool CheckIsRecentFilesEnabled()
246296
return true;
247297
}
248298

249-
/// <summary>
250-
/// Returns whether two RecentItem enumerables have the same order.
251-
/// This function depends on `RecentItem` implementing IEquatable.
252-
/// </summary>
253-
private bool RecentItemsOrderEquals(IEnumerable<RecentItem> oldOrder, IEnumerable<RecentItem> newOrder)
254-
{
255-
return oldOrder != null && newOrder != null && oldOrder.SequenceEqual(newOrder);
256-
}
257-
258299
public void Dispose()
259300
{
260301
RecentItemsManager.Default.RecentItemsChanged -= OnRecentItemsChanged;

src/Files.App/UserControls/Widgets/RecentFilesWidget.xaml.cs

Lines changed: 33 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
using Microsoft.UI.Xaml;
88
using Microsoft.UI.Xaml.Controls;
99
using System;
10+
using System.Collections.Generic;
1011
using System.Collections.ObjectModel;
1112
using System.Collections.Specialized;
1213
using System.ComponentModel;
@@ -138,30 +139,43 @@ private async Task UpdateRecentsList(NotifyCollectionChangedEventArgs e)
138139

139140
switch (e.Action)
140141
{
141-
// currently everything falls under Reset
142+
case NotifyCollectionChangedAction.Add:
143+
if (e.NewItems is not null)
144+
{
145+
var addedItem = e.NewItems.Cast<RecentItem>().Single();
146+
AddItemToRecentList(addedItem, 0);
147+
}
148+
break;
149+
150+
case NotifyCollectionChangedAction.Move:
151+
if (e.OldItems is not null)
152+
{
153+
var movedItem = e.OldItems.Cast<RecentItem>().Single();
154+
recentItemsCollection.RemoveAt(e.OldStartingIndex);
155+
AddItemToRecentList(movedItem, 0);
156+
}
157+
break;
158+
159+
case NotifyCollectionChangedAction.Remove:
160+
if (e.OldItems is not null)
161+
{
162+
var removedItem = e.OldItems.Cast<RecentItem>().Single();
163+
recentItemsCollection.RemoveAt(e.OldStartingIndex);
164+
}
165+
break;
166+
167+
// case NotifyCollectionChangedAction.Reset:
142168
default:
169+
var recentFiles = App.RecentItemsManager.RecentFiles; // already sorted, add all in order
170+
if (!recentFiles.SequenceEqual(recentItemsCollection))
143171
{
144-
var recentFiles = App.RecentItemsManager.RecentFiles; // already sorted, add all in order
145-
int idx = 0;
146-
for (; idx < recentFiles.Count; idx++) // Add new items (top of the list)
147-
{
148-
if (idx >= recentItemsCollection.Count || !recentFiles[idx].Equals(recentItemsCollection[idx]))
149-
{
150-
if (!AddItemToRecentList(recentFiles[idx], idx)) // Not a new item
151-
break;
152-
}
153-
else
154-
break;
155-
}
156-
while (idx < recentItemsCollection.Count) // Remove old items
172+
recentItemsCollection.Clear();
173+
foreach (var item in recentFiles)
157174
{
158-
if (idx >= recentFiles.Count || !recentFiles[idx].Equals(recentItemsCollection[idx]))
159-
recentItemsCollection.RemoveAt(idx);
160-
else
161-
idx++;
175+
AddItemToRecentList(item);
162176
}
163-
break;
164177
}
178+
break;
165179
}
166180

167181
// update chevron if there aren't any items

0 commit comments

Comments
 (0)