Border.cs 27 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906
  1. using System;
  2. namespace Terminal.Gui {
  3. /// <summary>
  4. /// Specifies the border style for a <see cref="View"/> and to be used by the <see cref="Border"/> class.
  5. /// </summary>
  6. public enum BorderStyle {
  7. /// <summary>
  8. /// No border is drawn.
  9. /// </summary>
  10. None,
  11. /// <summary>
  12. /// The border is drawn with a single line limits.
  13. /// </summary>
  14. Single,
  15. /// <summary>
  16. /// The border is drawn with a double line limits.
  17. /// </summary>
  18. Double,
  19. /// <summary>
  20. /// The border is drawn with a single line and rounded corners limits.
  21. /// </summary>
  22. Rounded
  23. }
  24. /// <summary>
  25. /// Describes the thickness of a frame around a rectangle. Four <see cref="int"/> values describe
  26. /// the <see cref="Left"/>, <see cref="Top"/>, <see cref="Right"/>, and <see cref="Bottom"/> sides
  27. /// of the rectangle, respectively.
  28. /// </summary>
  29. public struct Thickness {
  30. /// <summary>
  31. /// Gets or sets the width, in integers, of the left side of the bounding rectangle.
  32. /// </summary>
  33. public int Left;
  34. /// <summary>
  35. /// Gets or sets the width, in integers, of the upper side of the bounding rectangle.
  36. /// </summary>
  37. public int Top;
  38. /// <summary>
  39. /// Gets or sets the width, in integers, of the right side of the bounding rectangle.
  40. /// </summary>
  41. public int Right;
  42. /// <summary>
  43. /// Gets or sets the width, in integers, of the lower side of the bounding rectangle.
  44. /// </summary>
  45. public int Bottom;
  46. /// <summary>
  47. /// Initializes a new instance of the <see cref="Thickness"/> structure that has the
  48. /// specified uniform length on each side.
  49. /// </summary>
  50. /// <param name="length"></param>
  51. public Thickness (int length)
  52. {
  53. if (length < 0) {
  54. throw new ArgumentException ("Invalid value for this property.");
  55. }
  56. Left = Top = Right = Bottom = length;
  57. }
  58. /// <summary>
  59. /// Initializes a new instance of the <see cref="Thickness"/> structure that has specific
  60. /// lengths (supplied as a <see cref="int"/>) applied to each side of the rectangle.
  61. /// </summary>
  62. /// <param name="left"></param>
  63. /// <param name="top"></param>
  64. /// <param name="right"></param>
  65. /// <param name="bottom"></param>
  66. public Thickness (int left, int top, int right, int bottom)
  67. {
  68. if (left < 0 || top < 0 || right < 0 || bottom < 0) {
  69. throw new ArgumentException ("Invalid value for this property.");
  70. }
  71. Left = left;
  72. Top = top;
  73. Right = right;
  74. Bottom = bottom;
  75. }
  76. /// <summary>Returns the fully qualified type name of this instance.</summary>
  77. /// <returns>The fully qualified type name.</returns>
  78. public override string ToString ()
  79. {
  80. return $"(Left={Left},Top={Top},Right={Right},Bottom={Bottom})";
  81. }
  82. }
  83. /// <summary>
  84. /// Draws a border, background, or both around another element.
  85. /// </summary>
  86. public class Border {
  87. private int marginFrame => DrawMarginFrame ? 1 : 0;
  88. /// <summary>
  89. /// A sealed <see cref="Toplevel"/> derived class to implement <see cref="Border"/> feature.
  90. /// This is only a wrapper to get borders on a toplevel and is recommended using another
  91. /// derived, like <see cref="Window"/> where is possible to have borders with or without
  92. /// border line or spacing around.
  93. /// </summary>
  94. public sealed class ToplevelContainer : Toplevel {
  95. /// <inheritdoc/>
  96. public override Border Border {
  97. get => base.Border;
  98. set {
  99. if (base.Border != null && base.Border.Child != null && value.Child == null) {
  100. value.Child = base.Border.Child;
  101. }
  102. base.Border = value;
  103. if (value == null) {
  104. return;
  105. }
  106. Rect frame;
  107. if (Border.Child != null && (Border.Child.Width is Dim || Border.Child.Height is Dim)) {
  108. frame = Rect.Empty;
  109. } else {
  110. frame = Frame;
  111. }
  112. AdjustContentView (frame);
  113. Border.BorderChanged += Border_BorderChanged;
  114. }
  115. }
  116. void Border_BorderChanged (Border border)
  117. {
  118. Rect frame;
  119. if (Border.Child != null && (Border.Child.Width is Dim || Border.Child.Height is Dim)) {
  120. frame = Rect.Empty;
  121. } else {
  122. frame = Frame;
  123. }
  124. AdjustContentView (frame);
  125. }
  126. /// <summary>
  127. /// Initializes with default null values.
  128. /// </summary>
  129. public ToplevelContainer () : this (null, null) { }
  130. /// <summary>
  131. /// Initializes a <see cref="ToplevelContainer"/> with a <see cref="LayoutStyle.Computed"/>
  132. /// </summary>
  133. /// <param name="border">The border.</param>
  134. /// <param name="title">The title.</param>
  135. public ToplevelContainer (Border border, string title = null)
  136. {
  137. Initialize (Rect.Empty, border, title);
  138. }
  139. /// <summary>
  140. /// Initializes a <see cref="ToplevelContainer"/> with a <see cref="LayoutStyle.Absolute"/>
  141. /// </summary>
  142. /// <param name="frame">The frame.</param>
  143. /// <param name="border">The border.</param>
  144. /// <param name="title">The title.</param>
  145. public ToplevelContainer (Rect frame, Border border, string title = null) : base (frame)
  146. {
  147. Initialize (frame, border, title);
  148. }
  149. private void Initialize (Rect frame, Border border, string title = null)
  150. {
  151. ColorScheme = Colors.TopLevel;
  152. Text = title ?? "";
  153. if (border == null) {
  154. Border = new Border () {
  155. BorderStyle = BorderStyle.Single,
  156. BorderBrush = ColorScheme.Normal.Background
  157. };
  158. } else {
  159. Border = border;
  160. }
  161. }
  162. void AdjustContentView (Rect frame)
  163. {
  164. var borderLength = Border.DrawMarginFrame ? 1 : 0;
  165. var sumPadding = Border.GetSumThickness ();
  166. var wb = new Size ();
  167. if (frame == Rect.Empty) {
  168. wb.Width = borderLength + sumPadding.Right;
  169. wb.Height = borderLength + sumPadding.Bottom;
  170. if (Border.Child == null) {
  171. Border.Child = new ChildContentView (this) {
  172. X = borderLength + sumPadding.Left,
  173. Y = borderLength + sumPadding.Top,
  174. Width = Dim.Fill (wb.Width),
  175. Height = Dim.Fill (wb.Height)
  176. };
  177. } else {
  178. Border.Child.X = borderLength + sumPadding.Left;
  179. Border.Child.Y = borderLength + sumPadding.Top;
  180. Border.Child.Width = Dim.Fill (wb.Width);
  181. Border.Child.Height = Dim.Fill (wb.Height);
  182. }
  183. } else {
  184. wb.Width = (2 * borderLength) + sumPadding.Right + sumPadding.Left;
  185. wb.Height = (2 * borderLength) + sumPadding.Bottom + sumPadding.Top;
  186. var cFrame = new Rect (borderLength + sumPadding.Left, borderLength + sumPadding.Top, frame.Width - wb.Width, frame.Height - wb.Height);
  187. if (Border.Child == null) {
  188. Border.Child = new ChildContentView (cFrame, this);
  189. } else {
  190. Border.Child.Frame = cFrame;
  191. }
  192. }
  193. base.Add (Border.Child);
  194. Border.ChildContainer = this;
  195. }
  196. /// <inheritdoc/>
  197. public override void Add (View view)
  198. {
  199. Border.Child.Add (view);
  200. if (view.CanFocus) {
  201. CanFocus = true;
  202. }
  203. AddMenuStatusBar (view);
  204. }
  205. /// <inheritdoc/>
  206. public override void Remove (View view)
  207. {
  208. if (view == null) {
  209. return;
  210. }
  211. SetNeedsDisplay ();
  212. var touched = view.Frame;
  213. Border.Child.Remove (view);
  214. if (Border.Child.InternalSubviews.Count < 1) {
  215. CanFocus = false;
  216. }
  217. RemoveMenuStatusBar (view);
  218. }
  219. /// <inheritdoc/>
  220. public override void RemoveAll ()
  221. {
  222. Border.Child.RemoveAll ();
  223. }
  224. /// <inheritdoc/>
  225. public override void Redraw (Rect bounds)
  226. {
  227. if (!NeedDisplay.IsEmpty) {
  228. Driver.SetAttribute (GetNormalColor ());
  229. Border.DrawContent ();
  230. }
  231. var savedClip = Border.Child.ClipToBounds ();
  232. Border.Child.Redraw (Border.Child.Bounds);
  233. Driver.Clip = savedClip;
  234. ClearLayoutNeeded ();
  235. ClearNeedsDisplay ();
  236. if (Border.BorderStyle != BorderStyle.None) {
  237. Driver.SetAttribute (GetNormalColor ());
  238. Border.DrawTitle (this, this.Frame);
  239. }
  240. // Checks if there are any SuperView view which intersect with this window.
  241. if (SuperView != null) {
  242. SuperView.SetNeedsLayout ();
  243. SuperView.SetNeedsDisplay ();
  244. }
  245. }
  246. /// <inheritdoc/>
  247. public override void OnCanFocusChanged ()
  248. {
  249. if (Border.Child != null) {
  250. Border.Child.CanFocus = CanFocus;
  251. }
  252. base.OnCanFocusChanged ();
  253. }
  254. }
  255. private class ChildContentView : View {
  256. View instance;
  257. public ChildContentView (Rect frame, View instance) : base (frame)
  258. {
  259. this.instance = instance;
  260. }
  261. public ChildContentView (View instance)
  262. {
  263. this.instance = instance;
  264. }
  265. public override bool MouseEvent (MouseEvent mouseEvent)
  266. {
  267. return instance.MouseEvent (mouseEvent);
  268. }
  269. }
  270. /// <summary>
  271. /// Event to be invoked when any border property change.
  272. /// </summary>
  273. public event Action<Border> BorderChanged;
  274. private BorderStyle borderStyle;
  275. private bool drawMarginFrame;
  276. private Thickness borderThickness;
  277. private Thickness padding;
  278. private bool effect3D;
  279. private Point effect3DOffset = new Point (1, 1);
  280. /// <summary>
  281. /// Specifies the <see cref="Gui.BorderStyle"/> for a view.
  282. /// </summary>
  283. public BorderStyle BorderStyle {
  284. get => borderStyle;
  285. set {
  286. if (value != BorderStyle.None && !drawMarginFrame) {
  287. // Ensures drawn the border lines.
  288. drawMarginFrame = true;
  289. }
  290. borderStyle = value;
  291. OnBorderChanged ();
  292. }
  293. }
  294. /// <summary>
  295. /// Gets or sets if a margin frame is drawn around the <see cref="Child"/> regardless the <see cref="BorderStyle"/>
  296. /// </summary>
  297. public bool DrawMarginFrame {
  298. get => drawMarginFrame;
  299. set {
  300. if (borderStyle != BorderStyle.None
  301. && (!value || !drawMarginFrame)) {
  302. // Ensures drawn the border lines.
  303. drawMarginFrame = true;
  304. } else {
  305. drawMarginFrame = value;
  306. }
  307. OnBorderChanged ();
  308. }
  309. }
  310. /// <summary>
  311. /// Gets or sets the relative <see cref="Thickness"/> of a <see cref="Border"/>.
  312. /// </summary>
  313. public Thickness BorderThickness {
  314. get => borderThickness;
  315. set {
  316. borderThickness = value;
  317. OnBorderChanged ();
  318. }
  319. }
  320. /// <summary>
  321. /// Gets or sets the <see cref="Color"/> that draws the outer border color.
  322. /// </summary>
  323. public Color BorderBrush { get; set; }
  324. /// <summary>
  325. /// Gets or sets the <see cref="Color"/> that fills the area between the bounds of a <see cref="Border"/>.
  326. /// </summary>
  327. public Color Background { get; set; }
  328. /// <summary>
  329. /// Gets or sets a <see cref="Thickness"/> value that describes the amount of space between a
  330. /// <see cref="Border"/> and its child element.
  331. /// </summary>
  332. public Thickness Padding {
  333. get => padding;
  334. set {
  335. padding = value;
  336. OnBorderChanged ();
  337. }
  338. }
  339. /// <summary>
  340. /// Gets the rendered width of this element.
  341. /// </summary>
  342. public int ActualWidth {
  343. get {
  344. var driver = Application.Driver;
  345. if (Parent?.Border == null) {
  346. return Math.Min (Child?.Frame.Width + (2 * marginFrame) + Padding.Right
  347. + BorderThickness.Right + Padding.Left + BorderThickness.Left ?? 0, driver.Cols);
  348. }
  349. return Math.Min (Parent.Frame.Width, driver.Cols);
  350. }
  351. }
  352. /// <summary>
  353. /// Gets the rendered height of this element.
  354. /// </summary>
  355. public int ActualHeight {
  356. get {
  357. var driver = Application.Driver;
  358. if (Parent?.Border == null) {
  359. return Math.Min (Child?.Frame.Height + (2 * marginFrame) + Padding.Bottom
  360. + BorderThickness.Bottom + Padding.Top + BorderThickness.Top ?? 0, driver.Rows);
  361. }
  362. return Math.Min (Parent.Frame.Height, driver.Rows);
  363. }
  364. }
  365. /// <summary>
  366. /// Gets or sets the single child element of a <see cref="View"/>.
  367. /// </summary>
  368. public View Child { get; set; }
  369. /// <summary>
  370. /// Gets the parent <see cref="Child"/> parent if any.
  371. /// </summary>
  372. public View Parent { get => Child?.SuperView; }
  373. /// <summary>
  374. /// Gets or private sets by the <see cref="ToplevelContainer"/>
  375. /// </summary>
  376. public ToplevelContainer ChildContainer { get; private set; }
  377. /// <summary>
  378. /// Gets or sets the 3D effect around the <see cref="Border"/>.
  379. /// </summary>
  380. public bool Effect3D {
  381. get => effect3D;
  382. set {
  383. effect3D = value;
  384. OnBorderChanged ();
  385. }
  386. }
  387. /// <summary>
  388. /// Get or sets the offset start position for the <see cref="Effect3D"/>
  389. /// </summary>
  390. public Point Effect3DOffset {
  391. get => effect3DOffset;
  392. set {
  393. effect3DOffset = value;
  394. OnBorderChanged ();
  395. }
  396. }
  397. /// <summary>
  398. /// Gets or sets the color for the <see cref="Border"/>
  399. /// </summary>
  400. public Attribute? Effect3DBrush { get; set; }
  401. /// <summary>
  402. /// Calculate the sum of the <see cref="Padding"/> and the <see cref="BorderThickness"/>
  403. /// </summary>
  404. /// <returns>The total of the <see cref="Border"/> <see cref="Thickness"/></returns>
  405. public Thickness GetSumThickness ()
  406. {
  407. return new Thickness () {
  408. Left = Padding.Left + BorderThickness.Left,
  409. Top = Padding.Top + BorderThickness.Top,
  410. Right = Padding.Right + BorderThickness.Right,
  411. Bottom = Padding.Bottom + BorderThickness.Bottom
  412. };
  413. }
  414. /// <summary>
  415. /// Drawn the <see cref="BorderThickness"/> more the <see cref="Padding"/>
  416. /// more the <see cref="Border.BorderStyle"/> and the <see cref="Effect3D"/>.
  417. /// </summary>
  418. public void DrawContent (View view = null)
  419. {
  420. if (Child == null) {
  421. Child = view;
  422. }
  423. if (Parent?.Border != null) {
  424. DrawParentBorder (Parent.ViewToScreen (Parent.Bounds));
  425. } else {
  426. DrawChildBorder (Child.ViewToScreen (Child.Bounds));
  427. }
  428. }
  429. /// <summary>
  430. /// Same as <see cref="DrawContent"/> but drawing full frames for all borders.
  431. /// </summary>
  432. public void DrawFullContent ()
  433. {
  434. var borderThickness = BorderThickness;
  435. var padding = Padding;
  436. var marginFrame = DrawMarginFrame ? 1 : 0;
  437. var driver = Application.Driver;
  438. Rect scrRect;
  439. if (Parent?.Border != null) {
  440. scrRect = Parent.ViewToScreen (Parent.Bounds);
  441. } else {
  442. scrRect = Child.ViewToScreen (Child.Bounds);
  443. }
  444. Rect borderRect;
  445. if (Parent?.Border != null) {
  446. borderRect = scrRect;
  447. } else {
  448. borderRect = new Rect () {
  449. X = scrRect.X - marginFrame - padding.Left - borderThickness.Left,
  450. Y = scrRect.Y - marginFrame - padding.Top - borderThickness.Top,
  451. Width = ActualWidth,
  452. Height = ActualHeight
  453. };
  454. }
  455. var savedAttribute = driver.GetAttribute ();
  456. // Draw 3D effects
  457. if (Effect3D) {
  458. driver.SetAttribute (GetEffect3DBrush ());
  459. var effectBorder = new Rect () {
  460. X = borderRect.X + Effect3DOffset.X,
  461. Y = borderRect.Y + Effect3DOffset.Y,
  462. Width = ActualWidth,
  463. Height = ActualHeight
  464. };
  465. //Child.Clear (effectBorder);
  466. for (int r = effectBorder.Y; r < Math.Min (effectBorder.Bottom, driver.Rows); r++) {
  467. for (int c = effectBorder.X; c < Math.Min (effectBorder.Right, driver.Cols); c++) {
  468. AddRuneAt (driver, c, r, (Rune)driver.Contents [r, c, 0]);
  469. }
  470. }
  471. }
  472. // Draw border thickness
  473. driver.SetAttribute (new Attribute (BorderBrush));
  474. Child.Clear (borderRect);
  475. borderRect = new Rect () {
  476. X = borderRect.X + borderThickness.Left,
  477. Y = borderRect.Y + borderThickness.Top,
  478. Width = Math.Max (borderRect.Width - borderThickness.Right - borderThickness.Left, 0),
  479. Height = Math.Max (borderRect.Height - borderThickness.Bottom - borderThickness.Top, 0)
  480. };
  481. if (borderRect != scrRect) {
  482. // Draw padding
  483. driver.SetAttribute (new Attribute (Background));
  484. Child.Clear (borderRect);
  485. }
  486. driver.SetAttribute (savedAttribute);
  487. // Draw margin frame
  488. if (Parent?.Border != null) {
  489. var sumPadding = GetSumThickness ();
  490. borderRect = new Rect () {
  491. X = scrRect.X + sumPadding.Left,
  492. Y = scrRect.Y + sumPadding.Top,
  493. Width = Math.Max (scrRect.Width - sumPadding.Right - sumPadding.Left, 0),
  494. Height = Math.Max (scrRect.Height - sumPadding.Bottom - sumPadding.Top, 0)
  495. };
  496. } else {
  497. borderRect = new Rect () {
  498. X = borderRect.X + padding.Left,
  499. Y = borderRect.Y + padding.Top,
  500. Width = Math.Max (borderRect.Width - padding.Right - padding.Left, 0),
  501. Height = Math.Max (borderRect.Height - padding.Bottom - padding.Top, 0)
  502. };
  503. }
  504. if (borderRect.Width > 0 && borderRect.Height > 0) {
  505. driver.DrawWindowFrame (borderRect, 1, 1, 1, 1, BorderStyle != BorderStyle.None, fill: true, this);
  506. }
  507. }
  508. private void DrawChildBorder (Rect frame)
  509. {
  510. var drawMarginFrame = DrawMarginFrame ? 1 : 0;
  511. var sumThickness = GetSumThickness ();
  512. var padding = Padding;
  513. var effect3DOffset = Effect3DOffset;
  514. var driver = Application.Driver;
  515. var savedAttribute = driver.GetAttribute ();
  516. driver.SetAttribute (new Attribute (BorderBrush));
  517. // Draw the upper BorderThickness
  518. for (int r = frame.Y - drawMarginFrame - sumThickness.Top;
  519. r < frame.Y - drawMarginFrame - padding.Top; r++) {
  520. for (int c = frame.X - drawMarginFrame - sumThickness.Left;
  521. c < Math.Min (frame.Right + drawMarginFrame + sumThickness.Right, driver.Cols); c++) {
  522. AddRuneAt (driver, c, r, ' ');
  523. }
  524. }
  525. // Draw the left BorderThickness
  526. for (int r = frame.Y - drawMarginFrame - padding.Top;
  527. r < Math.Min (frame.Bottom + drawMarginFrame + padding.Bottom, driver.Rows); r++) {
  528. for (int c = frame.X - drawMarginFrame - sumThickness.Left;
  529. c < frame.X - drawMarginFrame - padding.Left; c++) {
  530. AddRuneAt (driver, c, r, ' ');
  531. }
  532. }
  533. // Draw the right BorderThickness
  534. for (int r = frame.Y - drawMarginFrame - padding.Top;
  535. r < Math.Min (frame.Bottom + drawMarginFrame + padding.Bottom, driver.Rows); r++) {
  536. for (int c = frame.Right + drawMarginFrame + padding.Right;
  537. c < Math.Min (frame.Right + drawMarginFrame + sumThickness.Right, driver.Cols); c++) {
  538. AddRuneAt (driver, c, r, ' ');
  539. }
  540. }
  541. // Draw the lower BorderThickness
  542. for (int r = frame.Bottom + drawMarginFrame + padding.Bottom;
  543. r < Math.Min (frame.Bottom + drawMarginFrame + sumThickness.Bottom, driver.Rows); r++) {
  544. for (int c = frame.X - drawMarginFrame - sumThickness.Left;
  545. c < Math.Min (frame.Right + drawMarginFrame + sumThickness.Right, driver.Cols); c++) {
  546. AddRuneAt (driver, c, r, ' ');
  547. }
  548. }
  549. driver.SetAttribute (new Attribute (Background));
  550. // Draw the upper Padding
  551. for (int r = frame.Y - drawMarginFrame - padding.Top;
  552. r < frame.Y - drawMarginFrame; r++) {
  553. for (int c = frame.X - drawMarginFrame - padding.Left;
  554. c < Math.Min (frame.Right + drawMarginFrame + padding.Right, driver.Cols); c++) {
  555. AddRuneAt (driver, c, r, ' ');
  556. }
  557. }
  558. // Draw the left Padding
  559. for (int r = frame.Y - drawMarginFrame;
  560. r < Math.Min (frame.Bottom + drawMarginFrame, driver.Rows); r++) {
  561. for (int c = frame.X - drawMarginFrame - padding.Left;
  562. c < frame.X - drawMarginFrame; c++) {
  563. AddRuneAt (driver, c, r, ' ');
  564. }
  565. }
  566. // Draw the right Padding
  567. for (int r = frame.Y - drawMarginFrame;
  568. r < Math.Min (frame.Bottom + drawMarginFrame, driver.Rows); r++) {
  569. for (int c = frame.Right + drawMarginFrame;
  570. c < Math.Min (frame.Right + drawMarginFrame + padding.Right, driver.Cols); c++) {
  571. AddRuneAt (driver, c, r, ' ');
  572. }
  573. }
  574. // Draw the lower Padding
  575. for (int r = frame.Bottom + drawMarginFrame;
  576. r < Math.Min (frame.Bottom + drawMarginFrame + padding.Bottom, driver.Rows); r++) {
  577. for (int c = frame.X - drawMarginFrame - padding.Left;
  578. c < Math.Min (frame.Right + drawMarginFrame + padding.Right, driver.Cols); c++) {
  579. AddRuneAt (driver, c, r, ' ');
  580. }
  581. }
  582. driver.SetAttribute (savedAttribute);
  583. // Draw the MarginFrame
  584. var rect = new Rect () {
  585. X = frame.X - drawMarginFrame,
  586. Y = frame.Y - drawMarginFrame,
  587. Width = frame.Width + (2 * drawMarginFrame),
  588. Height = frame.Height + (2 * drawMarginFrame)
  589. };
  590. if (rect.Width > 0 && rect.Height > 0) {
  591. driver.DrawWindowFrame (rect, 1, 1, 1, 1, BorderStyle != BorderStyle.None, fill: true, this);
  592. }
  593. if (Effect3D) {
  594. driver.SetAttribute (GetEffect3DBrush ());
  595. // Draw the upper Effect3D
  596. for (int r = frame.Y - drawMarginFrame - sumThickness.Top + effect3DOffset.Y;
  597. r >= 0 && r < frame.Y - drawMarginFrame - sumThickness.Top; r++) {
  598. for (int c = frame.X - drawMarginFrame - sumThickness.Left + effect3DOffset.X;
  599. c >= 0 && c < Math.Min (frame.Right + drawMarginFrame + sumThickness.Right + effect3DOffset.X, driver.Cols); c++) {
  600. AddRuneAt (driver, c, r, (Rune)driver.Contents [r, c, 0]);
  601. }
  602. }
  603. // Draw the left Effect3D
  604. for (int r = frame.Y - drawMarginFrame - sumThickness.Top + effect3DOffset.Y;
  605. r >= 0 && r < Math.Min (frame.Bottom + drawMarginFrame + sumThickness.Bottom + effect3DOffset.Y, driver.Rows); r++) {
  606. for (int c = frame.X - drawMarginFrame - sumThickness.Left + effect3DOffset.X;
  607. c >= 0 && c < frame.X - drawMarginFrame - sumThickness.Left; c++) {
  608. AddRuneAt (driver, c, r, (Rune)driver.Contents [r, c, 0]);
  609. }
  610. }
  611. // Draw the right Effect3D
  612. for (int r = frame.Y - drawMarginFrame - sumThickness.Top + effect3DOffset.Y;
  613. r >= 0 && r < Math.Min (frame.Bottom + drawMarginFrame + sumThickness.Bottom + effect3DOffset.Y, driver.Rows); r++) {
  614. for (int c = frame.Right + drawMarginFrame + sumThickness.Right;
  615. c >= 0 && c < Math.Min (frame.Right + drawMarginFrame + sumThickness.Right + effect3DOffset.X, driver.Cols); c++) {
  616. AddRuneAt (driver, c, r, (Rune)driver.Contents [r, c, 0]);
  617. }
  618. }
  619. // Draw the lower Effect3D
  620. for (int r = frame.Bottom + drawMarginFrame + sumThickness.Bottom;
  621. r >= 0 && r < Math.Min (frame.Bottom + drawMarginFrame + sumThickness.Bottom + effect3DOffset.Y, driver.Rows); r++) {
  622. for (int c = frame.X - drawMarginFrame - sumThickness.Left + effect3DOffset.X;
  623. c >= 0 && c < Math.Min (frame.Right + drawMarginFrame + sumThickness.Right + effect3DOffset.X, driver.Cols); c++) {
  624. AddRuneAt (driver, c, r, (Rune)driver.Contents [r, c, 0]);
  625. }
  626. }
  627. }
  628. driver.SetAttribute (savedAttribute);
  629. }
  630. private void DrawParentBorder (Rect frame)
  631. {
  632. var sumThickness = GetSumThickness ();
  633. var borderThickness = BorderThickness;
  634. var effect3DOffset = Effect3DOffset;
  635. var driver = Application.Driver;
  636. var savedAttribute = driver.GetAttribute ();
  637. driver.SetAttribute (new Attribute (BorderBrush));
  638. // Draw the upper BorderThickness
  639. for (int r = frame.Y;
  640. r < Math.Min (frame.Y + borderThickness.Top, frame.Bottom); r++) {
  641. for (int c = frame.X;
  642. c < Math.Min (frame.Right, driver.Cols); c++) {
  643. AddRuneAt (driver, c, r, ' ');
  644. }
  645. }
  646. // Draw the left BorderThickness
  647. for (int r = Math.Min (frame.Y + borderThickness.Top, frame.Bottom);
  648. r < Math.Min (frame.Bottom - borderThickness.Bottom, driver.Rows); r++) {
  649. for (int c = frame.X;
  650. c < Math.Min (frame.X + borderThickness.Left, frame.Right); c++) {
  651. AddRuneAt (driver, c, r, ' ');
  652. }
  653. }
  654. // Draw the right BorderThickness
  655. for (int r = Math.Min (frame.Y + borderThickness.Top, frame.Bottom);
  656. r < Math.Min (frame.Bottom - borderThickness.Bottom, driver.Rows); r++) {
  657. for (int c = Math.Max (frame.Right - borderThickness.Right, frame.X);
  658. c < Math.Min (frame.Right, driver.Cols); c++) {
  659. AddRuneAt (driver, c, r, ' ');
  660. }
  661. }
  662. // Draw the lower BorderThickness
  663. for (int r = Math.Max (frame.Bottom - borderThickness.Bottom, frame.Y);
  664. r < Math.Min (frame.Bottom, driver.Rows); r++) {
  665. for (int c = frame.X;
  666. c < Math.Min (frame.Right, driver.Cols); c++) {
  667. AddRuneAt (driver, c, r, ' ');
  668. }
  669. }
  670. driver.SetAttribute (new Attribute (Background));
  671. // Draw the upper Padding
  672. for (int r = frame.Y + borderThickness.Top;
  673. r < Math.Min (frame.Y + sumThickness.Top, frame.Bottom - borderThickness.Bottom); r++) {
  674. for (int c = frame.X + borderThickness.Left;
  675. c < Math.Min (frame.Right - borderThickness.Right, driver.Cols); c++) {
  676. AddRuneAt (driver, c, r, ' ');
  677. }
  678. }
  679. // Draw the left Padding
  680. for (int r = frame.Y + sumThickness.Top;
  681. r < Math.Min (frame.Bottom - sumThickness.Bottom, driver.Rows); r++) {
  682. for (int c = frame.X + borderThickness.Left;
  683. c < Math.Min (frame.X + sumThickness.Left, frame.Right - borderThickness.Right); c++) {
  684. AddRuneAt (driver, c, r, ' ');
  685. }
  686. }
  687. // Draw the right Padding
  688. for (int r = frame.Y + sumThickness.Top;
  689. r < Math.Min (frame.Bottom - sumThickness.Bottom, driver.Rows); r++) {
  690. for (int c = Math.Max (frame.Right - sumThickness.Right, frame.X + sumThickness.Left);
  691. c < Math.Max (frame.Right - borderThickness.Right, frame.X + sumThickness.Left); c++) {
  692. AddRuneAt (driver, c, r, ' ');
  693. }
  694. }
  695. // Draw the lower Padding
  696. for (int r = Math.Max (frame.Bottom - sumThickness.Bottom, frame.Y + borderThickness.Top);
  697. r < Math.Min (frame.Bottom - borderThickness.Bottom, driver.Rows); r++) {
  698. for (int c = frame.X + borderThickness.Left;
  699. c < Math.Min (frame.Right - borderThickness.Right, driver.Cols); c++) {
  700. AddRuneAt (driver, c, r, ' ');
  701. }
  702. }
  703. driver.SetAttribute (savedAttribute);
  704. // Draw the MarginFrame
  705. var rect = new Rect () {
  706. X = frame.X + sumThickness.Left,
  707. Y = frame.Y + sumThickness.Top,
  708. Width = Math.Max (frame.Width - sumThickness.Right - sumThickness.Left, 0),
  709. Height = Math.Max (frame.Height - sumThickness.Bottom - sumThickness.Top, 0)
  710. };
  711. if (rect.Width > 0 && rect.Height > 0) {
  712. driver.DrawWindowFrame (rect, 1, 1, 1, 1, BorderStyle != BorderStyle.None, fill: true, this);
  713. }
  714. if (Effect3D) {
  715. driver.SetAttribute (GetEffect3DBrush ());
  716. // Draw the upper Effect3D
  717. for (int r = Math.Max (frame.Y + effect3DOffset.Y, 0);
  718. r < frame.Y; r++) {
  719. for (int c = Math.Max (frame.X + effect3DOffset.X, 0);
  720. c < Math.Min (frame.Right + effect3DOffset.X, driver.Cols); c++) {
  721. AddRuneAt (driver, c, r, (Rune)driver.Contents [r, c, 0]);
  722. }
  723. }
  724. // Draw the left Effect3D
  725. for (int r = Math.Max (frame.Y + effect3DOffset.Y, 0);
  726. r < Math.Min (frame.Bottom + effect3DOffset.Y, driver.Rows); r++) {
  727. for (int c = Math.Max (frame.X + effect3DOffset.X, 0);
  728. c < frame.X; c++) {
  729. AddRuneAt (driver, c, r, (Rune)driver.Contents [r, c, 0]);
  730. }
  731. }
  732. // Draw the right Effect3D
  733. for (int r = Math.Max (frame.Y + effect3DOffset.Y, 0);
  734. r < Math.Min (frame.Bottom + effect3DOffset.Y, driver.Rows); r++) {
  735. for (int c = frame.Right;
  736. c < Math.Min (frame.Right + effect3DOffset.X, driver.Cols); c++) {
  737. AddRuneAt (driver, c, r, (Rune)driver.Contents [r, c, 0]);
  738. }
  739. }
  740. // Draw the lower Effect3D
  741. for (int r = frame.Bottom;
  742. r < Math.Min (frame.Bottom + effect3DOffset.Y, driver.Rows); r++) {
  743. for (int c = Math.Max (frame.X + effect3DOffset.X, 0);
  744. c < Math.Min (frame.Right + effect3DOffset.X, driver.Cols); c++) {
  745. AddRuneAt (driver, c, r, (Rune)driver.Contents [r, c, 0]);
  746. }
  747. }
  748. }
  749. driver.SetAttribute (savedAttribute);
  750. }
  751. private Attribute GetEffect3DBrush ()
  752. {
  753. return Effect3DBrush == null
  754. ? new Attribute (Color.Gray, Color.DarkGray)
  755. : (Attribute)Effect3DBrush;
  756. }
  757. private void AddRuneAt (ConsoleDriver driver, int col, int row, Rune ch)
  758. {
  759. driver.Move (col, row);
  760. driver.AddRune (ch);
  761. }
  762. /// <summary>
  763. /// Drawn the view text from a <see cref="View"/>.
  764. /// </summary>
  765. public void DrawTitle (View view, Rect rect)
  766. {
  767. var driver = Application.Driver;
  768. if (BorderStyle != BorderStyle.None) {
  769. driver.SetAttribute (view.GetNormalColor ());
  770. if (view.HasFocus) {
  771. driver.SetAttribute (view.ColorScheme.HotNormal);
  772. }
  773. var padding = GetSumThickness ();
  774. driver.DrawWindowTitle (rect, view.Text,
  775. padding.Left, padding.Top, padding.Right, padding.Bottom);
  776. }
  777. driver.SetAttribute (view.GetNormalColor ());
  778. }
  779. /// <summary>
  780. /// Invoke the <see cref="BorderChanged"/> event.
  781. /// </summary>
  782. public virtual void OnBorderChanged ()
  783. {
  784. BorderChanged?.Invoke (this);
  785. }
  786. }
  787. }