Border.cs 28 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915
  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. Frame = frame;
  154. if (border == null) {
  155. Border = new Border () {
  156. BorderStyle = BorderStyle.Single,
  157. BorderBrush = ColorScheme.Normal.Background
  158. };
  159. } else {
  160. Border = border;
  161. }
  162. }
  163. void AdjustContentView (Rect frame)
  164. {
  165. var borderLength = Border.DrawMarginFrame ? 1 : 0;
  166. var sumPadding = Border.GetSumThickness ();
  167. var wb = new Size ();
  168. if (frame == Rect.Empty) {
  169. wb.Width = borderLength + sumPadding.Right;
  170. wb.Height = borderLength + sumPadding.Bottom;
  171. if (Border.Child == null) {
  172. Border.Child = new ChildContentView (this) {
  173. X = borderLength + sumPadding.Left,
  174. Y = borderLength + sumPadding.Top,
  175. Width = Dim.Fill (wb.Width),
  176. Height = Dim.Fill (wb.Height)
  177. };
  178. } else {
  179. Border.Child.X = borderLength + sumPadding.Left;
  180. Border.Child.Y = borderLength + sumPadding.Top;
  181. Border.Child.Width = Dim.Fill (wb.Width);
  182. Border.Child.Height = Dim.Fill (wb.Height);
  183. }
  184. } else {
  185. wb.Width = (2 * borderLength) + sumPadding.Right + sumPadding.Left;
  186. wb.Height = (2 * borderLength) + sumPadding.Bottom + sumPadding.Top;
  187. var cFrame = new Rect (borderLength + sumPadding.Left, borderLength + sumPadding.Top, frame.Width - wb.Width, frame.Height - wb.Height);
  188. if (Border.Child == null) {
  189. Border.Child = new ChildContentView (cFrame, this);
  190. } else {
  191. Border.Child.Frame = cFrame;
  192. }
  193. }
  194. base.Add (Border.Child);
  195. Border.ChildContainer = this;
  196. }
  197. /// <inheritdoc/>
  198. public override void Add (View view)
  199. {
  200. Border.Child.Add (view);
  201. if (view.CanFocus) {
  202. CanFocus = true;
  203. }
  204. AddMenuStatusBar (view);
  205. }
  206. /// <inheritdoc/>
  207. public override void Remove (View view)
  208. {
  209. if (view == null) {
  210. return;
  211. }
  212. SetNeedsDisplay ();
  213. var touched = view.Frame;
  214. Border.Child.Remove (view);
  215. if (Border.Child.InternalSubviews.Count < 1) {
  216. CanFocus = false;
  217. }
  218. RemoveMenuStatusBar (view);
  219. }
  220. /// <inheritdoc/>
  221. public override void RemoveAll ()
  222. {
  223. Border.Child.RemoveAll ();
  224. }
  225. /// <inheritdoc/>
  226. public override void Redraw (Rect bounds)
  227. {
  228. if (!NeedDisplay.IsEmpty) {
  229. Driver.SetAttribute (GetNormalColor ());
  230. Border.DrawContent ();
  231. }
  232. var savedClip = Border.Child.ClipToBounds ();
  233. Border.Child.Redraw (Border.Child.Bounds);
  234. Driver.Clip = savedClip;
  235. ClearLayoutNeeded ();
  236. ClearNeedsDisplay ();
  237. if (Border.BorderStyle != BorderStyle.None) {
  238. Driver.SetAttribute (GetNormalColor ());
  239. Border.DrawTitle (this, this.Frame);
  240. }
  241. // Checks if there are any SuperView view which intersect with this window.
  242. if (SuperView != null) {
  243. SuperView.SetNeedsLayout ();
  244. SuperView.SetNeedsDisplay ();
  245. }
  246. }
  247. /// <inheritdoc/>
  248. public override void OnCanFocusChanged ()
  249. {
  250. if (Border.Child != null) {
  251. Border.Child.CanFocus = CanFocus;
  252. }
  253. base.OnCanFocusChanged ();
  254. }
  255. }
  256. private class ChildContentView : View {
  257. View instance;
  258. public ChildContentView (Rect frame, View instance) : base (frame)
  259. {
  260. this.instance = instance;
  261. }
  262. public ChildContentView (View instance)
  263. {
  264. this.instance = instance;
  265. }
  266. public override bool MouseEvent (MouseEvent mouseEvent)
  267. {
  268. return instance.MouseEvent (mouseEvent);
  269. }
  270. }
  271. /// <summary>
  272. /// Event to be invoked when any border property change.
  273. /// </summary>
  274. public event Action<Border> BorderChanged;
  275. private BorderStyle borderStyle;
  276. private bool drawMarginFrame;
  277. private Thickness borderThickness;
  278. private Thickness padding;
  279. private bool effect3D;
  280. private Point effect3DOffset = new Point (1, 1);
  281. /// <summary>
  282. /// Specifies the <see cref="Gui.BorderStyle"/> for a view.
  283. /// </summary>
  284. public BorderStyle BorderStyle {
  285. get => borderStyle;
  286. set {
  287. if (value != BorderStyle.None && !drawMarginFrame) {
  288. // Ensures drawn the border lines.
  289. drawMarginFrame = true;
  290. }
  291. borderStyle = value;
  292. OnBorderChanged ();
  293. }
  294. }
  295. /// <summary>
  296. /// Gets or sets if a margin frame is drawn around the <see cref="Child"/> regardless the <see cref="BorderStyle"/>
  297. /// </summary>
  298. public bool DrawMarginFrame {
  299. get => drawMarginFrame;
  300. set {
  301. if (borderStyle != BorderStyle.None
  302. && (!value || !drawMarginFrame)) {
  303. // Ensures drawn the border lines.
  304. drawMarginFrame = true;
  305. } else {
  306. drawMarginFrame = value;
  307. }
  308. OnBorderChanged ();
  309. }
  310. }
  311. /// <summary>
  312. /// Gets or sets the relative <see cref="Thickness"/> of a <see cref="Border"/>.
  313. /// </summary>
  314. public Thickness BorderThickness {
  315. get => borderThickness;
  316. set {
  317. borderThickness = value;
  318. OnBorderChanged ();
  319. }
  320. }
  321. /// <summary>
  322. /// Gets or sets the <see cref="Color"/> that draws the outer border color.
  323. /// </summary>
  324. public Color BorderBrush { get; set; }
  325. /// <summary>
  326. /// Gets or sets the <see cref="Color"/> that fills the area between the bounds of a <see cref="Border"/>.
  327. /// </summary>
  328. public Color Background { get; set; }
  329. /// <summary>
  330. /// Gets or sets a <see cref="Thickness"/> value that describes the amount of space between a
  331. /// <see cref="Border"/> and its child element.
  332. /// </summary>
  333. public Thickness Padding {
  334. get => padding;
  335. set {
  336. padding = value;
  337. OnBorderChanged ();
  338. }
  339. }
  340. /// <summary>
  341. /// Gets the rendered width of this element.
  342. /// </summary>
  343. public int ActualWidth {
  344. get {
  345. var driver = Application.Driver;
  346. if (Parent?.Border == null) {
  347. return Math.Min (Child?.Frame.Width + (2 * marginFrame) + Padding.Right
  348. + BorderThickness.Right + Padding.Left + BorderThickness.Left ?? 0, driver.Cols);
  349. }
  350. return Math.Min (Parent.Frame.Width, driver.Cols);
  351. }
  352. }
  353. /// <summary>
  354. /// Gets the rendered height of this element.
  355. /// </summary>
  356. public int ActualHeight {
  357. get {
  358. var driver = Application.Driver;
  359. if (Parent?.Border == null) {
  360. return Math.Min (Child?.Frame.Height + (2 * marginFrame) + Padding.Bottom
  361. + BorderThickness.Bottom + Padding.Top + BorderThickness.Top ?? 0, driver.Rows);
  362. }
  363. return Math.Min (Parent.Frame.Height, driver.Rows);
  364. }
  365. }
  366. /// <summary>
  367. /// Gets or sets the single child element of a <see cref="View"/>.
  368. /// </summary>
  369. public View Child { get; set; }
  370. /// <summary>
  371. /// Gets the parent <see cref="Child"/> parent if any.
  372. /// </summary>
  373. public View Parent { get => Child?.SuperView; }
  374. /// <summary>
  375. /// Gets or private sets by the <see cref="ToplevelContainer"/>
  376. /// </summary>
  377. public ToplevelContainer ChildContainer { get; private set; }
  378. /// <summary>
  379. /// Gets or sets the 3D effect around the <see cref="Border"/>.
  380. /// </summary>
  381. public bool Effect3D {
  382. get => effect3D;
  383. set {
  384. effect3D = value;
  385. OnBorderChanged ();
  386. }
  387. }
  388. /// <summary>
  389. /// Get or sets the offset start position for the <see cref="Effect3D"/>
  390. /// </summary>
  391. public Point Effect3DOffset {
  392. get => effect3DOffset;
  393. set {
  394. effect3DOffset = value;
  395. OnBorderChanged ();
  396. }
  397. }
  398. /// <summary>
  399. /// Gets or sets the color for the <see cref="Border"/>
  400. /// </summary>
  401. public Attribute? Effect3DBrush { get; set; }
  402. /// <summary>
  403. /// Calculate the sum of the <see cref="Padding"/> and the <see cref="BorderThickness"/>
  404. /// </summary>
  405. /// <returns>The total of the <see cref="Border"/> <see cref="Thickness"/></returns>
  406. public Thickness GetSumThickness ()
  407. {
  408. return new Thickness () {
  409. Left = Padding.Left + BorderThickness.Left,
  410. Top = Padding.Top + BorderThickness.Top,
  411. Right = Padding.Right + BorderThickness.Right,
  412. Bottom = Padding.Bottom + BorderThickness.Bottom
  413. };
  414. }
  415. /// <summary>
  416. /// Drawn the <see cref="BorderThickness"/> more the <see cref="Padding"/>
  417. /// more the <see cref="Border.BorderStyle"/> and the <see cref="Effect3D"/>.
  418. /// </summary>
  419. /// <param name="view">The view to draw.</param>
  420. /// <param name="fill">If it will clear or not the content area.</param>
  421. public void DrawContent (View view = null, bool fill = true)
  422. {
  423. if (Child == null) {
  424. Child = view;
  425. }
  426. if (Parent?.Border != null) {
  427. DrawParentBorder (Parent.ViewToScreen (Parent.Bounds), fill);
  428. } else {
  429. DrawChildBorder (Child.ViewToScreen (Child.Bounds), fill);
  430. }
  431. }
  432. /// <summary>
  433. /// Same as <see cref="DrawContent"/> but drawing full frames for all borders.
  434. /// </summary>
  435. public void DrawFullContent ()
  436. {
  437. var borderThickness = BorderThickness;
  438. var padding = Padding;
  439. var marginFrame = DrawMarginFrame ? 1 : 0;
  440. var driver = Application.Driver;
  441. Rect scrRect;
  442. if (Parent?.Border != null) {
  443. scrRect = Parent.ViewToScreen (Parent.Bounds);
  444. } else {
  445. scrRect = Child.ViewToScreen (Child.Bounds);
  446. }
  447. Rect borderRect;
  448. if (Parent?.Border != null) {
  449. borderRect = scrRect;
  450. } else {
  451. borderRect = new Rect () {
  452. X = scrRect.X - marginFrame - padding.Left - borderThickness.Left,
  453. Y = scrRect.Y - marginFrame - padding.Top - borderThickness.Top,
  454. Width = ActualWidth,
  455. Height = ActualHeight
  456. };
  457. }
  458. var savedAttribute = driver.GetAttribute ();
  459. // Draw 3D effects
  460. if (Effect3D) {
  461. driver.SetAttribute (GetEffect3DBrush ());
  462. var effectBorder = new Rect () {
  463. X = borderRect.X + Effect3DOffset.X,
  464. Y = borderRect.Y + Effect3DOffset.Y,
  465. Width = ActualWidth,
  466. Height = ActualHeight
  467. };
  468. //Child.Clear (effectBorder);
  469. for (int r = effectBorder.Y; r < Math.Min (effectBorder.Bottom, driver.Rows); r++) {
  470. for (int c = effectBorder.X; c < Math.Min (effectBorder.Right, driver.Cols); c++) {
  471. AddRuneAt (driver, c, r, (Rune)driver.Contents [r, c, 0]);
  472. }
  473. }
  474. }
  475. // Draw border thickness
  476. driver.SetAttribute (new Attribute (BorderBrush));
  477. Child.Clear (borderRect);
  478. borderRect = new Rect () {
  479. X = borderRect.X + borderThickness.Left,
  480. Y = borderRect.Y + borderThickness.Top,
  481. Width = Math.Max (borderRect.Width - borderThickness.Right - borderThickness.Left, 0),
  482. Height = Math.Max (borderRect.Height - borderThickness.Bottom - borderThickness.Top, 0)
  483. };
  484. if (borderRect != scrRect) {
  485. // Draw padding
  486. driver.SetAttribute (new Attribute (Background));
  487. Child.Clear (borderRect);
  488. }
  489. driver.SetAttribute (savedAttribute);
  490. // Draw margin frame
  491. if (Parent?.Border != null) {
  492. var sumPadding = GetSumThickness ();
  493. borderRect = new Rect () {
  494. X = scrRect.X + sumPadding.Left,
  495. Y = scrRect.Y + sumPadding.Top,
  496. Width = Math.Max (scrRect.Width - sumPadding.Right - sumPadding.Left, 0),
  497. Height = Math.Max (scrRect.Height - sumPadding.Bottom - sumPadding.Top, 0)
  498. };
  499. } else {
  500. borderRect = new Rect () {
  501. X = borderRect.X + padding.Left,
  502. Y = borderRect.Y + padding.Top,
  503. Width = Math.Max (borderRect.Width - padding.Right - padding.Left, 0),
  504. Height = Math.Max (borderRect.Height - padding.Bottom - padding.Top, 0)
  505. };
  506. }
  507. if (borderRect.Width > 0 && borderRect.Height > 0) {
  508. driver.DrawWindowFrame (borderRect, 1, 1, 1, 1, BorderStyle != BorderStyle.None, fill: true, this);
  509. }
  510. }
  511. private void DrawChildBorder (Rect frame, bool fill = true)
  512. {
  513. var drawMarginFrame = DrawMarginFrame ? 1 : 0;
  514. var sumThickness = GetSumThickness ();
  515. var padding = Padding;
  516. var effect3DOffset = Effect3DOffset;
  517. var driver = Application.Driver;
  518. var savedAttribute = driver.GetAttribute ();
  519. driver.SetAttribute (new Attribute (BorderBrush));
  520. // Draw the upper BorderThickness
  521. for (int r = frame.Y - drawMarginFrame - sumThickness.Top;
  522. r < frame.Y - drawMarginFrame - padding.Top; r++) {
  523. for (int c = frame.X - drawMarginFrame - sumThickness.Left;
  524. c < Math.Min (frame.Right + drawMarginFrame + sumThickness.Right, driver.Cols); c++) {
  525. AddRuneAt (driver, c, r, ' ');
  526. }
  527. }
  528. // Draw the left BorderThickness
  529. for (int r = frame.Y - drawMarginFrame - padding.Top;
  530. r < Math.Min (frame.Bottom + drawMarginFrame + padding.Bottom, driver.Rows); r++) {
  531. for (int c = frame.X - drawMarginFrame - sumThickness.Left;
  532. c < frame.X - drawMarginFrame - padding.Left; c++) {
  533. AddRuneAt (driver, c, r, ' ');
  534. }
  535. }
  536. // Draw the right BorderThickness
  537. for (int r = frame.Y - drawMarginFrame - padding.Top;
  538. r < Math.Min (frame.Bottom + drawMarginFrame + padding.Bottom, driver.Rows); r++) {
  539. for (int c = frame.Right + drawMarginFrame + padding.Right;
  540. c < Math.Min (frame.Right + drawMarginFrame + sumThickness.Right, driver.Cols); c++) {
  541. AddRuneAt (driver, c, r, ' ');
  542. }
  543. }
  544. // Draw the lower BorderThickness
  545. for (int r = frame.Bottom + drawMarginFrame + padding.Bottom;
  546. r < Math.Min (frame.Bottom + drawMarginFrame + sumThickness.Bottom, driver.Rows); r++) {
  547. for (int c = frame.X - drawMarginFrame - sumThickness.Left;
  548. c < Math.Min (frame.Right + drawMarginFrame + sumThickness.Right, driver.Cols); c++) {
  549. AddRuneAt (driver, c, r, ' ');
  550. }
  551. }
  552. driver.SetAttribute (new Attribute (Background));
  553. // Draw the upper Padding
  554. for (int r = frame.Y - drawMarginFrame - padding.Top;
  555. r < frame.Y - drawMarginFrame; r++) {
  556. for (int c = frame.X - drawMarginFrame - padding.Left;
  557. c < Math.Min (frame.Right + drawMarginFrame + padding.Right, driver.Cols); c++) {
  558. AddRuneAt (driver, c, r, ' ');
  559. }
  560. }
  561. // Draw the left Padding
  562. for (int r = frame.Y - drawMarginFrame;
  563. r < Math.Min (frame.Bottom + drawMarginFrame, driver.Rows); r++) {
  564. for (int c = frame.X - drawMarginFrame - padding.Left;
  565. c < frame.X - drawMarginFrame; c++) {
  566. AddRuneAt (driver, c, r, ' ');
  567. }
  568. }
  569. // Draw the right Padding
  570. for (int r = frame.Y - drawMarginFrame;
  571. r < Math.Min (frame.Bottom + drawMarginFrame, driver.Rows); r++) {
  572. for (int c = frame.Right + drawMarginFrame;
  573. c < Math.Min (frame.Right + drawMarginFrame + padding.Right, driver.Cols); c++) {
  574. AddRuneAt (driver, c, r, ' ');
  575. }
  576. }
  577. // Draw the lower Padding
  578. for (int r = frame.Bottom + drawMarginFrame;
  579. r < Math.Min (frame.Bottom + drawMarginFrame + padding.Bottom, driver.Rows); r++) {
  580. for (int c = frame.X - drawMarginFrame - padding.Left;
  581. c < Math.Min (frame.Right + drawMarginFrame + padding.Right, driver.Cols); c++) {
  582. AddRuneAt (driver, c, r, ' ');
  583. }
  584. }
  585. driver.SetAttribute (savedAttribute);
  586. // Draw the MarginFrame
  587. var rect = new Rect () {
  588. X = frame.X - drawMarginFrame,
  589. Y = frame.Y - drawMarginFrame,
  590. Width = frame.Width + (2 * drawMarginFrame),
  591. Height = frame.Height + (2 * drawMarginFrame)
  592. };
  593. if (rect.Width > 0 && rect.Height > 0) {
  594. driver.DrawWindowFrame (rect, 1, 1, 1, 1, BorderStyle != BorderStyle.None, fill, this);
  595. }
  596. if (Effect3D) {
  597. driver.SetAttribute (GetEffect3DBrush ());
  598. // Draw the upper Effect3D
  599. for (int r = frame.Y - drawMarginFrame - sumThickness.Top + effect3DOffset.Y;
  600. r >= 0 && r < frame.Y - drawMarginFrame - sumThickness.Top; r++) {
  601. for (int c = frame.X - drawMarginFrame - sumThickness.Left + effect3DOffset.X;
  602. c >= 0 && c < Math.Min (frame.Right + drawMarginFrame + sumThickness.Right + effect3DOffset.X, driver.Cols); c++) {
  603. AddRuneAt (driver, c, r, (Rune)driver.Contents [r, c, 0]);
  604. }
  605. }
  606. // Draw the left Effect3D
  607. for (int r = frame.Y - drawMarginFrame - sumThickness.Top + effect3DOffset.Y;
  608. r >= 0 && r < Math.Min (frame.Bottom + drawMarginFrame + sumThickness.Bottom + effect3DOffset.Y, driver.Rows); r++) {
  609. for (int c = frame.X - drawMarginFrame - sumThickness.Left + effect3DOffset.X;
  610. c >= 0 && c < frame.X - drawMarginFrame - sumThickness.Left; c++) {
  611. AddRuneAt (driver, c, r, (Rune)driver.Contents [r, c, 0]);
  612. }
  613. }
  614. // Draw the right Effect3D
  615. for (int r = frame.Y - drawMarginFrame - sumThickness.Top + effect3DOffset.Y;
  616. r >= 0 && r < Math.Min (frame.Bottom + drawMarginFrame + sumThickness.Bottom + effect3DOffset.Y, driver.Rows); r++) {
  617. for (int c = frame.Right + drawMarginFrame + sumThickness.Right;
  618. c >= 0 && c < Math.Min (frame.Right + drawMarginFrame + sumThickness.Right + effect3DOffset.X, driver.Cols); c++) {
  619. AddRuneAt (driver, c, r, (Rune)driver.Contents [r, c, 0]);
  620. }
  621. }
  622. // Draw the lower Effect3D
  623. for (int r = frame.Bottom + drawMarginFrame + sumThickness.Bottom;
  624. r >= 0 && r < Math.Min (frame.Bottom + drawMarginFrame + sumThickness.Bottom + effect3DOffset.Y, driver.Rows); r++) {
  625. for (int c = frame.X - drawMarginFrame - sumThickness.Left + effect3DOffset.X;
  626. c >= 0 && c < Math.Min (frame.Right + drawMarginFrame + sumThickness.Right + effect3DOffset.X, driver.Cols); c++) {
  627. AddRuneAt (driver, c, r, (Rune)driver.Contents [r, c, 0]);
  628. }
  629. }
  630. }
  631. driver.SetAttribute (savedAttribute);
  632. }
  633. private void DrawParentBorder (Rect frame, bool fill = true)
  634. {
  635. var sumThickness = GetSumThickness ();
  636. var borderThickness = BorderThickness;
  637. var effect3DOffset = Effect3DOffset;
  638. var driver = Application.Driver;
  639. var savedAttribute = driver.GetAttribute ();
  640. driver.SetAttribute (new Attribute (BorderBrush));
  641. // Draw the upper BorderThickness
  642. for (int r = frame.Y;
  643. r < Math.Min (frame.Y + borderThickness.Top, frame.Bottom); r++) {
  644. for (int c = frame.X;
  645. c < Math.Min (frame.Right, driver.Cols); c++) {
  646. AddRuneAt (driver, c, r, ' ');
  647. }
  648. }
  649. // Draw the left BorderThickness
  650. for (int r = Math.Min (frame.Y + borderThickness.Top, frame.Bottom);
  651. r < Math.Min (frame.Bottom - borderThickness.Bottom, driver.Rows); r++) {
  652. for (int c = frame.X;
  653. c < Math.Min (frame.X + borderThickness.Left, frame.Right); c++) {
  654. AddRuneAt (driver, c, r, ' ');
  655. }
  656. }
  657. // Draw the right BorderThickness
  658. for (int r = Math.Min (frame.Y + borderThickness.Top, frame.Bottom);
  659. r < Math.Min (frame.Bottom - borderThickness.Bottom, driver.Rows); r++) {
  660. for (int c = Math.Max (frame.Right - borderThickness.Right, frame.X);
  661. c < Math.Min (frame.Right, driver.Cols); c++) {
  662. AddRuneAt (driver, c, r, ' ');
  663. }
  664. }
  665. // Draw the lower BorderThickness
  666. for (int r = Math.Max (frame.Bottom - borderThickness.Bottom, frame.Y);
  667. r < Math.Min (frame.Bottom, driver.Rows); r++) {
  668. for (int c = frame.X;
  669. c < Math.Min (frame.Right, driver.Cols); c++) {
  670. AddRuneAt (driver, c, r, ' ');
  671. }
  672. }
  673. driver.SetAttribute (new Attribute (Background));
  674. // Draw the upper Padding
  675. for (int r = frame.Y + borderThickness.Top;
  676. r < Math.Min (frame.Y + sumThickness.Top, frame.Bottom - borderThickness.Bottom); r++) {
  677. for (int c = frame.X + borderThickness.Left;
  678. c < Math.Min (frame.Right - borderThickness.Right, driver.Cols); c++) {
  679. AddRuneAt (driver, c, r, ' ');
  680. }
  681. }
  682. // Draw the left Padding
  683. for (int r = frame.Y + sumThickness.Top;
  684. r < Math.Min (frame.Bottom - sumThickness.Bottom, driver.Rows); r++) {
  685. for (int c = frame.X + borderThickness.Left;
  686. c < Math.Min (frame.X + sumThickness.Left, frame.Right - borderThickness.Right); c++) {
  687. AddRuneAt (driver, c, r, ' ');
  688. }
  689. }
  690. // Draw the right Padding
  691. for (int r = frame.Y + sumThickness.Top;
  692. r < Math.Min (frame.Bottom - sumThickness.Bottom, driver.Rows); r++) {
  693. for (int c = Math.Max (frame.Right - sumThickness.Right, frame.X + sumThickness.Left);
  694. c < Math.Max (frame.Right - borderThickness.Right, frame.X + sumThickness.Left); c++) {
  695. AddRuneAt (driver, c, r, ' ');
  696. }
  697. }
  698. // Draw the lower Padding
  699. for (int r = Math.Max (frame.Bottom - sumThickness.Bottom, frame.Y + borderThickness.Top);
  700. r < Math.Min (frame.Bottom - borderThickness.Bottom, driver.Rows); r++) {
  701. for (int c = frame.X + borderThickness.Left;
  702. c < Math.Min (frame.Right - borderThickness.Right, driver.Cols); c++) {
  703. AddRuneAt (driver, c, r, ' ');
  704. }
  705. }
  706. driver.SetAttribute (savedAttribute);
  707. // Draw the MarginFrame
  708. var rect = new Rect () {
  709. X = frame.X + sumThickness.Left,
  710. Y = frame.Y + sumThickness.Top,
  711. Width = Math.Max (frame.Width - sumThickness.Right - sumThickness.Left, 0),
  712. Height = Math.Max (frame.Height - sumThickness.Bottom - sumThickness.Top, 0)
  713. };
  714. if (rect.Width > 0 && rect.Height > 0) {
  715. driver.DrawWindowFrame (rect, 1, 1, 1, 1, BorderStyle != BorderStyle.None, fill, this);
  716. }
  717. if (Effect3D) {
  718. driver.SetAttribute (GetEffect3DBrush ());
  719. // Draw the upper Effect3D
  720. for (int r = Math.Max (frame.Y + effect3DOffset.Y, 0);
  721. r < frame.Y; r++) {
  722. for (int c = Math.Max (frame.X + effect3DOffset.X, 0);
  723. c < Math.Min (frame.Right + effect3DOffset.X, driver.Cols); c++) {
  724. AddRuneAt (driver, c, r, (Rune)driver.Contents [r, c, 0]);
  725. }
  726. }
  727. // Draw the left Effect3D
  728. for (int r = Math.Max (frame.Y + effect3DOffset.Y, 0);
  729. r < Math.Min (frame.Bottom + effect3DOffset.Y, driver.Rows); r++) {
  730. for (int c = Math.Max (frame.X + effect3DOffset.X, 0);
  731. c < frame.X; c++) {
  732. AddRuneAt (driver, c, r, (Rune)driver.Contents [r, c, 0]);
  733. }
  734. }
  735. // Draw the right Effect3D
  736. for (int r = Math.Max (frame.Y + effect3DOffset.Y, 0);
  737. r < Math.Min (frame.Bottom + effect3DOffset.Y, driver.Rows); r++) {
  738. for (int c = frame.Right;
  739. c < Math.Min (frame.Right + effect3DOffset.X, driver.Cols); c++) {
  740. AddRuneAt (driver, c, r, (Rune)driver.Contents [r, c, 0]);
  741. }
  742. }
  743. // Draw the lower Effect3D
  744. for (int r = frame.Bottom;
  745. r < Math.Min (frame.Bottom + effect3DOffset.Y, driver.Rows); r++) {
  746. for (int c = Math.Max (frame.X + effect3DOffset.X, 0);
  747. c < Math.Min (frame.Right + effect3DOffset.X, driver.Cols); c++) {
  748. AddRuneAt (driver, c, r, (Rune)driver.Contents [r, c, 0]);
  749. }
  750. }
  751. }
  752. driver.SetAttribute (savedAttribute);
  753. }
  754. private Attribute GetEffect3DBrush ()
  755. {
  756. return Effect3DBrush == null
  757. ? new Attribute (Color.Gray, Color.DarkGray)
  758. : (Attribute)Effect3DBrush;
  759. }
  760. private void AddRuneAt (ConsoleDriver driver, int col, int row, Rune ch)
  761. {
  762. if (col < driver.Cols && row < driver.Rows && col > 0 && driver.Contents [row, col, 2] == 0
  763. && Rune.ColumnWidth ((char)driver.Contents [row, col - 1, 0]) > 1) {
  764. driver.Contents [row, col, 1] = driver.GetAttribute ();
  765. return;
  766. }
  767. driver.Move (col, row);
  768. driver.AddRune (ch);
  769. }
  770. /// <summary>
  771. /// Drawn the view text from a <see cref="View"/>.
  772. /// </summary>
  773. public void DrawTitle (View view, Rect rect)
  774. {
  775. var driver = Application.Driver;
  776. if (BorderStyle != BorderStyle.None) {
  777. driver.SetAttribute (view.GetNormalColor ());
  778. if (view.HasFocus) {
  779. driver.SetAttribute (view.ColorScheme.HotNormal);
  780. }
  781. var padding = GetSumThickness ();
  782. driver.DrawWindowTitle (rect, view.Text,
  783. padding.Left, padding.Top, padding.Right, padding.Bottom);
  784. }
  785. driver.SetAttribute (view.GetNormalColor ());
  786. }
  787. /// <summary>
  788. /// Invoke the <see cref="BorderChanged"/> event.
  789. /// </summary>
  790. public virtual void OnBorderChanged ()
  791. {
  792. BorderChanged?.Invoke (this);
  793. }
  794. }
  795. }