@@ -43,6 +43,9 @@ Pane::Pane(const IPaneContent& content, const bool lastFocused) :
43
43
_lostFocusRevoker = control.LostFocus (winrt::auto_revoke, { this , &Pane::_ContentLostFocusHandler });
44
44
}
45
45
46
+ _manipulationDeltaRevoker = _root.ManipulationDelta (winrt::auto_revoke, { this , &Pane::_ManipulationDeltaHandler });
47
+ _manipulationStartedRevoker = _root.ManipulationStarted (winrt::auto_revoke, { this , &Pane::_ManipulationStartedHandler });
48
+
46
49
// When our border is tapped, make sure to transfer focus to our control.
47
50
// LOAD-BEARING: This will NOT work if the border's BorderBrush is set to
48
51
// Colors::Transparent! The border won't get Tapped events, and they'll fall
@@ -73,6 +76,8 @@ Pane::Pane(std::shared_ptr<Pane> first,
73
76
_root.Children ().Append (_borderFirst);
74
77
_root.Children ().Append (_borderSecond);
75
78
79
+ _manipulationDeltaRevoker = _root.ManipulationDelta (winrt::auto_revoke, { this , &Pane::_ManipulationDeltaHandler });
80
+
76
81
_ApplySplitDefinitions ();
77
82
78
83
// 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
243
248
// decreasing the size of our first child.
244
249
// Return Value:
245
250
// - 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 )
247
252
{
248
253
if (!DirectionMatchesSplit (direction, _splitState))
249
254
{
250
255
return false ;
251
256
}
252
257
253
- auto amount = .05f ;
254
258
if (direction == ResizeDirection::Right || direction == ResizeDirection::Down)
255
259
{
256
260
amount = -amount;
@@ -284,7 +288,7 @@ bool Pane::_Resize(const ResizeDirection& direction)
284
288
// - direction: The direction to move the separator in.
285
289
// Return Value:
286
290
// - 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 )
288
292
{
289
293
// If we're a leaf, do nothing. We can't possibly have a descendant with a
290
294
// separator the correct direction.
@@ -301,7 +305,7 @@ bool Pane::ResizePane(const ResizeDirection& direction)
301
305
const auto secondIsFocused = _secondChild->_lastActive ;
302
306
if (firstIsFocused || secondIsFocused)
303
307
{
304
- return _Resize (direction);
308
+ return _Resize (direction, amount );
305
309
}
306
310
307
311
// If neither of our children were the focused pane, then recurse into
@@ -315,17 +319,200 @@ bool Pane::ResizePane(const ResizeDirection& direction)
315
319
// either.
316
320
if ((!_firstChild->_IsLeaf ()) && _firstChild->_HasFocusedChild ())
317
321
{
318
- return _firstChild->ResizePane (direction) || _Resize (direction);
322
+ return _firstChild->ResizePane (direction, amount ) || _Resize (direction, amount );
319
323
}
320
324
321
325
if ((!_secondChild->_IsLeaf ()) && _secondChild->_HasFocusedChild ())
322
326
{
323
- return _secondChild->ResizePane (direction) || _Resize (direction);
327
+ return _secondChild->ResizePane (direction, amount ) || _Resize (direction, amount );
324
328
}
325
329
326
330
return false ;
327
331
}
328
332
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
+
329
516
// Method Description:
330
517
// - Attempt to navigate from the sourcePane according to direction.
331
518
// - If the direction is NextInOrder or PreviousInOrder, the next or previous
@@ -1847,6 +2034,9 @@ Borders Pane::_GetCommonBorders()
1847
2034
// - <none>
1848
2035
void Pane::_ApplySplitDefinitions ()
1849
2036
{
2037
+ // Remove our old handler, if we had one.
2038
+ _manipulationDeltaRevoker.revoke ();
2039
+
1850
2040
if (_splitState == SplitState::Vertical)
1851
2041
{
1852
2042
Controls::Grid::SetColumn (_borderFirst, 0 );
@@ -1871,6 +2061,17 @@ void Pane::_ApplySplitDefinitions()
1871
2061
_firstChild->_ApplySplitDefinitions ();
1872
2062
_secondChild->_ApplySplitDefinitions ();
1873
2063
}
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);
1874
2075
_UpdateBorders ();
1875
2076
}
1876
2077
@@ -2254,6 +2455,9 @@ std::pair<std::shared_ptr<Pane>, std::shared_ptr<Pane>> Pane::_Split(SplitDirect
2254
2455
// Create a new pane from ourself
2255
2456
if (!_IsLeaf ())
2256
2457
{
2458
+ _firstChild->ManipulationRequested (_firstManipulatedToken);
2459
+ _secondChild->ManipulationRequested (_secondManipulatedToken);
2460
+
2257
2461
// Since we are a parent we don't have borders normally,
2258
2462
// so set them temporarily for when we update our split definition.
2259
2463
_borders = _GetCommonBorders ();
@@ -2292,6 +2496,9 @@ std::pair<std::shared_ptr<Pane>, std::shared_ptr<Pane>> Pane::_Split(SplitDirect
2292
2496
2293
2497
_ApplySplitDefinitions ();
2294
2498
2499
+ _firstManipulatedToken = _firstChild->ManipulationRequested ({ this , &Pane::_handleOrBubbleManipulation });
2500
+ _secondManipulatedToken = _secondChild->ManipulationRequested ({ this , &Pane::_handleOrBubbleManipulation });
2501
+
2295
2502
// Register event handlers on our children to handle their Close events
2296
2503
_SetupChildCloseHandlers ();
2297
2504
0 commit comments