Add support for moving focus between panes with the keyboard (#1910)

Enables the user to set keybindings to move focus between panes with the keyboard. 
This is highly based off the work done for resizing panes. Same logic applies - 
  moving focus will move up the panes tree until we find a pane to move the focus to.
This commit is contained in:
Mike Griese 2019-07-17 09:30:15 -05:00 committed by GitHub
parent a0782bfd6c
commit 8d52ba0990
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
10 changed files with 150 additions and 3 deletions

View file

@ -505,6 +505,7 @@ namespace winrt::TerminalApp::implementation
bindings.SwitchToTab([this](const auto index) { _SelectTab({ index }); });
bindings.OpenSettings([this]() { _OpenSettings(); });
bindings.ResizePane([this](const auto direction) { _ResizePane(direction); });
bindings.MoveFocus([this](const auto direction) { _MoveFocus(direction); });
bindings.CopyText([this](const auto trimWhitespace) { _CopyText(trimWhitespace); });
bindings.PasteText([this]() { _PasteText(); });
}
@ -1028,6 +1029,20 @@ namespace winrt::TerminalApp::implementation
_tabs[focusedTabIndex]->ResizePane(direction);
}
// Method Description:
// - Attempt to move focus between panes, as to focus the child on
// the other side of the separator. See Pane::NavigateFocus for details.
// - Moves the focus of the currently focused tab.
// Arguments:
// - direction: The direction to move the focus in.
// Return Value:
// - <none>
void App::_MoveFocus(const Direction& direction)
{
const auto focusedTabIndex = _GetFocusedTabIndex();
_tabs[focusedTabIndex]->NavigateFocus(direction);
}
// Method Description:
// - Copy text from the focused terminal to the Windows Clipboard
// Arguments:

View file

@ -122,6 +122,7 @@ namespace winrt::TerminalApp::implementation
// MSFT:20641986: Add keybindings for New Window
void _ScrollPage(int delta);
void _ResizePane(const Direction& direction);
void _MoveFocus(const Direction& direction);
void _OnLoaded(const IInspectable& sender, const Windows::UI::Xaml::RoutedEventArgs& eventArgs);
void _OnTabSelectionChanged(const IInspectable& sender, const Windows::UI::Xaml::Controls::SelectionChangedEventArgs& eventArgs);

View file

@ -169,7 +169,18 @@ namespace winrt::TerminalApp::implementation
case ShortcutAction::ResizePaneDown:
_ResizePaneHandlers(Direction::Down);
return true;
case ShortcutAction::MoveFocusLeft:
_MoveFocusHandlers(Direction::Left);
return true;
case ShortcutAction::MoveFocusRight:
_MoveFocusHandlers(Direction::Right);
return true;
case ShortcutAction::MoveFocusUp:
_MoveFocusHandlers(Direction::Up);
return true;
case ShortcutAction::MoveFocusDown:
_MoveFocusHandlers(Direction::Down);
return true;
default:
return false;
}
@ -251,5 +262,6 @@ namespace winrt::TerminalApp::implementation
DEFINE_EVENT(AppKeyBindings, ScrollDownPage, _ScrollDownPageHandlers, TerminalApp::ScrollDownPageEventArgs);
DEFINE_EVENT(AppKeyBindings, OpenSettings, _OpenSettingsHandlers, TerminalApp::OpenSettingsEventArgs);
DEFINE_EVENT(AppKeyBindings, ResizePane, _ResizePaneHandlers, TerminalApp::ResizePaneEventArgs);
DEFINE_EVENT(AppKeyBindings, MoveFocus, _MoveFocusHandlers, TerminalApp::MoveFocusEventArgs);
// clang-format on
}

View file

@ -61,6 +61,7 @@ namespace winrt::TerminalApp::implementation
DECLARE_EVENT(ScrollDownPage, _ScrollDownPageHandlers, TerminalApp::ScrollDownPageEventArgs);
DECLARE_EVENT(OpenSettings, _OpenSettingsHandlers, TerminalApp::OpenSettingsEventArgs);
DECLARE_EVENT(ResizePane, _ResizePaneHandlers, TerminalApp::ResizePaneEventArgs);
DECLARE_EVENT(MoveFocus, _MoveFocusHandlers, TerminalApp::MoveFocusEventArgs);
// clang-format on
private:

View file

@ -53,6 +53,10 @@ namespace TerminalApp
ResizePaneRight,
ResizePaneUp,
ResizePaneDown,
MoveFocusLeft,
MoveFocusRight,
MoveFocusUp,
MoveFocusDown,
OpenSettings
};
@ -77,9 +81,9 @@ namespace TerminalApp
delegate void ScrollDownPageEventArgs();
delegate void OpenSettingsEventArgs();
delegate void ResizePaneEventArgs(Direction direction);
delegate void MoveFocusEventArgs(Direction direction);
[default_interface]
runtimeclass AppKeyBindings : Microsoft.Terminal.Settings.IKeyBindings
[default_interface] runtimeclass AppKeyBindings : Microsoft.Terminal.Settings.IKeyBindings
{
AppKeyBindings();
@ -107,5 +111,6 @@ namespace TerminalApp
event ScrollDownPageEventArgs ScrollDownPage;
event OpenSettingsEventArgs OpenSettings;
event ResizePaneEventArgs ResizePane;
event MoveFocusEventArgs MoveFocus;
}
}

View file

@ -55,6 +55,10 @@ static constexpr std::string_view ResizePaneLeftKey{ "resizePaneLeft" };
static constexpr std::string_view ResizePaneRightKey{ "resizePaneRight" };
static constexpr std::string_view ResizePaneUpKey{ "resizePaneUp" };
static constexpr std::string_view ResizePaneDownKey{ "resizePaneDown" };
static constexpr std::string_view MoveFocusLeftKey{ "moveFocusLeft" };
static constexpr std::string_view MoveFocusRightKey{ "moveFocusRight" };
static constexpr std::string_view MoveFocusUpKey{ "moveFocusUp" };
static constexpr std::string_view MoveFocusDownKey{ "moveFocusDown" };
// Specifically use a map here over an unordered_map. We want to be able to
// iterate over these entries in-order when we're serializing the keybindings.
@ -105,6 +109,10 @@ static const std::map<std::string_view, ShortcutAction, std::less<>> commandName
{ ResizePaneRightKey, ShortcutAction::ResizePaneRight },
{ ResizePaneUpKey, ShortcutAction::ResizePaneUp },
{ ResizePaneDownKey, ShortcutAction::ResizePaneDown },
{ MoveFocusLeftKey, ShortcutAction::MoveFocusLeft },
{ MoveFocusRightKey, ShortcutAction::MoveFocusRight },
{ MoveFocusUpKey, ShortcutAction::MoveFocusUp },
{ MoveFocusDownKey, ShortcutAction::MoveFocusDown },
{ OpenSettingsKey, ShortcutAction::OpenSettings },
};

View file

@ -187,6 +187,95 @@ bool Pane::ResizePane(const Direction& direction)
return false;
}
// Method Description:
// - Attempts to handle moving focus to one of our children. If our split
// direction isn't appropriate for the move direction, then we'll return
// false, to try and let our parent handle the move. If our child we'd move
// focus to is already focused, we'll also return false, to again let our
// parent try and handle the focus movement.
// Arguments:
// - direction: The direction to move the focus in.
// Return Value:
// - true if we handled this focus move request.
bool Pane::_NavigateFocus(const Direction& direction)
{
if (!DirectionMatchesSplit(direction, _splitState))
{
return false;
}
const bool focusSecond = (direction == Direction::Right) || (direction == Direction::Down);
const auto newlyFocusedChild = focusSecond ? _secondChild : _firstChild;
// If the child we want to move focus to is _already_ focused, return false,
// to try and let our parent figure it out.
if (newlyFocusedChild->WasLastFocused())
{
return false;
}
// Transfer focus to our child, and update the focus of our tree.
newlyFocusedChild->_FocusFirstChild();
UpdateFocus();
return true;
}
// Method Description:
// - Attempts to move focus to one of our children. If we have a focused child,
// we'll try to move the focus in the direction requested.
// - If there isn't a pane that exists as a child of this pane in the correct
// direction, we'll return false. This will indicate to our parent that they
// should try and move the focus themselves. In this way, the focus can move
// up and down the tree to the correct pane.
// - This method is _very_ similar to ResizePane. Both are trying to find the
// right separator to move (focus) in a direction.
// Arguments:
// - direction: The direction to move the focus in.
// Return Value:
// - true if we or a child handled this focus move request.
bool Pane::NavigateFocus(const Direction& direction)
{
// If we're a leaf, do nothing. We can't possibly have a descendant with a
// separator the correct direction.
if (_IsLeaf())
{
return false;
}
// Check if either our first or second child is the currently focused leaf.
// If it is, and the requested move direction matches our separator, then
// we're the pane that needs to handle this focus move.
const bool firstIsFocused = _firstChild->_IsLeaf() && _firstChild->_lastFocused;
const bool secondIsFocused = _secondChild->_IsLeaf() && _secondChild->_lastFocused;
if (firstIsFocused || secondIsFocused)
{
return _NavigateFocus(direction);
}
else
{
// If neither of our children were the focused leaf, then recurse into
// our children and see if they can handle the focus move.
// For each child, if it has a focused descendant, try having that child
// handle the focus move.
// If the child wasn't able to handle the focus move, it's possible that
// there were no descendants with a separator the correct direction. If
// our separator _is_ the correct direction, then we should be the pane
// to move focus into our other child. Otherwise, just return false, as
// we couldn't handle it either.
if ((!_firstChild->_IsLeaf()) && _firstChild->_HasFocusedChild())
{
return _firstChild->NavigateFocus(direction) || _NavigateFocus(direction);
}
else if ((!_secondChild->_IsLeaf()) && _secondChild->_HasFocusedChild())
{
return _secondChild->NavigateFocus(direction) || _NavigateFocus(direction);
}
}
return false;
}
// Method Description:
// - Called when our attached control is closed. Triggers listeners to our close
// event, if we're a leaf pane.

View file

@ -47,6 +47,7 @@ public:
void UpdateSettings(const winrt::Microsoft::Terminal::Settings::TerminalSettings& settings, const GUID& profile);
void ResizeContent(const winrt::Windows::Foundation::Size& newSize);
bool ResizePane(const winrt::TerminalApp::Direction& direction);
bool NavigateFocus(const winrt::TerminalApp::Direction& direction);
void SplitHorizontal(const GUID& profile, const winrt::Microsoft::Terminal::TerminalControl::TermControl& control);
void SplitVertical(const GUID& profile, const winrt::Microsoft::Terminal::TerminalControl::TermControl& control);
@ -80,7 +81,9 @@ private:
void _CreateRowColDefinitions(const winrt::Windows::Foundation::Size& rootSize);
void _CreateSplitContent();
void _ApplySplitDefinitions();
bool _Resize(const winrt::TerminalApp::Direction& direction);
bool _NavigateFocus(const winrt::TerminalApp::Direction& direction);
void _CloseChild(const bool closeFirst);

View file

@ -237,4 +237,16 @@ void Tab::ResizePane(const winrt::TerminalApp::Direction& direction)
_rootPane->ResizePane(direction);
}
// Method Description:
// - Attempt to move focus between panes, as to focus the child on
// the other side of the separator. See Pane::NavigateFocus for details.
// Arguments:
// - direction: The direction to move the focus in.
// Return Value:
// - <none>
void Tab::NavigateFocus(const winrt::TerminalApp::Direction& direction)
{
_rootPane->NavigateFocus(direction);
}
DEFINE_EVENT(Tab, Closed, _closedHandlers, ConnectionClosedEventArgs);

View file

@ -25,6 +25,7 @@ public:
void UpdateFocus();
void ResizeContent(const winrt::Windows::Foundation::Size& newSize);
void ResizePane(const winrt::TerminalApp::Direction& direction);
void NavigateFocus(const winrt::TerminalApp::Direction& direction);
void UpdateSettings(const winrt::Microsoft::Terminal::Settings::TerminalSettings& settings, const GUID& profile);
winrt::hstring GetFocusedTitle() const;