Border.cs 52 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492
  1. #nullable enable
  2. using System.Diagnostics;
  3. namespace Terminal.Gui;
  4. /// <summary>The Border for a <see cref="View"/>. Accessed via <see cref="View.Border"/></summary>
  5. /// <remarks>
  6. /// <para>
  7. /// Renders a border around the view with the <see cref="View.Title"/>. A border using <see cref="LineStyle"/>
  8. /// will be drawn on the sides of <see cref="Thickness"/> that are greater than zero.
  9. /// </para>
  10. /// <para>
  11. /// The <see cref="View.Title"/> of <see cref="Adornment.Parent"/> will be drawn based on the value of
  12. /// <see cref="Thickness.Top"/>:
  13. /// <example>
  14. /// // If Thickness.Top is 1:
  15. /// ┌┤1234├──┐
  16. /// │ │
  17. /// └────────┘
  18. /// // If Thickness.Top is 2:
  19. /// ┌────┐
  20. /// ┌┤1234├──┐
  21. /// │ │
  22. /// └────────┘
  23. /// If Thickness.Top is 3:
  24. /// ┌────┐
  25. /// ┌┤1234├──┐
  26. /// │└────┘ │
  27. /// │ │
  28. /// └────────┘
  29. /// </example>
  30. /// </para>
  31. /// </remarks>
  32. public class Border : Adornment
  33. {
  34. private LineStyle? _lineStyle;
  35. /// <inheritdoc/>
  36. public Border ()
  37. { /* Do nothing; A parameter-less constructor is required to support all views unit tests. */
  38. }
  39. /// <inheritdoc/>
  40. public Border (View parent) : base (parent)
  41. {
  42. Parent = parent;
  43. CanFocus = false;
  44. TabStop = TabBehavior.TabGroup;
  45. Application.GrabbingMouse += Application_GrabbingMouse;
  46. Application.UnGrabbingMouse += Application_UnGrabbingMouse;
  47. HighlightStyle |= HighlightStyle.Pressed;
  48. Highlight += Border_Highlight;
  49. ThicknessChanged += OnThicknessChanged;
  50. }
  51. // TODO: Move DrawIndicator out of Border and into View
  52. private void OnThicknessChanged (object? sender, EventArgs e)
  53. {
  54. if (IsInitialized)
  55. {
  56. ShowHideDrawIndicator ();
  57. }
  58. }
  59. private void ShowHideDrawIndicator ()
  60. {
  61. if (View.Diagnostics.HasFlag (ViewDiagnosticFlags.DrawIndicator) && Thickness != Thickness.Empty)
  62. {
  63. if (DrawIndicator is null)
  64. {
  65. DrawIndicator = new()
  66. {
  67. Id = "DrawIndicator",
  68. X = 1,
  69. Style = new SpinnerStyle.Dots2 (),
  70. SpinDelay = 0,
  71. Visible = false
  72. };
  73. Add (DrawIndicator);
  74. }
  75. }
  76. else if (DrawIndicator is { })
  77. {
  78. Remove (DrawIndicator);
  79. DrawIndicator!.Dispose ();
  80. DrawIndicator = null;
  81. }
  82. }
  83. internal void AdvanceDrawIndicator ()
  84. {
  85. if (View.Diagnostics.HasFlag (ViewDiagnosticFlags.DrawIndicator) && DrawIndicator is { })
  86. {
  87. DrawIndicator.AdvanceAnimation (false);
  88. DrawIndicator.Render ();
  89. }
  90. }
  91. #if SUBVIEW_BASED_BORDER
  92. private Line _left;
  93. /// <summary>
  94. /// The close button for the border. Set to <see cref="View.Visible"/>, to <see langword="true"/> to enable.
  95. /// </summary>
  96. public Button CloseButton { get; internal set; }
  97. #endif
  98. /// <inheritdoc/>
  99. public override void BeginInit ()
  100. {
  101. base.BeginInit ();
  102. ShowHideDrawIndicator ();
  103. #if SUBVIEW_BASED_BORDER
  104. if (Parent is { })
  105. {
  106. // Left
  107. _left = new ()
  108. {
  109. Orientation = Orientation.Vertical,
  110. };
  111. Add (_left);
  112. CloseButton = new Button ()
  113. {
  114. Text = "X",
  115. CanFocus = true,
  116. Visible = false,
  117. };
  118. CloseButton.Accept += (s, e) =>
  119. {
  120. e.Cancel = Parent.InvokeCommand (Command.QuitToplevel) == true;
  121. };
  122. Add (CloseButton);
  123. LayoutStarted += OnLayoutStarted;
  124. }
  125. #endif
  126. }
  127. #if SUBVIEW_BASED_BORDER
  128. private void OnLayoutStarted (object sender, LayoutEventArgs e)
  129. {
  130. _left.Border.LineStyle = LineStyle;
  131. _left.X = Thickness.Left - 1;
  132. _left.Y = Thickness.Top - 1;
  133. _left.Width = 1;
  134. _left.Height = Height;
  135. CloseButton.X = Pos.AnchorEnd (Thickness.Right / 2 + 1) -
  136. (Pos.Right (CloseButton) -
  137. Pos.Left (CloseButton));
  138. CloseButton.Y = 0;
  139. }
  140. #endif
  141. /// <summary>
  142. /// The color scheme for the Border. If set to <see langword="null"/>, gets the <see cref="Adornment.Parent"/>
  143. /// scheme. color scheme.
  144. /// </summary>
  145. public override ColorScheme? ColorScheme
  146. {
  147. get => base.ColorScheme ?? Parent?.ColorScheme;
  148. set
  149. {
  150. base.ColorScheme = value;
  151. Parent?.SetNeedsDraw ();
  152. }
  153. }
  154. internal Rectangle GetBorderRectangle ()
  155. {
  156. Rectangle screenRect = ViewportToScreen (Viewport);
  157. return new (
  158. screenRect.X + Math.Max (0, Thickness.Left - 1),
  159. screenRect.Y + Math.Max (0, Thickness.Top - 1),
  160. Math.Max (
  161. 0,
  162. screenRect.Width
  163. - Math.Max (
  164. 0,
  165. Math.Max (0, Thickness.Left - 1)
  166. + Math.Max (0, Thickness.Right - 1)
  167. )
  168. ),
  169. Math.Max (
  170. 0,
  171. screenRect.Height
  172. - Math.Max (
  173. 0,
  174. Math.Max (0, Thickness.Top - 1)
  175. + Math.Max (0, Thickness.Bottom - 1)
  176. )
  177. )
  178. );
  179. }
  180. /// <summary>
  181. /// Sets the style of the border by changing the <see cref="Thickness"/>. This is a helper API for setting the
  182. /// <see cref="Thickness"/> to <c>(1,1,1,1)</c> and setting the line style of the views that comprise the border. If
  183. /// set to <see cref="LineStyle.None"/> no border will be drawn.
  184. /// </summary>
  185. public LineStyle LineStyle
  186. {
  187. get
  188. {
  189. if (_lineStyle.HasValue)
  190. {
  191. return _lineStyle.Value;
  192. }
  193. // TODO: Make Border.LineStyle inherit from the SuperView hierarchy
  194. // TODO: Right now, Window and FrameView use CM to set BorderStyle, which negates
  195. // TODO: all this.
  196. return Parent?.SuperView?.BorderStyle ?? LineStyle.None;
  197. }
  198. set => _lineStyle = value;
  199. }
  200. private BorderSettings _settings = BorderSettings.Title;
  201. /// <summary>
  202. /// Gets or sets the settings for the border.
  203. /// </summary>
  204. public BorderSettings Settings
  205. {
  206. get => _settings;
  207. set
  208. {
  209. if (value == _settings)
  210. {
  211. return;
  212. }
  213. _settings = value;
  214. Parent?.SetNeedsDraw ();
  215. }
  216. }
  217. #region Mouse Support
  218. private Color? _savedForeColor;
  219. private void Border_Highlight (object? sender, CancelEventArgs<HighlightStyle> e)
  220. {
  221. if (!Parent!.Arrangement.HasFlag (ViewArrangement.Movable))
  222. {
  223. e.Cancel = true;
  224. return;
  225. }
  226. if (e.NewValue.HasFlag (HighlightStyle.Pressed))
  227. {
  228. if (!_savedForeColor.HasValue)
  229. {
  230. _savedForeColor = ColorScheme!.Normal.Foreground;
  231. }
  232. var cs = new ColorScheme (ColorScheme)
  233. {
  234. Normal = new (ColorScheme!.Normal.Foreground.GetHighlightColor (), ColorScheme.Normal.Background)
  235. };
  236. ColorScheme = cs;
  237. }
  238. if (e.NewValue == HighlightStyle.None && _savedForeColor.HasValue)
  239. {
  240. var cs = new ColorScheme (ColorScheme)
  241. {
  242. Normal = new (_savedForeColor.Value, ColorScheme!.Normal.Background)
  243. };
  244. ColorScheme = cs;
  245. }
  246. Parent?.SetNeedsDraw ();
  247. e.Cancel = true;
  248. }
  249. private Point? _dragPosition;
  250. private Point _startGrabPoint;
  251. /// <inheritdoc/>
  252. protected override bool OnMouseEvent (MouseEventArgs mouseEvent)
  253. {
  254. // BUGBUG: See https://github.com/gui-cs/Terminal.Gui/issues/3312
  255. if (!_dragPosition.HasValue && mouseEvent.Flags.HasFlag (MouseFlags.Button1Pressed)
  256. // HACK: Prevents Window from being draggable if it's Top
  257. //&& Parent is Toplevel { Modal: true }
  258. )
  259. {
  260. Parent!.SetFocus ();
  261. if (!Parent!.Arrangement.HasFlag (ViewArrangement.Movable)
  262. && !Parent!.Arrangement.HasFlag (ViewArrangement.BottomResizable)
  263. && !Parent!.Arrangement.HasFlag (ViewArrangement.TopResizable)
  264. && !Parent!.Arrangement.HasFlag (ViewArrangement.LeftResizable)
  265. && !Parent!.Arrangement.HasFlag (ViewArrangement.RightResizable)
  266. )
  267. {
  268. return false;
  269. }
  270. // Only start grabbing if the user clicks in the Thickness area
  271. // Adornment.Contains takes Parent SuperView=relative coords.
  272. if (Contains (new (mouseEvent.Position.X + Parent.Frame.X + Frame.X, mouseEvent.Position.Y + Parent.Frame.Y + Frame.Y)))
  273. {
  274. if (Arranging != ViewArrangement.Fixed)
  275. {
  276. EndArrangeMode ();
  277. }
  278. // Set the start grab point to the Frame coords
  279. _startGrabPoint = new (mouseEvent.Position.X + Frame.X, mouseEvent.Position.Y + Frame.Y);
  280. _dragPosition = mouseEvent.Position;
  281. Application.GrabMouse (this);
  282. SetPressedHighlight (HighlightStyle);
  283. // Arrange Mode -
  284. // TODO: This code can be refactored to be more readable and maintainable.
  285. // If not resizable, but movable: Drag anywhere is move
  286. // If resizable and movable: Drag on top is move, other 3 sides are size
  287. // If not movable, but resizable: Drag on any side sizes.
  288. // Get rectangle representing Thickness.Top
  289. // If mouse is in that rectangle, set _arranging to ViewArrangement.Movable
  290. Rectangle sideRect;
  291. // If mouse is in any other rectangle, set _arranging to ViewArrangement.<side>
  292. if (Parent!.Arrangement.HasFlag (ViewArrangement.LeftResizable))
  293. {
  294. sideRect = new (Frame.X, Frame.Y + Thickness.Top, Thickness.Left, Frame.Height - Thickness.Top - Thickness.Bottom);
  295. if (sideRect.Contains (_startGrabPoint))
  296. {
  297. EnterArrangeMode (ViewArrangement.LeftResizable);
  298. return true;
  299. }
  300. }
  301. if (Parent!.Arrangement.HasFlag (ViewArrangement.RightResizable))
  302. {
  303. sideRect = new (
  304. Frame.X + Frame.Width - Thickness.Right,
  305. Frame.Y + Thickness.Top,
  306. Thickness.Right,
  307. Frame.Height - Thickness.Top - Thickness.Bottom);
  308. if (sideRect.Contains (_startGrabPoint))
  309. {
  310. EnterArrangeMode (ViewArrangement.RightResizable);
  311. return true;
  312. }
  313. }
  314. if (Parent!.Arrangement.HasFlag (ViewArrangement.TopResizable) && !Parent!.Arrangement.HasFlag (ViewArrangement.Movable))
  315. {
  316. sideRect = new (Frame.X + Thickness.Left, Frame.Y, Frame.Width - Thickness.Left - Thickness.Right, Thickness.Top);
  317. if (sideRect.Contains (_startGrabPoint))
  318. {
  319. EnterArrangeMode (ViewArrangement.TopResizable);
  320. return true;
  321. }
  322. }
  323. if (Parent!.Arrangement.HasFlag (ViewArrangement.BottomResizable))
  324. {
  325. sideRect = new (
  326. Frame.X + Thickness.Left,
  327. Frame.Y + Frame.Height - Thickness.Bottom,
  328. Frame.Width - Thickness.Left - Thickness.Right,
  329. Thickness.Bottom);
  330. if (sideRect.Contains (_startGrabPoint))
  331. {
  332. EnterArrangeMode (ViewArrangement.BottomResizable);
  333. return true;
  334. }
  335. }
  336. if (Parent!.Arrangement.HasFlag (ViewArrangement.BottomResizable) && Parent!.Arrangement.HasFlag (ViewArrangement.LeftResizable))
  337. {
  338. sideRect = new (Frame.X, Frame.Height - Thickness.Top, Thickness.Left, Thickness.Bottom);
  339. if (sideRect.Contains (_startGrabPoint))
  340. {
  341. EnterArrangeMode (ViewArrangement.BottomResizable | ViewArrangement.LeftResizable);
  342. return true;
  343. }
  344. }
  345. if (Parent!.Arrangement.HasFlag (ViewArrangement.BottomResizable) && Parent!.Arrangement.HasFlag (ViewArrangement.RightResizable))
  346. {
  347. sideRect = new (Frame.X + Frame.Width - Thickness.Right, Frame.Height - Thickness.Top, Thickness.Right, Thickness.Bottom);
  348. if (sideRect.Contains (_startGrabPoint))
  349. {
  350. EnterArrangeMode (ViewArrangement.BottomResizable | ViewArrangement.RightResizable);
  351. return true;
  352. }
  353. }
  354. if (Parent!.Arrangement.HasFlag (ViewArrangement.TopResizable) && Parent!.Arrangement.HasFlag (ViewArrangement.RightResizable))
  355. {
  356. sideRect = new (Frame.X + Frame.Width - Thickness.Right, Frame.Y, Thickness.Right, Thickness.Top);
  357. if (sideRect.Contains (_startGrabPoint))
  358. {
  359. EnterArrangeMode (ViewArrangement.TopResizable | ViewArrangement.RightResizable);
  360. return true;
  361. }
  362. }
  363. if (Parent!.Arrangement.HasFlag (ViewArrangement.TopResizable) && Parent!.Arrangement.HasFlag (ViewArrangement.LeftResizable))
  364. {
  365. sideRect = new (Frame.X, Frame.Y, Thickness.Left, Thickness.Top);
  366. if (sideRect.Contains (_startGrabPoint))
  367. {
  368. EnterArrangeMode (ViewArrangement.TopResizable | ViewArrangement.LeftResizable);
  369. return true;
  370. }
  371. }
  372. if (Parent!.Arrangement.HasFlag (ViewArrangement.Movable))
  373. {
  374. //sideRect = new (Frame.X + Thickness.Left, Frame.Y, Frame.Width - Thickness.Left - Thickness.Right, Thickness.Top);
  375. //if (sideRect.Contains (_startGrabPoint))
  376. {
  377. EnterArrangeMode (ViewArrangement.Movable);
  378. return true;
  379. }
  380. }
  381. }
  382. return true;
  383. }
  384. if (mouseEvent.Flags is (MouseFlags.Button1Pressed | MouseFlags.ReportMousePosition) && Application.MouseGrabView == this)
  385. {
  386. if (_dragPosition.HasValue)
  387. {
  388. if (Parent!.SuperView is null)
  389. {
  390. // Redraw the entire app window.
  391. Application.Top!.SetNeedsDraw ();
  392. }
  393. else
  394. {
  395. Parent.SuperView.SetNeedsDraw ();
  396. }
  397. _dragPosition = mouseEvent.Position;
  398. Point parentLoc = Parent.SuperView?.ScreenToViewport (new (mouseEvent.ScreenPosition.X, mouseEvent.ScreenPosition.Y))
  399. ?? mouseEvent.ScreenPosition;
  400. int minHeight = Thickness.Vertical + Parent!.Margin!.Thickness.Bottom;
  401. int minWidth = Thickness.Horizontal + Parent!.Margin!.Thickness.Right;
  402. // TODO: This code can be refactored to be more readable and maintainable.
  403. switch (Arranging)
  404. {
  405. case ViewArrangement.Movable:
  406. GetLocationEnsuringFullVisibility (
  407. Parent,
  408. parentLoc.X - _startGrabPoint.X,
  409. parentLoc.Y - _startGrabPoint.Y,
  410. out int nx,
  411. out int ny
  412. //,
  413. // out _
  414. );
  415. Parent.X = parentLoc.X - _startGrabPoint.X;
  416. Parent.Y = parentLoc.Y - _startGrabPoint.Y;
  417. break;
  418. case ViewArrangement.TopResizable:
  419. // Get how much the mouse has moved since the start of the drag
  420. // and adjust the height of the parent by that amount
  421. int deltaY = parentLoc.Y - Parent.Frame.Y;
  422. int newHeight = Math.Max (minHeight, Parent.Frame.Height - deltaY);
  423. if (newHeight != Parent.Frame.Height)
  424. {
  425. Parent.Height = newHeight;
  426. Parent.Y = parentLoc.Y - _startGrabPoint.Y;
  427. }
  428. break;
  429. case ViewArrangement.BottomResizable:
  430. Parent.Height = Math.Max (minHeight, parentLoc.Y - Parent.Frame.Y + Parent!.Margin.Thickness.Bottom + 1);
  431. break;
  432. case ViewArrangement.LeftResizable:
  433. // Get how much the mouse has moved since the start of the drag
  434. // and adjust the height of the parent by that amount
  435. int deltaX = parentLoc.X - Parent.Frame.X;
  436. int newWidth = Math.Max (minWidth, Parent.Frame.Width - deltaX);
  437. if (newWidth != Parent.Frame.Width)
  438. {
  439. Parent.Width = newWidth;
  440. Parent.X = parentLoc.X - _startGrabPoint.X;
  441. }
  442. break;
  443. case ViewArrangement.RightResizable:
  444. Parent.Width = Math.Max (minWidth, parentLoc.X - Parent.Frame.X + Parent!.Margin.Thickness.Right + 1);
  445. break;
  446. case ViewArrangement.BottomResizable | ViewArrangement.RightResizable:
  447. Parent.Width = Math.Max (minWidth, parentLoc.X - Parent.Frame.X + Parent!.Margin.Thickness.Right + 1);
  448. Parent.Height = Math.Max (minHeight, parentLoc.Y - Parent.Frame.Y + Parent!.Margin.Thickness.Bottom + 1);
  449. break;
  450. case ViewArrangement.BottomResizable | ViewArrangement.LeftResizable:
  451. int dX = parentLoc.X - Parent.Frame.X;
  452. int newW = Math.Max (minWidth, Parent.Frame.Width - dX);
  453. if (newW != Parent.Frame.Width)
  454. {
  455. Parent.Width = newW;
  456. Parent.X = parentLoc.X - _startGrabPoint.X;
  457. }
  458. Parent.Height = Math.Max (minHeight, parentLoc.Y - Parent.Frame.Y + Parent!.Margin.Thickness.Bottom + 1);
  459. break;
  460. case ViewArrangement.TopResizable | ViewArrangement.RightResizable:
  461. int dY = parentLoc.Y - Parent.Frame.Y;
  462. int newH = Math.Max (minHeight, Parent.Frame.Height - dY);
  463. if (newH != Parent.Frame.Height)
  464. {
  465. Parent.Height = newH;
  466. Parent.Y = parentLoc.Y - _startGrabPoint.Y;
  467. }
  468. Parent.Width = Math.Max (minWidth, parentLoc.X - Parent.Frame.X + Parent!.Margin.Thickness.Right + 1);
  469. break;
  470. case ViewArrangement.TopResizable | ViewArrangement.LeftResizable:
  471. int dY2 = parentLoc.Y - Parent.Frame.Y;
  472. int newH2 = Math.Max (minHeight, Parent.Frame.Height - dY2);
  473. if (newH2 != Parent.Frame.Height)
  474. {
  475. Parent.Height = newH2;
  476. Parent.Y = parentLoc.Y - _startGrabPoint.Y;
  477. }
  478. int dX2 = parentLoc.X - Parent.Frame.X;
  479. int newW2 = Math.Max (minWidth, Parent.Frame.Width - dX2);
  480. if (newW2 != Parent.Frame.Width)
  481. {
  482. Parent.Width = newW2;
  483. Parent.X = parentLoc.X - _startGrabPoint.X;
  484. }
  485. break;
  486. }
  487. return true;
  488. }
  489. }
  490. if (mouseEvent.Flags.HasFlag (MouseFlags.Button1Released) && _dragPosition.HasValue)
  491. {
  492. _dragPosition = null;
  493. Application.UngrabMouse ();
  494. SetPressedHighlight (HighlightStyle.None);
  495. EndArrangeMode ();
  496. return true;
  497. }
  498. return false;
  499. }
  500. private void Application_GrabbingMouse (object? sender, GrabMouseEventArgs e)
  501. {
  502. if (Application.MouseGrabView == this && _dragPosition.HasValue)
  503. {
  504. e.Cancel = true;
  505. }
  506. }
  507. private void Application_UnGrabbingMouse (object? sender, GrabMouseEventArgs e)
  508. {
  509. if (Application.MouseGrabView == this && _dragPosition.HasValue)
  510. {
  511. e.Cancel = true;
  512. }
  513. }
  514. #endregion Mouse Support
  515. /// <inheritdoc/>
  516. protected override bool OnDrawingContent ()
  517. {
  518. if (Thickness == Thickness.Empty)
  519. {
  520. return true;
  521. }
  522. Rectangle screenBounds = ViewportToScreen (Viewport);
  523. // TODO: v2 - this will eventually be two controls: "BorderView" and "Label" (for the title)
  524. // The border adornment (and title) are drawn at the outermost edge of border;
  525. // For Border
  526. // ...thickness extends outward (border/title is always as far in as possible)
  527. // PERF: How about a call to Rectangle.Offset?
  528. Rectangle borderBounds = GetBorderRectangle ();
  529. int topTitleLineY = borderBounds.Y;
  530. int titleY = borderBounds.Y;
  531. var titleBarsLength = 0; // the little vertical thingies
  532. int maxTitleWidth = Math.Max (
  533. 0,
  534. Math.Min (
  535. Parent?.TitleTextFormatter.FormatAndGetSize ().Width ?? 0,
  536. Math.Min (screenBounds.Width - 4, borderBounds.Width - 4)
  537. )
  538. );
  539. if (Parent is { })
  540. {
  541. Parent.TitleTextFormatter.ConstrainToSize = new (maxTitleWidth, 1);
  542. }
  543. int sideLineLength = borderBounds.Height;
  544. bool canDrawBorder = borderBounds is { Width: > 0, Height: > 0 };
  545. LineStyle lineStyle = LineStyle;
  546. if (Settings.FastHasFlags (BorderSettings.Title))
  547. {
  548. if (Thickness.Top == 2)
  549. {
  550. topTitleLineY = borderBounds.Y - 1;
  551. titleY = topTitleLineY + 1;
  552. titleBarsLength = 2;
  553. }
  554. // ┌────┐
  555. //┌┘View└
  556. //│
  557. if (Thickness.Top == 3)
  558. {
  559. topTitleLineY = borderBounds.Y - (Thickness.Top - 1);
  560. titleY = topTitleLineY + 1;
  561. titleBarsLength = 3;
  562. sideLineLength++;
  563. }
  564. // ┌────┐
  565. //┌┘View└
  566. //│
  567. if (Thickness.Top > 3)
  568. {
  569. topTitleLineY = borderBounds.Y - 2;
  570. titleY = topTitleLineY + 1;
  571. titleBarsLength = 3;
  572. sideLineLength++;
  573. }
  574. }
  575. if (Parent is { }
  576. && canDrawBorder
  577. && Thickness.Top > 0
  578. && maxTitleWidth > 0
  579. && Settings.FastHasFlags (BorderSettings.Title)
  580. && !string.IsNullOrEmpty (Parent?.Title))
  581. {
  582. Attribute focus = Parent.GetNormalColor ();
  583. if (Parent.SuperView is { } && Parent.SuperView?.SubViews!.Count (s => s.CanFocus) > 1)
  584. {
  585. // Only use focus color if there are multiple focusable views
  586. focus = GetFocusColor ();
  587. }
  588. Rectangle titleRect = new (borderBounds.X + 2, titleY, maxTitleWidth, 1);
  589. Parent.TitleTextFormatter.Draw (
  590. titleRect,
  591. Parent.HasFocus ? focus : GetNormalColor (),
  592. Parent.HasFocus ? focus : GetHotNormalColor ());
  593. Parent?.LineCanvas.Exclude (new (titleRect));
  594. }
  595. if (canDrawBorder && LineStyle != LineStyle.None)
  596. {
  597. LineCanvas? lc = Parent?.LineCanvas;
  598. bool drawTop = Thickness.Top > 0 && Frame.Width > 1 && Frame.Height >= 1;
  599. bool drawLeft = Thickness.Left > 0 && (Frame.Height > 1 || Thickness.Top == 0);
  600. bool drawBottom = Thickness.Bottom > 0 && Frame.Width > 1 && Frame.Height > 1;
  601. bool drawRight = Thickness.Right > 0 && (Frame.Height > 1 || Thickness.Top == 0);
  602. Attribute prevAttr = Driver?.GetAttribute () ?? Attribute.Default;
  603. if (ColorScheme is { })
  604. {
  605. SetAttribute (GetNormalColor ());
  606. }
  607. else
  608. {
  609. SetAttribute (Parent!.GetNormalColor ());
  610. }
  611. if (drawTop)
  612. {
  613. // ╔╡Title╞═════╗
  614. // ╔╡╞═════╗
  615. if (borderBounds.Width < 4 || !Settings.FastHasFlags (BorderSettings.Title) || string.IsNullOrEmpty (Parent?.Title))
  616. {
  617. // ╔╡╞╗ should be ╔══╗
  618. lc?.AddLine (
  619. new (borderBounds.Location.X, titleY),
  620. borderBounds.Width,
  621. Orientation.Horizontal,
  622. lineStyle,
  623. Driver?.GetAttribute ()
  624. );
  625. }
  626. else
  627. {
  628. // ┌────┐
  629. //┌┘View└
  630. //│
  631. if (Thickness.Top == 2)
  632. {
  633. lc?.AddLine (
  634. new (borderBounds.X + 1, topTitleLineY),
  635. Math.Min (borderBounds.Width - 2, maxTitleWidth + 2),
  636. Orientation.Horizontal,
  637. lineStyle,
  638. Driver?.GetAttribute ()
  639. );
  640. }
  641. // ┌────┐
  642. //┌┘View└
  643. //│
  644. if (borderBounds.Width >= 4 && Thickness.Top > 2)
  645. {
  646. lc?.AddLine (
  647. new (borderBounds.X + 1, topTitleLineY),
  648. Math.Min (borderBounds.Width - 2, maxTitleWidth + 2),
  649. Orientation.Horizontal,
  650. lineStyle,
  651. Driver?.GetAttribute ()
  652. );
  653. lc?.AddLine (
  654. new (borderBounds.X + 1, topTitleLineY + 2),
  655. Math.Min (borderBounds.Width - 2, maxTitleWidth + 2),
  656. Orientation.Horizontal,
  657. lineStyle,
  658. Driver?.GetAttribute ()
  659. );
  660. }
  661. // ╔╡Title╞═════╗
  662. // Add a short horiz line for ╔╡
  663. lc?.AddLine (
  664. new (borderBounds.Location.X, titleY),
  665. 2,
  666. Orientation.Horizontal,
  667. lineStyle,
  668. Driver?.GetAttribute ()
  669. );
  670. // Add a vert line for ╔╡
  671. lc?.AddLine (
  672. new (borderBounds.X + 1, topTitleLineY),
  673. titleBarsLength,
  674. Orientation.Vertical,
  675. LineStyle.Single,
  676. Driver?.GetAttribute ()
  677. );
  678. // Add a vert line for ╞
  679. lc?.AddLine (
  680. new (
  681. borderBounds.X
  682. + 1
  683. + Math.Min (borderBounds.Width - 2, maxTitleWidth + 2)
  684. - 1,
  685. topTitleLineY
  686. ),
  687. titleBarsLength,
  688. Orientation.Vertical,
  689. LineStyle.Single,
  690. Driver?.GetAttribute ()
  691. );
  692. // Add the right hand line for ╞═════╗
  693. lc?.AddLine (
  694. new (
  695. borderBounds.X
  696. + 1
  697. + Math.Min (borderBounds.Width - 2, maxTitleWidth + 2)
  698. - 1,
  699. titleY
  700. ),
  701. borderBounds.Width - Math.Min (borderBounds.Width - 2, maxTitleWidth + 2),
  702. Orientation.Horizontal,
  703. lineStyle,
  704. Driver?.GetAttribute ()
  705. );
  706. }
  707. }
  708. #if !SUBVIEW_BASED_BORDER
  709. if (drawLeft)
  710. {
  711. lc?.AddLine (
  712. new (borderBounds.Location.X, titleY),
  713. sideLineLength,
  714. Orientation.Vertical,
  715. lineStyle,
  716. Driver?.GetAttribute ()
  717. );
  718. }
  719. #endif
  720. if (drawBottom)
  721. {
  722. lc?.AddLine (
  723. new (borderBounds.X, borderBounds.Y + borderBounds.Height - 1),
  724. borderBounds.Width,
  725. Orientation.Horizontal,
  726. lineStyle,
  727. Driver?.GetAttribute ()
  728. );
  729. }
  730. if (drawRight)
  731. {
  732. lc?.AddLine (
  733. new (borderBounds.X + borderBounds.Width - 1, titleY),
  734. sideLineLength,
  735. Orientation.Vertical,
  736. lineStyle,
  737. Driver?.GetAttribute ()
  738. );
  739. }
  740. SetAttribute (prevAttr);
  741. // TODO: This should be moved to LineCanvas as a new BorderStyle.Ruler
  742. if (Diagnostics.HasFlag (ViewDiagnosticFlags.Ruler))
  743. {
  744. // Top
  745. var hruler = new Ruler { Length = screenBounds.Width, Orientation = Orientation.Horizontal };
  746. if (drawTop)
  747. {
  748. hruler.Draw (new (screenBounds.X, screenBounds.Y));
  749. }
  750. // Redraw title
  751. if (drawTop && maxTitleWidth > 0 && Settings.FastHasFlags (BorderSettings.Title))
  752. {
  753. Parent!.TitleTextFormatter.Draw (
  754. new (borderBounds.X + 2, titleY, maxTitleWidth, 1),
  755. Parent.HasFocus ? Parent.GetFocusColor () : Parent.GetNormalColor (),
  756. Parent.HasFocus ? Parent.GetFocusColor () : Parent.GetNormalColor ());
  757. }
  758. //Left
  759. var vruler = new Ruler { Length = screenBounds.Height - 2, Orientation = Orientation.Vertical };
  760. if (drawLeft)
  761. {
  762. vruler.Draw (new (screenBounds.X, screenBounds.Y + 1), 1);
  763. }
  764. // Bottom
  765. if (drawBottom)
  766. {
  767. hruler.Draw (new (screenBounds.X, screenBounds.Y + screenBounds.Height - 1));
  768. }
  769. // Right
  770. if (drawRight)
  771. {
  772. vruler.Draw (new (screenBounds.X + screenBounds.Width - 1, screenBounds.Y + 1), 1);
  773. }
  774. }
  775. // TODO: This should not be done on each draw?
  776. if (Settings.FastHasFlags (BorderSettings.Gradient))
  777. {
  778. SetupGradientLineCanvas (lc!, screenBounds);
  779. }
  780. else
  781. {
  782. lc!.Fill = null;
  783. }
  784. }
  785. return true;
  786. ;
  787. }
  788. /// <summary>
  789. /// Gets the subview used to render <see cref="ViewDiagnosticFlags.DrawIndicator"/>.
  790. /// </summary>
  791. public SpinnerView? DrawIndicator { get; private set; }
  792. private void SetupGradientLineCanvas (LineCanvas lc, Rectangle rect)
  793. {
  794. GetAppealingGradientColors (out List<Color> stops, out List<int> steps);
  795. var g = new Gradient (stops, steps);
  796. var fore = new GradientFill (rect, g, GradientDirection.Diagonal);
  797. var back = new SolidFill (GetNormalColor ().Background);
  798. lc.Fill = new (fore, back);
  799. }
  800. private static void GetAppealingGradientColors (out List<Color> stops, out List<int> steps)
  801. {
  802. // Define the colors of the gradient stops with more appealing colors
  803. stops =
  804. [
  805. new (0, 128, 255), // Bright Blue
  806. new (0, 255, 128), // Bright Green
  807. new (255, 255), // Bright Yellow
  808. new (255, 128), // Bright Orange
  809. new (255, 0, 128)
  810. ];
  811. // Define the number of steps between each color for smoother transitions
  812. // If we pass only a single value then it will assume equal steps between all pairs
  813. steps = [15];
  814. }
  815. internal ViewArrangement Arranging { get; set; }
  816. private Button? _moveButton; // always top-left
  817. private Button? _allSizeButton;
  818. private Button? _leftSizeButton;
  819. private Button? _rightSizeButton;
  820. private Button? _topSizeButton;
  821. private Button? _bottomSizeButton;
  822. /// <summary>
  823. /// Starts "Arrange Mode" where <see cref="Adornment.Parent"/> can be moved and/or resized using the mouse
  824. /// or keyboard. If <paramref name="arrangement"/> is <see cref="ViewArrangement.Fixed"/> keyboard mode is enabled.
  825. /// </summary>
  826. /// <remarks>
  827. /// Arrange Mode is exited by the user pressing <see cref="Application.ArrangeKey"/>, <see cref="Key.Esc"/>, or by
  828. /// clicking
  829. /// the mouse out of the <see cref="Adornment.Parent"/>'s Frame.
  830. /// </remarks>
  831. /// <returns></returns>
  832. public bool? EnterArrangeMode (ViewArrangement arrangement)
  833. {
  834. Debug.Assert (Arranging == ViewArrangement.Fixed);
  835. if (!Parent!.Arrangement.HasFlag (ViewArrangement.Movable)
  836. && !Parent!.Arrangement.HasFlag (ViewArrangement.BottomResizable)
  837. && !Parent!.Arrangement.HasFlag (ViewArrangement.TopResizable)
  838. && !Parent!.Arrangement.HasFlag (ViewArrangement.LeftResizable)
  839. && !Parent!.Arrangement.HasFlag (ViewArrangement.RightResizable)
  840. )
  841. {
  842. return false;
  843. }
  844. // Add Commands and Keybindigs - Note it's ok these get added each time. KeyBindings are cleared in EndArrange()
  845. AddArrangeModeKeyBindings ();
  846. Application.MouseEvent += ApplicationOnMouseEvent;
  847. // TODO: This code can be refactored to be more readable and maintainable.
  848. // Create buttons for resizing and moving
  849. if (Parent!.Arrangement.HasFlag (ViewArrangement.Movable))
  850. {
  851. Debug.Assert (_moveButton is null);
  852. _moveButton = new ()
  853. {
  854. Id = "moveButton",
  855. CanFocus = true,
  856. Width = 1,
  857. Height = 1,
  858. NoDecorations = true,
  859. NoPadding = true,
  860. ShadowStyle = ShadowStyle.None,
  861. Text = $"{Glyphs.Move}",
  862. Visible = false,
  863. Data = ViewArrangement.Movable
  864. };
  865. Add (_moveButton);
  866. }
  867. if (Parent!.Arrangement.HasFlag (ViewArrangement.Resizable))
  868. {
  869. Debug.Assert (_allSizeButton is null);
  870. _allSizeButton = new ()
  871. {
  872. Id = "allSizeButton",
  873. CanFocus = true,
  874. Width = 1,
  875. Height = 1,
  876. NoDecorations = true,
  877. NoPadding = true,
  878. ShadowStyle = ShadowStyle.None,
  879. Text = $"{Glyphs.SizeBottomRight}",
  880. X = Pos.AnchorEnd (),
  881. Y = Pos.AnchorEnd (),
  882. Visible = false,
  883. Data = ViewArrangement.Resizable
  884. };
  885. Add (_allSizeButton);
  886. }
  887. if (Parent!.Arrangement.HasFlag (ViewArrangement.TopResizable))
  888. {
  889. Debug.Assert (_topSizeButton is null);
  890. _topSizeButton = new ()
  891. {
  892. Id = "topSizeButton",
  893. CanFocus = true,
  894. Width = 1,
  895. Height = 1,
  896. NoDecorations = true,
  897. NoPadding = true,
  898. ShadowStyle = ShadowStyle.None,
  899. Text = $"{Glyphs.SizeVertical}",
  900. X = Pos.Center () + Parent!.Margin!.Thickness.Horizontal,
  901. Y = 0,
  902. Visible = false,
  903. Data = ViewArrangement.TopResizable
  904. };
  905. Add (_topSizeButton);
  906. }
  907. if (Parent!.Arrangement.HasFlag (ViewArrangement.RightResizable))
  908. {
  909. Debug.Assert (_rightSizeButton is null);
  910. _rightSizeButton = new ()
  911. {
  912. Id = "rightSizeButton",
  913. CanFocus = true,
  914. Width = 1,
  915. Height = 1,
  916. NoDecorations = true,
  917. NoPadding = true,
  918. ShadowStyle = ShadowStyle.None,
  919. Text = $"{Glyphs.SizeHorizontal}",
  920. X = Pos.AnchorEnd (),
  921. Y = Pos.Center () + Parent!.Margin!.Thickness.Vertical / 2,
  922. Visible = false,
  923. Data = ViewArrangement.RightResizable
  924. };
  925. Add (_rightSizeButton);
  926. }
  927. if (Parent!.Arrangement.HasFlag (ViewArrangement.LeftResizable))
  928. {
  929. Debug.Assert (_leftSizeButton is null);
  930. _leftSizeButton = new ()
  931. {
  932. Id = "leftSizeButton",
  933. CanFocus = true,
  934. Width = 1,
  935. Height = 1,
  936. NoDecorations = true,
  937. NoPadding = true,
  938. ShadowStyle = ShadowStyle.None,
  939. Text = $"{Glyphs.SizeHorizontal}",
  940. X = 0,
  941. Y = Pos.Center () + Parent!.Margin!.Thickness.Vertical / 2,
  942. Visible = false,
  943. Data = ViewArrangement.LeftResizable
  944. };
  945. Add (_leftSizeButton);
  946. }
  947. if (Parent!.Arrangement.HasFlag (ViewArrangement.BottomResizable))
  948. {
  949. Debug.Assert (_bottomSizeButton is null);
  950. _bottomSizeButton = new ()
  951. {
  952. Id = "bottomSizeButton",
  953. CanFocus = true,
  954. Width = 1,
  955. Height = 1,
  956. NoDecorations = true,
  957. NoPadding = true,
  958. ShadowStyle = ShadowStyle.None,
  959. Text = $"{Glyphs.SizeVertical}",
  960. X = Pos.Center () + Parent!.Margin!.Thickness.Horizontal / 2,
  961. Y = Pos.AnchorEnd (),
  962. Visible = false,
  963. Data = ViewArrangement.BottomResizable
  964. };
  965. Add (_bottomSizeButton);
  966. }
  967. if (arrangement == ViewArrangement.Fixed)
  968. {
  969. // Keyboard mode
  970. if (Parent!.Arrangement.HasFlag (ViewArrangement.Movable))
  971. {
  972. _moveButton!.Visible = true;
  973. }
  974. if (Parent!.Arrangement.HasFlag (ViewArrangement.Resizable))
  975. {
  976. _allSizeButton!.Visible = true;
  977. }
  978. Arranging = ViewArrangement.Movable;
  979. CanFocus = true;
  980. SetFocus ();
  981. }
  982. else
  983. {
  984. // Mouse mode
  985. Arranging = arrangement;
  986. switch (Arranging)
  987. {
  988. case ViewArrangement.Movable:
  989. _moveButton!.Visible = true;
  990. break;
  991. case ViewArrangement.RightResizable | ViewArrangement.BottomResizable:
  992. case ViewArrangement.Resizable:
  993. _rightSizeButton!.Visible = true;
  994. _bottomSizeButton!.Visible = true;
  995. if (_allSizeButton is { })
  996. {
  997. _allSizeButton!.X = Pos.AnchorEnd ();
  998. _allSizeButton!.Y = Pos.AnchorEnd ();
  999. _allSizeButton!.Visible = true;
  1000. }
  1001. break;
  1002. case ViewArrangement.LeftResizable:
  1003. _leftSizeButton!.Visible = true;
  1004. break;
  1005. case ViewArrangement.RightResizable:
  1006. _rightSizeButton!.Visible = true;
  1007. break;
  1008. case ViewArrangement.TopResizable:
  1009. _topSizeButton!.Visible = true;
  1010. break;
  1011. case ViewArrangement.BottomResizable:
  1012. _bottomSizeButton!.Visible = true;
  1013. break;
  1014. case ViewArrangement.LeftResizable | ViewArrangement.BottomResizable:
  1015. _rightSizeButton!.Visible = true;
  1016. _bottomSizeButton!.Visible = true;
  1017. if (_allSizeButton is { })
  1018. {
  1019. _allSizeButton.X = 0;
  1020. _allSizeButton.Y = Pos.AnchorEnd ();
  1021. _allSizeButton.Visible = true;
  1022. }
  1023. break;
  1024. case ViewArrangement.LeftResizable | ViewArrangement.TopResizable:
  1025. _leftSizeButton!.Visible = true;
  1026. _topSizeButton!.Visible = true;
  1027. break;
  1028. case ViewArrangement.RightResizable | ViewArrangement.TopResizable:
  1029. _rightSizeButton!.Visible = true;
  1030. _topSizeButton!.Visible = true;
  1031. if (_allSizeButton is { })
  1032. {
  1033. _allSizeButton.X = Pos.AnchorEnd ();
  1034. _allSizeButton.Y = 0;
  1035. _allSizeButton.Visible = true;
  1036. }
  1037. break;
  1038. }
  1039. }
  1040. if (Arranging != ViewArrangement.Fixed)
  1041. {
  1042. if (arrangement == ViewArrangement.Fixed)
  1043. {
  1044. // Keyboard mode - enable nav
  1045. // TODO: Keyboard mode only supports sizing from bottom/right.
  1046. Arranging = (ViewArrangement)(Focused?.Data ?? ViewArrangement.Fixed);
  1047. }
  1048. return true;
  1049. }
  1050. // Hack for now
  1051. EndArrangeMode ();
  1052. return false;
  1053. }
  1054. private void AddArrangeModeKeyBindings ()
  1055. {
  1056. AddCommand (Command.Quit, EndArrangeMode);
  1057. AddCommand (
  1058. Command.Up,
  1059. () =>
  1060. {
  1061. if (Parent is null)
  1062. {
  1063. return false;
  1064. }
  1065. if (Arranging == ViewArrangement.Movable)
  1066. {
  1067. Parent!.Y = Parent.Y - 1;
  1068. }
  1069. if (Arranging == ViewArrangement.Resizable)
  1070. {
  1071. if (Parent!.Viewport.Height > 0)
  1072. {
  1073. Parent!.Height = Parent.Height! - 1;
  1074. }
  1075. }
  1076. return true;
  1077. });
  1078. AddCommand (
  1079. Command.Down,
  1080. () =>
  1081. {
  1082. if (Parent is null)
  1083. {
  1084. return false;
  1085. }
  1086. if (Arranging == ViewArrangement.Movable)
  1087. {
  1088. Parent!.Y = Parent.Y + 1;
  1089. }
  1090. if (Arranging == ViewArrangement.Resizable)
  1091. {
  1092. Parent!.Height = Parent.Height! + 1;
  1093. }
  1094. return true;
  1095. });
  1096. AddCommand (
  1097. Command.Left,
  1098. () =>
  1099. {
  1100. if (Parent is null)
  1101. {
  1102. return false;
  1103. }
  1104. if (Arranging == ViewArrangement.Movable)
  1105. {
  1106. Parent!.X = Parent.X - 1;
  1107. }
  1108. if (Arranging == ViewArrangement.Resizable)
  1109. {
  1110. if (Parent!.Viewport.Width > 0)
  1111. {
  1112. Parent!.Width = Parent.Width! - 1;
  1113. }
  1114. }
  1115. return true;
  1116. });
  1117. AddCommand (
  1118. Command.Right,
  1119. () =>
  1120. {
  1121. if (Parent is null)
  1122. {
  1123. return false;
  1124. }
  1125. if (Arranging == ViewArrangement.Movable)
  1126. {
  1127. Parent!.X = Parent.X + 1;
  1128. }
  1129. if (Arranging == ViewArrangement.Resizable)
  1130. {
  1131. Parent!.Width = Parent.Width! + 1;
  1132. }
  1133. return true;
  1134. });
  1135. AddCommand (
  1136. Command.Tab,
  1137. () =>
  1138. {
  1139. // BUGBUG: If an arrangable view has only arrangable subviews, it's not possible to activate
  1140. // BUGBUG: ArrangeMode with keyboard for the superview.
  1141. // BUGBUG: AdvanceFocus should be wise to this and when in ArrangeMode, should move across
  1142. // BUGBUG: the view hierachy.
  1143. AdvanceFocus (NavigationDirection.Forward, TabBehavior.TabStop);
  1144. Arranging = (ViewArrangement)(Focused?.Data ?? ViewArrangement.Fixed);
  1145. return true; // Always eat
  1146. });
  1147. AddCommand (
  1148. Command.BackTab,
  1149. () =>
  1150. {
  1151. AdvanceFocus (NavigationDirection.Backward, TabBehavior.TabStop);
  1152. Arranging = (ViewArrangement)(Focused?.Data ?? ViewArrangement.Fixed);
  1153. return true; // Always eat
  1154. });
  1155. HotKeyBindings.Add (Key.Esc, Command.Quit);
  1156. HotKeyBindings.Add (Application.ArrangeKey, Command.Quit);
  1157. HotKeyBindings.Add (Key.CursorUp, Command.Up);
  1158. HotKeyBindings.Add (Key.CursorDown, Command.Down);
  1159. HotKeyBindings.Add (Key.CursorLeft, Command.Left);
  1160. HotKeyBindings.Add (Key.CursorRight, Command.Right);
  1161. HotKeyBindings.Add (Key.Tab, Command.Tab);
  1162. HotKeyBindings.Add (Key.Tab.WithShift, Command.BackTab);
  1163. }
  1164. private void ApplicationOnMouseEvent (object? sender, MouseEventArgs e)
  1165. {
  1166. if (e.Flags != MouseFlags.Button1Clicked)
  1167. {
  1168. return;
  1169. }
  1170. // If mouse click is outside of Border.Thickness then exit Arrange Mode
  1171. // e.Position is screen relative
  1172. Point framePos = ScreenToFrame (e.ScreenPosition);
  1173. if (!Thickness.Contains (Frame, framePos))
  1174. {
  1175. EndArrangeMode ();
  1176. }
  1177. }
  1178. private bool? EndArrangeMode ()
  1179. {
  1180. // Debug.Assert (_arranging != ViewArrangement.Fixed);
  1181. Arranging = ViewArrangement.Fixed;
  1182. Application.MouseEvent -= ApplicationOnMouseEvent;
  1183. if (Application.MouseGrabView == this && _dragPosition.HasValue)
  1184. {
  1185. Application.UngrabMouse ();
  1186. }
  1187. if (_moveButton is { })
  1188. {
  1189. Remove (_moveButton);
  1190. _moveButton.Dispose ();
  1191. _moveButton = null;
  1192. }
  1193. if (_allSizeButton is { })
  1194. {
  1195. Remove (_allSizeButton);
  1196. _allSizeButton.Dispose ();
  1197. _allSizeButton = null;
  1198. }
  1199. if (_leftSizeButton is { })
  1200. {
  1201. Remove (_leftSizeButton);
  1202. _leftSizeButton.Dispose ();
  1203. _leftSizeButton = null;
  1204. }
  1205. if (_rightSizeButton is { })
  1206. {
  1207. Remove (_rightSizeButton);
  1208. _rightSizeButton.Dispose ();
  1209. _rightSizeButton = null;
  1210. }
  1211. if (_topSizeButton is { })
  1212. {
  1213. Remove (_topSizeButton);
  1214. _topSizeButton.Dispose ();
  1215. _topSizeButton = null;
  1216. }
  1217. if (_bottomSizeButton is { })
  1218. {
  1219. Remove (_bottomSizeButton);
  1220. _bottomSizeButton.Dispose ();
  1221. _bottomSizeButton = null;
  1222. }
  1223. HotKeyBindings.Clear ();
  1224. if (CanFocus)
  1225. {
  1226. CanFocus = false;
  1227. }
  1228. return true;
  1229. }
  1230. /// <inheritdoc/>
  1231. protected override void Dispose (bool disposing)
  1232. {
  1233. Application.GrabbingMouse -= Application_GrabbingMouse;
  1234. Application.UnGrabbingMouse -= Application_UnGrabbingMouse;
  1235. _dragPosition = null;
  1236. base.Dispose (disposing);
  1237. }
  1238. }