Border.Arrangment.cs 27 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800
  1. using System.Diagnostics;
  2. namespace Terminal.Gui.ViewBase;
  3. // Border Arrange Mode
  4. public partial class Border
  5. {
  6. internal ViewArrangement Arranging { get; set; }
  7. private Button? _moveButton; // always top-left
  8. private Button? _allSizeButton;
  9. private Button? _leftSizeButton;
  10. private Button? _rightSizeButton;
  11. private Button? _topSizeButton;
  12. private Button? _bottomSizeButton;
  13. /// <summary>
  14. /// Starts "Arrange Mode" where <see cref="Adornment.Parent"/> can be moved and/or resized using the mouse
  15. /// or keyboard. If <paramref name="arrangement"/> is <see cref="ViewArrangement.Fixed"/> keyboard mode is enabled.
  16. /// </summary>
  17. /// <remarks>
  18. /// Arrange Mode is exited by the user pressing <see cref="Application.ArrangeKey"/>, <see cref="Key.Esc"/>, or by
  19. /// clicking the mouse out of the <see cref="Adornment.Parent"/>'s Frame.
  20. /// </remarks>
  21. /// <returns></returns>
  22. public bool? EnterArrangeMode (ViewArrangement arrangement)
  23. {
  24. Debug.Assert (Arranging == ViewArrangement.Fixed);
  25. if (!HasAnyArrangementOptions ())
  26. {
  27. return false;
  28. }
  29. MouseState |= MouseState.Pressed;
  30. // Add Commands and KeyBindings - Note it's ok these get added each time. KeyBindings are cleared in EndArrange()
  31. AddArrangeModeKeyBindings ();
  32. if (App is { })
  33. {
  34. App.Mouse.MouseEvent += ApplicationOnMouseEvent;
  35. }
  36. // Create all necessary arrangement buttons
  37. CreateArrangementButtons ();
  38. if (arrangement == ViewArrangement.Fixed)
  39. {
  40. // Keyboard mode
  41. SetVisibilityForKeyboardMode ();
  42. Arranging = ViewArrangement.Movable;
  43. CanFocus = true;
  44. SetFocus ();
  45. }
  46. else
  47. {
  48. // Mouse mode
  49. Arranging = arrangement;
  50. SetVisibilityForMouseMode (arrangement);
  51. }
  52. if (Arranging != ViewArrangement.Fixed)
  53. {
  54. if (arrangement == ViewArrangement.Fixed)
  55. {
  56. // Keyboard mode - enable nav
  57. // TODO: Keyboard mode only supports sizing from bottom/right.
  58. Arranging = (ViewArrangement)(Focused?.Data ?? ViewArrangement.Fixed);
  59. }
  60. return true;
  61. }
  62. // Hack for now
  63. EndArrangeMode ();
  64. return false;
  65. }
  66. /// <summary>
  67. /// Checks if the parent view has any arrangement options enabled
  68. /// </summary>
  69. private bool HasAnyArrangementOptions ()
  70. {
  71. return Parent!.Arrangement.HasFlag (ViewArrangement.Movable)
  72. || Parent!.Arrangement.HasFlag (ViewArrangement.BottomResizable)
  73. || Parent!.Arrangement.HasFlag (ViewArrangement.TopResizable)
  74. || Parent!.Arrangement.HasFlag (ViewArrangement.LeftResizable)
  75. || Parent!.Arrangement.HasFlag (ViewArrangement.RightResizable);
  76. }
  77. /// <summary>
  78. /// Creates all the buttons required for the arrange mode based on allowed arrangement options
  79. /// </summary>
  80. private void CreateArrangementButtons ()
  81. {
  82. if (Parent!.Arrangement.HasFlag (ViewArrangement.Movable))
  83. {
  84. _moveButton = CreateArrangementButton ("moveButton", Glyphs.Move, 0, 0, ViewArrangement.Movable);
  85. }
  86. if (Parent!.Arrangement.HasFlag (ViewArrangement.Resizable))
  87. {
  88. _allSizeButton = CreateArrangementButton (
  89. "allSizeButton",
  90. Glyphs.SizeBottomRight,
  91. Pos.AnchorEnd (),
  92. Pos.AnchorEnd (),
  93. ViewArrangement.Resizable);
  94. }
  95. if (Parent!.Arrangement.HasFlag (ViewArrangement.TopResizable))
  96. {
  97. _topSizeButton = CreateArrangementButton (
  98. "topSizeButton",
  99. Glyphs.SizeVertical,
  100. Pos.Center () + Parent!.Margin!.Thickness.Horizontal,
  101. 0,
  102. ViewArrangement.TopResizable);
  103. }
  104. if (Parent!.Arrangement.HasFlag (ViewArrangement.RightResizable))
  105. {
  106. _rightSizeButton = CreateArrangementButton (
  107. "rightSizeButton",
  108. Glyphs.SizeHorizontal,
  109. Pos.AnchorEnd (),
  110. Pos.Center () + Parent!.Margin!.Thickness.Vertical / 2,
  111. ViewArrangement.RightResizable);
  112. }
  113. if (Parent!.Arrangement.HasFlag (ViewArrangement.LeftResizable))
  114. {
  115. _leftSizeButton = CreateArrangementButton (
  116. "leftSizeButton",
  117. Glyphs.SizeHorizontal,
  118. 0,
  119. Pos.Center () + Parent!.Margin!.Thickness.Vertical / 2,
  120. ViewArrangement.LeftResizable);
  121. }
  122. if (Parent!.Arrangement.HasFlag (ViewArrangement.BottomResizable))
  123. {
  124. _bottomSizeButton = CreateArrangementButton (
  125. "bottomSizeButton",
  126. Glyphs.SizeVertical,
  127. Pos.Center () + Parent!.Margin!.Thickness.Horizontal / 2,
  128. Pos.AnchorEnd (),
  129. ViewArrangement.BottomResizable);
  130. }
  131. }
  132. /// <summary>
  133. /// Factory method to create a standardized arrangement button
  134. /// </summary>
  135. private Button CreateArrangementButton (string id, Rune glyph, Pos x, Pos y, ViewArrangement arrangement)
  136. {
  137. var button = new Button
  138. {
  139. Id = id,
  140. CanFocus = true,
  141. Width = 1,
  142. Height = 1,
  143. NoDecorations = true,
  144. NoPadding = true,
  145. ShadowStyle = ShadowStyle.None,
  146. Text = $"{glyph}",
  147. X = x,
  148. Y = y,
  149. Visible = false,
  150. Data = arrangement
  151. };
  152. Add (button);
  153. return button;
  154. }
  155. /// <summary>
  156. /// Sets button visibility for keyboard arrangement mode
  157. /// </summary>
  158. private void SetVisibilityForKeyboardMode ()
  159. {
  160. if (_moveButton != null)
  161. {
  162. _moveButton.Visible = true;
  163. }
  164. if (_allSizeButton != null)
  165. {
  166. _allSizeButton.Visible = true;
  167. }
  168. }
  169. /// <summary>
  170. /// Sets button visibility based on the specified mouse arrangement mode
  171. /// </summary>
  172. private void SetVisibilityForMouseMode (ViewArrangement arrangement)
  173. {
  174. switch (arrangement)
  175. {
  176. case ViewArrangement.Movable:
  177. SetVisibleButton (_moveButton);
  178. break;
  179. case ViewArrangement.RightResizable | ViewArrangement.BottomResizable:
  180. case ViewArrangement.Resizable:
  181. SetVisibleButton (_rightSizeButton);
  182. SetVisibleButton (_bottomSizeButton);
  183. if (_allSizeButton != null)
  184. {
  185. _allSizeButton.X = Pos.AnchorEnd ();
  186. _allSizeButton.Y = Pos.AnchorEnd ();
  187. _allSizeButton.Visible = true;
  188. }
  189. break;
  190. case ViewArrangement.LeftResizable:
  191. SetVisibleButton (_leftSizeButton);
  192. break;
  193. case ViewArrangement.RightResizable:
  194. SetVisibleButton (_rightSizeButton);
  195. break;
  196. case ViewArrangement.TopResizable:
  197. SetVisibleButton (_topSizeButton);
  198. break;
  199. case ViewArrangement.BottomResizable:
  200. SetVisibleButton (_bottomSizeButton);
  201. break;
  202. case ViewArrangement.LeftResizable | ViewArrangement.BottomResizable:
  203. SetVisibleButton (_leftSizeButton);
  204. SetVisibleButton (_bottomSizeButton);
  205. if (_allSizeButton != null)
  206. {
  207. _allSizeButton.X = 0;
  208. _allSizeButton.Y = Pos.AnchorEnd ();
  209. _allSizeButton.Visible = true;
  210. }
  211. break;
  212. case ViewArrangement.LeftResizable | ViewArrangement.TopResizable:
  213. SetVisibleButton (_leftSizeButton);
  214. SetVisibleButton (_topSizeButton);
  215. break;
  216. case ViewArrangement.RightResizable | ViewArrangement.TopResizable:
  217. SetVisibleButton (_rightSizeButton);
  218. SetVisibleButton (_topSizeButton);
  219. if (_allSizeButton != null)
  220. {
  221. _allSizeButton.X = Pos.AnchorEnd ();
  222. _allSizeButton.Y = 0;
  223. _allSizeButton.Visible = true;
  224. }
  225. break;
  226. }
  227. }
  228. /// <summary>
  229. /// Helper method to make a button visible if it's not null
  230. /// </summary>
  231. private void SetVisibleButton (Button? button)
  232. {
  233. if (button != null)
  234. {
  235. button.Visible = true;
  236. }
  237. }
  238. private void AddArrangeModeKeyBindings ()
  239. {
  240. AddCommand (Command.Quit, EndArrangeMode);
  241. AddCommand (
  242. Command.Up,
  243. () =>
  244. {
  245. if (Parent is null)
  246. {
  247. return false;
  248. }
  249. if (Arranging == ViewArrangement.Movable)
  250. {
  251. Parent.Y = Parent.Y - 1;
  252. }
  253. if (Arranging == ViewArrangement.Resizable)
  254. {
  255. if (Parent.Viewport.Height > 0)
  256. {
  257. Parent.Height = Parent.Height! - 1;
  258. }
  259. }
  260. return true;
  261. });
  262. AddCommand (
  263. Command.Down,
  264. () =>
  265. {
  266. if (Parent is null)
  267. {
  268. return false;
  269. }
  270. if (Arranging == ViewArrangement.Movable)
  271. {
  272. Parent.Y = Parent.Y + 1;
  273. }
  274. if (Arranging == ViewArrangement.Resizable)
  275. {
  276. Parent.Height = Parent.Height! + 1;
  277. }
  278. return true;
  279. });
  280. AddCommand (
  281. Command.Left,
  282. () =>
  283. {
  284. if (Parent is null)
  285. {
  286. return false;
  287. }
  288. if (Arranging == ViewArrangement.Movable)
  289. {
  290. Parent.X = Parent.X - 1;
  291. }
  292. if (Arranging == ViewArrangement.Resizable)
  293. {
  294. if (Parent.Viewport.Width > 0)
  295. {
  296. Parent.Width = Parent.Width! - 1;
  297. }
  298. }
  299. return true;
  300. });
  301. AddCommand (
  302. Command.Right,
  303. () =>
  304. {
  305. if (Parent is null)
  306. {
  307. return false;
  308. }
  309. if (Arranging == ViewArrangement.Movable)
  310. {
  311. Parent.X = Parent.X + 1;
  312. }
  313. if (Arranging == ViewArrangement.Resizable)
  314. {
  315. Parent.Width = Parent.Width! + 1;
  316. }
  317. return true;
  318. });
  319. AddCommand (
  320. Command.Tab,
  321. () =>
  322. {
  323. // BUGBUG: If an arrangeable view has only arrangeable subviews, it's not possible to activate
  324. // BUGBUG: ArrangeMode with keyboard for the superview.
  325. // BUGBUG: AdvanceFocus should be wise to this and when in ArrangeMode, should move across
  326. // BUGBUG: the view hierarchy.
  327. AdvanceFocus (NavigationDirection.Forward, TabBehavior.TabStop);
  328. Arranging = (ViewArrangement)(Focused?.Data ?? ViewArrangement.Fixed);
  329. return true; // Always eat
  330. });
  331. AddCommand (
  332. Command.BackTab,
  333. () =>
  334. {
  335. AdvanceFocus (NavigationDirection.Backward, TabBehavior.TabStop);
  336. Arranging = (ViewArrangement)(Focused?.Data ?? ViewArrangement.Fixed);
  337. return true; // Always eat
  338. });
  339. HotKeyBindings.Add (Key.Esc, Command.Quit);
  340. HotKeyBindings.Add (Application.ArrangeKey, Command.Quit);
  341. HotKeyBindings.Add (Key.CursorUp, Command.Up);
  342. HotKeyBindings.Add (Key.CursorDown, Command.Down);
  343. HotKeyBindings.Add (Key.CursorLeft, Command.Left);
  344. HotKeyBindings.Add (Key.CursorRight, Command.Right);
  345. HotKeyBindings.Add (Key.Tab, Command.Tab);
  346. HotKeyBindings.Add (Key.Tab.WithShift, Command.BackTab);
  347. }
  348. private void ApplicationOnMouseEvent (object? sender, MouseEventArgs e)
  349. {
  350. if (e.Flags != MouseFlags.Button1Clicked)
  351. {
  352. return;
  353. }
  354. // If mouse click is outside of Border.Thickness then exit Arrange Mode
  355. // e.Position is screen relative
  356. Point framePos = ScreenToFrame (e.ScreenPosition);
  357. if (!Thickness.Contains (Frame, framePos))
  358. {
  359. EndArrangeMode ();
  360. }
  361. }
  362. private bool? EndArrangeMode ()
  363. {
  364. // Debug.Assert (_arranging != ViewArrangement.Fixed);
  365. Arranging = ViewArrangement.Fixed;
  366. MouseState &= ~MouseState.Pressed;
  367. if (App is { })
  368. {
  369. App.Mouse.MouseEvent -= ApplicationOnMouseEvent;
  370. if (App.Mouse.MouseGrabView == this && _dragPosition.HasValue)
  371. {
  372. App.Mouse.UngrabMouse ();
  373. }
  374. }
  375. // Clean up all arrangement buttons
  376. DisposeSizeButton (ref _moveButton);
  377. DisposeSizeButton (ref _allSizeButton);
  378. DisposeSizeButton (ref _leftSizeButton);
  379. DisposeSizeButton (ref _rightSizeButton);
  380. DisposeSizeButton (ref _topSizeButton);
  381. DisposeSizeButton (ref _bottomSizeButton);
  382. HotKeyBindings.Clear ();
  383. if (CanFocus)
  384. {
  385. CanFocus = false;
  386. }
  387. return true;
  388. }
  389. /// <summary>
  390. /// Helper method to dispose and remove a button
  391. /// </summary>
  392. private void DisposeSizeButton (ref Button? button)
  393. {
  394. if (button != null)
  395. {
  396. Remove (button);
  397. button.Dispose ();
  398. button = null;
  399. }
  400. }
  401. #region Mouse Support
  402. private Point? _dragPosition;
  403. private Point _startGrabPoint;
  404. /// <inheritdoc/>
  405. protected override bool OnMouseEvent (MouseEventArgs mouseEvent)
  406. {
  407. // BUGBUG: See https://github.com/gui-cs/Terminal.Gui/issues/3312
  408. if (!_dragPosition.HasValue && mouseEvent.Flags.HasFlag (MouseFlags.Button1Pressed))
  409. {
  410. Parent!.SetFocus ();
  411. if (!HasAnyArrangementOptions ())
  412. {
  413. return false;
  414. }
  415. // Only start grabbing if the user clicks in the Thickness area
  416. // Adornment.Contains takes Parent SuperView=relative coords.
  417. if (Contains (new (mouseEvent.Position.X + Parent.Frame.X + Frame.X, mouseEvent.Position.Y + Parent.Frame.Y + Frame.Y)))
  418. {
  419. if (Arranging != ViewArrangement.Fixed)
  420. {
  421. EndArrangeMode ();
  422. }
  423. // Set the start grab point to the Frame coords
  424. _startGrabPoint = new (mouseEvent.Position.X + Frame.X, mouseEvent.Position.Y + Frame.Y);
  425. _dragPosition = mouseEvent.Position;
  426. App?.Mouse.GrabMouse (this);
  427. // Determine the mode based on where the click occurred
  428. ViewArrangement arrangeMode = DetermineArrangeModeFromClick ();
  429. EnterArrangeMode (arrangeMode);
  430. // BUGBUG: Should we return the result of EnterArrangeMode?
  431. return true;
  432. }
  433. return true;
  434. }
  435. if (mouseEvent.Flags is (MouseFlags.Button1Pressed | MouseFlags.ReportMousePosition) && App?.Mouse.MouseGrabView == this)
  436. {
  437. if (_dragPosition.HasValue)
  438. {
  439. HandleDragOperation (mouseEvent);
  440. return true;
  441. }
  442. }
  443. if (mouseEvent.Flags.HasFlag (MouseFlags.Button1Released) && _dragPosition.HasValue)
  444. {
  445. _dragPosition = null;
  446. App?.Mouse.UngrabMouse ();
  447. EndArrangeMode ();
  448. return true;
  449. }
  450. return false;
  451. }
  452. /// <summary>
  453. /// Determines the arrangement mode based on where the mouse was clicked
  454. /// </summary>
  455. internal ViewArrangement DetermineArrangeModeFromClick ()
  456. {
  457. Rectangle sideRect;
  458. // Check for left resizable region
  459. if (Parent!.Arrangement.HasFlag (ViewArrangement.LeftResizable))
  460. {
  461. sideRect = new (Frame.X, Frame.Y + Thickness.Top, Thickness.Left, Frame.Height - Thickness.Top - Thickness.Bottom);
  462. if (sideRect.Contains (_startGrabPoint))
  463. {
  464. return ViewArrangement.LeftResizable;
  465. }
  466. }
  467. // Check for right resizable region
  468. if (Parent!.Arrangement.HasFlag (ViewArrangement.RightResizable))
  469. {
  470. sideRect = new (
  471. Frame.X + Frame.Width - Thickness.Right,
  472. Frame.Y + Thickness.Top,
  473. Thickness.Right,
  474. Frame.Height - Thickness.Top - Thickness.Bottom);
  475. if (sideRect.Contains (_startGrabPoint))
  476. {
  477. return (ViewArrangement.RightResizable);
  478. }
  479. }
  480. // Check for top resizable region (only if not movable)
  481. if (Parent!.Arrangement.HasFlag (ViewArrangement.TopResizable) && !Parent!.Arrangement.HasFlag (ViewArrangement.Movable))
  482. {
  483. sideRect = new (Frame.X + Thickness.Left, Frame.Y, Frame.Width - Thickness.Left - Thickness.Right, Thickness.Top);
  484. if (sideRect.Contains (_startGrabPoint))
  485. {
  486. return (ViewArrangement.TopResizable);
  487. }
  488. }
  489. // Check for bottom resizable region
  490. if (Parent!.Arrangement.HasFlag (ViewArrangement.BottomResizable))
  491. {
  492. sideRect = new (
  493. Frame.X + Thickness.Left,
  494. Frame.Y + Frame.Height - Thickness.Bottom,
  495. Frame.Width - Thickness.Left - Thickness.Right,
  496. Thickness.Bottom);
  497. if (sideRect.Contains (_startGrabPoint))
  498. {
  499. return (ViewArrangement.BottomResizable);
  500. }
  501. }
  502. // Check for bottom-left corner region
  503. if (Parent!.Arrangement.HasFlag (ViewArrangement.BottomResizable) && Parent!.Arrangement.HasFlag (ViewArrangement.LeftResizable))
  504. {
  505. sideRect = new (Frame.X, Frame.Height - Thickness.Top, Thickness.Left, Thickness.Bottom);
  506. if (sideRect.Contains (_startGrabPoint))
  507. {
  508. return (ViewArrangement.BottomResizable | ViewArrangement.LeftResizable);
  509. }
  510. }
  511. // Check for bottom-right corner region
  512. if (Parent!.Arrangement.HasFlag (ViewArrangement.BottomResizable) && Parent!.Arrangement.HasFlag (ViewArrangement.RightResizable))
  513. {
  514. sideRect = new (Frame.X + Frame.Width - Thickness.Right, Frame.Height - Thickness.Top, Thickness.Right, Thickness.Bottom);
  515. if (sideRect.Contains (_startGrabPoint))
  516. {
  517. return (ViewArrangement.BottomResizable | ViewArrangement.RightResizable);
  518. }
  519. }
  520. // Check for top-right corner region
  521. if (Parent!.Arrangement.HasFlag (ViewArrangement.TopResizable) && Parent!.Arrangement.HasFlag (ViewArrangement.RightResizable))
  522. {
  523. sideRect = new (Frame.X + Frame.Width - Thickness.Right, Frame.Y, Thickness.Right, Thickness.Top);
  524. if (sideRect.Contains (_startGrabPoint))
  525. {
  526. return (ViewArrangement.TopResizable | ViewArrangement.RightResizable);
  527. }
  528. }
  529. // Check for top-left corner region
  530. if (Parent!.Arrangement.HasFlag (ViewArrangement.TopResizable) && Parent!.Arrangement.HasFlag (ViewArrangement.LeftResizable))
  531. {
  532. sideRect = new (Frame.X, Frame.Y, Thickness.Left, Thickness.Top);
  533. if (sideRect.Contains (_startGrabPoint))
  534. {
  535. return (ViewArrangement.TopResizable | ViewArrangement.LeftResizable);
  536. }
  537. }
  538. // Default to movable if enabled
  539. if (Parent!.Arrangement.HasFlag (ViewArrangement.Movable))
  540. {
  541. return ViewArrangement.Movable;
  542. }
  543. return ViewArrangement.Fixed;
  544. }
  545. /// <summary>
  546. /// Handles drag operations for moving and resizing
  547. /// </summary>
  548. internal void HandleDragOperation (MouseEventArgs mouseEvent)
  549. {
  550. if (Parent!.SuperView is null)
  551. {
  552. // Redraw the entire app window.
  553. App?.TopRunnableView?.SetNeedsDraw ();
  554. }
  555. else
  556. {
  557. Parent.SuperView.SetNeedsDraw ();
  558. }
  559. _dragPosition = mouseEvent.Position;
  560. Point parentLoc = Parent!.SuperView?.ScreenToViewport (new (mouseEvent.ScreenPosition.X, mouseEvent.ScreenPosition.Y))
  561. ?? mouseEvent.ScreenPosition;
  562. int minHeight = Thickness.Vertical + Parent!.Margin!.Thickness.Bottom;
  563. int minWidth = Thickness.Horizontal + Parent!.Margin!.Thickness.Right;
  564. switch (Arranging)
  565. {
  566. case ViewArrangement.Movable:
  567. Parent.X = parentLoc.X - _startGrabPoint.X;
  568. Parent.Y = parentLoc.Y - _startGrabPoint.Y;
  569. break;
  570. case ViewArrangement.TopResizable:
  571. // Get how much the mouse has moved since the start of the drag
  572. // and adjust the height of the parent by that amount
  573. int deltaY = parentLoc.Y - Parent.Frame.Y;
  574. int newHeight = Math.Max (minHeight, Parent.Frame.Height - deltaY);
  575. if (newHeight != Parent.Frame.Height)
  576. {
  577. Parent.Height = newHeight;
  578. Parent.Y = parentLoc.Y - _startGrabPoint.Y;
  579. }
  580. break;
  581. case ViewArrangement.BottomResizable:
  582. Parent.Height = Math.Max (minHeight, parentLoc.Y - Parent.Frame.Y + Parent!.Margin!.Thickness.Bottom + 1);
  583. break;
  584. case ViewArrangement.LeftResizable:
  585. // Get how much the mouse has moved since the start of the drag
  586. // and adjust the width of the parent by that amount
  587. int deltaX = parentLoc.X - Parent.Frame.X;
  588. int newWidth = Math.Max (minWidth, Parent.Frame.Width - deltaX);
  589. if (newWidth != Parent.Frame.Width)
  590. {
  591. Parent.Width = newWidth;
  592. Parent.X = parentLoc.X - _startGrabPoint.X;
  593. }
  594. break;
  595. case ViewArrangement.RightResizable:
  596. Parent.Width = Math.Max (minWidth, parentLoc.X - Parent.Frame.X + Parent!.Margin!.Thickness.Right + 1);
  597. break;
  598. case ViewArrangement.BottomResizable | ViewArrangement.RightResizable:
  599. Parent.Width = Math.Max (minWidth, parentLoc.X - Parent.Frame.X + Parent!.Margin!.Thickness.Right + 1);
  600. Parent.Height = Math.Max (minHeight, parentLoc.Y - Parent.Frame.Y + Parent!.Margin!.Thickness.Bottom + 1);
  601. break;
  602. case ViewArrangement.BottomResizable | ViewArrangement.LeftResizable:
  603. int dX = parentLoc.X - Parent.Frame.X;
  604. int newW = Math.Max (minWidth, Parent.Frame.Width - dX);
  605. if (newW != Parent.Frame.Width)
  606. {
  607. Parent.Width = newW;
  608. Parent.X = parentLoc.X - _startGrabPoint.X;
  609. }
  610. Parent.Height = Math.Max (minHeight, parentLoc.Y - Parent.Frame.Y + Parent!.Margin!.Thickness.Bottom + 1);
  611. break;
  612. case ViewArrangement.TopResizable | ViewArrangement.RightResizable:
  613. int dY = parentLoc.Y - Parent.Frame.Y;
  614. int newH = Math.Max (minHeight, Parent.Frame.Height - dY);
  615. if (newH != Parent.Frame.Height)
  616. {
  617. Parent.Height = newH;
  618. Parent.Y = parentLoc.Y - _startGrabPoint.Y;
  619. }
  620. Parent.Width = Math.Max (minWidth, parentLoc.X - Parent.Frame.X + Parent!.Margin!.Thickness.Right + 1);
  621. break;
  622. case ViewArrangement.TopResizable | ViewArrangement.LeftResizable:
  623. int dY2 = parentLoc.Y - Parent.Frame.Y;
  624. int newH2 = Math.Max (minHeight, Parent.Frame.Height - dY2);
  625. if (newH2 != Parent.Frame.Height)
  626. {
  627. Parent.Height = newH2;
  628. Parent.Y = parentLoc.Y - _startGrabPoint.Y;
  629. }
  630. int dX2 = parentLoc.X - Parent.Frame.X;
  631. int newW2 = Math.Max (minWidth, Parent.Frame.Width - dX2);
  632. if (newW2 != Parent.Frame.Width)
  633. {
  634. Parent.Width = newW2;
  635. Parent.X = parentLoc.X - _startGrabPoint.X;
  636. }
  637. break;
  638. }
  639. }
  640. /// <summary>
  641. /// Cancels <see cref="IMouseGrabHandler.GrabbingMouse"/> events during an active drag to prevent other views from
  642. /// stealing the mouse grab mid-operation.
  643. /// </summary>
  644. /// <remarks>
  645. /// During an Arrange Mode drag (<see cref="_dragPosition"/> has a value), Border owns the mouse grab and
  646. /// must receive all mouse events until Button1Released. If another view (e.g., scrollbar, slider) were allowed
  647. /// to grab the mouse, the drag would freeze, leaving Border in an inconsistent state with no cleanup.
  648. /// Canceling follows the CWP pattern, ensuring Border maintains exclusive mouse control until it explicitly
  649. /// releases via <see cref="IMouseGrabHandler.UngrabMouse"/> in <see cref="OnMouseEvent"/>.
  650. /// </remarks>
  651. private void Application_GrabbingMouse (object? sender, GrabMouseEventArgs e)
  652. {
  653. if (App?.Mouse.MouseGrabView == this && _dragPosition.HasValue)
  654. {
  655. e.Cancel = true;
  656. }
  657. }
  658. #endregion Mouse Support
  659. /// <inheritdoc/>
  660. protected override void Dispose (bool disposing)
  661. {
  662. if (App is { })
  663. {
  664. App.Mouse.GrabbingMouse -= Application_GrabbingMouse;
  665. }
  666. _dragPosition = null;
  667. base.Dispose (disposing);
  668. }
  669. }