Border.cs 31 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948
  1. #nullable enable
  2. using System.Diagnostics;
  3. using static Terminal.Gui.SpinnerStyle;
  4. namespace Terminal.Gui;
  5. /// <summary>The Border for a <see cref="View"/>.</summary>
  6. /// <remarks>
  7. /// <para>
  8. /// Renders a border around the view with the <see cref="View.Title"/>. A border using <see cref="LineStyle"/>
  9. /// will be drawn on the sides of <see cref="Thickness"/> that are greater than zero.
  10. /// </para>
  11. /// <para>
  12. /// The <see cref="View.Title"/> of <see cref="Adornment.Parent"/> will be drawn based on the value of
  13. /// <see cref="Thickness.Top"/>:
  14. /// </para>
  15. /// <para>
  16. /// If <c>1</c>:
  17. /// <code>
  18. /// ┌┤1234├──┐
  19. /// │ │
  20. /// └────────┘
  21. /// </code>
  22. /// </para>
  23. /// <para>
  24. /// If <c>2</c>:
  25. /// <code>
  26. /// ┌────┐
  27. /// ┌┤1234├──┐
  28. /// │ │
  29. /// └────────┘
  30. /// </code>
  31. /// </para>
  32. /// <para>
  33. /// If <c>3</c>:
  34. /// <code>
  35. /// ┌────┐
  36. /// ┌┤1234├──┐
  37. /// │└────┘ │
  38. /// │ │
  39. /// └────────┘
  40. /// </code>
  41. /// </para>
  42. /// <para/>
  43. /// <para>See the <see cref="Adornment"/> class.</para>
  44. /// </remarks>
  45. public class Border : Adornment
  46. {
  47. private LineStyle? _lineStyle;
  48. /// <inheritdoc/>
  49. public Border ()
  50. { /* Do nothing; A parameter-less constructor is required to support all views unit tests. */
  51. }
  52. /// <inheritdoc/>
  53. public Border (View parent) : base (parent)
  54. {
  55. Parent = parent;
  56. CanFocus = false;
  57. Application.GrabbingMouse += Application_GrabbingMouse;
  58. Application.UnGrabbingMouse += Application_UnGrabbingMouse;
  59. HighlightStyle |= HighlightStyle.Pressed;
  60. Highlight += Border_Highlight;
  61. }
  62. #if SUBVIEW_BASED_BORDER
  63. private Line _left;
  64. /// <summary>
  65. /// The close button for the border. Set to <see cref="View.Visible"/>, to <see langword="true"/> to enable.
  66. /// </summary>
  67. public Button CloseButton { get; internal set; }
  68. #endif
  69. /// <inheritdoc/>
  70. public override void BeginInit ()
  71. {
  72. #if HOVER
  73. // TOOD: Hack - make Arrangement overridable
  74. if ((Parent?.Arrangement & ViewArrangement.Movable) != 0)
  75. {
  76. HighlightStyle |= HighlightStyle.Hover;
  77. }
  78. #endif
  79. base.BeginInit ();
  80. #if SUBVIEW_BASED_BORDER
  81. if (Parent is { })
  82. {
  83. // Left
  84. _left = new ()
  85. {
  86. Orientation = Orientation.Vertical,
  87. };
  88. Add (_left);
  89. CloseButton = new Button ()
  90. {
  91. Text = "X",
  92. CanFocus = true,
  93. Visible = false,
  94. };
  95. CloseButton.Accept += (s, e) =>
  96. {
  97. e.Cancel = Parent.InvokeCommand (Command.QuitToplevel) == true;
  98. };
  99. Add (CloseButton);
  100. LayoutStarted += OnLayoutStarted;
  101. }
  102. #endif
  103. }
  104. #if SUBVIEW_BASED_BORDER
  105. private void OnLayoutStarted (object sender, LayoutEventArgs e)
  106. {
  107. _left.Border.LineStyle = LineStyle;
  108. _left.X = Thickness.Left - 1;
  109. _left.Y = Thickness.Top - 1;
  110. _left.Width = 1;
  111. _left.Height = Height;
  112. CloseButton.X = Pos.AnchorEnd (Thickness.Right / 2 + 1) -
  113. (Pos.Right (CloseButton) -
  114. Pos.Left (CloseButton));
  115. CloseButton.Y = 0;
  116. }
  117. #endif
  118. /// <summary>
  119. /// The color scheme for the Border. If set to <see langword="null"/>, gets the <see cref="Adornment.Parent"/>
  120. /// scheme. color scheme.
  121. /// </summary>
  122. public override ColorScheme? ColorScheme
  123. {
  124. get
  125. {
  126. if (base.ColorScheme is { })
  127. {
  128. return base.ColorScheme;
  129. }
  130. if (Parent?.ColorScheme is { })
  131. {
  132. return Parent.ColorScheme;
  133. }
  134. return null;
  135. }
  136. set
  137. {
  138. base.ColorScheme = value;
  139. Parent?.SetNeedsDisplay ();
  140. }
  141. }
  142. internal Rectangle GetBorderRectangle ()
  143. {
  144. Rectangle screenRect = ViewportToScreen (Viewport);
  145. return new (
  146. screenRect.X + Math.Max (0, Thickness.Left - 1),
  147. screenRect.Y + Math.Max (0, Thickness.Top - 1),
  148. Math.Max (
  149. 0,
  150. screenRect.Width
  151. - Math.Max (
  152. 0,
  153. Math.Max (0, Thickness.Left - 1)
  154. + Math.Max (0, Thickness.Right - 1)
  155. )
  156. ),
  157. Math.Max (
  158. 0,
  159. screenRect.Height
  160. - Math.Max (
  161. 0,
  162. Math.Max (0, Thickness.Top - 1)
  163. + Math.Max (0, Thickness.Bottom - 1)
  164. )
  165. )
  166. );
  167. }
  168. /// <summary>
  169. /// Sets the style of the border by changing the <see cref="Thickness"/>. This is a helper API for setting the
  170. /// <see cref="Thickness"/> to <c>(1,1,1,1)</c> and setting the line style of the views that comprise the border. If
  171. /// set to <see cref="LineStyle.None"/> no border will be drawn.
  172. /// </summary>
  173. public LineStyle LineStyle
  174. {
  175. get
  176. {
  177. if (_lineStyle.HasValue)
  178. {
  179. return _lineStyle.Value;
  180. }
  181. // TODO: Make Border.LineStyle inherit from the SuperView hierarchy
  182. // TODO: Right now, Window and FrameView use CM to set BorderStyle, which negates
  183. // TODO: all this.
  184. return Parent!.SuperView?.BorderStyle ?? LineStyle.None;
  185. }
  186. set => _lineStyle = value;
  187. }
  188. private BorderSettings _settings = BorderSettings.Title;
  189. /// <summary>
  190. /// Gets or sets the settings for the border.
  191. /// </summary>
  192. public BorderSettings Settings
  193. {
  194. get => _settings;
  195. set
  196. {
  197. if (value == _settings)
  198. {
  199. return;
  200. }
  201. _settings = value;
  202. Parent?.SetNeedsDisplay ();
  203. }
  204. }
  205. #region Mouse Support
  206. private Color? _savedForeColor;
  207. private void Border_Highlight (object? sender, CancelEventArgs<HighlightStyle> e)
  208. {
  209. if (!Parent!.Arrangement.HasFlag (ViewArrangement.Movable))
  210. {
  211. e.Cancel = true;
  212. return;
  213. }
  214. if (e.NewValue.HasFlag (HighlightStyle.Pressed))
  215. {
  216. if (!_savedForeColor.HasValue)
  217. {
  218. _savedForeColor = ColorScheme!.Normal.Foreground;
  219. }
  220. var cs = new ColorScheme (ColorScheme)
  221. {
  222. Normal = new (ColorScheme!.Normal.Foreground.GetHighlightColor (), ColorScheme.Normal.Background)
  223. };
  224. ColorScheme = cs;
  225. }
  226. #if HOVER
  227. else if (e.HighlightStyle.HasFlag (HighlightStyle.Hover))
  228. {
  229. if (!_savedHighlightLineStyle.HasValue)
  230. {
  231. _savedHighlightLineStyle = Parent?.BorderStyle ?? LineStyle;
  232. }
  233. LineStyle = LineStyle.Double;
  234. }
  235. #endif
  236. if (e.NewValue == HighlightStyle.None && _savedForeColor.HasValue)
  237. {
  238. var cs = new ColorScheme (ColorScheme)
  239. {
  240. Normal = new (_savedForeColor.Value, ColorScheme!.Normal.Background)
  241. };
  242. ColorScheme = cs;
  243. }
  244. Parent?.SetNeedsDisplay ();
  245. e.Cancel = true;
  246. }
  247. private Point? _dragPosition;
  248. private Point _startGrabPoint;
  249. /// <inheritdoc/>
  250. protected internal override bool OnMouseEvent (MouseEvent mouseEvent)
  251. {
  252. if (base.OnMouseEvent (mouseEvent))
  253. {
  254. return true;
  255. }
  256. // BUGBUG: Shouldn't non-focusable views be draggable??
  257. //if (!Parent.CanFocus)
  258. //{
  259. // return false;
  260. //}
  261. if (!Parent!.Arrangement.HasFlag (ViewArrangement.Movable))
  262. {
  263. return false;
  264. }
  265. // BUGBUG: See https://github.com/gui-cs/Terminal.Gui/issues/3312
  266. if (!_dragPosition.HasValue && mouseEvent.Flags.HasFlag (MouseFlags.Button1Pressed))
  267. {
  268. Parent.SetFocus ();
  269. ApplicationOverlapped.BringOverlappedTopToFront ();
  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. // Set the start grab point to the Frame coords
  275. _startGrabPoint = new (mouseEvent.Position.X + Frame.X, mouseEvent.Position.Y + Frame.Y);
  276. _dragPosition = mouseEvent.Position;
  277. Application.GrabMouse (this);
  278. SetHighlight (HighlightStyle);
  279. }
  280. return true;
  281. }
  282. if (mouseEvent.Flags is (MouseFlags.Button1Pressed | MouseFlags.ReportMousePosition))
  283. {
  284. if (Application.MouseGrabView == this && _dragPosition.HasValue)
  285. {
  286. if (Parent.SuperView is null)
  287. {
  288. // Redraw the entire app window.
  289. Application.Top!.SetNeedsDisplay ();
  290. }
  291. else
  292. {
  293. Parent.SuperView.SetNeedsDisplay ();
  294. }
  295. _dragPosition = mouseEvent.Position;
  296. Point parentLoc = Parent.SuperView?.ScreenToViewport (new (mouseEvent.ScreenPosition.X, mouseEvent.ScreenPosition.Y))
  297. ?? mouseEvent.ScreenPosition;
  298. GetLocationEnsuringFullVisibility (
  299. Parent,
  300. parentLoc.X - _startGrabPoint.X,
  301. parentLoc.Y - _startGrabPoint.Y,
  302. out int nx,
  303. out int ny,
  304. out _
  305. );
  306. Parent.X = nx;
  307. Parent.Y = ny;
  308. return true;
  309. }
  310. }
  311. if (mouseEvent.Flags.HasFlag (MouseFlags.Button1Released) && _dragPosition.HasValue)
  312. {
  313. _dragPosition = null;
  314. Application.UngrabMouse ();
  315. SetHighlight (HighlightStyle.None);
  316. return true;
  317. }
  318. return false;
  319. }
  320. /// <inheritdoc/>
  321. protected override void Dispose (bool disposing)
  322. {
  323. Application.GrabbingMouse -= Application_GrabbingMouse;
  324. Application.UnGrabbingMouse -= Application_UnGrabbingMouse;
  325. _dragPosition = null;
  326. base.Dispose (disposing);
  327. }
  328. private void Application_GrabbingMouse (object? sender, GrabMouseEventArgs e)
  329. {
  330. if (Application.MouseGrabView == this && _dragPosition.HasValue)
  331. {
  332. e.Cancel = true;
  333. }
  334. }
  335. private void Application_UnGrabbingMouse (object? sender, GrabMouseEventArgs e)
  336. {
  337. if (Application.MouseGrabView == this && _dragPosition.HasValue)
  338. {
  339. e.Cancel = true;
  340. }
  341. }
  342. #endregion Mouse Support
  343. /// <inheritdoc/>
  344. public override void OnDrawContent (Rectangle viewport)
  345. {
  346. base.OnDrawContent (viewport);
  347. if (Thickness == Thickness.Empty)
  348. {
  349. return;
  350. }
  351. //Driver.SetAttribute (Colors.ColorSchemes ["Error"].Normal);
  352. Rectangle screenBounds = ViewportToScreen (viewport);
  353. //OnDrawSubviews (bounds);
  354. // TODO: v2 - this will eventually be two controls: "BorderView" and "Label" (for the title)
  355. // The border adornment (and title) are drawn at the outermost edge of border;
  356. // For Border
  357. // ...thickness extends outward (border/title is always as far in as possible)
  358. // PERF: How about a call to Rectangle.Offset?
  359. Rectangle borderBounds = GetBorderRectangle ();
  360. int topTitleLineY = borderBounds.Y;
  361. int titleY = borderBounds.Y;
  362. var titleBarsLength = 0; // the little vertical thingies
  363. int maxTitleWidth = Math.Max (
  364. 0,
  365. Math.Min (
  366. Parent!.TitleTextFormatter.FormatAndGetSize ().Width,
  367. Math.Min (screenBounds.Width - 4, borderBounds.Width - 4)
  368. )
  369. );
  370. Parent.TitleTextFormatter.ConstrainToSize = new (maxTitleWidth, 1);
  371. int sideLineLength = borderBounds.Height;
  372. bool canDrawBorder = borderBounds is { Width: > 0, Height: > 0 };
  373. LineStyle lineStyle = LineStyle;
  374. if (Settings.FastHasFlags (BorderSettings.Title))
  375. {
  376. if (Thickness.Top == 2)
  377. {
  378. topTitleLineY = borderBounds.Y - 1;
  379. titleY = topTitleLineY + 1;
  380. titleBarsLength = 2;
  381. }
  382. // ┌────┐
  383. //┌┘View└
  384. //│
  385. if (Thickness.Top == 3)
  386. {
  387. topTitleLineY = borderBounds.Y - (Thickness.Top - 1);
  388. titleY = topTitleLineY + 1;
  389. titleBarsLength = 3;
  390. sideLineLength++;
  391. }
  392. // ┌────┐
  393. //┌┘View└
  394. //│
  395. if (Thickness.Top > 3)
  396. {
  397. topTitleLineY = borderBounds.Y - 2;
  398. titleY = topTitleLineY + 1;
  399. titleBarsLength = 3;
  400. sideLineLength++;
  401. }
  402. }
  403. if (canDrawBorder && Thickness.Top > 0 && maxTitleWidth > 0 && Settings.FastHasFlags (BorderSettings.Title) && !string.IsNullOrEmpty (Parent?.Title))
  404. {
  405. Attribute focus = Parent.GetNormalColor ();
  406. if (Parent.SuperView is { } && Parent.SuperView?.Subviews!.Count (s => s.CanFocus) > 1)
  407. {
  408. // Only use focus color if there are multiple focusable views
  409. focus = Parent.GetFocusColor ();
  410. }
  411. Parent.TitleTextFormatter.Draw (
  412. new (borderBounds.X + 2, titleY, maxTitleWidth, 1),
  413. Parent.HasFocus ? focus : Parent.GetNormalColor (),
  414. Parent.HasFocus ? focus : Parent.GetHotNormalColor ());
  415. }
  416. if (canDrawBorder && LineStyle != LineStyle.None)
  417. {
  418. LineCanvas? lc = Parent?.LineCanvas;
  419. bool drawTop = Thickness.Top > 0 && Frame.Width > 1 && Frame.Height >= 1;
  420. bool drawLeft = Thickness.Left > 0 && (Frame.Height > 1 || Thickness.Top == 0);
  421. bool drawBottom = Thickness.Bottom > 0 && Frame.Width > 1 && Frame.Height > 1;
  422. bool drawRight = Thickness.Right > 0 && (Frame.Height > 1 || Thickness.Top == 0);
  423. Attribute prevAttr = Driver.GetAttribute ();
  424. if (ColorScheme is { })
  425. {
  426. Driver.SetAttribute (GetNormalColor ());
  427. }
  428. else
  429. {
  430. Driver.SetAttribute (Parent!.GetNormalColor ());
  431. }
  432. if (drawTop)
  433. {
  434. // ╔╡Title╞═════╗
  435. // ╔╡╞═════╗
  436. if (borderBounds.Width < 4 || !Settings.FastHasFlags (BorderSettings.Title) || string.IsNullOrEmpty (Parent?.Title))
  437. {
  438. // ╔╡╞╗ should be ╔══╗
  439. lc?.AddLine (
  440. new (borderBounds.Location.X, titleY),
  441. borderBounds.Width,
  442. Orientation.Horizontal,
  443. lineStyle,
  444. Driver.GetAttribute ()
  445. );
  446. }
  447. else
  448. {
  449. // ┌────┐
  450. //┌┘View└
  451. //│
  452. if (Thickness.Top == 2)
  453. {
  454. lc?.AddLine (
  455. new (borderBounds.X + 1, topTitleLineY),
  456. Math.Min (borderBounds.Width - 2, maxTitleWidth + 2),
  457. Orientation.Horizontal,
  458. lineStyle,
  459. Driver.GetAttribute ()
  460. );
  461. }
  462. // ┌────┐
  463. //┌┘View└
  464. //│
  465. if (borderBounds.Width >= 4 && Thickness.Top > 2)
  466. {
  467. lc?.AddLine (
  468. new (borderBounds.X + 1, topTitleLineY),
  469. Math.Min (borderBounds.Width - 2, maxTitleWidth + 2),
  470. Orientation.Horizontal,
  471. lineStyle,
  472. Driver.GetAttribute ()
  473. );
  474. lc?.AddLine (
  475. new (borderBounds.X + 1, topTitleLineY + 2),
  476. Math.Min (borderBounds.Width - 2, maxTitleWidth + 2),
  477. Orientation.Horizontal,
  478. lineStyle,
  479. Driver.GetAttribute ()
  480. );
  481. }
  482. // ╔╡Title╞═════╗
  483. // Add a short horiz line for ╔╡
  484. lc?.AddLine (
  485. new (borderBounds.Location.X, titleY),
  486. 2,
  487. Orientation.Horizontal,
  488. lineStyle,
  489. Driver.GetAttribute ()
  490. );
  491. // Add a vert line for ╔╡
  492. lc?.AddLine (
  493. new (borderBounds.X + 1, topTitleLineY),
  494. titleBarsLength,
  495. Orientation.Vertical,
  496. LineStyle.Single,
  497. Driver.GetAttribute ()
  498. );
  499. // Add a vert line for ╞
  500. lc?.AddLine (
  501. new (
  502. borderBounds.X
  503. + 1
  504. + Math.Min (borderBounds.Width - 2, maxTitleWidth + 2)
  505. - 1,
  506. topTitleLineY
  507. ),
  508. titleBarsLength,
  509. Orientation.Vertical,
  510. LineStyle.Single,
  511. Driver.GetAttribute ()
  512. );
  513. // Add the right hand line for ╞═════╗
  514. lc?.AddLine (
  515. new (
  516. borderBounds.X
  517. + 1
  518. + Math.Min (borderBounds.Width - 2, maxTitleWidth + 2)
  519. - 1,
  520. titleY
  521. ),
  522. borderBounds.Width - Math.Min (borderBounds.Width - 2, maxTitleWidth + 2),
  523. Orientation.Horizontal,
  524. lineStyle,
  525. Driver.GetAttribute ()
  526. );
  527. }
  528. }
  529. #if !SUBVIEW_BASED_BORDER
  530. if (drawLeft)
  531. {
  532. lc?.AddLine (
  533. new (borderBounds.Location.X, titleY),
  534. sideLineLength,
  535. Orientation.Vertical,
  536. lineStyle,
  537. Driver.GetAttribute ()
  538. );
  539. }
  540. #endif
  541. if (drawBottom)
  542. {
  543. lc?.AddLine (
  544. new (borderBounds.X, borderBounds.Y + borderBounds.Height - 1),
  545. borderBounds.Width,
  546. Orientation.Horizontal,
  547. lineStyle,
  548. Driver.GetAttribute ()
  549. );
  550. }
  551. if (drawRight)
  552. {
  553. lc?.AddLine (
  554. new (borderBounds.X + borderBounds.Width - 1, titleY),
  555. sideLineLength,
  556. Orientation.Vertical,
  557. lineStyle,
  558. Driver.GetAttribute ()
  559. );
  560. }
  561. Driver.SetAttribute (prevAttr);
  562. // TODO: This should be moved to LineCanvas as a new BorderStyle.Ruler
  563. if (Diagnostics.HasFlag (ViewDiagnosticFlags.Ruler))
  564. {
  565. // Top
  566. var hruler = new Ruler { Length = screenBounds.Width, Orientation = Orientation.Horizontal };
  567. if (drawTop)
  568. {
  569. hruler.Draw (new (screenBounds.X, screenBounds.Y));
  570. }
  571. // Redraw title
  572. if (drawTop && maxTitleWidth > 0 && Settings.FastHasFlags (BorderSettings.Title))
  573. {
  574. Parent!.TitleTextFormatter.Draw (
  575. new (borderBounds.X + 2, titleY, maxTitleWidth, 1),
  576. Parent.HasFocus ? Parent.GetFocusColor () : Parent.GetNormalColor (),
  577. Parent.HasFocus ? Parent.GetFocusColor () : Parent.GetNormalColor ());
  578. }
  579. //Left
  580. var vruler = new Ruler { Length = screenBounds.Height - 2, Orientation = Orientation.Vertical };
  581. if (drawLeft)
  582. {
  583. vruler.Draw (new (screenBounds.X, screenBounds.Y + 1), 1);
  584. }
  585. // Bottom
  586. if (drawBottom)
  587. {
  588. hruler.Draw (new (screenBounds.X, screenBounds.Y + screenBounds.Height - 1));
  589. }
  590. // Right
  591. if (drawRight)
  592. {
  593. vruler.Draw (new (screenBounds.X + screenBounds.Width - 1, screenBounds.Y + 1), 1);
  594. }
  595. }
  596. // TODO: This should not be done on each draw?
  597. if (Settings.FastHasFlags (BorderSettings.Gradient))
  598. {
  599. SetupGradientLineCanvas (lc!, screenBounds);
  600. }
  601. else
  602. {
  603. lc!.Fill = null;
  604. }
  605. }
  606. }
  607. private void SetupGradientLineCanvas (LineCanvas lc, Rectangle rect)
  608. {
  609. GetAppealingGradientColors (out List<Color> stops, out List<int> steps);
  610. var g = new Gradient (stops, steps);
  611. var fore = new GradientFill (rect, g, GradientDirection.Diagonal);
  612. var back = new SolidFill (GetNormalColor ().Background);
  613. lc.Fill = new (fore, back);
  614. }
  615. private static void GetAppealingGradientColors (out List<Color> stops, out List<int> steps)
  616. {
  617. // Define the colors of the gradient stops with more appealing colors
  618. stops =
  619. [
  620. new (0, 128, 255), // Bright Blue
  621. new (0, 255, 128), // Bright Green
  622. new (255, 255), // Bright Yellow
  623. new (255, 128), // Bright Orange
  624. new (255, 0, 128)
  625. ];
  626. // Define the number of steps between each color for smoother transitions
  627. // If we pass only a single value then it will assume equal steps between all pairs
  628. steps = [15];
  629. }
  630. private ViewArrangement _arranging;
  631. private Button? _arrangeButton;
  632. /// <summary>
  633. /// Starts "Arrange Mode" where <see cref="Adornment.Parent"/> can be moved and/or resized using the mouse
  634. /// or keyboard.
  635. /// </summary>
  636. /// <remarks>
  637. /// Arrange Mode is exited by the user pressing <see cref="Application.ArrangeKey"/>, <see cref="Key.Esc"/>, or by clicking
  638. /// the mouse out of the <see cref="Adornment.Parent"/>'s Frame.
  639. /// </remarks>
  640. /// <returns></returns>
  641. public bool? EnterArrangeMode ()
  642. {
  643. Debug.Assert (_arranging == ViewArrangement.Fixed);
  644. if (!Parent!.Arrangement.HasFlag (ViewArrangement.Movable)
  645. && !Parent!.Arrangement.HasFlag (ViewArrangement.BottomResizable)
  646. && !Parent!.Arrangement.HasFlag (ViewArrangement.TopResizable)
  647. && !Parent!.Arrangement.HasFlag (ViewArrangement.LeftResizable)
  648. && !Parent!.Arrangement.HasFlag (ViewArrangement.RightResizable)
  649. )
  650. {
  651. return false;
  652. }
  653. Debug.Assert (_arrangeButton is null);
  654. _arrangeButton = new Button
  655. {
  656. CanFocus = true,
  657. Width = 1,
  658. Height = 1,
  659. NoDecorations = true,
  660. NoPadding = true,
  661. ShadowStyle = ShadowStyle.None,
  662. Text = $"{Glyphs.Diamond}",
  663. };
  664. Add (_arrangeButton);
  665. CanFocus = true;
  666. //_arrangeButton.SetFocus ();
  667. AddCommand (Command.Quit, EndArrange);
  668. AddCommand (Command.Up,
  669. () =>
  670. {
  671. if (Parent is null)
  672. {
  673. return false;
  674. }
  675. if (_arranging == ViewArrangement.Movable)
  676. {
  677. Parent!.Y = Parent.Y - 1;
  678. }
  679. if (_arranging == ViewArrangement.Resizable)
  680. {
  681. if (Parent!.Viewport.Height > 0)
  682. {
  683. Parent!.Height = Parent.Height! - 1;
  684. }
  685. }
  686. return true;
  687. });
  688. AddCommand (Command.Down,
  689. () =>
  690. {
  691. if (Parent is null)
  692. {
  693. return false;
  694. }
  695. if (_arranging == ViewArrangement.Movable)
  696. {
  697. Parent!.Y = Parent.Y + 1;
  698. }
  699. if (_arranging == ViewArrangement.Resizable)
  700. {
  701. Parent!.Height = Parent.Height! + 1;
  702. }
  703. return true;
  704. });
  705. AddCommand (Command.Left,
  706. () =>
  707. {
  708. if (Parent is null)
  709. {
  710. return false;
  711. }
  712. if (_arranging == ViewArrangement.Movable)
  713. {
  714. Parent!.X = Parent.X - 1;
  715. }
  716. if (_arranging == ViewArrangement.Resizable)
  717. {
  718. if (Parent!.Viewport.Width > 0)
  719. {
  720. Parent!.Width = Parent.Width! - 1;
  721. }
  722. }
  723. return true;
  724. });
  725. AddCommand (Command.Right,
  726. () =>
  727. {
  728. if (Parent is null)
  729. {
  730. return false;
  731. }
  732. if (_arranging == ViewArrangement.Movable)
  733. {
  734. Parent!.X = Parent.X + 1;
  735. }
  736. if (_arranging == ViewArrangement.Resizable)
  737. {
  738. Parent!.Width = Parent.Width! + 1;
  739. }
  740. return true;
  741. });
  742. AddCommand (Command.Tab, Navigate);
  743. AddCommand (Command.BackTab, Navigate);
  744. KeyBindings.Add (Key.Esc, KeyBindingScope.HotKey, Command.Quit);
  745. KeyBindings.Add (Application.ArrangeKey, KeyBindingScope.HotKey, Command.Quit);
  746. KeyBindings.Add (Key.CursorUp, KeyBindingScope.HotKey, Command.Up);
  747. KeyBindings.Add (Key.CursorDown, KeyBindingScope.HotKey, Command.Down);
  748. KeyBindings.Add (Key.CursorLeft, KeyBindingScope.HotKey, Command.Left);
  749. KeyBindings.Add (Key.CursorRight, KeyBindingScope.HotKey, Command.Right);
  750. KeyBindings.Add (Key.Tab, KeyBindingScope.HotKey, Command.Tab);
  751. KeyBindings.Add (Key.Tab.WithShift, KeyBindingScope.HotKey, Command.BackTab);
  752. if (Parent!.Arrangement.HasFlag (ViewArrangement.Movable))
  753. {
  754. _arranging = ViewArrangement.Movable;
  755. _arrangeButton.X = 0;
  756. _arrangeButton.Y = 0;
  757. return true;
  758. }
  759. else
  760. {
  761. if (Parent!.Arrangement.HasFlag (ViewArrangement.Resizable))
  762. {
  763. _arranging = ViewArrangement.Resizable;
  764. _arrangeButton.X = Pos.AnchorEnd ();
  765. _arrangeButton.Y = Pos.AnchorEnd ();
  766. return true;
  767. }
  768. }
  769. // Hack for now
  770. EndArrange ();
  771. return false;
  772. bool? Navigate ()
  773. {
  774. if (_arranging == ViewArrangement.Movable)
  775. {
  776. if (Parent!.Arrangement.HasFlag (ViewArrangement.Resizable))
  777. {
  778. _arranging = ViewArrangement.Resizable;
  779. _arrangeButton.X = Pos.AnchorEnd ();
  780. _arrangeButton.Y = Pos.AnchorEnd ();
  781. }
  782. }
  783. else if (_arranging == ViewArrangement.Resizable)
  784. {
  785. if (Parent!.Arrangement.HasFlag (ViewArrangement.Movable))
  786. {
  787. _arranging = ViewArrangement.Movable;
  788. _arrangeButton.X = 0;
  789. _arrangeButton.Y = 0;
  790. }
  791. }
  792. return true;
  793. }
  794. }
  795. private bool? EndArrange ()
  796. {
  797. _arranging = ViewArrangement.Fixed;
  798. CanFocus = false;
  799. if (_arrangeButton is { })
  800. {
  801. Remove (_arrangeButton);
  802. _arrangeButton.Dispose ();
  803. _arrangeButton = null;
  804. }
  805. KeyBindings.Clear ();
  806. return true;
  807. }
  808. }