mirror of
https://github.com/microsoft/PowerToys.git
synced 2026-04-03 09:46:54 +02:00
CmdPal: Lightning-fast mode (#45764)
## Summary of the Pull Request
This PR unlocks lightning-fast mode for Command Palette:
- Hides visual and motion distractions when updating the result list:
- Ensures the first interactable result item is selected as early as
possible after the result list is updated, reducing flashing and
blinking caused by the selection highlight moving around.
- Removes the list item selection indicator animation (unfortunately by
removing the pill altogether for now) and prevents it from temporarily
appearing on other items as the selection moves.
- Adds a new "Results" section header above the home page results when
no other section is present.
- This ensures the first item on the home page has consistent visuals
and styling, preventing offsets and excessive visual changes when
elements are replaced in place.
- Improves update performance and container reuse:
- Fixes the `removed` output parameter in `ListHelper.UpdateInPlace` to
only include items that were actually removed (items that were merely
moved to a different position should not be reported as removed).
- Adds unit tests to prevent regression.
- Updates `ListHelper.UpdateInPlace` for `ObservableCollection` to use
`Move` instead of `Remove`/`Add`, and avoids `Clear` to prevent
`ListView` resets (which force recreation of all item containers and are
expensive).
- Adds a simple cache for list page item view models to reduce
unnecessary recreation during forward incremental search.
- `ListViewModel` and `FetchItems` have no notion of item lifetime or
incremental search phase, so the cache intentionally remains simple
rather than clever.
- Updates ListPage templates to make them a little lighter:
- Tag template uses OneTime, instead of OneWay - since Tag is immutable
- Replaces ItemsControl with ItemsRepeater for Tag list on list items
- Increases the debounce for showing the details pane and adds a
debounce for hiding it. This improves performance when browsing the list
and prevents the details pane animation from bouncing left and right
## Pictures? Moving!
https://github.com/user-attachments/assets/36428d20-cf46-4321-83c0-d94d6d4a2299
<!-- Please review the items on the PR checklist before submitting-->
## PR Checklist
- [x] Closes: #44407
- [x] Closes: #45691
This commit is contained in:
@@ -36,6 +36,11 @@ public sealed partial class MainListPage : DynamicListPage,
|
||||
private readonly ScoringFunction<IListItem> _fallbackScoringFunction;
|
||||
private readonly IFuzzyMatcherProvider _fuzzyMatcherProvider;
|
||||
|
||||
// Stable separator instances so that the VM cache and InPlaceUpdateList
|
||||
// recognise them across successive GetItems() calls
|
||||
private readonly Separator _resultsSeparator = new(Resources.results);
|
||||
private readonly Separator _fallbacksSeparator = new(Resources.fallbacks);
|
||||
|
||||
private RoScored<IListItem>[]? _filteredItems;
|
||||
private RoScored<IListItem>[]? _filteredApps;
|
||||
|
||||
@@ -171,9 +176,40 @@ public sealed partial class MainListPage : DynamicListPage,
|
||||
// filtered results.
|
||||
if (string.IsNullOrWhiteSpace(SearchText))
|
||||
{
|
||||
return _tlcManager.TopLevelCommands
|
||||
.Where(tlc => !tlc.IsFallback && !string.IsNullOrEmpty(tlc.Title))
|
||||
.ToArray();
|
||||
var allCommands = _tlcManager.TopLevelCommands;
|
||||
|
||||
// First pass: count eligible commands
|
||||
var eligibleCount = 0;
|
||||
for (var i = 0; i < allCommands.Count; i++)
|
||||
{
|
||||
var cmd = allCommands[i];
|
||||
if (!cmd.IsFallback && !string.IsNullOrEmpty(cmd.Title))
|
||||
{
|
||||
eligibleCount++;
|
||||
}
|
||||
}
|
||||
|
||||
if (eligibleCount == 0)
|
||||
{
|
||||
return [];
|
||||
}
|
||||
|
||||
// +1 for the separator
|
||||
var result = new IListItem[eligibleCount + 1];
|
||||
result[0] = _resultsSeparator;
|
||||
|
||||
// Second pass: populate
|
||||
var writeIndex = 1;
|
||||
for (var i = 0; i < allCommands.Count; i++)
|
||||
{
|
||||
var cmd = allCommands[i];
|
||||
if (!cmd.IsFallback && !string.IsNullOrEmpty(cmd.Title))
|
||||
{
|
||||
result[writeIndex++] = cmd;
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -190,6 +226,8 @@ public sealed partial class MainListPage : DynamicListPage,
|
||||
validScoredFallbacks,
|
||||
_filteredApps,
|
||||
validFallbacks,
|
||||
_resultsSeparator,
|
||||
_fallbacksSeparator,
|
||||
AppResultLimit);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -21,6 +21,8 @@ internal static class MainListPageResultFactory
|
||||
IList<RoScored<IListItem>>? scoredFallbackItems,
|
||||
IList<RoScored<IListItem>>? filteredApps,
|
||||
IList<RoScored<IListItem>>? fallbackItems,
|
||||
IListItem resultsSeparator,
|
||||
IListItem fallbacksSeparator,
|
||||
int appResultLimit)
|
||||
{
|
||||
if (appResultLimit < 0)
|
||||
@@ -40,8 +42,13 @@ internal static class MainListPageResultFactory
|
||||
int nonEmptyFallbackCount = fallbackItems?.Count ?? 0;
|
||||
|
||||
// Allocate the exact size of the result array.
|
||||
// We'll add an extra slot for the fallbacks section header if needed.
|
||||
int totalCount = len1 + len2 + len3 + nonEmptyFallbackCount + (nonEmptyFallbackCount > 0 ? 1 : 0);
|
||||
// We'll add an extra slot for the fallbacks section header if needed,
|
||||
// and another for the "Results" section header when merged results exist.
|
||||
int mergedCount = len1 + len2 + len3;
|
||||
bool needsResultsHeader = mergedCount > 0;
|
||||
int totalCount = mergedCount + nonEmptyFallbackCount
|
||||
+ (needsResultsHeader ? 1 : 0)
|
||||
+ (nonEmptyFallbackCount > 0 ? 1 : 0);
|
||||
|
||||
var result = new IListItem[totalCount];
|
||||
|
||||
@@ -49,6 +56,12 @@ internal static class MainListPageResultFactory
|
||||
int idx1 = 0, idx2 = 0, idx3 = 0;
|
||||
int writePos = 0;
|
||||
|
||||
// Add "Results" section header when merged results will precede the fallbacks.
|
||||
if (needsResultsHeader)
|
||||
{
|
||||
result[writePos++] = resultsSeparator;
|
||||
}
|
||||
|
||||
// Merge while all three lists have items. To maintain a stable sort, the
|
||||
// priority is: list1 > list2 > list3 when scores are equal.
|
||||
while (idx1 < len1 && idx2 < len2 && idx3 < len3)
|
||||
@@ -132,7 +145,7 @@ internal static class MainListPageResultFactory
|
||||
// Create the fallbacks section header
|
||||
if (fallbackItems.Count > 0)
|
||||
{
|
||||
result[writePos++] = new Separator(Properties.Resources.fallbacks);
|
||||
result[writePos++] = fallbacksSeparator;
|
||||
}
|
||||
|
||||
for (int i = 0; i < fallbackItems.Count; i++)
|
||||
|
||||
Reference in New Issue
Block a user