Skip to content

Commit

Permalink
Add support for moving focus between panes with the keyboard (microso…
Browse files Browse the repository at this point in the history
…ft#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.
  • Loading branch information
zadjii-msft authored and mcpiroman committed Jul 23, 2019
1 parent c1f990f commit e8f6ca2
Show file tree
Hide file tree
Showing 10 changed files with 150 additions and 3 deletions.
15 changes: 15 additions & 0 deletions src/cascadia/TerminalApp/App.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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(); });
}
Expand Down Expand Up @@ -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:
Expand Down
1 change: 1 addition & 0 deletions src/cascadia/TerminalApp/App.h
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
14 changes: 13 additions & 1 deletion src/cascadia/TerminalApp/AppKeyBindings.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}
Expand Down Expand Up @@ -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
}
1 change: 1 addition & 0 deletions src/cascadia/TerminalApp/AppKeyBindings.h
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down
9 changes: 7 additions & 2 deletions src/cascadia/TerminalApp/AppKeyBindings.idl
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,10 @@ namespace TerminalApp
ResizePaneRight,
ResizePaneUp,
ResizePaneDown,
MoveFocusLeft,
MoveFocusRight,
MoveFocusUp,
MoveFocusDown,
OpenSettings
};

Expand All @@ -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();

Expand Down Expand Up @@ -107,5 +111,6 @@ namespace TerminalApp
event ScrollDownPageEventArgs ScrollDownPage;
event OpenSettingsEventArgs OpenSettings;
event ResizePaneEventArgs ResizePane;
event MoveFocusEventArgs MoveFocus;
}
}
8 changes: 8 additions & 0 deletions src/cascadia/TerminalApp/AppKeyBindingsSerialization.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -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 },
};

Expand Down
89 changes: 89 additions & 0 deletions src/cascadia/TerminalApp/Pane.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
3 changes: 3 additions & 0 deletions src/cascadia/TerminalApp/Pane.h
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ class Pane : public std::enable_shared_from_this<Pane>
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);
Expand Down Expand Up @@ -80,7 +81,9 @@ class Pane : public std::enable_shared_from_this<Pane>
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);

Expand Down
12 changes: 12 additions & 0 deletions src/cascadia/TerminalApp/Tab.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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);
1 change: 1 addition & 0 deletions src/cascadia/TerminalApp/Tab.h
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ class Tab
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;
Expand Down

0 comments on commit e8f6ca2

Please sign in to comment.