Compare commits

...

12 Commits

Author SHA1 Message Date
Mike Griese
e83b69616d A bunch of changes for #3181 that helped me really understand the code more 2019-11-05 10:08:46 -06:00
MCpiroman
b00fef800e Fix rebase 2019-10-18 20:00:46 +02:00
MCpiroman
583541d976 Review changes 2019-10-18 19:23:48 +02:00
MCpiroman
3dcd00f292 Style, comments, format; fix layout just after split 2019-10-18 19:23:48 +02:00
MCpiroman
0ecbec551c Fix minimum pane size calculation 2019-10-18 19:19:00 +02:00
MCpiroman
b732e745b5 (drastically) optimize layout algorithm ( O(n^m) -> O(n) ) 2019-10-18 19:19:00 +02:00
MCpiroman
d69f8ed403 Cache padding and scrollbar width, fix infinite loop 2019-10-18 19:19:00 +02:00
MCpiroman
cbda9118da Change child layout algorithm, now 100% correct 2019-10-18 19:18:59 +02:00
MCpiroman
90649b2bdf Partially fix child displacement 2019-10-18 19:18:59 +02:00
MCpiroman
5d0f1d3eca General work, add resolution for children along resize axis 2019-10-18 19:18:59 +02:00
MCpiroman
68f613b7c4 Calc sizes more accurately 2019-10-18 19:18:00 +02:00
MCpiroman
b8a5725a8e Kinda works, no splitted panes 2019-10-18 18:51:03 +02:00
17 changed files with 697 additions and 108 deletions

View File

@@ -397,6 +397,13 @@ namespace winrt::TerminalApp::implementation
return point;
}
// Method Description:
// - See Pane::SnapDimension
float App::SnapDimension(const bool widthOrHeight, const float dimension) const
{
return _root->SnapDimension(widthOrHeight, dimension);
}
bool App::GetShowTabsInTitlebar()
{
if (!_loadedInitialSettings)

View File

@@ -33,6 +33,7 @@ namespace winrt::TerminalApp::implementation
Windows::Foundation::Point GetLaunchDimensions(uint32_t dpi);
winrt::Windows::Foundation::Point GetLaunchInitialPositions(int32_t defaultInitialX, int32_t defaultInitialY);
LaunchMode GetLaunchMode();
float SnapDimension(const bool widthOrHeight, const float dimension) const;
bool GetShowTabsInTitlebar();
Windows::UI::Xaml::UIElement GetRoot() noexcept;

View File

@@ -32,6 +32,7 @@ namespace TerminalApp
Windows.Foundation.Point GetLaunchDimensions(UInt32 dpi);
Windows.Foundation.Point GetLaunchInitialPositions(Int32 defaultInitialX, Int32 defaultInitialY);
LaunchMode GetLaunchMode();
Single SnapDimension(Boolean widthOrHeight, Single dimension);
Boolean GetShowTabsInTitlebar();
void TitlebarClicked();
void WindowCloseButtonClicked();

View File

@@ -14,13 +14,16 @@ using namespace winrt::TerminalApp;
static const int PaneSeparatorSize = 4;
static const float Half = 0.50f;
Pane::Pane(const GUID& profile, const TermControl& control, const bool lastFocused) :
Pane::Pane(const GUID& profile, const TermControl& control, Pane* const rootPane, const bool lastFocused) :
_control{ control },
_lastFocused{ lastFocused },
_profile{ profile }
{
_rootPane = rootPane ? rootPane : this;
_root.Children().Append(_control);
_connectionClosedToken = _control.ConnectionClosed({ this, &Pane::_ControlClosedHandler });
_fontSizeChangedToken = _control.FontSizeChanged({ this, &Pane::_FontSizeChangedHandler });
// Set the background of the pane to match that of the theme's default grid
// background. This way, we'll match the small underline under the tabs, and
@@ -107,26 +110,9 @@ bool Pane::_Resize(const Direction& direction)
gsl::narrow_cast<float>(_root.ActualHeight()) };
// actualDimension is the size in DIPs of this pane in the direction we're
// resizing.
auto actualDimension = changeWidth ? actualSize.Width : actualSize.Height;
actualDimension -= PaneSeparatorSize;
const auto actualDimension = changeWidth ? actualSize.Width : actualSize.Height;
const auto firstMinSize = _firstChild->_GetMinSize();
const auto secondMinSize = _secondChild->_GetMinSize();
// These are the minimum amount of space we need for each of our children
const auto firstMinDimension = changeWidth ? firstMinSize.Width : firstMinSize.Height;
const auto secondMinDimension = changeWidth ? secondMinSize.Width : secondMinSize.Height;
const auto firstMinPercent = firstMinDimension / actualDimension;
const auto secondMinPercent = secondMinDimension / actualDimension;
// Make sure that the first pane doesn't get bigger than the space we need
// to reserve for the second.
const auto firstMaxPercent = 1.0f - secondMinPercent;
_firstPercent = std::clamp(_firstPercent.value() - amount, firstMinPercent, firstMaxPercent);
// Update the other child to fill the remaining percent
_secondPercent = 1.0f - _firstPercent.value();
_desiredSplitPosition = _ClampSplitPosition(changeWidth, _desiredSplitPosition - amount, actualDimension);
// Resize our columns to match the new percentages.
ResizeContent(actualSize);
@@ -308,6 +294,26 @@ void Pane::_ControlClosedHandler()
}
}
// Method Description:
// - Called when our terminal changes its font size or sets it for the first time
// (because when we just create terminal via its ctor it has invalid font size).
// On the latter event, we tell the root pane to resize itself so that its
// descendants (including ourself) can properly snap to character grids. In future,
// we may also want to do that on regular font changes.
// Arguments:
// - fontWidth - new font width in pixels
// - fontHeight - new font height in pixels
// - isInitialChange - whether terminal just got its proper font size.
// Return Value:
// - <none>
void Pane::_FontSizeChangedHandler(const int /* fontWidth */, const int /* fontHeight */, const bool isInitialChange)
{
if (isInitialChange)
{
_rootPane->ResizeContent(_rootPane->_root.ActualSize());
}
}
// Method Description:
// - Fire our Closed event to tell our parent that we should be removed.
// Arguments:
@@ -316,6 +322,9 @@ void Pane::_ControlClosedHandler()
// - <none>
void Pane::Close()
{
_control.FontSizeChanged(_fontSizeChangedToken);
_fontSizeChangedToken.value = 0;
// Fire our Closed event to tell our parent that we should be removed.
_closedHandlers();
}
@@ -670,7 +679,7 @@ void Pane::_SetupChildCloseHandlers()
// 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 _firstPercent and _secondPercent.
// 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.)
@@ -881,12 +890,11 @@ void Pane::_Split(SplitState splitType, const GUID& profile, const TermControl&
_control.ConnectionClosed(_connectionClosedToken);
_connectionClosedToken.value = 0;
_control.FontSizeChanged(_fontSizeChangedToken);
_fontSizeChangedToken.value = 0;
_splitState = splitType;
_firstPercent = { Half };
_secondPercent = { Half };
_CreateSplitContent();
_desiredSplitPosition = Half;
// Remove any children we currently have. We can't add the existing
// TermControl to a new grid until we do this.
@@ -895,10 +903,12 @@ void Pane::_Split(SplitState splitType, const GUID& profile, const TermControl&
// Create two new Panes
// Move our control, guid into the first one.
// Move the new guid, control into the second.
_firstChild = std::make_shared<Pane>(_profile.value(), _control);
_firstChild = std::make_shared<Pane>(_profile.value(), _control, _rootPane);
_profile = std::nullopt;
_control = { nullptr };
_secondChild = std::make_shared<Pane>(profile, control);
_secondChild = std::make_shared<Pane>(profile, control, _rootPane);
_CreateSplitContent();
_root.Children().Append(_firstChild->GetRootElement());
_root.Children().Append(_separatorRoot);
@@ -914,25 +924,272 @@ void Pane::_Split(SplitState splitType, const GUID& profile, const TermControl&
// Method Description:
// - Gets the size in pixels of each of our children, given the full size they
// should fill. Accounts for the size of the separator that should be between
// them as well.
// should fill. If specified size is lower than required then children will be
// of minimum size. Snaps first child to grid but not the second. Accounts for
// the size of the separator that should be between them as well.
// Arguments:
// - fullSize: the amount of space in pixels that should be filled by our
// children and their separator
// children and their separator. 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<float, float> Pane::_GetPaneSizes(const float& fullSize)
std::pair<float, float> Pane::_GetPaneSizes(const float fullSize) const
{
const auto snapToWidth = _splitState == SplitState::Vertical;
const auto snappedSizes = _CalcSnappedPaneDimensions(snapToWidth, fullSize, nullptr);
// Keep the first pane snapped and give the second pane all remaining size
return {
snappedSizes.first.lower,
fullSize - PaneSeparatorSize - snappedSizes.first.lower
};
}
// Method Description:
// - Gets the size in pixels of each of our children, given the full size they should
// fill. Each is snapped to char grid. If called multiple times with fullSize
// argument growing, then both returned sizes are guaranteed to be non-decreasing.
// This is important so that user doesn't get any pane shrinked when they actually
// increase the window/parent pane size. That's also required by the layout algorithm.
// Arguments:
// - snapToWidth: 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.
// - next: if not null, it will be assigned the next possible snapped sizes (see
// 'Return value' below), unless the children fit fullSize without any remaining space,
// in which case it is equal to returned value.
// Return Value:
// - a pair with the size of our first child and the size of our second child,
// respectively. Since they are snapped to grid, their sum might be (and usually is)
// lower than the specified full size.
ChildrenSnapBounds Pane::_CalcSnappedPaneDimensions(const bool snapToWidth,
const float fullSize /*,
std::pair<float, float>* next*/
) const
{
if (_IsLeaf())
{
THROW_HR(E_FAIL);
}
const auto sizeMinusSeparator = fullSize - PaneSeparatorSize;
const auto firstSize = sizeMinusSeparator * _firstPercent.value();
const auto secondSize = sizeMinusSeparator * _secondPercent.value();
return { firstSize, secondSize };
auto sizeTree = _GetMinSizeTree(snapToWidth);
LayoutSizeNode lastSizeTree{ sizeTree };
// MG: Continually attempt to snap our children upwards, until we find a
// size larger than the given size. This will let us find the nearest snap
// size both up and downwards for the given size.
while (sizeTree.size < fullSize)
{
lastSizeTree = sizeTree;
_SnapSizeUpwards(snapToWidth, sizeTree);
// MG: If by snapping upwards we exactly match the given size, then
// great! return that pair of sizes as both the lower and upper bound.
if (sizeTree.size == fullSize)
{
ChildrenSnapBounds bounds;
bounds.first{ sizeTree.firstChild->size, sizeTree.firstChild->size };
bounds.second{ sizeTree.secondChild->size, sizeTree.secondChild->size };
return bounds;
}
}
// MG: We're out of the loop. lastSizeTree has the size before the snap that
// would take us to a size larger than the given size, and sizeTree has the
// size of the sanp above the given size. Return these values.
ChildrenSnapBounds bounds;
bounds.first{ lastSizeTree.firstChild->size, sizeTree.firstChild->size };
bounds.second{ lastSizeTree.secondChild->size, sizeTree.secondChild->size };
return bounds;
}
// 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
// (either upward or downward). Also makes sure to fit in minimal sizes of the panes.
// Arguments:
// - snapToWidth: if true operates on width, otherwise on height
// - dimension: a dimension (width or height) to snap
// Return Value:
// - calculated dimension
float Pane::SnapDimension(const bool snapToWidth, const float dimension) const
{
const auto snapPossibilites = _GetProposedSnapSizes(snapToWidth, dimension);
const auto lower = snapPossibilites.lower;
const auto higher = snapPossibilites.higher;
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:
// - snapToWidth: 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.
SnapSizeBounds Pane::_GetProposedSnapSizes(const bool snapToWidth, const float dimension) const
{
if (_IsLeaf())
{
// If we're a leaf pane, alight to the grid of controlling terminal
const auto minSize = _GetMinSize();
const auto minDimension = snapToWidth ? minSize.Width : minSize.Height;
// MG: If the proposed size is smaller than our minimum size, just return our min size. We can't snpa smaller.
if (dimension <= minDimension)
{
return { minDimension, minDimension };
}
// MG: Ask our control what it would snap to for this size. This is always downwards.
const float lower = _control.SnapDimensionToGrid(snapToWidth, dimension);
// MG: If it would exactly snap downwards to the proposed size, great! Just return that size.
if (lower == dimension)
{
return { lower, lower };
}
else
{
// MG: Otherwise, calculate the next upwards snap size by adding one
// character to the control's "snap down" size. This will give us
// the next upwards snap size.
const auto cellSize = _control.CharacterDimensions();
const auto higher = lower + (snapToWidth ? cellSize.Width : cellSize.Height);
return { lower, higher };
}
}
else if (SnapDirectionIsParallelToSplit(snapToWidth, _splitState))
{
// If we're resizes along separator axis, snap to the closes possibility
// given by our children panes.
const auto firstSnapped = _firstChild->_GetProposedSnapSizes(snapToWidth, dimension);
const auto secondSnapped = _secondChild->_GetProposedSnapSizes(snapToWidth, dimension);
return {
std::max(firstSnapped.lower, secondSnapped.lower),
std::min(firstSnapped.higher, secondSnapped.higher)
};
}
else
{
// If we're resizes 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 bounds = _CalcSnappedPaneDimensions(snapToWidth, dimension);
return {
bounds.first.lower + PaneSeparatorSize + bounds.second.lower,
bounds.first.higher + PaneSeparatorSize + bounds.second.higher
};
}
}
// 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:
// - snapToWidth: if true operates on width, otherwise on height.
// - sizeNode: a layouting node that corresponds to this pane.
// Return Value:
// - <none>
void Pane::_SnapSizeUpwards(const bool snapToWidth, LayoutSizeNode& sizeNode) const
{
if (_IsLeaf())
{
if (sizeNode.isMinimumSize)
{
// If the node is of its minimum size, this size might not be snapped,
// so snap it upward. It might however be snapped, so add 1 to make
// sure it really increases (not really required but to avoid surprises).
sizeNode.size = _GetProposedSnapSizes(snapToWidth, sizeNode.size + 1).higher;
}
else
{
const auto cellSize = _control.CharacterDimensions();
sizeNode.size += snapToWidth ? cellSize.Width : cellSize.Height;
}
}
else
{
// The given node often has next possible (advanced) values already
// cached by the previous advance operation. If we're the first one,
// we need to calculate them now.
if (sizeNode.nextFirstChild == nullptr)
{
sizeNode.nextFirstChild.reset(new LayoutSizeNode(*sizeNode.firstChild));
_firstChild->_SnapSizeUpwards(snapToWidth, *sizeNode.nextFirstChild);
}
if (sizeNode.nextSecondChild == nullptr)
{
sizeNode.nextSecondChild.reset(new LayoutSizeNode(*sizeNode.secondChild));
_secondChild->_SnapSizeUpwards(snapToWidth, *sizeNode.nextSecondChild);
}
const auto nextFirstSize = sizeNode.nextFirstChild->size;
const auto nextSecondSize = sizeNode.nextSecondChild->size;
bool advanceFirst; // Whether to advance first or second child
if (SnapDirectionIsParallelToSplit(snapToWidth, _splitState))
{
// If we're growing along separator axis, choose the child that
// wants to be smaller than the other.
advanceFirst = nextFirstSize < nextSecondSize;
}
else
{
// If we're growing perpendicularly to separator axis, choose
// the child so that their size ratio is closer to the currently
// maintained (so that the relative separator position is closer
// to the _desiredSplitPosition field).
const auto firstSize = sizeNode.firstChild->size;
const auto secondSize = sizeNode.secondChild->size;
// Because we relay on equality check these calculations have to be
// immune to floating point errors.
const auto deviation1 = nextFirstSize - (nextFirstSize + secondSize) * _desiredSplitPosition;
const auto deviation2 = -1 * (firstSize - (firstSize + nextSecondSize) * _desiredSplitPosition);
advanceFirst = deviation1 <= deviation2;
}
// MG: Here, we're taking the value from the child we decided to snap
// upwards on, and calculating a new upwards snap size for that child?
// MG: I'm very unsure.
if (advanceFirst)
{
*sizeNode.firstChild = *sizeNode.nextFirstChild;
_firstChild->_SnapSizeUpwards(snapToWidth, *sizeNode.nextFirstChild);
}
else
{
*sizeNode.secondChild = *sizeNode.nextSecondChild;
_secondChild->_SnapSizeUpwards(snapToWidth, *sizeNode.nextSecondChild);
}
// MG: If we're resizing parallel to the split, then our new size is the
// size from the largest child. If we're resizing perpendicularly, then
// our new size is the sum of the sizes of our children, plus the size
// of the separator.
if (SnapDirectionIsParallelToSplit(snapToWidth, _splitState))
{
sizeNode.size = std::max(sizeNode.firstChild->size, sizeNode.secondChild->size);
}
else
{
sizeNode.size = sizeNode.firstChild->size + PaneSeparatorSize + sizeNode.secondChild->size;
}
}
sizeNode.isMinimumSize = false;
}
// Method Description:
@@ -953,9 +1210,126 @@ Size Pane::_GetMinSize() const
const auto firstSize = _firstChild->_GetMinSize();
const auto secondSize = _secondChild->_GetMinSize();
const auto newWidth = firstSize.Width + secondSize.Width + (_splitState == SplitState::Vertical ? PaneSeparatorSize : 0);
const auto newHeight = firstSize.Height + secondSize.Height + (_splitState == SplitState::Horizontal ? PaneSeparatorSize : 0);
return { newWidth, newHeight };
const auto minWidth = _splitState == SplitState::Vertical ?
firstSize.Width + PaneSeparatorSize + secondSize.Width :
std::max(firstSize.Width, secondSize.Width);
const auto minHeight = _splitState == SplitState::Horizontal ?
firstSize.Height + PaneSeparatorSize + 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.
// Arguments:
// - snapToWidth: if true operates on width, otherwise on height
// Return Value:
// - Root node of built tree that matches this pane.
Pane::LayoutSizeNode Pane::_GetMinSizeTree(const bool snapToWidth) const
{
const auto size = _GetMinSize();
LayoutSizeNode node(snapToWidth ? size.Width : size.Height);
if (!_IsLeaf())
{
node.firstChild.reset(new LayoutSizeNode(_firstChild->_GetMinSizeTree(snapToWidth)));
node.secondChild.reset(new LayoutSizeNode(_secondChild->_GetMinSizeTree(snapToWidth)));
}
return node;
}
// Method Description:
// - Adjusts split position so that no child pane is smaller then its
// minimum size
// Arguments:
// - snapToWidth: 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 snapToWidth, const float requestedValue, const float totalSize) const
{
const auto firstMinSize = _firstChild->_GetMinSize();
const auto secondMinSize = _secondChild->_GetMinSize();
const auto firstMinDimension = snapToWidth ? firstMinSize.Width : firstMinSize.Height;
const auto secondMinDimension = snapToWidth ? secondMinSize.Width : secondMinSize.Height;
const auto minSplitPosition = firstMinDimension / (totalSize - PaneSeparatorSize);
const auto maxSplitPosition = 1.0f - (secondMinDimension / (totalSize - PaneSeparatorSize));
return std::clamp(requestedValue, minSplitPosition, maxSplitPosition);
}
DEFINE_EVENT(Pane, Closed, _closedHandlers, ConnectionClosedEventArgs);
Pane::LayoutSizeNode::LayoutSizeNode(const float minSize) :
size{ minSize },
isMinimumSize{ true },
firstChild{ nullptr },
secondChild{ nullptr },
nextFirstChild{ nullptr },
nextSecondChild{ nullptr }
{
}
Pane::LayoutSizeNode::LayoutSizeNode(const LayoutSizeNode& other) :
size{ other.size },
isMinimumSize{ other.isMinimumSize },
firstChild{ other.firstChild ? std::make_unique<LayoutSizeNode>(*other.firstChild) : nullptr },
secondChild{ other.secondChild ? std::make_unique<LayoutSizeNode>(*other.secondChild) : nullptr },
nextFirstChild{ other.nextFirstChild ? std::make_unique<LayoutSizeNode>(*other.nextFirstChild) : nullptr },
nextSecondChild{ other.nextSecondChild ? std::make_unique<LayoutSizeNode>(*other.nextSecondChild) : nullptr }
{
}
// Method Description:
// - Makes sure that this node and all its descendants equal the supplied node.
// This may be more efficient that copy construction since it will reuse its
// allocated children.
// Arguments:
// - other: Node to take the values from.
// Return Value:
// - itself
Pane::LayoutSizeNode& Pane::LayoutSizeNode::operator=(const LayoutSizeNode& other)
{
size = other.size;
isMinimumSize = other.isMinimumSize;
_AssignChildNode(firstChild, other.firstChild.get());
_AssignChildNode(secondChild, other.secondChild.get());
_AssignChildNode(nextFirstChild, other.nextFirstChild.get());
_AssignChildNode(nextSecondChild, other.nextSecondChild.get());
return *this;
}
// Method Description:
// - Performs assignment operation on a single child node reusing
// - current one if present.
// Arguments:
// - nodeField: Reference to our field holding concerned node.
// - other: Node to take the values from.
// Return Value:
// - <none>
void Pane::LayoutSizeNode::_AssignChildNode(std::unique_ptr<LayoutSizeNode>& nodeField, const LayoutSizeNode* const newNode)
{
if (newNode)
{
if (nodeField)
{
*nodeField = *newNode;
}
else
{
nodeField.reset(new LayoutSizeNode(*newNode));
}
}
else
{
nodeField.release();
}
}

View File

@@ -33,7 +33,7 @@ public:
Horizontal = 2
};
Pane(const GUID& profile, const winrt::Microsoft::Terminal::TerminalControl::TermControl& control, const bool lastFocused = false);
Pane(const GUID& profile, const winrt::Microsoft::Terminal::TerminalControl::TermControl& control, Pane* const rootPane, const bool lastFocused = false);
std::shared_ptr<Pane> GetFocusedPane();
winrt::Microsoft::Terminal::TerminalControl::TermControl GetFocusedTerminalControl();
@@ -51,27 +51,31 @@ public:
bool CanSplit(SplitState splitType);
void Split(SplitState splitType, const GUID& profile, const winrt::Microsoft::Terminal::TerminalControl::TermControl& control);
float SnapDimension(const bool widthOrHeight, const float dimension) const;
void Close();
DECLARE_EVENT(Closed, _closedHandlers, winrt::Microsoft::Terminal::TerminalControl::ConnectionClosedEventArgs);
private:
struct LayoutSizeNode;
winrt::Windows::UI::Xaml::Controls::Grid _root{};
winrt::Windows::UI::Xaml::Controls::Grid _separatorRoot{ nullptr };
winrt::Microsoft::Terminal::TerminalControl::TermControl _control{ nullptr };
Pane* _rootPane;
std::shared_ptr<Pane> _firstChild{ nullptr };
std::shared_ptr<Pane> _secondChild{ nullptr };
SplitState _splitState{ SplitState::None };
std::optional<float> _firstPercent{ std::nullopt };
std::optional<float> _secondPercent{ std::nullopt };
float _desiredSplitPosition;
bool _lastFocused{ false };
std::optional<GUID> _profile{ std::nullopt };
winrt::event_token _connectionClosedToken{ 0 };
winrt::event_token _firstClosedToken{ 0 };
winrt::event_token _secondClosedToken{ 0 };
winrt::event_token _fontSizeChangedToken{ 0 };
std::shared_mutex _createCloseLock{};
@@ -92,10 +96,16 @@ private:
void _FocusFirstChild();
void _ControlClosedHandler();
void _FontSizeChangedHandler(const int fontWidth, const int fontHeight, const bool isInitialChange);
std::pair<float, float> _GetPaneSizes(const float& fullSize);
std::pair<float, float> _GetPaneSizes(const float fullSize) const;
ChildrenSnapBounds _CalcSnappedPaneDimensions(const bool snapToWidth, const float fullSize) const;
SnapSizeBounds _GetProposedSnapSizes(const bool snapToWidth, const float dimension) const;
void _SnapSizeUpwards(const bool snapToWidth, LayoutSizeNode& sizeNode) const;
winrt::Windows::Foundation::Size _GetMinSize() const;
LayoutSizeNode _GetMinSizeTree(const bool snapToWidth) const;
float _ClampSplitPosition(const bool snapToWidth, const float requestedValue, const float totalSize) const;
// Function Description:
// - Returns true if the given direction can be used with the given split
@@ -130,4 +140,37 @@ private:
}
return false;
}
static constexpr bool SnapDirectionIsParallelToSplit(const bool snapToWidth, const SplitState& splitType)
{
return splitType == (snapToWidth ? SplitState::Horizontal : SplitState::Vertical)
}
// Helper structure that builds a (roughly) binary tree corresponding
// to the pane tree. Used for layouting panes with snapped sizes.
struct LayoutSizeNode
{
float size;
bool isMinimumSize;
std::unique_ptr<LayoutSizeNode> firstChild;
std::unique_ptr<LayoutSizeNode> secondChild;
std::unique_ptr<LayoutSizeNode> nextFirstChild;
std::unique_ptr<LayoutSizeNode> nextSecondChild;
LayoutSizeNode(const float minSize);
LayoutSizeNode(const LayoutSizeNode& other);
LayoutSizeNode& operator=(const LayoutSizeNode& other);
private:
void _AssignChildNode(std::unique_ptr<LayoutSizeNode>& nodeField, const LayoutSizeNode* const newNode);
};
struct SnapSizeBounds
{
float lower;
float higher;
};
using ChildrenSnapBounds = std::pair<SnapSizeBounds, SnapSizeBounds>;
};

View File

@@ -17,7 +17,7 @@ namespace winrt
Tab::Tab(const GUID& profile, const TermControl& control)
{
_rootPane = std::make_shared<Pane>(profile, control, true);
_rootPane = std::make_shared<Pane>(profile, control, nullptr, true);
_rootPane->Closed([=]() {
_closedHandlers();
@@ -230,6 +230,13 @@ void Tab::SplitPane(Pane::SplitState splitType, const GUID& profile, TermControl
_rootPane->Split(splitType, profile, control);
}
// Method Description:
// - See Pane::SnapDimension
float Tab::SnapDimension(const bool widthOrHeight, const float dimension) const
{
return _rootPane->SnapDimension(widthOrHeight, dimension);
}
// Method Description:
// - Update the size of our panes to fill the new given size. This happens when
// the window is resized.

View File

@@ -23,6 +23,8 @@ public:
bool CanSplitPane(Pane::SplitState splitType);
void SplitPane(Pane::SplitState splitType, const GUID& profile, winrt::Microsoft::Terminal::TerminalControl::TermControl& control);
float SnapDimension(const bool widthOrHeight, const float dimension) const;
void UpdateFocus();
void UpdateIcon(const winrt::hstring iconPath);

View File

@@ -1058,6 +1058,14 @@ namespace winrt::TerminalApp::implementation
}
}
// Method Description:
// - See Pane::SnapDimension
float TerminalPage::SnapDimension(const bool widthOrHeight, const float dimension) const
{
const auto focusedTabIndex = _GetFocusedTabIndex();
return _tabs[focusedTabIndex]->SnapDimension(widthOrHeight, dimension);
}
// Method Description:
// - Place `copiedData` into the clipboard as text. Triggered when a
// terminal control raises it's CopyToClipboard event.

View File

@@ -36,6 +36,8 @@ namespace winrt::TerminalApp::implementation
void TitlebarClicked();
float SnapDimension(const bool widthOrHeight, const float dimension) const;
void CloseWindow();
// -------------------------------- WinRT Events ---------------------------------

View File

@@ -53,6 +53,8 @@ namespace winrt::Microsoft::Terminal::TerminalControl::implementation
_swapChainPanel{ nullptr },
_settings{ settings },
_closing{ false },
_padding{ 0 },
_scrollBarWidth{ std::nullopt },
_lastScrollOffset{ std::nullopt },
_autoScrollVelocity{ 0 },
_autoScrollingPointerPoint{ std::nullopt },
@@ -206,21 +208,21 @@ namespace winrt::Microsoft::Terminal::TerminalControl::implementation
uint32_t bg = _settings.DefaultBackground();
_BackgroundColorChanged(bg);
const auto existingPadding = _padding;
_padding = _ParseThicknessFromPadding(_settings.Padding());
// Apply padding as swapChainPanel's margin
auto newMargin = _ParseThicknessFromPadding(_settings.Padding());
auto existingMargin = _swapChainPanel.Margin();
_swapChainPanel.Margin(newMargin);
_swapChainPanel.Margin(_padding);
if (newMargin != existingMargin && newMargin != Thickness{ 0 })
if (_padding != existingPadding && _padding != Thickness{ 0 })
{
TraceLoggingWrite(g_hTerminalControlProvider,
"NonzeroPaddingApplied",
TraceLoggingDescription("An event emitted when a control has padding applied to it"),
TraceLoggingStruct(4, "Padding"),
TraceLoggingFloat64(newMargin.Left, "Left"),
TraceLoggingFloat64(newMargin.Top, "Top"),
TraceLoggingFloat64(newMargin.Right, "Right"),
TraceLoggingFloat64(newMargin.Bottom, "Bottom"),
TraceLoggingFloat64(_padding.Left, "Left"),
TraceLoggingFloat64(_padding.Top, "Top"),
TraceLoggingFloat64(_padding.Right, "Right"),
TraceLoggingFloat64(_padding.Bottom, "Bottom"),
TraceLoggingKeyword(MICROSOFT_KEYWORD_MEASURES),
TelemetryPrivacyDataTag(PDT_ProductAndServicePerformance));
}
@@ -392,7 +394,7 @@ namespace winrt::Microsoft::Terminal::TerminalControl::implementation
const Windows::UI::Xaml::Thickness TermControl::GetPadding() const
{
return _swapChainPanel.Margin();
return _padding;
}
void TermControl::SwapChainChanged()
@@ -445,10 +447,17 @@ namespace winrt::Microsoft::Terminal::TerminalControl::implementation
auto dxEngine = std::make_unique<::Microsoft::Console::Render::DxEngine>();
_renderer->AddRenderEngine(dxEngine.get());
// Set up the renderer to be used to calculate the width of a glyph,
// should we be unable to figure out its width another way.
auto pfn = std::bind(&::Microsoft::Console::Render::Renderer::IsGlyphWideByFont, _renderer.get(), std::placeholders::_1);
SetGlyphWidthFallback(pfn);
_scrollBarWidth = gsl::narrow_cast<float>(_scrollBar.ActualWidth());
// Initialize our font with the renderer
// We don't have to care about DPI. We'll get a change message immediately if it's not 96
// and react accordingly.
_UpdateFont();
_UpdateFont(true);
const COORD windowSize{ static_cast<short>(windowWidth), static_cast<short>(windowHeight) };
@@ -800,8 +809,8 @@ namespace winrt::Microsoft::Terminal::TerminalControl::implementation
const auto cursorPosition = point.Position();
_SetEndSelectionPointAtCursor(cursorPosition);
const double cursorBelowBottomDist = cursorPosition.Y - _swapChainPanel.Margin().Top - _swapChainPanel.ActualHeight();
const double cursorAboveTopDist = -1 * cursorPosition.Y + _swapChainPanel.Margin().Top;
const double cursorBelowBottomDist = cursorPosition.Y - _padding.Top - _swapChainPanel.ActualHeight();
const double cursorAboveTopDist = -1 * cursorPosition.Y + _padding.Top;
constexpr double MinAutoScrollDist = 2.0; // Arbitrary value
double newAutoScrollVelocity = 0.0;
@@ -1211,15 +1220,23 @@ namespace winrt::Microsoft::Terminal::TerminalControl::implementation
// font change. This method will *not* change the buffer/viewport size
// to account for the new glyph dimensions. Callers should make sure to
// appropriately call _DoResize after this method is called.
void TermControl::_UpdateFont()
// Arguments:
// - initialUpdate: whether this font update should be considered as being
// concerned with initialization process. Value forwarded to event handler.
void TermControl::_UpdateFont(const bool initialUpdate)
{
auto lock = _terminal->LockForWriting();
{
auto lock = _terminal->LockForWriting();
const int newDpi = static_cast<int>(static_cast<double>(USER_DEFAULT_SCREEN_DPI) * _swapChainPanel.CompositionScaleX());
const int newDpi = static_cast<int>(static_cast<double>(USER_DEFAULT_SCREEN_DPI) * _swapChainPanel.CompositionScaleX());
// TODO: MSFT:20895307 If the font doesn't exist, this doesn't
// actually fail. We need a way to gracefully fallback.
_renderer->TriggerFontChange(newDpi, _desiredFont, _actualFont);
// TODO: MSFT:20895307 If the font doesn't exist, this doesn't
// actually fail. We need a way to gracefully fallback.
_renderer->TriggerFontChange(newDpi, _desiredFont, _actualFont);
}
const auto fontSize = _actualFont.GetSize();
_fontSizeChangedHandlers(fontSize.X, fontSize.Y, initialUpdate);
}
// Method Description:
@@ -1632,17 +1649,43 @@ namespace winrt::Microsoft::Terminal::TerminalControl::implementation
// Reserve additional space if scrollbar is intended to be visible
if (_settings.ScrollState() == ScrollbarState::Visible)
{
width += _scrollBar.ActualWidth();
width += _scrollBarWidth.value_or(0);
}
// Account for the size of any padding
auto thickness = _ParseThicknessFromPadding(_settings.Padding());
width += thickness.Left + thickness.Right;
height += thickness.Top + thickness.Bottom;
width += _padding.Left + _padding.Right;
height += _padding.Top + _padding.Bottom;
return { gsl::narrow_cast<float>(width), gsl::narrow_cast<float>(height) };
}
// Method Description:
// - Adjusts given dimension (width or height) so that it aligns to the character grid.
// The snap is always downward.
// Arguments:
// - widthOrHeight: if true operates on width, otherwise on height
// - dimension: a dimension (width or height) to be snapped
// Return Value:
// - A dimension that would be aligned to the character grid.
float TermControl::SnapDimensionToGrid(const bool widthOrHeight, const float dimension) const
{
const auto fontSize = _actualFont.GetSize();
const auto fontDimension = widthOrHeight ? fontSize.X : fontSize.Y;
auto nonTerminalArea = gsl::narrow_cast<float>(widthOrHeight ?
_padding.Left + _padding.Right :
_padding.Top + _padding.Bottom);
if (widthOrHeight && _settings.ScrollState() == ScrollbarState::Visible)
{
nonTerminalArea += _scrollBarWidth.value_or(0);
}
const auto gridSize = dimension - nonTerminalArea;
const int cells = static_cast<int>(gridSize / fontDimension);
return cells * fontDimension + nonTerminalArea;
}
// Method Description:
// - Create XAML Thickness object based on padding props provided.
// Used for controlling the TermControl XAML Grid container's Padding prop.
@@ -1771,8 +1814,8 @@ namespace winrt::Microsoft::Terminal::TerminalControl::implementation
{
// Exclude padding from cursor position calculation
COORD terminalPosition = {
static_cast<SHORT>(cursorPosition.X - _swapChainPanel.Margin().Left),
static_cast<SHORT>(cursorPosition.Y - _swapChainPanel.Margin().Top)
static_cast<SHORT>(cursorPosition.X - _padding.Left),
static_cast<SHORT>(cursorPosition.Y - _padding.Top)
};
const auto fontSize = _actualFont.GetSize();
@@ -1830,6 +1873,7 @@ namespace winrt::Microsoft::Terminal::TerminalControl::implementation
// Winrt events need a method for adding a callback to the event and removing the callback.
// These macros will define them both for you.
DEFINE_EVENT(TermControl, TitleChanged, _titleChangedHandlers, TerminalControl::TitleChangedEventArgs);
DEFINE_EVENT(TermControl, FontSizeChanged, _fontSizeChangedHandlers, TerminalControl::FontSizeChangedEventArgs);
DEFINE_EVENT(TermControl, ConnectionClosed, _connectionClosedHandlers, TerminalControl::ConnectionClosedEventArgs);
DEFINE_EVENT(TermControl, ScrollPositionChanged, _scrollPositionChangedHandlers, TerminalControl::ScrollPositionChangedEventArgs);

View File

@@ -62,6 +62,7 @@ namespace winrt::Microsoft::Terminal::TerminalControl::implementation
bool ShouldCloseOnExit() const noexcept;
Windows::Foundation::Size CharacterDimensions() const;
Windows::Foundation::Size MinimumSize() const;
float SnapDimensionToGrid(const bool widthOrHeight, const float dimension) const;
void ScrollViewport(int viewTop);
void KeyboardScrollViewport(int viewTop);
@@ -83,6 +84,7 @@ namespace winrt::Microsoft::Terminal::TerminalControl::implementation
// clang-format off
// -------------------------------- WinRT Events ---------------------------------
DECLARE_EVENT(TitleChanged, _titleChangedHandlers, TerminalControl::TitleChangedEventArgs);
DECLARE_EVENT(FontSizeChanged, _fontSizeChangedHandlers, TerminalControl::FontSizeChangedEventArgs);
DECLARE_EVENT(ConnectionClosed, _connectionClosedHandlers, TerminalControl::ConnectionClosedEventArgs);
DECLARE_EVENT(ScrollPositionChanged, _scrollPositionChangedHandlers, TerminalControl::ScrollPositionChangedEventArgs);
@@ -111,6 +113,10 @@ namespace winrt::Microsoft::Terminal::TerminalControl::implementation
FontInfoDesired _desiredFont;
FontInfo _actualFont;
Windows::UI::Xaml::Thickness _padding;
// Cached since _scrollBar.ActualWidth() became bottle-neck when resizing
std::optional<float> _scrollBarWidth;
std::optional<int> _lastScrollOffset;
@@ -151,7 +157,7 @@ namespace winrt::Microsoft::Terminal::TerminalControl::implementation
void _InitializeBackgroundBrush();
void _BackgroundColorChanged(const uint32_t color);
bool _InitializeTerminal();
void _UpdateFont();
void _UpdateFont(const bool initialUpdate = false);
void _KeyDownHandler(Windows::Foundation::IInspectable const& sender, Windows::UI::Xaml::Input::KeyRoutedEventArgs const& e);
void _CharacterHandler(Windows::Foundation::IInspectable const& sender, Windows::UI::Xaml::Input::CharacterReceivedRoutedEventArgs const& e);
void _PointerPressedHandler(Windows::Foundation::IInspectable const& sender, Windows::UI::Xaml::Input::PointerRoutedEventArgs const& e);

View File

@@ -4,6 +4,7 @@
namespace Microsoft.Terminal.TerminalControl
{
delegate void TitleChangedEventArgs(String newTitle);
delegate void FontSizeChangedEventArgs(Int32 width, Int32 height, Boolean isInitialChange);
delegate void ConnectionClosedEventArgs();
delegate void ScrollPositionChangedEventArgs(Int32 viewTop, Int32 viewHeight, Int32 bufferLength);
@@ -28,6 +29,7 @@ namespace Microsoft.Terminal.TerminalControl
void UpdateSettings(Microsoft.Terminal.Settings.IControlSettings newSettings);
event TitleChangedEventArgs TitleChanged;
event FontSizeChangedEventArgs FontSizeChanged;
event ConnectionClosedEventArgs ConnectionClosed;
event Windows.Foundation.TypedEventHandler<TermControl, CopyToClipboardEventArgs> CopyToClipboard;
event Windows.Foundation.TypedEventHandler<TermControl, PasteFromClipboardEventArgs> PasteFromClipboard;
@@ -40,6 +42,7 @@ namespace Microsoft.Terminal.TerminalControl
Boolean ShouldCloseOnExit { get; };
Windows.Foundation.Size CharacterDimensions { get; };
Windows.Foundation.Size MinimumSize { get; };
Single SnapDimensionToGrid(Boolean widthOrHeight, Single dimension);
void ScrollViewport(Int32 viewTop);
void KeyboardScrollViewport(Int32 viewTop);

View File

@@ -37,6 +37,11 @@ AppHost::AppHost() noexcept :
std::placeholders::_3);
_window->SetCreateCallback(pfn);
_window->SetSnapDimensionCallback(std::bind(&winrt::TerminalApp::App::SnapDimension,
_app,
std::placeholders::_1,
std::placeholders::_2));
_window->MakeWindow();
}
@@ -194,48 +199,15 @@ void AppHost::_HandleCreateWindow(const HWND hwnd, RECT proposedRect, winrt::Ter
static_cast<long>(ceil(initialSize.Y)), 1);
// Create a RECT from our requested client size
auto nonClient = Viewport::FromDimensions({ _currentWidth,
_currentHeight })
.ToRect();
const auto clientRect = Viewport::FromDimensions({ _currentWidth,
_currentHeight })
.ToRect();
// Get the size of a window we'd need to host that client rect. This will
// add the titlebar space.
if (_useNonClientArea)
{
// If we're in NC tabs mode, do the math ourselves. Get the margins
// we're using for the window - this will include the size of the
// titlebar content.
const auto pNcWindow = static_cast<NonClientIslandWindow*>(_window.get());
const MARGINS margins = pNcWindow->GetFrameMargins();
nonClient.left = 0;
nonClient.top = 0;
nonClient.right = margins.cxLeftWidth + nonClient.right + margins.cxRightWidth;
nonClient.bottom = margins.cyTopHeight + nonClient.bottom + margins.cyBottomHeight;
}
else
{
bool succeeded = AdjustWindowRectExForDpi(&nonClient, WS_OVERLAPPEDWINDOW, false, 0, dpix);
if (!succeeded)
{
// If we failed to get the correct window size for whatever reason, log
// the error and go on. We'll use whatever the control proposed as the
// size of our window, which will be at least close.
LOG_LAST_ERROR();
nonClient = Viewport::FromDimensions({ _currentWidth,
_currentHeight })
.ToRect();
}
// For client island scenario, there is an invisible border of 8 pixels.
// We need to remove this border to guarantee the left edge of the window
// coincides with the screen
const auto pCWindow = static_cast<IslandWindow*>(_window.get());
const RECT frame = pCWindow->GetFrameBorderMargins(dpix);
proposedRect.left += frame.left;
}
adjustedHeight = nonClient.bottom - nonClient.top;
adjustedWidth = nonClient.right - nonClient.left;
const auto nonClientSize = _window->GetNonClientSize(dpix);
adjustedHeight = clientRect.bottom - clientRect.top + nonClientSize.cx;
adjustedWidth = clientRect.right - clientRect.left + nonClientSize.cy;
}
const COORD origin{ gsl::narrow<short>(proposedRect.left),

View File

@@ -93,6 +93,26 @@ void IslandWindow::SetCreateCallback(std::function<void(const HWND, const RECT,
_pfnCreateCallback = pfn;
}
// Method Description:
// - Set a callback to be called when the window is being resized by user. For given
// requested window dimension (width or height, whichever border is dragged) it should
// return a resulting window dimension that is actually set. It is used to make the
// window 'snap' to the underling terminal's character grid.
// Arguments:
// - pfn: a function that transforms requested to actual window dimension.
// pfn's parameters:
// * widthOrHeight: whether the dimension is width (true) or height (false)
// * dimension: The requested dimension that comes from user dragging a border
// of the window. It is in pixels and represents only the client area.
// pfn's return value:
// * A dimension of client area that the window should resize to.
// Return Value:
// - <none>
void IslandWindow::SetSnapDimensionCallback(std::function<float(bool widthOrHeight, float dimension)> pfn) noexcept
{
_pfnSnapDimensionCallback = pfn;
}
// Method Description:
// - Handles a WM_CREATE message. Calls our create callback, if one's been set.
// Arguments:
@@ -205,6 +225,62 @@ void IslandWindow::OnSize(const UINT width, const UINT height)
// key that does not correspond to any mnemonic or accelerator key,
return MAKELRESULT(0, MNC_CLOSE);
}
case WM_SIZING:
{
LPRECT winRect = (LPRECT)lparam;
// Find nearest monitor.
HMONITOR hmon = MonitorFromRect(winRect, MONITOR_DEFAULTTONEAREST);
// This API guarantees that dpix and dpiy will be equal, but neither is an
// optional parameter so give two UINTs.
UINT dpix = USER_DEFAULT_SCREEN_DPI;
UINT dpiy = USER_DEFAULT_SCREEN_DPI;
// If this fails, we'll use the default of 96.
GetDpiForMonitor(hmon, MDT_EFFECTIVE_DPI, &dpix, &dpiy);
const auto nonClientSize = GetNonClientSize(dpix);
auto clientWidth = winRect->right - winRect->left - nonClientSize.cx;
auto clientHeight = winRect->bottom - winRect->top - nonClientSize.cy;
if (wparam != WMSZ_TOP && wparam != WMSZ_BOTTOM)
{
clientWidth = static_cast<int>(_pfnSnapDimensionCallback(true, static_cast<float>(clientWidth)));
}
if (wparam != WMSZ_LEFT && wparam != WMSZ_RIGHT)
{
clientHeight = static_cast<int>(_pfnSnapDimensionCallback(false, static_cast<float>(clientHeight)));
}
switch (wparam)
{
case WMSZ_LEFT:
case WMSZ_TOPLEFT:
case WMSZ_BOTTOMLEFT:
winRect->left = winRect->right - (clientWidth + nonClientSize.cx);
break;
case WMSZ_RIGHT:
case WMSZ_TOPRIGHT:
case WMSZ_BOTTOMRIGHT:
winRect->right = winRect->left + (clientWidth + nonClientSize.cx);
break;
}
switch (wparam)
{
case WMSZ_BOTTOM:
case WMSZ_BOTTOMLEFT:
case WMSZ_BOTTOMRIGHT:
winRect->bottom = winRect->top + (clientHeight + nonClientSize.cy);
break;
case WMSZ_TOP:
case WMSZ_TOPLEFT:
case WMSZ_TOPRIGHT:
winRect->top = winRect->bottom - (clientHeight + nonClientSize.cy);
break;
}
return TRUE;
}
case WM_CLOSE:
{
// If the user wants to close the app by clicking 'X' button,
@@ -285,6 +361,27 @@ void IslandWindow::SetContent(winrt::Windows::UI::Xaml::UIElement content)
_rootGrid.Children().Append(content);
}
// Method Description:
// - Gets the difference between window and client area size.
// Arguments:
// - dpix: dpi of a monitor on which the window is placed
// Return Value
// - The size difference
SIZE IslandWindow::GetNonClientSize(const UINT dpix) const noexcept
{
RECT rect{};
bool succeeded = AdjustWindowRectExForDpi(&rect, WS_OVERLAPPEDWINDOW, false, 0, dpix);
if (!succeeded)
{
// If we failed to get the correct window size for whatever reason, log
// the error and go on. We'll use whatever the control proposed as the
// size of our window, which will be at least close.
LOG_LAST_ERROR();
}
return { rect.right - rect.left, rect.bottom - rect.top };
}
void IslandWindow::OnAppInitialized()
{
// Do a quick resize to force the island to paint

View File

@@ -29,10 +29,12 @@ public:
void OnRestore() override;
virtual void OnAppInitialized();
virtual void SetContent(winrt::Windows::UI::Xaml::UIElement content);
virtual SIZE GetNonClientSize(const UINT dpix) const noexcept;
virtual void Initialize();
void SetCreateCallback(std::function<void(const HWND, const RECT, winrt::TerminalApp::LaunchMode& launchMode)> pfn) noexcept;
void SetSnapDimensionCallback(std::function<float(bool widthOrHeight, float dimension)> pfn) noexcept;
void UpdateTheme(const winrt::Windows::UI::Xaml::ElementTheme& requestedTheme);
@@ -87,6 +89,7 @@ protected:
winrt::Windows::UI::Xaml::Controls::Grid _rootGrid;
std::function<void(const HWND, const RECT, winrt::TerminalApp::LaunchMode& launchMode)> _pfnCreateCallback;
std::function<float(bool widthOrHeight, float dimension)> _pfnSnapDimensionCallback;
void _HandleCreateWindow(const WPARAM wParam, const LPARAM lParam) noexcept;
};

View File

@@ -340,6 +340,24 @@ MARGINS NonClientIslandWindow::GetFrameMargins() const noexcept
return margins;
}
// Method Description:
// - Gets the difference between window and client area size.
// Arguments:
// - dpix: dpi of a monitor on which the window is placed
// Return Value
// - The size difference
SIZE NonClientIslandWindow::GetNonClientSize(UINT /* dpix */) const noexcept
{
SIZE result;
// If we're in NC tabs mode, do the math ourselves. Get the margins
// we're using for the window - this will include the size of the
// titlebar content.
const MARGINS margins = GetFrameMargins();
result.cx = margins.cxLeftWidth + margins.cxRightWidth;
result.cy = margins.cyTopHeight + margins.cyBottomHeight;
return result;
}
// Method Description:
// - Updates the borders of our window frame, using DwmExtendFrameIntoClientArea.
// Arguments:

View File

@@ -34,6 +34,7 @@ public:
[[nodiscard]] virtual LRESULT MessageHandler(UINT const message, WPARAM const wparam, LPARAM const lparam) noexcept override;
MARGINS GetFrameMargins() const noexcept;
virtual SIZE GetNonClientSize(UINT dpix) const noexcept override;
void Initialize() override;