From 7062a830b844a6dbfa6d2c06c9c001545b6483c4 Mon Sep 17 00:00:00 2001 From: jtippet Date: Thu, 16 Jul 2020 17:33:03 -0700 Subject: [PATCH] Smooth animation of command palette filtering (#6939) The command palette is a ListView of commands. As you type into the search box, commands are added or removed from the ListView. Currently, each update is done by completely clearing the backing list, then adding back any items that should be displayed. However, this defeats the ListView's built-in animations: upon every keystroke, ListView displays its list-clearing animation, then animates the insertion of every item that wasn't deleted. This results in noticeable flickering. This PR changes the update logic so that it updates the list using (roughly) the minimum number of Insert and Remove calls, so the ListView makes smoother transitions as you type. I implemented it by keeping the existing code that builds the filtered list, but I changed it to build into a scratch list. Then I grafted on a generic delta algorithm to make the real list look like the scratch list. To verify the delta algorithm, I tested all 360,000 permutations of pairs of up to 5 element lists in a toy C# app. ## Validation I'm not sure if my screen capture tool really caught all the flickering here, but the screencasts below should give a rough idea of the difference. (All the flickering was becoming a nuisance while I was testing out the HC changes.) See the images in #6939 for more info. Co-authored-by: Jeffrey Tippet --- src/cascadia/TerminalApp/CommandPalette.cpp | 64 ++++++++++++++++++--- src/cascadia/TerminalApp/CommandPalette.h | 1 + 2 files changed, 58 insertions(+), 7 deletions(-) diff --git a/src/cascadia/TerminalApp/CommandPalette.cpp b/src/cascadia/TerminalApp/CommandPalette.cpp index 6fac2d35d..33d32defe 100644 --- a/src/cascadia/TerminalApp/CommandPalette.cpp +++ b/src/cascadia/TerminalApp/CommandPalette.cpp @@ -271,16 +271,17 @@ namespace winrt::TerminalApp::implementation }; // Method Description: - // - Update our list of filtered actions to reflect the current contents of + // - Produce a list of filtered actions to reflect the current contents of // the input box. For more details on which commands will be displayed, // see `_getWeight`. // Arguments: - // - + // - A collection that will receive the filtered actions // Return Value: // - - void CommandPalette::_updateFilteredActions() + std::vector CommandPalette::_collectFilteredActions() { - _filteredActions.Clear(); + std::vector actions; + auto searchText = _searchBox().Text(); const bool addAll = searchText.empty(); @@ -303,10 +304,10 @@ namespace winrt::TerminalApp::implementation for (auto action : sortedCommands) { - _filteredActions.Append(action); + actions.push_back(action); } - return; + return actions; } // Here, there was some filter text. @@ -343,7 +344,56 @@ namespace winrt::TerminalApp::implementation { auto top = heap.top(); heap.pop(); - _filteredActions.Append(top.command); + actions.push_back(top.command); + } + + return actions; + } + + // Method Description: + // - Update our list of filtered actions to reflect the current contents of + // the input box. For more details on which commands will be displayed, + // see `_getWeight`. + // Arguments: + // - + // Return Value: + // - + void CommandPalette::_updateFilteredActions() + { + auto actions = _collectFilteredActions(); + + // Make _filteredActions look identical to actions, using only Insert and Remove. + // This allows WinUI to nicely animate the ListView as it changes. + for (uint32_t i = 0; i < _filteredActions.Size() && i < actions.size(); i++) + { + for (uint32_t j = i; j < _filteredActions.Size(); j++) + { + if (_filteredActions.GetAt(j) == actions[i]) + { + for (uint32_t k = i; k < j; k++) + { + _filteredActions.RemoveAt(i); + } + break; + } + } + + if (_filteredActions.GetAt(i) != actions[i]) + { + _filteredActions.InsertAt(i, actions[i]); + } + } + + // Remove any extra trailing items from the destination + while (_filteredActions.Size() > actions.size()) + { + _filteredActions.RemoveAtEnd(); + } + + // Add any extra trailing items from the source + while (_filteredActions.Size() < actions.size()) + { + _filteredActions.Append(actions[_filteredActions.Size()]); } } diff --git a/src/cascadia/TerminalApp/CommandPalette.h b/src/cascadia/TerminalApp/CommandPalette.h index 2d3960705..a14466f4f 100644 --- a/src/cascadia/TerminalApp/CommandPalette.h +++ b/src/cascadia/TerminalApp/CommandPalette.h @@ -37,6 +37,7 @@ namespace winrt::TerminalApp::implementation void _selectNextItem(const bool moveDown); void _updateFilteredActions(); + std::vector _collectFilteredActions(); static int _getWeight(const winrt::hstring& searchText, const winrt::hstring& name); void _close();