mirror of
https://github.com/microsoft/terminal.git
synced 2026-04-06 14:19:45 +00:00
Compare commits
12 Commits
dev/cazamo
...
dev/migrie
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
e83b69616d | ||
|
|
b00fef800e | ||
|
|
583541d976 | ||
|
|
3dcd00f292 | ||
|
|
0ecbec551c | ||
|
|
b732e745b5 | ||
|
|
d69f8ed403 | ||
|
|
cbda9118da | ||
|
|
90649b2bdf | ||
|
|
5d0f1d3eca | ||
|
|
68f613b7c4 | ||
|
|
b8a5725a8e |
@@ -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)
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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>;
|
||||
};
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -36,6 +36,8 @@ namespace winrt::TerminalApp::implementation
|
||||
|
||||
void TitlebarClicked();
|
||||
|
||||
float SnapDimension(const bool widthOrHeight, const float dimension) const;
|
||||
|
||||
void CloseWindow();
|
||||
|
||||
// -------------------------------- WinRT Events ---------------------------------
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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),
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
};
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
Reference in New Issue
Block a user