From c2d8e274afa462af79af13ed10693a079d9c3784 Mon Sep 17 00:00:00 2001 From: MCpiroman Date: Sat, 21 Dec 2019 20:58:01 +0100 Subject: [PATCH 01/17] Now it works --- src/cascadia/TerminalApp/ActionArgs.h | 4 +- src/cascadia/TerminalApp/ActionArgs.idl | 6 +- src/cascadia/TerminalApp/Pane.h | 157 +---- src/cascadia/TerminalApp/Tab.cpp | 105 ++- src/cascadia/TerminalApp/Tab.h | 12 +- src/cascadia/TerminalApp/TerminalPage.cpp | 6 - src/cascadia/TerminalApp/lib/LeafPane.cpp | 448 ++++++++++++ src/cascadia/TerminalApp/lib/LeafPane.h | 83 +++ src/cascadia/TerminalApp/lib/ParentPane.cpp | 732 ++++++++++++++++++++ src/cascadia/TerminalApp/lib/ParentPane.h | 87 +++ 10 files changed, 1468 insertions(+), 172 deletions(-) create mode 100644 src/cascadia/TerminalApp/lib/LeafPane.cpp create mode 100644 src/cascadia/TerminalApp/lib/LeafPane.h create mode 100644 src/cascadia/TerminalApp/lib/ParentPane.cpp create mode 100644 src/cascadia/TerminalApp/lib/ParentPane.h diff --git a/src/cascadia/TerminalApp/ActionArgs.h b/src/cascadia/TerminalApp/ActionArgs.h index d3ef59058b8..c912e9e394e 100644 --- a/src/cascadia/TerminalApp/ActionArgs.h +++ b/src/cascadia/TerminalApp/ActionArgs.h @@ -311,13 +311,13 @@ namespace winrt::TerminalApp::implementation return TerminalApp::SplitState::Automatic; } // default behavior for invalid data - return TerminalApp::SplitState::None; + return TerminalApp::SplitState::Vertical; }; struct SplitPaneArgs : public SplitPaneArgsT { SplitPaneArgs() = default; - GETSET_PROPERTY(winrt::TerminalApp::SplitState, SplitStyle, winrt::TerminalApp::SplitState::None); + GETSET_PROPERTY(winrt::TerminalApp::SplitState, SplitStyle, winrt::TerminalApp::SplitState::Vertical); GETSET_PROPERTY(winrt::TerminalApp::NewTerminalArgs, TerminalArgs, nullptr); static constexpr std::string_view SplitKey{ "split" }; diff --git a/src/cascadia/TerminalApp/ActionArgs.idl b/src/cascadia/TerminalApp/ActionArgs.idl index 92b21f6f7b5..3f21db46c8a 100644 --- a/src/cascadia/TerminalApp/ActionArgs.idl +++ b/src/cascadia/TerminalApp/ActionArgs.idl @@ -25,10 +25,8 @@ namespace TerminalApp enum SplitState { - Automatic = -1, - None = 0, - Vertical = 1, - Horizontal = 2 + Vertical = 0, + Horizontal = 1 }; [default_interface] runtimeclass NewTerminalArgs { diff --git a/src/cascadia/TerminalApp/Pane.h b/src/cascadia/TerminalApp/Pane.h index cbc7072764a..767006eb8d2 100644 --- a/src/cascadia/TerminalApp/Pane.h +++ b/src/cascadia/TerminalApp/Pane.h @@ -19,154 +19,44 @@ // - Mike Griese (zadjii-msft) 16-May-2019 #pragma once -#include #include #include "../../cascadia/inc/cppwinrt_utils.h" -enum class Borders : int -{ - None = 0x0, - Top = 0x1, - Bottom = 0x2, - Left = 0x4, - Right = 0x8 -}; -DEFINE_ENUM_FLAG_OPERATORS(Borders); +class LeafPane; +class ParentPane; -class Pane : public std::enable_shared_from_this +class Pane { public: - Pane(const GUID& profile, - const winrt::Microsoft::Terminal::TerminalControl::TermControl& control, - const bool lastFocused = false); - - std::shared_ptr GetActivePane(); - winrt::Microsoft::Terminal::TerminalControl::TermControl GetTerminalControl(); - std::optional GetFocusedProfile(); + virtual ~Pane() = default; winrt::Windows::UI::Xaml::Controls::Grid GetRootElement(); - - bool WasLastFocused() const noexcept; - void UpdateVisuals(); - void ClearActive(); - void SetActive(); - - void UpdateSettings(const winrt::Microsoft::Terminal::Settings::TerminalSettings& settings, - const GUID& profile); - void ResizeContent(const winrt::Windows::Foundation::Size& newSize); - void Relayout(); - bool ResizePane(const winrt::TerminalApp::Direction& direction); - bool NavigateFocus(const winrt::TerminalApp::Direction& direction); - - bool CanSplit(winrt::TerminalApp::SplitState splitType); - std::pair, std::shared_ptr> Split(winrt::TerminalApp::SplitState splitType, - const GUID& profile, - const winrt::Microsoft::Terminal::TerminalControl::TermControl& control); float CalcSnappedDimension(const bool widthOrHeight, const float dimension) const; - void Close(); - - WINRT_CALLBACK(Closed, winrt::Windows::Foundation::EventHandler); - DECLARE_EVENT(GotFocus, _GotFocusHandlers, winrt::delegate>); + virtual std::shared_ptr FindActivePane() = 0; + virtual void Relayout() = 0; + virtual void ClearActive() = 0; + virtual void UpdateSettings(const winrt::Microsoft::Terminal::Settings::TerminalSettings& settings, + const GUID& profile) = 0; + virtual void ResizeContent(const winrt::Windows::Foundation::Size& newSize) = 0; -private: +protected: struct SnapSizeResult; struct SnapChildrenSizeResult; struct LayoutSizeNode; + Pane(); + winrt::Windows::UI::Xaml::Controls::Grid _root{}; - winrt::Windows::UI::Xaml::Controls::Border _border{}; - winrt::Microsoft::Terminal::TerminalControl::TermControl _control{ nullptr }; - static winrt::Windows::UI::Xaml::Media::SolidColorBrush s_focusedBorderBrush; - static winrt::Windows::UI::Xaml::Media::SolidColorBrush s_unfocusedBorderBrush; - - std::shared_ptr _firstChild{ nullptr }; - std::shared_ptr _secondChild{ nullptr }; - winrt::TerminalApp::SplitState _splitState{ winrt::TerminalApp::SplitState::None }; - float _desiredSplitPosition; - - bool _lastActive{ false }; - std::optional _profile{ std::nullopt }; - winrt::event_token _connectionStateChangedToken{ 0 }; - winrt::event_token _firstClosedToken{ 0 }; - winrt::event_token _secondClosedToken{ 0 }; - - winrt::Windows::UI::Xaml::UIElement::GotFocus_revoker _gotFocusRevoker; - - std::shared_mutex _createCloseLock{}; - - Borders _borders{ Borders::None }; - - bool _IsLeaf() const noexcept; - bool _HasFocusedChild() const noexcept; - void _SetupChildCloseHandlers(); - - bool _CanSplit(winrt::TerminalApp::SplitState splitType); - std::pair, std::shared_ptr> _Split(winrt::TerminalApp::SplitState splitType, - const GUID& profile, - const winrt::Microsoft::Terminal::TerminalControl::TermControl& control); - - void _CreateRowColDefinitions(const winrt::Windows::Foundation::Size& rootSize); - void _CreateSplitContent(); - void _ApplySplitDefinitions(); - void _UpdateBorders(); - - bool _Resize(const winrt::TerminalApp::Direction& direction); - bool _NavigateFocus(const winrt::TerminalApp::Direction& direction); - - void _CloseChild(const bool closeFirst); - winrt::fire_and_forget _CloseChildRoutine(const bool closeFirst); - - void _FocusFirstChild(); - void _ControlConnectionStateChangedHandler(const winrt::Microsoft::Terminal::TerminalControl::TermControl& sender, const winrt::Windows::Foundation::IInspectable& /*args*/); - void _ControlGotFocusHandler(winrt::Windows::Foundation::IInspectable const& sender, - winrt::Windows::UI::Xaml::RoutedEventArgs const& e); - - std::pair _CalcChildrenSizes(const float fullSize) const; - SnapChildrenSizeResult _CalcSnappedChildrenSizes(const bool widthOrHeight, const float fullSize) const; - SnapSizeResult _CalcSnappedDimension(const bool widthOrHeight, const float dimension) const; - void _AdvanceSnappedDimension(const bool widthOrHeight, LayoutSizeNode& sizeNode) const; - - winrt::Windows::Foundation::Size _GetMinSize() const; - LayoutSizeNode _CreateMinSizeTree(const bool widthOrHeight) const; - float _ClampSplitPosition(const bool widthOrHeight, const float requestedValue, const float totalSize) const; - - winrt::TerminalApp::SplitState _convertAutomaticSplitState(const winrt::TerminalApp::SplitState& splitType) const; - // Function Description: - // - Returns true if the given direction can be used with the given split - // type. - // - This is used for pane resizing (which will need a pane separator - // that's perpendicular to the direction to be able to move the separator - // in that direction). - // - Additionally, it will be used for moving focus between panes, which - // again happens _across_ a separator. - // Arguments: - // - direction: The Direction to compare - // - splitType: The winrt::TerminalApp::SplitState to compare - // Return Value: - // - true iff the direction is perpendicular to the splitType. False for - // winrt::TerminalApp::SplitState::None. - static constexpr bool DirectionMatchesSplit(const winrt::TerminalApp::Direction& direction, - const winrt::TerminalApp::SplitState& splitType) - { - if (splitType == winrt::TerminalApp::SplitState::None) - { - return false; - } - else if (splitType == winrt::TerminalApp::SplitState::Horizontal) - { - return direction == winrt::TerminalApp::Direction::Up || - direction == winrt::TerminalApp::Direction::Down; - } - else if (splitType == winrt::TerminalApp::SplitState::Vertical) - { - return direction == winrt::TerminalApp::Direction::Left || - direction == winrt::TerminalApp::Direction::Right; - } - return false; - } - - static void _SetupResources(); + + virtual std::shared_ptr _FindFirstLeaf() = 0; + virtual SnapSizeResult _CalcSnappedDimension(const bool widthOrHeight, const float dimension) const = 0; + virtual void _AdvanceSnappedDimension(const bool widthOrHeight, LayoutSizeNode& sizeNode) const = 0; + virtual winrt::Windows::Foundation::Size _GetMinSize() const = 0; + virtual LayoutSizeNode _CreateMinSizeTree(const bool widthOrHeight) const; + + friend class LeafPane; + friend class ParentPane; struct SnapSizeResult { @@ -205,3 +95,6 @@ class Pane : public std::enable_shared_from_this void _AssignChildNode(std::unique_ptr& nodeField, const LayoutSizeNode* const newNode); }; }; + +#include "LeafPane.h" +#include "ParentPane.h" diff --git a/src/cascadia/TerminalApp/Tab.cpp b/src/cascadia/TerminalApp/Tab.cpp index cea9db3470c..9ccfccddc04 100644 --- a/src/cascadia/TerminalApp/Tab.cpp +++ b/src/cascadia/TerminalApp/Tab.cpp @@ -4,6 +4,7 @@ #include "pch.h" #include "Tab.h" #include "Utils.h" +#include using namespace winrt::Windows::UI::Xaml; using namespace winrt::Windows::UI::Core; @@ -17,15 +18,57 @@ namespace winrt Tab::Tab(const GUID& profile, const TermControl& control) { - _rootPane = std::make_shared(profile, control, true); + _rootPane = std::make_shared(profile, control, true); + _MakeTabViewItem(); +} - _rootPane->Closed([=](auto&& /*s*/, auto&& /*e*/) { - _ClosedHandlers(nullptr, nullptr); - }); +Tab::~Tab() +{ + _RemoveAllRootPaneEventHandlers(); + OutputDebugString(L"~Tab()\n"); +} - _activePane = _rootPane; +void Tab::_SetupRootPaneEventHandlers() +{ + if (const auto rootAsLeaf = std::dynamic_pointer_cast(_rootPane)) + { + _rootPaneClosedToken = rootAsLeaf->Closed([=](auto&& /*s*/, auto&& /*e*/) { + _RemoveAllRootPaneEventHandlers(); - _MakeTabViewItem(); + _ClosedHandlers(nullptr, nullptr); + }); + + _rootPaneTypeChangedToken = rootAsLeaf->Splitted([=](std::shared_ptr splittedPane) { + _RemoveAllRootPaneEventHandlers(); + + _rootPane = splittedPane; + _SetupRootPaneEventHandlers(); + _RootPaneChangedHandlers(); + }); + } + else if (const auto rootAsParent = std::dynamic_pointer_cast(_rootPane)) + { + _rootPaneTypeChangedToken = rootAsParent->ChildClosed([=](std::shared_ptr collapsedPane) { + _RemoveAllRootPaneEventHandlers(); + + _rootPane = collapsedPane; + _SetupRootPaneEventHandlers(); + _RootPaneChangedHandlers(); + }); + } +} + +void Tab::_RemoveAllRootPaneEventHandlers() +{ + if (const auto rootAsLeaf = std::dynamic_pointer_cast(_rootPane)) + { + rootAsLeaf->Closed(_rootPaneClosedToken); + rootAsLeaf->Splitted(_rootPaneTypeChangedToken); + } + else if (const auto rootAsParent = std::dynamic_pointer_cast(_rootPane)) + { + rootAsParent->ChildClosed(_rootPaneTypeChangedToken); + } } void Tab::_MakeTabViewItem() @@ -51,7 +94,7 @@ UIElement Tab::GetRootElement() // that was last focused. TermControl Tab::GetActiveTerminalControl() const { - return _activePane->GetTerminalControl(); + return _rootPane->FindActivePane()->GetTerminalControl(); } winrt::MUX::Controls::TabViewItem Tab::GetTabViewItem() @@ -101,7 +144,7 @@ void Tab::SetFocused(const bool focused) // focused, else the GUID of the profile of the last control to be focused std::optional Tab::GetFocusedProfile() const noexcept { - return _activePane->GetFocusedProfile(); + return _rootPane->FindActivePane()->GetProfile(); } // Method Description: @@ -111,10 +154,14 @@ std::optional Tab::GetFocusedProfile() const noexcept // - control: reference to the TermControl object to bind event to // Return Value: // - -void Tab::BindEventHandlers(const TermControl& control) noexcept +void Tab::BindEventHandlers() noexcept { - _AttachEventHandlersToPane(_rootPane); - _AttachEventHandlersToControl(control); + const auto rootPaneAsLeaf = std::dynamic_pointer_cast(_rootPane); + THROW_HR_IF_NULL(E_FAIL, rootPaneAsLeaf); + + _SetupRootPaneEventHandlers(); + _AttachEventHandlersToPane(rootPaneAsLeaf); + _AttachEventHandlersToControl(rootPaneAsLeaf->GetTerminalControl()); } // Method Description: @@ -225,7 +272,7 @@ winrt::fire_and_forget Tab::Scroll(const int delta) // - True if the focused pane can be split. False otherwise. bool Tab::CanSplitPane(winrt::TerminalApp::SplitState splitType) { - return _activePane->CanSplit(splitType); + return _rootPane->FindActivePane()->CanSplit(splitType); } // Method Description: @@ -239,14 +286,13 @@ bool Tab::CanSplitPane(winrt::TerminalApp::SplitState splitType) // - void Tab::SplitPane(winrt::TerminalApp::SplitState splitType, const GUID& profile, TermControl& control) { - auto [first, second] = _activePane->Split(splitType, profile, control); + const auto result = _rootPane->FindActivePane()->Split(splitType, profile, control); _AttachEventHandlersToControl(control); - // Add a event handlers to the new panes' GotFocus event. When the pane + // Add a event handlers to the new pane's GotFocus event. When the pane // gains focus, we'll mark it as the new active pane. - _AttachEventHandlersToPane(first); - _AttachEventHandlersToPane(second); + _AttachEventHandlersToPane(result.secondChild); } // Method Description: @@ -281,7 +327,12 @@ void Tab::ResizePane(const winrt::TerminalApp::Direction& direction) { // NOTE: This _must_ be called on the root pane, so that it can propogate // throughout the entire tree. - _rootPane->ResizePane(direction); + + //_rootPane->ResizePane(direction); + if (auto rootPaneAsParent = std::dynamic_pointer_cast(_rootPane)) + { + rootPaneAsParent->ResizePane(direction); + } } // Method Description: @@ -295,7 +346,12 @@ void Tab::NavigateFocus(const winrt::TerminalApp::Direction& direction) { // NOTE: This _must_ be called on the root pane, so that it can propogate // throughout the entire tree. - _rootPane->NavigateFocus(direction); + + //_rootPane->NavigateFocus(direction); + if (auto rootPaneAsParent = std::dynamic_pointer_cast(_rootPane)) + { + rootPaneAsParent->NavigateFocus(direction); + } } // Method Description: @@ -308,7 +364,7 @@ void Tab::NavigateFocus(const winrt::TerminalApp::Direction& direction) // - void Tab::ClosePane() { - _activePane->Close(); + _rootPane->FindActivePane()->Close(); } // Method Description: @@ -359,20 +415,18 @@ void Tab::_AttachEventHandlersToControl(const TermControl& control) // - // Return Value: // - -void Tab::_AttachEventHandlersToPane(std::shared_ptr pane) +void Tab::_AttachEventHandlersToPane(std::shared_ptr pane) { std::weak_ptr weakThis{ shared_from_this() }; - pane->GotFocus([weakThis](std::shared_ptr sender) { + pane->GotFocus([weakThis](std::shared_ptr sender) { // Do nothing if the Tab's lifetime is expired or pane isn't new. auto tab{ weakThis.lock() }; - - if (tab && sender != tab->_activePane) + if (tab && sender != tab->_rootPane->FindActivePane()) { // Clear the active state of the entire tree, and mark only the sender as active. tab->_rootPane->ClearActive(); - tab->_activePane = sender; - tab->_activePane->SetActive(); + sender->SetActive(); // Update our own title text to match the newly-active pane. tab->SetTabText(tab->GetActiveTitle()); @@ -384,3 +438,4 @@ void Tab::_AttachEventHandlersToPane(std::shared_ptr pane) } DEFINE_EVENT(Tab, ActivePaneChanged, _ActivePaneChangedHandlers, winrt::delegate<>); +DEFINE_EVENT(Tab, RootPaneChanged, _RootPaneChangedHandlers, winrt::delegate<>); diff --git a/src/cascadia/TerminalApp/Tab.h b/src/cascadia/TerminalApp/Tab.h index 56a97f75653..9083817a27f 100644 --- a/src/cascadia/TerminalApp/Tab.h +++ b/src/cascadia/TerminalApp/Tab.h @@ -8,9 +8,10 @@ class Tab : public std::enable_shared_from_this { public: Tab(const GUID& profile, const winrt::Microsoft::Terminal::TerminalControl::TermControl& control); + ~Tab(); // Called after construction to setup events with weak_ptr - void BindEventHandlers(const winrt::Microsoft::Terminal::TerminalControl::TermControl& control) noexcept; + void BindEventHandlers() noexcept; winrt::Microsoft::UI::Xaml::Controls::TabViewItem GetTabViewItem(); winrt::Windows::UI::Xaml::UIElement GetRootElement(); @@ -41,18 +42,23 @@ class Tab : public std::enable_shared_from_this WINRT_CALLBACK(Closed, winrt::Windows::Foundation::EventHandler); DECLARE_EVENT(ActivePaneChanged, _ActivePaneChangedHandlers, winrt::delegate<>); + DECLARE_EVENT(RootPaneChanged, _RootPaneChangedHandlers, winrt::delegate<>); private: std::shared_ptr _rootPane{ nullptr }; - std::shared_ptr _activePane{ nullptr }; winrt::hstring _lastIconPath{}; bool _focused{ false }; winrt::Microsoft::UI::Xaml::Controls::TabViewItem _tabViewItem{ nullptr }; + winrt::event_token _rootPaneClosedToken{ 0 }; + winrt::event_token _rootPaneTypeChangedToken{ 0 }; + void _MakeTabViewItem(); + void _SetupRootPaneEventHandlers(); + void _RemoveAllRootPaneEventHandlers(); void _Focus(); void _AttachEventHandlersToControl(const winrt::Microsoft::Terminal::TerminalControl::TermControl& control); - void _AttachEventHandlersToPane(std::shared_ptr pane); + void _AttachEventHandlersToPane(std::shared_ptr pane); }; diff --git a/src/cascadia/TerminalApp/TerminalPage.cpp b/src/cascadia/TerminalApp/TerminalPage.cpp index 70e98b95676..244123902a5 100644 --- a/src/cascadia/TerminalApp/TerminalPage.cpp +++ b/src/cascadia/TerminalApp/TerminalPage.cpp @@ -970,12 +970,6 @@ namespace winrt::TerminalApp::implementation void TerminalPage::_SplitPane(const TerminalApp::SplitState splitType, const winrt::TerminalApp::NewTerminalArgs& newTerminalArgs) { - // Do nothing if we're requesting no split. - if (splitType == TerminalApp::SplitState::None) - { - return; - } - const auto [realGuid, controlSettings] = _settings->BuildSettings(newTerminalArgs); const auto controlConnection = _CreateConnectionFromSettings(realGuid, controlSettings); diff --git a/src/cascadia/TerminalApp/lib/LeafPane.cpp b/src/cascadia/TerminalApp/lib/LeafPane.cpp new file mode 100644 index 00000000000..44434face71 --- /dev/null +++ b/src/cascadia/TerminalApp/lib/LeafPane.cpp @@ -0,0 +1,448 @@ +#include "pch.h" +#include "LeafPane.h" +#include "Profile.h" +#include "CascadiaSettings.h" + +using namespace winrt::Windows::Foundation; +using namespace winrt::Windows::UI; +using namespace winrt::Windows::UI::Xaml; +using namespace winrt::Windows::UI::Core; +using namespace winrt::Windows::UI::Xaml::Media; +using namespace winrt::Microsoft::Terminal::Settings; +using namespace winrt::Microsoft::Terminal::TerminalControl; +using namespace winrt::Microsoft::Terminal::TerminalConnection; +using namespace winrt::TerminalApp; +using namespace TerminalApp; + +static const int PaneBorderSize = 2; +static const int CombinedPaneBorderSize = 2 * PaneBorderSize; +static const float Half = 0.50f; +winrt::Windows::UI::Xaml::Media::SolidColorBrush LeafPane::s_focusedBorderBrush = { nullptr }; +winrt::Windows::UI::Xaml::Media::SolidColorBrush LeafPane::s_unfocusedBorderBrush = { nullptr }; + +LeafPane::LeafPane(const GUID& profile, const TermControl& control, const bool lastFocused) : + _control{ control }, + _lastActive{ lastFocused }, + _profile{ profile } +{ + _root.Children().Append(_border); + _border.Child(_control); + + _connectionStateChangedToken = _control.ConnectionStateChanged({ this, &LeafPane::_ControlConnectionStateChangedHandler }); + + // On the first Pane's creation, lookup resources we'll use to theme the + // Pane, including the brushed to use for the focused/unfocused border + // color. + if (s_focusedBorderBrush == nullptr || s_unfocusedBorderBrush == nullptr) + { + _SetupResources(); + } + + // Register an event with the control to have it inform us when it gains focus. + _gotFocusRevoker = control.GotFocus(winrt::auto_revoke, { this, &LeafPane::_ControlGotFocusHandler }); + + // When our border is tapped, make sure to transfer focus to our control. + // LOAD-BEARING: This will NOT work if the border's BorderBrush is set to + // Colors::Transparent! The border won't get Tapped events, and they'll fall + // through to something else. + _border.Tapped([this](auto&, auto& e) { + SetActive(); + e.Handled(true); + }); +} + +LeafPane::~LeafPane() +{ + OutputDebugString(L"~LeafPane()\n"); +} + +// Method Description: +// - Recalculates and reapplies sizes of all descendant panes. +// Arguments: +// - +// Return Value: +// - +void LeafPane::Relayout() +{ +} + +std::shared_ptr LeafPane::FindActivePane() +{ + return _lastActive ? shared_from_this() : nullptr; +} + +void LeafPane::ClearActive() +{ + _lastActive = false; + _UpdateVisuals(); +} + +void LeafPane::UpdateSettings(const TerminalSettings& settings, const GUID& profile) +{ + if (profile == _profile) + { + _control.UpdateSettings(settings); + } +} + +// Method Description: +// - Determines whether the pane can be split. +// Arguments: +// - splitType: what type of split we want to create. +// Return Value: +// - True if the pane can be split. False otherwise. +bool LeafPane::CanSplit(SplitState splitType) +{ + const Size actualSize{ gsl::narrow_cast(_root.ActualWidth()), + gsl::narrow_cast(_root.ActualHeight()) }; + + const Size minSize = _GetMinSize(); + + if (splitType == SplitState::Vertical) + { + const auto widthMinusSeparator = actualSize.Width - CombinedPaneBorderSize; + const auto newWidth = widthMinusSeparator * Half; + + return newWidth > minSize.Width; + } + + if (splitType == SplitState::Horizontal) + { + const auto heightMinusSeparator = actualSize.Height - CombinedPaneBorderSize; + const auto newHeight = heightMinusSeparator * Half; + + return newHeight > minSize.Height; + } + + return false; +} + +LeafPane::SplitResult LeafPane::Split(winrt::TerminalApp::SplitState splitType, + const GUID& profile, + const winrt::Microsoft::Terminal::TerminalControl::TermControl& control) +{ + const auto secondLeaf = std::make_shared(profile, control); + + if (splitType == SplitState::Vertical) + { + secondLeaf->_borders = _borders | Borders::Left; + _borders = _borders | Borders::Right; + } + else + { + secondLeaf->_borders = _borders | Borders::Top; + _borders = _borders | Borders::Bottom; + } + + _UpdateBorders(); + secondLeaf->_UpdateBorders(); + + ClearActive(); + secondLeaf->SetActive(); + + Size actualSize{ gsl::narrow_cast(_root.ActualWidth()), + gsl::narrow_cast(_root.ActualHeight()) }; + const auto newParent = std::make_shared(shared_from_this(), secondLeaf, splitType, actualSize); + + _SplittedHandlers(newParent); + newParent->InitializeChildren(); + + return { newParent, shared_from_this(), secondLeaf }; +} + +Pane::SnapSizeResult LeafPane::_CalcSnappedDimension(const bool widthOrHeight, const float dimension) const +{ + // If we're a leaf pane, align to the grid of controlling terminal + + const auto minSize = _GetMinSize(); + const auto minDimension = widthOrHeight ? minSize.Width : minSize.Height; + + if (dimension <= minDimension) + { + return { minDimension, minDimension }; + } + + float lower = _control.SnapDimensionToGrid(widthOrHeight, dimension); + if (widthOrHeight) + { + lower += WI_IsFlagSet(_borders, Borders::Left) ? PaneBorderSize : 0; + lower += WI_IsFlagSet(_borders, Borders::Right) ? PaneBorderSize : 0; + } + else + { + lower += WI_IsFlagSet(_borders, Borders::Top) ? PaneBorderSize : 0; + lower += WI_IsFlagSet(_borders, Borders::Bottom) ? PaneBorderSize : 0; + } + + if (lower == dimension) + { + // If we happen to be already snapped, then just return this size + // as both lower and higher values. + return { lower, lower }; + } + else + { + const auto cellSize = _control.CharacterDimensions(); + const auto higher = lower + (widthOrHeight ? cellSize.Width : cellSize.Height); + return { lower, higher }; + } +} + +void LeafPane::_AdvanceSnappedDimension(const bool widthOrHeight, LayoutSizeNode& sizeNode) const +{ + // We're a leaf pane, so just add one more row or column (unless isMinimumSize + // is true, see below). + + if (sizeNode.isMinimumSize) + { + // If the node is of its minimum size, this size might not be snapped (it might + // be, say, half a character, or fixed 10 pixels), so snap it upward. It might + // however be already snapped, so add 1 to make sure it really increases + // (not strictly necessary but to avoid surprises). + sizeNode.size = _CalcSnappedDimension(widthOrHeight, sizeNode.size + 1).higher; + } + else + { + const auto cellSize = _control.CharacterDimensions(); + sizeNode.size += widthOrHeight ? cellSize.Width : cellSize.Height; + } + + // Because we have grown, we're certainly no longer of our + // minimal size (if we've ever been). + sizeNode.isMinimumSize = false; +} + +Size LeafPane::_GetMinSize() const +{ + auto controlSize = _control.MinimumSize(); + auto newWidth = controlSize.Width; + auto newHeight = controlSize.Height; + + newWidth += WI_IsFlagSet(_borders, Borders::Left) ? PaneBorderSize : 0; + newWidth += WI_IsFlagSet(_borders, Borders::Right) ? PaneBorderSize : 0; + newHeight += WI_IsFlagSet(_borders, Borders::Top) ? PaneBorderSize : 0; + newHeight += WI_IsFlagSet(_borders, Borders::Bottom) ? PaneBorderSize : 0; + + return { newWidth, newHeight }; +} + +// Method Description: +// - Gets the TermControl of this pane. If this Pane is not a leaf, this will return nullptr. +// Arguments: +// - +// Return Value: +// - nullptr if this Pane is a parent, otherwise the TermControl of this Pane. +TermControl LeafPane::GetTerminalControl() +{ + return _control; +} + +// Method Description: +// - Returns true if this pane was the last pane to be focused in a tree of panes. +// Arguments: +// - +// Return Value: +// - true iff we were the last pane focused in this tree of panes. +bool LeafPane::WasLastActive() const noexcept +{ + return _lastActive; +} + +// Method Description: +// - Update the focus state of this pane. We'll make sure to colorize our +// borders depending on if we are the active pane or not. +// Arguments: +// - +// Return Value: +// - +void LeafPane::_UpdateVisuals() +{ + _border.BorderBrush(_lastActive ? s_focusedBorderBrush : s_unfocusedBorderBrush); +} + +// Method Description: +// - Sets the "Active" state on this Pane. Only one Pane in a tree of Panes +// should be "active", and that pane should be a leaf. +// - Updates our visuals to match our new state, including highlighting our borders. +// Arguments: +// - +// Return Value: +// - +void LeafPane::SetActive() +{ + _lastActive = true; + _control.Focus(FocusState::Programmatic); + _UpdateVisuals(); +} + +// Method Description: +// - Sets the thickness of each side of our borders to match our _borders state. +// Arguments: +// - +// Return Value: +// - +void LeafPane::_UpdateBorders() +{ + double top = 0, bottom = 0, left = 0, right = 0; + + Thickness newBorders{ 0 }; + if (WI_IsFlagSet(_borders, Borders::Top)) + { + top = PaneBorderSize; + } + if (WI_IsFlagSet(_borders, Borders::Bottom)) + { + bottom = PaneBorderSize; + } + if (WI_IsFlagSet(_borders, Borders::Left)) + { + left = PaneBorderSize; + } + if (WI_IsFlagSet(_borders, Borders::Right)) + { + right = PaneBorderSize; + } + _border.BorderThickness(ThicknessHelper::FromLengths(left, top, right, bottom)); +} + +// Method Description: +// - Called when our attached control is closed. Triggers listeners to our close +// event, if we're a leaf pane. +// - If this was called, and we became a parent pane (due to work on another +// thread), this function will do nothing (allowing the control's new parent +// to handle the event instead). +// Arguments: +// - +// Return Value: +// - +winrt::fire_and_forget LeafPane::_ControlConnectionStateChangedHandler(const TermControl& /*sender*/, const winrt::Windows::Foundation::IInspectable& /*args*/) +{ + const auto newConnectionState = _control.ConnectionState(); + + co_await winrt::resume_foreground(_root.Dispatcher()); + + if (newConnectionState < ConnectionState::Closed) + { + // Pane doesn't care if the connection isn't entering a terminal state. + co_return; + } + + const auto& settings = CascadiaSettings::GetCurrentAppSettings(); + auto paneProfile = settings.FindProfile(_profile); + if (paneProfile) + { + auto mode = paneProfile->GetCloseOnExitMode(); + if ((mode == CloseOnExitMode::Always) || + (mode == CloseOnExitMode::Graceful && newConnectionState == ConnectionState::Closed)) + { + _ClosedHandlers(nullptr, nullptr); + } + } +} + +// Event Description: +// - Called when our control gains focus. We'll use this to trigger our GotFocus +// callback. The tab that's hosting us should have registered a callback which +// can be used to mark us as active. +// Arguments: +// - +// Return Value: +// - +void LeafPane::_ControlGotFocusHandler(winrt::Windows::Foundation::IInspectable const& /* sender */, + RoutedEventArgs const& /* args */) +{ + _GotFocusHandlers(shared_from_this()); +} + +// Function Description: +// - Attempts to load some XAML resources that the Pane will need. This includes: +// * The Color we'll use for active Panes's borders - SystemAccentColor +// * The Brush we'll use for inactive Panes - TabViewBackground (to match the +// color of the titlebar) +// Arguments: +// - +// Return Value: +// - +void LeafPane::_SetupResources() +{ + const auto res = Application::Current().Resources(); + const auto accentColorKey = winrt::box_value(L"SystemAccentColor"); + if (res.HasKey(accentColorKey)) + { + const auto colorFromResources = res.Lookup(accentColorKey); + // If SystemAccentColor is _not_ a Color for some reason, use + // Transparent as the color, so we don't do this process again on + // the next pane (by leaving s_focusedBorderBrush nullptr) + auto actualColor = winrt::unbox_value_or(colorFromResources, Colors::Black()); + s_focusedBorderBrush = SolidColorBrush(actualColor); + } + else + { + // DON'T use Transparent here - if it's "Transparent", then it won't + // be able to hittest for clicks, and then clicking on the border + // will eat focus. + s_focusedBorderBrush = SolidColorBrush{ Colors::Black() }; + } + + const auto tabViewBackgroundKey = winrt::box_value(L"TabViewBackground"); + if (res.HasKey(tabViewBackgroundKey)) + { + winrt::Windows::Foundation::IInspectable obj = res.Lookup(tabViewBackgroundKey); + s_unfocusedBorderBrush = obj.try_as(); + } + else + { + // DON'T use Transparent here - if it's "Transparent", then it won't + // be able to hittest for clicks, and then clicking on the border + // will eat focus. + s_unfocusedBorderBrush = SolidColorBrush{ Colors::Black() }; + } +} + +std::shared_ptr LeafPane::_FindFirstLeaf() +{ + return shared_from_this(); + //_control.Focus(FocusState::Programmatic); +} + +void LeafPane::ResizeContent(const winrt::Windows::Foundation::Size& /* newSize */) +{ +} + +void LeafPane::OnNeightbourClosed(std::shared_ptr closedNeightbour) +{ + _borders = _borders & closedNeightbour->_borders; + _lastActive = _lastActive || closedNeightbour->_lastActive; + + // If we're inheriting the "last active" state from one of our children, + // focus our control now. This should trigger our own GotFocus event. + if (_lastActive) + { + _control.Focus(FocusState::Programmatic); + } + + _UpdateBorders(); +} + +GUID LeafPane::GetProfile() +{ + return _profile; +} + +void LeafPane::Close() +{ + // Fire our Closed event to tell our parent that we should be removed. + _ClosedHandlers(nullptr, nullptr); +} + +//DEFINE_EVENT(LeafPane, Splitted, _SplittedHandlers, winrt::delegate>); +DEFINE_EVENT(LeafPane, GotFocus, _GotFocusHandlers, winrt::delegate>); + +winrt::event_token LeafPane::Splitted(winrt::delegate> const& handler) +{ + return _SplittedHandlers.add(handler); +} +void LeafPane::Splitted(winrt::event_token const& token) noexcept +{ + _SplittedHandlers.remove(token); +} diff --git a/src/cascadia/TerminalApp/lib/LeafPane.h b/src/cascadia/TerminalApp/lib/LeafPane.h new file mode 100644 index 00000000000..8092224b323 --- /dev/null +++ b/src/cascadia/TerminalApp/lib/LeafPane.h @@ -0,0 +1,83 @@ +#pragma once +#include "Pane.h" +#include +#include "../../cascadia/inc/cppwinrt_utils.h" +#include + +enum class Borders : int +{ + None = 0x0, + Top = 0x1, + Bottom = 0x2, + Left = 0x4, + Right = 0x8 +}; +DEFINE_ENUM_FLAG_OPERATORS(Borders); + +class LeafPane : public Pane, public std::enable_shared_from_this +{ +public: + struct SplitResult; + + LeafPane(const GUID& profile, + const winrt::Microsoft::Terminal::TerminalControl::TermControl& control, + const bool lastFocused = false); + ~LeafPane() override; + + void Relayout() override; + void ClearActive() override; + void UpdateSettings(const winrt::Microsoft::Terminal::Settings::TerminalSettings& settings, + const GUID& profile); + void ResizeContent(const winrt::Windows::Foundation::Size& newSize) override; + std::shared_ptr FindActivePane() override; + bool CanSplit(winrt::TerminalApp::SplitState splitType); + SplitResult Split(winrt::TerminalApp::SplitState splitType, + const GUID& profile, + const winrt::Microsoft::Terminal::TerminalControl::TermControl& control); + winrt::Microsoft::Terminal::TerminalControl::TermControl GetTerminalControl(); + bool WasLastActive() const noexcept; + void SetActive(); + GUID GetProfile(); + void OnNeightbourClosed(std::shared_ptr closedNeightbour); + void Close(); + + struct SplitResult + { + std::shared_ptr newParent; + std::shared_ptr firstChild; + std::shared_ptr secondChild; + }; + + friend class Pane; + + WINRT_CALLBACK(Closed, winrt::Windows::Foundation::EventHandler); + DECLARE_EVENT(Splitted, _SplittedHandlers, winrt::delegate>); + DECLARE_EVENT(GotFocus, _GotFocusHandlers, winrt::delegate>); +private: + static winrt::Windows::UI::Xaml::Media::SolidColorBrush s_focusedBorderBrush; + static winrt::Windows::UI::Xaml::Media::SolidColorBrush s_unfocusedBorderBrush; + + Borders _borders{ Borders::None }; + bool _lastActive{ false }; + winrt::Windows::UI::Xaml::Controls::Border _border{}; + winrt::Microsoft::Terminal::TerminalControl::TermControl _control{ nullptr }; + + GUID _profile; + winrt::event_token _connectionStateChangedToken{ 0 }; + winrt::Windows::UI::Xaml::UIElement::GotFocus_revoker _gotFocusRevoker; + + void _UpdateBorders(); + void _UpdateVisuals(); + winrt::fire_and_forget _ControlConnectionStateChangedHandler( + const winrt::Microsoft::Terminal::TerminalControl::TermControl& sender, + const winrt::Windows::Foundation::IInspectable& /*args*/); + void _ControlGotFocusHandler(winrt::Windows::Foundation::IInspectable const& sender, + winrt::Windows::UI::Xaml::RoutedEventArgs const& e); + + static void _SetupResources(); + + std::shared_ptr _FindFirstLeaf() override; + SnapSizeResult _CalcSnappedDimension(const bool widthOrHeight, const float dimension) const override; + void _AdvanceSnappedDimension(const bool widthOrHeight, LayoutSizeNode& sizeNode) const override; + winrt::Windows::Foundation::Size _GetMinSize() const override; +}; diff --git a/src/cascadia/TerminalApp/lib/ParentPane.cpp b/src/cascadia/TerminalApp/lib/ParentPane.cpp new file mode 100644 index 00000000000..b106af8bb1a --- /dev/null +++ b/src/cascadia/TerminalApp/lib/ParentPane.cpp @@ -0,0 +1,732 @@ +#include "pch.h" +#include "ParentPane.h" +#include "Profile.h" +#include "CascadiaSettings.h" + +using namespace winrt::Windows::Foundation; +using namespace winrt::Windows::UI; +using namespace winrt::Windows::UI::Xaml; +using namespace winrt::Windows::UI::Core; +using namespace winrt::Windows::UI::Xaml::Media; +using namespace winrt::Microsoft::Terminal::Settings; +using namespace winrt::Microsoft::Terminal::TerminalControl; +using namespace winrt::Microsoft::Terminal::TerminalConnection; +using namespace winrt::TerminalApp; +using namespace TerminalApp; + +static const float Half = 0.50f; + +ParentPane::ParentPane(std::shared_ptr firstChild, std::shared_ptr secondChild, SplitState splitState, Size currentSize) : + _firstChild(std::static_pointer_cast(firstChild)), + _secondChild(std::static_pointer_cast(secondChild)), + _splitState(splitState), + _desiredSplitPosition(Half) + +{ + _CreateRowColDefinitions(currentSize); + + if (_splitState == SplitState::Vertical) + { + Controls::Grid::SetColumn(firstChild->GetRootElement(), 0); + Controls::Grid::SetColumn(secondChild->GetRootElement(), 1); + } + else + { + Controls::Grid::SetRow(firstChild->GetRootElement(), 0); + Controls::Grid::SetRow(secondChild->GetRootElement(), 1); + } +} + +ParentPane::~ParentPane() +{ + _RemoveAllChildEventHandlers(true); + _RemoveAllChildEventHandlers(false); + + OutputDebugString(L"~ParentPane()\n"); +} + +void ParentPane::InitializeChildren() +{ + _root.Children().Append(_firstChild->GetRootElement()); + _root.Children().Append(_secondChild->GetRootElement()); + _SetupChildEventHandlers(true); + _SetupChildEventHandlers(false); +} + +void ParentPane::_SetupChildEventHandlers(bool firstChild) +{ + auto& child = firstChild ? _firstChild : _secondChild; + auto& closedToken = firstChild ? _firstClosedToken : _secondClosedToken; + auto& typeChangedToken = firstChild ? _firstTypeChangedToken : _secondTypeChangedToken; + + if (const auto childAsLeaf = std::dynamic_pointer_cast(child)) + { + closedToken = childAsLeaf->Closed([=, &child](auto&& /*s*/, auto&& /*e*/) { + _RemoveAllChildEventHandlers(true); + _RemoveAllChildEventHandlers(false); + + _CloseChild(firstChild); + }); + + typeChangedToken = childAsLeaf->Splitted([=, &child](std::shared_ptr splittedChild) { + _RemoveAllChildEventHandlers(firstChild); + + child = splittedChild; + _root.Children().SetAt(firstChild ? 0 : 1, child->GetRootElement()); + if (_splitState == SplitState::Vertical) + { + Controls::Grid::SetColumn(child->GetRootElement(), firstChild ? 0 : 1); + } + else + { + Controls::Grid::SetRow(child->GetRootElement(), firstChild ? 0 : 1); + } + + _SetupChildEventHandlers(firstChild); + }); + } + else if (const auto childAsParent = std::dynamic_pointer_cast(child)) + { + typeChangedToken = childAsParent->ChildClosed([=, &child](std::shared_ptr collapsedChild) { + _RemoveAllChildEventHandlers(firstChild); + + child = collapsedChild; + _root.Children().SetAt(firstChild ? 0 : 1, child->GetRootElement()); + if (_splitState == SplitState::Vertical) + { + Controls::Grid::SetColumn(child->GetRootElement(), firstChild ? 0 : 1); + } + else + { + Controls::Grid::SetRow(child->GetRootElement(), firstChild ? 0 : 1); + } + + _SetupChildEventHandlers(firstChild); + }); + } +} + +void ParentPane::_RemoveAllChildEventHandlers(bool firstChild) +{ + const auto child = firstChild ? _firstChild : _secondChild; + const auto closedToken = firstChild ? _firstClosedToken : _secondClosedToken; + const auto typeChangedToken = firstChild ? _firstTypeChangedToken : _secondTypeChangedToken; + + if (const auto childAsLeaf = std::dynamic_pointer_cast(child)) + { + childAsLeaf->Closed(closedToken); + childAsLeaf->Splitted(typeChangedToken); + } + else if (const auto childAsParent = std::dynamic_pointer_cast(child)) + { + childAsParent->ChildClosed(typeChangedToken); + } +} + +// Method Description: +// - Recalculates and reapplies sizes of all descendant panes. +// Arguments: +// - +// Return Value: +// - +void ParentPane::Relayout() +{ + ResizeContent(_root.ActualSize()); +} + +std::shared_ptr ParentPane::FindActivePane() +{ + auto firstFocused = _firstChild->FindActivePane(); + if (firstFocused != nullptr) + { + return firstFocused; + } + return _secondChild->FindActivePane(); +} + +void ParentPane::ClearActive() +{ + _firstChild->ClearActive(); + _secondChild->ClearActive(); +} + +void ParentPane::UpdateSettings(const TerminalSettings& settings, const GUID& profile) +{ + _firstChild->UpdateSettings(settings, profile); + _secondChild->UpdateSettings(settings, profile); +} + +Pane::SnapSizeResult ParentPane::_CalcSnappedDimension(const bool widthOrHeight, const float dimension) const +{ + if (_splitState == (widthOrHeight ? SplitState::Horizontal : SplitState::Vertical)) + { + // If we're resizing along separator axis, snap to the closest possibility + // given by our children panes. + + const auto firstSnapped = _firstChild->_CalcSnappedDimension(widthOrHeight, dimension); + const auto secondSnapped = _secondChild->_CalcSnappedDimension(widthOrHeight, dimension); + return { + std::max(firstSnapped.lower, secondSnapped.lower), + std::min(firstSnapped.higher, secondSnapped.higher) + }; + } + else + { + // If we're resizing perpendicularly to separator axis, calculate the sizes + // of child panes that would fit the given size. We use same algorithm that + // is used for real resize routine, but exclude the remaining empty space that + // would appear after the second pane. This will be the 'downward' snap possibility, + // while the 'upward' will be given as a side product of the layout function. + + const auto childSizes = _CalcSnappedChildrenSizes(widthOrHeight, dimension); + return { + childSizes.lower.first + childSizes.lower.second, + childSizes.higher.first + childSizes.higher.second + }; + } +} + +void ParentPane::_AdvanceSnappedDimension(const bool widthOrHeight, LayoutSizeNode& sizeNode) const +{ + // We're a parent pane, so we have to advance dimension of our children panes. In + // fact, we advance only one child (chosen later) to keep the growth fine-grained. + + // To choose which child pane to advance, we actually need to know their advanced sizes + // in advance (oh), to see which one would 'fit' better. Often, this is already cached + // by the previous invocation of this function in nextFirstChild and nextSecondChild + // fields of given node. If not, we need to calculate them now. + if (sizeNode.nextFirstChild == nullptr) + { + sizeNode.nextFirstChild = std::make_unique(*sizeNode.firstChild); + _firstChild->_AdvanceSnappedDimension(widthOrHeight, *sizeNode.nextFirstChild); + } + if (sizeNode.nextSecondChild == nullptr) + { + sizeNode.nextSecondChild = std::make_unique(*sizeNode.secondChild); + _secondChild->_AdvanceSnappedDimension(widthOrHeight, *sizeNode.nextSecondChild); + } + + const auto nextFirstSize = sizeNode.nextFirstChild->size; + const auto nextSecondSize = sizeNode.nextSecondChild->size; + + // Choose which child pane to advance. + bool advanceFirstOrSecond; + if (_splitState == (widthOrHeight ? SplitState::Horizontal : SplitState::Vertical)) + { + // If we're growing along separator axis, choose the child that + // wants to be smaller than the other, so that the resulting size + // will be the smallest. + advanceFirstOrSecond = nextFirstSize < nextSecondSize; + } + else + { + // If we're growing perpendicularly to separator axis, choose a + // child so that their size ratio is closer to that we're trying + // to maintain (this is, the relative separator position is closer + // to the _desiredSplitPosition field). + + const auto firstSize = sizeNode.firstChild->size; + const auto secondSize = sizeNode.secondChild->size; + + // Because we rely on equality check, these calculations have to be + // immune to floating point errors. In common situation where both panes + // have the same character sizes and _desiredSplitPosition is 0.5 (or + // some simple fraction) both ratios will often be the same, and if so + // we always take the left child. It could be right as well, but it's + // important that it's consistent: that it would always go + // 1 -> 2 -> 1 -> 2 -> 1 -> 2 and not like 1 -> 1 -> 2 -> 2 -> 2 -> 1 + // which would look silly to the user but which occur if there was + // a non-floating-point-safe math. + const auto deviation1 = nextFirstSize - (nextFirstSize + secondSize) * _desiredSplitPosition; + const auto deviation2 = -1 * (firstSize - (firstSize + nextSecondSize) * _desiredSplitPosition); + advanceFirstOrSecond = deviation1 <= deviation2; + } + + // Here we advance one of our children. Because we already know the appropriate + // (advanced) size that given child would need to have, we simply assign that size + // to it. We then advance its 'next*' size (nextFirstChild or nextSecondChild) so + // the invariant holds (as it will likely be used by the next invocation of this + // function). The other child's next* size remains unchanged because its size + // haven't changed either. + if (advanceFirstOrSecond) + { + *sizeNode.firstChild = *sizeNode.nextFirstChild; + _firstChild->_AdvanceSnappedDimension(widthOrHeight, *sizeNode.nextFirstChild); + } + else + { + *sizeNode.secondChild = *sizeNode.nextSecondChild; + _secondChild->_AdvanceSnappedDimension(widthOrHeight, *sizeNode.nextSecondChild); + } + + // Since the size of one of our children has changed we need to update our size as well. + if (_splitState == (widthOrHeight ? SplitState::Horizontal : SplitState::Vertical)) + { + sizeNode.size = std::max(sizeNode.firstChild->size, sizeNode.secondChild->size); + } + else + { + sizeNode.size = sizeNode.firstChild->size + sizeNode.secondChild->size; + } + + // Because we have grown, we're certainly no longer of our + // minimal size (if we've ever been). + sizeNode.isMinimumSize = false; +} + +Size ParentPane::_GetMinSize() const +{ + const auto firstSize = _firstChild->_GetMinSize(); + const auto secondSize = _secondChild->_GetMinSize(); + + const auto minWidth = _splitState == SplitState::Vertical ? + firstSize.Width + secondSize.Width : + std::max(firstSize.Width, secondSize.Width); + const auto minHeight = _splitState == SplitState::Horizontal ? + firstSize.Height + secondSize.Height : + std::max(firstSize.Height, secondSize.Height); + + return { minWidth, minHeight }; +} + +Pane::LayoutSizeNode ParentPane::_CreateMinSizeTree(const bool widthOrHeight) const +{ + LayoutSizeNode node = Pane::_CreateMinSizeTree(widthOrHeight); + node.firstChild = std::make_unique(_firstChild->_CreateMinSizeTree(widthOrHeight)); + node.secondChild = std::make_unique(_secondChild->_CreateMinSizeTree(widthOrHeight)); + return node; +} + +// Method Description: +// - Update the size of this pane. Resizes each of our columns so they have the +// same relative sizes, given the newSize. +// - Because we're just manually setting the row/column sizes in pixels, we have +// to be told our new size, we can't just use our own OnSized event, because +// that _won't fire when we get smaller_. +// Arguments: +// - newSize: the amount of space that this pane has to fill now. +// Return Value: +// - +void ParentPane::ResizeContent(const Size& newSize) +{ + const auto width = newSize.Width; + const auto height = newSize.Height; + + _CreateRowColDefinitions(newSize); + + if (_splitState == SplitState::Vertical) + { + const auto paneSizes = _CalcChildrenSizes(width); + + const Size firstSize{ paneSizes.first, height }; + const Size secondSize{ paneSizes.second, height }; + _firstChild->ResizeContent(firstSize); + _secondChild->ResizeContent(secondSize); + } + else if (_splitState == SplitState::Horizontal) + { + const auto paneSizes = _CalcChildrenSizes(height); + + const Size firstSize{ width, paneSizes.first }; + const Size secondSize{ width, paneSizes.second }; + _firstChild->ResizeContent(firstSize); + _secondChild->ResizeContent(secondSize); + } +} + +// Method Description: +// - Moves the separator between panes, as to resize each child on either size +// of the separator. Tries to move a separator in the given direction. The +// separator moved is the separator that's closest depth-wise to the +// currently focused pane, that's also in the correct direction to be moved. +// If there isn't such a separator, then this method returns false, as we +// couldn't handle the resize. +// Arguments: +// - direction: The direction to move the separator in. +// Return Value: +// - true if we or a child handled this resize request. +bool ParentPane::ResizePane(const Direction& direction) +{ + // Check if either our first or second child is the currently focused leaf. + // If it is, and the requested resize direction matches our separator, then + // we're the pane that needs to adjust its separator. + // If our separator is the wrong direction, then we can't handle it. + const auto firstChildAsLeaf = std::dynamic_pointer_cast(_firstChild); + const auto secondChildAsLeaf = std::dynamic_pointer_cast(_secondChild); + const bool firstIsFocused = firstChildAsLeaf && firstChildAsLeaf->WasLastActive(); + const bool secondIsFocused = secondChildAsLeaf && secondChildAsLeaf->WasLastActive(); + if (firstIsFocused || secondIsFocused) + { + return _Resize(direction); + } + + // If neither of our children were the focused leaf, then recurse into + // our children and see if they can handle the resize. + // For each child, if it has a focused descendant, try having that child + // handle the resize. + // If the child wasn't able to handle the resize, 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 resize. Otherwise, just return false, as we couldn't handle it + // either. + if (auto firstChildAsParent = std::dynamic_pointer_cast(_firstChild)) + { + if (_firstChild->FindActivePane()) + { + return firstChildAsParent->ResizePane(direction) || _Resize(direction); + } + } + + if (auto secondChildAsParent = std::dynamic_pointer_cast(_secondChild)) + { + if (_secondChild->FindActivePane()) + { + return secondChildAsParent->ResizePane(direction) || _Resize(direction); + } + } + + return false; +} + +// 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 ParentPane::NavigateFocus(const Direction& direction) +{ + // 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 auto firstChildAsLeaf = std::dynamic_pointer_cast(_firstChild); + const auto secondChildAsLeaf = std::dynamic_pointer_cast(_secondChild); + const bool firstIsFocused = firstChildAsLeaf && firstChildAsLeaf->WasLastActive(); + const bool secondIsFocused = secondChildAsLeaf && secondChildAsLeaf->WasLastActive(); + if (firstIsFocused || secondIsFocused) + { + return _NavigateFocus(direction); + } + + // 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 (auto firstChildAsParent = std::dynamic_pointer_cast(_firstChild)) + { + if (_firstChild->FindActivePane()) + { + return firstChildAsParent->NavigateFocus(direction) || _NavigateFocus(direction); + } + } + + if (auto secondChildAsParent = std::dynamic_pointer_cast(_secondChild)) + { + if (_secondChild->FindActivePane()) + { + return secondChildAsParent->NavigateFocus(direction) || _NavigateFocus(direction); + } + } + + return false; +} + +//// Method Description: +//// - Adds event handlers to our children to handle their close events. +//// Arguments: +//// - +//// Return Value: +//// - +//void ParentPane::_SetupChildCloseHandlers() +//{ +// _firstClosedToken = _firstChild->Closed([this](auto&& /*s*/, auto&& /*e*/) { +// _root.Dispatcher().RunAsync(CoreDispatcherPriority::Normal, [=]() { +// _CloseChild(true); +// }); +// }); +// +// _secondClosedToken = _secondChild->Closed([this](auto&& /*s*/, auto&& /*e*/) { +// _root.Dispatcher().RunAsync(CoreDispatcherPriority::Normal, [=]() { +// _CloseChild(false); +// }); +// }); +//} + +// Method Description: +// - Sets up row/column definitions for this pane. There are three total +// row/cols. The middle one is for the separator. The first and third are for +// each of the child panes, and are given a size in pixels, based off the +// availiable space, and the percent of the space they respectively consume, +// which is stored in _desiredSplitPosition +// - Does nothing if our split state is currently set to SplitState::None +// Arguments: +// - rootSize: The dimensions in pixels that this pane (and its children should consume.) +// Return Value: +// - +void ParentPane::_CreateRowColDefinitions(const Size& rootSize) +{ + if (_splitState == SplitState::Vertical) + { + _root.ColumnDefinitions().Clear(); + + // Create two columns in this grid: one for each pane + const auto paneSizes = _CalcChildrenSizes(rootSize.Width); + + auto firstColDef = Controls::ColumnDefinition(); + firstColDef.Width(GridLengthHelper::FromPixels(paneSizes.first)); + + auto secondColDef = Controls::ColumnDefinition(); + secondColDef.Width(GridLengthHelper::FromPixels(paneSizes.second)); + + _root.ColumnDefinitions().Append(firstColDef); + _root.ColumnDefinitions().Append(secondColDef); + } + else if (_splitState == SplitState::Horizontal) + { + _root.RowDefinitions().Clear(); + + // Create two rows in this grid: one for each pane + const auto paneSizes = _CalcChildrenSizes(rootSize.Height); + + auto firstRowDef = Controls::RowDefinition(); + firstRowDef.Height(GridLengthHelper::FromPixels(paneSizes.first)); + + auto secondRowDef = Controls::RowDefinition(); + secondRowDef.Height(GridLengthHelper::FromPixels(paneSizes.second)); + + _root.RowDefinitions().Append(firstRowDef); + _root.RowDefinitions().Append(secondRowDef); + } +} + +// Method Description: +// - Adjust our child percentages to increase the size of one of our children +// and decrease the size of the other. +// - Adjusts the separation amount by 5% +// - Does nothing if the direction doesn't match our current split direction +// Arguments: +// - direction: the direction to move our separator. If it's down or right, +// we'll be increasing the size of the first of our children. Else, we'll be +// decreasing the size of our first child. +// Return Value: +// - false if we couldn't resize this pane in the given direction, else true. +bool ParentPane::_Resize(const Direction& direction) +{ + if (!DirectionMatchesSplit(direction, _splitState)) + { + return false; + } + + float amount = .05f; + if (direction == Direction::Right || direction == Direction::Down) + { + amount = -amount; + } + + // Make sure we're not making a pane explode here by resizing it to 0 characters. + const bool changeWidth = _splitState == SplitState::Vertical; + + const Size actualSize{ gsl::narrow_cast(_root.ActualWidth()), + gsl::narrow_cast(_root.ActualHeight()) }; + // actualDimension is the size in DIPs of this pane in the direction we're + // resizing. + const auto actualDimension = changeWidth ? actualSize.Width : actualSize.Height; + + _desiredSplitPosition = _ClampSplitPosition(changeWidth, _desiredSplitPosition - amount, actualDimension); + + // Resize our columns to match the new percentages. + Relayout(); + + return true; +} + +// 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 ParentPane::_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; + (focusSecond ? _firstChild : _secondChild)->ClearActive(); + + // 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->FindActivePane()) + { + return false; + } + + // Transfer focus to our child, and update the focus of our tree. + newlyFocusedChild->_FindFirstLeaf()->SetActive(); + + return true; +} + +// Method Description: +// - Closes one of our children. In doing so, takes the control from the other +// child, and makes this pane a leaf node again. +// Arguments: +// - closeFirst: if true, the first child should be closed, and the second +// should be preserved, and vice-versa for false. +// Return Value: +// - +void ParentPane::_CloseChild(const bool closeFirst) +{ + auto closedChild = closeFirst ? _firstChild : _secondChild; + auto remainingChild = closeFirst ? _secondChild : _firstChild; + + if (auto remainingChildAsLeaf = std::dynamic_pointer_cast(remainingChild)) + { + // When the remaining child is a leaf, that means both our children were + // previously leaves, so this is safe to cast it here. + remainingChildAsLeaf->OnNeightbourClosed(std::dynamic_pointer_cast(closedChild)); + } + + _root.Children().Clear(); + + _ChildClosedHandlers(remainingChild); + + if (closedChild->FindActivePane()) + { + remainingChild->_FindFirstLeaf()->SetActive(); + } +} + +std::shared_ptr ParentPane::_FindFirstLeaf() +{ + return _firstChild->_FindFirstLeaf(); +} + +// Method Description: +// - Adjusts split position so that no child pane is smaller then its +// minimum size +// Arguments: +// - widthOrHeight: if true, operates on width, otherwise on height. +// - requestedValue: split position value to be clamped +// - totalSize: size (width or height) of the parent pane +// Return Value: +// - split position (value in range <0.0, 1.0>) +float ParentPane::_ClampSplitPosition(const bool widthOrHeight, const float requestedValue, const float totalSize) const +{ + const auto firstMinSize = _firstChild->_GetMinSize(); + const auto secondMinSize = _secondChild->_GetMinSize(); + + const auto firstMinDimension = widthOrHeight ? firstMinSize.Width : firstMinSize.Height; + const auto secondMinDimension = widthOrHeight ? secondMinSize.Width : secondMinSize.Height; + + const auto minSplitPosition = firstMinDimension / totalSize; + const auto maxSplitPosition = 1.0f - (secondMinDimension / totalSize); + + return std::clamp(requestedValue, minSplitPosition, maxSplitPosition); +} + +// Method Description: +// - Gets the size in pixels of each of our children, given the full size they should +// fill. Each child is snapped to char grid as close as possible. If called multiple +// times with fullSize argument growing, then both returned sizes are guaranteed to be +// non-decreasing (it's a monotonically increasing function). This is important so that +// user doesn't get any pane shrank when they actually expand the window or parent pane. +// That is also required by the layout algorithm. +// Arguments: +// - widthOrHeight: if true, operates on width, otherwise on height. +// - fullSize: the amount of space in pixels that should be filled by our children and +// their separator. Can be arbitrarily low. +// Return Value: +// - a structure holding the result of this calculation. The 'lower' field represents the +// children sizes that would fit in the fullSize, but might (and usually do) not fill it +// completely. The 'higher' field represents the size of the children if they slightly exceed +// the fullSize, but are snapped. If the children can be snapped and also exactly match +// the fullSize, then both this fields have the same value that represent this situation. +Pane::SnapChildrenSizeResult ParentPane::_CalcSnappedChildrenSizes(const bool widthOrHeight, const float fullSize) const +{ + // First we build a tree of nodes corresponding to the tree of our descendant panes. + // Each node represents a size of given pane. At the beginning, each node has the minimum + // size that the corresponding pane can have; so has the our (root) node. We then gradually + // expand our node (which in turn expands some of the child nodes) until we hit the desired + // size. Since each expand step (done in _AdvanceSnappedDimension()) guarantees that all the + // sizes will be snapped, our return values is also snapped. + // Why do we do it this, iterative way? Why can't we just split the given size by + // _desiredSplitPosition and snap it latter? Because it's hardly doable, if possible, to also + // fulfill the monotonicity requirement that way. As the fullSize increases, the proportional + // point that separates children panes also moves and cells sneak in the available area in + // unpredictable way, regardless which child has the snap priority or whether we snap them + // upward, downward or to nearest. + // With present way we run the same sequence of actions regardless to the fullSize value and + // only just stop at various moments when the built sizes reaches it. Eventually, this could + // be optimized for simple cases like when both children are both leaves with the same character + // size, but it doesn't seem to be beneficial. + + auto sizeTree = _CreateMinSizeTree(widthOrHeight); + LayoutSizeNode lastSizeTree{ sizeTree }; + + while (sizeTree.size < fullSize) + { + lastSizeTree = sizeTree; + _AdvanceSnappedDimension(widthOrHeight, sizeTree); + + if (sizeTree.size == fullSize) + { + // If we just hit exactly the requested value, then just return the + // current state of children. + return { { sizeTree.firstChild->size, sizeTree.secondChild->size }, + { sizeTree.firstChild->size, sizeTree.secondChild->size } }; + } + } + + // We exceeded the requested size in the loop above, so lastSizeTree will have + // the last good sizes (so that children fit in) and sizeTree has the next possible + // snapped sizes. Return them as lower and higher snap possibilities. + return { { lastSizeTree.firstChild->size, lastSizeTree.secondChild->size }, + { sizeTree.firstChild->size, sizeTree.secondChild->size } }; +} + +// Method Description: +// - Gets the size in pixels of each of our children, given the full size they +// should fill. Since these children own their own separators (borders), this +// size is their portion of our _entire_ size. If specified size is lower than +// required then children will be of minimum size. Snaps first child to grid +// but not the second. +// Arguments: +// - fullSize: the amount of space in pixels that should be filled by our +// children and their separators. Can be arbitrarily low. +// Return Value: +// - a pair with the size of our first child and the size of our second child, +// respectively. +std::pair ParentPane::_CalcChildrenSizes(const float fullSize) const +{ + const auto widthOrHeight = _splitState == SplitState::Vertical; + const auto snappedSizes = _CalcSnappedChildrenSizes(widthOrHeight, fullSize).lower; + + // Keep the first pane snapped and give the second pane all remaining size + return { + snappedSizes.first, + fullSize - snappedSizes.first + }; +} + +DEFINE_EVENT(ParentPane, ChildClosed, _ChildClosedHandlers, winrt::delegate>); diff --git a/src/cascadia/TerminalApp/lib/ParentPane.h b/src/cascadia/TerminalApp/lib/ParentPane.h new file mode 100644 index 00000000000..5926f0664a5 --- /dev/null +++ b/src/cascadia/TerminalApp/lib/ParentPane.h @@ -0,0 +1,87 @@ +#pragma once +#include "Pane.h" +#include +#include "../../cascadia/inc/cppwinrt_utils.h" +#include + + +class ParentPane : public Pane, public std::enable_shared_from_this +{ +public: + ParentPane(std::shared_ptr firstChild, std::shared_ptr secondChild, + winrt::TerminalApp::SplitState splitState, + winrt::Windows::Foundation::Size currentSize); + ~ParentPane() override; + + std::shared_ptr FindActivePane() override; + void Relayout() override; + void ClearActive() override; + void UpdateSettings(const winrt::Microsoft::Terminal::Settings::TerminalSettings& settings, + const GUID& profile); + void ResizeContent(const winrt::Windows::Foundation::Size& newSize) override; + + void InitializeChildren(); + bool ResizePane(const winrt::TerminalApp::Direction& direction); + bool NavigateFocus(const winrt::TerminalApp::Direction& direction); + + DECLARE_EVENT(ChildClosed, _ChildClosedHandlers, winrt::delegate>); + +private: + std::shared_ptr _firstChild; + std::shared_ptr _secondChild; + winrt::TerminalApp::SplitState _splitState; + float _desiredSplitPosition; + + winrt::event_token _firstClosedToken{ 0 }; + winrt::event_token _firstTypeChangedToken{ 0 }; + winrt::event_token _secondClosedToken{ 0 }; + winrt::event_token _secondTypeChangedToken{ 0 }; + + void _SetupChildEventHandlers(bool firstChild); + void _RemoveAllChildEventHandlers(bool firstChild); + void _CreateRowColDefinitions(const winrt::Windows::Foundation::Size& rootSize); + + bool _Resize(const winrt::TerminalApp::Direction& direction); + bool _NavigateFocus(const winrt::TerminalApp::Direction& direction); + void _CloseChild(const bool closeFirst); + + std::pair _CalcChildrenSizes(const float fullSize) const; + SnapChildrenSizeResult _CalcSnappedChildrenSizes(const bool widthOrHeight, const float fullSize) const; + float _ClampSplitPosition(const bool widthOrHeight, const float requestedValue, const float totalSize) const; + + // Function Description: + // - Returns true if the given direction can be used with the given split + // type. + // - This is used for pane resizing (which will need a pane separator + // that's perpendicular to the direction to be able to move the separator + // in that direction). + // - Additionally, it will be used for moving focus between panes, which + // again happens _across_ a separator. + // Arguments: + // - direction: The Direction to compare + // - splitType: The winrt::TerminalApp::SplitState to compare + // Return Value: + // - true iff the direction is perpendicular to the splitType. False for + // winrt::TerminalApp::SplitState::None. + static constexpr bool DirectionMatchesSplit(const winrt::TerminalApp::Direction& direction, + const winrt::TerminalApp::SplitState& splitType) + { + if (splitType == winrt::TerminalApp::SplitState::Horizontal) + { + return direction == winrt::TerminalApp::Direction::Up || + direction == winrt::TerminalApp::Direction::Down; + } + else + { + return direction == winrt::TerminalApp::Direction::Left || + direction == winrt::TerminalApp::Direction::Right; + } + return false; + } + + std::shared_ptr _FindFirstLeaf() override; + SnapSizeResult _CalcSnappedDimension(const bool widthOrHeight, const float dimension) const override; + void _AdvanceSnappedDimension(const bool widthOrHeight, LayoutSizeNode& sizeNode) const override; + winrt::Windows::Foundation::Size _GetMinSize() const override; + LayoutSizeNode _CreateMinSizeTree(const bool widthOrHeight) const override; +}; From 424485f7b28c770e1a04a9ce2ef41132e744403a Mon Sep 17 00:00:00 2001 From: MCpiroman Date: Mon, 23 Dec 2019 10:39:59 +0100 Subject: [PATCH 02/17] Establish interfaces and majority of implementation, order things in files --- src/cascadia/TerminalApp/ActionArgs.idl | 4 +- src/cascadia/TerminalApp/Pane.h | 29 +- src/cascadia/TerminalApp/Tab.cpp | 11 +- src/cascadia/TerminalApp/Tab.h | 2 + src/cascadia/TerminalApp/TerminalPage.cpp | 2 +- src/cascadia/TerminalApp/lib/LeafPane.cpp | 318 +++++----- src/cascadia/TerminalApp/lib/LeafPane.h | 39 +- src/cascadia/TerminalApp/lib/ParentPane.cpp | 668 ++++++++++---------- src/cascadia/TerminalApp/lib/ParentPane.h | 27 +- 9 files changed, 543 insertions(+), 557 deletions(-) diff --git a/src/cascadia/TerminalApp/ActionArgs.idl b/src/cascadia/TerminalApp/ActionArgs.idl index 3f21db46c8a..a3c2892f76d 100644 --- a/src/cascadia/TerminalApp/ActionArgs.idl +++ b/src/cascadia/TerminalApp/ActionArgs.idl @@ -17,10 +17,10 @@ namespace TerminalApp enum Direction { None = 0, + Up, + Down, Left, Right, - Up, - Down }; enum SplitState diff --git a/src/cascadia/TerminalApp/Pane.h b/src/cascadia/TerminalApp/Pane.h index 767006eb8d2..c503c1f2700 100644 --- a/src/cascadia/TerminalApp/Pane.h +++ b/src/cascadia/TerminalApp/Pane.h @@ -5,7 +5,7 @@ // - Pane.h // // Abstract: -// - Panes are an abstraction by which the terminal can dislay multiple terminal +// - Panes are an abstraction by which the terminal can display multiple terminal // instances simultaneously in a single terminal window. While tabs allow for // a single terminal window to have many terminal sessions running // simultaneously within a single window, only one tab can be visible at a @@ -19,6 +19,7 @@ // - Mike Griese (zadjii-msft) 16-May-2019 #pragma once +#include #include #include "../../cascadia/inc/cppwinrt_utils.h" @@ -31,30 +32,37 @@ class Pane virtual ~Pane() = default; winrt::Windows::UI::Xaml::Controls::Grid GetRootElement(); - float CalcSnappedDimension(const bool widthOrHeight, const float dimension) const; virtual std::shared_ptr FindActivePane() = 0; - virtual void Relayout() = 0; - virtual void ClearActive() = 0; + virtual void PropagateToLeaves(std::function action) = 0; + virtual void PropagateToLeavesOnEdge(const winrt::TerminalApp::Direction& edge, + std::function action) = 0; + virtual void UpdateSettings(const winrt::Microsoft::Terminal::Settings::TerminalSettings& settings, - const GUID& profile) = 0; + const GUID& profile) = 0; virtual void ResizeContent(const winrt::Windows::Foundation::Size& newSize) = 0; + virtual void Relayout() = 0; + float CalcSnappedDimension(const bool widthOrHeight, const float dimension) const; protected: struct SnapSizeResult; struct SnapChildrenSizeResult; struct LayoutSizeNode; - Pane(); - winrt::Windows::UI::Xaml::Controls::Grid _root{}; + Pane(); + virtual std::shared_ptr _FindFirstLeaf() = 0; - virtual SnapSizeResult _CalcSnappedDimension(const bool widthOrHeight, const float dimension) const = 0; - virtual void _AdvanceSnappedDimension(const bool widthOrHeight, LayoutSizeNode& sizeNode) const = 0; + virtual winrt::Windows::Foundation::Size _GetMinSize() const = 0; virtual LayoutSizeNode _CreateMinSizeTree(const bool widthOrHeight) const; + virtual SnapSizeResult _CalcSnappedDimension(const bool widthOrHeight, const float dimension) const = 0; + virtual void _AdvanceSnappedDimension(const bool widthOrHeight, LayoutSizeNode& sizeNode) const = 0; + // Make derived classes friends, so they can access protected members of Pane. C++ for some + // reason doesn't allow that by default - e.g. LeafPane can access LeafPane::_ProtectedMember, + // but not Pane::_ProtectedMember. friend class LeafPane; friend class ParentPane; @@ -95,6 +103,3 @@ class Pane void _AssignChildNode(std::unique_ptr& nodeField, const LayoutSizeNode* const newNode); }; }; - -#include "LeafPane.h" -#include "ParentPane.h" diff --git a/src/cascadia/TerminalApp/Tab.cpp b/src/cascadia/TerminalApp/Tab.cpp index 9ccfccddc04..3246e0dc1c2 100644 --- a/src/cascadia/TerminalApp/Tab.cpp +++ b/src/cascadia/TerminalApp/Tab.cpp @@ -318,7 +318,7 @@ void Tab::ResizeContent(const winrt::Windows::Foundation::Size& newSize) // Method Description: // - Attempt to move a separator between panes, as to resize each child on -// either size of the separator. See Pane::ResizePane for details. +// either size of the separator. See Pane::ResizeChild for details. // Arguments: // - direction: The direction to move the separator in. // Return Value: @@ -328,10 +328,9 @@ void Tab::ResizePane(const winrt::TerminalApp::Direction& direction) // NOTE: This _must_ be called on the root pane, so that it can propogate // throughout the entire tree. - //_rootPane->ResizePane(direction); if (auto rootPaneAsParent = std::dynamic_pointer_cast(_rootPane)) { - rootPaneAsParent->ResizePane(direction); + rootPaneAsParent->ResizeChild(direction); } } @@ -347,7 +346,6 @@ void Tab::NavigateFocus(const winrt::TerminalApp::Direction& direction) // NOTE: This _must_ be called on the root pane, so that it can propogate // throughout the entire tree. - //_rootPane->NavigateFocus(direction); if (auto rootPaneAsParent = std::dynamic_pointer_cast(_rootPane)) { rootPaneAsParent->NavigateFocus(direction); @@ -425,7 +423,10 @@ void Tab::_AttachEventHandlersToPane(std::shared_ptr pane) if (tab && sender != tab->_rootPane->FindActivePane()) { // Clear the active state of the entire tree, and mark only the sender as active. - tab->_rootPane->ClearActive(); + tab->_rootPane->PropagateToLeaves([](LeafPane& pane) { + pane.ClearActive(); + }); + sender->SetActive(); // Update our own title text to match the newly-active pane. diff --git a/src/cascadia/TerminalApp/Tab.h b/src/cascadia/TerminalApp/Tab.h index 9083817a27f..2950cf772ea 100644 --- a/src/cascadia/TerminalApp/Tab.h +++ b/src/cascadia/TerminalApp/Tab.h @@ -3,6 +3,8 @@ #pragma once #include "Pane.h" +#include "LeafPane.h" +#include "ParentPane.h" class Tab : public std::enable_shared_from_this { diff --git a/src/cascadia/TerminalApp/TerminalPage.cpp b/src/cascadia/TerminalApp/TerminalPage.cpp index 244123902a5..7d631ba48ac 100644 --- a/src/cascadia/TerminalApp/TerminalPage.cpp +++ b/src/cascadia/TerminalApp/TerminalPage.cpp @@ -994,7 +994,7 @@ namespace winrt::TerminalApp::implementation // Method Description: // - Attempt to move a separator between panes, as to resize each child on - // either size of the separator. See Pane::ResizePane for details. + // either size of the separator. See Pane::ResizeChild for details. // - Moves a separator on the currently focused tab. // Arguments: // - direction: The direction to move the separator in. diff --git a/src/cascadia/TerminalApp/lib/LeafPane.cpp b/src/cascadia/TerminalApp/lib/LeafPane.cpp index 44434face71..ae09051f31c 100644 --- a/src/cascadia/TerminalApp/lib/LeafPane.cpp +++ b/src/cascadia/TerminalApp/lib/LeafPane.cpp @@ -56,14 +56,49 @@ LeafPane::~LeafPane() OutputDebugString(L"~LeafPane()\n"); } -// Method Description: -// - Recalculates and reapplies sizes of all descendant panes. +// Function Description: +// - Attempts to load some XAML resources that the Pane will need. This includes: +// * The Color we'll use for active Panes's borders - SystemAccentColor +// * The Brush we'll use for inactive Panes - TabViewBackground (to match the +// color of the titlebar) // Arguments: // - // Return Value: // - -void LeafPane::Relayout() +void LeafPane::_SetupResources() { + const auto res = Application::Current().Resources(); + const auto accentColorKey = winrt::box_value(L"SystemAccentColor"); + if (res.HasKey(accentColorKey)) + { + const auto colorFromResources = res.Lookup(accentColorKey); + // If SystemAccentColor is _not_ a Color for some reason, use + // Transparent as the color, so we don't do this process again on + // the next pane (by leaving s_focusedBorderBrush nullptr) + auto actualColor = winrt::unbox_value_or(colorFromResources, Colors::Black()); + s_focusedBorderBrush = SolidColorBrush(actualColor); + } + else + { + // DON'T use Transparent here - if it's "Transparent", then it won't + // be able to hittest for clicks, and then clicking on the border + // will eat focus. + s_focusedBorderBrush = SolidColorBrush{ Colors::Black() }; + } + + const auto tabViewBackgroundKey = winrt::box_value(L"TabViewBackground"); + if (res.HasKey(tabViewBackgroundKey)) + { + winrt::Windows::Foundation::IInspectable obj = res.Lookup(tabViewBackgroundKey); + s_unfocusedBorderBrush = obj.try_as(); + } + else + { + // DON'T use Transparent here - if it's "Transparent", then it won't + // be able to hittest for clicks, and then clicking on the border + // will eat focus. + s_unfocusedBorderBrush = SolidColorBrush{ Colors::Black() }; + } } std::shared_ptr LeafPane::FindActivePane() @@ -71,10 +106,19 @@ std::shared_ptr LeafPane::FindActivePane() return _lastActive ? shared_from_this() : nullptr; } -void LeafPane::ClearActive() +std::shared_ptr LeafPane::_FindFirstLeaf() { - _lastActive = false; - _UpdateVisuals(); + return shared_from_this(); +} + +void LeafPane::PropagateToLeaves(std::function action) +{ + action(*this); +} + +void LeafPane::PropagateToLeavesOnEdge(const winrt::TerminalApp::Direction& /* edge */, std::function action) +{ + action(*this); } void LeafPane::UpdateSettings(const TerminalSettings& settings, const GUID& profile) @@ -85,6 +129,22 @@ void LeafPane::UpdateSettings(const TerminalSettings& settings, const GUID& prof } } +// Method Description: +// - Gets the TermControl of this pane. If this Pane is not a leaf, this will return nullptr. +// Arguments: +// - +// Return Value: +// - nullptr if this Pane is a parent, otherwise the TermControl of this Pane. +TermControl LeafPane::GetTerminalControl() +{ + return _control; +} + +GUID LeafPane::GetProfile() +{ + return _profile; +} + // Method Description: // - Determines whether the pane can be split. // Arguments: @@ -118,8 +178,8 @@ bool LeafPane::CanSplit(SplitState splitType) } LeafPane::SplitResult LeafPane::Split(winrt::TerminalApp::SplitState splitType, - const GUID& profile, - const winrt::Microsoft::Terminal::TerminalControl::TermControl& control) + const GUID& profile, + const winrt::Microsoft::Terminal::TerminalControl::TermControl& control) { const auto secondLeaf = std::make_shared(profile, control); @@ -150,91 +210,25 @@ LeafPane::SplitResult LeafPane::Split(winrt::TerminalApp::SplitState splitType, return { newParent, shared_from_this(), secondLeaf }; } -Pane::SnapSizeResult LeafPane::_CalcSnappedDimension(const bool widthOrHeight, const float dimension) const -{ - // If we're a leaf pane, align to the grid of controlling terminal - - const auto minSize = _GetMinSize(); - const auto minDimension = widthOrHeight ? minSize.Width : minSize.Height; - - if (dimension <= minDimension) - { - return { minDimension, minDimension }; - } - - float lower = _control.SnapDimensionToGrid(widthOrHeight, dimension); - if (widthOrHeight) - { - lower += WI_IsFlagSet(_borders, Borders::Left) ? PaneBorderSize : 0; - lower += WI_IsFlagSet(_borders, Borders::Right) ? PaneBorderSize : 0; - } - else - { - lower += WI_IsFlagSet(_borders, Borders::Top) ? PaneBorderSize : 0; - lower += WI_IsFlagSet(_borders, Borders::Bottom) ? PaneBorderSize : 0; - } - - if (lower == dimension) - { - // If we happen to be already snapped, then just return this size - // as both lower and higher values. - return { lower, lower }; - } - else - { - const auto cellSize = _control.CharacterDimensions(); - const auto higher = lower + (widthOrHeight ? cellSize.Width : cellSize.Height); - return { lower, higher }; - } -} - -void LeafPane::_AdvanceSnappedDimension(const bool widthOrHeight, LayoutSizeNode& sizeNode) const -{ - // We're a leaf pane, so just add one more row or column (unless isMinimumSize - // is true, see below). - - if (sizeNode.isMinimumSize) - { - // If the node is of its minimum size, this size might not be snapped (it might - // be, say, half a character, or fixed 10 pixels), so snap it upward. It might - // however be already snapped, so add 1 to make sure it really increases - // (not strictly necessary but to avoid surprises). - sizeNode.size = _CalcSnappedDimension(widthOrHeight, sizeNode.size + 1).higher; - } - else - { - const auto cellSize = _control.CharacterDimensions(); - sizeNode.size += widthOrHeight ? cellSize.Width : cellSize.Height; - } - - // Because we have grown, we're certainly no longer of our - // minimal size (if we've ever been). - sizeNode.isMinimumSize = false; -} - -Size LeafPane::_GetMinSize() const -{ - auto controlSize = _control.MinimumSize(); - auto newWidth = controlSize.Width; - auto newHeight = controlSize.Height; - - newWidth += WI_IsFlagSet(_borders, Borders::Left) ? PaneBorderSize : 0; - newWidth += WI_IsFlagSet(_borders, Borders::Right) ? PaneBorderSize : 0; - newHeight += WI_IsFlagSet(_borders, Borders::Top) ? PaneBorderSize : 0; - newHeight += WI_IsFlagSet(_borders, Borders::Bottom) ? PaneBorderSize : 0; - - return { newWidth, newHeight }; -} - // Method Description: -// - Gets the TermControl of this pane. If this Pane is not a leaf, this will return nullptr. +// - Sets the "Active" state on this Pane. Only one Pane in a tree of Panes +// should be "active", and that pane should be a leaf. +// - Updates our visuals to match our new state, including highlighting our borders. // Arguments: // - // Return Value: -// - nullptr if this Pane is a parent, otherwise the TermControl of this Pane. -TermControl LeafPane::GetTerminalControl() +// - +void LeafPane::SetActive() { - return _control; + _lastActive = true; + _control.Focus(FocusState::Programmatic); + _UpdateVisuals(); +} + +void LeafPane::ClearActive() +{ + _lastActive = false; + _UpdateVisuals(); } // Method Description: @@ -260,21 +254,6 @@ void LeafPane::_UpdateVisuals() _border.BorderBrush(_lastActive ? s_focusedBorderBrush : s_unfocusedBorderBrush); } -// Method Description: -// - Sets the "Active" state on this Pane. Only one Pane in a tree of Panes -// should be "active", and that pane should be a leaf. -// - Updates our visuals to match our new state, including highlighting our borders. -// Arguments: -// - -// Return Value: -// - -void LeafPane::SetActive() -{ - _lastActive = true; - _control.Focus(FocusState::Programmatic); - _UpdateVisuals(); -} - // Method Description: // - Sets the thickness of each side of our borders to match our _borders state. // Arguments: @@ -305,6 +284,19 @@ void LeafPane::_UpdateBorders() _border.BorderThickness(ThicknessHelper::FromLengths(left, top, right, bottom)); } +void LeafPane::UpdateBorderWithClosedNeightbour(std::shared_ptr closedNeightbour, const winrt::TerminalApp::Direction& neightbourDirection) +{ + const auto borderMask = static_cast(1 << (static_cast(neightbourDirection) - 1)); + WI_UpdateFlagsInMask(_borders, borderMask, closedNeightbour->_borders); + _UpdateBorders(); +} + +void LeafPane::Close() +{ + // Fire our Closed event to tell our parent that we should be removed. + _ClosedHandlers(nullptr, nullptr); +} + // Method Description: // - Called when our attached control is closed. Triggers listeners to our close // event, if we're a leaf pane. @@ -335,7 +327,7 @@ winrt::fire_and_forget LeafPane::_ControlConnectionStateChangedHandler(const Ter if ((mode == CloseOnExitMode::Always) || (mode == CloseOnExitMode::Graceful && newConnectionState == ConnectionState::Closed)) { - _ClosedHandlers(nullptr, nullptr); + Close(); } } } @@ -354,95 +346,81 @@ void LeafPane::_ControlGotFocusHandler(winrt::Windows::Foundation::IInspectable _GotFocusHandlers(shared_from_this()); } -// Function Description: -// - Attempts to load some XAML resources that the Pane will need. This includes: -// * The Color we'll use for active Panes's borders - SystemAccentColor -// * The Brush we'll use for inactive Panes - TabViewBackground (to match the -// color of the titlebar) -// Arguments: -// - -// Return Value: -// - -void LeafPane::_SetupResources() +Pane::SnapSizeResult LeafPane::_CalcSnappedDimension(const bool widthOrHeight, const float dimension) const { - const auto res = Application::Current().Resources(); - const auto accentColorKey = winrt::box_value(L"SystemAccentColor"); - if (res.HasKey(accentColorKey)) + // If we're a leaf pane, align to the grid of controlling terminal + + const auto minSize = _GetMinSize(); + const auto minDimension = widthOrHeight ? minSize.Width : minSize.Height; + + if (dimension <= minDimension) { - const auto colorFromResources = res.Lookup(accentColorKey); - // If SystemAccentColor is _not_ a Color for some reason, use - // Transparent as the color, so we don't do this process again on - // the next pane (by leaving s_focusedBorderBrush nullptr) - auto actualColor = winrt::unbox_value_or(colorFromResources, Colors::Black()); - s_focusedBorderBrush = SolidColorBrush(actualColor); + return { minDimension, minDimension }; + } + + float lower = _control.SnapDimensionToGrid(widthOrHeight, dimension); + if (widthOrHeight) + { + lower += WI_IsFlagSet(_borders, Borders::Left) ? PaneBorderSize : 0; + lower += WI_IsFlagSet(_borders, Borders::Right) ? PaneBorderSize : 0; } else { - // DON'T use Transparent here - if it's "Transparent", then it won't - // be able to hittest for clicks, and then clicking on the border - // will eat focus. - s_focusedBorderBrush = SolidColorBrush{ Colors::Black() }; + lower += WI_IsFlagSet(_borders, Borders::Top) ? PaneBorderSize : 0; + lower += WI_IsFlagSet(_borders, Borders::Bottom) ? PaneBorderSize : 0; } - const auto tabViewBackgroundKey = winrt::box_value(L"TabViewBackground"); - if (res.HasKey(tabViewBackgroundKey)) + if (lower == dimension) { - winrt::Windows::Foundation::IInspectable obj = res.Lookup(tabViewBackgroundKey); - s_unfocusedBorderBrush = obj.try_as(); + // If we happen to be already snapped, then just return this size + // as both lower and higher values. + return { lower, lower }; } else { - // DON'T use Transparent here - if it's "Transparent", then it won't - // be able to hittest for clicks, and then clicking on the border - // will eat focus. - s_unfocusedBorderBrush = SolidColorBrush{ Colors::Black() }; + const auto cellSize = _control.CharacterDimensions(); + const auto higher = lower + (widthOrHeight ? cellSize.Width : cellSize.Height); + return { lower, higher }; } } -std::shared_ptr LeafPane::_FindFirstLeaf() -{ - return shared_from_this(); - //_control.Focus(FocusState::Programmatic); -} - -void LeafPane::ResizeContent(const winrt::Windows::Foundation::Size& /* newSize */) -{ -} - -void LeafPane::OnNeightbourClosed(std::shared_ptr closedNeightbour) +void LeafPane::_AdvanceSnappedDimension(const bool widthOrHeight, LayoutSizeNode& sizeNode) const { - _borders = _borders & closedNeightbour->_borders; - _lastActive = _lastActive || closedNeightbour->_lastActive; + // We're a leaf pane, so just add one more row or column (unless isMinimumSize + // is true, see below). - // If we're inheriting the "last active" state from one of our children, - // focus our control now. This should trigger our own GotFocus event. - if (_lastActive) + if (sizeNode.isMinimumSize) { - _control.Focus(FocusState::Programmatic); + // If the node is of its minimum size, this size might not be snapped (it might + // be, say, half a character, or fixed 10 pixels), so snap it upward. It might + // however be already snapped, so add 1 to make sure it really increases + // (not strictly necessary but to avoid surprises). + sizeNode.size = _CalcSnappedDimension(widthOrHeight, sizeNode.size + 1).higher; + } + else + { + const auto cellSize = _control.CharacterDimensions(); + sizeNode.size += widthOrHeight ? cellSize.Width : cellSize.Height; } - _UpdateBorders(); + // Because we have grown, we're certainly no longer of our + // minimal size (if we've ever been). + sizeNode.isMinimumSize = false; } -GUID LeafPane::GetProfile() +Size LeafPane::_GetMinSize() const { - return _profile; -} + auto controlSize = _control.MinimumSize(); + auto newWidth = controlSize.Width; + auto newHeight = controlSize.Height; -void LeafPane::Close() -{ - // Fire our Closed event to tell our parent that we should be removed. - _ClosedHandlers(nullptr, nullptr); + newWidth += WI_IsFlagSet(_borders, Borders::Left) ? PaneBorderSize : 0; + newWidth += WI_IsFlagSet(_borders, Borders::Right) ? PaneBorderSize : 0; + newHeight += WI_IsFlagSet(_borders, Borders::Top) ? PaneBorderSize : 0; + newHeight += WI_IsFlagSet(_borders, Borders::Bottom) ? PaneBorderSize : 0; + + return { newWidth, newHeight }; } -//DEFINE_EVENT(LeafPane, Splitted, _SplittedHandlers, winrt::delegate>); +DEFINE_EVENT(LeafPane, Splitted, _SplittedHandlers, winrt::delegate>); DEFINE_EVENT(LeafPane, GotFocus, _GotFocusHandlers, winrt::delegate>); - -winrt::event_token LeafPane::Splitted(winrt::delegate> const& handler) -{ - return _SplittedHandlers.add(handler); -} -void LeafPane::Splitted(winrt::event_token const& token) noexcept -{ - _SplittedHandlers.remove(token); -} diff --git a/src/cascadia/TerminalApp/lib/LeafPane.h b/src/cascadia/TerminalApp/lib/LeafPane.h index 8092224b323..8bb5ee35f80 100644 --- a/src/cascadia/TerminalApp/lib/LeafPane.h +++ b/src/cascadia/TerminalApp/lib/LeafPane.h @@ -1,5 +1,6 @@ #pragma once #include "Pane.h" +#include "ParentPane.h" #include #include "../../cascadia/inc/cppwinrt_utils.h" #include @@ -24,21 +25,28 @@ class LeafPane : public Pane, public std::enable_shared_from_this const bool lastFocused = false); ~LeafPane() override; - void Relayout() override; - void ClearActive() override; + std::shared_ptr FindActivePane() override; + void PropagateToLeaves(std::function action) override; + void PropagateToLeavesOnEdge(const winrt::TerminalApp::Direction& edge, + std::function action) override; + void UpdateSettings(const winrt::Microsoft::Terminal::Settings::TerminalSettings& settings, const GUID& profile); - void ResizeContent(const winrt::Windows::Foundation::Size& newSize) override; - std::shared_ptr FindActivePane() override; + void ResizeContent(const winrt::Windows::Foundation::Size& /* newSize */) override{}; + void Relayout() override{}; + + winrt::Microsoft::Terminal::TerminalControl::TermControl GetTerminalControl(); + GUID GetProfile(); bool CanSplit(winrt::TerminalApp::SplitState splitType); SplitResult Split(winrt::TerminalApp::SplitState splitType, const GUID& profile, const winrt::Microsoft::Terminal::TerminalControl::TermControl& control); - winrt::Microsoft::Terminal::TerminalControl::TermControl GetTerminalControl(); - bool WasLastActive() const noexcept; + void SetActive(); - GUID GetProfile(); - void OnNeightbourClosed(std::shared_ptr closedNeightbour); + void ClearActive(); + bool WasLastActive() const noexcept; + void UpdateBorderWithClosedNeightbour(std::shared_ptr closedNeightbour, + const winrt::TerminalApp::Direction& neightbourDirection); void Close(); struct SplitResult @@ -48,8 +56,6 @@ class LeafPane : public Pane, public std::enable_shared_from_this std::shared_ptr secondChild; }; - friend class Pane; - WINRT_CALLBACK(Closed, winrt::Windows::Foundation::EventHandler); DECLARE_EVENT(Splitted, _SplittedHandlers, winrt::delegate>); DECLARE_EVENT(GotFocus, _GotFocusHandlers, winrt::delegate>); @@ -57,27 +63,28 @@ class LeafPane : public Pane, public std::enable_shared_from_this static winrt::Windows::UI::Xaml::Media::SolidColorBrush s_focusedBorderBrush; static winrt::Windows::UI::Xaml::Media::SolidColorBrush s_unfocusedBorderBrush; - Borders _borders{ Borders::None }; - bool _lastActive{ false }; winrt::Windows::UI::Xaml::Controls::Border _border{}; winrt::Microsoft::Terminal::TerminalControl::TermControl _control{ nullptr }; GUID _profile; + bool _lastActive{ false }; + Borders _borders{ Borders::None }; + winrt::event_token _connectionStateChangedToken{ 0 }; winrt::Windows::UI::Xaml::UIElement::GotFocus_revoker _gotFocusRevoker; - void _UpdateBorders(); - void _UpdateVisuals(); winrt::fire_and_forget _ControlConnectionStateChangedHandler( const winrt::Microsoft::Terminal::TerminalControl::TermControl& sender, const winrt::Windows::Foundation::IInspectable& /*args*/); void _ControlGotFocusHandler(winrt::Windows::Foundation::IInspectable const& sender, winrt::Windows::UI::Xaml::RoutedEventArgs const& e); - static void _SetupResources(); - std::shared_ptr _FindFirstLeaf() override; + void _UpdateBorders(); + void _UpdateVisuals(); SnapSizeResult _CalcSnappedDimension(const bool widthOrHeight, const float dimension) const override; void _AdvanceSnappedDimension(const bool widthOrHeight, LayoutSizeNode& sizeNode) const override; winrt::Windows::Foundation::Size _GetMinSize() const override; + + static void _SetupResources(); }; diff --git a/src/cascadia/TerminalApp/lib/ParentPane.cpp b/src/cascadia/TerminalApp/lib/ParentPane.cpp index b106af8bb1a..a400a9a6a4f 100644 --- a/src/cascadia/TerminalApp/lib/ParentPane.cpp +++ b/src/cascadia/TerminalApp/lib/ParentPane.cpp @@ -25,24 +25,60 @@ ParentPane::ParentPane(std::shared_ptr firstChild, std::shared_ptrGetRootElement(), 0); + _GetGridSetColOrRowFunc()(secondChild->GetRootElement(), 1); +} + +ParentPane::~ParentPane() +{ + OutputDebugString(L"~ParentPane()\n"); +} + +// Method Description: +// - Sets up row/column definitions for this pane. There are three total +// row/cols. The middle one is for the separator. The first and third are for +// each of the child panes, and are given a size in pixels, based off the +// availiable space, and the percent of the space they respectively consume, +// which is stored in _desiredSplitPosition +// - Does nothing if our split state is currently set to SplitState::None +// Arguments: +// - rootSize: The dimensions in pixels that this pane (and its children should consume.) +// Return Value: +// - +void ParentPane::_CreateRowColDefinitions(const Size& rootSize) +{ if (_splitState == SplitState::Vertical) { - Controls::Grid::SetColumn(firstChild->GetRootElement(), 0); - Controls::Grid::SetColumn(secondChild->GetRootElement(), 1); + _root.ColumnDefinitions().Clear(); + + // Create two columns in this grid: one for each pane + const auto paneSizes = _CalcChildrenSizes(rootSize.Width); + + auto firstColDef = Controls::ColumnDefinition(); + firstColDef.Width(GridLengthHelper::FromPixels(paneSizes.first)); + + auto secondColDef = Controls::ColumnDefinition(); + secondColDef.Width(GridLengthHelper::FromPixels(paneSizes.second)); + + _root.ColumnDefinitions().Append(firstColDef); + _root.ColumnDefinitions().Append(secondColDef); } else { - Controls::Grid::SetRow(firstChild->GetRootElement(), 0); - Controls::Grid::SetRow(secondChild->GetRootElement(), 1); - } -} + _root.RowDefinitions().Clear(); -ParentPane::~ParentPane() -{ - _RemoveAllChildEventHandlers(true); - _RemoveAllChildEventHandlers(false); + // Create two rows in this grid: one for each pane + const auto paneSizes = _CalcChildrenSizes(rootSize.Height); - OutputDebugString(L"~ParentPane()\n"); + auto firstRowDef = Controls::RowDefinition(); + firstRowDef.Height(GridLengthHelper::FromPixels(paneSizes.first)); + + auto secondRowDef = Controls::RowDefinition(); + secondRowDef.Height(GridLengthHelper::FromPixels(paneSizes.second)); + + _root.RowDefinitions().Append(firstRowDef); + _root.RowDefinitions().Append(secondRowDef); + } } void ParentPane::InitializeChildren() @@ -73,14 +109,7 @@ void ParentPane::_SetupChildEventHandlers(bool firstChild) child = splittedChild; _root.Children().SetAt(firstChild ? 0 : 1, child->GetRootElement()); - if (_splitState == SplitState::Vertical) - { - Controls::Grid::SetColumn(child->GetRootElement(), firstChild ? 0 : 1); - } - else - { - Controls::Grid::SetRow(child->GetRootElement(), firstChild ? 0 : 1); - } + _GetGridSetColOrRowFunc()(child->GetRootElement(), firstChild ? 0 : 1); _SetupChildEventHandlers(firstChild); }); @@ -92,14 +121,7 @@ void ParentPane::_SetupChildEventHandlers(bool firstChild) child = collapsedChild; _root.Children().SetAt(firstChild ? 0 : 1, child->GetRootElement()); - if (_splitState == SplitState::Vertical) - { - Controls::Grid::SetColumn(child->GetRootElement(), firstChild ? 0 : 1); - } - else - { - Controls::Grid::SetRow(child->GetRootElement(), firstChild ? 0 : 1); - } + _GetGridSetColOrRowFunc()(child->GetRootElement(), firstChild ? 0 : 1); _SetupChildEventHandlers(firstChild); }); @@ -123,15 +145,16 @@ void ParentPane::_RemoveAllChildEventHandlers(bool firstChild) } } -// Method Description: -// - Recalculates and reapplies sizes of all descendant panes. -// Arguments: -// - -// Return Value: -// - -void ParentPane::Relayout() +std::function ParentPane::_GetGridSetColOrRowFunc() const { - ResizeContent(_root.ActualSize()); + if (_splitState == SplitState::Vertical) + { + return Controls::Grid::SetColumn; + } + else + { + return Controls::Grid::SetRow; + } } std::shared_ptr ParentPane::FindActivePane() @@ -144,157 +167,37 @@ std::shared_ptr ParentPane::FindActivePane() return _secondChild->FindActivePane(); } -void ParentPane::ClearActive() -{ - _firstChild->ClearActive(); - _secondChild->ClearActive(); -} - -void ParentPane::UpdateSettings(const TerminalSettings& settings, const GUID& profile) +std::shared_ptr ParentPane::_FindFirstLeaf() { - _firstChild->UpdateSettings(settings, profile); - _secondChild->UpdateSettings(settings, profile); + return _firstChild->_FindFirstLeaf(); } -Pane::SnapSizeResult ParentPane::_CalcSnappedDimension(const bool widthOrHeight, const float dimension) const +void ParentPane::PropagateToLeaves(std::function action) { - if (_splitState == (widthOrHeight ? SplitState::Horizontal : SplitState::Vertical)) - { - // If we're resizing along separator axis, snap to the closest possibility - // given by our children panes. - - const auto firstSnapped = _firstChild->_CalcSnappedDimension(widthOrHeight, dimension); - const auto secondSnapped = _secondChild->_CalcSnappedDimension(widthOrHeight, dimension); - return { - std::max(firstSnapped.lower, secondSnapped.lower), - std::min(firstSnapped.higher, secondSnapped.higher) - }; - } - else - { - // If we're resizing perpendicularly to separator axis, calculate the sizes - // of child panes that would fit the given size. We use same algorithm that - // is used for real resize routine, but exclude the remaining empty space that - // would appear after the second pane. This will be the 'downward' snap possibility, - // while the 'upward' will be given as a side product of the layout function. - - const auto childSizes = _CalcSnappedChildrenSizes(widthOrHeight, dimension); - return { - childSizes.lower.first + childSizes.lower.second, - childSizes.higher.first + childSizes.higher.second - }; - } + _firstChild->PropagateToLeaves(action); + _secondChild->PropagateToLeaves(action); } -void ParentPane::_AdvanceSnappedDimension(const bool widthOrHeight, LayoutSizeNode& sizeNode) const +void ParentPane::PropagateToLeavesOnEdge(const winrt::TerminalApp::Direction& edge, std::function action) { - // We're a parent pane, so we have to advance dimension of our children panes. In - // fact, we advance only one child (chosen later) to keep the growth fine-grained. - - // To choose which child pane to advance, we actually need to know their advanced sizes - // in advance (oh), to see which one would 'fit' better. Often, this is already cached - // by the previous invocation of this function in nextFirstChild and nextSecondChild - // fields of given node. If not, we need to calculate them now. - if (sizeNode.nextFirstChild == nullptr) - { - sizeNode.nextFirstChild = std::make_unique(*sizeNode.firstChild); - _firstChild->_AdvanceSnappedDimension(widthOrHeight, *sizeNode.nextFirstChild); - } - if (sizeNode.nextSecondChild == nullptr) - { - sizeNode.nextSecondChild = std::make_unique(*sizeNode.secondChild); - _secondChild->_AdvanceSnappedDimension(widthOrHeight, *sizeNode.nextSecondChild); - } - - const auto nextFirstSize = sizeNode.nextFirstChild->size; - const auto nextSecondSize = sizeNode.nextSecondChild->size; - - // Choose which child pane to advance. - bool advanceFirstOrSecond; - if (_splitState == (widthOrHeight ? SplitState::Horizontal : SplitState::Vertical)) - { - // If we're growing along separator axis, choose the child that - // wants to be smaller than the other, so that the resulting size - // will be the smallest. - advanceFirstOrSecond = nextFirstSize < nextSecondSize; - } - else - { - // If we're growing perpendicularly to separator axis, choose a - // child so that their size ratio is closer to that we're trying - // to maintain (this is, the relative separator position is closer - // to the _desiredSplitPosition field). - - const auto firstSize = sizeNode.firstChild->size; - const auto secondSize = sizeNode.secondChild->size; - - // Because we rely on equality check, these calculations have to be - // immune to floating point errors. In common situation where both panes - // have the same character sizes and _desiredSplitPosition is 0.5 (or - // some simple fraction) both ratios will often be the same, and if so - // we always take the left child. It could be right as well, but it's - // important that it's consistent: that it would always go - // 1 -> 2 -> 1 -> 2 -> 1 -> 2 and not like 1 -> 1 -> 2 -> 2 -> 2 -> 1 - // which would look silly to the user but which occur if there was - // a non-floating-point-safe math. - const auto deviation1 = nextFirstSize - (nextFirstSize + secondSize) * _desiredSplitPosition; - const auto deviation2 = -1 * (firstSize - (firstSize + nextSecondSize) * _desiredSplitPosition); - advanceFirstOrSecond = deviation1 <= deviation2; - } - - // Here we advance one of our children. Because we already know the appropriate - // (advanced) size that given child would need to have, we simply assign that size - // to it. We then advance its 'next*' size (nextFirstChild or nextSecondChild) so - // the invariant holds (as it will likely be used by the next invocation of this - // function). The other child's next* size remains unchanged because its size - // haven't changed either. - if (advanceFirstOrSecond) - { - *sizeNode.firstChild = *sizeNode.nextFirstChild; - _firstChild->_AdvanceSnappedDimension(widthOrHeight, *sizeNode.nextFirstChild); - } - else - { - *sizeNode.secondChild = *sizeNode.nextSecondChild; - _secondChild->_AdvanceSnappedDimension(widthOrHeight, *sizeNode.nextSecondChild); - } - - // Since the size of one of our children has changed we need to update our size as well. - if (_splitState == (widthOrHeight ? SplitState::Horizontal : SplitState::Vertical)) + if (DirectionMatchesSplit(edge, _splitState)) { - sizeNode.size = std::max(sizeNode.firstChild->size, sizeNode.secondChild->size); + const auto adjacentChild = (_splitState == SplitState::Vertical && edge == Direction::Left || + _splitState == SplitState::Horizontal && edge == Direction::Up) ? + _firstChild : _secondChild; + adjacentChild->PropagateToLeavesOnEdge(edge, action); } else { - sizeNode.size = sizeNode.firstChild->size + sizeNode.secondChild->size; + _firstChild->PropagateToLeavesOnEdge(edge, action); + _secondChild->PropagateToLeavesOnEdge(edge, action); } - - // Because we have grown, we're certainly no longer of our - // minimal size (if we've ever been). - sizeNode.isMinimumSize = false; -} - -Size ParentPane::_GetMinSize() const -{ - const auto firstSize = _firstChild->_GetMinSize(); - const auto secondSize = _secondChild->_GetMinSize(); - - const auto minWidth = _splitState == SplitState::Vertical ? - firstSize.Width + secondSize.Width : - std::max(firstSize.Width, secondSize.Width); - const auto minHeight = _splitState == SplitState::Horizontal ? - firstSize.Height + secondSize.Height : - std::max(firstSize.Height, secondSize.Height); - - return { minWidth, minHeight }; } -Pane::LayoutSizeNode ParentPane::_CreateMinSizeTree(const bool widthOrHeight) const +void ParentPane::UpdateSettings(const TerminalSettings& settings, const GUID& profile) { - LayoutSizeNode node = Pane::_CreateMinSizeTree(widthOrHeight); - node.firstChild = std::make_unique(_firstChild->_CreateMinSizeTree(widthOrHeight)); - node.secondChild = std::make_unique(_secondChild->_CreateMinSizeTree(widthOrHeight)); - return node; + _firstChild->UpdateSettings(settings, profile); + _secondChild->UpdateSettings(settings, profile); } // Method Description: @@ -314,24 +217,35 @@ void ParentPane::ResizeContent(const Size& newSize) _CreateRowColDefinitions(newSize); + Size firstSize, secondSize; if (_splitState == SplitState::Vertical) { const auto paneSizes = _CalcChildrenSizes(width); - const Size firstSize{ paneSizes.first, height }; - const Size secondSize{ paneSizes.second, height }; - _firstChild->ResizeContent(firstSize); - _secondChild->ResizeContent(secondSize); + firstSize = { paneSizes.first, height }; + secondSize = { paneSizes.second, height }; } else if (_splitState == SplitState::Horizontal) { const auto paneSizes = _CalcChildrenSizes(height); - const Size firstSize{ width, paneSizes.first }; - const Size secondSize{ width, paneSizes.second }; - _firstChild->ResizeContent(firstSize); - _secondChild->ResizeContent(secondSize); + firstSize = { width, paneSizes.first }; + secondSize = { width, paneSizes.second }; } + + _firstChild->ResizeContent(firstSize); + _secondChild->ResizeContent(secondSize); +} + +// Method Description: +// - Recalculates and reapplies sizes of all descendant panes. +// Arguments: +// - +// Return Value: +// - +void ParentPane::Relayout() +{ + ResizeContent(_root.ActualSize()); } // Method Description: @@ -345,7 +259,7 @@ void ParentPane::ResizeContent(const Size& newSize) // - direction: The direction to move the separator in. // Return Value: // - true if we or a child handled this resize request. -bool ParentPane::ResizePane(const Direction& direction) +bool ParentPane::ResizeChild(const Direction& direction) { // Check if either our first or second child is the currently focused leaf. // If it is, and the requested resize direction matches our separator, then @@ -357,7 +271,7 @@ bool ParentPane::ResizePane(const Direction& direction) const bool secondIsFocused = secondChildAsLeaf && secondChildAsLeaf->WasLastActive(); if (firstIsFocused || secondIsFocused) { - return _Resize(direction); + return _ResizeChild(direction); } // If neither of our children were the focused leaf, then recurse into @@ -373,7 +287,7 @@ bool ParentPane::ResizePane(const Direction& direction) { if (_firstChild->FindActivePane()) { - return firstChildAsParent->ResizePane(direction) || _Resize(direction); + return firstChildAsParent->ResizeChild(direction) || _ResizeChild(direction); } } @@ -381,13 +295,54 @@ bool ParentPane::ResizePane(const Direction& direction) { if (_secondChild->FindActivePane()) { - return secondChildAsParent->ResizePane(direction) || _Resize(direction); + return secondChildAsParent->ResizeChild(direction) || _ResizeChild(direction); } } return false; } +// Method Description: +// - Adjust our child percentages to increase the size of one of our children +// and decrease the size of the other. +// - Adjusts the separation amount by 5% +// - Does nothing if the direction doesn't match our current split direction +// Arguments: +// - direction: the direction to move our separator. If it's down or right, +// we'll be increasing the size of the first of our children. Else, we'll be +// decreasing the size of our first child. +// Return Value: +// - false if we couldn't resize this pane in the given direction, else true. +bool ParentPane::_ResizeChild(const Direction& direction) +{ + if (!DirectionMatchesSplit(direction, _splitState)) + { + return false; + } + + float amount = .05f; + if (direction == Direction::Right || direction == Direction::Down) + { + amount = -amount; + } + + // Make sure we're not making a pane explode here by resizing it to 0 characters. + const bool changeWidth = _splitState == SplitState::Vertical; + + const Size actualSize{ gsl::narrow_cast(_root.ActualWidth()), + gsl::narrow_cast(_root.ActualHeight()) }; + // actualDimension is the size in DIPs of this pane in the direction we're + // resizing. + const auto actualDimension = changeWidth ? actualSize.Width : actualSize.Height; + + _desiredSplitPosition = _ClampSplitPosition(changeWidth, _desiredSplitPosition - amount, actualDimension); + + // Resize our columns to match the new percentages. + Relayout(); + + 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. @@ -395,7 +350,7 @@ bool ParentPane::ResizePane(const Direction& direction) // 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 +// - This method is _very_ similar to ResizeChild. Both are trying to find the // right separator to move (focus) in a direction. // Arguments: // - direction: The direction to move the focus in. @@ -443,115 +398,6 @@ bool ParentPane::NavigateFocus(const Direction& direction) return false; } -//// Method Description: -//// - Adds event handlers to our children to handle their close events. -//// Arguments: -//// - -//// Return Value: -//// - -//void ParentPane::_SetupChildCloseHandlers() -//{ -// _firstClosedToken = _firstChild->Closed([this](auto&& /*s*/, auto&& /*e*/) { -// _root.Dispatcher().RunAsync(CoreDispatcherPriority::Normal, [=]() { -// _CloseChild(true); -// }); -// }); -// -// _secondClosedToken = _secondChild->Closed([this](auto&& /*s*/, auto&& /*e*/) { -// _root.Dispatcher().RunAsync(CoreDispatcherPriority::Normal, [=]() { -// _CloseChild(false); -// }); -// }); -//} - -// Method Description: -// - Sets up row/column definitions for this pane. There are three total -// row/cols. The middle one is for the separator. The first and third are for -// each of the child panes, and are given a size in pixels, based off the -// availiable space, and the percent of the space they respectively consume, -// which is stored in _desiredSplitPosition -// - Does nothing if our split state is currently set to SplitState::None -// Arguments: -// - rootSize: The dimensions in pixels that this pane (and its children should consume.) -// Return Value: -// - -void ParentPane::_CreateRowColDefinitions(const Size& rootSize) -{ - if (_splitState == SplitState::Vertical) - { - _root.ColumnDefinitions().Clear(); - - // Create two columns in this grid: one for each pane - const auto paneSizes = _CalcChildrenSizes(rootSize.Width); - - auto firstColDef = Controls::ColumnDefinition(); - firstColDef.Width(GridLengthHelper::FromPixels(paneSizes.first)); - - auto secondColDef = Controls::ColumnDefinition(); - secondColDef.Width(GridLengthHelper::FromPixels(paneSizes.second)); - - _root.ColumnDefinitions().Append(firstColDef); - _root.ColumnDefinitions().Append(secondColDef); - } - else if (_splitState == SplitState::Horizontal) - { - _root.RowDefinitions().Clear(); - - // Create two rows in this grid: one for each pane - const auto paneSizes = _CalcChildrenSizes(rootSize.Height); - - auto firstRowDef = Controls::RowDefinition(); - firstRowDef.Height(GridLengthHelper::FromPixels(paneSizes.first)); - - auto secondRowDef = Controls::RowDefinition(); - secondRowDef.Height(GridLengthHelper::FromPixels(paneSizes.second)); - - _root.RowDefinitions().Append(firstRowDef); - _root.RowDefinitions().Append(secondRowDef); - } -} - -// Method Description: -// - Adjust our child percentages to increase the size of one of our children -// and decrease the size of the other. -// - Adjusts the separation amount by 5% -// - Does nothing if the direction doesn't match our current split direction -// Arguments: -// - direction: the direction to move our separator. If it's down or right, -// we'll be increasing the size of the first of our children. Else, we'll be -// decreasing the size of our first child. -// Return Value: -// - false if we couldn't resize this pane in the given direction, else true. -bool ParentPane::_Resize(const Direction& direction) -{ - if (!DirectionMatchesSplit(direction, _splitState)) - { - return false; - } - - float amount = .05f; - if (direction == Direction::Right || direction == Direction::Down) - { - amount = -amount; - } - - // Make sure we're not making a pane explode here by resizing it to 0 characters. - const bool changeWidth = _splitState == SplitState::Vertical; - - const Size actualSize{ gsl::narrow_cast(_root.ActualWidth()), - gsl::narrow_cast(_root.ActualHeight()) }; - // actualDimension is the size in DIPs of this pane in the direction we're - // resizing. - const auto actualDimension = changeWidth ? actualSize.Width : actualSize.Height; - - _desiredSplitPosition = _ClampSplitPosition(changeWidth, _desiredSplitPosition - amount, actualDimension); - - // Resize our columns to match the new percentages. - Relayout(); - - return true; -} - // 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 @@ -572,7 +418,8 @@ bool ParentPane::_NavigateFocus(const Direction& direction) const bool focusSecond = (direction == Direction::Right) || (direction == Direction::Down); const auto newlyFocusedChild = focusSecond ? _secondChild : _firstChild; - (focusSecond ? _firstChild : _secondChild)->ClearActive(); + const auto notFocusedChild = focusSecond ? _firstChild : _secondChild; + notFocusedChild->ClearActive(); // If the child we want to move focus to is _already_ focused, return false, // to try and let our parent figure it out. @@ -597,18 +444,23 @@ bool ParentPane::_NavigateFocus(const Direction& direction) // - void ParentPane::_CloseChild(const bool closeFirst) { - auto closedChild = closeFirst ? _firstChild : _secondChild; - auto remainingChild = closeFirst ? _secondChild : _firstChild; + // The closed child must always be leaf + const auto closedChild = std::dynamic_pointer_cast(closeFirst ? _firstChild : _secondChild); + THROW_HR_IF_NULL(E_FAIL, closedChild); - if (auto remainingChildAsLeaf = std::dynamic_pointer_cast(remainingChild)) - { - // When the remaining child is a leaf, that means both our children were - // previously leaves, so this is safe to cast it here. - remainingChildAsLeaf->OnNeightbourClosed(std::dynamic_pointer_cast(closedChild)); - } + const auto remainingChild = closeFirst ? _secondChild : _firstChild; _root.Children().Clear(); + const auto closedChildDir = (_splitState == SplitState::Vertical) ? + (closeFirst ? Direction::Left : Direction::Right) : + (closeFirst ? Direction::Up : Direction::Down); + + remainingChild->PropagateToLeavesOnEdge(closedChildDir, [=](LeafPane& paneOnEdge) { + paneOnEdge.UpdateBorderWithClosedNeightbour(closedChild, closedChildDir); + }); + + const auto lifeSaver = shared_from_this(); _ChildClosedHandlers(remainingChild); if (closedChild->FindActivePane()) @@ -617,32 +469,28 @@ void ParentPane::_CloseChild(const bool closeFirst) } } -std::shared_ptr ParentPane::_FindFirstLeaf() -{ - return _firstChild->_FindFirstLeaf(); -} - // Method Description: -// - Adjusts split position so that no child pane is smaller then its -// minimum size +// - Gets the size in pixels of each of our children, given the full size they +// should fill. Since these children own their own separators (borders), this +// size is their portion of our _entire_ size. If specified size is lower than +// required then children will be of minimum size. Snaps first child to grid +// but not the second. // Arguments: -// - widthOrHeight: if true, operates on width, otherwise on height. -// - requestedValue: split position value to be clamped -// - totalSize: size (width or height) of the parent pane +// - fullSize: the amount of space in pixels that should be filled by our +// children and their separators. Can be arbitrarily low. // Return Value: -// - split position (value in range <0.0, 1.0>) -float ParentPane::_ClampSplitPosition(const bool widthOrHeight, const float requestedValue, const float totalSize) const +// - a pair with the size of our first child and the size of our second child, +// respectively. +std::pair ParentPane::_CalcChildrenSizes(const float fullSize) const { - const auto firstMinSize = _firstChild->_GetMinSize(); - const auto secondMinSize = _secondChild->_GetMinSize(); - - const auto firstMinDimension = widthOrHeight ? firstMinSize.Width : firstMinSize.Height; - const auto secondMinDimension = widthOrHeight ? secondMinSize.Width : secondMinSize.Height; - - const auto minSplitPosition = firstMinDimension / totalSize; - const auto maxSplitPosition = 1.0f - (secondMinDimension / totalSize); + const auto widthOrHeight = _splitState == SplitState::Vertical; + const auto snappedSizes = _CalcSnappedChildrenSizes(widthOrHeight, fullSize).lower; - return std::clamp(requestedValue, minSplitPosition, maxSplitPosition); + // Keep the first pane snapped and give the second pane all remaining size + return { + snappedSizes.first, + fullSize - snappedSizes.first + }; } // Method Description: @@ -705,28 +553,168 @@ Pane::SnapChildrenSizeResult ParentPane::_CalcSnappedChildrenSizes(const bool wi { sizeTree.firstChild->size, sizeTree.secondChild->size } }; } +Pane::SnapSizeResult ParentPane::_CalcSnappedDimension(const bool widthOrHeight, const float dimension) const +{ + if (_splitState == (widthOrHeight ? SplitState::Horizontal : SplitState::Vertical)) + { + // If we're resizing along separator axis, snap to the closest possibility + // given by our children panes. + + const auto firstSnapped = _firstChild->_CalcSnappedDimension(widthOrHeight, dimension); + const auto secondSnapped = _secondChild->_CalcSnappedDimension(widthOrHeight, dimension); + return { + std::max(firstSnapped.lower, secondSnapped.lower), + std::min(firstSnapped.higher, secondSnapped.higher) + }; + } + else + { + // If we're resizing perpendicularly to separator axis, calculate the sizes + // of child panes that would fit the given size. We use same algorithm that + // is used for real resize routine, but exclude the remaining empty space that + // would appear after the second pane. This will be the 'downward' snap possibility, + // while the 'upward' will be given as a side product of the layout function. + + const auto childSizes = _CalcSnappedChildrenSizes(widthOrHeight, dimension); + return { + childSizes.lower.first + childSizes.lower.second, + childSizes.higher.first + childSizes.higher.second + }; + } +} + +void ParentPane::_AdvanceSnappedDimension(const bool widthOrHeight, LayoutSizeNode& sizeNode) const +{ + // We're a parent pane, so we have to advance dimension of our children panes. In + // fact, we advance only one child (chosen later) to keep the growth fine-grained. + + // To choose which child pane to advance, we actually need to know their advanced sizes + // in advance (oh), to see which one would 'fit' better. Often, this is already cached + // by the previous invocation of this function in nextFirstChild and nextSecondChild + // fields of given node. If not, we need to calculate them now. + if (sizeNode.nextFirstChild == nullptr) + { + sizeNode.nextFirstChild = std::make_unique(*sizeNode.firstChild); + _firstChild->_AdvanceSnappedDimension(widthOrHeight, *sizeNode.nextFirstChild); + } + if (sizeNode.nextSecondChild == nullptr) + { + sizeNode.nextSecondChild = std::make_unique(*sizeNode.secondChild); + _secondChild->_AdvanceSnappedDimension(widthOrHeight, *sizeNode.nextSecondChild); + } + + const auto nextFirstSize = sizeNode.nextFirstChild->size; + const auto nextSecondSize = sizeNode.nextSecondChild->size; + + // Choose which child pane to advance. + bool advanceFirstOrSecond; + if (_splitState == (widthOrHeight ? SplitState::Horizontal : SplitState::Vertical)) + { + // If we're growing along separator axis, choose the child that + // wants to be smaller than the other, so that the resulting size + // will be the smallest. + advanceFirstOrSecond = nextFirstSize < nextSecondSize; + } + else + { + // If we're growing perpendicularly to separator axis, choose a + // child so that their size ratio is closer to that we're trying + // to maintain (this is, the relative separator position is closer + // to the _desiredSplitPosition field). + + const auto firstSize = sizeNode.firstChild->size; + const auto secondSize = sizeNode.secondChild->size; + + // Because we rely on equality check, these calculations have to be + // immune to floating point errors. In common situation where both panes + // have the same character sizes and _desiredSplitPosition is 0.5 (or + // some simple fraction) both ratios will often be the same, and if so + // we always take the left child. It could be right as well, but it's + // important that it's consistent: that it would always go + // 1 -> 2 -> 1 -> 2 -> 1 -> 2 and not like 1 -> 1 -> 2 -> 2 -> 2 -> 1 + // which would look silly to the user but which occur if there was + // a non-floating-point-safe math. + const auto deviation1 = nextFirstSize - (nextFirstSize + secondSize) * _desiredSplitPosition; + const auto deviation2 = -1 * (firstSize - (firstSize + nextSecondSize) * _desiredSplitPosition); + advanceFirstOrSecond = deviation1 <= deviation2; + } + + // Here we advance one of our children. Because we already know the appropriate + // (advanced) size that given child would need to have, we simply assign that size + // to it. We then advance its 'next*' size (nextFirstChild or nextSecondChild) so + // the invariant holds (as it will likely be used by the next invocation of this + // function). The other child's next* size remains unchanged because its size + // haven't changed either. + if (advanceFirstOrSecond) + { + *sizeNode.firstChild = *sizeNode.nextFirstChild; + _firstChild->_AdvanceSnappedDimension(widthOrHeight, *sizeNode.nextFirstChild); + } + else + { + *sizeNode.secondChild = *sizeNode.nextSecondChild; + _secondChild->_AdvanceSnappedDimension(widthOrHeight, *sizeNode.nextSecondChild); + } + + // Since the size of one of our children has changed we need to update our size as well. + if (_splitState == (widthOrHeight ? SplitState::Horizontal : SplitState::Vertical)) + { + sizeNode.size = std::max(sizeNode.firstChild->size, sizeNode.secondChild->size); + } + else + { + sizeNode.size = sizeNode.firstChild->size + sizeNode.secondChild->size; + } + + // Because we have grown, we're certainly no longer of our + // minimal size (if we've ever been). + sizeNode.isMinimumSize = false; +} + +Size ParentPane::_GetMinSize() const +{ + const auto firstSize = _firstChild->_GetMinSize(); + const auto secondSize = _secondChild->_GetMinSize(); + + const auto minWidth = _splitState == SplitState::Vertical ? + firstSize.Width + secondSize.Width : + std::max(firstSize.Width, secondSize.Width); + const auto minHeight = _splitState == SplitState::Horizontal ? + firstSize.Height + secondSize.Height : + std::max(firstSize.Height, secondSize.Height); + + return { minWidth, minHeight }; +} + +Pane::LayoutSizeNode ParentPane::_CreateMinSizeTree(const bool widthOrHeight) const +{ + LayoutSizeNode node = Pane::_CreateMinSizeTree(widthOrHeight); + node.firstChild = std::make_unique(_firstChild->_CreateMinSizeTree(widthOrHeight)); + node.secondChild = std::make_unique(_secondChild->_CreateMinSizeTree(widthOrHeight)); + return node; +} + // Method Description: -// - Gets the size in pixels of each of our children, given the full size they -// should fill. Since these children own their own separators (borders), this -// size is their portion of our _entire_ size. If specified size is lower than -// required then children will be of minimum size. Snaps first child to grid -// but not the second. +// - Adjusts split position so that no child pane is smaller then its +// minimum size // Arguments: -// - fullSize: the amount of space in pixels that should be filled by our -// children and their separators. Can be arbitrarily low. +// - widthOrHeight: if true, operates on width, otherwise on height. +// - requestedValue: split position value to be clamped +// - totalSize: size (width or height) of the parent pane // Return Value: -// - a pair with the size of our first child and the size of our second child, -// respectively. -std::pair ParentPane::_CalcChildrenSizes(const float fullSize) const +// - split position (value in range <0.0, 1.0>) +float ParentPane::_ClampSplitPosition(const bool widthOrHeight, const float requestedValue, const float totalSize) const { - const auto widthOrHeight = _splitState == SplitState::Vertical; - const auto snappedSizes = _CalcSnappedChildrenSizes(widthOrHeight, fullSize).lower; + const auto firstMinSize = _firstChild->_GetMinSize(); + const auto secondMinSize = _secondChild->_GetMinSize(); - // Keep the first pane snapped and give the second pane all remaining size - return { - snappedSizes.first, - fullSize - snappedSizes.first - }; + const auto firstMinDimension = widthOrHeight ? firstMinSize.Width : firstMinSize.Height; + const auto secondMinDimension = widthOrHeight ? secondMinSize.Width : secondMinSize.Height; + + const auto minSplitPosition = firstMinDimension / totalSize; + const auto maxSplitPosition = 1.0f - (secondMinDimension / totalSize); + + return std::clamp(requestedValue, minSplitPosition, maxSplitPosition); } DEFINE_EVENT(ParentPane, ChildClosed, _ChildClosedHandlers, winrt::delegate>); diff --git a/src/cascadia/TerminalApp/lib/ParentPane.h b/src/cascadia/TerminalApp/lib/ParentPane.h index 5926f0664a5..1db938e1b26 100644 --- a/src/cascadia/TerminalApp/lib/ParentPane.h +++ b/src/cascadia/TerminalApp/lib/ParentPane.h @@ -1,5 +1,6 @@ #pragma once #include "Pane.h" +#include "LeafPane.h" #include #include "../../cascadia/inc/cppwinrt_utils.h" #include @@ -13,15 +14,19 @@ class ParentPane : public Pane, public std::enable_shared_from_this winrt::Windows::Foundation::Size currentSize); ~ParentPane() override; + void InitializeChildren(); + std::shared_ptr FindActivePane() override; - void Relayout() override; - void ClearActive() override; + void PropagateToLeaves(std::function action) override; + void PropagateToLeavesOnEdge(const winrt::TerminalApp::Direction& edge, + std::function action) override; + void UpdateSettings(const winrt::Microsoft::Terminal::Settings::TerminalSettings& settings, const GUID& profile); void ResizeContent(const winrt::Windows::Foundation::Size& newSize) override; + void Relayout() override; - void InitializeChildren(); - bool ResizePane(const winrt::TerminalApp::Direction& direction); + bool ResizeChild(const winrt::TerminalApp::Direction& direction); bool NavigateFocus(const winrt::TerminalApp::Direction& direction); DECLARE_EVENT(ChildClosed, _ChildClosedHandlers, winrt::delegate>); @@ -40,13 +45,19 @@ class ParentPane : public Pane, public std::enable_shared_from_this void _SetupChildEventHandlers(bool firstChild); void _RemoveAllChildEventHandlers(bool firstChild); void _CreateRowColDefinitions(const winrt::Windows::Foundation::Size& rootSize); + std::function _GetGridSetColOrRowFunc() const; - bool _Resize(const winrt::TerminalApp::Direction& direction); + std::shared_ptr _FindFirstLeaf() override; + bool _ResizeChild(const winrt::TerminalApp::Direction& direction); bool _NavigateFocus(const winrt::TerminalApp::Direction& direction); void _CloseChild(const bool closeFirst); std::pair _CalcChildrenSizes(const float fullSize) const; SnapChildrenSizeResult _CalcSnappedChildrenSizes(const bool widthOrHeight, const float fullSize) const; + SnapSizeResult _CalcSnappedDimension(const bool widthOrHeight, const float dimension) const override; + void _AdvanceSnappedDimension(const bool widthOrHeight, LayoutSizeNode& sizeNode) const override; + winrt::Windows::Foundation::Size _GetMinSize() const override; + LayoutSizeNode _CreateMinSizeTree(const bool widthOrHeight) const override; float _ClampSplitPosition(const bool widthOrHeight, const float requestedValue, const float totalSize) const; // Function Description: @@ -78,10 +89,4 @@ class ParentPane : public Pane, public std::enable_shared_from_this } return false; } - - std::shared_ptr _FindFirstLeaf() override; - SnapSizeResult _CalcSnappedDimension(const bool widthOrHeight, const float dimension) const override; - void _AdvanceSnappedDimension(const bool widthOrHeight, LayoutSizeNode& sizeNode) const override; - winrt::Windows::Foundation::Size _GetMinSize() const override; - LayoutSizeNode _CreateMinSizeTree(const bool widthOrHeight) const override; }; From 816228e447f58d53b54e97db5db2de54711fbb92 Mon Sep 17 00:00:00 2001 From: MCpiroman Date: Mon, 23 Dec 2019 11:02:40 +0100 Subject: [PATCH 03/17] Make compile and const qualify --- src/cascadia/TerminalApp/Pane.cpp | 1969 +++++-------------- src/cascadia/TerminalApp/Pane.h | 8 +- src/cascadia/TerminalApp/lib/LeafPane.cpp | 6 +- src/cascadia/TerminalApp/lib/LeafPane.h | 13 +- src/cascadia/TerminalApp/lib/ParentPane.cpp | 21 +- src/cascadia/TerminalApp/lib/ParentPane.h | 11 +- 6 files changed, 550 insertions(+), 1478 deletions(-) diff --git a/src/cascadia/TerminalApp/Pane.cpp b/src/cascadia/TerminalApp/Pane.cpp index aaa42789104..0b8cb0afcc6 100644 --- a/src/cascadia/TerminalApp/Pane.cpp +++ b/src/cascadia/TerminalApp/Pane.cpp @@ -17,345 +17,8 @@ using namespace winrt::Microsoft::Terminal::TerminalConnection; using namespace winrt::TerminalApp; using namespace TerminalApp; -static const int PaneBorderSize = 2; -static const int CombinedPaneBorderSize = 2 * PaneBorderSize; -static const float Half = 0.50f; - -winrt::Windows::UI::Xaml::Media::SolidColorBrush Pane::s_focusedBorderBrush = { nullptr }; -winrt::Windows::UI::Xaml::Media::SolidColorBrush Pane::s_unfocusedBorderBrush = { nullptr }; - -Pane::Pane(const GUID& profile, const TermControl& control, const bool lastFocused) : - _control{ control }, - _lastActive{ lastFocused }, - _profile{ profile } -{ - _root.Children().Append(_border); - _border.Child(_control); - - _connectionStateChangedToken = _control.ConnectionStateChanged({ this, &Pane::_ControlConnectionStateChangedHandler }); - - // On the first Pane's creation, lookup resources we'll use to theme the - // Pane, including the brushed to use for the focused/unfocused border - // color. - if (s_focusedBorderBrush == nullptr || s_unfocusedBorderBrush == nullptr) - { - _SetupResources(); - } - - // Register an event with the control to have it inform us when it gains focus. - _gotFocusRevoker = control.GotFocus(winrt::auto_revoke, { this, &Pane::_ControlGotFocusHandler }); - - // When our border is tapped, make sure to transfer focus to our control. - // LOAD-BEARING: This will NOT work if the border's BorderBrush is set to - // Colors::Transparent! The border won't get Tapped events, and they'll fall - // through to something else. - _border.Tapped([this](auto&, auto& e) { - _FocusFirstChild(); - e.Handled(true); - }); -} - -// Method Description: -// - Update the size of this pane. Resizes each of our columns so they have the -// same relative sizes, given the newSize. -// - Because we're just manually setting the row/column sizes in pixels, we have -// to be told our new size, we can't just use our own OnSized event, because -// that _won't fire when we get smaller_. -// Arguments: -// - newSize: the amount of space that this pane has to fill now. -// Return Value: -// - -void Pane::ResizeContent(const Size& newSize) +Pane::Pane() { - const auto width = newSize.Width; - const auto height = newSize.Height; - - _CreateRowColDefinitions(newSize); - - if (_splitState == SplitState::Vertical) - { - const auto paneSizes = _CalcChildrenSizes(width); - - const Size firstSize{ paneSizes.first, height }; - const Size secondSize{ paneSizes.second, height }; - _firstChild->ResizeContent(firstSize); - _secondChild->ResizeContent(secondSize); - } - else if (_splitState == SplitState::Horizontal) - { - const auto paneSizes = _CalcChildrenSizes(height); - - const Size firstSize{ width, paneSizes.first }; - const Size secondSize{ width, paneSizes.second }; - _firstChild->ResizeContent(firstSize); - _secondChild->ResizeContent(secondSize); - } -} - -// Method Description: -// - Recalculates and reapplies sizes of all descendant panes. -// Arguments: -// - -// Return Value: -// - -void Pane::Relayout() -{ - ResizeContent(_root.ActualSize()); -} - -// Method Description: -// - Adjust our child percentages to increase the size of one of our children -// and decrease the size of the other. -// - Adjusts the separation amount by 5% -// - Does nothing if the direction doesn't match our current split direction -// Arguments: -// - direction: the direction to move our separator. If it's down or right, -// we'll be increasing the size of the first of our children. Else, we'll be -// decreasing the size of our first child. -// Return Value: -// - false if we couldn't resize this pane in the given direction, else true. -bool Pane::_Resize(const Direction& direction) -{ - if (!DirectionMatchesSplit(direction, _splitState)) - { - return false; - } - - float amount = .05f; - if (direction == Direction::Right || direction == Direction::Down) - { - amount = -amount; - } - - // Make sure we're not making a pane explode here by resizing it to 0 characters. - const bool changeWidth = _splitState == SplitState::Vertical; - - const Size actualSize{ gsl::narrow_cast(_root.ActualWidth()), - gsl::narrow_cast(_root.ActualHeight()) }; - // actualDimension is the size in DIPs of this pane in the direction we're - // resizing. - const auto actualDimension = changeWidth ? actualSize.Width : actualSize.Height; - - _desiredSplitPosition = _ClampSplitPosition(changeWidth, _desiredSplitPosition - amount, actualDimension); - - // Resize our columns to match the new percentages. - ResizeContent(actualSize); - - return true; -} - -// Method Description: -// - Moves the separator between panes, as to resize each child on either size -// of the separator. Tries to move a separator in the given direction. The -// separator moved is the separator that's closest depth-wise to the -// currently focused pane, that's also in the correct direction to be moved. -// If there isn't such a separator, then this method returns false, as we -// couldn't handle the resize. -// Arguments: -// - direction: The direction to move the separator in. -// Return Value: -// - true if we or a child handled this resize request. -bool Pane::ResizePane(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 resize direction matches our separator, then - // we're the pane that needs to adjust its separator. - // If our separator is the wrong direction, then we can't handle it. - const bool firstIsFocused = _firstChild->_IsLeaf() && _firstChild->_lastActive; - const bool secondIsFocused = _secondChild->_IsLeaf() && _secondChild->_lastActive; - if (firstIsFocused || secondIsFocused) - { - return _Resize(direction); - } - - // If neither of our children were the focused leaf, then recurse into - // our children and see if they can handle the resize. - // For each child, if it has a focused descendant, try having that child - // handle the resize. - // If the child wasn't able to handle the resize, 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 resize. Otherwise, just return false, as we couldn't handle it - // either. - if ((!_firstChild->_IsLeaf()) && _firstChild->_HasFocusedChild()) - { - return _firstChild->ResizePane(direction) || _Resize(direction); - } - - if ((!_secondChild->_IsLeaf()) && _secondChild->_HasFocusedChild()) - { - return _secondChild->ResizePane(direction) || _Resize(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->_HasFocusedChild()) - { - return false; - } - - // Transfer focus to our child, and update the focus of our tree. - newlyFocusedChild->_FocusFirstChild(); - UpdateVisuals(); - - 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->_lastActive; - const bool secondIsFocused = _secondChild->_IsLeaf() && _secondChild->_lastActive; - if (firstIsFocused || secondIsFocused) - { - return _NavigateFocus(direction); - } - - // 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); - } - - 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. -// - If this was called, and we became a parent pane (due to work on another -// thread), this function will do nothing (allowing the control's new parent -// to handle the event instead). -// Arguments: -// - -// Return Value: -// - -void Pane::_ControlConnectionStateChangedHandler(const TermControl& /*sender*/, const winrt::Windows::Foundation::IInspectable& /*args*/) -{ - std::unique_lock lock{ _createCloseLock }; - // It's possible that this event handler started being executed, then before - // we got the lock, another thread created another child. So our control is - // actually no longer _our_ control, and instead could be a descendant. - // - // When the control's new Pane takes ownership of the control, the new - // parent will register it's own event handler. That event handler will get - // fired after this handler returns, and will properly cleanup state. - if (!_IsLeaf()) - { - return; - } - - const auto newConnectionState = _control.ConnectionState(); - - if (newConnectionState < ConnectionState::Closed) - { - // Pane doesn't care if the connection isn't entering a terminal state. - return; - } - - const auto& settings = CascadiaSettings::GetCurrentAppSettings(); - auto paneProfile = settings.FindProfile(_profile.value()); - if (paneProfile) - { - auto mode = paneProfile->GetCloseOnExitMode(); - if ((mode == CloseOnExitMode::Always) || - (mode == CloseOnExitMode::Graceful && newConnectionState == ConnectionState::Closed)) - { - _ClosedHandlers(nullptr, nullptr); - } - } -} - -// Event Description: -// - Called when our control gains focus. We'll use this to trigger our GotFocus -// callback. The tab that's hosting us should have registered a callback which -// can be used to mark us as active. -// Arguments: -// - -// Return Value: -// - -void Pane::_ControlGotFocusHandler(winrt::Windows::Foundation::IInspectable const& /* sender */, - RoutedEventArgs const& /* args */) -{ - _GotFocusHandlers(shared_from_this()); -} - -// Method Description: -// - Fire our Closed event to tell our parent that we should be removed. -// Arguments: -// - -// Return Value: -// - -void Pane::Close() -{ - // Fire our Closed event to tell our parent that we should be removed. - _ClosedHandlers(nullptr, nullptr); } // Method Description: @@ -365,803 +28,11 @@ void Pane::Close() // - // Return Value: // - the Grid acting as the root of this pane. -Controls::Grid Pane::GetRootElement() +Controls::Grid Pane::GetRootElement() const { return _root; } -// Method Description: -// - If this is the last focused pane, returns itself. Returns nullptr if this -// is a leaf and it's not focused. If it's a parent, it returns nullptr if no -// children of this pane were the last pane to be focused, or the Pane that -// _was_ the last pane to be focused (if there was one). -// - This Pane's control might not currently be focused, if the tab itself is -// not currently focused. -// Return Value: -// - nullptr if we're a leaf and unfocused, or no children were marked -// `_lastActive`, else returns this -std::shared_ptr Pane::GetActivePane() -{ - if (_IsLeaf()) - { - return _lastActive ? shared_from_this() : nullptr; - } - - auto firstFocused = _firstChild->GetActivePane(); - if (firstFocused != nullptr) - { - return firstFocused; - } - return _secondChild->GetActivePane(); -} - -// Method Description: -// - Gets the TermControl of this pane. If this Pane is not a leaf, this will return nullptr. -// Arguments: -// - -// Return Value: -// - nullptr if this Pane is a parent, otherwise the TermControl of this Pane. -TermControl Pane::GetTerminalControl() -{ - return _IsLeaf() ? _control : nullptr; -} - -// Method Description: -// - Recursively remove the "Active" state from this Pane and all it's children. -// - Updates our visuals to match our new state, including highlighting our borders. -// Arguments: -// - -// Return Value: -// - -void Pane::ClearActive() -{ - _lastActive = false; - if (!_IsLeaf()) - { - _firstChild->ClearActive(); - _secondChild->ClearActive(); - } - UpdateVisuals(); -} - -// Method Description: -// - Sets the "Active" state on this Pane. Only one Pane in a tree of Panes -// should be "active", and that pane should be a leaf. -// - Updates our visuals to match our new state, including highlighting our borders. -// Arguments: -// - -// Return Value: -// - -void Pane::SetActive() -{ - _lastActive = true; - UpdateVisuals(); -} - -// Method Description: -// - Returns nullopt if no children of this pane were the last control to be -// focused, or the GUID of the profile of the last control to be focused (if -// there was one). -// Arguments: -// - -// Return Value: -// - nullopt if no children of this pane were the last control to be -// focused, else the GUID of the profile of the last control to be focused -std::optional Pane::GetFocusedProfile() -{ - auto lastFocused = GetActivePane(); - return lastFocused ? lastFocused->_profile : std::nullopt; -} - -// Method Description: -// - Returns true if this pane was the last pane to be focused in a tree of panes. -// Arguments: -// - -// Return Value: -// - true iff we were the last pane focused in this tree of panes. -bool Pane::WasLastFocused() const noexcept -{ - return _lastActive; -} - -// Method Description: -// - Returns true iff this pane has no child panes. -// Arguments: -// - -// Return Value: -// - true iff this pane has no child panes. -bool Pane::_IsLeaf() const noexcept -{ - return _splitState == SplitState::None; -} - -// Method Description: -// - Returns true if this pane is currently focused, or there is a pane which is -// a child of this pane that is actively focused -// Arguments: -// - -// Return Value: -// - true if the currently focused pane is either this pane, or one of this -// pane's descendants -bool Pane::_HasFocusedChild() const noexcept -{ - // We're intentionally making this one giant expression, so the compiler - // will skip the following lookups if one of the lookups before it returns - // true - return (_control && _lastActive) || - (_firstChild && _firstChild->_HasFocusedChild()) || - (_secondChild && _secondChild->_HasFocusedChild()); -} - -// Method Description: -// - Update the focus state of this pane. We'll make sure to colorize our -// borders depending on if we are the active pane or not. -// Arguments: -// - -// Return Value: -// - -void Pane::UpdateVisuals() -{ - _border.BorderBrush(_lastActive ? s_focusedBorderBrush : s_unfocusedBorderBrush); -} - -// Method Description: -// - Focuses this control if we're a leaf, or attempts to focus the first leaf -// of our first child, recursively. -// Arguments: -// - -// Return Value: -// - -void Pane::_FocusFirstChild() -{ - if (_IsLeaf()) - { - _control.Focus(FocusState::Programmatic); - } - else - { - _firstChild->_FocusFirstChild(); - } -} - -// Method Description: -// - Attempts to update the settings of this pane or any children of this pane. -// * If this pane is a leaf, and our profile guid matches the parameter, then -// we'll apply the new settings to our control. -// * If we're not a leaf, we'll recurse on our children. -// Arguments: -// - settings: The new TerminalSettings to apply to any matching controls -// - profile: The GUID of the profile these settings should apply to. -// Return Value: -// - -void Pane::UpdateSettings(const TerminalSettings& settings, const GUID& profile) -{ - if (!_IsLeaf()) - { - _firstChild->UpdateSettings(settings, profile); - _secondChild->UpdateSettings(settings, profile); - } - else - { - if (profile == _profile) - { - _control.UpdateSettings(settings); - } - } -} - -// Method Description: -// - Closes one of our children. In doing so, takes the control from the other -// child, and makes this pane a leaf node again. -// Arguments: -// - closeFirst: if true, the first child should be closed, and the second -// should be preserved, and vice-versa for false. -// Return Value: -// - -void Pane::_CloseChild(const bool closeFirst) -{ - // Lock the create/close lock so that another operation won't concurrently - // modify our tree - std::unique_lock lock{ _createCloseLock }; - - // If we're a leaf, then chances are both our children closed in close - // succession. We waited on the lock while the other child was closed, so - // now we don't have a child to close anymore. Return here. When we moved - // the non-closed child into us, we also set up event handlers that will be - // triggered when we return from this. - if (_IsLeaf()) - { - return; - } - - auto closedChild = closeFirst ? _firstChild : _secondChild; - auto remainingChild = closeFirst ? _secondChild : _firstChild; - - // If the only child left is a leaf, that means we're a leaf now. - if (remainingChild->_IsLeaf()) - { - // When the remaining child is a leaf, that means both our children were - // previously leaves, and the only difference in their borders is the - // border that we gave them. Take a bitwise AND of those two children to - // remove that border. Other borders the children might have, they - // inherited from us, so the flag will be set for both children. - _borders = _firstChild->_borders & _secondChild->_borders; - - // take the control and profile of the pane that _wasn't_ closed. - _control = remainingChild->_control; - _profile = remainingChild->_profile; - - // Add our new event handler before revoking the old one. - _connectionStateChangedToken = _control.ConnectionStateChanged({ this, &Pane::_ControlConnectionStateChangedHandler }); - - // Revoke the old event handlers. Remove both the handlers for the panes - // themselves closing, and remove their handlers for their controls - // closing. At this point, if the remaining child's control is closed, - // they'll trigger only our event handler for the control's close. - _firstChild->Closed(_firstClosedToken); - _secondChild->Closed(_secondClosedToken); - closedChild->_control.ConnectionStateChanged(closedChild->_connectionStateChangedToken); - remainingChild->_control.ConnectionStateChanged(remainingChild->_connectionStateChangedToken); - - // If either of our children was focused, we want to take that focus from - // them. - _lastActive = _firstChild->_lastActive || _secondChild->_lastActive; - - // Remove all the ui elements of our children. This'll make sure we can - // re-attach the TermControl to our Grid. - _firstChild->_root.Children().Clear(); - _secondChild->_root.Children().Clear(); - _firstChild->_border.Child(nullptr); - _secondChild->_border.Child(nullptr); - - // Reset our UI: - _root.Children().Clear(); - _border.Child(nullptr); - _root.ColumnDefinitions().Clear(); - _root.RowDefinitions().Clear(); - - // Reattach the TermControl to our grid. - _root.Children().Append(_border); - _border.Child(_control); - - // Make sure to set our _splitState before focusing the control. If you - // fail to do this, when the tab handles the GotFocus event and asks us - // what our active control is, we won't technically be a "leaf", and - // GetTerminalControl will return null. - _splitState = SplitState::None; - - // re-attach our handler for the control's GotFocus event. - _gotFocusRevoker = _control.GotFocus(winrt::auto_revoke, { this, &Pane::_ControlGotFocusHandler }); - - // If we're inheriting the "last active" state from one of our children, - // focus our control now. This should trigger our own GotFocus event. - if (_lastActive) - { - _control.Focus(FocusState::Programmatic); - } - - _UpdateBorders(); - - // Release our children. - _firstChild = nullptr; - _secondChild = nullptr; - } - else - { - // Determine which border flag we gave to the child when we first split - // it, so that we can take just that flag away from them. - Borders clearBorderFlag = Borders::None; - if (_splitState == SplitState::Horizontal) - { - clearBorderFlag = closeFirst ? Borders::Top : Borders::Bottom; - } - else if (_splitState == SplitState::Vertical) - { - clearBorderFlag = closeFirst ? Borders::Left : Borders::Right; - } - - // First stash away references to the old panes and their tokens - const auto oldFirstToken = _firstClosedToken; - const auto oldSecondToken = _secondClosedToken; - const auto oldFirst = _firstChild; - const auto oldSecond = _secondChild; - - // Steal all the state from our child - _splitState = remainingChild->_splitState; - _firstChild = remainingChild->_firstChild; - _secondChild = remainingChild->_secondChild; - - // Set up new close handlers on the children - _SetupChildCloseHandlers(); - - // Revoke the old event handlers on our new children - _firstChild->Closed(remainingChild->_firstClosedToken); - _secondChild->Closed(remainingChild->_secondClosedToken); - - // Revoke event handlers on old panes and controls - oldFirst->Closed(oldFirstToken); - oldSecond->Closed(oldSecondToken); - closedChild->_control.ConnectionStateChanged(closedChild->_connectionStateChangedToken); - - // Reset our UI: - _root.Children().Clear(); - _border.Child(nullptr); - _root.ColumnDefinitions().Clear(); - _root.RowDefinitions().Clear(); - - // Copy the old UI over to our grid. - // Start by copying the row/column definitions. Iterate over the - // rows/cols, and remove each one from the old grid, and attach it to - // our grid instead. - while (remainingChild->_root.ColumnDefinitions().Size() > 0) - { - auto col = remainingChild->_root.ColumnDefinitions().GetAt(0); - remainingChild->_root.ColumnDefinitions().RemoveAt(0); - _root.ColumnDefinitions().Append(col); - } - while (remainingChild->_root.RowDefinitions().Size() > 0) - { - auto row = remainingChild->_root.RowDefinitions().GetAt(0); - remainingChild->_root.RowDefinitions().RemoveAt(0); - _root.RowDefinitions().Append(row); - } - - // Remove the child's UI elements from the child's grid, so we can - // attach them to us instead. - remainingChild->_root.Children().Clear(); - remainingChild->_border.Child(nullptr); - - _root.Children().Append(_firstChild->GetRootElement()); - _root.Children().Append(_secondChild->GetRootElement()); - - // Take the flag away from the children that they inherited from their - // parent, and update their borders to visually match - WI_ClearAllFlags(_firstChild->_borders, clearBorderFlag); - WI_ClearAllFlags(_secondChild->_borders, clearBorderFlag); - _UpdateBorders(); - _firstChild->_UpdateBorders(); - _secondChild->_UpdateBorders(); - - // If the closed child was focused, transfer the focus to it's first sibling. - if (closedChild->_lastActive) - { - _FocusFirstChild(); - } - - // Release the pointers that the child was holding. - remainingChild->_firstChild = nullptr; - remainingChild->_secondChild = nullptr; - } -} - -winrt::fire_and_forget Pane::_CloseChildRoutine(const bool closeFirst) -{ - auto weakThis{ shared_from_this() }; - - co_await winrt::resume_foreground(_root.Dispatcher()); - - if (auto pane{ weakThis.get() }) - { - _CloseChild(closeFirst); - } -} - -// Method Description: -// - Adds event handlers to our children to handle their close events. -// Arguments: -// - -// Return Value: -// - -void Pane::_SetupChildCloseHandlers() -{ - _firstClosedToken = _firstChild->Closed([this](auto&& /*s*/, auto&& /*e*/) { - _CloseChildRoutine(true); - }); - - _secondClosedToken = _secondChild->Closed([this](auto&& /*s*/, auto&& /*e*/) { - _CloseChildRoutine(false); - }); -} - -// Method Description: -// - Sets up row/column definitions for this pane. There are three total -// row/cols. The middle one is for the separator. The first and third are for -// each of the child panes, and are given a size in pixels, based off the -// availiable space, and the percent of the space they respectively consume, -// which is stored in _desiredSplitPosition -// - Does nothing if our split state is currently set to SplitState::None -// Arguments: -// - rootSize: The dimensions in pixels that this pane (and its children should consume.) -// Return Value: -// - -void Pane::_CreateRowColDefinitions(const Size& rootSize) -{ - if (_splitState == SplitState::Vertical) - { - _root.ColumnDefinitions().Clear(); - - // Create two columns in this grid: one for each pane - const auto paneSizes = _CalcChildrenSizes(rootSize.Width); - - auto firstColDef = Controls::ColumnDefinition(); - firstColDef.Width(GridLengthHelper::FromPixels(paneSizes.first)); - - auto secondColDef = Controls::ColumnDefinition(); - secondColDef.Width(GridLengthHelper::FromPixels(paneSizes.second)); - - _root.ColumnDefinitions().Append(firstColDef); - _root.ColumnDefinitions().Append(secondColDef); - } - else if (_splitState == SplitState::Horizontal) - { - _root.RowDefinitions().Clear(); - - // Create two rows in this grid: one for each pane - const auto paneSizes = _CalcChildrenSizes(rootSize.Height); - - auto firstRowDef = Controls::RowDefinition(); - firstRowDef.Height(GridLengthHelper::FromPixels(paneSizes.first)); - - auto secondRowDef = Controls::RowDefinition(); - secondRowDef.Height(GridLengthHelper::FromPixels(paneSizes.second)); - - _root.RowDefinitions().Append(firstRowDef); - _root.RowDefinitions().Append(secondRowDef); - } -} - -// Method Description: -// - Initializes our UI for a new split in this pane. Sets up row/column -// definitions, and initializes the separator grid. Does nothing if our split -// state is currently set to SplitState::None -// Arguments: -// - -// Return Value: -// - -void Pane::_CreateSplitContent() -{ - Size actualSize{ gsl::narrow_cast(_root.ActualWidth()), - gsl::narrow_cast(_root.ActualHeight()) }; - - _CreateRowColDefinitions(actualSize); -} - -// Method Description: -// - Sets the thickness of each side of our borders to match our _borders state. -// Arguments: -// - -// Return Value: -// - -void Pane::_UpdateBorders() -{ - double top = 0, bottom = 0, left = 0, right = 0; - - Thickness newBorders{ 0 }; - if (WI_IsFlagSet(_borders, Borders::Top)) - { - top = PaneBorderSize; - } - if (WI_IsFlagSet(_borders, Borders::Bottom)) - { - bottom = PaneBorderSize; - } - if (WI_IsFlagSet(_borders, Borders::Left)) - { - left = PaneBorderSize; - } - if (WI_IsFlagSet(_borders, Borders::Right)) - { - right = PaneBorderSize; - } - _border.BorderThickness(ThicknessHelper::FromLengths(left, top, right, bottom)); -} - -// Method Description: -// - Sets the row/column of our child UI elements, to match our current split type. -// Arguments: -// - -// Return Value: -// - -void Pane::_ApplySplitDefinitions() -{ - if (_splitState == SplitState::Vertical) - { - Controls::Grid::SetColumn(_firstChild->GetRootElement(), 0); - Controls::Grid::SetColumn(_secondChild->GetRootElement(), 1); - - _firstChild->_borders = _borders | Borders::Right; - _secondChild->_borders = _borders | Borders::Left; - _borders = Borders::None; - - _UpdateBorders(); - _firstChild->_UpdateBorders(); - _secondChild->_UpdateBorders(); - } - else if (_splitState == SplitState::Horizontal) - { - Controls::Grid::SetRow(_firstChild->GetRootElement(), 0); - Controls::Grid::SetRow(_secondChild->GetRootElement(), 1); - - _firstChild->_borders = _borders | Borders::Bottom; - _secondChild->_borders = _borders | Borders::Top; - _borders = Borders::None; - - _UpdateBorders(); - _firstChild->_UpdateBorders(); - _secondChild->_UpdateBorders(); - } -} - -// Method Description: -// - Determines whether the pane can be split -// Arguments: -// - splitType: what type of split we want to create. -// Return Value: -// - True if the pane can be split. False otherwise. -bool Pane::CanSplit(SplitState splitType) -{ - if (_IsLeaf()) - { - return _CanSplit(splitType); - } - - if (_firstChild->_HasFocusedChild()) - { - return _firstChild->CanSplit(splitType); - } - - if (_secondChild->_HasFocusedChild()) - { - return _secondChild->CanSplit(splitType); - } - - return false; -} - -// Method Description: -// - Split the focused pane in our tree of panes, and place the given -// TermControl into the newly created pane. If we're the focused pane, then -// we'll create two new children, and place them side-by-side in our Grid. -// Arguments: -// - splitType: what type of split we want to create. -// - profile: The profile GUID to associate with the newly created pane. -// - control: A TermControl to use in the new pane. -// Return Value: -// - The two newly created Panes -std::pair, std::shared_ptr> Pane::Split(SplitState splitType, const GUID& profile, const TermControl& control) -{ - if (!_IsLeaf()) - { - if (_firstChild->_HasFocusedChild()) - { - return _firstChild->Split(splitType, profile, control); - } - else if (_secondChild->_HasFocusedChild()) - { - return _secondChild->Split(splitType, profile, control); - } - - return { nullptr, nullptr }; - } - - return _Split(splitType, profile, control); -} - -// Method Description: -// - Converts an "automatic" split type into either Vertical or Horizontal, -// based upon the current dimensions of the Pane. -// - If any of the other SplitState values are passed in, they're returned -// unmodified. -// Arguments: -// - splitType: The SplitState to attempt to convert -// Return Value: -// - None if splitType was None, otherwise one of Horizontal or Vertical -SplitState Pane::_convertAutomaticSplitState(const SplitState& splitType) const -{ - // Careful here! If the pane doesn't yet have a size, these dimensions will - // be 0, and we'll always return Vertical. - - if (splitType == SplitState::Automatic) - { - // If the requested split type was "auto", determine which direction to - // split based on our current dimensions - const Size actualSize{ gsl::narrow_cast(_root.ActualWidth()), - gsl::narrow_cast(_root.ActualHeight()) }; - return actualSize.Width >= actualSize.Height ? SplitState::Vertical : SplitState::Horizontal; - } - return splitType; -} - -// Method Description: -// - Determines whether the pane can be split. -// Arguments: -// - splitType: what type of split we want to create. -// Return Value: -// - True if the pane can be split. False otherwise. -bool Pane::_CanSplit(SplitState splitType) -{ - const Size actualSize{ gsl::narrow_cast(_root.ActualWidth()), - gsl::narrow_cast(_root.ActualHeight()) }; - - const Size minSize = _GetMinSize(); - - auto actualSplitType = _convertAutomaticSplitState(splitType); - - if (actualSplitType == SplitState::None) - { - return false; - } - - if (actualSplitType == SplitState::Vertical) - { - const auto widthMinusSeparator = actualSize.Width - CombinedPaneBorderSize; - const auto newWidth = widthMinusSeparator * Half; - - return newWidth > minSize.Width; - } - - if (actualSplitType == SplitState::Horizontal) - { - const auto heightMinusSeparator = actualSize.Height - CombinedPaneBorderSize; - const auto newHeight = heightMinusSeparator * Half; - - return newHeight > minSize.Height; - } - - return false; -} - -// Method Description: -// - Does the bulk of the work of creating a new split. Initializes our UI, -// creates a new Pane to host the control, registers event handlers. -// Arguments: -// - splitType: what type of split we should create. -// - profile: The profile GUID to associate with the newly created pane. -// - control: A TermControl to use in the new pane. -// Return Value: -// - The two newly created Panes -std::pair, std::shared_ptr> Pane::_Split(SplitState splitType, const GUID& profile, const TermControl& control) -{ - if (splitType == SplitState::None) - { - return { nullptr, nullptr }; - } - - auto actualSplitType = _convertAutomaticSplitState(splitType); - - // Lock the create/close lock so that another operation won't concurrently - // modify our tree - std::unique_lock lock{ _createCloseLock }; - - // revoke our handler - the child will take care of the control now. - _control.ConnectionStateChanged(_connectionStateChangedToken); - _connectionStateChangedToken.value = 0; - - // Remove our old GotFocus handler from the control. We don't what the - // control telling us that it's now focused, we want it telling its new - // parent. - _gotFocusRevoker.revoke(); - - _splitState = actualSplitType; - _desiredSplitPosition = Half; - - // Remove any children we currently have. We can't add the existing - // TermControl to a new grid until we do this. - _root.Children().Clear(); - _border.Child(nullptr); - - // Create two new Panes - // Move our control, guid into the first one. - // Move the new guid, control into the second. - _firstChild = std::make_shared(_profile.value(), _control); - _profile = std::nullopt; - _control = { nullptr }; - _secondChild = std::make_shared(profile, control); - - _CreateSplitContent(); - - _root.Children().Append(_firstChild->GetRootElement()); - _root.Children().Append(_secondChild->GetRootElement()); - - _ApplySplitDefinitions(); - - // Register event handlers on our children to handle their Close events - _SetupChildCloseHandlers(); - - _lastActive = false; - - return { _firstChild, _secondChild }; -} - -// Method Description: -// - Gets the size in pixels of each of our children, given the full size they -// should fill. Since these children own their own separators (borders), this -// size is their portion of our _entire_ size. If specified size is lower than -// required then children will be of minimum size. Snaps first child to grid -// but not the second. -// Arguments: -// - fullSize: the amount of space in pixels that should be filled by our -// children and their separators. Can be arbitrarily low. -// Return Value: -// - a pair with the size of our first child and the size of our second child, -// respectively. -std::pair Pane::_CalcChildrenSizes(const float fullSize) const -{ - const auto widthOrHeight = _splitState == SplitState::Vertical; - const auto snappedSizes = _CalcSnappedChildrenSizes(widthOrHeight, fullSize).lower; - - // Keep the first pane snapped and give the second pane all remaining size - return { - snappedSizes.first, - fullSize - snappedSizes.first - }; -} - -// Method Description: -// - Gets the size in pixels of each of our children, given the full size they should -// fill. Each child is snapped to char grid as close as possible. If called multiple -// times with fullSize argument growing, then both returned sizes are guaranteed to be -// non-decreasing (it's a monotonically increasing function). This is important so that -// user doesn't get any pane shrank when they actually expand the window or parent pane. -// That is also required by the layout algorithm. -// Arguments: -// - widthOrHeight: if true, operates on width, otherwise on height. -// - fullSize: the amount of space in pixels that should be filled by our children and -// their separator. Can be arbitrarily low. -// Return Value: -// - a structure holding the result of this calculation. The 'lower' field represents the -// children sizes that would fit in the fullSize, but might (and usually do) not fill it -// completely. The 'higher' field represents the size of the children if they slightly exceed -// the fullSize, but are snapped. If the children can be snapped and also exactly match -// the fullSize, then both this fields have the same value that represent this situation. -Pane::SnapChildrenSizeResult Pane::_CalcSnappedChildrenSizes(const bool widthOrHeight, const float fullSize) const -{ - if (_IsLeaf()) - { - THROW_HR(E_FAIL); - } - - // First we build a tree of nodes corresponding to the tree of our descendant panes. - // Each node represents a size of given pane. At the beginning, each node has the minimum - // size that the corresponding pane can have; so has the our (root) node. We then gradually - // expand our node (which in turn expands some of the child nodes) until we hit the desired - // size. Since each expand step (done in _AdvanceSnappedDimension()) guarantees that all the - // sizes will be snapped, our return values is also snapped. - // Why do we do it this, iterative way? Why can't we just split the given size by - // _desiredSplitPosition and snap it latter? Because it's hardly doable, if possible, to also - // fulfill the monotonicity requirement that way. As the fullSize increases, the proportional - // point that separates children panes also moves and cells sneak in the available area in - // unpredictable way, regardless which child has the snap priority or whether we snap them - // upward, downward or to nearest. - // With present way we run the same sequence of actions regardless to the fullSize value and - // only just stop at various moments when the built sizes reaches it. Eventually, this could - // be optimized for simple cases like when both children are both leaves with the same character - // size, but it doesn't seem to be beneficial. - - auto sizeTree = _CreateMinSizeTree(widthOrHeight); - LayoutSizeNode lastSizeTree{ sizeTree }; - - while (sizeTree.size < fullSize) - { - lastSizeTree = sizeTree; - _AdvanceSnappedDimension(widthOrHeight, sizeTree); - - if (sizeTree.size == fullSize) - { - // If we just hit exactly the requested value, then just return the - // current state of children. - return { { sizeTree.firstChild->size, sizeTree.secondChild->size }, - { sizeTree.firstChild->size, sizeTree.secondChild->size } }; - } - } - - // We exceeded the requested size in the loop above, so lastSizeTree will have - // the last good sizes (so that children fit in) and sizeTree has the next possible - // snapped sizes. Return them as lower and higher snap possibilities. - return { { lastSizeTree.firstChild->size, lastSizeTree.secondChild->size }, - { sizeTree.firstChild->size, sizeTree.secondChild->size } }; -} - // Method Description: // - Adjusts given dimension (width or height) so that all descendant terminals // align with their character grids as close as possible. Snaps to closes match @@ -1177,244 +48,6 @@ float Pane::CalcSnappedDimension(const bool widthOrHeight, const float dimension return dimension - lower < higher - dimension ? lower : higher; } -// Method Description: -// - Adjusts given dimension (width or height) so that all descendant terminals -// align with their character grids as close as possible. Also makes sure to -// fit in minimal sizes of the panes. -// Arguments: -// - widthOrHeight: if true operates on width, otherwise on height -// - dimension: a dimension (width or height) to be snapped -// Return Value: -// - pair of floats, where first value is the size snapped downward (not greater then -// requested size) and second is the size snapped upward (not lower than requested size). -// If requested size is already snapped, then both returned values equal this value. -Pane::SnapSizeResult Pane::_CalcSnappedDimension(const bool widthOrHeight, const float dimension) const -{ - if (_IsLeaf()) - { - // If we're a leaf pane, align to the grid of controlling terminal - - const auto minSize = _GetMinSize(); - const auto minDimension = widthOrHeight ? minSize.Width : minSize.Height; - - if (dimension <= minDimension) - { - return { minDimension, minDimension }; - } - - float lower = _control.SnapDimensionToGrid(widthOrHeight, dimension); - if (widthOrHeight) - { - lower += WI_IsFlagSet(_borders, Borders::Left) ? PaneBorderSize : 0; - lower += WI_IsFlagSet(_borders, Borders::Right) ? PaneBorderSize : 0; - } - else - { - lower += WI_IsFlagSet(_borders, Borders::Top) ? PaneBorderSize : 0; - lower += WI_IsFlagSet(_borders, Borders::Bottom) ? PaneBorderSize : 0; - } - - if (lower == dimension) - { - // If we happen to be already snapped, then just return this size - // as both lower and higher values. - return { lower, lower }; - } - else - { - const auto cellSize = _control.CharacterDimensions(); - const auto higher = lower + (widthOrHeight ? cellSize.Width : cellSize.Height); - return { lower, higher }; - } - } - else if (_splitState == (widthOrHeight ? SplitState::Horizontal : SplitState::Vertical)) - { - // If we're resizing along separator axis, snap to the closest possibility - // given by our children panes. - - const auto firstSnapped = _firstChild->_CalcSnappedDimension(widthOrHeight, dimension); - const auto secondSnapped = _secondChild->_CalcSnappedDimension(widthOrHeight, dimension); - return { - std::max(firstSnapped.lower, secondSnapped.lower), - std::min(firstSnapped.higher, secondSnapped.higher) - }; - } - else - { - // If we're resizing perpendicularly to separator axis, calculate the sizes - // of child panes that would fit the given size. We use same algorithm that - // is used for real resize routine, but exclude the remaining empty space that - // would appear after the second pane. This will be the 'downward' snap possibility, - // while the 'upward' will be given as a side product of the layout function. - - const auto childSizes = _CalcSnappedChildrenSizes(widthOrHeight, dimension); - return { - childSizes.lower.first + childSizes.lower.second, - childSizes.higher.first + childSizes.higher.second - }; - } -} - -// Method Description: -// - Increases size of given LayoutSizeNode to match next possible 'snap'. In case of leaf -// pane this means the next cell of the terminal. Otherwise it means that one of its children -// advances (recursively). It expects the given node and its descendants to have either -// already snapped or minimum size. -// Arguments: -// - widthOrHeight: if true operates on width, otherwise on height. -// - sizeNode: a layouting node that corresponds to this pane. -// Return Value: -// - -void Pane::_AdvanceSnappedDimension(const bool widthOrHeight, LayoutSizeNode& sizeNode) const -{ - if (_IsLeaf()) - { - // We're a leaf pane, so just add one more row or column (unless isMinimumSize - // is true, see below). - - if (sizeNode.isMinimumSize) - { - // If the node is of its minimum size, this size might not be snapped (it might - // be, say, half a character, or fixed 10 pixels), so snap it upward. It might - // however be already snapped, so add 1 to make sure it really increases - // (not strictly necessary but to avoid surprises). - sizeNode.size = _CalcSnappedDimension(widthOrHeight, sizeNode.size + 1).higher; - } - else - { - const auto cellSize = _control.CharacterDimensions(); - sizeNode.size += widthOrHeight ? cellSize.Width : cellSize.Height; - } - } - else - { - // We're a parent pane, so we have to advance dimension of our children panes. In - // fact, we advance only one child (chosen later) to keep the growth fine-grained. - - // To choose which child pane to advance, we actually need to know their advanced sizes - // in advance (oh), to see which one would 'fit' better. Often, this is already cached - // by the previous invocation of this function in nextFirstChild and nextSecondChild - // fields of given node. If not, we need to calculate them now. - if (sizeNode.nextFirstChild == nullptr) - { - sizeNode.nextFirstChild = std::make_unique(*sizeNode.firstChild); - _firstChild->_AdvanceSnappedDimension(widthOrHeight, *sizeNode.nextFirstChild); - } - if (sizeNode.nextSecondChild == nullptr) - { - sizeNode.nextSecondChild = std::make_unique(*sizeNode.secondChild); - _secondChild->_AdvanceSnappedDimension(widthOrHeight, *sizeNode.nextSecondChild); - } - - const auto nextFirstSize = sizeNode.nextFirstChild->size; - const auto nextSecondSize = sizeNode.nextSecondChild->size; - - // Choose which child pane to advance. - bool advanceFirstOrSecond; - if (_splitState == (widthOrHeight ? SplitState::Horizontal : SplitState::Vertical)) - { - // If we're growing along separator axis, choose the child that - // wants to be smaller than the other, so that the resulting size - // will be the smallest. - advanceFirstOrSecond = nextFirstSize < nextSecondSize; - } - else - { - // If we're growing perpendicularly to separator axis, choose a - // child so that their size ratio is closer to that we're trying - // to maintain (this is, the relative separator position is closer - // to the _desiredSplitPosition field). - - const auto firstSize = sizeNode.firstChild->size; - const auto secondSize = sizeNode.secondChild->size; - - // Because we rely on equality check, these calculations have to be - // immune to floating point errors. In common situation where both panes - // have the same character sizes and _desiredSplitPosition is 0.5 (or - // some simple fraction) both ratios will often be the same, and if so - // we always take the left child. It could be right as well, but it's - // important that it's consistent: that it would always go - // 1 -> 2 -> 1 -> 2 -> 1 -> 2 and not like 1 -> 1 -> 2 -> 2 -> 2 -> 1 - // which would look silly to the user but which occur if there was - // a non-floating-point-safe math. - const auto deviation1 = nextFirstSize - (nextFirstSize + secondSize) * _desiredSplitPosition; - const auto deviation2 = -1 * (firstSize - (firstSize + nextSecondSize) * _desiredSplitPosition); - advanceFirstOrSecond = deviation1 <= deviation2; - } - - // Here we advance one of our children. Because we already know the appropriate - // (advanced) size that given child would need to have, we simply assign that size - // to it. We then advance its 'next*' size (nextFirstChild or nextSecondChild) so - // the invariant holds (as it will likely be used by the next invocation of this - // function). The other child's next* size remains unchanged because its size - // haven't changed either. - if (advanceFirstOrSecond) - { - *sizeNode.firstChild = *sizeNode.nextFirstChild; - _firstChild->_AdvanceSnappedDimension(widthOrHeight, *sizeNode.nextFirstChild); - } - else - { - *sizeNode.secondChild = *sizeNode.nextSecondChild; - _secondChild->_AdvanceSnappedDimension(widthOrHeight, *sizeNode.nextSecondChild); - } - - // Since the size of one of our children has changed we need to update our size as well. - if (_splitState == (widthOrHeight ? SplitState::Horizontal : SplitState::Vertical)) - { - sizeNode.size = std::max(sizeNode.firstChild->size, sizeNode.secondChild->size); - } - else - { - sizeNode.size = sizeNode.firstChild->size + sizeNode.secondChild->size; - } - } - - // Because we have grown, we're certainly no longer of our - // minimal size (if we've ever been). - sizeNode.isMinimumSize = false; -} - -// Method Description: -// - Get the absolute minimum size that this pane can be resized to and still -// have 1x1 character visible, in each of its children. If we're a leaf, we'll -// include the space needed for borders _within_ us. -// Arguments: -// - -// Return Value: -// - The minimum size that this pane can be resized to and still have a visible -// character. -Size Pane::_GetMinSize() const -{ - if (_IsLeaf()) - { - auto controlSize = _control.MinimumSize(); - auto newWidth = controlSize.Width; - auto newHeight = controlSize.Height; - - newWidth += WI_IsFlagSet(_borders, Borders::Left) ? PaneBorderSize : 0; - newWidth += WI_IsFlagSet(_borders, Borders::Right) ? PaneBorderSize : 0; - newHeight += WI_IsFlagSet(_borders, Borders::Top) ? PaneBorderSize : 0; - newHeight += WI_IsFlagSet(_borders, Borders::Bottom) ? PaneBorderSize : 0; - - return { newWidth, newHeight }; - } - else - { - const auto firstSize = _firstChild->_GetMinSize(); - const auto secondSize = _secondChild->_GetMinSize(); - - const auto minWidth = _splitState == SplitState::Vertical ? - firstSize.Width + secondSize.Width : - std::max(firstSize.Width, secondSize.Width); - const auto minHeight = _splitState == SplitState::Horizontal ? - firstSize.Height + secondSize.Height : - std::max(firstSize.Height, secondSize.Height); - - return { minWidth, minHeight }; - } -} - // Method Description: // - Builds a tree of LayoutSizeNode that matches the tree of panes. Each node // has minimum size that the corresponding pane can have. @@ -1425,82 +58,522 @@ Size Pane::_GetMinSize() const Pane::LayoutSizeNode Pane::_CreateMinSizeTree(const bool widthOrHeight) const { const auto size = _GetMinSize(); - LayoutSizeNode node(widthOrHeight ? size.Width : size.Height); - if (!_IsLeaf()) - { - node.firstChild = std::make_unique(_firstChild->_CreateMinSizeTree(widthOrHeight)); - node.secondChild = std::make_unique(_secondChild->_CreateMinSizeTree(widthOrHeight)); - } - - return node; -} - -// Method Description: -// - Adjusts split position so that no child pane is smaller then its -// minimum size -// Arguments: -// - widthOrHeight: if true, operates on width, otherwise on height. -// - requestedValue: split position value to be clamped -// - totalSize: size (width or height) of the parent pane -// Return Value: -// - split position (value in range <0.0, 1.0>) -float Pane::_ClampSplitPosition(const bool widthOrHeight, const float requestedValue, const float totalSize) const -{ - const auto firstMinSize = _firstChild->_GetMinSize(); - const auto secondMinSize = _secondChild->_GetMinSize(); - - const auto firstMinDimension = widthOrHeight ? firstMinSize.Width : firstMinSize.Height; - const auto secondMinDimension = widthOrHeight ? secondMinSize.Width : secondMinSize.Height; - - const auto minSplitPosition = firstMinDimension / totalSize; - const auto maxSplitPosition = 1.0f - (secondMinDimension / totalSize); - - return std::clamp(requestedValue, minSplitPosition, maxSplitPosition); -} - -// Function Description: -// - Attempts to load some XAML resources that the Pane will need. This includes: -// * The Color we'll use for active Panes's borders - SystemAccentColor -// * The Brush we'll use for inactive Panes - TabViewBackground (to match the -// color of the titlebar) -// Arguments: -// - -// Return Value: -// - -void Pane::_SetupResources() -{ - const auto res = Application::Current().Resources(); - const auto accentColorKey = winrt::box_value(L"SystemAccentColor"); - if (res.HasKey(accentColorKey)) - { - const auto colorFromResources = res.Lookup(accentColorKey); - // If SystemAccentColor is _not_ a Color for some reason, use - // Transparent as the color, so we don't do this process again on - // the next pane (by leaving s_focusedBorderBrush nullptr) - auto actualColor = winrt::unbox_value_or(colorFromResources, Colors::Black()); - s_focusedBorderBrush = SolidColorBrush(actualColor); - } - else - { - // DON'T use Transparent here - if it's "Transparent", then it won't - // be able to hittest for clicks, and then clicking on the border - // will eat focus. - s_focusedBorderBrush = SolidColorBrush{ Colors::Black() }; - } - - const auto tabViewBackgroundKey = winrt::box_value(L"TabViewBackground"); - if (res.HasKey(tabViewBackgroundKey)) - { - winrt::Windows::Foundation::IInspectable obj = res.Lookup(tabViewBackgroundKey); - s_unfocusedBorderBrush = obj.try_as(); - } - else - { - // DON'T use Transparent here - if it's "Transparent", then it won't - // be able to hittest for clicks, and then clicking on the border - // will eat focus. - s_unfocusedBorderBrush = SolidColorBrush{ Colors::Black() }; - } -} - -DEFINE_EVENT(Pane, GotFocus, _GotFocusHandlers, winrt::delegate>); + return LayoutSizeNode(widthOrHeight ? size.Width : size.Height); +} + +// +// +// +//// Method Description: +//// - Fire our Closed event to tell our parent that we should be removed. +//// Arguments: +//// - +//// Return Value: +//// - +//void Pane::Close() +//{ +// // Fire our Closed event to tell our parent that we should be removed. +// _ClosedHandlers(nullptr, nullptr); +//} +// +//// Method Description: +//// - If this is the last focused pane, returns itself. Returns nullptr if this +//// is a leaf and it's not focused. If it's a parent, it returns nullptr if no +//// children of this pane were the last pane to be focused, or the Pane that +//// _was_ the last pane to be focused (if there was one). +//// - This Pane's control might not currently be focused, if the tab itself is +//// not currently focused. +//// Return Value: +//// - nullptr if we're a leaf and unfocused, or no children were marked +//// `_lastActive`, else returns this +//std::shared_ptr Pane::GetActivePane() +//{ +// if (_IsLeaf()) +// { +// return _lastActive ? shared_from_this() : nullptr; +// } +// +// auto firstFocused = _firstChild->GetActivePane(); +// if (firstFocused != nullptr) +// { +// return firstFocused; +// } +// return _secondChild->GetActivePane(); +//} +// +//// Method Description: +//// - Recursively remove the "Active" state from this Pane and all it's children. +//// - Updates our visuals to match our new state, including highlighting our borders. +//// Arguments: +//// - +//// Return Value: +//// - +//void Pane::ClearActive() +//{ +// _lastActive = false; +// if (!_IsLeaf()) +// { +// _firstChild->ClearActive(); +// _secondChild->ClearActive(); +// } +// UpdateVisuals(); +//} +// +//// Method Description: +//// - Returns true if this pane is currently focused, or there is a pane which is +//// a child of this pane that is actively focused +//// Arguments: +//// - +//// Return Value: +//// - true if the currently focused pane is either this pane, or one of this +//// pane's descendants +//bool Pane::_HasFocusedChild() const noexcept +//{ +// // We're intentionally making this one giant expression, so the compiler +// // will skip the following lookups if one of the lookups before it returns +// // true +// return (_control && _lastActive) || +// (_firstChild && _firstChild->_HasFocusedChild()) || +// (_secondChild && _secondChild->_HasFocusedChild()); +//} +// +//// Method Description: +//// - Focuses this control if we're a leaf, or attempts to focus the first leaf +//// of our first child, recursively. +//// Arguments: +//// - +//// Return Value: +//// - +//void Pane::_FocusFirstChild() +//{ +// if (_IsLeaf()) +// { +// _control.Focus(FocusState::Programmatic); +// } +// else +// { +// _firstChild->_FocusFirstChild(); +// } +//} +// +//// Method Description: +//// - Attempts to update the settings of this pane or any children of this pane. +//// * If this pane is a leaf, and our profile guid matches the parameter, then +//// we'll apply the new settings to our control. +//// * If we're not a leaf, we'll recurse on our children. +//// Arguments: +//// - settings: The new TerminalSettings to apply to any matching controls +//// - profile: The GUID of the profile these settings should apply to. +//// Return Value: +//// - +//void Pane::UpdateSettings(const TerminalSettings& settings, const GUID& profile) +//{ +// if (!_IsLeaf()) +// { +// _firstChild->UpdateSettings(settings, profile); +// _secondChild->UpdateSettings(settings, profile); +// } +// else +// { +// if (profile == _profile) +// { +// _control.UpdateSettings(settings); +// } +// } +//} +// +// +//// Method Description: +//// - Sets up row/column definitions for this pane. There are three total +//// row/cols. The middle one is for the separator. The first and third are for +//// each of the child panes, and are given a size in pixels, based off the +//// availiable space, and the percent of the space they respectively consume, +//// which is stored in _desiredSplitPosition +//// - Does nothing if our split state is currently set to SplitState::None +//// Arguments: +//// - rootSize: The dimensions in pixels that this pane (and its children should consume.) +//// Return Value: +//// - +//void Pane::_CreateRowColDefinitions(const Size& rootSize) +//{ +// if (_splitState == SplitState::Vertical) +// { +// _root.ColumnDefinitions().Clear(); +// +// // Create two columns in this grid: one for each pane +// const auto paneSizes = _CalcChildrenSizes(rootSize.Width); +// +// auto firstColDef = Controls::ColumnDefinition(); +// firstColDef.Width(GridLengthHelper::FromPixels(paneSizes.first)); +// +// auto secondColDef = Controls::ColumnDefinition(); +// secondColDef.Width(GridLengthHelper::FromPixels(paneSizes.second)); +// +// _root.ColumnDefinitions().Append(firstColDef); +// _root.ColumnDefinitions().Append(secondColDef); +// } +// else if (_splitState == SplitState::Horizontal) +// { +// _root.RowDefinitions().Clear(); +// +// // Create two rows in this grid: one for each pane +// const auto paneSizes = _CalcChildrenSizes(rootSize.Height); +// +// auto firstRowDef = Controls::RowDefinition(); +// firstRowDef.Height(GridLengthHelper::FromPixels(paneSizes.first)); +// +// auto secondRowDef = Controls::RowDefinition(); +// secondRowDef.Height(GridLengthHelper::FromPixels(paneSizes.second)); +// +// _root.RowDefinitions().Append(firstRowDef); +// _root.RowDefinitions().Append(secondRowDef); +// } +//} +// +//// Method Description: +//// - Determines whether the pane can be split +//// Arguments: +//// - splitType: what type of split we want to create. +//// Return Value: +//// - True if the pane can be split. False otherwise. +//bool Pane::CanSplit(SplitState splitType) +//{ +// if (_IsLeaf()) +// { +// return _CanSplit(splitType); +// } +// +// if (_firstChild->_HasFocusedChild()) +// { +// return _firstChild->CanSplit(splitType); +// } +// +// if (_secondChild->_HasFocusedChild()) +// { +// return _secondChild->CanSplit(splitType); +// } +// +// return false; +//} +// +//// Method Description: +//// - Split the focused pane in our tree of panes, and place the given +//// TermControl into the newly created pane. If we're the focused pane, then +//// we'll create two new children, and place them side-by-side in our Grid. +//// Arguments: +//// - splitType: what type of split we want to create. +//// - profile: The profile GUID to associate with the newly created pane. +//// - control: A TermControl to use in the new pane. +//// Return Value: +//// - The two newly created Panes +//std::pair, std::shared_ptr> Pane::Split(SplitState splitType, const GUID& profile, const TermControl& control) +//{ +// if (!_IsLeaf()) +// { +// if (_firstChild->_HasFocusedChild()) +// { +// return _firstChild->Split(splitType, profile, control); +// } +// else if (_secondChild->_HasFocusedChild()) +// { +// return _secondChild->Split(splitType, profile, control); +// } +// +// return { nullptr, nullptr }; +// } +// +// return _Split(splitType, profile, control); +//} +// +//// Method Description: +//// - Does the bulk of the work of creating a new split. Initializes our UI, +//// creates a new Pane to host the control, registers event handlers. +//// Arguments: +//// - splitType: what type of split we should create. +//// - profile: The profile GUID to associate with the newly created pane. +//// - control: A TermControl to use in the new pane. +//// Return Value: +//// - The two newly created Panes +//std::pair, std::shared_ptr> Pane::_Split(SplitState splitType, const GUID& profile, const TermControl& control) +//{ +// // Lock the create/close lock so that another operation won't concurrently +// // modify our tree +// std::unique_lock lock{ _createCloseLock }; +// +// // revoke our handler - the child will take care of the control now. +// _control.ConnectionStateChanged(_connectionStateChangedToken); +// _connectionStateChangedToken.value = 0; +// +// // Remove our old GotFocus handler from the control. We don't what the +// // control telling us that it's now focused, we want it telling its new +// // parent. +// _gotFocusRevoker.revoke(); +// +// _splitState = splitType; +// _desiredSplitPosition = Half; +// +// // Remove any children we currently have. We can't add the existing +// // TermControl to a new grid until we do this. +// _root.Children().Clear(); +// _border.Child(nullptr); +// +// // Create two new Panes +// // Move our control, guid into the first one. +// // Move the new guid, control into the second. +// _firstChild = std::make_shared(_profile.value(), _control); +// _profile = std::nullopt; +// _control = { nullptr }; +// _secondChild = std::make_shared(profile, control); +// +// _CreateSplitContent(); +// +// _root.Children().Append(_firstChild->GetRootElement()); +// _root.Children().Append(_secondChild->GetRootElement()); +// +// _ApplySplitDefinitions(); +// +// // Register event handlers on our children to handle their Close events +// _SetupChildCloseHandlers(); +// +// _lastActive = false; +// +// return { _firstChild, _secondChild }; +//} +// +//// Method Description: +//// - Adjusts given dimension (width or height) so that all descendant terminals +//// align with their character grids as close as possible. Also makes sure to +//// fit in minimal sizes of the panes. +//// Arguments: +//// - widthOrHeight: if true operates on width, otherwise on height +//// - dimension: a dimension (width or height) to be snapped +//// Return Value: +//// - pair of floats, where first value is the size snapped downward (not greater then +//// requested size) and second is the size snapped upward (not lower than requested size). +//// If requested size is already snapped, then both returned values equal this value. +//Pane::SnapSizeResult Pane::_CalcSnappedDimension(const bool widthOrHeight, const float dimension) const +//{ +// if (_IsLeaf()) +// { +// // If we're a leaf pane, align to the grid of controlling terminal +// +// const auto minSize = _GetMinSize(); +// const auto minDimension = widthOrHeight ? minSize.Width : minSize.Height; +// +// if (dimension <= minDimension) +// { +// return { minDimension, minDimension }; +// } +// +// float lower = _control.SnapDimensionToGrid(widthOrHeight, dimension); +// if (widthOrHeight) +// { +// lower += WI_IsFlagSet(_borders, Borders::Left) ? PaneBorderSize : 0; +// lower += WI_IsFlagSet(_borders, Borders::Right) ? PaneBorderSize : 0; +// } +// else +// { +// lower += WI_IsFlagSet(_borders, Borders::Top) ? PaneBorderSize : 0; +// lower += WI_IsFlagSet(_borders, Borders::Bottom) ? PaneBorderSize : 0; +// } +// +// if (lower == dimension) +// { +// // If we happen to be already snapped, then just return this size +// // as both lower and higher values. +// return { lower, lower }; +// } +// else +// { +// const auto cellSize = _control.CharacterDimensions(); +// const auto higher = lower + (widthOrHeight ? cellSize.Width : cellSize.Height); +// return { lower, higher }; +// } +// } +// else if (_splitState == (widthOrHeight ? SplitState::Horizontal : SplitState::Vertical)) +// { +// // If we're resizing along separator axis, snap to the closest possibility +// // given by our children panes. +// +// const auto firstSnapped = _firstChild->_CalcSnappedDimension(widthOrHeight, dimension); +// const auto secondSnapped = _secondChild->_CalcSnappedDimension(widthOrHeight, dimension); +// return { +// std::max(firstSnapped.lower, secondSnapped.lower), +// std::min(firstSnapped.higher, secondSnapped.higher) +// }; +// } +// else +// { +// // If we're resizing perpendicularly to separator axis, calculate the sizes +// // of child panes that would fit the given size. We use same algorithm that +// // is used for real resize routine, but exclude the remaining empty space that +// // would appear after the second pane. This will be the 'downward' snap possibility, +// // while the 'upward' will be given as a side product of the layout function. +// +// const auto childSizes = _CalcSnappedChildrenSizes(widthOrHeight, dimension); +// return { +// childSizes.lower.first + childSizes.lower.second, +// childSizes.higher.first + childSizes.higher.second +// }; +// } +//} +// +//// Method Description: +//// - Increases size of given LayoutSizeNode to match next possible 'snap'. In case of leaf +//// pane this means the next cell of the terminal. Otherwise it means that one of its children +//// advances (recursively). It expects the given node and its descendants to have either +//// already snapped or minimum size. +//// Arguments: +//// - widthOrHeight: if true operates on width, otherwise on height. +//// - sizeNode: a layouting node that corresponds to this pane. +//// Return Value: +//// - +//void Pane::_AdvanceSnappedDimension(const bool widthOrHeight, LayoutSizeNode& sizeNode) const +//{ +// if (_IsLeaf()) +// { +// // We're a leaf pane, so just add one more row or column (unless isMinimumSize +// // is true, see below). +// +// if (sizeNode.isMinimumSize) +// { +// // If the node is of its minimum size, this size might not be snapped (it might +// // be, say, half a character, or fixed 10 pixels), so snap it upward. It might +// // however be already snapped, so add 1 to make sure it really increases +// // (not strictly necessary but to avoid surprises). +// sizeNode.size = _CalcSnappedDimension(widthOrHeight, sizeNode.size + 1).higher; +// } +// else +// { +// const auto cellSize = _control.CharacterDimensions(); +// sizeNode.size += widthOrHeight ? cellSize.Width : cellSize.Height; +// } +// } +// else +// { +// // We're a parent pane, so we have to advance dimension of our children panes. In +// // fact, we advance only one child (chosen later) to keep the growth fine-grained. +// +// // To choose which child pane to advance, we actually need to know their advanced sizes +// // in advance (oh), to see which one would 'fit' better. Often, this is already cached +// // by the previous invocation of this function in nextFirstChild and nextSecondChild +// // fields of given node. If not, we need to calculate them now. +// if (sizeNode.nextFirstChild == nullptr) +// { +// sizeNode.nextFirstChild = std::make_unique(*sizeNode.firstChild); +// _firstChild->_AdvanceSnappedDimension(widthOrHeight, *sizeNode.nextFirstChild); +// } +// if (sizeNode.nextSecondChild == nullptr) +// { +// sizeNode.nextSecondChild = std::make_unique(*sizeNode.secondChild); +// _secondChild->_AdvanceSnappedDimension(widthOrHeight, *sizeNode.nextSecondChild); +// } +// +// const auto nextFirstSize = sizeNode.nextFirstChild->size; +// const auto nextSecondSize = sizeNode.nextSecondChild->size; +// +// // Choose which child pane to advance. +// bool advanceFirstOrSecond; +// if (_splitState == (widthOrHeight ? SplitState::Horizontal : SplitState::Vertical)) +// { +// // If we're growing along separator axis, choose the child that +// // wants to be smaller than the other, so that the resulting size +// // will be the smallest. +// advanceFirstOrSecond = nextFirstSize < nextSecondSize; +// } +// else +// { +// // If we're growing perpendicularly to separator axis, choose a +// // child so that their size ratio is closer to that we're trying +// // to maintain (this is, the relative separator position is closer +// // to the _desiredSplitPosition field). +// +// const auto firstSize = sizeNode.firstChild->size; +// const auto secondSize = sizeNode.secondChild->size; +// +// // Because we rely on equality check, these calculations have to be +// // immune to floating point errors. In common situation where both panes +// // have the same character sizes and _desiredSplitPosition is 0.5 (or +// // some simple fraction) both ratios will often be the same, and if so +// // we always take the left child. It could be right as well, but it's +// // important that it's consistent: that it would always go +// // 1 -> 2 -> 1 -> 2 -> 1 -> 2 and not like 1 -> 1 -> 2 -> 2 -> 2 -> 1 +// // which would look silly to the user but which occur if there was +// // a non-floating-point-safe math. +// const auto deviation1 = nextFirstSize - (nextFirstSize + secondSize) * _desiredSplitPosition; +// const auto deviation2 = -1 * (firstSize - (firstSize + nextSecondSize) * _desiredSplitPosition); +// advanceFirstOrSecond = deviation1 <= deviation2; +// } +// +// // Here we advance one of our children. Because we already know the appropriate +// // (advanced) size that given child would need to have, we simply assign that size +// // to it. We then advance its 'next*' size (nextFirstChild or nextSecondChild) so +// // the invariant holds (as it will likely be used by the next invocation of this +// // function). The other child's next* size remains unchanged because its size +// // haven't changed either. +// if (advanceFirstOrSecond) +// { +// *sizeNode.firstChild = *sizeNode.nextFirstChild; +// _firstChild->_AdvanceSnappedDimension(widthOrHeight, *sizeNode.nextFirstChild); +// } +// else +// { +// *sizeNode.secondChild = *sizeNode.nextSecondChild; +// _secondChild->_AdvanceSnappedDimension(widthOrHeight, *sizeNode.nextSecondChild); +// } +// +// // Since the size of one of our children has changed we need to update our size as well. +// if (_splitState == (widthOrHeight ? SplitState::Horizontal : SplitState::Vertical)) +// { +// sizeNode.size = std::max(sizeNode.firstChild->size, sizeNode.secondChild->size); +// } +// else +// { +// sizeNode.size = sizeNode.firstChild->size + sizeNode.secondChild->size; +// } +// } +// +// // Because we have grown, we're certainly no longer of our +// // minimal size (if we've ever been). +// sizeNode.isMinimumSize = false; +//} +// +//// Method Description: +//// - Get the absolute minimum size that this pane can be resized to and still +//// have 1x1 character visible, in each of its children. If we're a leaf, we'll +//// include the space needed for borders _within_ us. +//// Arguments: +//// - +//// Return Value: +//// - The minimum size that this pane can be resized to and still have a visible +//// character. +//Size Pane::_GetMinSize() const +//{ +// if (_IsLeaf()) +// { +// auto controlSize = _control.MinimumSize(); +// auto newWidth = controlSize.Width; +// auto newHeight = controlSize.Height; +// +// newWidth += WI_IsFlagSet(_borders, Borders::Left) ? PaneBorderSize : 0; +// newWidth += WI_IsFlagSet(_borders, Borders::Right) ? PaneBorderSize : 0; +// newHeight += WI_IsFlagSet(_borders, Borders::Top) ? PaneBorderSize : 0; +// newHeight += WI_IsFlagSet(_borders, Borders::Bottom) ? PaneBorderSize : 0; +// +// return { newWidth, newHeight }; +// } +// else +// { +// const auto firstSize = _firstChild->_GetMinSize(); +// const auto secondSize = _secondChild->_GetMinSize(); +// +// const auto minWidth = _splitState == SplitState::Vertical ? +// firstSize.Width + secondSize.Width : +// std::max(firstSize.Width, secondSize.Width); +// const auto minHeight = _splitState == SplitState::Horizontal ? +// firstSize.Height + secondSize.Height : +// std::max(firstSize.Height, secondSize.Height); +// +// return { minWidth, minHeight }; +// } +//} diff --git a/src/cascadia/TerminalApp/Pane.h b/src/cascadia/TerminalApp/Pane.h index c503c1f2700..5ceedf8b47a 100644 --- a/src/cascadia/TerminalApp/Pane.h +++ b/src/cascadia/TerminalApp/Pane.h @@ -31,15 +31,15 @@ class Pane public: virtual ~Pane() = default; - winrt::Windows::UI::Xaml::Controls::Grid GetRootElement(); + winrt::Windows::UI::Xaml::Controls::Grid GetRootElement() const; virtual std::shared_ptr FindActivePane() = 0; virtual void PropagateToLeaves(std::function action) = 0; virtual void PropagateToLeavesOnEdge(const winrt::TerminalApp::Direction& edge, - std::function action) = 0; + std::function action) = 0; virtual void UpdateSettings(const winrt::Microsoft::Terminal::Settings::TerminalSettings& settings, - const GUID& profile) = 0; + const GUID& profile) = 0; virtual void ResizeContent(const winrt::Windows::Foundation::Size& newSize) = 0; virtual void Relayout() = 0; float CalcSnappedDimension(const bool widthOrHeight, const float dimension) const; @@ -60,7 +60,7 @@ class Pane virtual SnapSizeResult _CalcSnappedDimension(const bool widthOrHeight, const float dimension) const = 0; virtual void _AdvanceSnappedDimension(const bool widthOrHeight, LayoutSizeNode& sizeNode) const = 0; - // Make derived classes friends, so they can access protected members of Pane. C++ for some + // Make derived classes friends, so they can access protected members of Pane. C++ for some // reason doesn't allow that by default - e.g. LeafPane can access LeafPane::_ProtectedMember, // but not Pane::_ProtectedMember. friend class LeafPane; diff --git a/src/cascadia/TerminalApp/lib/LeafPane.cpp b/src/cascadia/TerminalApp/lib/LeafPane.cpp index ae09051f31c..6cf591574c8 100644 --- a/src/cascadia/TerminalApp/lib/LeafPane.cpp +++ b/src/cascadia/TerminalApp/lib/LeafPane.cpp @@ -135,12 +135,12 @@ void LeafPane::UpdateSettings(const TerminalSettings& settings, const GUID& prof // - // Return Value: // - nullptr if this Pane is a parent, otherwise the TermControl of this Pane. -TermControl LeafPane::GetTerminalControl() +TermControl LeafPane::GetTerminalControl() const noexcept { return _control; } -GUID LeafPane::GetProfile() +GUID LeafPane::GetProfile() const noexcept { return _profile; } @@ -202,7 +202,7 @@ LeafPane::SplitResult LeafPane::Split(winrt::TerminalApp::SplitState splitType, Size actualSize{ gsl::narrow_cast(_root.ActualWidth()), gsl::narrow_cast(_root.ActualHeight()) }; - const auto newParent = std::make_shared(shared_from_this(), secondLeaf, splitType, actualSize); + const auto newParent = std::make_shared(shared_from_this(), secondLeaf, splitType, Half, actualSize); _SplittedHandlers(newParent); newParent->InitializeChildren(); diff --git a/src/cascadia/TerminalApp/lib/LeafPane.h b/src/cascadia/TerminalApp/lib/LeafPane.h index 8bb5ee35f80..183950bf8f7 100644 --- a/src/cascadia/TerminalApp/lib/LeafPane.h +++ b/src/cascadia/TerminalApp/lib/LeafPane.h @@ -28,19 +28,19 @@ class LeafPane : public Pane, public std::enable_shared_from_this std::shared_ptr FindActivePane() override; void PropagateToLeaves(std::function action) override; void PropagateToLeavesOnEdge(const winrt::TerminalApp::Direction& edge, - std::function action) override; + std::function action) override; void UpdateSettings(const winrt::Microsoft::Terminal::Settings::TerminalSettings& settings, const GUID& profile); void ResizeContent(const winrt::Windows::Foundation::Size& /* newSize */) override{}; void Relayout() override{}; - winrt::Microsoft::Terminal::TerminalControl::TermControl GetTerminalControl(); - GUID GetProfile(); + winrt::Microsoft::Terminal::TerminalControl::TermControl GetTerminalControl() const noexcept; + GUID GetProfile() const noexcept; bool CanSplit(winrt::TerminalApp::SplitState splitType); SplitResult Split(winrt::TerminalApp::SplitState splitType, - const GUID& profile, - const winrt::Microsoft::Terminal::TerminalControl::TermControl& control); + const GUID& profile, + const winrt::Microsoft::Terminal::TerminalControl::TermControl& control); void SetActive(); void ClearActive(); @@ -59,6 +59,7 @@ class LeafPane : public Pane, public std::enable_shared_from_this WINRT_CALLBACK(Closed, winrt::Windows::Foundation::EventHandler); DECLARE_EVENT(Splitted, _SplittedHandlers, winrt::delegate>); DECLARE_EVENT(GotFocus, _GotFocusHandlers, winrt::delegate>); + private: static winrt::Windows::UI::Xaml::Media::SolidColorBrush s_focusedBorderBrush; static winrt::Windows::UI::Xaml::Media::SolidColorBrush s_unfocusedBorderBrush; @@ -85,6 +86,6 @@ class LeafPane : public Pane, public std::enable_shared_from_this SnapSizeResult _CalcSnappedDimension(const bool widthOrHeight, const float dimension) const override; void _AdvanceSnappedDimension(const bool widthOrHeight, LayoutSizeNode& sizeNode) const override; winrt::Windows::Foundation::Size _GetMinSize() const override; - + static void _SetupResources(); }; diff --git a/src/cascadia/TerminalApp/lib/ParentPane.cpp b/src/cascadia/TerminalApp/lib/ParentPane.cpp index a400a9a6a4f..841aff61036 100644 --- a/src/cascadia/TerminalApp/lib/ParentPane.cpp +++ b/src/cascadia/TerminalApp/lib/ParentPane.cpp @@ -14,13 +14,11 @@ using namespace winrt::Microsoft::Terminal::TerminalConnection; using namespace winrt::TerminalApp; using namespace TerminalApp; -static const float Half = 0.50f; - -ParentPane::ParentPane(std::shared_ptr firstChild, std::shared_ptr secondChild, SplitState splitState, Size currentSize) : +ParentPane::ParentPane(std::shared_ptr firstChild, std::shared_ptr secondChild, SplitState splitState, float splitPosition, Size currentSize) : _firstChild(std::static_pointer_cast(firstChild)), _secondChild(std::static_pointer_cast(secondChild)), _splitState(splitState), - _desiredSplitPosition(Half) + _desiredSplitPosition(splitPosition) { _CreateRowColDefinitions(currentSize); @@ -145,7 +143,7 @@ void ParentPane::_RemoveAllChildEventHandlers(bool firstChild) } } -std::function ParentPane::_GetGridSetColOrRowFunc() const +std::function ParentPane::_GetGridSetColOrRowFunc() const noexcept { if (_splitState == SplitState::Vertical) { @@ -184,7 +182,8 @@ void ParentPane::PropagateToLeavesOnEdge(const winrt::TerminalApp::Direction& ed { const auto adjacentChild = (_splitState == SplitState::Vertical && edge == Direction::Left || _splitState == SplitState::Horizontal && edge == Direction::Up) ? - _firstChild : _secondChild; + _firstChild : + _secondChild; adjacentChild->PropagateToLeavesOnEdge(edge, action); } else @@ -416,10 +415,12 @@ bool ParentPane::_NavigateFocus(const Direction& direction) } const bool focusSecond = (direction == Direction::Right) || (direction == Direction::Down); - const auto newlyFocusedChild = focusSecond ? _secondChild : _firstChild; + const auto notFocusedChild = focusSecond ? _firstChild : _secondChild; - notFocusedChild->ClearActive(); + notFocusedChild->PropagateToLeaves([](LeafPane& pane) { + pane.ClearActive(); + }); // If the child we want to move focus to is _already_ focused, return false, // to try and let our parent figure it out. @@ -453,8 +454,8 @@ void ParentPane::_CloseChild(const bool closeFirst) _root.Children().Clear(); const auto closedChildDir = (_splitState == SplitState::Vertical) ? - (closeFirst ? Direction::Left : Direction::Right) : - (closeFirst ? Direction::Up : Direction::Down); + (closeFirst ? Direction::Left : Direction::Right) : + (closeFirst ? Direction::Up : Direction::Down); remainingChild->PropagateToLeavesOnEdge(closedChildDir, [=](LeafPane& paneOnEdge) { paneOnEdge.UpdateBorderWithClosedNeightbour(closedChild, closedChildDir); diff --git a/src/cascadia/TerminalApp/lib/ParentPane.h b/src/cascadia/TerminalApp/lib/ParentPane.h index 1db938e1b26..800e7157c46 100644 --- a/src/cascadia/TerminalApp/lib/ParentPane.h +++ b/src/cascadia/TerminalApp/lib/ParentPane.h @@ -5,13 +5,10 @@ #include "../../cascadia/inc/cppwinrt_utils.h" #include - class ParentPane : public Pane, public std::enable_shared_from_this { public: - ParentPane(std::shared_ptr firstChild, std::shared_ptr secondChild, - winrt::TerminalApp::SplitState splitState, - winrt::Windows::Foundation::Size currentSize); + ParentPane(std::shared_ptr firstChild, std::shared_ptr secondChild, winrt::TerminalApp::SplitState splitState, float splitPosition, winrt::Windows::Foundation::Size currentSize); ~ParentPane() override; void InitializeChildren(); @@ -19,10 +16,10 @@ class ParentPane : public Pane, public std::enable_shared_from_this std::shared_ptr FindActivePane() override; void PropagateToLeaves(std::function action) override; void PropagateToLeavesOnEdge(const winrt::TerminalApp::Direction& edge, - std::function action) override; + std::function action) override; void UpdateSettings(const winrt::Microsoft::Terminal::Settings::TerminalSettings& settings, - const GUID& profile); + const GUID& profile); void ResizeContent(const winrt::Windows::Foundation::Size& newSize) override; void Relayout() override; @@ -45,7 +42,7 @@ class ParentPane : public Pane, public std::enable_shared_from_this void _SetupChildEventHandlers(bool firstChild); void _RemoveAllChildEventHandlers(bool firstChild); void _CreateRowColDefinitions(const winrt::Windows::Foundation::Size& rootSize); - std::function _GetGridSetColOrRowFunc() const; + std::function _GetGridSetColOrRowFunc() const noexcept; std::shared_ptr _FindFirstLeaf() override; bool _ResizeChild(const winrt::TerminalApp::Direction& direction); From 9a26040992089f28161322a9a7425408783a392c Mon Sep 17 00:00:00 2001 From: MCpiroman Date: Wed, 25 Dec 2019 20:15:56 +0100 Subject: [PATCH 04/17] Comments, comments and small but important changes and comments --- src/cascadia/TerminalApp/Pane.cpp | 517 -------------------- src/cascadia/TerminalApp/Pane.h | 82 ++++ src/cascadia/TerminalApp/Tab.cpp | 130 +++-- src/cascadia/TerminalApp/Tab.h | 2 +- src/cascadia/TerminalApp/lib/LeafPane.cpp | 96 ++-- src/cascadia/TerminalApp/lib/LeafPane.h | 9 +- src/cascadia/TerminalApp/lib/ParentPane.cpp | 112 +++-- 7 files changed, 314 insertions(+), 634 deletions(-) diff --git a/src/cascadia/TerminalApp/Pane.cpp b/src/cascadia/TerminalApp/Pane.cpp index 0b8cb0afcc6..6e8a1a29a15 100644 --- a/src/cascadia/TerminalApp/Pane.cpp +++ b/src/cascadia/TerminalApp/Pane.cpp @@ -60,520 +60,3 @@ Pane::LayoutSizeNode Pane::_CreateMinSizeTree(const bool widthOrHeight) const const auto size = _GetMinSize(); return LayoutSizeNode(widthOrHeight ? size.Width : size.Height); } - -// -// -// -//// Method Description: -//// - Fire our Closed event to tell our parent that we should be removed. -//// Arguments: -//// - -//// Return Value: -//// - -//void Pane::Close() -//{ -// // Fire our Closed event to tell our parent that we should be removed. -// _ClosedHandlers(nullptr, nullptr); -//} -// -//// Method Description: -//// - If this is the last focused pane, returns itself. Returns nullptr if this -//// is a leaf and it's not focused. If it's a parent, it returns nullptr if no -//// children of this pane were the last pane to be focused, or the Pane that -//// _was_ the last pane to be focused (if there was one). -//// - This Pane's control might not currently be focused, if the tab itself is -//// not currently focused. -//// Return Value: -//// - nullptr if we're a leaf and unfocused, or no children were marked -//// `_lastActive`, else returns this -//std::shared_ptr Pane::GetActivePane() -//{ -// if (_IsLeaf()) -// { -// return _lastActive ? shared_from_this() : nullptr; -// } -// -// auto firstFocused = _firstChild->GetActivePane(); -// if (firstFocused != nullptr) -// { -// return firstFocused; -// } -// return _secondChild->GetActivePane(); -//} -// -//// Method Description: -//// - Recursively remove the "Active" state from this Pane and all it's children. -//// - Updates our visuals to match our new state, including highlighting our borders. -//// Arguments: -//// - -//// Return Value: -//// - -//void Pane::ClearActive() -//{ -// _lastActive = false; -// if (!_IsLeaf()) -// { -// _firstChild->ClearActive(); -// _secondChild->ClearActive(); -// } -// UpdateVisuals(); -//} -// -//// Method Description: -//// - Returns true if this pane is currently focused, or there is a pane which is -//// a child of this pane that is actively focused -//// Arguments: -//// - -//// Return Value: -//// - true if the currently focused pane is either this pane, or one of this -//// pane's descendants -//bool Pane::_HasFocusedChild() const noexcept -//{ -// // We're intentionally making this one giant expression, so the compiler -// // will skip the following lookups if one of the lookups before it returns -// // true -// return (_control && _lastActive) || -// (_firstChild && _firstChild->_HasFocusedChild()) || -// (_secondChild && _secondChild->_HasFocusedChild()); -//} -// -//// Method Description: -//// - Focuses this control if we're a leaf, or attempts to focus the first leaf -//// of our first child, recursively. -//// Arguments: -//// - -//// Return Value: -//// - -//void Pane::_FocusFirstChild() -//{ -// if (_IsLeaf()) -// { -// _control.Focus(FocusState::Programmatic); -// } -// else -// { -// _firstChild->_FocusFirstChild(); -// } -//} -// -//// Method Description: -//// - Attempts to update the settings of this pane or any children of this pane. -//// * If this pane is a leaf, and our profile guid matches the parameter, then -//// we'll apply the new settings to our control. -//// * If we're not a leaf, we'll recurse on our children. -//// Arguments: -//// - settings: The new TerminalSettings to apply to any matching controls -//// - profile: The GUID of the profile these settings should apply to. -//// Return Value: -//// - -//void Pane::UpdateSettings(const TerminalSettings& settings, const GUID& profile) -//{ -// if (!_IsLeaf()) -// { -// _firstChild->UpdateSettings(settings, profile); -// _secondChild->UpdateSettings(settings, profile); -// } -// else -// { -// if (profile == _profile) -// { -// _control.UpdateSettings(settings); -// } -// } -//} -// -// -//// Method Description: -//// - Sets up row/column definitions for this pane. There are three total -//// row/cols. The middle one is for the separator. The first and third are for -//// each of the child panes, and are given a size in pixels, based off the -//// availiable space, and the percent of the space they respectively consume, -//// which is stored in _desiredSplitPosition -//// - Does nothing if our split state is currently set to SplitState::None -//// Arguments: -//// - rootSize: The dimensions in pixels that this pane (and its children should consume.) -//// Return Value: -//// - -//void Pane::_CreateRowColDefinitions(const Size& rootSize) -//{ -// if (_splitState == SplitState::Vertical) -// { -// _root.ColumnDefinitions().Clear(); -// -// // Create two columns in this grid: one for each pane -// const auto paneSizes = _CalcChildrenSizes(rootSize.Width); -// -// auto firstColDef = Controls::ColumnDefinition(); -// firstColDef.Width(GridLengthHelper::FromPixels(paneSizes.first)); -// -// auto secondColDef = Controls::ColumnDefinition(); -// secondColDef.Width(GridLengthHelper::FromPixels(paneSizes.second)); -// -// _root.ColumnDefinitions().Append(firstColDef); -// _root.ColumnDefinitions().Append(secondColDef); -// } -// else if (_splitState == SplitState::Horizontal) -// { -// _root.RowDefinitions().Clear(); -// -// // Create two rows in this grid: one for each pane -// const auto paneSizes = _CalcChildrenSizes(rootSize.Height); -// -// auto firstRowDef = Controls::RowDefinition(); -// firstRowDef.Height(GridLengthHelper::FromPixels(paneSizes.first)); -// -// auto secondRowDef = Controls::RowDefinition(); -// secondRowDef.Height(GridLengthHelper::FromPixels(paneSizes.second)); -// -// _root.RowDefinitions().Append(firstRowDef); -// _root.RowDefinitions().Append(secondRowDef); -// } -//} -// -//// Method Description: -//// - Determines whether the pane can be split -//// Arguments: -//// - splitType: what type of split we want to create. -//// Return Value: -//// - True if the pane can be split. False otherwise. -//bool Pane::CanSplit(SplitState splitType) -//{ -// if (_IsLeaf()) -// { -// return _CanSplit(splitType); -// } -// -// if (_firstChild->_HasFocusedChild()) -// { -// return _firstChild->CanSplit(splitType); -// } -// -// if (_secondChild->_HasFocusedChild()) -// { -// return _secondChild->CanSplit(splitType); -// } -// -// return false; -//} -// -//// Method Description: -//// - Split the focused pane in our tree of panes, and place the given -//// TermControl into the newly created pane. If we're the focused pane, then -//// we'll create two new children, and place them side-by-side in our Grid. -//// Arguments: -//// - splitType: what type of split we want to create. -//// - profile: The profile GUID to associate with the newly created pane. -//// - control: A TermControl to use in the new pane. -//// Return Value: -//// - The two newly created Panes -//std::pair, std::shared_ptr> Pane::Split(SplitState splitType, const GUID& profile, const TermControl& control) -//{ -// if (!_IsLeaf()) -// { -// if (_firstChild->_HasFocusedChild()) -// { -// return _firstChild->Split(splitType, profile, control); -// } -// else if (_secondChild->_HasFocusedChild()) -// { -// return _secondChild->Split(splitType, profile, control); -// } -// -// return { nullptr, nullptr }; -// } -// -// return _Split(splitType, profile, control); -//} -// -//// Method Description: -//// - Does the bulk of the work of creating a new split. Initializes our UI, -//// creates a new Pane to host the control, registers event handlers. -//// Arguments: -//// - splitType: what type of split we should create. -//// - profile: The profile GUID to associate with the newly created pane. -//// - control: A TermControl to use in the new pane. -//// Return Value: -//// - The two newly created Panes -//std::pair, std::shared_ptr> Pane::_Split(SplitState splitType, const GUID& profile, const TermControl& control) -//{ -// // Lock the create/close lock so that another operation won't concurrently -// // modify our tree -// std::unique_lock lock{ _createCloseLock }; -// -// // revoke our handler - the child will take care of the control now. -// _control.ConnectionStateChanged(_connectionStateChangedToken); -// _connectionStateChangedToken.value = 0; -// -// // Remove our old GotFocus handler from the control. We don't what the -// // control telling us that it's now focused, we want it telling its new -// // parent. -// _gotFocusRevoker.revoke(); -// -// _splitState = splitType; -// _desiredSplitPosition = Half; -// -// // Remove any children we currently have. We can't add the existing -// // TermControl to a new grid until we do this. -// _root.Children().Clear(); -// _border.Child(nullptr); -// -// // Create two new Panes -// // Move our control, guid into the first one. -// // Move the new guid, control into the second. -// _firstChild = std::make_shared(_profile.value(), _control); -// _profile = std::nullopt; -// _control = { nullptr }; -// _secondChild = std::make_shared(profile, control); -// -// _CreateSplitContent(); -// -// _root.Children().Append(_firstChild->GetRootElement()); -// _root.Children().Append(_secondChild->GetRootElement()); -// -// _ApplySplitDefinitions(); -// -// // Register event handlers on our children to handle their Close events -// _SetupChildCloseHandlers(); -// -// _lastActive = false; -// -// return { _firstChild, _secondChild }; -//} -// -//// Method Description: -//// - Adjusts given dimension (width or height) so that all descendant terminals -//// align with their character grids as close as possible. Also makes sure to -//// fit in minimal sizes of the panes. -//// Arguments: -//// - widthOrHeight: if true operates on width, otherwise on height -//// - dimension: a dimension (width or height) to be snapped -//// Return Value: -//// - pair of floats, where first value is the size snapped downward (not greater then -//// requested size) and second is the size snapped upward (not lower than requested size). -//// If requested size is already snapped, then both returned values equal this value. -//Pane::SnapSizeResult Pane::_CalcSnappedDimension(const bool widthOrHeight, const float dimension) const -//{ -// if (_IsLeaf()) -// { -// // If we're a leaf pane, align to the grid of controlling terminal -// -// const auto minSize = _GetMinSize(); -// const auto minDimension = widthOrHeight ? minSize.Width : minSize.Height; -// -// if (dimension <= minDimension) -// { -// return { minDimension, minDimension }; -// } -// -// float lower = _control.SnapDimensionToGrid(widthOrHeight, dimension); -// if (widthOrHeight) -// { -// lower += WI_IsFlagSet(_borders, Borders::Left) ? PaneBorderSize : 0; -// lower += WI_IsFlagSet(_borders, Borders::Right) ? PaneBorderSize : 0; -// } -// else -// { -// lower += WI_IsFlagSet(_borders, Borders::Top) ? PaneBorderSize : 0; -// lower += WI_IsFlagSet(_borders, Borders::Bottom) ? PaneBorderSize : 0; -// } -// -// if (lower == dimension) -// { -// // If we happen to be already snapped, then just return this size -// // as both lower and higher values. -// return { lower, lower }; -// } -// else -// { -// const auto cellSize = _control.CharacterDimensions(); -// const auto higher = lower + (widthOrHeight ? cellSize.Width : cellSize.Height); -// return { lower, higher }; -// } -// } -// else if (_splitState == (widthOrHeight ? SplitState::Horizontal : SplitState::Vertical)) -// { -// // If we're resizing along separator axis, snap to the closest possibility -// // given by our children panes. -// -// const auto firstSnapped = _firstChild->_CalcSnappedDimension(widthOrHeight, dimension); -// const auto secondSnapped = _secondChild->_CalcSnappedDimension(widthOrHeight, dimension); -// return { -// std::max(firstSnapped.lower, secondSnapped.lower), -// std::min(firstSnapped.higher, secondSnapped.higher) -// }; -// } -// else -// { -// // If we're resizing perpendicularly to separator axis, calculate the sizes -// // of child panes that would fit the given size. We use same algorithm that -// // is used for real resize routine, but exclude the remaining empty space that -// // would appear after the second pane. This will be the 'downward' snap possibility, -// // while the 'upward' will be given as a side product of the layout function. -// -// const auto childSizes = _CalcSnappedChildrenSizes(widthOrHeight, dimension); -// return { -// childSizes.lower.first + childSizes.lower.second, -// childSizes.higher.first + childSizes.higher.second -// }; -// } -//} -// -//// Method Description: -//// - Increases size of given LayoutSizeNode to match next possible 'snap'. In case of leaf -//// pane this means the next cell of the terminal. Otherwise it means that one of its children -//// advances (recursively). It expects the given node and its descendants to have either -//// already snapped or minimum size. -//// Arguments: -//// - widthOrHeight: if true operates on width, otherwise on height. -//// - sizeNode: a layouting node that corresponds to this pane. -//// Return Value: -//// - -//void Pane::_AdvanceSnappedDimension(const bool widthOrHeight, LayoutSizeNode& sizeNode) const -//{ -// if (_IsLeaf()) -// { -// // We're a leaf pane, so just add one more row or column (unless isMinimumSize -// // is true, see below). -// -// if (sizeNode.isMinimumSize) -// { -// // If the node is of its minimum size, this size might not be snapped (it might -// // be, say, half a character, or fixed 10 pixels), so snap it upward. It might -// // however be already snapped, so add 1 to make sure it really increases -// // (not strictly necessary but to avoid surprises). -// sizeNode.size = _CalcSnappedDimension(widthOrHeight, sizeNode.size + 1).higher; -// } -// else -// { -// const auto cellSize = _control.CharacterDimensions(); -// sizeNode.size += widthOrHeight ? cellSize.Width : cellSize.Height; -// } -// } -// else -// { -// // We're a parent pane, so we have to advance dimension of our children panes. In -// // fact, we advance only one child (chosen later) to keep the growth fine-grained. -// -// // To choose which child pane to advance, we actually need to know their advanced sizes -// // in advance (oh), to see which one would 'fit' better. Often, this is already cached -// // by the previous invocation of this function in nextFirstChild and nextSecondChild -// // fields of given node. If not, we need to calculate them now. -// if (sizeNode.nextFirstChild == nullptr) -// { -// sizeNode.nextFirstChild = std::make_unique(*sizeNode.firstChild); -// _firstChild->_AdvanceSnappedDimension(widthOrHeight, *sizeNode.nextFirstChild); -// } -// if (sizeNode.nextSecondChild == nullptr) -// { -// sizeNode.nextSecondChild = std::make_unique(*sizeNode.secondChild); -// _secondChild->_AdvanceSnappedDimension(widthOrHeight, *sizeNode.nextSecondChild); -// } -// -// const auto nextFirstSize = sizeNode.nextFirstChild->size; -// const auto nextSecondSize = sizeNode.nextSecondChild->size; -// -// // Choose which child pane to advance. -// bool advanceFirstOrSecond; -// if (_splitState == (widthOrHeight ? SplitState::Horizontal : SplitState::Vertical)) -// { -// // If we're growing along separator axis, choose the child that -// // wants to be smaller than the other, so that the resulting size -// // will be the smallest. -// advanceFirstOrSecond = nextFirstSize < nextSecondSize; -// } -// else -// { -// // If we're growing perpendicularly to separator axis, choose a -// // child so that their size ratio is closer to that we're trying -// // to maintain (this is, the relative separator position is closer -// // to the _desiredSplitPosition field). -// -// const auto firstSize = sizeNode.firstChild->size; -// const auto secondSize = sizeNode.secondChild->size; -// -// // Because we rely on equality check, these calculations have to be -// // immune to floating point errors. In common situation where both panes -// // have the same character sizes and _desiredSplitPosition is 0.5 (or -// // some simple fraction) both ratios will often be the same, and if so -// // we always take the left child. It could be right as well, but it's -// // important that it's consistent: that it would always go -// // 1 -> 2 -> 1 -> 2 -> 1 -> 2 and not like 1 -> 1 -> 2 -> 2 -> 2 -> 1 -// // which would look silly to the user but which occur if there was -// // a non-floating-point-safe math. -// const auto deviation1 = nextFirstSize - (nextFirstSize + secondSize) * _desiredSplitPosition; -// const auto deviation2 = -1 * (firstSize - (firstSize + nextSecondSize) * _desiredSplitPosition); -// advanceFirstOrSecond = deviation1 <= deviation2; -// } -// -// // Here we advance one of our children. Because we already know the appropriate -// // (advanced) size that given child would need to have, we simply assign that size -// // to it. We then advance its 'next*' size (nextFirstChild or nextSecondChild) so -// // the invariant holds (as it will likely be used by the next invocation of this -// // function). The other child's next* size remains unchanged because its size -// // haven't changed either. -// if (advanceFirstOrSecond) -// { -// *sizeNode.firstChild = *sizeNode.nextFirstChild; -// _firstChild->_AdvanceSnappedDimension(widthOrHeight, *sizeNode.nextFirstChild); -// } -// else -// { -// *sizeNode.secondChild = *sizeNode.nextSecondChild; -// _secondChild->_AdvanceSnappedDimension(widthOrHeight, *sizeNode.nextSecondChild); -// } -// -// // Since the size of one of our children has changed we need to update our size as well. -// if (_splitState == (widthOrHeight ? SplitState::Horizontal : SplitState::Vertical)) -// { -// sizeNode.size = std::max(sizeNode.firstChild->size, sizeNode.secondChild->size); -// } -// else -// { -// sizeNode.size = sizeNode.firstChild->size + sizeNode.secondChild->size; -// } -// } -// -// // Because we have grown, we're certainly no longer of our -// // minimal size (if we've ever been). -// sizeNode.isMinimumSize = false; -//} -// -//// Method Description: -//// - Get the absolute minimum size that this pane can be resized to and still -//// have 1x1 character visible, in each of its children. If we're a leaf, we'll -//// include the space needed for borders _within_ us. -//// Arguments: -//// - -//// Return Value: -//// - The minimum size that this pane can be resized to and still have a visible -//// character. -//Size Pane::_GetMinSize() const -//{ -// if (_IsLeaf()) -// { -// auto controlSize = _control.MinimumSize(); -// auto newWidth = controlSize.Width; -// auto newHeight = controlSize.Height; -// -// newWidth += WI_IsFlagSet(_borders, Borders::Left) ? PaneBorderSize : 0; -// newWidth += WI_IsFlagSet(_borders, Borders::Right) ? PaneBorderSize : 0; -// newHeight += WI_IsFlagSet(_borders, Borders::Top) ? PaneBorderSize : 0; -// newHeight += WI_IsFlagSet(_borders, Borders::Bottom) ? PaneBorderSize : 0; -// -// return { newWidth, newHeight }; -// } -// else -// { -// const auto firstSize = _firstChild->_GetMinSize(); -// const auto secondSize = _secondChild->_GetMinSize(); -// -// const auto minWidth = _splitState == SplitState::Vertical ? -// firstSize.Width + secondSize.Width : -// std::max(firstSize.Width, secondSize.Width); -// const auto minHeight = _splitState == SplitState::Horizontal ? -// firstSize.Height + secondSize.Height : -// std::max(firstSize.Height, secondSize.Height); -// -// return { minWidth, minHeight }; -// } -//} diff --git a/src/cascadia/TerminalApp/Pane.h b/src/cascadia/TerminalApp/Pane.h index 5ceedf8b47a..70207810df6 100644 --- a/src/cascadia/TerminalApp/Pane.h +++ b/src/cascadia/TerminalApp/Pane.h @@ -33,13 +33,51 @@ class Pane winrt::Windows::UI::Xaml::Controls::Grid GetRootElement() const; + // Method Description: + // - Searches for last focused pane in the pane tree and returns it. Returns + // nullptr, if the pane is not last focused and doesn't have any child + // that is. + // - This Pane's control might not currently be focused, if the tab itself is + // not currently focused. + // Return Value: + // - nullptr if we're a leaf and unfocused, or no children were marked + // `_lastActive`, else returns this virtual std::shared_ptr FindActivePane() = 0; + + // Method Description: + // - Invokes the given action on each descendant leaf pane, which may be just this pane if + // it is a leaf. + // Arguments: + // - action: function to invoke on leaves. Parameters: + // * pane: the leaf pane on which the action is invoked + // Return Value: + // - virtual void PropagateToLeaves(std::function action) = 0; + + // Method Description: + // - Invokes the given action on each descendant leaf pane that touches the given edge of this + // pane. In case this is a leaf pane, it is assumed that it touches every edge of itself. + // Arguments: + // - action: function to invoke on leaves. Parameters: + // * pane: the leaf pane on which the action is invoked + // Return Value: + // - virtual void PropagateToLeavesOnEdge(const winrt::TerminalApp::Direction& edge, std::function action) = 0; + // Method Description: + // - Attempts to update the settings of this pane or any children of this pane. + // * If this pane is a leaf, and our profile guid matches the parameter, then + // we'll apply the new settings to our control. + // * If we're not a leaf, we'll recurse on our children. + // Arguments: + // - settings: The new TerminalSettings to apply to any matching controls + // - profile: The GUID of the profile these settings should apply to. + // Return Value: + // - virtual void UpdateSettings(const winrt::Microsoft::Terminal::Settings::TerminalSettings& settings, const GUID& profile) = 0; + virtual void ResizeContent(const winrt::Windows::Foundation::Size& newSize) = 0; virtual void Relayout() = 0; float CalcSnappedDimension(const bool widthOrHeight, const float dimension) const; @@ -53,11 +91,55 @@ class Pane Pane(); + // Method Description: + // - Returns first leaf pane found in the tree. + // Return Value: + // - Itself if it's a leaf, or first of its children if it's a parent. virtual std::shared_ptr _FindFirstLeaf() = 0; + // Method Description: + // - Get the absolute minimum size that this pane can be resized to and still + // have 1x1 character visible, in each of its children. If we're a leaf, we'll + // include the space needed for borders _within_ us. + // Arguments: + // - + // Return Value: + // - The minimum size that this pane can be resized to and still have a visible + // character. virtual winrt::Windows::Foundation::Size _GetMinSize() const = 0; + + // Method Description: + // - Builds a tree of LayoutSizeNode that matches the tree of panes. Each node + // has minimum size that the corresponding pane can have. + // Arguments: + // - widthOrHeight: if true operates on width, otherwise on height + // Return Value: + // - Root node of built tree, which matches this pane. virtual LayoutSizeNode _CreateMinSizeTree(const bool widthOrHeight) const; + + // Method Description: + // - Adjusts given dimension (width or height) so that all descendant terminals + // align with their character grids as close as possible. Also makes sure to + // fit in minimal sizes of the panes. + // Arguments: + // - widthOrHeight: if true operates on width, otherwise on height + // - dimension: a dimension (width or height) to be snapped + // Return Value: + // - pair of floats, where first value is the size snapped downward (not greater then + // requested size) and second is the size snapped upward (not lower than requested size). + // If requested size is already snapped, then both returned values equal this value. virtual SnapSizeResult _CalcSnappedDimension(const bool widthOrHeight, const float dimension) const = 0; + + // Method Description: + // - Increases size of given LayoutSizeNode to match next possible 'snap'. In case of leaf + // pane this means the next cell of the terminal. Otherwise it means that one of its children + // advances (recursively). It expects the given node and its descendants to have either + // already snapped or minimum size. + // Arguments: + // - widthOrHeight: if true operates on width, otherwise on height. + // - sizeNode: a layouting node that corresponds to this pane. + // Return Value: + // - virtual void _AdvanceSnappedDimension(const bool widthOrHeight, LayoutSizeNode& sizeNode) const = 0; // Make derived classes friends, so they can access protected members of Pane. C++ for some diff --git a/src/cascadia/TerminalApp/Tab.cpp b/src/cascadia/TerminalApp/Tab.cpp index 3246e0dc1c2..399d0f17501 100644 --- a/src/cascadia/TerminalApp/Tab.cpp +++ b/src/cascadia/TerminalApp/Tab.cpp @@ -28,49 +28,6 @@ Tab::~Tab() OutputDebugString(L"~Tab()\n"); } -void Tab::_SetupRootPaneEventHandlers() -{ - if (const auto rootAsLeaf = std::dynamic_pointer_cast(_rootPane)) - { - _rootPaneClosedToken = rootAsLeaf->Closed([=](auto&& /*s*/, auto&& /*e*/) { - _RemoveAllRootPaneEventHandlers(); - - _ClosedHandlers(nullptr, nullptr); - }); - - _rootPaneTypeChangedToken = rootAsLeaf->Splitted([=](std::shared_ptr splittedPane) { - _RemoveAllRootPaneEventHandlers(); - - _rootPane = splittedPane; - _SetupRootPaneEventHandlers(); - _RootPaneChangedHandlers(); - }); - } - else if (const auto rootAsParent = std::dynamic_pointer_cast(_rootPane)) - { - _rootPaneTypeChangedToken = rootAsParent->ChildClosed([=](std::shared_ptr collapsedPane) { - _RemoveAllRootPaneEventHandlers(); - - _rootPane = collapsedPane; - _SetupRootPaneEventHandlers(); - _RootPaneChangedHandlers(); - }); - } -} - -void Tab::_RemoveAllRootPaneEventHandlers() -{ - if (const auto rootAsLeaf = std::dynamic_pointer_cast(_rootPane)) - { - rootAsLeaf->Closed(_rootPaneClosedToken); - rootAsLeaf->Splitted(_rootPaneTypeChangedToken); - } - else if (const auto rootAsParent = std::dynamic_pointer_cast(_rootPane)) - { - rootAsParent->ChildClosed(_rootPaneTypeChangedToken); - } -} - void Tab::_MakeTabViewItem() { _tabViewItem = ::winrt::MUX::Controls::TabViewItem{}; @@ -160,8 +117,6 @@ void Tab::BindEventHandlers() noexcept THROW_HR_IF_NULL(E_FAIL, rootPaneAsLeaf); _SetupRootPaneEventHandlers(); - _AttachEventHandlersToPane(rootPaneAsLeaf); - _AttachEventHandlersToControl(rootPaneAsLeaf->GetTerminalControl()); } // Method Description: @@ -286,13 +241,13 @@ bool Tab::CanSplitPane(winrt::TerminalApp::SplitState splitType) // - void Tab::SplitPane(winrt::TerminalApp::SplitState splitType, const GUID& profile, TermControl& control) { - const auto result = _rootPane->FindActivePane()->Split(splitType, profile, control); + const auto newLeafPane = _rootPane->FindActivePane()->Split(splitType, profile, control); _AttachEventHandlersToControl(control); // Add a event handlers to the new pane's GotFocus event. When the pane // gains focus, we'll mark it as the new active pane. - _AttachEventHandlersToPane(result.secondChild); + _AttachEventHandlersToLeafPane(newLeafPane); } // Method Description: @@ -328,7 +283,7 @@ void Tab::ResizePane(const winrt::TerminalApp::Direction& direction) // NOTE: This _must_ be called on the root pane, so that it can propogate // throughout the entire tree. - if (auto rootPaneAsParent = std::dynamic_pointer_cast(_rootPane)) + if (const auto rootPaneAsParent = std::dynamic_pointer_cast(_rootPane)) { rootPaneAsParent->ResizeChild(direction); } @@ -346,7 +301,7 @@ void Tab::NavigateFocus(const winrt::TerminalApp::Direction& direction) // NOTE: This _must_ be called on the root pane, so that it can propogate // throughout the entire tree. - if (auto rootPaneAsParent = std::dynamic_pointer_cast(_rootPane)) + if (const auto rootPaneAsParent = std::dynamic_pointer_cast(_rootPane)) { rootPaneAsParent->NavigateFocus(direction); } @@ -365,6 +320,78 @@ void Tab::ClosePane() _rootPane->FindActivePane()->Close(); } +// Method Description: +// - Setups all the event handlers we care about for a root pane. These are superset of +// the events that we register for every other pane in the tree, however, this method +// also calls _AttachEventHandlersToLeafPane and _AttachEventHandlersToControl, so there +// is no need to also call these on the root pane. +// - It is called on initialization, and when the root pane changes (this is, it gets +// splitted or collapsed after split). +// Arguments: +// - +// Return Value: +// - +void Tab::_SetupRootPaneEventHandlers() +{ + if (const auto rootPaneAsLeaf = std::dynamic_pointer_cast(_rootPane)) + { + // Root pane also belongs to the pane tree, so attach the usual events, as for + // every other pane. + _AttachEventHandlersToLeafPane(rootPaneAsLeaf); + _AttachEventHandlersToControl(rootPaneAsLeaf->GetTerminalControl()); + + // When root pane closes, the tab also closes. + _rootPaneClosedToken = rootPaneAsLeaf->Closed([=](auto&& /*s*/, auto&& /*e*/) { + _RemoveAllRootPaneEventHandlers(); + + _ClosedHandlers(nullptr, nullptr); + }); + + // When root pane is a leaf and got splitted, it produces the new parent pane that contains + // both him and the new leaf near him. We then replace that child with the new parent pane. + _rootPaneTypeChangedToken = rootPaneAsLeaf->Splitted([=](std::shared_ptr splittedPane) { + _RemoveAllRootPaneEventHandlers(); + + _rootPane = splittedPane; + _SetupRootPaneEventHandlers(); + _RootPaneChangedHandlers(); + }); + } + else if (const auto rootPaneAsParent = std::dynamic_pointer_cast(_rootPane)) + { + // When root pane is a parent and one of its children got closed (and so the parent collapses), + // we take in its remaining, orphaned child as our own. + _rootPaneTypeChangedToken = rootPaneAsParent->ChildClosed([=](std::shared_ptr collapsedPane) { + _RemoveAllRootPaneEventHandlers(); + + _rootPane = collapsedPane; + _SetupRootPaneEventHandlers(); + _RootPaneChangedHandlers(); + }); + } +} + +// Method Description: +// - Unsubscribes from all the events of the root pane that we're subscribed to. +// - Called when the root pane gets splitted/collapsed (because it is now the root +// pane then), when the root pane closes and in dtor. +// Arguments: +// - +// Return Value: +// - +void Tab::_RemoveAllRootPaneEventHandlers() +{ + if (const auto rootAsLeaf = std::dynamic_pointer_cast(_rootPane)) + { + rootAsLeaf->Closed(_rootPaneClosedToken); + rootAsLeaf->Splitted(_rootPaneTypeChangedToken); + } + else if (const auto rootAsParent = std::dynamic_pointer_cast(_rootPane)) + { + rootAsParent->ChildClosed(_rootPaneTypeChangedToken); + } +} + // Method Description: // - Register any event handlers that we may need with the given TermControl. // This should be called on each and every TermControl that we add to the tree @@ -408,12 +435,13 @@ void Tab::_AttachEventHandlersToControl(const TermControl& control) // - Add an event handler to this pane's GotFocus event. When that pane gains // focus, we'll mark it as the new active pane. We'll also query the title of // that pane when it's focused to set our own text, and finally, we'll trigger -// our own ActivePaneChanged event. +// our own ActivePaneChanged event. This is to be called on every leaf pane in +// the tree of panes in this tab. // Arguments: // - // Return Value: // - -void Tab::_AttachEventHandlersToPane(std::shared_ptr pane) +void Tab::_AttachEventHandlersToLeafPane(std::shared_ptr pane) { std::weak_ptr weakThis{ shared_from_this() }; diff --git a/src/cascadia/TerminalApp/Tab.h b/src/cascadia/TerminalApp/Tab.h index 2950cf772ea..853340851e7 100644 --- a/src/cascadia/TerminalApp/Tab.h +++ b/src/cascadia/TerminalApp/Tab.h @@ -62,5 +62,5 @@ class Tab : public std::enable_shared_from_this void _Focus(); void _AttachEventHandlersToControl(const winrt::Microsoft::Terminal::TerminalControl::TermControl& control); - void _AttachEventHandlersToPane(std::shared_ptr pane); + void _AttachEventHandlersToLeafPane(std::shared_ptr pane); }; diff --git a/src/cascadia/TerminalApp/lib/LeafPane.cpp b/src/cascadia/TerminalApp/lib/LeafPane.cpp index 6cf591574c8..a1954365f96 100644 --- a/src/cascadia/TerminalApp/lib/LeafPane.cpp +++ b/src/cascadia/TerminalApp/lib/LeafPane.cpp @@ -31,7 +31,7 @@ LeafPane::LeafPane(const GUID& profile, const TermControl& control, const bool l _connectionStateChangedToken = _control.ConnectionStateChanged({ this, &LeafPane::_ControlConnectionStateChangedHandler }); // On the first Pane's creation, lookup resources we'll use to theme the - // Pane, including the brushed to use for the focused/unfocused border + // LeafPane, including the brushed to use for the focused/unfocused border // color. if (s_focusedBorderBrush == nullptr || s_unfocusedBorderBrush == nullptr) { @@ -57,9 +57,9 @@ LeafPane::~LeafPane() } // Function Description: -// - Attempts to load some XAML resources that the Pane will need. This includes: -// * The Color we'll use for active Panes's borders - SystemAccentColor -// * The Brush we'll use for inactive Panes - TabViewBackground (to match the +// - Attempts to load some XAML resources that the pane will need. This includes: +// * The Color we'll use for active panes's borders - SystemAccentColor +// * The Brush we'll use for inactive panes - TabViewBackground (to match the // color of the titlebar) // Arguments: // - @@ -129,12 +129,6 @@ void LeafPane::UpdateSettings(const TerminalSettings& settings, const GUID& prof } } -// Method Description: -// - Gets the TermControl of this pane. If this Pane is not a leaf, this will return nullptr. -// Arguments: -// - -// Return Value: -// - nullptr if this Pane is a parent, otherwise the TermControl of this Pane. TermControl LeafPane::GetTerminalControl() const noexcept { return _control; @@ -177,42 +171,63 @@ bool LeafPane::CanSplit(SplitState splitType) return false; } -LeafPane::SplitResult LeafPane::Split(winrt::TerminalApp::SplitState splitType, - const GUID& profile, - const winrt::Microsoft::Terminal::TerminalControl::TermControl& control) +// Method Description: +// - Splits this pane, which results in creating a new LeafPane, that is our new +// sibling and a new ParentPane, which contains both us and the new leaf. W +// Invokes Splitted handler, in which, whoever owns us should, replace us with +// this newly created ParentPane. +// Arguments: +// - splitType: what type of split we want to create. +// - profile: The profile GUID to associate with the newly created LeafPane. +// - control: A TermControl that will be placed into the new LeafPane. +// Return Value: +// - The newly created LeafPane, that is now our the neighbour. +std::shared_ptr LeafPane::Split(winrt::TerminalApp::SplitState splitType, + const GUID& profile, + const winrt::Microsoft::Terminal::TerminalControl::TermControl& control) { - const auto secondLeaf = std::make_shared(profile, control); + const auto newNeighbour = std::make_shared(profile, control); + // Update the border of this pane and set appropriate border for the new leaf pane. if (splitType == SplitState::Vertical) { - secondLeaf->_borders = _borders | Borders::Left; + newNeighbour->_borders = _borders | Borders::Left; _borders = _borders | Borders::Right; } else { - secondLeaf->_borders = _borders | Borders::Top; + newNeighbour->_borders = _borders | Borders::Top; _borders = _borders | Borders::Bottom; } _UpdateBorders(); - secondLeaf->_UpdateBorders(); + newNeighbour->_UpdateBorders(); - ClearActive(); - secondLeaf->SetActive(); + // If we were active (and usually we were since it's the active pane that is chosen to split), + // then move the focus to the new pane. + if (WasLastActive()) + { + ClearActive(); + newNeighbour->SetActive(); + } + // Parent pane has to know it's size when creating, which will just be the size of ours. Size actualSize{ gsl::narrow_cast(_root.ActualWidth()), gsl::narrow_cast(_root.ActualHeight()) }; - const auto newParent = std::make_shared(shared_from_this(), secondLeaf, splitType, Half, actualSize); + const auto newParent = std::make_shared(shared_from_this(), newNeighbour, splitType, Half, actualSize); _SplittedHandlers(newParent); + + // Call InitializeChildren after invoking Splitted handlers, because that is were the we are detached and + // the new parent is attached to xaml view. Only when we are detached can the new parent actually attach us. newParent->InitializeChildren(); - return { newParent, shared_from_this(), secondLeaf }; + return newNeighbour; } // Method Description: -// - Sets the "Active" state on this Pane. Only one Pane in a tree of Panes -// should be "active", and that pane should be a leaf. +// - Sets the "Active" state on this pane. Only one pane in a tree of panes +// should be "active". // - Updates our visuals to match our new state, including highlighting our borders. // Arguments: // - @@ -225,6 +240,13 @@ void LeafPane::SetActive() _UpdateVisuals(); } +// Method Description: +// - Remove the "Active" state from this pane. +// - Updates our visuals to match our new state, including highlighting our borders. +// Arguments: +// - +// Return Value: +// - void LeafPane::ClearActive() { _lastActive = false; @@ -284,13 +306,31 @@ void LeafPane::_UpdateBorders() _border.BorderThickness(ThicknessHelper::FromLengths(left, top, right, bottom)); } +// Method Description: +// - Called when we were children of a parent pane and our neighbour pane was closed. This +// will update border on the side that was touching that neighbour. +// Arguments: +// - closedNeightbour - The sibling leaf pane that was just closed. +// - neightbourDirection - The side at which we were touching that sibling. +// Return Value: +// - void LeafPane::UpdateBorderWithClosedNeightbour(std::shared_ptr closedNeightbour, const winrt::TerminalApp::Direction& neightbourDirection) { + // Prepare a mask that includes the only the border which was touching our neighbour. const auto borderMask = static_cast(1 << (static_cast(neightbourDirection) - 1)); + + // Set the border on this side to the state that the neighbour had. WI_UpdateFlagsInMask(_borders, borderMask, closedNeightbour->_borders); + _UpdateBorders(); } +// Method Description: +// - Fire our Closed event to tell our parent that we should be removed. +// Arguments: +// - +// Return Value: +// - void LeafPane::Close() { // Fire our Closed event to tell our parent that we should be removed. @@ -298,11 +338,7 @@ void LeafPane::Close() } // Method Description: -// - Called when our attached control is closed. Triggers listeners to our close -// event, if we're a leaf pane. -// - If this was called, and we became a parent pane (due to work on another -// thread), this function will do nothing (allowing the control's new parent -// to handle the event instead). +// - Called when our attached control is closed. Triggers listeners to our close event. // Arguments: // - // Return Value: @@ -313,6 +349,8 @@ winrt::fire_and_forget LeafPane::_ControlConnectionStateChangedHandler(const Ter co_await winrt::resume_foreground(_root.Dispatcher()); + //TODO: Check if we still listen to this event (but how?). + if (newConnectionState < ConnectionState::Closed) { // Pane doesn't care if the connection isn't entering a terminal state. @@ -348,7 +386,7 @@ void LeafPane::_ControlGotFocusHandler(winrt::Windows::Foundation::IInspectable Pane::SnapSizeResult LeafPane::_CalcSnappedDimension(const bool widthOrHeight, const float dimension) const { - // If we're a leaf pane, align to the grid of controlling terminal + // We're a leaf pane, so just align to the grid of controlling terminal. const auto minSize = _GetMinSize(); const auto minDimension = widthOrHeight ? minSize.Width : minSize.Height; diff --git a/src/cascadia/TerminalApp/lib/LeafPane.h b/src/cascadia/TerminalApp/lib/LeafPane.h index 183950bf8f7..2792d9c7760 100644 --- a/src/cascadia/TerminalApp/lib/LeafPane.h +++ b/src/cascadia/TerminalApp/lib/LeafPane.h @@ -38,7 +38,7 @@ class LeafPane : public Pane, public std::enable_shared_from_this winrt::Microsoft::Terminal::TerminalControl::TermControl GetTerminalControl() const noexcept; GUID GetProfile() const noexcept; bool CanSplit(winrt::TerminalApp::SplitState splitType); - SplitResult Split(winrt::TerminalApp::SplitState splitType, + std::shared_ptr Split(winrt::TerminalApp::SplitState splitType, const GUID& profile, const winrt::Microsoft::Terminal::TerminalControl::TermControl& control); @@ -49,13 +49,6 @@ class LeafPane : public Pane, public std::enable_shared_from_this const winrt::TerminalApp::Direction& neightbourDirection); void Close(); - struct SplitResult - { - std::shared_ptr newParent; - std::shared_ptr firstChild; - std::shared_ptr secondChild; - }; - WINRT_CALLBACK(Closed, winrt::Windows::Foundation::EventHandler); DECLARE_EVENT(Splitted, _SplittedHandlers, winrt::delegate>); DECLARE_EVENT(GotFocus, _GotFocusHandlers, winrt::delegate>); diff --git a/src/cascadia/TerminalApp/lib/ParentPane.cpp b/src/cascadia/TerminalApp/lib/ParentPane.cpp index 841aff61036..0d080467957 100644 --- a/src/cascadia/TerminalApp/lib/ParentPane.cpp +++ b/src/cascadia/TerminalApp/lib/ParentPane.cpp @@ -33,12 +33,10 @@ ParentPane::~ParentPane() } // Method Description: -// - Sets up row/column definitions for this pane. There are three total -// row/cols. The middle one is for the separator. The first and third are for -// each of the child panes, and are given a size in pixels, based off the -// availiable space, and the percent of the space they respectively consume, +// - Sets up row/column definitions for this pane. There are two total row/cols, +// one for each children and are given a size in pixels, based off the +// available space, and the percent of the space they respectively consume, // which is stored in _desiredSplitPosition -// - Does nothing if our split state is currently set to SplitState::None // Arguments: // - rootSize: The dimensions in pixels that this pane (and its children should consume.) // Return Value: @@ -79,6 +77,14 @@ void ParentPane::_CreateRowColDefinitions(const Size& rootSize) } } +// Method Description: +// - Called after ctor to start listening to our children and append their xaml +// elements. Call this only when both children can be attached (ep. they are not +// already attached somewhere). +// Arguments: +// - +// Return Value: +// - void ParentPane::InitializeChildren() { _root.Children().Append(_firstChild->GetRootElement()); @@ -87,6 +93,14 @@ void ParentPane::InitializeChildren() _SetupChildEventHandlers(false); } +// Method Description: +// - Setups all the event handlers we care about for a given child. +// - It is called on initialization, for both children, and when one of our children +// changes (this is, it gets splitted or collapsed after split). +// Arguments: +// - firstChild - true to deal with first child, false for second child +// Return Value: +// - void ParentPane::_SetupChildEventHandlers(bool firstChild) { auto& child = firstChild ? _firstChild : _secondChild; @@ -95,37 +109,58 @@ void ParentPane::_SetupChildEventHandlers(bool firstChild) if (const auto childAsLeaf = std::dynamic_pointer_cast(child)) { + // When our child is a leaf and got closed, we, well, just close him. closedToken = childAsLeaf->Closed([=, &child](auto&& /*s*/, auto&& /*e*/) { + // Unsubscribe from events of both our children, as we ourself will also + // get closed when our child does. _RemoveAllChildEventHandlers(true); _RemoveAllChildEventHandlers(false); _CloseChild(firstChild); }); + // When our child is a leaf and got splitted, it produces the new parent pane that contains + // both him and the new leaf near him. We then replace that child with the new parent pane. typeChangedToken = childAsLeaf->Splitted([=, &child](std::shared_ptr splittedChild) { + // Unsub form all the events of the child. It will now have a new parent and we + // don't care about him anymore. _RemoveAllChildEventHandlers(firstChild); child = splittedChild; _root.Children().SetAt(firstChild ? 0 : 1, child->GetRootElement()); _GetGridSetColOrRowFunc()(child->GetRootElement(), firstChild ? 0 : 1); + // The child is now a ParentPane. Setup events appropriate for him. _SetupChildEventHandlers(firstChild); }); } else if (const auto childAsParent = std::dynamic_pointer_cast(child)) { + // When our child is a parent and one of its children got closed (and so the parent collapses), + // we take in its remaining, orphaned child as our own. typeChangedToken = childAsParent->ChildClosed([=, &child](std::shared_ptr collapsedChild) { + // Unsub form all the events of the parent child. It will get destroyed, so don't + // leak its ref count. _RemoveAllChildEventHandlers(firstChild); child = collapsedChild; _root.Children().SetAt(firstChild ? 0 : 1, child->GetRootElement()); _GetGridSetColOrRowFunc()(child->GetRootElement(), firstChild ? 0 : 1); + // The child is now a LeafPane. Setup events appropriate for him. _SetupChildEventHandlers(firstChild); }); } } +// Method Description: +// - Unsubscribes from all the events of a given child that we're subscribed to. +// - Called when the child gets splitted/collapsed (because it is now our child then), +// when a child closes and in dtor. +// Arguments: +// - firstChild - true to deal with first child, false for second child +// Return Value: +// - void ParentPane::_RemoveAllChildEventHandlers(bool firstChild) { const auto child = firstChild ? _firstChild : _secondChild; @@ -143,6 +178,13 @@ void ParentPane::_RemoveAllChildEventHandlers(bool firstChild) } } +// Method Description: +// - Returns an appropriate function to set a row or column on grid, depending on _splitState. +// Arguments: +// - +// Return Value: +// - Controls::Grid::SetColumn when _splitState is Vertical +// - Controls::Grid::SetRow when _splitState is Horizontal std::function ParentPane::_GetGridSetColOrRowFunc() const noexcept { if (_splitState == SplitState::Vertical) @@ -248,14 +290,12 @@ void ParentPane::Relayout() } // Method Description: -// - Moves the separator between panes, as to resize each child on either size -// of the separator. Tries to move a separator in the given direction. The -// separator moved is the separator that's closest depth-wise to the -// currently focused pane, that's also in the correct direction to be moved. -// If there isn't such a separator, then this method returns false, as we -// couldn't handle the resize. +// - Changes the relative sizes of our children, so that the separation point moves +// in given direction. Chooses ParentPane that's closest depth-wise to the currently +// focused LeafPane, that's also in the correct direction to be moved. If it didn't +// find one in the tree, then this method returns false, as we couldn't handle the resize. // Arguments: -// - direction: The direction to move the separator in. +// - direction: The direction to move the separation point in. // Return Value: // - true if we or a child handled this resize request. bool ParentPane::ResizeChild(const Direction& direction) @@ -305,7 +345,6 @@ bool ParentPane::ResizeChild(const Direction& direction) // - Adjust our child percentages to increase the size of one of our children // and decrease the size of the other. // - Adjusts the separation amount by 5% -// - Does nothing if the direction doesn't match our current split direction // Arguments: // - direction: the direction to move our separator. If it's down or right, // we'll be increasing the size of the first of our children. Else, we'll be @@ -320,12 +359,11 @@ bool ParentPane::_ResizeChild(const Direction& direction) } float amount = .05f; - if (direction == Direction::Right || direction == Direction::Down) + if (direction == Direction::Left || direction == Direction::Up) { amount = -amount; } - // Make sure we're not making a pane explode here by resizing it to 0 characters. const bool changeWidth = _splitState == SplitState::Vertical; const Size actualSize{ gsl::narrow_cast(_root.ActualWidth()), @@ -334,7 +372,8 @@ bool ParentPane::_ResizeChild(const Direction& direction) // resizing. const auto actualDimension = changeWidth ? actualSize.Width : actualSize.Height; - _desiredSplitPosition = _ClampSplitPosition(changeWidth, _desiredSplitPosition - amount, actualDimension); + // Make sure we're not making a pane explode here by resizing it to 0 characters. + _desiredSplitPosition = _ClampSplitPosition(changeWidth, _desiredSplitPosition + amount, actualDimension); // Resize our columns to match the new percentages. Relayout(); @@ -417,11 +456,6 @@ bool ParentPane::_NavigateFocus(const Direction& direction) const bool focusSecond = (direction == Direction::Right) || (direction == Direction::Down); const auto newlyFocusedChild = focusSecond ? _secondChild : _firstChild; - const auto notFocusedChild = focusSecond ? _firstChild : _secondChild; - notFocusedChild->PropagateToLeaves([](LeafPane& pane) { - pane.ClearActive(); - }); - // 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->FindActivePane()) @@ -429,15 +463,23 @@ bool ParentPane::_NavigateFocus(const Direction& direction) return false; } - // Transfer focus to our child, and update the focus of our tree. + // Make sure to clear the focus from all the children that aren't going + // to be focused. + const auto notFocusedChild = focusSecond ? _firstChild : _secondChild; + notFocusedChild->PropagateToLeaves([](LeafPane& pane) { + pane.ClearActive(); + }); + + // Transfer focus to our child. newlyFocusedChild->_FindFirstLeaf()->SetActive(); return true; } // Method Description: -// - Closes one of our children. In doing so, takes the control from the other -// child, and makes this pane a leaf node again. +// - Closes one of our children. After that, this parent pare is useless as +// it only has one child, so it should be replaced by the remaining child +// in the ChildClosed event, which this function rises. // Arguments: // - closeFirst: if true, the first child should be closed, and the second // should be preserved, and vice-versa for false. @@ -445,25 +487,35 @@ bool ParentPane::_NavigateFocus(const Direction& direction) // - void ParentPane::_CloseChild(const bool closeFirst) { - // The closed child must always be leaf + // The closed child must always be a leaf. const auto closedChild = std::dynamic_pointer_cast(closeFirst ? _firstChild : _secondChild); THROW_HR_IF_NULL(E_FAIL, closedChild); const auto remainingChild = closeFirst ? _secondChild : _firstChild; + // Detach all the controls form our grid, so they can be attached later. _root.Children().Clear(); const auto closedChildDir = (_splitState == SplitState::Vertical) ? (closeFirst ? Direction::Left : Direction::Right) : (closeFirst ? Direction::Up : Direction::Down); + // On all the leaf descendants that were adjacent to the closed child, update its + // border, so that it matches the border of the closed child. remainingChild->PropagateToLeavesOnEdge(closedChildDir, [=](LeafPane& paneOnEdge) { paneOnEdge.UpdateBorderWithClosedNeightbour(closedChild, closedChildDir); }); + // When we invoke the ChildClosed event, our reference count might (and should) drop + // to 0 and we'd become freed, so to prevent that we capture one more ref for the + // duration of this function. const auto lifeSaver = shared_from_this(); + _ChildClosedHandlers(remainingChild); + // If any children of closed pane was previously active, we move the focus to the remaining + // child. We do that after we invoke the ChildClosed event, because it attaches that child's + // control to xaml tree and only then can it properly gain focus. if (closedChild->FindActivePane()) { remainingChild->_FindFirstLeaf()->SetActive(); @@ -478,7 +530,7 @@ void ParentPane::_CloseChild(const bool closeFirst) // but not the second. // Arguments: // - fullSize: the amount of space in pixels that should be filled by our -// children and their separators. Can be arbitrarily low. +// children. Can be arbitrarily low. // Return Value: // - a pair with the size of our first child and the size of our second child, // respectively. @@ -498,8 +550,8 @@ std::pair ParentPane::_CalcChildrenSizes(const float fullSize) con // - Gets the size in pixels of each of our children, given the full size they should // fill. Each child is snapped to char grid as close as possible. If called multiple // times with fullSize argument growing, then both returned sizes are guaranteed to be -// non-decreasing (it's a monotonically increasing function). This is important so that -// user doesn't get any pane shrank when they actually expand the window or parent pane. +// non-decreasing (it's a monotonically increasing function). This is important, so that +// user doesn't get any pane shrank when they actually expand the window or a parent pane. // That is also required by the layout algorithm. // Arguments: // - widthOrHeight: if true, operates on width, otherwise on height. @@ -690,8 +742,12 @@ Size ParentPane::_GetMinSize() const Pane::LayoutSizeNode ParentPane::_CreateMinSizeTree(const bool widthOrHeight) const { LayoutSizeNode node = Pane::_CreateMinSizeTree(widthOrHeight); + + // Pane::_CreateMinSizeTree only sets the size of this node, but since we are parent, + // we have children, so include them in the size node. node.firstChild = std::make_unique(_firstChild->_CreateMinSizeTree(widthOrHeight)); node.secondChild = std::make_unique(_secondChild->_CreateMinSizeTree(widthOrHeight)); + return node; } From 205b39576cb9970d3d1393998364c9817be1e0ba Mon Sep 17 00:00:00 2001 From: MCpiroman Date: Tue, 31 Dec 2019 11:54:45 +0100 Subject: [PATCH 05/17] Fix build --- .../KeyBindingsTests.cpp | 26 ++++++------------- .../TerminalApp/{lib => }/LeafPane.cpp | 8 +++--- src/cascadia/TerminalApp/{lib => }/LeafPane.h | 0 .../TerminalApp/{lib => }/ParentPane.cpp | 0 .../TerminalApp/{lib => }/ParentPane.h | 3 +-- src/cascadia/TerminalApp/TerminalPage.cpp | 1 - .../TerminalApp/lib/TerminalAppLib.vcxproj | 5 ++++ .../lib/TerminalAppLib.vcxproj.filters | 12 +++++++++ 8 files changed, 29 insertions(+), 26 deletions(-) rename src/cascadia/TerminalApp/{lib => }/LeafPane.cpp (97%) rename src/cascadia/TerminalApp/{lib => }/LeafPane.h (100%) rename src/cascadia/TerminalApp/{lib => }/ParentPane.cpp (100%) rename src/cascadia/TerminalApp/{lib => }/ParentPane.h (96%) diff --git a/src/cascadia/LocalTests_TerminalApp/KeyBindingsTests.cpp b/src/cascadia/LocalTests_TerminalApp/KeyBindingsTests.cpp index 42160d0b408..5c9264832cb 100644 --- a/src/cascadia/LocalTests_TerminalApp/KeyBindingsTests.cpp +++ b/src/cascadia/LocalTests_TerminalApp/KeyBindingsTests.cpp @@ -361,10 +361,9 @@ namespace TerminalAppLocalTests { "keys": ["ctrl+c"], "command": { "action": "splitPane", "split": null } }, { "keys": ["ctrl+d"], "command": { "action": "splitPane", "split": "vertical" } }, { "keys": ["ctrl+e"], "command": { "action": "splitPane", "split": "horizontal" } }, - { "keys": ["ctrl+f"], "command": { "action": "splitPane", "split": "none" } }, - { "keys": ["ctrl+g"], "command": { "action": "splitPane" } }, - { "keys": ["ctrl+h"], "command": { "action": "splitPane", "split": "auto" } }, - { "keys": ["ctrl+i"], "command": { "action": "splitPane", "split": "foo" } } + { "keys": ["ctrl+f"], "command": { "action": "splitPane" } }, + { "keys": ["ctrl+g"], "command": { "action": "splitPane", "split": "auto" } }, + { "keys": ["ctrl+h"], "command": { "action": "splitPane", "split": "foo" } } ])" }; const auto bindings0Json = VerifyParseSucceeded(bindings0String); @@ -373,7 +372,7 @@ namespace TerminalAppLocalTests VERIFY_IS_NOT_NULL(appKeyBindings); VERIFY_ARE_EQUAL(0u, appKeyBindings->_keyShortcuts.size()); appKeyBindings->LayerJson(bindings0Json); - VERIFY_ARE_EQUAL(9u, appKeyBindings->_keyShortcuts.size()); + VERIFY_ARE_EQUAL(8u, appKeyBindings->_keyShortcuts.size()); { KeyChord kc{ true, false, false, static_cast('A') }; @@ -400,7 +399,7 @@ namespace TerminalAppLocalTests const auto& realArgs = actionAndArgs.Args().try_as(); VERIFY_IS_NOT_NULL(realArgs); // Verify the args have the expected value - VERIFY_ARE_EQUAL(winrt::TerminalApp::SplitState::None, realArgs.SplitStyle()); + VERIFY_ARE_EQUAL(winrt::TerminalApp::SplitState::Vertical, realArgs.SplitStyle()); } { KeyChord kc{ true, false, false, static_cast('D') }; @@ -427,7 +426,7 @@ namespace TerminalAppLocalTests const auto& realArgs = actionAndArgs.Args().try_as(); VERIFY_IS_NOT_NULL(realArgs); // Verify the args have the expected value - VERIFY_ARE_EQUAL(winrt::TerminalApp::SplitState::None, realArgs.SplitStyle()); + VERIFY_ARE_EQUAL(winrt::TerminalApp::SplitState::Vertical, realArgs.SplitStyle()); } { KeyChord kc{ true, false, false, static_cast('G') }; @@ -436,25 +435,16 @@ namespace TerminalAppLocalTests const auto& realArgs = actionAndArgs.Args().try_as(); VERIFY_IS_NOT_NULL(realArgs); // Verify the args have the expected value - VERIFY_ARE_EQUAL(winrt::TerminalApp::SplitState::None, realArgs.SplitStyle()); - } - { - KeyChord kc{ true, false, false, static_cast('H') }; - auto actionAndArgs = TestUtils::GetActionAndArgs(*appKeyBindings, kc); - VERIFY_ARE_EQUAL(ShortcutAction::SplitPane, actionAndArgs.Action()); - const auto& realArgs = actionAndArgs.Args().try_as(); - VERIFY_IS_NOT_NULL(realArgs); - // Verify the args have the expected value VERIFY_ARE_EQUAL(winrt::TerminalApp::SplitState::Automatic, realArgs.SplitStyle()); } { - KeyChord kc{ true, false, false, static_cast('I') }; + KeyChord kc{ true, false, false, static_cast('H') }; auto actionAndArgs = TestUtils::GetActionAndArgs(*appKeyBindings, kc); VERIFY_ARE_EQUAL(ShortcutAction::SplitPane, actionAndArgs.Action()); const auto& realArgs = actionAndArgs.Args().try_as(); VERIFY_IS_NOT_NULL(realArgs); // Verify the args have the expected value - VERIFY_ARE_EQUAL(winrt::TerminalApp::SplitState::None, realArgs.SplitStyle()); + VERIFY_ARE_EQUAL(winrt::TerminalApp::SplitState::Vertical, realArgs.SplitStyle()); } } diff --git a/src/cascadia/TerminalApp/lib/LeafPane.cpp b/src/cascadia/TerminalApp/LeafPane.cpp similarity index 97% rename from src/cascadia/TerminalApp/lib/LeafPane.cpp rename to src/cascadia/TerminalApp/LeafPane.cpp index a1954365f96..6f19eaa29b3 100644 --- a/src/cascadia/TerminalApp/lib/LeafPane.cpp +++ b/src/cascadia/TerminalApp/LeafPane.cpp @@ -346,17 +346,15 @@ void LeafPane::Close() winrt::fire_and_forget LeafPane::_ControlConnectionStateChangedHandler(const TermControl& /*sender*/, const winrt::Windows::Foundation::IInspectable& /*args*/) { const auto newConnectionState = _control.ConnectionState(); - - co_await winrt::resume_foreground(_root.Dispatcher()); - - //TODO: Check if we still listen to this event (but how?). - if (newConnectionState < ConnectionState::Closed) { // Pane doesn't care if the connection isn't entering a terminal state. co_return; } + co_await winrt::resume_foreground(_root.Dispatcher()); + //TODO: Check if we still listen to this event (but how?). + const auto& settings = CascadiaSettings::GetCurrentAppSettings(); auto paneProfile = settings.FindProfile(_profile); if (paneProfile) diff --git a/src/cascadia/TerminalApp/lib/LeafPane.h b/src/cascadia/TerminalApp/LeafPane.h similarity index 100% rename from src/cascadia/TerminalApp/lib/LeafPane.h rename to src/cascadia/TerminalApp/LeafPane.h diff --git a/src/cascadia/TerminalApp/lib/ParentPane.cpp b/src/cascadia/TerminalApp/ParentPane.cpp similarity index 100% rename from src/cascadia/TerminalApp/lib/ParentPane.cpp rename to src/cascadia/TerminalApp/ParentPane.cpp diff --git a/src/cascadia/TerminalApp/lib/ParentPane.h b/src/cascadia/TerminalApp/ParentPane.h similarity index 96% rename from src/cascadia/TerminalApp/lib/ParentPane.h rename to src/cascadia/TerminalApp/ParentPane.h index 800e7157c46..d9b5e44e12b 100644 --- a/src/cascadia/TerminalApp/lib/ParentPane.h +++ b/src/cascadia/TerminalApp/ParentPane.h @@ -69,8 +69,7 @@ class ParentPane : public Pane, public std::enable_shared_from_this // - direction: The Direction to compare // - splitType: The winrt::TerminalApp::SplitState to compare // Return Value: - // - true iff the direction is perpendicular to the splitType. False for - // winrt::TerminalApp::SplitState::None. + // - true iff the direction is perpendicular to the splitType. static constexpr bool DirectionMatchesSplit(const winrt::TerminalApp::Direction& direction, const winrt::TerminalApp::SplitState& splitType) { diff --git a/src/cascadia/TerminalApp/TerminalPage.cpp b/src/cascadia/TerminalApp/TerminalPage.cpp index 7d631ba48ac..1a52a787229 100644 --- a/src/cascadia/TerminalApp/TerminalPage.cpp +++ b/src/cascadia/TerminalApp/TerminalPage.cpp @@ -960,7 +960,6 @@ namespace winrt::TerminalApp::implementation // Method Description: // - Split the focused pane either horizontally or vertically, and place the // given TermControl into the newly created pane. - // - If splitType == SplitState::None, this method does nothing. // Arguments: // - splitType: one value from the TerminalApp::SplitState enum, indicating how the // new pane should be split from its parent. diff --git a/src/cascadia/TerminalApp/lib/TerminalAppLib.vcxproj b/src/cascadia/TerminalApp/lib/TerminalAppLib.vcxproj index 358feab1d28..6e907b66bd6 100644 --- a/src/cascadia/TerminalApp/lib/TerminalAppLib.vcxproj +++ b/src/cascadia/TerminalApp/lib/TerminalAppLib.vcxproj @@ -82,6 +82,8 @@ + + ../ShortcutActionDispatch.idl @@ -134,6 +136,8 @@ + + Create @@ -256,6 +260,7 @@ ..;$(OpenConsoleDir)\dep\jsoncpp\json;%(AdditionalIncludeDirectories); 4702;%(DisableSpecificWarnings) + true WindowsApp.lib;shell32.lib;%(AdditionalDependencies) diff --git a/src/cascadia/TerminalApp/lib/TerminalAppLib.vcxproj.filters b/src/cascadia/TerminalApp/lib/TerminalAppLib.vcxproj.filters index 03e86156bdb..f7be8c85a9d 100644 --- a/src/cascadia/TerminalApp/lib/TerminalAppLib.vcxproj.filters +++ b/src/cascadia/TerminalApp/lib/TerminalAppLib.vcxproj.filters @@ -59,6 +59,12 @@ pane + + pane + + + pane + @@ -107,6 +113,12 @@ tab + + pane + + + pane + From b5a04b1e594bb18f9fffdb46647702d9f4b0996d Mon Sep 17 00:00:00 2001 From: MCpiroman Date: Sat, 4 Jan 2020 11:22:56 +0100 Subject: [PATCH 06/17] Initial PR fixes that won't tear things apart --- src/cascadia/TerminalApp/ActionArgs.idl | 4 ++-- src/cascadia/TerminalApp/LeafPane.cpp | 9 ++++++--- src/cascadia/TerminalApp/LeafPane.h | 9 +++++++-- src/cascadia/TerminalApp/Pane.h | 5 ++++- src/cascadia/TerminalApp/ParentPane.cpp | 5 +++-- src/cascadia/TerminalApp/ParentPane.h | 8 +++++++- src/cascadia/TerminalApp/Tab.cpp | 6 +++--- 7 files changed, 32 insertions(+), 14 deletions(-) diff --git a/src/cascadia/TerminalApp/ActionArgs.idl b/src/cascadia/TerminalApp/ActionArgs.idl index a3c2892f76d..ab7a6f3e92c 100644 --- a/src/cascadia/TerminalApp/ActionArgs.idl +++ b/src/cascadia/TerminalApp/ActionArgs.idl @@ -25,8 +25,8 @@ namespace TerminalApp enum SplitState { - Vertical = 0, - Horizontal = 1 + Vertical, + Horizontal }; [default_interface] runtimeclass NewTerminalArgs { diff --git a/src/cascadia/TerminalApp/LeafPane.cpp b/src/cascadia/TerminalApp/LeafPane.cpp index 6f19eaa29b3..bbc5eb2565d 100644 --- a/src/cascadia/TerminalApp/LeafPane.cpp +++ b/src/cascadia/TerminalApp/LeafPane.cpp @@ -1,3 +1,6 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. + #include "pch.h" #include "LeafPane.h" #include "Profile.h" @@ -14,9 +17,9 @@ using namespace winrt::Microsoft::Terminal::TerminalConnection; using namespace winrt::TerminalApp; using namespace TerminalApp; -static const int PaneBorderSize = 2; -static const int CombinedPaneBorderSize = 2 * PaneBorderSize; -static const float Half = 0.50f; +static constexpr int PaneBorderSize = 2; +static constexpr int CombinedPaneBorderSize = 2 * PaneBorderSize; +static constexpr float Half = 0.50f; winrt::Windows::UI::Xaml::Media::SolidColorBrush LeafPane::s_focusedBorderBrush = { nullptr }; winrt::Windows::UI::Xaml::Media::SolidColorBrush LeafPane::s_unfocusedBorderBrush = { nullptr }; diff --git a/src/cascadia/TerminalApp/LeafPane.h b/src/cascadia/TerminalApp/LeafPane.h index 2792d9c7760..9f45d43d637 100644 --- a/src/cascadia/TerminalApp/LeafPane.h +++ b/src/cascadia/TerminalApp/LeafPane.h @@ -1,3 +1,6 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. + #pragma once #include "Pane.h" #include "ParentPane.h" @@ -18,12 +21,14 @@ DEFINE_ENUM_FLAG_OPERATORS(Borders); class LeafPane : public Pane, public std::enable_shared_from_this { public: - struct SplitResult; - LeafPane(const GUID& profile, const winrt::Microsoft::Terminal::TerminalControl::TermControl& control, const bool lastFocused = false); ~LeafPane() override; + LeafPane(const LeafPane&) = delete; + LeafPane(LeafPane&&) = delete; + LeafPane& operator=(const LeafPane&) = delete; + LeafPane& operator=(LeafPane&&) = delete; std::shared_ptr FindActivePane() override; void PropagateToLeaves(std::function action) override; diff --git a/src/cascadia/TerminalApp/Pane.h b/src/cascadia/TerminalApp/Pane.h index 70207810df6..51e4284e41e 100644 --- a/src/cascadia/TerminalApp/Pane.h +++ b/src/cascadia/TerminalApp/Pane.h @@ -19,7 +19,6 @@ // - Mike Griese (zadjii-msft) 16-May-2019 #pragma once -#include #include #include "../../cascadia/inc/cppwinrt_utils.h" @@ -30,6 +29,10 @@ class Pane { public: virtual ~Pane() = default; + Pane(const Pane&) = delete; + Pane(Pane&&) = delete; + Pane& operator=(const Pane&) = delete; + Pane& operator=(Pane&&) = delete; winrt::Windows::UI::Xaml::Controls::Grid GetRootElement() const; diff --git a/src/cascadia/TerminalApp/ParentPane.cpp b/src/cascadia/TerminalApp/ParentPane.cpp index 0d080467957..6e53c56643f 100644 --- a/src/cascadia/TerminalApp/ParentPane.cpp +++ b/src/cascadia/TerminalApp/ParentPane.cpp @@ -1,3 +1,6 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. + #include "pch.h" #include "ParentPane.h" #include "Profile.h" @@ -9,8 +12,6 @@ using namespace winrt::Windows::UI::Xaml; using namespace winrt::Windows::UI::Core; using namespace winrt::Windows::UI::Xaml::Media; using namespace winrt::Microsoft::Terminal::Settings; -using namespace winrt::Microsoft::Terminal::TerminalControl; -using namespace winrt::Microsoft::Terminal::TerminalConnection; using namespace winrt::TerminalApp; using namespace TerminalApp; diff --git a/src/cascadia/TerminalApp/ParentPane.h b/src/cascadia/TerminalApp/ParentPane.h index d9b5e44e12b..4ff0e6052c2 100644 --- a/src/cascadia/TerminalApp/ParentPane.h +++ b/src/cascadia/TerminalApp/ParentPane.h @@ -1,15 +1,21 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. + #pragma once #include "Pane.h" #include "LeafPane.h" #include #include "../../cascadia/inc/cppwinrt_utils.h" -#include class ParentPane : public Pane, public std::enable_shared_from_this { public: ParentPane(std::shared_ptr firstChild, std::shared_ptr secondChild, winrt::TerminalApp::SplitState splitState, float splitPosition, winrt::Windows::Foundation::Size currentSize); ~ParentPane() override; + ParentPane(const ParentPane&) = delete; + ParentPane(ParentPane&&) = delete; + ParentPane& operator=(const ParentPane&) = delete; + ParentPane& operator=(ParentPane&&) = delete; void InitializeChildren(); diff --git a/src/cascadia/TerminalApp/Tab.cpp b/src/cascadia/TerminalApp/Tab.cpp index 399d0f17501..4dc6dbe1956 100644 --- a/src/cascadia/TerminalApp/Tab.cpp +++ b/src/cascadia/TerminalApp/Tab.cpp @@ -266,7 +266,7 @@ float Tab::CalcSnappedDimension(const bool widthOrHeight, const float dimension) // - void Tab::ResizeContent(const winrt::Windows::Foundation::Size& newSize) { - // NOTE: This _must_ be called on the root pane, so that it can propogate + // NOTE: This _must_ be called on the root pane, so that it can propagate // throughout the entire tree. _rootPane->ResizeContent(newSize); } @@ -280,7 +280,7 @@ void Tab::ResizeContent(const winrt::Windows::Foundation::Size& newSize) // - void Tab::ResizePane(const winrt::TerminalApp::Direction& direction) { - // NOTE: This _must_ be called on the root pane, so that it can propogate + // NOTE: This _must_ be called on the root pane, so that it can propagate // throughout the entire tree. if (const auto rootPaneAsParent = std::dynamic_pointer_cast(_rootPane)) @@ -298,7 +298,7 @@ void Tab::ResizePane(const winrt::TerminalApp::Direction& direction) // - void Tab::NavigateFocus(const winrt::TerminalApp::Direction& direction) { - // NOTE: This _must_ be called on the root pane, so that it can propogate + // NOTE: This _must_ be called on the root pane, so that it can propagate // throughout the entire tree. if (const auto rootPaneAsParent = std::dynamic_pointer_cast(_rootPane)) From f9aaed675526a4663b7024e36f334086409c6ebc Mon Sep 17 00:00:00 2001 From: MCpiroman Date: Sat, 11 Jan 2020 12:26:20 +0100 Subject: [PATCH 07/17] Support SplitState::Automatic --- src/cascadia/TerminalApp/ActionArgs.idl | 3 ++- src/cascadia/TerminalApp/LeafPane.cpp | 27 +++++++++++++++++++++++ src/cascadia/TerminalApp/LeafPane.h | 1 + src/cascadia/TerminalApp/TerminalPage.cpp | 15 ++++++++++--- 4 files changed, 42 insertions(+), 4 deletions(-) diff --git a/src/cascadia/TerminalApp/ActionArgs.idl b/src/cascadia/TerminalApp/ActionArgs.idl index ab7a6f3e92c..c8db12cffae 100644 --- a/src/cascadia/TerminalApp/ActionArgs.idl +++ b/src/cascadia/TerminalApp/ActionArgs.idl @@ -26,7 +26,8 @@ namespace TerminalApp enum SplitState { Vertical, - Horizontal + Horizontal, + Automatic }; [default_interface] runtimeclass NewTerminalArgs { diff --git a/src/cascadia/TerminalApp/LeafPane.cpp b/src/cascadia/TerminalApp/LeafPane.cpp index bbc5eb2565d..ef2327d83e5 100644 --- a/src/cascadia/TerminalApp/LeafPane.cpp +++ b/src/cascadia/TerminalApp/LeafPane.cpp @@ -150,6 +150,7 @@ GUID LeafPane::GetProfile() const noexcept // - True if the pane can be split. False otherwise. bool LeafPane::CanSplit(SplitState splitType) { + splitType = _ConvertAutomaticSplitState(splitType); const Size actualSize{ gsl::narrow_cast(_root.ActualWidth()), gsl::narrow_cast(_root.ActualHeight()) }; @@ -189,6 +190,7 @@ std::shared_ptr LeafPane::Split(winrt::TerminalApp::SplitState splitTy const GUID& profile, const winrt::Microsoft::Terminal::TerminalControl::TermControl& control) { + splitType = _ConvertAutomaticSplitState(splitType); const auto newNeighbour = std::make_shared(profile, control); // Update the border of this pane and set appropriate border for the new leaf pane. @@ -228,6 +230,31 @@ std::shared_ptr LeafPane::Split(winrt::TerminalApp::SplitState splitTy return newNeighbour; } +// Method Description: +// - Converts an "automatic" split type into either Vertical or Horizontal, +// based upon the current dimensions of the Pane. +// - If any of the other SplitState values are passed in, they're returned +// unmodified. +// Arguments: +// - splitType: The SplitState to attempt to convert +// Return Value: +// - One of Horizontal or Vertical +SplitState LeafPane::_ConvertAutomaticSplitState(const SplitState& splitType) const +{ + // Careful here! If the pane doesn't yet have a size, these dimensions will + // be 0, and we'll always return Vertical. + + if (splitType == SplitState::Automatic) + { + // If the requested split type was "auto", determine which direction to + // split based on our current dimensions + const Size actualSize{ gsl::narrow_cast(_root.ActualWidth()), + gsl::narrow_cast(_root.ActualHeight()) }; + return actualSize.Width >= actualSize.Height ? SplitState::Vertical : SplitState::Horizontal; + } + return splitType; +} + // Method Description: // - Sets the "Active" state on this pane. Only one pane in a tree of panes // should be "active". diff --git a/src/cascadia/TerminalApp/LeafPane.h b/src/cascadia/TerminalApp/LeafPane.h index 9f45d43d637..5db523e2edc 100644 --- a/src/cascadia/TerminalApp/LeafPane.h +++ b/src/cascadia/TerminalApp/LeafPane.h @@ -81,6 +81,7 @@ class LeafPane : public Pane, public std::enable_shared_from_this std::shared_ptr _FindFirstLeaf() override; void _UpdateBorders(); void _UpdateVisuals(); + winrt::TerminalApp::SplitState _ConvertAutomaticSplitState(const winrt::TerminalApp::SplitState& splitType) const; SnapSizeResult _CalcSnappedDimension(const bool widthOrHeight, const float dimension) const override; void _AdvanceSnappedDimension(const bool widthOrHeight, LayoutSizeNode& sizeNode) const override; winrt::Windows::Foundation::Size _GetMinSize() const override; diff --git a/src/cascadia/TerminalApp/TerminalPage.cpp b/src/cascadia/TerminalApp/TerminalPage.cpp index 1a52a787229..bc58ab11f4a 100644 --- a/src/cascadia/TerminalApp/TerminalPage.cpp +++ b/src/cascadia/TerminalApp/TerminalPage.cpp @@ -450,6 +450,7 @@ namespace winrt::TerminalApp::implementation // Add the new tab to the list of our tabs. auto newTab = _tabs.emplace_back(std::make_shared(profileGuid, term)); + newTab->BindEventHandlers(); // Hookup our event handlers to the new terminal _RegisterTerminalEvents(term, newTab); @@ -474,6 +475,17 @@ namespace winrt::TerminalApp::implementation } }); + newTab->RootPaneChanged([weakTabPtr, weakThis{ get_weak() }]() { + auto page{ weakThis.get() }; + auto tab{ weakTabPtr.lock() }; + + if (page && tab) + { + page->_tabContent.Children().Clear(); + page->_tabContent.Children().Append(tab->GetRootElement()); + } + }); + auto tabViewItem = newTab->GetTabViewItem(); _tabView.TabItems().Append(tabViewItem); @@ -798,9 +810,6 @@ namespace winrt::TerminalApp::implementation // Add an event handler when the terminal wants to paste data from the Clipboard. term.PasteFromClipboard({ this, &TerminalPage::_PasteFromClipboardHandler }); - // Bind Tab events to the TermControl and the Tab's Pane - hostingTab->BindEventHandlers(term); - // Don't capture a strong ref to the tab. If the tab is removed as this // is called, we don't really care anymore about handling the event. std::weak_ptr weakTabPtr = hostingTab; From 58ee87f1c1c1bbeb721dc660fb37a9c41489e0e4 Mon Sep 17 00:00:00 2001 From: MCpiroman Date: Sat, 11 Jan 2020 14:49:13 +0100 Subject: [PATCH 08/17] Small fixes --- src/cascadia/TerminalApp/LeafPane.cpp | 37 ++++++++++++++----------- src/cascadia/TerminalApp/LeafPane.h | 2 +- src/cascadia/TerminalApp/ParentPane.cpp | 4 +-- src/cascadia/TerminalApp/Tab.cpp | 5 +++- 4 files changed, 28 insertions(+), 20 deletions(-) diff --git a/src/cascadia/TerminalApp/LeafPane.cpp b/src/cascadia/TerminalApp/LeafPane.cpp index ef2327d83e5..bcb0615682f 100644 --- a/src/cascadia/TerminalApp/LeafPane.cpp +++ b/src/cascadia/TerminalApp/LeafPane.cpp @@ -49,7 +49,7 @@ LeafPane::LeafPane(const GUID& profile, const TermControl& control, const bool l // Colors::Transparent! The border won't get Tapped events, and they'll fall // through to something else. _border.Tapped([this](auto&, auto& e) { - SetActive(); + SetActive(true); e.Handled(true); }); } @@ -185,7 +185,7 @@ bool LeafPane::CanSplit(SplitState splitType) // - profile: The profile GUID to associate with the newly created LeafPane. // - control: A TermControl that will be placed into the new LeafPane. // Return Value: -// - The newly created LeafPane, that is now our the neighbour. +// - Newly created LeafPane that is now our neighbour. std::shared_ptr LeafPane::Split(winrt::TerminalApp::SplitState splitType, const GUID& profile, const winrt::Microsoft::Terminal::TerminalControl::TermControl& control) @@ -208,12 +208,10 @@ std::shared_ptr LeafPane::Split(winrt::TerminalApp::SplitState splitTy _UpdateBorders(); newNeighbour->_UpdateBorders(); - // If we were active (and usually we were since it's the active pane that is chosen to split), - // then move the focus to the new pane. if (WasLastActive()) { ClearActive(); - newNeighbour->SetActive(); + newNeighbour->SetActive(false); } // Parent pane has to know it's size when creating, which will just be the size of ours. @@ -232,7 +230,7 @@ std::shared_ptr LeafPane::Split(winrt::TerminalApp::SplitState splitTy // Method Description: // - Converts an "automatic" split type into either Vertical or Horizontal, -// based upon the current dimensions of the Pane. +// based upon the current dimensions of the pane. // - If any of the other SplitState values are passed in, they're returned // unmodified. // Arguments: @@ -263,10 +261,13 @@ SplitState LeafPane::_ConvertAutomaticSplitState(const SplitState& splitType) co // - // Return Value: // - -void LeafPane::SetActive() +void LeafPane::SetActive(const bool focusControl) { _lastActive = true; - _control.Focus(FocusState::Programmatic); + if (focusControl) + { + _control.Focus(FocusState::Programmatic); + } _UpdateVisuals(); } @@ -382,18 +383,22 @@ winrt::fire_and_forget LeafPane::_ControlConnectionStateChangedHandler(const Ter co_return; } + auto weakThis{ weak_from_this() }; co_await winrt::resume_foreground(_root.Dispatcher()); - //TODO: Check if we still listen to this event (but how?). - const auto& settings = CascadiaSettings::GetCurrentAppSettings(); - auto paneProfile = settings.FindProfile(_profile); - if (paneProfile) + if (auto strongThis{ weakThis.lock() }) { - auto mode = paneProfile->GetCloseOnExitMode(); - if ((mode == CloseOnExitMode::Always) || - (mode == CloseOnExitMode::Graceful && newConnectionState == ConnectionState::Closed)) + //TODO: Check if we still listen to this event (but how?). + const auto& settings = CascadiaSettings::GetCurrentAppSettings(); + auto paneProfile = settings.FindProfile(_profile); + if (paneProfile) { - Close(); + auto mode = paneProfile->GetCloseOnExitMode(); + if ((mode == CloseOnExitMode::Always) || + (mode == CloseOnExitMode::Graceful && newConnectionState == ConnectionState::Closed)) + { + strongThis->Close(); + } } } } diff --git a/src/cascadia/TerminalApp/LeafPane.h b/src/cascadia/TerminalApp/LeafPane.h index 5db523e2edc..e0b567a7673 100644 --- a/src/cascadia/TerminalApp/LeafPane.h +++ b/src/cascadia/TerminalApp/LeafPane.h @@ -47,7 +47,7 @@ class LeafPane : public Pane, public std::enable_shared_from_this const GUID& profile, const winrt::Microsoft::Terminal::TerminalControl::TermControl& control); - void SetActive(); + void SetActive(const bool focusControl); void ClearActive(); bool WasLastActive() const noexcept; void UpdateBorderWithClosedNeightbour(std::shared_ptr closedNeightbour, diff --git a/src/cascadia/TerminalApp/ParentPane.cpp b/src/cascadia/TerminalApp/ParentPane.cpp index 6e53c56643f..b79c6271d52 100644 --- a/src/cascadia/TerminalApp/ParentPane.cpp +++ b/src/cascadia/TerminalApp/ParentPane.cpp @@ -472,7 +472,7 @@ bool ParentPane::_NavigateFocus(const Direction& direction) }); // Transfer focus to our child. - newlyFocusedChild->_FindFirstLeaf()->SetActive(); + newlyFocusedChild->_FindFirstLeaf()->SetActive(true); return true; } @@ -519,7 +519,7 @@ void ParentPane::_CloseChild(const bool closeFirst) // control to xaml tree and only then can it properly gain focus. if (closedChild->FindActivePane()) { - remainingChild->_FindFirstLeaf()->SetActive(); + remainingChild->_FindFirstLeaf()->SetActive(true); } } diff --git a/src/cascadia/TerminalApp/Tab.cpp b/src/cascadia/TerminalApp/Tab.cpp index 4dc6dbe1956..e6e23a824fb 100644 --- a/src/cascadia/TerminalApp/Tab.cpp +++ b/src/cascadia/TerminalApp/Tab.cpp @@ -241,6 +241,7 @@ bool Tab::CanSplitPane(winrt::TerminalApp::SplitState splitType) // - void Tab::SplitPane(winrt::TerminalApp::SplitState splitType, const GUID& profile, TermControl& control) { + OutputDebugString(L"SplitPane() start\n"); const auto newLeafPane = _rootPane->FindActivePane()->Split(splitType, profile, control); _AttachEventHandlersToControl(control); @@ -248,6 +249,7 @@ void Tab::SplitPane(winrt::TerminalApp::SplitState splitType, const GUID& profil // Add a event handlers to the new pane's GotFocus event. When the pane // gains focus, we'll mark it as the new active pane. _AttachEventHandlersToLeafPane(newLeafPane); + OutputDebugString(L"SplitPane() end\n"); } // Method Description: @@ -450,12 +452,13 @@ void Tab::_AttachEventHandlersToLeafPane(std::shared_ptr pane) auto tab{ weakThis.lock() }; if (tab && sender != tab->_rootPane->FindActivePane()) { + OutputDebugString(L"GotFocus()\n"); // Clear the active state of the entire tree, and mark only the sender as active. tab->_rootPane->PropagateToLeaves([](LeafPane& pane) { pane.ClearActive(); }); - sender->SetActive(); + sender->SetActive(false); // Update our own title text to match the newly-active pane. tab->SetTabText(tab->GetActiveTitle()); From a9b3056136cf5be6416e97932c2d9efbad757763 Mon Sep 17 00:00:00 2001 From: MCpiroman Date: Sat, 11 Jan 2020 15:14:02 +0100 Subject: [PATCH 09/17] Remove debug crap --- src/cascadia/TerminalApp/LeafPane.cpp | 6 ------ src/cascadia/TerminalApp/LeafPane.h | 2 +- src/cascadia/TerminalApp/ParentPane.cpp | 5 ----- src/cascadia/TerminalApp/ParentPane.h | 2 +- src/cascadia/TerminalApp/Tab.cpp | 2 -- 5 files changed, 2 insertions(+), 15 deletions(-) diff --git a/src/cascadia/TerminalApp/LeafPane.cpp b/src/cascadia/TerminalApp/LeafPane.cpp index bcb0615682f..da2ab445360 100644 --- a/src/cascadia/TerminalApp/LeafPane.cpp +++ b/src/cascadia/TerminalApp/LeafPane.cpp @@ -54,11 +54,6 @@ LeafPane::LeafPane(const GUID& profile, const TermControl& control, const bool l }); } -LeafPane::~LeafPane() -{ - OutputDebugString(L"~LeafPane()\n"); -} - // Function Description: // - Attempts to load some XAML resources that the pane will need. This includes: // * The Color we'll use for active panes's borders - SystemAccentColor @@ -388,7 +383,6 @@ winrt::fire_and_forget LeafPane::_ControlConnectionStateChangedHandler(const Ter if (auto strongThis{ weakThis.lock() }) { - //TODO: Check if we still listen to this event (but how?). const auto& settings = CascadiaSettings::GetCurrentAppSettings(); auto paneProfile = settings.FindProfile(_profile); if (paneProfile) diff --git a/src/cascadia/TerminalApp/LeafPane.h b/src/cascadia/TerminalApp/LeafPane.h index e0b567a7673..771613972cb 100644 --- a/src/cascadia/TerminalApp/LeafPane.h +++ b/src/cascadia/TerminalApp/LeafPane.h @@ -24,7 +24,7 @@ class LeafPane : public Pane, public std::enable_shared_from_this LeafPane(const GUID& profile, const winrt::Microsoft::Terminal::TerminalControl::TermControl& control, const bool lastFocused = false); - ~LeafPane() override; + ~LeafPane() override = default; LeafPane(const LeafPane&) = delete; LeafPane(LeafPane&&) = delete; LeafPane& operator=(const LeafPane&) = delete; diff --git a/src/cascadia/TerminalApp/ParentPane.cpp b/src/cascadia/TerminalApp/ParentPane.cpp index b79c6271d52..8017c7bd172 100644 --- a/src/cascadia/TerminalApp/ParentPane.cpp +++ b/src/cascadia/TerminalApp/ParentPane.cpp @@ -28,11 +28,6 @@ ParentPane::ParentPane(std::shared_ptr firstChild, std::shared_ptrGetRootElement(), 1); } -ParentPane::~ParentPane() -{ - OutputDebugString(L"~ParentPane()\n"); -} - // Method Description: // - Sets up row/column definitions for this pane. There are two total row/cols, // one for each children and are given a size in pixels, based off the diff --git a/src/cascadia/TerminalApp/ParentPane.h b/src/cascadia/TerminalApp/ParentPane.h index 4ff0e6052c2..da8d6dc877d 100644 --- a/src/cascadia/TerminalApp/ParentPane.h +++ b/src/cascadia/TerminalApp/ParentPane.h @@ -11,7 +11,7 @@ class ParentPane : public Pane, public std::enable_shared_from_this { public: ParentPane(std::shared_ptr firstChild, std::shared_ptr secondChild, winrt::TerminalApp::SplitState splitState, float splitPosition, winrt::Windows::Foundation::Size currentSize); - ~ParentPane() override; + ~ParentPane() override = default; ParentPane(const ParentPane&) = delete; ParentPane(ParentPane&&) = delete; ParentPane& operator=(const ParentPane&) = delete; diff --git a/src/cascadia/TerminalApp/Tab.cpp b/src/cascadia/TerminalApp/Tab.cpp index e6e23a824fb..41d46ec4341 100644 --- a/src/cascadia/TerminalApp/Tab.cpp +++ b/src/cascadia/TerminalApp/Tab.cpp @@ -4,7 +4,6 @@ #include "pch.h" #include "Tab.h" #include "Utils.h" -#include using namespace winrt::Windows::UI::Xaml; using namespace winrt::Windows::UI::Core; @@ -25,7 +24,6 @@ Tab::Tab(const GUID& profile, const TermControl& control) Tab::~Tab() { _RemoveAllRootPaneEventHandlers(); - OutputDebugString(L"~Tab()\n"); } void Tab::_MakeTabViewItem() From e02fd898c4893cdec5acef15434128a735afb81b Mon Sep 17 00:00:00 2001 From: MCpiroman Date: Sat, 11 Jan 2020 15:21:13 +0100 Subject: [PATCH 10/17] Explicit format, so there is enough small commits to mess with in rebase --- src/cascadia/TerminalApp/LeafPane.h | 4 ++-- src/cascadia/TerminalApp/Pane.h | 2 +- src/cascadia/TerminalApp/ParentPane.cpp | 12 ++++++------ src/cascadia/TerminalApp/Tab.cpp | 4 ++-- 4 files changed, 11 insertions(+), 11 deletions(-) diff --git a/src/cascadia/TerminalApp/LeafPane.h b/src/cascadia/TerminalApp/LeafPane.h index 771613972cb..2fedbe7e9fd 100644 --- a/src/cascadia/TerminalApp/LeafPane.h +++ b/src/cascadia/TerminalApp/LeafPane.h @@ -44,8 +44,8 @@ class LeafPane : public Pane, public std::enable_shared_from_this GUID GetProfile() const noexcept; bool CanSplit(winrt::TerminalApp::SplitState splitType); std::shared_ptr Split(winrt::TerminalApp::SplitState splitType, - const GUID& profile, - const winrt::Microsoft::Terminal::TerminalControl::TermControl& control); + const GUID& profile, + const winrt::Microsoft::Terminal::TerminalControl::TermControl& control); void SetActive(const bool focusControl); void ClearActive(); diff --git a/src/cascadia/TerminalApp/Pane.h b/src/cascadia/TerminalApp/Pane.h index 51e4284e41e..7909e6ed7f9 100644 --- a/src/cascadia/TerminalApp/Pane.h +++ b/src/cascadia/TerminalApp/Pane.h @@ -48,7 +48,7 @@ class Pane virtual std::shared_ptr FindActivePane() = 0; // Method Description: - // - Invokes the given action on each descendant leaf pane, which may be just this pane if + // - Invokes the given action on each descendant leaf pane, which may be just this pane if // it is a leaf. // Arguments: // - action: function to invoke on leaves. Parameters: diff --git a/src/cascadia/TerminalApp/ParentPane.cpp b/src/cascadia/TerminalApp/ParentPane.cpp index 8017c7bd172..b752bfc3de7 100644 --- a/src/cascadia/TerminalApp/ParentPane.cpp +++ b/src/cascadia/TerminalApp/ParentPane.cpp @@ -29,7 +29,7 @@ ParentPane::ParentPane(std::shared_ptr firstChild, std::shared_ptr @@ -115,7 +115,7 @@ void ParentPane::_SetupChildEventHandlers(bool firstChild) _CloseChild(firstChild); }); - // When our child is a leaf and got splitted, it produces the new parent pane that contains + // When our child is a leaf and got splitted, it produces the new parent pane that contains // both him and the new leaf near him. We then replace that child with the new parent pane. typeChangedToken = childAsLeaf->Splitted([=, &child](std::shared_ptr splittedChild) { // Unsub form all the events of the child. It will now have a new parent and we @@ -287,7 +287,7 @@ void ParentPane::Relayout() // Method Description: // - Changes the relative sizes of our children, so that the separation point moves -// in given direction. Chooses ParentPane that's closest depth-wise to the currently +// in given direction. Chooses ParentPane that's closest depth-wise to the currently // focused LeafPane, that's also in the correct direction to be moved. If it didn't // find one in the tree, then this method returns false, as we couldn't handle the resize. // Arguments: @@ -503,7 +503,7 @@ void ParentPane::_CloseChild(const bool closeFirst) }); // When we invoke the ChildClosed event, our reference count might (and should) drop - // to 0 and we'd become freed, so to prevent that we capture one more ref for the + // to 0 and we'd become freed, so to prevent that we capture one more ref for the // duration of this function. const auto lifeSaver = shared_from_this(); diff --git a/src/cascadia/TerminalApp/Tab.cpp b/src/cascadia/TerminalApp/Tab.cpp index 41d46ec4341..ce6aaf04ce8 100644 --- a/src/cascadia/TerminalApp/Tab.cpp +++ b/src/cascadia/TerminalApp/Tab.cpp @@ -325,7 +325,7 @@ void Tab::ClosePane() // the events that we register for every other pane in the tree, however, this method // also calls _AttachEventHandlersToLeafPane and _AttachEventHandlersToControl, so there // is no need to also call these on the root pane. -// - It is called on initialization, and when the root pane changes (this is, it gets +// - It is called on initialization, and when the root pane changes (this is, it gets // splitted or collapsed after split). // Arguments: // - @@ -373,7 +373,7 @@ void Tab::_SetupRootPaneEventHandlers() // Method Description: // - Unsubscribes from all the events of the root pane that we're subscribed to. -// - Called when the root pane gets splitted/collapsed (because it is now the root +// - Called when the root pane gets splitted/collapsed (because it is now the root // pane then), when the root pane closes and in dtor. // Arguments: // - From af6ed189e167a957a46da3c070b8946a4a1eb2e8 Mon Sep 17 00:00:00 2001 From: mcpiroman <38111589+mcpiroman@users.noreply.github.com> Date: Sat, 29 Feb 2020 18:31:14 +0100 Subject: [PATCH 11/17] Default SplitState to Automatic --- src/cascadia/TerminalApp/ActionArgs.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/cascadia/TerminalApp/ActionArgs.h b/src/cascadia/TerminalApp/ActionArgs.h index c912e9e394e..6327b01a431 100644 --- a/src/cascadia/TerminalApp/ActionArgs.h +++ b/src/cascadia/TerminalApp/ActionArgs.h @@ -311,13 +311,13 @@ namespace winrt::TerminalApp::implementation return TerminalApp::SplitState::Automatic; } // default behavior for invalid data - return TerminalApp::SplitState::Vertical; + return TerminalApp::SplitState::Automatic; }; struct SplitPaneArgs : public SplitPaneArgsT { SplitPaneArgs() = default; - GETSET_PROPERTY(winrt::TerminalApp::SplitState, SplitStyle, winrt::TerminalApp::SplitState::Vertical); + GETSET_PROPERTY(winrt::TerminalApp::SplitState, SplitStyle, winrt::TerminalApp::SplitState::Automatic); GETSET_PROPERTY(winrt::TerminalApp::NewTerminalArgs, TerminalArgs, nullptr); static constexpr std::string_view SplitKey{ "split" }; From 9ea73c7e8ed4a795e50074cc86cedfcf529afd98 Mon Sep 17 00:00:00 2001 From: MCpiroman Date: Sat, 7 Mar 2020 09:42:16 +0100 Subject: [PATCH 12/17] Correct split tests --- src/cascadia/LocalTests_TerminalApp/KeyBindingsTests.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/cascadia/LocalTests_TerminalApp/KeyBindingsTests.cpp b/src/cascadia/LocalTests_TerminalApp/KeyBindingsTests.cpp index 5c9264832cb..92ae76ef415 100644 --- a/src/cascadia/LocalTests_TerminalApp/KeyBindingsTests.cpp +++ b/src/cascadia/LocalTests_TerminalApp/KeyBindingsTests.cpp @@ -399,7 +399,7 @@ namespace TerminalAppLocalTests const auto& realArgs = actionAndArgs.Args().try_as(); VERIFY_IS_NOT_NULL(realArgs); // Verify the args have the expected value - VERIFY_ARE_EQUAL(winrt::TerminalApp::SplitState::Vertical, realArgs.SplitStyle()); + VERIFY_ARE_EQUAL(winrt::TerminalApp::SplitState::Automatic, realArgs.SplitStyle()); } { KeyChord kc{ true, false, false, static_cast('D') }; @@ -426,7 +426,7 @@ namespace TerminalAppLocalTests const auto& realArgs = actionAndArgs.Args().try_as(); VERIFY_IS_NOT_NULL(realArgs); // Verify the args have the expected value - VERIFY_ARE_EQUAL(winrt::TerminalApp::SplitState::Vertical, realArgs.SplitStyle()); + VERIFY_ARE_EQUAL(winrt::TerminalApp::SplitState::Automatic, realArgs.SplitStyle()); } { KeyChord kc{ true, false, false, static_cast('G') }; @@ -444,7 +444,7 @@ namespace TerminalAppLocalTests const auto& realArgs = actionAndArgs.Args().try_as(); VERIFY_IS_NOT_NULL(realArgs); // Verify the args have the expected value - VERIFY_ARE_EQUAL(winrt::TerminalApp::SplitState::Vertical, realArgs.SplitStyle()); + VERIFY_ARE_EQUAL(winrt::TerminalApp::SplitState::Automatic, realArgs.SplitStyle()); } } From 52893ecd360adce53cef22bdfb39c9f38b69754a Mon Sep 17 00:00:00 2001 From: MCpiroman Date: Sat, 7 Mar 2020 12:56:28 +0100 Subject: [PATCH 13/17] Code format --- src/cascadia/TerminalApp/Tab.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/cascadia/TerminalApp/Tab.h b/src/cascadia/TerminalApp/Tab.h index b358eb7aff8..dd12717b693 100644 --- a/src/cascadia/TerminalApp/Tab.h +++ b/src/cascadia/TerminalApp/Tab.h @@ -72,4 +72,4 @@ namespace winrt::TerminalApp::implementation void _AttachEventHandlersToControl(const winrt::Microsoft::Terminal::TerminalControl::TermControl& control); void _AttachEventHandlersToLeafPane(std::shared_ptr pane); -}; + }; From 9ce6a3dcbe07e122c8fdcff50da6b6b74fc0c3db Mon Sep 17 00:00:00 2001 From: MCpiroman Date: Sat, 7 Mar 2020 13:23:12 +0100 Subject: [PATCH 14/17] Fix merge --- src/cascadia/TerminalApp/TerminalPage.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/cascadia/TerminalApp/TerminalPage.cpp b/src/cascadia/TerminalApp/TerminalPage.cpp index 0f0d0426a0d..4336455045b 100644 --- a/src/cascadia/TerminalApp/TerminalPage.cpp +++ b/src/cascadia/TerminalApp/TerminalPage.cpp @@ -542,7 +542,7 @@ namespace winrt::TerminalApp::implementation } }); - newTnewTabImplab->RootPaneChanged([weakTab, weakThis{ get_weak() }]() { + newTabImpl->RootPaneChanged([weakTab, weakThis{ get_weak() }]() { auto page{ weakThis.get() }; auto tab{ weakTab.get() }; From 792e5156166409870de763a42cdc9dd18e32bece Mon Sep 17 00:00:00 2001 From: MCpiroman Date: Sat, 7 Mar 2020 18:17:01 +0100 Subject: [PATCH 15/17] Review changes --- src/cascadia/TerminalApp/LeafPane.cpp | 150 ++++++++++++---------- src/cascadia/TerminalApp/LeafPane.h | 4 +- src/cascadia/TerminalApp/ParentPane.cpp | 95 ++++++++------ src/cascadia/TerminalApp/ParentPane.h | 5 +- src/cascadia/TerminalApp/Tab.cpp | 40 +++--- src/cascadia/TerminalApp/TerminalPage.cpp | 6 +- 6 files changed, 168 insertions(+), 132 deletions(-) diff --git a/src/cascadia/TerminalApp/LeafPane.cpp b/src/cascadia/TerminalApp/LeafPane.cpp index e10657449af..756d5f4af3f 100644 --- a/src/cascadia/TerminalApp/LeafPane.cpp +++ b/src/cascadia/TerminalApp/LeafPane.cpp @@ -336,17 +336,28 @@ void LeafPane::_UpdateBorders() // - Called when we were children of a parent pane and our neighbour pane was closed. This // will update border on the side that was touching that neighbour. // Arguments: -// - closedNeightbour - The sibling leaf pane that was just closed. -// - neightbourDirection - The side at which we were touching that sibling. +// - closedNeighbour - The sibling leaf pane that was just closed. +// - neighbourDirection - The side at which we were touching that sibling. // Return Value: // - -void LeafPane::UpdateBorderWithClosedNeightbour(std::shared_ptr closedNeightbour, const winrt::TerminalApp::Direction& neightbourDirection) +void LeafPane::UpdateBorderWithClosedNeighbour(std::shared_ptr closedNeighbour, const winrt::TerminalApp::Direction& neighbourDirection) { // Prepare a mask that includes the only the border which was touching our neighbour. - const auto borderMask = static_cast(1 << (static_cast(neightbourDirection) - 1)); + Borders borderMask; + switch (neighbourDirection) + { + case Direction::Up: + borderMask = Borders::Top; + case Direction::Down: + borderMask = Borders::Bottom; + case Direction::Left: + borderMask = Borders::Left; + case Direction::Right: + borderMask = Borders::Right; + } - // Set the border on this side to the state that the neighbour had. - WI_UpdateFlagsInMask(_borders, borderMask, closedNeightbour->_borders); + // Set the border on this side to the same state that the neighbour had. + WI_UpdateFlagsInMask(_borders, borderMask, closedNeighbour->_borders); _UpdateBorders(); } @@ -424,76 +435,77 @@ Pane::SnapSizeResult LeafPane::_CalcSnappedDimension(const bool widthOrHeight, c // We're a leaf pane, so just align to the grid of controlling terminal. const auto minSize = _GetMinSize(); - const auto minDimension = widthOrHeight ? minSize.Width : minSize.Height; + const auto minDimension = widthOrHeight ? minSize.Width : + minSize.Height; - if (dimension <= minDimension) - { - return { minDimension, minDimension }; - } + if (dimension <= minDimension) + { + return { minDimension, minDimension }; + } - float lower = _control.SnapDimensionToGrid(widthOrHeight, dimension); - if (widthOrHeight) - { - lower += WI_IsFlagSet(_borders, Borders::Left) ? PaneBorderSize : 0; - lower += WI_IsFlagSet(_borders, Borders::Right) ? PaneBorderSize : 0; - } - else - { - lower += WI_IsFlagSet(_borders, Borders::Top) ? PaneBorderSize : 0; - lower += WI_IsFlagSet(_borders, Borders::Bottom) ? PaneBorderSize : 0; - } + float lower = _control.SnapDimensionToGrid(widthOrHeight, dimension); + if (widthOrHeight) + { + lower += WI_IsFlagSet(_borders, Borders::Left) ? PaneBorderSize : 0; + lower += WI_IsFlagSet(_borders, Borders::Right) ? PaneBorderSize : 0; + } + else + { + lower += WI_IsFlagSet(_borders, Borders::Top) ? PaneBorderSize : 0; + lower += WI_IsFlagSet(_borders, Borders::Bottom) ? PaneBorderSize : 0; + } - if (lower == dimension) - { - // If we happen to be already snapped, then just return this size - // as both lower and higher values. - return { lower, lower }; - } - else - { - const auto cellSize = _control.CharacterDimensions(); - const auto higher = lower + (widthOrHeight ? cellSize.Width : cellSize.Height); - return { lower, higher }; - } -} + if (lower == dimension) + { + // If we happen to be already snapped, then just return this size + // as both lower and higher values. + return { lower, lower }; + } + else + { + const auto cellSize = _control.CharacterDimensions(); + const auto higher = lower + (widthOrHeight ? cellSize.Width : cellSize.Height); + return { lower, higher }; + } + } -void LeafPane::_AdvanceSnappedDimension(const bool widthOrHeight, LayoutSizeNode& sizeNode) const -{ - // We're a leaf pane, so just add one more row or column (unless isMinimumSize - // is true, see below). + void LeafPane::_AdvanceSnappedDimension(const bool widthOrHeight, LayoutSizeNode& sizeNode) const + { + // We're a leaf pane, so just add one more row or column (unless isMinimumSize + // is true, see below). - if (sizeNode.isMinimumSize) - { - // If the node is of its minimum size, this size might not be snapped (it might - // be, say, half a character, or fixed 10 pixels), so snap it upward. It might - // however be already snapped, so add 1 to make sure it really increases - // (not strictly necessary but to avoid surprises). - sizeNode.size = _CalcSnappedDimension(widthOrHeight, sizeNode.size + 1).higher; - } - else - { - const auto cellSize = _control.CharacterDimensions(); - sizeNode.size += widthOrHeight ? cellSize.Width : cellSize.Height; - } + if (sizeNode.isMinimumSize) + { + // If the node is of its minimum size, this size might not be snapped (it might + // be, say, half a character, or fixed 10 pixels), so snap it upward. It might + // however be already snapped, so add 1 to make sure it really increases + // (not strictly necessary but to avoid surprises). + sizeNode.size = _CalcSnappedDimension(widthOrHeight, sizeNode.size + 1).higher; + } + else + { + const auto cellSize = _control.CharacterDimensions(); + sizeNode.size += widthOrHeight ? cellSize.Width : cellSize.Height; + } - // Because we have grown, we're certainly no longer of our - // minimal size (if we've ever been). - sizeNode.isMinimumSize = false; -} + // Because we have grown, we're certainly no longer of our + // minimal size (if we've ever been). + sizeNode.isMinimumSize = false; + } -Size LeafPane::_GetMinSize() const -{ - auto controlSize = _control.MinimumSize(); - auto newWidth = controlSize.Width; - auto newHeight = controlSize.Height; + Size LeafPane::_GetMinSize() const + { + auto controlSize = _control.MinimumSize(); + auto newWidth = controlSize.Width; + auto newHeight = controlSize.Height; - newWidth += WI_IsFlagSet(_borders, Borders::Left) ? PaneBorderSize : 0; - newWidth += WI_IsFlagSet(_borders, Borders::Right) ? PaneBorderSize : 0; - newHeight += WI_IsFlagSet(_borders, Borders::Top) ? PaneBorderSize : 0; - newHeight += WI_IsFlagSet(_borders, Borders::Bottom) ? PaneBorderSize : 0; + newWidth += WI_IsFlagSet(_borders, Borders::Left) ? PaneBorderSize : 0; + newWidth += WI_IsFlagSet(_borders, Borders::Right) ? PaneBorderSize : 0; + newHeight += WI_IsFlagSet(_borders, Borders::Top) ? PaneBorderSize : 0; + newHeight += WI_IsFlagSet(_borders, Borders::Bottom) ? PaneBorderSize : 0; - return { newWidth, newHeight }; -} + return { newWidth, newHeight }; + } -DEFINE_EVENT(LeafPane, Splitted, _SplittedHandlers, winrt::delegate>); -DEFINE_EVENT(LeafPane, GotFocus, _GotFocusHandlers, winrt::delegate>); + DEFINE_EVENT(LeafPane, Splitted, _SplittedHandlers, winrt::delegate>); + DEFINE_EVENT(LeafPane, GotFocus, _GotFocusHandlers, winrt::delegate>); diff --git a/src/cascadia/TerminalApp/LeafPane.h b/src/cascadia/TerminalApp/LeafPane.h index 2ecb4736717..67ca140464a 100644 --- a/src/cascadia/TerminalApp/LeafPane.h +++ b/src/cascadia/TerminalApp/LeafPane.h @@ -50,8 +50,8 @@ class LeafPane : public Pane, public std::enable_shared_from_this void SetActive(const bool focusControl); void ClearActive(); bool WasLastActive() const noexcept; - void UpdateBorderWithClosedNeightbour(std::shared_ptr closedNeightbour, - const winrt::TerminalApp::Direction& neightbourDirection); + void UpdateBorderWithClosedNeighbour(std::shared_ptr closedNeighbour, + const winrt::TerminalApp::Direction& neighbourDirection); void Close(); void Shutdown(); diff --git a/src/cascadia/TerminalApp/ParentPane.cpp b/src/cascadia/TerminalApp/ParentPane.cpp index b752bfc3de7..fc631a32961 100644 --- a/src/cascadia/TerminalApp/ParentPane.cpp +++ b/src/cascadia/TerminalApp/ParentPane.cpp @@ -94,57 +94,48 @@ void ParentPane::InitializeChildren() // - It is called on initialization, for both children, and when one of our children // changes (this is, it gets splitted or collapsed after split). // Arguments: -// - firstChild - true to deal with first child, false for second child +// - isFirstChild - true to deal with first child, false for second child // Return Value: // - -void ParentPane::_SetupChildEventHandlers(bool firstChild) +void ParentPane::_SetupChildEventHandlers(const bool isFirstChild) { - auto& child = firstChild ? _firstChild : _secondChild; - auto& closedToken = firstChild ? _firstClosedToken : _secondClosedToken; - auto& typeChangedToken = firstChild ? _firstTypeChangedToken : _secondTypeChangedToken; + auto& child = isFirstChild ? _firstChild : _secondChild; + auto& closedToken = isFirstChild ? _firstClosedToken : _secondClosedToken; + auto& typeChangedToken = isFirstChild ? _firstTypeChangedToken : _secondTypeChangedToken; if (const auto childAsLeaf = std::dynamic_pointer_cast(child)) { // When our child is a leaf and got closed, we, well, just close him. - closedToken = childAsLeaf->Closed([=, &child](auto&& /*s*/, auto&& /*e*/) { - // Unsubscribe from events of both our children, as we ourself will also - // get closed when our child does. - _RemoveAllChildEventHandlers(true); - _RemoveAllChildEventHandlers(false); - - _CloseChild(firstChild); + closedToken = childAsLeaf->Closed([=, weakThis = weak_from_this()](auto&& /*s*/, auto&& /*e*/) { + if (auto pane{ weakThis.lock() }) + { + // Unsubscribe from events of both our children, as we ourself will also + // get closed when our child does. + pane->_RemoveAllChildEventHandlers(true); + pane->_RemoveAllChildEventHandlers(false); + + pane->_CloseChild(isFirstChild); + } }); // When our child is a leaf and got splitted, it produces the new parent pane that contains // both him and the new leaf near him. We then replace that child with the new parent pane. - typeChangedToken = childAsLeaf->Splitted([=, &child](std::shared_ptr splittedChild) { - // Unsub form all the events of the child. It will now have a new parent and we - // don't care about him anymore. - _RemoveAllChildEventHandlers(firstChild); - - child = splittedChild; - _root.Children().SetAt(firstChild ? 0 : 1, child->GetRootElement()); - _GetGridSetColOrRowFunc()(child->GetRootElement(), firstChild ? 0 : 1); - - // The child is now a ParentPane. Setup events appropriate for him. - _SetupChildEventHandlers(firstChild); + typeChangedToken = childAsLeaf->Splitted([=, weakThis = weak_from_this(), &child](std::shared_ptr splittedChild) { + if (auto pane{ weakThis.lock() }) + { + pane->_OnChildSplittedOrCollapsed(isFirstChild, splittedChild); + } }); } else if (const auto childAsParent = std::dynamic_pointer_cast(child)) { // When our child is a parent and one of its children got closed (and so the parent collapses), // we take in its remaining, orphaned child as our own. - typeChangedToken = childAsParent->ChildClosed([=, &child](std::shared_ptr collapsedChild) { - // Unsub form all the events of the parent child. It will get destroyed, so don't - // leak its ref count. - _RemoveAllChildEventHandlers(firstChild); - - child = collapsedChild; - _root.Children().SetAt(firstChild ? 0 : 1, child->GetRootElement()); - _GetGridSetColOrRowFunc()(child->GetRootElement(), firstChild ? 0 : 1); - - // The child is now a LeafPane. Setup events appropriate for him. - _SetupChildEventHandlers(firstChild); + typeChangedToken = childAsParent->ChildClosed([=, weakThis = weak_from_this(), &child](std::shared_ptr collapsedChild) { + if (auto pane{ weakThis.lock() }) + { + pane->_OnChildSplittedOrCollapsed(isFirstChild, collapsedChild); + } }); } } @@ -154,14 +145,14 @@ void ParentPane::_SetupChildEventHandlers(bool firstChild) // - Called when the child gets splitted/collapsed (because it is now our child then), // when a child closes and in dtor. // Arguments: -// - firstChild - true to deal with first child, false for second child +// - isFirstChild - true to deal with first child, false for second child // Return Value: // - -void ParentPane::_RemoveAllChildEventHandlers(bool firstChild) +void ParentPane::_RemoveAllChildEventHandlers(const bool isFirstChild) { - const auto child = firstChild ? _firstChild : _secondChild; - const auto closedToken = firstChild ? _firstClosedToken : _secondClosedToken; - const auto typeChangedToken = firstChild ? _firstTypeChangedToken : _secondTypeChangedToken; + const auto child = isFirstChild ? _firstChild : _secondChild; + const auto closedToken = isFirstChild ? _firstClosedToken : _secondClosedToken; + const auto typeChangedToken = isFirstChild ? _firstTypeChangedToken : _secondTypeChangedToken; if (const auto childAsLeaf = std::dynamic_pointer_cast(child)) { @@ -174,6 +165,30 @@ void ParentPane::_RemoveAllChildEventHandlers(bool firstChild) } } +// Method Description: +// - Called when one of our children either: +// - Was a leaf pane and got splitted, in which case we replace him with the new parent +// pane that now holds him and his new neighbour, or +// - Was a parent pane and one of its children got closed, in which case we replace him +// with his remaining child, which we take over as our own +// Arguments: +// - isFirstChild - true if this applies to the first child, false for the second child +// - newChild - the pane to replace the specified child with +// Return Value: +// - +void ParentPane::_OnChildSplittedOrCollapsed(const bool isFirstChild, std::shared_ptr newChild) +{ + // Unsub from all the events of the parent child. + _RemoveAllChildEventHandlers(isFirstChild); + + (isFirstChild ? _firstChild : _secondChild) = newChild; + _root.Children().SetAt(isFirstChild ? 0 : 1, newChild->GetRootElement()); + _GetGridSetColOrRowFunc()(child->GetRootElement(), isFirstChild ? 0 : 1); + + // The child is now a LeafPane. Setup events appropriate for him. + _SetupChildEventHandlers(isFirstChild); +} + // Method Description: // - Returns an appropriate function to set a row or column on grid, depending on _splitState. // Arguments: @@ -499,7 +514,7 @@ void ParentPane::_CloseChild(const bool closeFirst) // On all the leaf descendants that were adjacent to the closed child, update its // border, so that it matches the border of the closed child. remainingChild->PropagateToLeavesOnEdge(closedChildDir, [=](LeafPane& paneOnEdge) { - paneOnEdge.UpdateBorderWithClosedNeightbour(closedChild, closedChildDir); + paneOnEdge.UpdateBorderWithClosedNeighbour(closedChild, closedChildDir); }); // When we invoke the ChildClosed event, our reference count might (and should) drop diff --git a/src/cascadia/TerminalApp/ParentPane.h b/src/cascadia/TerminalApp/ParentPane.h index da8d6dc877d..95210580ad1 100644 --- a/src/cascadia/TerminalApp/ParentPane.h +++ b/src/cascadia/TerminalApp/ParentPane.h @@ -45,8 +45,9 @@ class ParentPane : public Pane, public std::enable_shared_from_this winrt::event_token _secondClosedToken{ 0 }; winrt::event_token _secondTypeChangedToken{ 0 }; - void _SetupChildEventHandlers(bool firstChild); - void _RemoveAllChildEventHandlers(bool firstChild); + void _SetupChildEventHandlers(const bool firstChild); + void _RemoveAllChildEventHandlers(const bool firstChild); + void _OnChildSplittedOrCollapsed(const bool firstChild, std::shared_ptr newChild); void _CreateRowColDefinitions(const winrt::Windows::Foundation::Size& rootSize); std::function _GetGridSetColOrRowFunc() const noexcept; diff --git a/src/cascadia/TerminalApp/Tab.cpp b/src/cascadia/TerminalApp/Tab.cpp index bc0241ee236..16bdeab785f 100644 --- a/src/cascadia/TerminalApp/Tab.cpp +++ b/src/cascadia/TerminalApp/Tab.cpp @@ -372,32 +372,40 @@ namespace winrt::TerminalApp::implementation _AttachEventHandlersToControl(rootPaneAsLeaf->GetTerminalControl()); // When root pane closes, the tab also closes. - _rootPaneClosedToken = rootPaneAsLeaf->Closed([=](auto&& /*s*/, auto&& /*e*/) { - _RemoveAllRootPaneEventHandlers(); - - _ClosedHandlers(nullptr, nullptr); + _rootPaneClosedToken = rootPaneAsLeaf->Closed([weakThis = get_weak()](auto&& /*s*/, auto&& /*e*/) { + if (auto tab{ weakThis.get() }) + { + tab->_RemoveAllRootPaneEventHandlers(); + tab->_ClosedHandlers(nullptr, nullptr); + } }); // When root pane is a leaf and got splitted, it produces the new parent pane that contains // both him and the new leaf near him. We then replace that child with the new parent pane. - _rootPaneTypeChangedToken = rootPaneAsLeaf->Splitted([=](std::shared_ptr splittedPane) { - _RemoveAllRootPaneEventHandlers(); - - _rootPane = splittedPane; - _SetupRootPaneEventHandlers(); - _RootPaneChangedHandlers(); + _rootPaneTypeChangedToken = rootPaneAsLeaf->Splitted([weakThis = get_weak()](std::shared_ptr splittedPane) { + if (auto tab{ weakThis.get() }) + { + tab->_RemoveAllRootPaneEventHandlers(); + + tab->_rootPane = splittedPane; + tab->_SetupRootPaneEventHandlers(); + tab->_RootPaneChangedHandlers(); + } }); } else if (const auto rootPaneAsParent = std::dynamic_pointer_cast(_rootPane)) { // When root pane is a parent and one of its children got closed (and so the parent collapses), // we take in its remaining, orphaned child as our own. - _rootPaneTypeChangedToken = rootPaneAsParent->ChildClosed([=](std::shared_ptr collapsedPane) { - _RemoveAllRootPaneEventHandlers(); - - _rootPane = collapsedPane; - _SetupRootPaneEventHandlers(); - _RootPaneChangedHandlers(); + _rootPaneTypeChangedToken = rootPaneAsParent->ChildClosed([weakThis = get_weak()](std::shared_ptr collapsedPane) { + if (auto tab{ weakThis.get() }) + { + tab->_RemoveAllRootPaneEventHandlers(); + + tab->_rootPane = collapsedPane; + tab->_SetupRootPaneEventHandlers(); + tab->_RootPaneChangedHandlers(); + } }); } } diff --git a/src/cascadia/TerminalApp/TerminalPage.cpp b/src/cascadia/TerminalApp/TerminalPage.cpp index 4336455045b..5638023a2de 100644 --- a/src/cascadia/TerminalApp/TerminalPage.cpp +++ b/src/cascadia/TerminalApp/TerminalPage.cpp @@ -517,7 +517,7 @@ namespace winrt::TerminalApp::implementation // Add the new tab to the list of our tabs. auto newTabImpl = winrt::make_self(profileGuid, term); _tabs.Append(*newTabImpl); - newTabImpl->BindEventHandlers(); + //newTabImpl->BindEventHandlers(); // Hookup our event handlers to the new terminal _RegisterTerminalEvents(term, *newTabImpl); @@ -542,7 +542,7 @@ namespace winrt::TerminalApp::implementation } }); - newTabImpl->RootPaneChanged([weakTab, weakThis{ get_weak() }]() { + /* newTabImpl->RootPaneChanged([weakTab, weakThis{ get_weak() }]() { auto page{ weakThis.get() }; auto tab{ weakTab.get() }; @@ -551,7 +551,7 @@ namespace winrt::TerminalApp::implementation page->_tabContent.Children().Clear(); page->_tabContent.Children().Append(tab->GetRootElement()); } - }); + });*/ auto tabViewItem = newTabImpl->GetTabViewItem(); _tabView.TabItems().Append(tabViewItem); From 9ea9608a3ea3c9557a225007bf732fc1ac249466 Mon Sep 17 00:00:00 2001 From: MCpiroman Date: Sat, 7 Mar 2020 18:21:42 +0100 Subject: [PATCH 16/17] Code format --- src/cascadia/TerminalApp/LeafPane.cpp | 126 +++++++++++----------- src/cascadia/TerminalApp/LeafPane.h | 2 +- src/cascadia/TerminalApp/TerminalPage.cpp | 2 +- 3 files changed, 65 insertions(+), 65 deletions(-) diff --git a/src/cascadia/TerminalApp/LeafPane.cpp b/src/cascadia/TerminalApp/LeafPane.cpp index 756d5f4af3f..fcba936b08b 100644 --- a/src/cascadia/TerminalApp/LeafPane.cpp +++ b/src/cascadia/TerminalApp/LeafPane.cpp @@ -436,76 +436,76 @@ Pane::SnapSizeResult LeafPane::_CalcSnappedDimension(const bool widthOrHeight, c const auto minSize = _GetMinSize(); const auto minDimension = widthOrHeight ? minSize.Width : - minSize.Height; + minSize.Height; - if (dimension <= minDimension) - { - return { minDimension, minDimension }; - } + if (dimension <= minDimension) + { + return { minDimension, minDimension }; + } - float lower = _control.SnapDimensionToGrid(widthOrHeight, dimension); - if (widthOrHeight) - { - lower += WI_IsFlagSet(_borders, Borders::Left) ? PaneBorderSize : 0; - lower += WI_IsFlagSet(_borders, Borders::Right) ? PaneBorderSize : 0; - } - else - { - lower += WI_IsFlagSet(_borders, Borders::Top) ? PaneBorderSize : 0; - lower += WI_IsFlagSet(_borders, Borders::Bottom) ? PaneBorderSize : 0; - } + float lower = _control.SnapDimensionToGrid(widthOrHeight, dimension); + if (widthOrHeight) + { + lower += WI_IsFlagSet(_borders, Borders::Left) ? PaneBorderSize : 0; + lower += WI_IsFlagSet(_borders, Borders::Right) ? PaneBorderSize : 0; + } + else + { + lower += WI_IsFlagSet(_borders, Borders::Top) ? PaneBorderSize : 0; + lower += WI_IsFlagSet(_borders, Borders::Bottom) ? PaneBorderSize : 0; + } - if (lower == dimension) - { - // If we happen to be already snapped, then just return this size - // as both lower and higher values. - return { lower, lower }; - } - else - { - const auto cellSize = _control.CharacterDimensions(); - const auto higher = lower + (widthOrHeight ? cellSize.Width : cellSize.Height); - return { lower, higher }; - } - } + if (lower == dimension) + { + // If we happen to be already snapped, then just return this size + // as both lower and higher values. + return { lower, lower }; + } + else + { + const auto cellSize = _control.CharacterDimensions(); + const auto higher = lower + (widthOrHeight ? cellSize.Width : cellSize.Height); + return { lower, higher }; + } +} - void LeafPane::_AdvanceSnappedDimension(const bool widthOrHeight, LayoutSizeNode& sizeNode) const - { - // We're a leaf pane, so just add one more row or column (unless isMinimumSize - // is true, see below). +void LeafPane::_AdvanceSnappedDimension(const bool widthOrHeight, LayoutSizeNode& sizeNode) const +{ + // We're a leaf pane, so just add one more row or column (unless isMinimumSize + // is true, see below). - if (sizeNode.isMinimumSize) - { - // If the node is of its minimum size, this size might not be snapped (it might - // be, say, half a character, or fixed 10 pixels), so snap it upward. It might - // however be already snapped, so add 1 to make sure it really increases - // (not strictly necessary but to avoid surprises). - sizeNode.size = _CalcSnappedDimension(widthOrHeight, sizeNode.size + 1).higher; - } - else - { - const auto cellSize = _control.CharacterDimensions(); - sizeNode.size += widthOrHeight ? cellSize.Width : cellSize.Height; - } + if (sizeNode.isMinimumSize) + { + // If the node is of its minimum size, this size might not be snapped (it might + // be, say, half a character, or fixed 10 pixels), so snap it upward. It might + // however be already snapped, so add 1 to make sure it really increases + // (not strictly necessary but to avoid surprises). + sizeNode.size = _CalcSnappedDimension(widthOrHeight, sizeNode.size + 1).higher; + } + else + { + const auto cellSize = _control.CharacterDimensions(); + sizeNode.size += widthOrHeight ? cellSize.Width : cellSize.Height; + } - // Because we have grown, we're certainly no longer of our - // minimal size (if we've ever been). - sizeNode.isMinimumSize = false; - } + // Because we have grown, we're certainly no longer of our + // minimal size (if we've ever been). + sizeNode.isMinimumSize = false; +} - Size LeafPane::_GetMinSize() const - { - auto controlSize = _control.MinimumSize(); - auto newWidth = controlSize.Width; - auto newHeight = controlSize.Height; +Size LeafPane::_GetMinSize() const +{ + auto controlSize = _control.MinimumSize(); + auto newWidth = controlSize.Width; + auto newHeight = controlSize.Height; - newWidth += WI_IsFlagSet(_borders, Borders::Left) ? PaneBorderSize : 0; - newWidth += WI_IsFlagSet(_borders, Borders::Right) ? PaneBorderSize : 0; - newHeight += WI_IsFlagSet(_borders, Borders::Top) ? PaneBorderSize : 0; - newHeight += WI_IsFlagSet(_borders, Borders::Bottom) ? PaneBorderSize : 0; + newWidth += WI_IsFlagSet(_borders, Borders::Left) ? PaneBorderSize : 0; + newWidth += WI_IsFlagSet(_borders, Borders::Right) ? PaneBorderSize : 0; + newHeight += WI_IsFlagSet(_borders, Borders::Top) ? PaneBorderSize : 0; + newHeight += WI_IsFlagSet(_borders, Borders::Bottom) ? PaneBorderSize : 0; - return { newWidth, newHeight }; - } + return { newWidth, newHeight }; +} - DEFINE_EVENT(LeafPane, Splitted, _SplittedHandlers, winrt::delegate>); - DEFINE_EVENT(LeafPane, GotFocus, _GotFocusHandlers, winrt::delegate>); +DEFINE_EVENT(LeafPane, Splitted, _SplittedHandlers, winrt::delegate>); +DEFINE_EVENT(LeafPane, GotFocus, _GotFocusHandlers, winrt::delegate>); diff --git a/src/cascadia/TerminalApp/LeafPane.h b/src/cascadia/TerminalApp/LeafPane.h index 67ca140464a..7884ce9c08d 100644 --- a/src/cascadia/TerminalApp/LeafPane.h +++ b/src/cascadia/TerminalApp/LeafPane.h @@ -51,7 +51,7 @@ class LeafPane : public Pane, public std::enable_shared_from_this void ClearActive(); bool WasLastActive() const noexcept; void UpdateBorderWithClosedNeighbour(std::shared_ptr closedNeighbour, - const winrt::TerminalApp::Direction& neighbourDirection); + const winrt::TerminalApp::Direction& neighbourDirection); void Close(); void Shutdown(); diff --git a/src/cascadia/TerminalApp/TerminalPage.cpp b/src/cascadia/TerminalApp/TerminalPage.cpp index 5638023a2de..fc98744b51e 100644 --- a/src/cascadia/TerminalApp/TerminalPage.cpp +++ b/src/cascadia/TerminalApp/TerminalPage.cpp @@ -542,7 +542,7 @@ namespace winrt::TerminalApp::implementation } }); - /* newTabImpl->RootPaneChanged([weakTab, weakThis{ get_weak() }]() { + /* newTabImpl->RootPaneChanged([weakTab, weakThis{ get_weak() }]() { auto page{ weakThis.get() }; auto tab{ weakTab.get() }; From f2b6585413eb3607876294afc6705ca5e5362942 Mon Sep 17 00:00:00 2001 From: MCpiroman Date: Sat, 7 Mar 2020 20:30:00 +0100 Subject: [PATCH 17/17] Fix compilation --- src/cascadia/TerminalApp/ParentPane.cpp | 2 +- src/cascadia/TerminalApp/Tab.h | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/src/cascadia/TerminalApp/ParentPane.cpp b/src/cascadia/TerminalApp/ParentPane.cpp index fc631a32961..e361226b896 100644 --- a/src/cascadia/TerminalApp/ParentPane.cpp +++ b/src/cascadia/TerminalApp/ParentPane.cpp @@ -183,7 +183,7 @@ void ParentPane::_OnChildSplittedOrCollapsed(const bool isFirstChild, std::share (isFirstChild ? _firstChild : _secondChild) = newChild; _root.Children().SetAt(isFirstChild ? 0 : 1, newChild->GetRootElement()); - _GetGridSetColOrRowFunc()(child->GetRootElement(), isFirstChild ? 0 : 1); + _GetGridSetColOrRowFunc()(newChild->GetRootElement(), isFirstChild ? 0 : 1); // The child is now a LeafPane. Setup events appropriate for him. _SetupChildEventHandlers(isFirstChild); diff --git a/src/cascadia/TerminalApp/Tab.h b/src/cascadia/TerminalApp/Tab.h index dd12717b693..5b6975afb65 100644 --- a/src/cascadia/TerminalApp/Tab.h +++ b/src/cascadia/TerminalApp/Tab.h @@ -73,3 +73,4 @@ namespace winrt::TerminalApp::implementation void _AttachEventHandlersToControl(const winrt::Microsoft::Terminal::TerminalControl::TermControl& control); void _AttachEventHandlersToLeafPane(std::shared_ptr pane); }; +}