diff --git a/src/cascadia/TerminalApp/App.cpp b/src/cascadia/TerminalApp/App.cpp index 794bb63e2ea..e710080c910 100644 --- a/src/cascadia/TerminalApp/App.cpp +++ b/src/cascadia/TerminalApp/App.cpp @@ -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: + // - + 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: diff --git a/src/cascadia/TerminalApp/App.h b/src/cascadia/TerminalApp/App.h index b444e399830..3fc704215bb 100644 --- a/src/cascadia/TerminalApp/App.h +++ b/src/cascadia/TerminalApp/App.h @@ -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); diff --git a/src/cascadia/TerminalApp/AppKeyBindings.cpp b/src/cascadia/TerminalApp/AppKeyBindings.cpp index f8246bbe01d..8a71fe4f0ef 100644 --- a/src/cascadia/TerminalApp/AppKeyBindings.cpp +++ b/src/cascadia/TerminalApp/AppKeyBindings.cpp @@ -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 } diff --git a/src/cascadia/TerminalApp/AppKeyBindings.h b/src/cascadia/TerminalApp/AppKeyBindings.h index d7c812924bd..eeb1e0270f5 100644 --- a/src/cascadia/TerminalApp/AppKeyBindings.h +++ b/src/cascadia/TerminalApp/AppKeyBindings.h @@ -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: diff --git a/src/cascadia/TerminalApp/AppKeyBindings.idl b/src/cascadia/TerminalApp/AppKeyBindings.idl index 76290dcf963..b17d2dc5415 100644 --- a/src/cascadia/TerminalApp/AppKeyBindings.idl +++ b/src/cascadia/TerminalApp/AppKeyBindings.idl @@ -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; } } diff --git a/src/cascadia/TerminalApp/AppKeyBindingsSerialization.cpp b/src/cascadia/TerminalApp/AppKeyBindingsSerialization.cpp index c4d777facb1..5d43ef422fb 100644 --- a/src/cascadia/TerminalApp/AppKeyBindingsSerialization.cpp +++ b/src/cascadia/TerminalApp/AppKeyBindingsSerialization.cpp @@ -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> 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 }, }; diff --git a/src/cascadia/TerminalApp/Pane.cpp b/src/cascadia/TerminalApp/Pane.cpp index f022b6344e0..d093fc68b3b 100644 --- a/src/cascadia/TerminalApp/Pane.cpp +++ b/src/cascadia/TerminalApp/Pane.cpp @@ -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. diff --git a/src/cascadia/TerminalApp/Pane.h b/src/cascadia/TerminalApp/Pane.h index aaad52cc25f..5f84ae0518c 100644 --- a/src/cascadia/TerminalApp/Pane.h +++ b/src/cascadia/TerminalApp/Pane.h @@ -47,6 +47,7 @@ class Pane : public std::enable_shared_from_this 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 @@ class Pane : public std::enable_shared_from_this 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); diff --git a/src/cascadia/TerminalApp/Tab.cpp b/src/cascadia/TerminalApp/Tab.cpp index 83d4ee30574..0c4ea0d26a9 100644 --- a/src/cascadia/TerminalApp/Tab.cpp +++ b/src/cascadia/TerminalApp/Tab.cpp @@ -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: +// - +void Tab::NavigateFocus(const winrt::TerminalApp::Direction& direction) +{ + _rootPane->NavigateFocus(direction); +} + DEFINE_EVENT(Tab, Closed, _closedHandlers, ConnectionClosedEventArgs); diff --git a/src/cascadia/TerminalApp/Tab.h b/src/cascadia/TerminalApp/Tab.h index 6c8e073ee3b..ef796a28dc7 100644 --- a/src/cascadia/TerminalApp/Tab.h +++ b/src/cascadia/TerminalApp/Tab.h @@ -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;