2
0

Border.cs 35 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137
  1. using NStack;
  2. using System;
  3. using System.Collections.Generic;
  4. using Terminal.Gui.Graphs;
  5. namespace Terminal.Gui {
  6. /// <summary>
  7. /// Specifies the border style for a <see cref="View"/> and to be used by the <see cref="Border"/> class.
  8. /// </summary>
  9. public enum BorderStyle {
  10. /// <summary>
  11. /// No border is drawn.
  12. /// </summary>
  13. None,
  14. /// <summary>
  15. /// The border is drawn with a single line limits.
  16. /// </summary>
  17. Single,
  18. /// <summary>
  19. /// The border is drawn with a double line limits.
  20. /// </summary>
  21. Double,
  22. /// <summary>
  23. /// The border is drawn with a single line and rounded corners limits.
  24. /// </summary>
  25. Rounded
  26. }
  27. /// <summary>
  28. /// Describes the thickness of a frame around a rectangle. Four <see cref="int"/> values describe
  29. /// the <see cref="Left"/>, <see cref="Top"/>, <see cref="Right"/>, and <see cref="Bottom"/> sides
  30. /// of the rectangle, respectively.
  31. /// </summary>
  32. public class Thickness : IEquatable<Thickness> {
  33. private int _left;
  34. private int _right;
  35. private int _top;
  36. private int _bottom;
  37. private int validate (int width)
  38. {
  39. if (width < 0) {
  40. throw new ArgumentException ("Thickness widths cannot be negative.");
  41. }
  42. return width;
  43. }
  44. /// <summary>
  45. /// Gets or sets the width of the left side of the rectangle.
  46. /// </summary>
  47. public int Left { get => _left; set => _left = validate (value); }
  48. /// <summary>
  49. /// Gets or sets the width of the upper side of the rectangle.
  50. /// </summary>
  51. public int Top { get => _top; set => _top = validate (value); }
  52. /// <summary>
  53. /// Gets or sets the width of the right side of the rectangle.
  54. /// </summary>
  55. public int Right { get => _right; set => _right = validate (value); }
  56. /// <summary>
  57. /// Gets or sets the width of the lower side of the rectangle.
  58. /// </summary>
  59. public int Bottom { get => _bottom; set => _bottom = validate (value); }
  60. /// <summary>
  61. /// Initializes a new instance of the <see cref="Thickness"/> class with all widths
  62. /// set to 0.
  63. /// </summary>
  64. public Thickness () { }
  65. /// <summary>
  66. /// Initializes a new instance of the <see cref="Thickness"/> class with a uniform width to each side.
  67. /// </summary>
  68. /// <param name="width"></param>
  69. public Thickness (int width) : this (width, width, width, width) { }
  70. /// <summary>
  71. /// Initializes a new instance of the <see cref="Thickness"/> class that has specific
  72. /// widths applied to each side of the rectangle.
  73. /// </summary>
  74. /// <param name="left"></param>
  75. /// <param name="top"></param>
  76. /// <param name="right"></param>
  77. /// <param name="bottom"></param>
  78. public Thickness (int left, int top, int right, int bottom)
  79. {
  80. Left = left;
  81. Top = top;
  82. Right = right;
  83. Bottom = bottom;
  84. }
  85. /// <summary>
  86. /// Returns a rectangle describing the location and size of the inner area of <paramref name="rect"/>
  87. /// with the thickness widths subracted. The height and width of the retunred rect may be zero.
  88. /// </summary>
  89. /// <param name="rect">The source rectangle</param>
  90. /// <returns></returns>
  91. public Rect GetInnerRect (Rect rect)
  92. {
  93. var width = rect.Size.Width - (Left + Right);
  94. var height = rect.Size.Height - (Top + Bottom);
  95. var size = new Size (Math.Max (0, width), Math.Max (0, height));
  96. return new Rect (new Point (rect.X + Left, rect.Y + Top), size);
  97. }
  98. /// <summary>
  99. /// Draws the thickness rectangle with an optional diagnostics label.
  100. /// </summary>
  101. /// <param name="rect">The location and size of the rectangle that bounds the thickness rectangle, in
  102. /// screen coordinates.</param>
  103. /// <param name="label">The diagnostics label to draw on the bottom of the <see cref="Bottom"/>.</param>
  104. /// <returns>The inner rectangle remaining to be drawn.</returns>
  105. public Rect Draw (Rect rect, string label = null)
  106. {
  107. // Draw the Top side
  108. for (var r = rect.Y; r < Math.Min (rect.Y + rect.Height, rect.Y + Top); r++) {
  109. for (var c = rect.X; c < rect.X + rect.Width; c++) {
  110. Application.Driver.Move (c, r);
  111. Application.Driver.AddRune (' ');
  112. }
  113. }
  114. // Draw the Left side
  115. for (var r = rect.Y; r < rect.Y + rect.Height; r++) {
  116. for (var c = rect.X; c < Math.Min (rect.X + rect.Width, rect.X + Left); c++) {
  117. Application.Driver.Move (c, r);
  118. Application.Driver.AddRune (' ');
  119. }
  120. }
  121. // Draw the Right side
  122. for (var r = rect.Y; r < rect.Y + rect.Height; r++) {
  123. for (var c = rect.X + Math.Max (0, rect.Width - Right); c < rect.X + rect.Width; c++) {
  124. Application.Driver.Move (c, r);
  125. Application.Driver.AddRune (' ');
  126. }
  127. }
  128. // Draw the Bottom side
  129. for (var r = rect.Y + Math.Max (0, rect.Height - Bottom); r < rect.Y + rect.Height; r++) {
  130. for (var c = rect.X; c < rect.X + rect.Width; c++) {
  131. Application.Driver.Move (c, r);
  132. Application.Driver.AddRune (' ');
  133. }
  134. }
  135. // Draw the diagnostics label on the bottom
  136. var tf = new TextFormatter () {
  137. Text = label == null ? string.Empty : $"{label} {this}",
  138. Alignment = TextAlignment.Centered,
  139. VerticalAlignment = VerticalTextAlignment.Bottom
  140. };
  141. tf.Draw (rect, Application.Driver.CurrentAttribute, Application.Driver.CurrentAttribute);
  142. return GetInnerRect (rect);
  143. }
  144. // TODO: Add GetHashCode, and operator overloads
  145. /// <summary>
  146. /// Gets an empty thickness.
  147. /// </summary>
  148. public static Thickness Empty => new Thickness (0);
  149. public override bool Equals (object obj)
  150. {
  151. //Check for null and compare run-time types.
  152. if ((obj == null) || !this.GetType ().Equals (obj.GetType ())) {
  153. return false;
  154. } else {
  155. return Equals ((Thickness)obj);
  156. }
  157. }
  158. /// <summary>Returns the thickness widths of the Thickness formatted as a string.</summary>
  159. /// <returns>The thickness widths as a string.</returns>
  160. public override string ToString ()
  161. {
  162. return $"(Left={Left},Top={Top},Right={Right},Bottom={Bottom})";
  163. }
  164. // IEquitable
  165. public bool Equals (Thickness other)
  166. {
  167. return other is not null &&
  168. _left == other._left &&
  169. _right == other._right &&
  170. _top == other._top &&
  171. _bottom == other._bottom;
  172. }
  173. public override int GetHashCode ()
  174. {
  175. int hashCode = 1380952125;
  176. hashCode = hashCode * -1521134295 + _left.GetHashCode ();
  177. hashCode = hashCode * -1521134295 + _right.GetHashCode ();
  178. hashCode = hashCode * -1521134295 + _top.GetHashCode ();
  179. hashCode = hashCode * -1521134295 + _bottom.GetHashCode ();
  180. return hashCode;
  181. }
  182. public static bool operator == (Thickness left, Thickness right)
  183. {
  184. return EqualityComparer<Thickness>.Default.Equals (left, right);
  185. }
  186. public static bool operator != (Thickness left, Thickness right)
  187. {
  188. return !(left == right);
  189. }
  190. }
  191. /// <summary>
  192. /// Draws a border, background, or both around another element.
  193. /// </summary>
  194. public class Border {
  195. private int marginFrame => DrawMarginFrame ? 1 : 0;
  196. /// <summary>
  197. /// A sealed <see cref="Toplevel"/> derived class to implement <see cref="Border"/> feature.
  198. /// This is only a wrapper to get borders on a toplevel and is recommended using another
  199. /// derived, like <see cref="Window"/> where is possible to have borders with or without
  200. /// border line or spacing around.
  201. /// </summary>
  202. public sealed class ToplevelContainer : Toplevel {
  203. /// <inheritdoc/>
  204. public override Border Border {
  205. get => base.Border;
  206. set {
  207. if (base.Border != null && base.Border.Child != null && value.Child == null) {
  208. value.Child = base.Border.Child;
  209. }
  210. base.Border = value;
  211. if (value == null) {
  212. return;
  213. }
  214. Rect frame;
  215. if (Border.Child != null && (Border.Child.Width is Dim || Border.Child.Height is Dim)) {
  216. frame = Rect.Empty;
  217. } else {
  218. frame = Frame;
  219. }
  220. AdjustContentView (frame);
  221. Border.BorderChanged += Border_BorderChanged;
  222. }
  223. }
  224. void Border_BorderChanged (Border border)
  225. {
  226. Rect frame;
  227. if (Border.Child != null && (Border.Child.Width is Dim || Border.Child.Height is Dim)) {
  228. frame = Rect.Empty;
  229. } else {
  230. frame = Frame;
  231. }
  232. AdjustContentView (frame);
  233. }
  234. /// <summary>
  235. /// Initializes with default null values.
  236. /// </summary>
  237. public ToplevelContainer () : this (null, string.Empty) { }
  238. /// <summary>
  239. /// Initializes a <see cref="ToplevelContainer"/> with a <see cref="LayoutStyle.Computed"/>
  240. /// </summary>
  241. /// <param name="border">The border.</param>
  242. /// <param name="title">The title.</param>
  243. public ToplevelContainer (Border border, string title = null)
  244. {
  245. Initialize (Rect.Empty, border, title ?? string.Empty);
  246. }
  247. /// <summary>
  248. /// Initializes a <see cref="ToplevelContainer"/> with a <see cref="LayoutStyle.Absolute"/>
  249. /// </summary>
  250. /// <param name="frame">The frame.</param>
  251. /// <param name="border">The border.</param>
  252. /// <param name="title">The title.</param>
  253. public ToplevelContainer (Rect frame, Border border, string title = null) : base (frame)
  254. {
  255. Initialize (frame, border, title ?? string.Empty);
  256. }
  257. private void Initialize (Rect frame, Border border, string title)
  258. {
  259. ColorScheme = Colors.TopLevel;
  260. if (border == null) {
  261. Border = new Border () {
  262. BorderStyle = BorderStyle.Single,
  263. BorderBrush = ColorScheme.Normal.Background,
  264. Title = (ustring)title
  265. };
  266. } else {
  267. Border = border;
  268. }
  269. AdjustContentView (frame);
  270. }
  271. void AdjustContentView (Rect frame)
  272. {
  273. var borderLength = Border.DrawMarginFrame ? 1 : 0;
  274. var sumPadding = Border.GetSumThickness ();
  275. var wp = new Point ();
  276. var wb = new Size ();
  277. if (frame == Rect.Empty) {
  278. wp.X = borderLength + sumPadding.Left;
  279. wp.Y = borderLength + sumPadding.Top;
  280. wb.Width = borderLength + sumPadding.Right;
  281. wb.Height = borderLength + sumPadding.Bottom;
  282. if (Border.Child == null) {
  283. Border.Child = new ChildContentView (this) {
  284. X = wp.X,
  285. Y = wp.Y,
  286. Width = Dim.Fill (wb.Width),
  287. Height = Dim.Fill (wb.Height)
  288. };
  289. } else {
  290. Border.Child.X = wp.X;
  291. Border.Child.Y = wp.Y;
  292. Border.Child.Width = Dim.Fill (wb.Width);
  293. Border.Child.Height = Dim.Fill (wb.Height);
  294. }
  295. } else {
  296. wb.Width = (2 * borderLength) + sumPadding.Right + sumPadding.Left;
  297. wb.Height = (2 * borderLength) + sumPadding.Bottom + sumPadding.Top;
  298. var cFrame = new Rect (borderLength + sumPadding.Left, borderLength + sumPadding.Top, frame.Width - wb.Width, frame.Height - wb.Height);
  299. if (Border.Child == null) {
  300. Border.Child = new ChildContentView (cFrame, this);
  301. } else {
  302. Border.Child.Frame = cFrame;
  303. }
  304. }
  305. if (Subviews?.Count == 0)
  306. base.Add (Border.Child);
  307. Border.ChildContainer = this;
  308. }
  309. /// <inheritdoc/>
  310. public override void Add (View view)
  311. {
  312. Border.Child.Add (view);
  313. if (view.CanFocus) {
  314. CanFocus = true;
  315. }
  316. AddMenuStatusBar (view);
  317. }
  318. /// <inheritdoc/>
  319. public override void Remove (View view)
  320. {
  321. if (view == null) {
  322. return;
  323. }
  324. SetNeedsDisplay ();
  325. var touched = view.Frame;
  326. Border.Child.Remove (view);
  327. if (Border.Child.InternalSubviews.Count < 1) {
  328. CanFocus = false;
  329. }
  330. RemoveMenuStatusBar (view);
  331. }
  332. /// <inheritdoc/>
  333. public override void RemoveAll ()
  334. {
  335. Border.Child.RemoveAll ();
  336. }
  337. /// <inheritdoc/>
  338. public override void Redraw (Rect bounds)
  339. {
  340. if (!NeedDisplay.IsEmpty) {
  341. Driver.SetAttribute (GetNormalColor ());
  342. Clear ();
  343. }
  344. var savedClip = Border.Child.ClipToBounds ();
  345. Border.Child.Redraw (Border.Child.Bounds);
  346. Driver.Clip = savedClip;
  347. ClearLayoutNeeded ();
  348. ClearNeedsDisplay ();
  349. Driver.SetAttribute (GetNormalColor ());
  350. Border.DrawContent (this, false);
  351. if (HasFocus)
  352. Driver.SetAttribute (ColorScheme.HotNormal);
  353. if (Border.DrawMarginFrame) {
  354. if (!ustring.IsNullOrEmpty (Border.Title))
  355. Border.DrawTitle (this);
  356. else
  357. Border.DrawTitle (this, Frame);
  358. }
  359. Driver.SetAttribute (GetNormalColor ());
  360. // Checks if there are any SuperView view which intersect with this window.
  361. if (SuperView != null) {
  362. SuperView.SetNeedsLayout ();
  363. SuperView.SetNeedsDisplay ();
  364. }
  365. }
  366. /// <inheritdoc/>
  367. public override void OnCanFocusChanged ()
  368. {
  369. if (Border.Child != null) {
  370. Border.Child.CanFocus = CanFocus;
  371. }
  372. base.OnCanFocusChanged ();
  373. }
  374. }
  375. private class ChildContentView : View {
  376. View instance;
  377. public ChildContentView (Rect frame, View instance) : base (frame)
  378. {
  379. this.instance = instance;
  380. }
  381. public ChildContentView (View instance)
  382. {
  383. this.instance = instance;
  384. }
  385. public override bool MouseEvent (MouseEvent mouseEvent)
  386. {
  387. return instance.MouseEvent (mouseEvent);
  388. }
  389. }
  390. /// <summary>
  391. /// Invoked when any property of Border changes (except <see cref="Child"/>).
  392. /// </summary>
  393. public event Action<Border> BorderChanged;
  394. private BorderStyle borderStyle;
  395. private bool drawMarginFrame;
  396. private Thickness borderThickness = new Thickness (0);
  397. private Color borderBrush;
  398. private Color background;
  399. private Thickness padding = new Thickness (0);
  400. private bool effect3D;
  401. private Point effect3DOffset = new Point (1, 1);
  402. private Attribute? effect3DBrush;
  403. private ustring title = ustring.Empty;
  404. /// <summary>
  405. /// Specifies the <see cref="Gui.BorderStyle"/> for a view.
  406. /// </summary>
  407. public BorderStyle BorderStyle {
  408. get => borderStyle;
  409. set {
  410. if (value != BorderStyle.None && !drawMarginFrame) {
  411. // Ensures drawn the border lines.
  412. drawMarginFrame = true;
  413. }
  414. borderStyle = value;
  415. OnBorderChanged ();
  416. }
  417. }
  418. /// <summary>
  419. /// Gets or sets if a margin frame is drawn around the <see cref="Child"/> regardless the <see cref="BorderStyle"/>
  420. /// </summary>
  421. public bool DrawMarginFrame {
  422. get => drawMarginFrame;
  423. set {
  424. if (borderStyle != BorderStyle.None
  425. && (!value || !drawMarginFrame)) {
  426. // Ensures drawn the border lines.
  427. drawMarginFrame = true;
  428. } else {
  429. drawMarginFrame = value;
  430. }
  431. OnBorderChanged ();
  432. }
  433. }
  434. /// <summary>
  435. /// Gets or sets the relative <see cref="Thickness"/> of a <see cref="Border"/>.
  436. /// </summary>
  437. public Thickness BorderThickness {
  438. get => borderThickness;
  439. set {
  440. borderThickness = value;
  441. OnBorderChanged ();
  442. }
  443. }
  444. /// <summary>
  445. /// Gets or sets the <see cref="Color"/> that draws the outer border color.
  446. /// </summary>
  447. public Color BorderBrush {
  448. get => borderBrush;
  449. set {
  450. borderBrush = value;
  451. OnBorderChanged ();
  452. }
  453. }
  454. /// <summary>
  455. /// Gets or sets the <see cref="Color"/> that fills the area between the bounds of a <see cref="Border"/>.
  456. /// </summary>
  457. public Color Background {
  458. get => background;
  459. set {
  460. background = value;
  461. OnBorderChanged ();
  462. }
  463. }
  464. /// <summary>
  465. /// Gets or sets a <see cref="Thickness"/> value that describes the amount of space between a
  466. /// <see cref="Border"/> and its child element.
  467. /// </summary>
  468. public Thickness Padding {
  469. get => padding;
  470. set {
  471. padding = value;
  472. OnBorderChanged ();
  473. }
  474. }
  475. /// <summary>
  476. /// Gets the rendered width of this element.
  477. /// </summary>
  478. public int ActualWidth {
  479. get {
  480. var driver = Application.Driver;
  481. if (Parent?.Border == null) {
  482. return Math.Min (Child?.Frame.Width + (2 * marginFrame) + Padding.Right
  483. + BorderThickness.Right + Padding.Left + BorderThickness.Left ?? 0, driver.Cols);
  484. }
  485. return Math.Min (Parent.Frame.Width, driver.Cols);
  486. }
  487. }
  488. /// <summary>
  489. /// Gets the rendered height of this element.
  490. /// </summary>
  491. public int ActualHeight {
  492. get {
  493. var driver = Application.Driver;
  494. if (Parent?.Border == null) {
  495. return Math.Min (Child?.Frame.Height + (2 * marginFrame) + Padding.Bottom
  496. + BorderThickness.Bottom + Padding.Top + BorderThickness.Top ?? 0, driver.Rows);
  497. }
  498. return Math.Min (Parent.Frame.Height, driver.Rows);
  499. }
  500. }
  501. /// <summary>
  502. /// Gets or sets the single child element of a <see cref="View"/>.
  503. /// </summary>
  504. public View Child { get; set; }
  505. /// <summary>
  506. /// Gets the parent <see cref="Child"/> parent if any.
  507. /// </summary>
  508. public View Parent { get => Child?.SuperView; }
  509. /// <summary>
  510. /// Gets or private sets by the <see cref="ToplevelContainer"/>
  511. /// </summary>
  512. public ToplevelContainer ChildContainer { get; private set; }
  513. /// <summary>
  514. /// Gets or sets the 3D effect around the <see cref="Border"/>.
  515. /// </summary>
  516. public bool Effect3D {
  517. get => effect3D;
  518. set {
  519. effect3D = value;
  520. OnBorderChanged ();
  521. }
  522. }
  523. /// <summary>
  524. /// Get or sets the offset start position for the <see cref="Effect3D"/>
  525. /// </summary>
  526. public Point Effect3DOffset {
  527. get => effect3DOffset;
  528. set {
  529. effect3DOffset = value;
  530. OnBorderChanged ();
  531. }
  532. }
  533. /// <summary>
  534. /// Gets or sets the color for the <see cref="Border"/>
  535. /// </summary>
  536. public Attribute? Effect3DBrush {
  537. get => effect3DBrush;
  538. set {
  539. effect3DBrush = value;
  540. OnBorderChanged ();
  541. }
  542. }
  543. /// <summary>
  544. /// The title to be displayed for this view.
  545. /// </summary>
  546. public ustring Title {
  547. get => title;
  548. set {
  549. title = value;
  550. OnBorderChanged ();
  551. }
  552. }
  553. /// <summary>
  554. /// Calculate the sum of the <see cref="Padding"/> and the <see cref="BorderThickness"/>
  555. /// </summary>
  556. /// <returns>The total of the <see cref="Border"/> <see cref="Thickness"/></returns>
  557. public Thickness GetSumThickness ()
  558. {
  559. return new Thickness () {
  560. Left = Padding.Left + BorderThickness.Left,
  561. Top = Padding.Top + BorderThickness.Top,
  562. Right = Padding.Right + BorderThickness.Right,
  563. Bottom = Padding.Bottom + BorderThickness.Bottom
  564. };
  565. }
  566. /// <summary>
  567. /// Drawn the <see cref="BorderThickness"/> more the <see cref="Padding"/>
  568. /// more the <see cref="Border.BorderStyle"/> and the <see cref="Effect3D"/>.
  569. /// </summary>
  570. /// <param name="view">The view to draw.</param>
  571. /// <param name="fill">If it will clear or not the content area.</param>
  572. public void DrawContent (View view = null, bool fill = true)
  573. {
  574. if (Child == null) {
  575. Child = view;
  576. }
  577. if (Parent?.Border != null) {
  578. DrawParentBorder (Parent.ViewToScreen (Parent.Bounds), fill);
  579. } else {
  580. DrawChildBorder (Child.ViewToScreen (Child.Bounds), fill);
  581. }
  582. }
  583. /// <summary>
  584. /// Same as <see cref="DrawContent"/> but drawing full frames for all borders.
  585. /// </summary>
  586. public void DrawFullContent ()
  587. {
  588. var borderThickness = BorderThickness;
  589. var padding = Padding;
  590. var marginFrame = DrawMarginFrame ? 1 : 0;
  591. var driver = Application.Driver;
  592. Rect scrRect;
  593. if (Parent?.Border != null) {
  594. scrRect = Parent.ViewToScreen (Parent.Bounds);
  595. } else {
  596. scrRect = Child.ViewToScreen (Child.Bounds);
  597. }
  598. Rect borderRect;
  599. if (Parent?.Border != null) {
  600. borderRect = scrRect;
  601. } else {
  602. borderRect = new Rect () {
  603. X = scrRect.X - marginFrame - padding.Left - borderThickness.Left,
  604. Y = scrRect.Y - marginFrame - padding.Top - borderThickness.Top,
  605. Width = ActualWidth,
  606. Height = ActualHeight
  607. };
  608. }
  609. var savedAttribute = driver.GetAttribute ();
  610. // Draw 3D effects
  611. if (Effect3D) {
  612. driver.SetAttribute (GetEffect3DBrush ());
  613. var effectBorder = new Rect () {
  614. X = borderRect.X + Effect3DOffset.X,
  615. Y = borderRect.Y + Effect3DOffset.Y,
  616. Width = ActualWidth,
  617. Height = ActualHeight
  618. };
  619. //Child.Clear (effectBorder);
  620. for (int r = effectBorder.Y; r < Math.Min (effectBorder.Bottom, driver.Rows); r++) {
  621. for (int c = effectBorder.X; c < Math.Min (effectBorder.Right, driver.Cols); c++) {
  622. AddRuneAt (driver, c, r, (Rune)driver.Contents [r, c, 0]);
  623. }
  624. }
  625. }
  626. // Draw border thickness
  627. driver.SetAttribute (new Attribute (BorderBrush));
  628. Child.Clear (borderRect);
  629. borderRect = new Rect () {
  630. X = borderRect.X + borderThickness.Left,
  631. Y = borderRect.Y + borderThickness.Top,
  632. Width = Math.Max (borderRect.Width - borderThickness.Right - borderThickness.Left, 0),
  633. Height = Math.Max (borderRect.Height - borderThickness.Bottom - borderThickness.Top, 0)
  634. };
  635. if (borderRect != scrRect) {
  636. // Draw padding
  637. driver.SetAttribute (new Attribute (Background));
  638. Child.Clear (borderRect);
  639. }
  640. driver.SetAttribute (savedAttribute);
  641. // Draw margin frame
  642. if (DrawMarginFrame) {
  643. if (Parent?.Border != null) {
  644. var sumPadding = GetSumThickness ();
  645. borderRect = new Rect () {
  646. X = scrRect.X + sumPadding.Left,
  647. Y = scrRect.Y + sumPadding.Top,
  648. Width = Math.Max (scrRect.Width - sumPadding.Right - sumPadding.Left, 0),
  649. Height = Math.Max (scrRect.Height - sumPadding.Bottom - sumPadding.Top, 0)
  650. };
  651. } else {
  652. borderRect = new Rect () {
  653. X = borderRect.X + padding.Left,
  654. Y = borderRect.Y + padding.Top,
  655. Width = Math.Max (borderRect.Width - padding.Right - padding.Left, 0),
  656. Height = Math.Max (borderRect.Height - padding.Bottom - padding.Top, 0)
  657. };
  658. }
  659. if (borderRect.Width > 0 && borderRect.Height > 0) {
  660. driver.DrawWindowFrame (borderRect, 1, 1, 1, 1, BorderStyle != BorderStyle.None, fill: true, this);
  661. }
  662. }
  663. }
  664. private void DrawChildBorder (Rect frame, bool fill = true)
  665. {
  666. var drawMarginFrame = DrawMarginFrame ? 1 : 0;
  667. var sumThickness = GetSumThickness ();
  668. var padding = Padding;
  669. var effect3DOffset = Effect3DOffset;
  670. var driver = Application.Driver;
  671. var savedAttribute = driver.GetAttribute ();
  672. driver.SetAttribute (new Attribute (BorderBrush));
  673. // Draw the upper BorderThickness
  674. for (int r = frame.Y - drawMarginFrame - sumThickness.Top;
  675. r < frame.Y - drawMarginFrame - padding.Top; r++) {
  676. for (int c = frame.X - drawMarginFrame - sumThickness.Left;
  677. c < Math.Min (frame.Right + drawMarginFrame + sumThickness.Right, driver.Cols); c++) {
  678. AddRuneAt (driver, c, r, ' ');
  679. }
  680. }
  681. // Draw the left BorderThickness
  682. for (int r = frame.Y - drawMarginFrame - padding.Top;
  683. r < Math.Min (frame.Bottom + drawMarginFrame + padding.Bottom, driver.Rows); r++) {
  684. for (int c = frame.X - drawMarginFrame - sumThickness.Left;
  685. c < frame.X - drawMarginFrame - padding.Left; c++) {
  686. AddRuneAt (driver, c, r, ' ');
  687. }
  688. }
  689. // Draw the right BorderThickness
  690. for (int r = frame.Y - drawMarginFrame - padding.Top;
  691. r < Math.Min (frame.Bottom + drawMarginFrame + padding.Bottom, driver.Rows); r++) {
  692. for (int c = frame.Right + drawMarginFrame + padding.Right;
  693. c < Math.Min (frame.Right + drawMarginFrame + sumThickness.Right, driver.Cols); c++) {
  694. AddRuneAt (driver, c, r, ' ');
  695. }
  696. }
  697. // Draw the lower BorderThickness
  698. for (int r = frame.Bottom + drawMarginFrame + padding.Bottom;
  699. r < Math.Min (frame.Bottom + drawMarginFrame + sumThickness.Bottom, driver.Rows); r++) {
  700. for (int c = frame.X - drawMarginFrame - sumThickness.Left;
  701. c < Math.Min (frame.Right + drawMarginFrame + sumThickness.Right, driver.Cols); c++) {
  702. AddRuneAt (driver, c, r, ' ');
  703. }
  704. }
  705. driver.SetAttribute (new Attribute (Background));
  706. // Draw the upper Padding
  707. for (int r = frame.Y - drawMarginFrame - padding.Top;
  708. r < frame.Y - drawMarginFrame; r++) {
  709. for (int c = frame.X - drawMarginFrame - padding.Left;
  710. c < Math.Min (frame.Right + drawMarginFrame + padding.Right, driver.Cols); c++) {
  711. AddRuneAt (driver, c, r, ' ');
  712. }
  713. }
  714. // Draw the left Padding
  715. for (int r = frame.Y - drawMarginFrame;
  716. r < Math.Min (frame.Bottom + drawMarginFrame, driver.Rows); r++) {
  717. for (int c = frame.X - drawMarginFrame - padding.Left;
  718. c < frame.X - drawMarginFrame; c++) {
  719. AddRuneAt (driver, c, r, ' ');
  720. }
  721. }
  722. // Draw the right Padding
  723. for (int r = frame.Y - drawMarginFrame;
  724. r < Math.Min (frame.Bottom + drawMarginFrame, driver.Rows); r++) {
  725. for (int c = frame.Right + drawMarginFrame;
  726. c < Math.Min (frame.Right + drawMarginFrame + padding.Right, driver.Cols); c++) {
  727. AddRuneAt (driver, c, r, ' ');
  728. }
  729. }
  730. // Draw the lower Padding
  731. for (int r = frame.Bottom + drawMarginFrame;
  732. r < Math.Min (frame.Bottom + drawMarginFrame + padding.Bottom, driver.Rows); r++) {
  733. for (int c = frame.X - drawMarginFrame - padding.Left;
  734. c < Math.Min (frame.Right + drawMarginFrame + padding.Right, driver.Cols); c++) {
  735. AddRuneAt (driver, c, r, ' ');
  736. }
  737. }
  738. driver.SetAttribute (savedAttribute);
  739. // Draw the MarginFrame
  740. if (DrawMarginFrame) {
  741. var rect = new Rect () {
  742. X = frame.X - drawMarginFrame,
  743. Y = frame.Y - drawMarginFrame,
  744. Width = frame.Width + (2 * drawMarginFrame),
  745. Height = frame.Height + (2 * drawMarginFrame)
  746. };
  747. if (rect.Width > 0 && rect.Height > 0) {
  748. driver.DrawWindowFrame (rect, 1, 1, 1, 1, BorderStyle != BorderStyle.None, fill, this);
  749. DrawTitle (Child);
  750. }
  751. //var rect = Child.ViewToScreen (new Rect (-1, -1, Child.Frame.Width + 2, Child.Frame.Height + 2));
  752. //if (rect.Width > 0 && rect.Height > 0) {
  753. // var lc = new LineCanvas ();
  754. // lc.AddLine (rect.Location, rect.Width-1, Orientation.Horizontal, BorderStyle);
  755. // lc.AddLine (rect.Location, rect.Height-1, Orientation.Vertical, BorderStyle);
  756. // lc.AddLine (new Point (rect.X, rect.Y + rect.Height-1), rect.Width, Orientation.Horizontal, BorderStyle);
  757. // lc.AddLine (new Point (rect.X + rect.Width-1, rect.Y), rect.Height, Orientation.Vertical, BorderStyle);
  758. // //driver.SetAttribute (new Attribute(Color.Red, Color.BrightYellow));
  759. // foreach (var p in lc.GenerateImage (rect)) {
  760. // AddRuneAt (driver, p.Key.X, p.Key.Y, p.Value);
  761. // }
  762. // DrawTitle (Child);
  763. //}
  764. }
  765. if (Effect3D) {
  766. driver.SetAttribute (GetEffect3DBrush ());
  767. // Draw the upper Effect3D
  768. for (int r = frame.Y - drawMarginFrame - sumThickness.Top + effect3DOffset.Y;
  769. r >= 0 && r < frame.Y - drawMarginFrame - sumThickness.Top; r++) {
  770. for (int c = frame.X - drawMarginFrame - sumThickness.Left + effect3DOffset.X;
  771. c >= 0 && c < Math.Min (frame.Right + drawMarginFrame + sumThickness.Right + effect3DOffset.X, driver.Cols); c++) {
  772. AddRuneAt (driver, c, r, (Rune)driver.Contents [r, c, 0]);
  773. }
  774. }
  775. // Draw the left Effect3D
  776. for (int r = frame.Y - drawMarginFrame - sumThickness.Top + effect3DOffset.Y;
  777. r >= 0 && r < Math.Min (frame.Bottom + drawMarginFrame + sumThickness.Bottom + effect3DOffset.Y, driver.Rows); r++) {
  778. for (int c = frame.X - drawMarginFrame - sumThickness.Left + effect3DOffset.X;
  779. c >= 0 && c < frame.X - drawMarginFrame - sumThickness.Left; c++) {
  780. AddRuneAt (driver, c, r, (Rune)driver.Contents [r, c, 0]);
  781. }
  782. }
  783. // Draw the right Effect3D
  784. for (int r = frame.Y - drawMarginFrame - sumThickness.Top + effect3DOffset.Y;
  785. r >= 0 && r < Math.Min (frame.Bottom + drawMarginFrame + sumThickness.Bottom + effect3DOffset.Y, driver.Rows); r++) {
  786. for (int c = frame.Right + drawMarginFrame + sumThickness.Right;
  787. c >= 0 && c < Math.Min (frame.Right + drawMarginFrame + sumThickness.Right + effect3DOffset.X, driver.Cols); c++) {
  788. AddRuneAt (driver, c, r, (Rune)driver.Contents [r, c, 0]);
  789. }
  790. }
  791. // Draw the lower Effect3D
  792. for (int r = frame.Bottom + drawMarginFrame + sumThickness.Bottom;
  793. r >= 0 && r < Math.Min (frame.Bottom + drawMarginFrame + sumThickness.Bottom + effect3DOffset.Y, driver.Rows); r++) {
  794. for (int c = frame.X - drawMarginFrame - sumThickness.Left + effect3DOffset.X;
  795. c >= 0 && c < Math.Min (frame.Right + drawMarginFrame + sumThickness.Right + effect3DOffset.X, driver.Cols); c++) {
  796. AddRuneAt (driver, c, r, (Rune)driver.Contents [r, c, 0]);
  797. }
  798. }
  799. }
  800. driver.SetAttribute (savedAttribute);
  801. }
  802. private void DrawParentBorder (Rect frame, bool fill = true)
  803. {
  804. var sumThickness = GetSumThickness ();
  805. var borderThickness = BorderThickness;
  806. var effect3DOffset = Effect3DOffset;
  807. var driver = Application.Driver;
  808. var savedAttribute = driver.GetAttribute ();
  809. driver.SetAttribute (new Attribute (BorderBrush));
  810. // Draw the upper BorderThickness
  811. for (int r = frame.Y;
  812. r < Math.Min (frame.Y + borderThickness.Top, frame.Bottom); r++) {
  813. for (int c = frame.X;
  814. c < Math.Min (frame.Right, driver.Cols); c++) {
  815. AddRuneAt (driver, c, r, ' ');
  816. }
  817. }
  818. // Draw the left BorderThickness
  819. for (int r = Math.Min (frame.Y + borderThickness.Top, frame.Bottom);
  820. r < Math.Min (frame.Bottom - borderThickness.Bottom, driver.Rows); r++) {
  821. for (int c = frame.X;
  822. c < Math.Min (frame.X + borderThickness.Left, frame.Right); c++) {
  823. AddRuneAt (driver, c, r, ' ');
  824. }
  825. }
  826. // Draw the right BorderThickness
  827. for (int r = Math.Min (frame.Y + borderThickness.Top, frame.Bottom);
  828. r < Math.Min (frame.Bottom - borderThickness.Bottom, driver.Rows); r++) {
  829. for (int c = Math.Max (frame.Right - borderThickness.Right, frame.X);
  830. c < Math.Min (frame.Right, driver.Cols); c++) {
  831. AddRuneAt (driver, c, r, ' ');
  832. }
  833. }
  834. // Draw the lower BorderThickness
  835. for (int r = Math.Max (frame.Bottom - borderThickness.Bottom, frame.Y);
  836. r < Math.Min (frame.Bottom, driver.Rows); r++) {
  837. for (int c = frame.X;
  838. c < Math.Min (frame.Right, driver.Cols); c++) {
  839. AddRuneAt (driver, c, r, ' ');
  840. }
  841. }
  842. driver.SetAttribute (new Attribute (Background));
  843. // Draw the upper Padding
  844. for (int r = frame.Y + borderThickness.Top;
  845. r < Math.Min (frame.Y + sumThickness.Top, frame.Bottom - borderThickness.Bottom); r++) {
  846. for (int c = frame.X + borderThickness.Left;
  847. c < Math.Min (frame.Right - borderThickness.Right, driver.Cols); c++) {
  848. AddRuneAt (driver, c, r, ' ');
  849. }
  850. }
  851. // Draw the left Padding
  852. for (int r = frame.Y + sumThickness.Top;
  853. r < Math.Min (frame.Bottom - sumThickness.Bottom, driver.Rows); r++) {
  854. for (int c = frame.X + borderThickness.Left;
  855. c < Math.Min (frame.X + sumThickness.Left, frame.Right - borderThickness.Right); c++) {
  856. AddRuneAt (driver, c, r, ' ');
  857. }
  858. }
  859. // Draw the right Padding
  860. for (int r = frame.Y + sumThickness.Top;
  861. r < Math.Min (frame.Bottom - sumThickness.Bottom, driver.Rows); r++) {
  862. for (int c = Math.Max (frame.Right - sumThickness.Right, frame.X + sumThickness.Left);
  863. c < Math.Max (frame.Right - borderThickness.Right, frame.X + sumThickness.Left); c++) {
  864. AddRuneAt (driver, c, r, ' ');
  865. }
  866. }
  867. // Draw the lower Padding
  868. for (int r = Math.Max (frame.Bottom - sumThickness.Bottom, frame.Y + borderThickness.Top);
  869. r < Math.Min (frame.Bottom - borderThickness.Bottom, driver.Rows); r++) {
  870. for (int c = frame.X + borderThickness.Left;
  871. c < Math.Min (frame.Right - borderThickness.Right, driver.Cols); c++) {
  872. AddRuneAt (driver, c, r, ' ');
  873. }
  874. }
  875. driver.SetAttribute (savedAttribute);
  876. // Draw the MarginFrame
  877. if (DrawMarginFrame) {
  878. var rect = new Rect () {
  879. X = frame.X + sumThickness.Left,
  880. Y = frame.Y + sumThickness.Top,
  881. Width = Math.Max (frame.Width - sumThickness.Right - sumThickness.Left, 0),
  882. Height = Math.Max (frame.Height - sumThickness.Bottom - sumThickness.Top, 0)
  883. };
  884. if (rect.Width > 0 && rect.Height > 0) {
  885. driver.DrawWindowFrame (rect, 1, 1, 1, 1, BorderStyle != BorderStyle.None, fill, this);
  886. DrawTitle (Parent);
  887. }
  888. }
  889. if (Effect3D) {
  890. driver.SetAttribute (GetEffect3DBrush ());
  891. // Draw the upper Effect3D
  892. for (int r = Math.Max (frame.Y + effect3DOffset.Y, 0);
  893. r < frame.Y; r++) {
  894. for (int c = Math.Max (frame.X + effect3DOffset.X, 0);
  895. c < Math.Min (frame.Right + effect3DOffset.X, driver.Cols); c++) {
  896. AddRuneAt (driver, c, r, (Rune)driver.Contents [r, c, 0]);
  897. }
  898. }
  899. // Draw the left Effect3D
  900. for (int r = Math.Max (frame.Y + effect3DOffset.Y, 0);
  901. r < Math.Min (frame.Bottom + effect3DOffset.Y, driver.Rows); r++) {
  902. for (int c = Math.Max (frame.X + effect3DOffset.X, 0);
  903. c < frame.X; c++) {
  904. AddRuneAt (driver, c, r, (Rune)driver.Contents [r, c, 0]);
  905. }
  906. }
  907. // Draw the right Effect3D
  908. for (int r = Math.Max (frame.Y + effect3DOffset.Y, 0);
  909. r < Math.Min (frame.Bottom + effect3DOffset.Y, driver.Rows); r++) {
  910. for (int c = frame.Right;
  911. c < Math.Min (frame.Right + effect3DOffset.X, driver.Cols); c++) {
  912. AddRuneAt (driver, c, r, (Rune)driver.Contents [r, c, 0]);
  913. }
  914. }
  915. // Draw the lower Effect3D
  916. for (int r = frame.Bottom;
  917. r < Math.Min (frame.Bottom + effect3DOffset.Y, driver.Rows); r++) {
  918. for (int c = Math.Max (frame.X + effect3DOffset.X, 0);
  919. c < Math.Min (frame.Right + effect3DOffset.X, driver.Cols); c++) {
  920. AddRuneAt (driver, c, r, (Rune)driver.Contents [r, c, 0]);
  921. }
  922. }
  923. }
  924. driver.SetAttribute (savedAttribute);
  925. }
  926. private Attribute GetEffect3DBrush ()
  927. {
  928. return Effect3DBrush == null
  929. ? new Attribute (Color.Gray, Color.DarkGray)
  930. : (Attribute)Effect3DBrush;
  931. }
  932. private void AddRuneAt (ConsoleDriver driver, int col, int row, Rune ch)
  933. {
  934. if (col < driver.Cols && row < driver.Rows && col > 0 && driver.Contents [row, col, 2] == 0
  935. && Rune.ColumnWidth ((char)driver.Contents [row, col - 1, 0]) > 1) {
  936. driver.Contents [row, col, 1] = driver.GetAttribute ();
  937. return;
  938. }
  939. driver.Move (col, row);
  940. driver.AddRune (ch);
  941. }
  942. /// <summary>
  943. /// Draws the view <see cref="Title"/> to the screen.
  944. /// </summary>
  945. /// <param name="view">The view.</param>
  946. public void DrawTitle (View view)
  947. {
  948. var driver = Application.Driver;
  949. if (DrawMarginFrame) {
  950. driver.SetAttribute (Child.GetNormalColor ());
  951. if (Child.HasFocus)
  952. driver.SetAttribute (Child.ColorScheme.HotNormal);
  953. var padding = view.Border.GetSumThickness ();
  954. Rect scrRect;
  955. if (view == Child) {
  956. scrRect = view.ViewToScreen (new Rect (0, 0, view.Frame.Width + 2, view.Frame.Height + 2));
  957. scrRect = new Rect (scrRect.X - 1, scrRect.Y - 1, scrRect.Width, scrRect.Height);
  958. driver.DrawWindowTitle (scrRect, Title, 0, 0, 0, 0);
  959. } else {
  960. scrRect = view.ViewToScreen (new Rect (0, 0, view.Frame.Width, view.Frame.Height));
  961. driver.DrawWindowTitle (scrRect, Title,
  962. padding.Left, padding.Top, padding.Right, padding.Bottom);
  963. }
  964. }
  965. driver.SetAttribute (Child.GetNormalColor ());
  966. }
  967. /// <summary>
  968. /// Draws the <see cref="View.Text"/> to the screen.
  969. /// </summary>
  970. /// <param name="view">The view.</param>
  971. /// <param name="rect">The frame.</param>
  972. public void DrawTitle (View view, Rect rect)
  973. {
  974. var driver = Application.Driver;
  975. if (DrawMarginFrame) {
  976. driver.SetAttribute (view.GetNormalColor ());
  977. if (view.HasFocus) {
  978. driver.SetAttribute (view.ColorScheme.HotNormal);
  979. }
  980. var padding = Parent.Border.GetSumThickness ();
  981. var scrRect = Parent.ViewToScreen (new Rect (0, 0, rect.Width, rect.Height));
  982. driver.DrawWindowTitle (scrRect, view.Text,
  983. padding.Left, padding.Top, padding.Right, padding.Bottom);
  984. }
  985. driver.SetAttribute (view.GetNormalColor ());
  986. }
  987. /// <summary>
  988. /// Invoke the <see cref="BorderChanged"/> event.
  989. /// </summary>
  990. public virtual void OnBorderChanged ()
  991. {
  992. BorderChanged?.Invoke (this);
  993. }
  994. }
  995. }