Skip to content

Commit ab25a1e

Browse files
committed
PRE-MERGE microsoft#16895 Add support for resizing panes with mouse
2 parents 7802e20 + aa6f9bc commit ab25a1e

File tree

2 files changed

+230
-8
lines changed

2 files changed

+230
-8
lines changed

src/cascadia/TerminalApp/Pane.cpp

Lines changed: 213 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,9 @@ Pane::Pane(const IPaneContent& content, const bool lastFocused) :
4343
_lostFocusRevoker = control.LostFocus(winrt::auto_revoke, { this, &Pane::_ContentLostFocusHandler });
4444
}
4545

46+
_manipulationDeltaRevoker = _root.ManipulationDelta(winrt::auto_revoke, { this, &Pane::_ManipulationDeltaHandler });
47+
_manipulationStartedRevoker = _root.ManipulationStarted(winrt::auto_revoke, { this, &Pane::_ManipulationStartedHandler });
48+
4649
// When our border is tapped, make sure to transfer focus to our control.
4750
// LOAD-BEARING: This will NOT work if the border's BorderBrush is set to
4851
// Colors::Transparent! The border won't get Tapped events, and they'll fall
@@ -73,6 +76,8 @@ Pane::Pane(std::shared_ptr<Pane> first,
7376
_root.Children().Append(_borderFirst);
7477
_root.Children().Append(_borderSecond);
7578

79+
_manipulationDeltaRevoker = _root.ManipulationDelta(winrt::auto_revoke, { this, &Pane::_ManipulationDeltaHandler });
80+
7681
_ApplySplitDefinitions();
7782

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

253-
auto amount = .05f;
254258
if (direction == ResizeDirection::Right || direction == ResizeDirection::Down)
255259
{
256260
amount = -amount;
@@ -284,7 +288,7 @@ bool Pane::_Resize(const ResizeDirection& direction)
284288
// - direction: The direction to move the separator in.
285289
// Return Value:
286290
// - true if we or a child handled this resize request.
287-
bool Pane::ResizePane(const ResizeDirection& direction)
291+
bool Pane::ResizePane(const ResizeDirection& direction, float amount)
288292
{
289293
// If we're a leaf, do nothing. We can't possibly have a descendant with a
290294
// separator the correct direction.
@@ -301,7 +305,7 @@ bool Pane::ResizePane(const ResizeDirection& direction)
301305
const auto secondIsFocused = _secondChild->_lastActive;
302306
if (firstIsFocused || secondIsFocused)
303307
{
304-
return _Resize(direction);
308+
return _Resize(direction, amount);
305309
}
306310

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

321325
if ((!_secondChild->_IsLeaf()) && _secondChild->_HasFocusedChild())
322326
{
323-
return _secondChild->ResizePane(direction) || _Resize(direction);
327+
return _secondChild->ResizePane(direction, amount) || _Resize(direction, amount);
324328
}
325329

326330
return false;
327331
}
328332

333+
// Handler for the _root's ManipulationStarted event. We use this to check if a
334+
// manipulation (read: drag) started inside our content. If it did, we _don't_
335+
// want to do our normal pane dragging.
336+
//
337+
// Consider the case that the TermControl might be selecting text, and the user
338+
// drags the mouse over the pane border. We don't want that to start moving the
339+
// border!
340+
void Pane::_ManipulationStartedHandler(const winrt::Windows::Foundation::IInspectable& /*sender*/,
341+
const winrt::Windows::UI::Xaml::Input::ManipulationStartedRoutedEventArgs& args)
342+
{
343+
// This is added to each _root. But it also bubbles, so only leaves should actually try to handle this.
344+
if (args.Handled())
345+
{
346+
return;
347+
}
348+
args.Handled(true);
349+
350+
assert(_IsLeaf());
351+
352+
const auto contentSize = _content.GetRoot().ActualSize();
353+
auto transformCurrentPos = args.Position();
354+
auto transformOrigin = transformCurrentPos;
355+
356+
const auto transform_contentFromOurRoot = _root.TransformToVisual(_content.GetRoot());
357+
const auto transformInControlSpace = transform_contentFromOurRoot.TransformPoint(transformOrigin);
358+
359+
// If we clicked on the control. bail, and don't allow any manipulations
360+
// for this series of events.
361+
_shouldManipulate = !((transformInControlSpace.X >= 0 && transformInControlSpace.X < contentSize.x) &&
362+
(transformInControlSpace.Y >= 0 && transformInControlSpace.Y < contentSize.y));
363+
}
364+
365+
// Handler for the _root's ManipulationDelta event. This is the event raised
366+
// when a user clicks and drags somewhere inside the pane. We're going to use
367+
// this to determine if the user clicked on one of our borders. If they did,
368+
// we're going to need to ask our parent pane (or other ancestors) to resize
369+
// their split.
370+
//
371+
// Recall that a leaf itself is responsible for having the right borders, but it
372+
// is the parent of the leaf that actually controls how big a split is.
373+
//
374+
// When we do want to be resized, we'll pass the delta from this event upwards
375+
// via ManipulationRequested, which will be handled in
376+
// Pane::_handleOrBubbleManipulation.
377+
void Pane::_ManipulationDeltaHandler(const winrt::Windows::Foundation::IInspectable& /*sender*/,
378+
const winrt::Windows::UI::Xaml::Input::ManipulationDeltaRoutedEventArgs& args)
379+
{
380+
// sender is ORIGINALLY the root Grid of a leaf, and the leaf may or may not
381+
// have a border.
382+
if (args.Handled())
383+
{
384+
return;
385+
}
386+
if (!_shouldManipulate)
387+
{
388+
// Using our stored _shouldManipulate set up in
389+
// _ManipulationStartedHandler, bail if the manipulation didn't start
390+
// _on the border_.
391+
return;
392+
}
393+
394+
assert(_IsLeaf());
395+
396+
const auto delta = args.Delta().Translation;
397+
const auto transformOrigin = args.Position();
398+
399+
const auto contentSize = _content.GetRoot().ActualSize();
400+
401+
const auto transform_contentFromOurRoot = _root.TransformToVisual(_content.GetRoot());
402+
// This is the position of the drag relative to the bounds of our content.
403+
const auto transformInControlSpace = transform_contentFromOurRoot.TransformPoint(transformOrigin);
404+
405+
// Did we click somewhere in the bounds of our content?
406+
if ((transformInControlSpace.X >= 0 && transformInControlSpace.X < contentSize.x) &&
407+
(transformInControlSpace.Y >= 0 && transformInControlSpace.Y < contentSize.y))
408+
{
409+
// We did! Bail.
410+
return;
411+
}
412+
413+
// Now, we know we clicked somewhere outside the bounds of our content. Set
414+
// border flags based on the side that was clicked on.
415+
Borders clicked = Borders::None;
416+
clicked |= (transformInControlSpace.X < 0) ? Borders::Left : Borders::None;
417+
clicked |= (transformInControlSpace.Y < 0) ? Borders::Top : Borders::None;
418+
clicked |= (transformInControlSpace.X > contentSize.x) ? Borders::Right : Borders::None;
419+
clicked |= (transformInControlSpace.Y > contentSize.y) ? Borders::Bottom : Borders::None;
420+
421+
// Ask our parent to resize their split.
422+
ManipulationRequested.raise(shared_from_this(), delta, clicked);
423+
}
424+
425+
// Handler for our child's own ManipulationRequested event. They will pass to us
426+
// (their immediate parent) the delta and the side that was clicked on.
427+
// * If we control that border, then we'll handle the resize ourself in _handleManipulation.
428+
// * If not, then we'll ask our own parent to try and resize that same border.
429+
void Pane::_handleOrBubbleManipulation(std::shared_ptr<Pane> sender,
430+
const winrt::Windows::Foundation::Point delta,
431+
Borders side)
432+
{
433+
if (side == Borders::None || _splitState == SplitState::None)
434+
{
435+
return;
436+
}
437+
438+
const bool isFirstChild = sender == _firstChild;
439+
// We want to handle this drag in the following cases
440+
// * In a vertical split: if we're dragging the right of the first pane or the left of the second
441+
// * In a horizontal split: if we're dragging the bottom of the first pane or the top of the second
442+
const auto sideMatched = (_splitState == SplitState::Vertical) ? (isFirstChild && WI_IsFlagSet(side, Borders::Right)) || (!isFirstChild && WI_IsFlagSet(side, Borders::Left)) :
443+
(_splitState == SplitState::Horizontal) ? (isFirstChild && WI_IsFlagSet(side, Borders::Bottom)) || (!isFirstChild && WI_IsFlagSet(side, Borders::Top)) :
444+
false;
445+
446+
if (sideMatched)
447+
{
448+
_handleManipulation(delta);
449+
}
450+
else
451+
{
452+
// Bubble, with us as the new sender.
453+
ManipulationRequested.raise(shared_from_this(), delta, side);
454+
}
455+
}
456+
457+
// Actually handle resizing our split in response to a drag event. If we're
458+
// being called, then we know that the delta that's passed to us should be
459+
// applied to our own split. The delta that's passed in here is in PIXELS, not
460+
// DIPs.
461+
void Pane::_handleManipulation(const winrt::Windows::Foundation::Point delta)
462+
{
463+
const auto scaleFactor = DisplayInformation::GetForCurrentView().RawPixelsPerViewPixel();
464+
465+
const auto weAreVertical = _splitState == SplitState::Vertical;
466+
const winrt::Windows::Foundation::Point translationForUs = (weAreVertical) ? Point{ delta.X, 0 } : Point{ 0, delta.Y };
467+
468+
// Decide on direction based on delta
469+
ResizeDirection dir = ResizeDirection::None;
470+
if (_splitState == SplitState::Vertical)
471+
{
472+
if (translationForUs.X < 0)
473+
{
474+
dir = ResizeDirection::Left;
475+
}
476+
else if (translationForUs.X > 0)
477+
{
478+
dir = ResizeDirection::Right;
479+
}
480+
}
481+
else if (_splitState == SplitState::Horizontal)
482+
{
483+
if (translationForUs.Y < 0)
484+
{
485+
dir = ResizeDirection::Up;
486+
}
487+
else if (translationForUs.Y > 0)
488+
{
489+
dir = ResizeDirection::Down;
490+
}
491+
}
492+
493+
// Resize in the given direction
494+
if (dir != ResizeDirection::None)
495+
{
496+
// turn delta into a percentage
497+
base::ClampedNumeric<float> amount;
498+
base::ClampedNumeric<float> actualDimension;
499+
if (dir == ResizeDirection::Left || dir == ResizeDirection::Right)
500+
{
501+
amount = translationForUs.X;
502+
actualDimension = base::ClampedNumeric<float>(_root.ActualWidth());
503+
}
504+
else if (dir == ResizeDirection::Up || dir == ResizeDirection::Down)
505+
{
506+
amount = translationForUs.Y;
507+
actualDimension = base::ClampedNumeric<float>(_root.ActualHeight());
508+
}
509+
const auto scaledAmount = amount * scaleFactor;
510+
const auto percentDelta = scaledAmount / actualDimension;
511+
512+
_Resize(dir, percentDelta.Abs());
513+
}
514+
}
515+
329516
// Method Description:
330517
// - Attempt to navigate from the sourcePane according to direction.
331518
// - If the direction is NextInOrder or PreviousInOrder, the next or previous
@@ -1847,6 +2034,9 @@ Borders Pane::_GetCommonBorders()
18472034
// - <none>
18482035
void Pane::_ApplySplitDefinitions()
18492036
{
2037+
// Remove our old handler, if we had one.
2038+
_manipulationDeltaRevoker.revoke();
2039+
18502040
if (_splitState == SplitState::Vertical)
18512041
{
18522042
Controls::Grid::SetColumn(_borderFirst, 0);
@@ -1871,6 +2061,17 @@ void Pane::_ApplySplitDefinitions()
18712061
_firstChild->_ApplySplitDefinitions();
18722062
_secondChild->_ApplySplitDefinitions();
18732063
}
2064+
else
2065+
{
2066+
assert(_IsLeaf());
2067+
// If we're a leaf, then add a ManipulationDelta handler.
2068+
_manipulationDeltaRevoker = _root.ManipulationDelta(winrt::auto_revoke, { this, &Pane::_ManipulationDeltaHandler });
2069+
}
2070+
2071+
_root.ManipulationMode(Xaml::Input::ManipulationModes::TranslateX |
2072+
Xaml::Input::ManipulationModes::TranslateRailsX |
2073+
Xaml::Input::ManipulationModes::TranslateY |
2074+
Xaml::Input::ManipulationModes::TranslateRailsY);
18742075
_UpdateBorders();
18752076
}
18762077

@@ -2254,6 +2455,9 @@ std::pair<std::shared_ptr<Pane>, std::shared_ptr<Pane>> Pane::_Split(SplitDirect
22542455
// Create a new pane from ourself
22552456
if (!_IsLeaf())
22562457
{
2458+
_firstChild->ManipulationRequested(_firstManipulatedToken);
2459+
_secondChild->ManipulationRequested(_secondManipulatedToken);
2460+
22572461
// Since we are a parent we don't have borders normally,
22582462
// so set them temporarily for when we update our split definition.
22592463
_borders = _GetCommonBorders();
@@ -2292,6 +2496,9 @@ std::pair<std::shared_ptr<Pane>, std::shared_ptr<Pane>> Pane::_Split(SplitDirect
22922496

22932497
_ApplySplitDefinitions();
22942498

2499+
_firstManipulatedToken = _firstChild->ManipulationRequested({ this, &Pane::_handleOrBubbleManipulation });
2500+
_secondManipulatedToken = _secondChild->ManipulationRequested({ this, &Pane::_handleOrBubbleManipulation });
2501+
22952502
// Register event handlers on our children to handle their Close events
22962503
_SetupChildCloseHandlers();
22972504

src/cascadia/TerminalApp/Pane.h

Lines changed: 17 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -109,7 +109,8 @@ class Pane : public std::enable_shared_from_this<Pane>
109109
winrt::Microsoft::Terminal::Settings::Model::INewContentArgs GetTerminalArgsForPane(winrt::TerminalApp::BuildStartupKind kind) const;
110110

111111
void UpdateSettings(const winrt::Microsoft::Terminal::Settings::Model::CascadiaSettings& settings);
112-
bool ResizePane(const winrt::Microsoft::Terminal::Settings::Model::ResizeDirection& direction);
112+
bool ResizePane(const winrt::Microsoft::Terminal::Settings::Model::ResizeDirection& direction, float amount = .05f);
113+
113114
std::shared_ptr<Pane> NavigateDirection(const std::shared_ptr<Pane> sourcePane,
114115
const winrt::Microsoft::Terminal::Settings::Model::FocusDirection& direction,
115116
const std::vector<uint32_t>& mruPanes);
@@ -223,6 +224,7 @@ class Pane : public std::enable_shared_from_this<Pane>
223224
til::event<gotFocusArgs> GotFocus;
224225
til::event<winrt::delegate<std::shared_ptr<Pane>>> LostFocus;
225226
til::event<winrt::delegate<std::shared_ptr<Pane>>> Detached;
227+
til::event<winrt::delegate<std::shared_ptr<Pane>, winrt::Windows::Foundation::Point, Borders>> ManipulationRequested;
226228

227229
private:
228230
struct PanePoint;
@@ -253,8 +255,14 @@ class Pane : public std::enable_shared_from_this<Pane>
253255
winrt::event_token _firstClosedToken{ 0 };
254256
winrt::event_token _secondClosedToken{ 0 };
255257

258+
winrt::event_token _firstManipulatedToken{ 0 };
259+
winrt::event_token _secondManipulatedToken{ 0 };
260+
256261
winrt::Windows::UI::Xaml::UIElement::GotFocus_revoker _gotFocusRevoker;
257262
winrt::Windows::UI::Xaml::UIElement::LostFocus_revoker _lostFocusRevoker;
263+
winrt::Windows::UI::Xaml::UIElement::ManipulationDelta_revoker _manipulationDeltaRevoker;
264+
winrt::Windows::UI::Xaml::UIElement::ManipulationStarted_revoker _manipulationStartedRevoker;
265+
bool _shouldManipulate{ false };
258266

259267
Borders _borders{ Borders::None };
260268

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

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

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

317+
void _ManipulationStartedHandler(const winrt::Windows::Foundation::IInspectable& sender,
318+
const winrt::Windows::UI::Xaml::Input::ManipulationStartedRoutedEventArgs& e);
319+
void _ManipulationDeltaHandler(const winrt::Windows::Foundation::IInspectable& sender,
320+
const winrt::Windows::UI::Xaml::Input::ManipulationDeltaRoutedEventArgs& e);
321+
307322
std::pair<float, float> _CalcChildrenSizes(const float fullSize) const;
308323
SnapChildrenSizeResult _CalcSnappedChildrenSizes(const bool widthOrHeight, const float fullSize) const;
309324
SnapSizeResult _CalcSnappedDimension(const bool widthOrHeight, const float dimension) const;

0 commit comments

Comments
 (0)