Skip to content

Commit

Permalink
PRE-MERGE #16895 Add support for resizing panes with mouse
Browse files Browse the repository at this point in the history
  • Loading branch information
zadjii-msft committed Jun 3, 2024
2 parents 7802e20 + aa6f9bc commit ab25a1e
Show file tree
Hide file tree
Showing 2 changed files with 230 additions and 8 deletions.
219 changes: 213 additions & 6 deletions src/cascadia/TerminalApp/Pane.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,9 @@ Pane::Pane(const IPaneContent& content, const bool lastFocused) :
_lostFocusRevoker = control.LostFocus(winrt::auto_revoke, { this, &Pane::_ContentLostFocusHandler });
}

_manipulationDeltaRevoker = _root.ManipulationDelta(winrt::auto_revoke, { this, &Pane::_ManipulationDeltaHandler });
_manipulationStartedRevoker = _root.ManipulationStarted(winrt::auto_revoke, { this, &Pane::_ManipulationStartedHandler });

// When our border is tapped, make sure to transfer focus to our control.
// LOAD-BEARING: This will NOT work if the border's BorderBrush is set to
// Colors::Transparent! The border won't get Tapped events, and they'll fall
Expand Down Expand Up @@ -73,6 +76,8 @@ Pane::Pane(std::shared_ptr<Pane> first,
_root.Children().Append(_borderFirst);
_root.Children().Append(_borderSecond);

_manipulationDeltaRevoker = _root.ManipulationDelta(winrt::auto_revoke, { this, &Pane::_ManipulationDeltaHandler });

_ApplySplitDefinitions();

// Register event handlers on our children to handle their Close events
Expand Down Expand Up @@ -243,14 +248,13 @@ Pane::BuildStartupState Pane::BuildStartupActions(uint32_t currentId, uint32_t n
// decreasing the size of our first child.
// Return Value:
// - false if we couldn't resize this pane in the given direction, else true.
bool Pane::_Resize(const ResizeDirection& direction)
bool Pane::_Resize(const ResizeDirection& direction, float amount)
{
if (!DirectionMatchesSplit(direction, _splitState))
{
return false;
}

auto amount = .05f;
if (direction == ResizeDirection::Right || direction == ResizeDirection::Down)
{
amount = -amount;
Expand Down Expand Up @@ -284,7 +288,7 @@ bool Pane::_Resize(const ResizeDirection& direction)
// - direction: The direction to move the separator in.
// Return Value:
// - true if we or a child handled this resize request.
bool Pane::ResizePane(const ResizeDirection& direction)
bool Pane::ResizePane(const ResizeDirection& direction, float amount)
{
// If we're a leaf, do nothing. We can't possibly have a descendant with a
// separator the correct direction.
Expand All @@ -301,7 +305,7 @@ bool Pane::ResizePane(const ResizeDirection& direction)
const auto secondIsFocused = _secondChild->_lastActive;
if (firstIsFocused || secondIsFocused)
{
return _Resize(direction);
return _Resize(direction, amount);
}

// If neither of our children were the focused pane, then recurse into
Expand All @@ -315,17 +319,200 @@ bool Pane::ResizePane(const ResizeDirection& direction)
// either.
if ((!_firstChild->_IsLeaf()) && _firstChild->_HasFocusedChild())
{
return _firstChild->ResizePane(direction) || _Resize(direction);
return _firstChild->ResizePane(direction, amount) || _Resize(direction, amount);
}

if ((!_secondChild->_IsLeaf()) && _secondChild->_HasFocusedChild())
{
return _secondChild->ResizePane(direction) || _Resize(direction);
return _secondChild->ResizePane(direction, amount) || _Resize(direction, amount);
}

return false;
}

// Handler for the _root's ManipulationStarted event. We use this to check if a
// manipulation (read: drag) started inside our content. If it did, we _don't_
// want to do our normal pane dragging.
//
// Consider the case that the TermControl might be selecting text, and the user
// drags the mouse over the pane border. We don't want that to start moving the
// border!
void Pane::_ManipulationStartedHandler(const winrt::Windows::Foundation::IInspectable& /*sender*/,
const winrt::Windows::UI::Xaml::Input::ManipulationStartedRoutedEventArgs& args)
{
// This is added to each _root. But it also bubbles, so only leaves should actually try to handle this.
if (args.Handled())
{
return;
}
args.Handled(true);

assert(_IsLeaf());

const auto contentSize = _content.GetRoot().ActualSize();
auto transformCurrentPos = args.Position();
auto transformOrigin = transformCurrentPos;

const auto transform_contentFromOurRoot = _root.TransformToVisual(_content.GetRoot());
const auto transformInControlSpace = transform_contentFromOurRoot.TransformPoint(transformOrigin);

// If we clicked on the control. bail, and don't allow any manipulations
// for this series of events.
_shouldManipulate = !((transformInControlSpace.X >= 0 && transformInControlSpace.X < contentSize.x) &&
(transformInControlSpace.Y >= 0 && transformInControlSpace.Y < contentSize.y));
}

// Handler for the _root's ManipulationDelta event. This is the event raised
// when a user clicks and drags somewhere inside the pane. We're going to use
// this to determine if the user clicked on one of our borders. If they did,
// we're going to need to ask our parent pane (or other ancestors) to resize
// their split.
//
// Recall that a leaf itself is responsible for having the right borders, but it
// is the parent of the leaf that actually controls how big a split is.
//
// When we do want to be resized, we'll pass the delta from this event upwards
// via ManipulationRequested, which will be handled in
// Pane::_handleOrBubbleManipulation.
void Pane::_ManipulationDeltaHandler(const winrt::Windows::Foundation::IInspectable& /*sender*/,
const winrt::Windows::UI::Xaml::Input::ManipulationDeltaRoutedEventArgs& args)
{
// sender is ORIGINALLY the root Grid of a leaf, and the leaf may or may not
// have a border.
if (args.Handled())
{
return;
}
if (!_shouldManipulate)
{
// Using our stored _shouldManipulate set up in
// _ManipulationStartedHandler, bail if the manipulation didn't start
// _on the border_.
return;
}

assert(_IsLeaf());

const auto delta = args.Delta().Translation;
const auto transformOrigin = args.Position();

const auto contentSize = _content.GetRoot().ActualSize();

const auto transform_contentFromOurRoot = _root.TransformToVisual(_content.GetRoot());
// This is the position of the drag relative to the bounds of our content.
const auto transformInControlSpace = transform_contentFromOurRoot.TransformPoint(transformOrigin);

// Did we click somewhere in the bounds of our content?
if ((transformInControlSpace.X >= 0 && transformInControlSpace.X < contentSize.x) &&
(transformInControlSpace.Y >= 0 && transformInControlSpace.Y < contentSize.y))
{
// We did! Bail.
return;
}

// Now, we know we clicked somewhere outside the bounds of our content. Set
// border flags based on the side that was clicked on.
Borders clicked = Borders::None;
clicked |= (transformInControlSpace.X < 0) ? Borders::Left : Borders::None;
clicked |= (transformInControlSpace.Y < 0) ? Borders::Top : Borders::None;
clicked |= (transformInControlSpace.X > contentSize.x) ? Borders::Right : Borders::None;
clicked |= (transformInControlSpace.Y > contentSize.y) ? Borders::Bottom : Borders::None;

// Ask our parent to resize their split.
ManipulationRequested.raise(shared_from_this(), delta, clicked);
}

// Handler for our child's own ManipulationRequested event. They will pass to us
// (their immediate parent) the delta and the side that was clicked on.
// * If we control that border, then we'll handle the resize ourself in _handleManipulation.
// * If not, then we'll ask our own parent to try and resize that same border.
void Pane::_handleOrBubbleManipulation(std::shared_ptr<Pane> sender,
const winrt::Windows::Foundation::Point delta,
Borders side)
{
if (side == Borders::None || _splitState == SplitState::None)
{
return;
}

const bool isFirstChild = sender == _firstChild;
// We want to handle this drag in the following cases
// * In a vertical split: if we're dragging the right of the first pane or the left of the second
// * In a horizontal split: if we're dragging the bottom of the first pane or the top of the second
const auto sideMatched = (_splitState == SplitState::Vertical) ? (isFirstChild && WI_IsFlagSet(side, Borders::Right)) || (!isFirstChild && WI_IsFlagSet(side, Borders::Left)) :
(_splitState == SplitState::Horizontal) ? (isFirstChild && WI_IsFlagSet(side, Borders::Bottom)) || (!isFirstChild && WI_IsFlagSet(side, Borders::Top)) :
false;

if (sideMatched)
{
_handleManipulation(delta);
}
else
{
// Bubble, with us as the new sender.
ManipulationRequested.raise(shared_from_this(), delta, side);
}
}

// Actually handle resizing our split in response to a drag event. If we're
// being called, then we know that the delta that's passed to us should be
// applied to our own split. The delta that's passed in here is in PIXELS, not
// DIPs.
void Pane::_handleManipulation(const winrt::Windows::Foundation::Point delta)
{
const auto scaleFactor = DisplayInformation::GetForCurrentView().RawPixelsPerViewPixel();

const auto weAreVertical = _splitState == SplitState::Vertical;
const winrt::Windows::Foundation::Point translationForUs = (weAreVertical) ? Point{ delta.X, 0 } : Point{ 0, delta.Y };

// Decide on direction based on delta
ResizeDirection dir = ResizeDirection::None;
if (_splitState == SplitState::Vertical)
{
if (translationForUs.X < 0)
{
dir = ResizeDirection::Left;
}
else if (translationForUs.X > 0)
{
dir = ResizeDirection::Right;
}
}
else if (_splitState == SplitState::Horizontal)
{
if (translationForUs.Y < 0)
{
dir = ResizeDirection::Up;
}
else if (translationForUs.Y > 0)
{
dir = ResizeDirection::Down;
}
}

// Resize in the given direction
if (dir != ResizeDirection::None)
{
// turn delta into a percentage
base::ClampedNumeric<float> amount;
base::ClampedNumeric<float> actualDimension;
if (dir == ResizeDirection::Left || dir == ResizeDirection::Right)
{
amount = translationForUs.X;
actualDimension = base::ClampedNumeric<float>(_root.ActualWidth());
}
else if (dir == ResizeDirection::Up || dir == ResizeDirection::Down)
{
amount = translationForUs.Y;
actualDimension = base::ClampedNumeric<float>(_root.ActualHeight());
}
const auto scaledAmount = amount * scaleFactor;
const auto percentDelta = scaledAmount / actualDimension;

_Resize(dir, percentDelta.Abs());
}
}

// Method Description:
// - Attempt to navigate from the sourcePane according to direction.
// - If the direction is NextInOrder or PreviousInOrder, the next or previous
Expand Down Expand Up @@ -1847,6 +2034,9 @@ Borders Pane::_GetCommonBorders()
// - <none>
void Pane::_ApplySplitDefinitions()
{
// Remove our old handler, if we had one.
_manipulationDeltaRevoker.revoke();

if (_splitState == SplitState::Vertical)
{
Controls::Grid::SetColumn(_borderFirst, 0);
Expand All @@ -1871,6 +2061,17 @@ void Pane::_ApplySplitDefinitions()
_firstChild->_ApplySplitDefinitions();
_secondChild->_ApplySplitDefinitions();
}
else
{
assert(_IsLeaf());
// If we're a leaf, then add a ManipulationDelta handler.
_manipulationDeltaRevoker = _root.ManipulationDelta(winrt::auto_revoke, { this, &Pane::_ManipulationDeltaHandler });
}

_root.ManipulationMode(Xaml::Input::ManipulationModes::TranslateX |
Xaml::Input::ManipulationModes::TranslateRailsX |
Xaml::Input::ManipulationModes::TranslateY |
Xaml::Input::ManipulationModes::TranslateRailsY);
_UpdateBorders();
}

Expand Down Expand Up @@ -2254,6 +2455,9 @@ std::pair<std::shared_ptr<Pane>, std::shared_ptr<Pane>> Pane::_Split(SplitDirect
// Create a new pane from ourself
if (!_IsLeaf())
{
_firstChild->ManipulationRequested(_firstManipulatedToken);
_secondChild->ManipulationRequested(_secondManipulatedToken);

// Since we are a parent we don't have borders normally,
// so set them temporarily for when we update our split definition.
_borders = _GetCommonBorders();
Expand Down Expand Up @@ -2292,6 +2496,9 @@ std::pair<std::shared_ptr<Pane>, std::shared_ptr<Pane>> Pane::_Split(SplitDirect

_ApplySplitDefinitions();

_firstManipulatedToken = _firstChild->ManipulationRequested({ this, &Pane::_handleOrBubbleManipulation });
_secondManipulatedToken = _secondChild->ManipulationRequested({ this, &Pane::_handleOrBubbleManipulation });

// Register event handlers on our children to handle their Close events
_SetupChildCloseHandlers();

Expand Down
19 changes: 17 additions & 2 deletions src/cascadia/TerminalApp/Pane.h
Original file line number Diff line number Diff line change
Expand Up @@ -109,7 +109,8 @@ class Pane : public std::enable_shared_from_this<Pane>
winrt::Microsoft::Terminal::Settings::Model::INewContentArgs GetTerminalArgsForPane(winrt::TerminalApp::BuildStartupKind kind) const;

void UpdateSettings(const winrt::Microsoft::Terminal::Settings::Model::CascadiaSettings& settings);
bool ResizePane(const winrt::Microsoft::Terminal::Settings::Model::ResizeDirection& direction);
bool ResizePane(const winrt::Microsoft::Terminal::Settings::Model::ResizeDirection& direction, float amount = .05f);

std::shared_ptr<Pane> NavigateDirection(const std::shared_ptr<Pane> sourcePane,
const winrt::Microsoft::Terminal::Settings::Model::FocusDirection& direction,
const std::vector<uint32_t>& mruPanes);
Expand Down Expand Up @@ -223,6 +224,7 @@ class Pane : public std::enable_shared_from_this<Pane>
til::event<gotFocusArgs> GotFocus;
til::event<winrt::delegate<std::shared_ptr<Pane>>> LostFocus;
til::event<winrt::delegate<std::shared_ptr<Pane>>> Detached;
til::event<winrt::delegate<std::shared_ptr<Pane>, winrt::Windows::Foundation::Point, Borders>> ManipulationRequested;

private:
struct PanePoint;
Expand Down Expand Up @@ -253,8 +255,14 @@ class Pane : public std::enable_shared_from_this<Pane>
winrt::event_token _firstClosedToken{ 0 };
winrt::event_token _secondClosedToken{ 0 };

winrt::event_token _firstManipulatedToken{ 0 };
winrt::event_token _secondManipulatedToken{ 0 };

winrt::Windows::UI::Xaml::UIElement::GotFocus_revoker _gotFocusRevoker;
winrt::Windows::UI::Xaml::UIElement::LostFocus_revoker _lostFocusRevoker;
winrt::Windows::UI::Xaml::UIElement::ManipulationDelta_revoker _manipulationDeltaRevoker;
winrt::Windows::UI::Xaml::UIElement::ManipulationStarted_revoker _manipulationStartedRevoker;
bool _shouldManipulate{ false };

Borders _borders{ Borders::None };

Expand All @@ -281,7 +289,9 @@ class Pane : public std::enable_shared_from_this<Pane>
Borders _GetCommonBorders();
winrt::Windows::UI::Xaml::Media::SolidColorBrush _ComputeBorderColor();

bool _Resize(const winrt::Microsoft::Terminal::Settings::Model::ResizeDirection& direction);
void _handleOrBubbleManipulation(std::shared_ptr<Pane> sender, const winrt::Windows::Foundation::Point delta, Borders side);
void _handleManipulation(const winrt::Windows::Foundation::Point delta);
bool _Resize(const winrt::Microsoft::Terminal::Settings::Model::ResizeDirection& direction, float amount);

std::shared_ptr<Pane> _FindParentOfPane(const std::shared_ptr<Pane> pane);
std::pair<PanePoint, PanePoint> _GetOffsetsForPane(const PanePoint parentOffset) const;
Expand All @@ -304,6 +314,11 @@ class Pane : public std::enable_shared_from_this<Pane>
void _ContentLostFocusHandler(const winrt::Windows::Foundation::IInspectable& sender,
const winrt::Windows::UI::Xaml::RoutedEventArgs& e);

void _ManipulationStartedHandler(const winrt::Windows::Foundation::IInspectable& sender,
const winrt::Windows::UI::Xaml::Input::ManipulationStartedRoutedEventArgs& e);
void _ManipulationDeltaHandler(const winrt::Windows::Foundation::IInspectable& sender,
const winrt::Windows::UI::Xaml::Input::ManipulationDeltaRoutedEventArgs& e);

std::pair<float, float> _CalcChildrenSizes(const float fullSize) const;
SnapChildrenSizeResult _CalcSnappedChildrenSizes(const bool widthOrHeight, const float fullSize) const;
SnapSizeResult _CalcSnappedDimension(const bool widthOrHeight, const float dimension) const;
Expand Down

0 comments on commit ab25a1e

Please sign in to comment.