Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Make command palette ephemeral #8377

Merged
11 commits merged into from
Dec 9, 2020
38 changes: 38 additions & 0 deletions src/cascadia/TerminalApp/CommandPalette.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -435,6 +435,44 @@ namespace winrt::TerminalApp::implementation
void CommandPalette::_rootPointerPressed(Windows::Foundation::IInspectable const& /*sender*/,
Windows::UI::Xaml::Input::PointerRoutedEventArgs const& /*e*/)
{
if (Visibility() != Visibility::Collapsed)
{
_dismissPalette();
}
}

// Method Description:
// - The purpose of this event handler is to hide the palette if it loses focus.
// We say we lost focus if our root element and all its descendants lost focus.
// This handler is invoked when our root element or some descendant loses focus.
// At this point we need to learn if the newly focused element belongs to this palette.
// To achieve this:
// - We start with the newly focused element and traverse its visual ancestors up to the Xaml root.
// - If one of the ancestors is this CommandPalette, then by our definition the focus is not lost
// - If we reach the Xaml root without meeting this CommandPalette,
// then the focus is not contained in it anymore and it should be dismissed
// Arguments:
// - <unused>
// Return Value:
// - <none>
void CommandPalette::_lostFocusHandler(Windows::Foundation::IInspectable const& /*sender*/,
Windows::UI::Xaml::RoutedEventArgs const& /*args*/)
{
auto focusedElementOrAncestor = Input::FocusManager::GetFocusedElement(this->XamlRoot()).try_as<DependencyObject>();
while (focusedElementOrAncestor)
{
if (focusedElementOrAncestor == *this)
{
// This palette is the focused element or an ancestor of the focused element. No need to dismiss.
return;
}

// Go up to the next ancestor
focusedElementOrAncestor = winrt::Windows::UI::Xaml::Media::VisualTreeHelper::GetParent(focusedElementOrAncestor);
}

// We got to the root (the element with no parent) and didn't meet this palette on the path.
// It means that it lost the focus and needs to be dismissed.
_dismissPalette();
}

Expand Down
3 changes: 3 additions & 0 deletions src/cascadia/TerminalApp/CommandPalette.h
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,9 @@ namespace winrt::TerminalApp::implementation
void _updateUIForStackChange();

void _rootPointerPressed(Windows::Foundation::IInspectable const& sender, Windows::UI::Xaml::Input::PointerRoutedEventArgs const& e);

void _lostFocusHandler(Windows::Foundation::IInspectable const& sender, Windows::UI::Xaml::RoutedEventArgs const& args);

void _backdropPointerPressed(Windows::Foundation::IInspectable const& sender, Windows::UI::Xaml::Input::PointerRoutedEventArgs const& e);

void _listItemClicked(Windows::Foundation::IInspectable const& sender, Windows::UI::Xaml::Controls::ItemClickEventArgs const& e);
Expand Down
6 changes: 4 additions & 2 deletions src/cascadia/TerminalApp/CommandPalette.xaml
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ the MIT License. See LICENSE in the project root for license information. -->
PreviewKeyDown="_previewKeyDownHandler"
KeyDown="_keyDownHandler"
PreviewKeyUp="_keyUpHandler"
LostFocus="_lostFocusHandler"
mc:Ignorable="d"
AutomationProperties.Name="{x:Bind ControlName, Mode=OneWay}">

Expand Down Expand Up @@ -245,8 +246,9 @@ the MIT License. See LICENSE in the project root for license information. -->
<!-- This HorizontalContentAlignment="Stretch" is important
to make sure it takes the entire width of the line -->
<ListViewItem HorizontalContentAlignment="Stretch"
AutomationProperties.Name="{x:Bind Command.Name, Mode=OneWay}"
AutomationProperties.AcceleratorKey="{x:Bind Command.KeyChordText, Mode=OneWay}">
IsTabStop="False"
AutomationProperties.Name="{x:Bind Command.Name, Mode=OneWay}"
AutomationProperties.AcceleratorKey="{x:Bind Command.KeyChordText, Mode=OneWay}">

<Grid HorizontalAlignment="Stretch" ColumnSpacing="8" >
<Grid.ColumnDefinitions>
Expand Down
51 changes: 25 additions & 26 deletions src/cascadia/TerminalApp/TerminalPage.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -576,6 +576,21 @@ namespace winrt::TerminalApp::implementation
newTabFlyout.Items().Append(aboutFlyout);
}

// Before opening the fly-out set focus on the current tab
// so no matter how fly-out is closed later on the focus will return to some tab.
// We cannot do it on closing because if the window loses focus (alt+tab)
// the closing event is not fired.
// It is important to set the focus on the tab
// Since the previous focus location might be discarded in the background,
// e.g., the command palette will be dismissed by the menu,
// and then closing the fly-out will move the focus to wrong location.
newTabFlyout.Opening([this](auto&&, auto&&) {
if (auto index{ _GetFocusedTabIndex() })
{
_tabs.GetAt(*index).Focus(FocusState::Programmatic);
_UpdateMRUTab(index.value());
}
});
_newTabButton.Flyout(newTabFlyout);
}

Expand Down Expand Up @@ -1060,19 +1075,6 @@ namespace winrt::TerminalApp::implementation
_tabView.TabItems().RemoveAt(tabIndex);
_UpdateTabIndices();

// If the tab switcher is currently open, update it to reflect the
// current list of tabs.
const auto tabSwitchMode = _settings.GlobalSettings().TabSwitcherMode();
const bool commandPaletteIsVisible = CommandPalette().Visibility() == Visibility::Visible;
if (tabSwitchMode == TabSwitcherMode::MostRecentlyUsed && commandPaletteIsVisible)
{
CommandPalette().SetTabActions(_mruTabActions, false);
}
else if (commandPaletteIsVisible)
{
_UpdatePaletteWithInOrderTabs();
}

// To close the window here, we need to close the hosting window.
if (_tabs.Size() == 0)
{
Expand Down Expand Up @@ -2083,6 +2085,7 @@ namespace winrt::TerminalApp::implementation
}
}

CommandPalette().Visibility(Visibility::Collapsed);
_UpdateTabView();
}

Expand Down Expand Up @@ -2669,11 +2672,16 @@ namespace winrt::TerminalApp::implementation
void TerminalPage::_CommandPaletteClosed(const IInspectable& /*sender*/,
const RoutedEventArgs& /*eventArgs*/)
{
// Return focus to the active control
if (auto index{ _GetFocusedTabIndex() })
// We don't want to set focus on the tab if fly-out is open as it will be closed
// TODO GH#5400: consider checking we are not in the opening state, by hooking both Opening and Open events
if (!_newTabButton.Flyout().IsOpen())
{
_tabs.GetAt(*index).Focus(FocusState::Programmatic);
_UpdateMRUTab(index.value());
// Return focus to the active control
if (auto index{ _GetFocusedTabIndex() })
{
_tabs.GetAt(*index).Focus(FocusState::Programmatic);
_UpdateMRUTab(index.value());
}
}
}

Expand Down Expand Up @@ -2829,15 +2837,6 @@ namespace winrt::TerminalApp::implementation
{
_mruTabActions.RemoveAt(mruIndex);
_mruTabActions.InsertAt(0, command);

// If the tab switcher is currently open, AND we're using it in
// MRU mode, then update it to reflect the current list of tabs.
const auto tabSwitchMode = _settings.GlobalSettings().TabSwitcherMode();
const bool commandPaletteIsVisible = CommandPalette().Visibility() == Visibility::Visible;
if (tabSwitchMode == TabSwitcherMode::MostRecentlyUsed && commandPaletteIsVisible)
{
CommandPalette().SetTabActions(_mruTabActions, false);
}
}
}
}
Expand Down