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 <jtippet@microsoft.com>
This commit is contained in:
jtippet 2020-07-16 17:33:03 -07:00 committed by GitHub
parent 53df6c7f96
commit 7062a830b8
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
2 changed files with 58 additions and 7 deletions

View file

@ -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:
// - <none>
// - A collection that will receive the filtered actions
// Return Value:
// - <none>
void CommandPalette::_updateFilteredActions()
std::vector<winrt::TerminalApp::Command> CommandPalette::_collectFilteredActions()
{
_filteredActions.Clear();
std::vector<winrt::TerminalApp::Command> 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:
// - <none>
// Return Value:
// - <none>
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()]);
}
}

View file

@ -37,6 +37,7 @@ namespace winrt::TerminalApp::implementation
void _selectNextItem(const bool moveDown);
void _updateFilteredActions();
std::vector<winrt::TerminalApp::Command> _collectFilteredActions();
static int _getWeight(const winrt::hstring& searchText, const winrt::hstring& name);
void _close();