// Copyright (c) Microsoft Corporation. // Licensed under the MIT license. // // Module Name: // - Pane.h // // Abstract: // - 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 // time. Panes, on the other hand, allow a user to have many different // terminal sessions visible to the user within the context of a single window // at the same time. This can enable greater productivity from the user, as // they can see the output of one terminal window while working in another. // - See doc/cascadia/Panes.md for a detailed description. // // Author: // - Mike Griese (zadjii-msft) 16-May-2019 #pragma once #include "../../cascadia/inc/cppwinrt_utils.h" #include "TaskbarState.h" // fwdecl unittest classes namespace TerminalAppLocalTests { class TabTests; }; namespace winrt::TerminalApp::implementation { struct TerminalTab; } enum class Borders : int { None = 0x0, Top = 0x1, Bottom = 0x2, Left = 0x4, Right = 0x8, All = 0xF }; DEFINE_ENUM_FLAG_OPERATORS(Borders); enum class SplitState : int { None = 0, Horizontal = 1, Vertical = 2 }; class Pane : public std::enable_shared_from_this { public: Pane(const winrt::Microsoft::Terminal::Settings::Model::Profile& profile, const winrt::Microsoft::Terminal::Control::TermControl& control, const bool lastFocused = false); Pane(std::shared_ptr first, std::shared_ptr second, const SplitState splitType, const float splitPosition, const bool lastFocused = false); std::shared_ptr GetActivePane(); winrt::Microsoft::Terminal::Control::TermControl GetLastFocusedTerminalControl(); winrt::Microsoft::Terminal::Control::TermControl GetTerminalControl(); winrt::Microsoft::Terminal::Settings::Model::Profile GetFocusedProfile(); // Method Description: // - If this is a leaf pane, return its profile. // - If this is a branch/root pane, return nullptr. winrt::Microsoft::Terminal::Settings::Model::Profile GetProfile() const { return _profile; } winrt::Windows::UI::Xaml::Controls::Grid GetRootElement(); bool WasLastFocused() const noexcept; void UpdateVisuals(); void ClearActive(); void SetActive(); struct BuildStartupState { std::vector args; std::shared_ptr firstPane; std::optional focusedPaneId; uint32_t panesCreated; }; BuildStartupState BuildStartupActions(uint32_t currentId, uint32_t nextId); winrt::Microsoft::Terminal::Settings::Model::NewTerminalArgs GetTerminalArgsForPane() const; void UpdateSettings(const winrt::Microsoft::Terminal::Settings::Model::TerminalSettingsCreateResult& settings, const winrt::Microsoft::Terminal::Settings::Model::Profile& profile); void ResizeContent(const winrt::Windows::Foundation::Size& newSize); void Relayout(); bool ResizePane(const winrt::Microsoft::Terminal::Settings::Model::ResizeDirection& direction); std::shared_ptr NavigateDirection(const std::shared_ptr sourcePane, const winrt::Microsoft::Terminal::Settings::Model::FocusDirection& direction, const std::vector& mruPanes); bool SwapPanes(std::shared_ptr first, std::shared_ptr second); std::shared_ptr NextPane(const std::shared_ptr pane); std::shared_ptr PreviousPane(const std::shared_ptr pane); std::pair, std::shared_ptr> Split(winrt::Microsoft::Terminal::Settings::Model::SplitDirection splitType, const float splitSize, const winrt::Microsoft::Terminal::Settings::Model::Profile& profile, const winrt::Microsoft::Terminal::Control::TermControl& control); bool ToggleSplitOrientation(); float CalcSnappedDimension(const bool widthOrHeight, const float dimension) const; std::optional PreCalculateAutoSplit(const std::shared_ptr target, const winrt::Windows::Foundation::Size parentSize) const; std::optional PreCalculateCanSplit(const std::shared_ptr target, winrt::Microsoft::Terminal::Settings::Model::SplitDirection splitType, const float splitSize, const winrt::Windows::Foundation::Size availableSpace) const; void Shutdown(); void Close(); std::shared_ptr AttachPane(std::shared_ptr pane, winrt::Microsoft::Terminal::Settings::Model::SplitDirection splitType); std::shared_ptr DetachPane(std::shared_ptr pane); int GetLeafPaneCount() const noexcept; void Maximize(std::shared_ptr zoomedPane); void Restore(std::shared_ptr zoomedPane); std::optional Id() noexcept; void Id(uint32_t id) noexcept; bool FocusPane(const uint32_t id); bool FocusPane(const std::shared_ptr pane); std::shared_ptr FindPane(const uint32_t id); bool ContainsReadOnly() const; // Method Description: // - A helper method for ad-hoc recursion on a pane tree. Walks the pane // tree, calling a predicate on each pane in a depth-first pattern. // - If the predicate returns true, recursion is stopped early. // Arguments: // - f: The function to be applied to each pane. // Return Value: // - true if the predicate returned true on any pane. template //requires std::predicate> auto WalkTree(F f) -> decltype(f(shared_from_this())) { using R = std::invoke_result_t>; static constexpr auto IsVoid = std::is_void_v; if constexpr (IsVoid) { f(shared_from_this()); if (!_IsLeaf()) { _firstChild->WalkTree(f); _secondChild->WalkTree(f); } } else { if (f(shared_from_this())) { return true; } if (!_IsLeaf()) { return _firstChild->WalkTree(f) || _secondChild->WalkTree(f); } return false; } } void CollectTaskbarStates(std::vector& states); WINRT_CALLBACK(Closed, winrt::Windows::Foundation::EventHandler); using gotFocusArgs = winrt::delegate, winrt::Windows::UI::Xaml::FocusState>; DECLARE_EVENT(GotFocus, _GotFocusHandlers, gotFocusArgs); DECLARE_EVENT(LostFocus, _LostFocusHandlers, winrt::delegate>); DECLARE_EVENT(PaneRaiseBell, _PaneRaiseBellHandlers, winrt::Windows::Foundation::EventHandler); DECLARE_EVENT(Detached, _PaneDetachedHandlers, winrt::delegate>); private: struct PanePoint; struct PaneNeighborSearch; struct SnapSizeResult; struct SnapChildrenSizeResult; struct LayoutSizeNode; winrt::Windows::UI::Xaml::Controls::Grid _root{}; winrt::Windows::UI::Xaml::Controls::Border _borderFirst{}; winrt::Windows::UI::Xaml::Controls::Border _borderSecond{}; winrt::Microsoft::Terminal::Control::TermControl _control{ nullptr }; winrt::Microsoft::Terminal::TerminalConnection::ConnectionState _connectionState{ winrt::Microsoft::Terminal::TerminalConnection::ConnectionState::NotConnected }; 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 }; SplitState _splitState{ SplitState::None }; float _desiredSplitPosition; std::optional _id; std::weak_ptr _parentChildPath{}; bool _lastActive{ false }; winrt::Microsoft::Terminal::Settings::Model::Profile _profile{ nullptr }; winrt::event_token _connectionStateChangedToken{ 0 }; winrt::event_token _firstClosedToken{ 0 }; winrt::event_token _secondClosedToken{ 0 }; winrt::event_token _warningBellToken{ 0 }; winrt::Windows::UI::Xaml::UIElement::GotFocus_revoker _gotFocusRevoker; winrt::Windows::UI::Xaml::UIElement::LostFocus_revoker _lostFocusRevoker; std::shared_mutex _createCloseLock{}; Borders _borders{ Borders::None }; bool _zoomed{ false }; bool _IsLeaf() const noexcept; bool _HasFocusedChild() const noexcept; void _SetupChildCloseHandlers(); bool _HasChild(const std::shared_ptr child); std::pair, std::shared_ptr> _Split(winrt::Microsoft::Terminal::Settings::Model::SplitDirection splitType, const float splitSize, std::shared_ptr newPane); void _CreateRowColDefinitions(); void _ApplySplitDefinitions(); void _SetupEntranceAnimation(); void _UpdateBorders(); Borders _GetCommonBorders(); bool _Resize(const winrt::Microsoft::Terminal::Settings::Model::ResizeDirection& direction); std::shared_ptr _FindParentOfPane(const std::shared_ptr pane); std::pair _GetOffsetsForPane(const PanePoint parentOffset) const; bool _IsAdjacent(const PanePoint firstOffset, const PanePoint secondOffset, const winrt::Microsoft::Terminal::Settings::Model::FocusDirection& direction) const; PaneNeighborSearch _FindNeighborForPane(const winrt::Microsoft::Terminal::Settings::Model::FocusDirection& direction, PaneNeighborSearch searchResult, const bool focusIsSecondSide, const PanePoint offset); PaneNeighborSearch _FindPaneAndNeighbor(const std::shared_ptr sourcePane, const winrt::Microsoft::Terminal::Settings::Model::FocusDirection& direction, const PanePoint offset); void _CloseChild(const bool closeFirst, const bool isDetaching); winrt::fire_and_forget _CloseChildRoutine(const bool closeFirst); void _Focus(); void _FocusFirstChild(); void _ControlConnectionStateChangedHandler(const winrt::Windows::Foundation::IInspectable& sender, const winrt::Windows::Foundation::IInspectable& /*args*/); void _ControlWarningBellHandler(winrt::Windows::Foundation::IInspectable const& sender, winrt::Windows::Foundation::IInspectable const& e); void _ControlGotFocusHandler(winrt::Windows::Foundation::IInspectable const& sender, winrt::Windows::UI::Xaml::RoutedEventArgs const& e); void _ControlLostFocusHandler(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; SplitState _convertAutomaticOrDirectionalSplitState(const winrt::Microsoft::Terminal::Settings::Model::SplitDirection& splitType) const; std::optional _preCalculateAutoSplit(const std::shared_ptr target, const winrt::Windows::Foundation::Size parentSize) 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). // - Also used for moving focus between panes, which again happens _across_ a separator. // Arguments: // - direction: The Direction to compare // - splitType: The SplitState to compare // Return Value: // - true iff the direction is perpendicular to the splitType. False for // SplitState::None. template static constexpr bool DirectionMatchesSplit(const T& direction, const SplitState& splitType) { if (splitType == SplitState::None) { return false; } else if (splitType == SplitState::Horizontal) { return direction == T::Up || direction == T::Down; } else if (splitType == SplitState::Vertical) { return direction == T::Left || direction == T::Right; } return false; } static void _SetupResources(); struct PanePoint { float x; float y; float scaleX; float scaleY; }; struct PaneNeighborSearch { std::shared_ptr source; std::shared_ptr neighbor; PanePoint sourceOffset; }; struct SnapSizeResult { float lower; float higher; }; struct SnapChildrenSizeResult { std::pair lower; std::pair higher; }; // Helper structure that builds a (roughly) binary tree corresponding // to the pane tree. Used for laying out panes with snapped sizes. struct LayoutSizeNode { float size; bool isMinimumSize; std::unique_ptr firstChild; std::unique_ptr secondChild; // These two fields hold next possible snapped values of firstChild and // secondChild. Although that could be calculated from these fields themselves, // it would be wasteful as we have to know these values more often than for // simple increment. Hence we cache that here. std::unique_ptr nextFirstChild; std::unique_ptr nextSecondChild; explicit LayoutSizeNode(const float minSize); LayoutSizeNode(const LayoutSizeNode& other); LayoutSizeNode& operator=(const LayoutSizeNode& other); private: void _AssignChildNode(std::unique_ptr& nodeField, const LayoutSizeNode* const newNode); }; friend struct winrt::TerminalApp::implementation::TerminalTab; friend class ::TerminalAppLocalTests::TabTests; };