Border.cs 28 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914
  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. /// <param name="view">The view to draw.</param>
  419. /// <param name="fill">If it will clear or not the content area.</param>
  420. public void DrawContent (View view = null, bool fill = true)
  421. {
  422. if (Child == null) {
  423. Child = view;
  424. }
  425. if (Parent?.Border != null) {
  426. DrawParentBorder (Parent.ViewToScreen (Parent.Bounds), fill);
  427. } else {
  428. DrawChildBorder (Child.ViewToScreen (Child.Bounds), fill);
  429. }
  430. }
  431. /// <summary>
  432. /// Same as <see cref="DrawContent"/> but drawing full frames for all borders.
  433. /// </summary>
  434. public void DrawFullContent ()
  435. {
  436. var borderThickness = BorderThickness;
  437. var padding = Padding;
  438. var marginFrame = DrawMarginFrame ? 1 : 0;
  439. var driver = Application.Driver;
  440. Rect scrRect;
  441. if (Parent?.Border != null) {
  442. scrRect = Parent.ViewToScreen (Parent.Bounds);
  443. } else {
  444. scrRect = Child.ViewToScreen (Child.Bounds);
  445. }
  446. Rect borderRect;
  447. if (Parent?.Border != null) {
  448. borderRect = scrRect;
  449. } else {
  450. borderRect = new Rect () {
  451. X = scrRect.X - marginFrame - padding.Left - borderThickness.Left,
  452. Y = scrRect.Y - marginFrame - padding.Top - borderThickness.Top,
  453. Width = ActualWidth,
  454. Height = ActualHeight
  455. };
  456. }
  457. var savedAttribute = driver.GetAttribute ();
  458. // Draw 3D effects
  459. if (Effect3D) {
  460. driver.SetAttribute (GetEffect3DBrush ());
  461. var effectBorder = new Rect () {
  462. X = borderRect.X + Effect3DOffset.X,
  463. Y = borderRect.Y + Effect3DOffset.Y,
  464. Width = ActualWidth,
  465. Height = ActualHeight
  466. };
  467. //Child.Clear (effectBorder);
  468. for (int r = effectBorder.Y; r < Math.Min (effectBorder.Bottom, driver.Rows); r++) {
  469. for (int c = effectBorder.X; c < Math.Min (effectBorder.Right, driver.Cols); c++) {
  470. AddRuneAt (driver, c, r, (Rune)driver.Contents [r, c, 0]);
  471. }
  472. }
  473. }
  474. // Draw border thickness
  475. driver.SetAttribute (new Attribute (BorderBrush));
  476. Child.Clear (borderRect);
  477. borderRect = new Rect () {
  478. X = borderRect.X + borderThickness.Left,
  479. Y = borderRect.Y + borderThickness.Top,
  480. Width = Math.Max (borderRect.Width - borderThickness.Right - borderThickness.Left, 0),
  481. Height = Math.Max (borderRect.Height - borderThickness.Bottom - borderThickness.Top, 0)
  482. };
  483. if (borderRect != scrRect) {
  484. // Draw padding
  485. driver.SetAttribute (new Attribute (Background));
  486. Child.Clear (borderRect);
  487. }
  488. driver.SetAttribute (savedAttribute);
  489. // Draw margin frame
  490. if (Parent?.Border != null) {
  491. var sumPadding = GetSumThickness ();
  492. borderRect = new Rect () {
  493. X = scrRect.X + sumPadding.Left,
  494. Y = scrRect.Y + sumPadding.Top,
  495. Width = Math.Max (scrRect.Width - sumPadding.Right - sumPadding.Left, 0),
  496. Height = Math.Max (scrRect.Height - sumPadding.Bottom - sumPadding.Top, 0)
  497. };
  498. } else {
  499. borderRect = new Rect () {
  500. X = borderRect.X + padding.Left,
  501. Y = borderRect.Y + padding.Top,
  502. Width = Math.Max (borderRect.Width - padding.Right - padding.Left, 0),
  503. Height = Math.Max (borderRect.Height - padding.Bottom - padding.Top, 0)
  504. };
  505. }
  506. if (borderRect.Width > 0 && borderRect.Height > 0) {
  507. driver.DrawWindowFrame (borderRect, 1, 1, 1, 1, BorderStyle != BorderStyle.None, fill: true, this);
  508. }
  509. }
  510. private void DrawChildBorder (Rect frame, bool fill = true)
  511. {
  512. var drawMarginFrame = DrawMarginFrame ? 1 : 0;
  513. var sumThickness = GetSumThickness ();
  514. var padding = Padding;
  515. var effect3DOffset = Effect3DOffset;
  516. var driver = Application.Driver;
  517. var savedAttribute = driver.GetAttribute ();
  518. driver.SetAttribute (new Attribute (BorderBrush));
  519. // Draw the upper BorderThickness
  520. for (int r = frame.Y - drawMarginFrame - sumThickness.Top;
  521. r < frame.Y - drawMarginFrame - padding.Top; r++) {
  522. for (int c = frame.X - drawMarginFrame - sumThickness.Left;
  523. c < Math.Min (frame.Right + drawMarginFrame + sumThickness.Right, driver.Cols); c++) {
  524. AddRuneAt (driver, c, r, ' ');
  525. }
  526. }
  527. // Draw the left BorderThickness
  528. for (int r = frame.Y - drawMarginFrame - padding.Top;
  529. r < Math.Min (frame.Bottom + drawMarginFrame + padding.Bottom, driver.Rows); r++) {
  530. for (int c = frame.X - drawMarginFrame - sumThickness.Left;
  531. c < frame.X - drawMarginFrame - padding.Left; c++) {
  532. AddRuneAt (driver, c, r, ' ');
  533. }
  534. }
  535. // Draw the right BorderThickness
  536. for (int r = frame.Y - drawMarginFrame - padding.Top;
  537. r < Math.Min (frame.Bottom + drawMarginFrame + padding.Bottom, driver.Rows); r++) {
  538. for (int c = frame.Right + drawMarginFrame + padding.Right;
  539. c < Math.Min (frame.Right + drawMarginFrame + sumThickness.Right, driver.Cols); c++) {
  540. AddRuneAt (driver, c, r, ' ');
  541. }
  542. }
  543. // Draw the lower BorderThickness
  544. for (int r = frame.Bottom + drawMarginFrame + padding.Bottom;
  545. r < Math.Min (frame.Bottom + drawMarginFrame + sumThickness.Bottom, driver.Rows); r++) {
  546. for (int c = frame.X - drawMarginFrame - sumThickness.Left;
  547. c < Math.Min (frame.Right + drawMarginFrame + sumThickness.Right, driver.Cols); c++) {
  548. AddRuneAt (driver, c, r, ' ');
  549. }
  550. }
  551. driver.SetAttribute (new Attribute (Background));
  552. // Draw the upper Padding
  553. for (int r = frame.Y - drawMarginFrame - padding.Top;
  554. r < frame.Y - drawMarginFrame; r++) {
  555. for (int c = frame.X - drawMarginFrame - padding.Left;
  556. c < Math.Min (frame.Right + drawMarginFrame + padding.Right, driver.Cols); c++) {
  557. AddRuneAt (driver, c, r, ' ');
  558. }
  559. }
  560. // Draw the left Padding
  561. for (int r = frame.Y - drawMarginFrame;
  562. r < Math.Min (frame.Bottom + drawMarginFrame, driver.Rows); r++) {
  563. for (int c = frame.X - drawMarginFrame - padding.Left;
  564. c < frame.X - drawMarginFrame; c++) {
  565. AddRuneAt (driver, c, r, ' ');
  566. }
  567. }
  568. // Draw the right Padding
  569. for (int r = frame.Y - drawMarginFrame;
  570. r < Math.Min (frame.Bottom + drawMarginFrame, driver.Rows); r++) {
  571. for (int c = frame.Right + drawMarginFrame;
  572. c < Math.Min (frame.Right + drawMarginFrame + padding.Right, driver.Cols); c++) {
  573. AddRuneAt (driver, c, r, ' ');
  574. }
  575. }
  576. // Draw the lower Padding
  577. for (int r = frame.Bottom + drawMarginFrame;
  578. r < Math.Min (frame.Bottom + drawMarginFrame + padding.Bottom, driver.Rows); r++) {
  579. for (int c = frame.X - drawMarginFrame - padding.Left;
  580. c < Math.Min (frame.Right + drawMarginFrame + padding.Right, driver.Cols); c++) {
  581. AddRuneAt (driver, c, r, ' ');
  582. }
  583. }
  584. driver.SetAttribute (savedAttribute);
  585. // Draw the MarginFrame
  586. var rect = new Rect () {
  587. X = frame.X - drawMarginFrame,
  588. Y = frame.Y - drawMarginFrame,
  589. Width = frame.Width + (2 * drawMarginFrame),
  590. Height = frame.Height + (2 * drawMarginFrame)
  591. };
  592. if (rect.Width > 0 && rect.Height > 0) {
  593. driver.DrawWindowFrame (rect, 1, 1, 1, 1, BorderStyle != BorderStyle.None, fill, this);
  594. }
  595. if (Effect3D) {
  596. driver.SetAttribute (GetEffect3DBrush ());
  597. // Draw the upper Effect3D
  598. for (int r = frame.Y - drawMarginFrame - sumThickness.Top + effect3DOffset.Y;
  599. r >= 0 && r < frame.Y - drawMarginFrame - sumThickness.Top; r++) {
  600. for (int c = frame.X - drawMarginFrame - sumThickness.Left + effect3DOffset.X;
  601. c >= 0 && c < Math.Min (frame.Right + drawMarginFrame + sumThickness.Right + effect3DOffset.X, driver.Cols); c++) {
  602. AddRuneAt (driver, c, r, (Rune)driver.Contents [r, c, 0]);
  603. }
  604. }
  605. // Draw the left Effect3D
  606. for (int r = frame.Y - drawMarginFrame - sumThickness.Top + effect3DOffset.Y;
  607. r >= 0 && r < Math.Min (frame.Bottom + drawMarginFrame + sumThickness.Bottom + effect3DOffset.Y, driver.Rows); r++) {
  608. for (int c = frame.X - drawMarginFrame - sumThickness.Left + effect3DOffset.X;
  609. c >= 0 && c < frame.X - drawMarginFrame - sumThickness.Left; c++) {
  610. AddRuneAt (driver, c, r, (Rune)driver.Contents [r, c, 0]);
  611. }
  612. }
  613. // Draw the right Effect3D
  614. for (int r = frame.Y - drawMarginFrame - sumThickness.Top + effect3DOffset.Y;
  615. r >= 0 && r < Math.Min (frame.Bottom + drawMarginFrame + sumThickness.Bottom + effect3DOffset.Y, driver.Rows); r++) {
  616. for (int c = frame.Right + drawMarginFrame + sumThickness.Right;
  617. c >= 0 && c < Math.Min (frame.Right + drawMarginFrame + sumThickness.Right + effect3DOffset.X, driver.Cols); c++) {
  618. AddRuneAt (driver, c, r, (Rune)driver.Contents [r, c, 0]);
  619. }
  620. }
  621. // Draw the lower Effect3D
  622. for (int r = frame.Bottom + drawMarginFrame + sumThickness.Bottom;
  623. r >= 0 && r < Math.Min (frame.Bottom + drawMarginFrame + sumThickness.Bottom + effect3DOffset.Y, driver.Rows); r++) {
  624. for (int c = frame.X - drawMarginFrame - sumThickness.Left + effect3DOffset.X;
  625. c >= 0 && c < Math.Min (frame.Right + drawMarginFrame + sumThickness.Right + effect3DOffset.X, driver.Cols); c++) {
  626. AddRuneAt (driver, c, r, (Rune)driver.Contents [r, c, 0]);
  627. }
  628. }
  629. }
  630. driver.SetAttribute (savedAttribute);
  631. }
  632. private void DrawParentBorder (Rect frame, bool fill = true)
  633. {
  634. var sumThickness = GetSumThickness ();
  635. var borderThickness = BorderThickness;
  636. var effect3DOffset = Effect3DOffset;
  637. var driver = Application.Driver;
  638. var savedAttribute = driver.GetAttribute ();
  639. driver.SetAttribute (new Attribute (BorderBrush));
  640. // Draw the upper BorderThickness
  641. for (int r = frame.Y;
  642. r < Math.Min (frame.Y + borderThickness.Top, frame.Bottom); r++) {
  643. for (int c = frame.X;
  644. c < Math.Min (frame.Right, driver.Cols); c++) {
  645. AddRuneAt (driver, c, r, ' ');
  646. }
  647. }
  648. // Draw the left BorderThickness
  649. for (int r = Math.Min (frame.Y + borderThickness.Top, frame.Bottom);
  650. r < Math.Min (frame.Bottom - borderThickness.Bottom, driver.Rows); r++) {
  651. for (int c = frame.X;
  652. c < Math.Min (frame.X + borderThickness.Left, frame.Right); c++) {
  653. AddRuneAt (driver, c, r, ' ');
  654. }
  655. }
  656. // Draw the right BorderThickness
  657. for (int r = Math.Min (frame.Y + borderThickness.Top, frame.Bottom);
  658. r < Math.Min (frame.Bottom - borderThickness.Bottom, driver.Rows); r++) {
  659. for (int c = Math.Max (frame.Right - borderThickness.Right, frame.X);
  660. c < Math.Min (frame.Right, driver.Cols); c++) {
  661. AddRuneAt (driver, c, r, ' ');
  662. }
  663. }
  664. // Draw the lower BorderThickness
  665. for (int r = Math.Max (frame.Bottom - borderThickness.Bottom, frame.Y);
  666. r < Math.Min (frame.Bottom, driver.Rows); r++) {
  667. for (int c = frame.X;
  668. c < Math.Min (frame.Right, driver.Cols); c++) {
  669. AddRuneAt (driver, c, r, ' ');
  670. }
  671. }
  672. driver.SetAttribute (new Attribute (Background));
  673. // Draw the upper Padding
  674. for (int r = frame.Y + borderThickness.Top;
  675. r < Math.Min (frame.Y + sumThickness.Top, frame.Bottom - borderThickness.Bottom); r++) {
  676. for (int c = frame.X + borderThickness.Left;
  677. c < Math.Min (frame.Right - borderThickness.Right, driver.Cols); c++) {
  678. AddRuneAt (driver, c, r, ' ');
  679. }
  680. }
  681. // Draw the left Padding
  682. for (int r = frame.Y + sumThickness.Top;
  683. r < Math.Min (frame.Bottom - sumThickness.Bottom, driver.Rows); r++) {
  684. for (int c = frame.X + borderThickness.Left;
  685. c < Math.Min (frame.X + sumThickness.Left, frame.Right - borderThickness.Right); c++) {
  686. AddRuneAt (driver, c, r, ' ');
  687. }
  688. }
  689. // Draw the right Padding
  690. for (int r = frame.Y + sumThickness.Top;
  691. r < Math.Min (frame.Bottom - sumThickness.Bottom, driver.Rows); r++) {
  692. for (int c = Math.Max (frame.Right - sumThickness.Right, frame.X + sumThickness.Left);
  693. c < Math.Max (frame.Right - borderThickness.Right, frame.X + sumThickness.Left); c++) {
  694. AddRuneAt (driver, c, r, ' ');
  695. }
  696. }
  697. // Draw the lower Padding
  698. for (int r = Math.Max (frame.Bottom - sumThickness.Bottom, frame.Y + borderThickness.Top);
  699. r < Math.Min (frame.Bottom - borderThickness.Bottom, driver.Rows); r++) {
  700. for (int c = frame.X + borderThickness.Left;
  701. c < Math.Min (frame.Right - borderThickness.Right, driver.Cols); c++) {
  702. AddRuneAt (driver, c, r, ' ');
  703. }
  704. }
  705. driver.SetAttribute (savedAttribute);
  706. // Draw the MarginFrame
  707. var rect = new Rect () {
  708. X = frame.X + sumThickness.Left,
  709. Y = frame.Y + sumThickness.Top,
  710. Width = Math.Max (frame.Width - sumThickness.Right - sumThickness.Left, 0),
  711. Height = Math.Max (frame.Height - sumThickness.Bottom - sumThickness.Top, 0)
  712. };
  713. if (rect.Width > 0 && rect.Height > 0) {
  714. driver.DrawWindowFrame (rect, 1, 1, 1, 1, BorderStyle != BorderStyle.None, fill, this);
  715. }
  716. if (Effect3D) {
  717. driver.SetAttribute (GetEffect3DBrush ());
  718. // Draw the upper Effect3D
  719. for (int r = Math.Max (frame.Y + effect3DOffset.Y, 0);
  720. r < frame.Y; r++) {
  721. for (int c = Math.Max (frame.X + effect3DOffset.X, 0);
  722. c < Math.Min (frame.Right + effect3DOffset.X, driver.Cols); c++) {
  723. AddRuneAt (driver, c, r, (Rune)driver.Contents [r, c, 0]);
  724. }
  725. }
  726. // Draw the left Effect3D
  727. for (int r = Math.Max (frame.Y + effect3DOffset.Y, 0);
  728. r < Math.Min (frame.Bottom + effect3DOffset.Y, driver.Rows); r++) {
  729. for (int c = Math.Max (frame.X + effect3DOffset.X, 0);
  730. c < frame.X; c++) {
  731. AddRuneAt (driver, c, r, (Rune)driver.Contents [r, c, 0]);
  732. }
  733. }
  734. // Draw the right Effect3D
  735. for (int r = Math.Max (frame.Y + effect3DOffset.Y, 0);
  736. r < Math.Min (frame.Bottom + effect3DOffset.Y, driver.Rows); r++) {
  737. for (int c = frame.Right;
  738. c < Math.Min (frame.Right + effect3DOffset.X, driver.Cols); c++) {
  739. AddRuneAt (driver, c, r, (Rune)driver.Contents [r, c, 0]);
  740. }
  741. }
  742. // Draw the lower Effect3D
  743. for (int r = frame.Bottom;
  744. r < Math.Min (frame.Bottom + effect3DOffset.Y, driver.Rows); r++) {
  745. for (int c = Math.Max (frame.X + effect3DOffset.X, 0);
  746. c < Math.Min (frame.Right + effect3DOffset.X, driver.Cols); c++) {
  747. AddRuneAt (driver, c, r, (Rune)driver.Contents [r, c, 0]);
  748. }
  749. }
  750. }
  751. driver.SetAttribute (savedAttribute);
  752. }
  753. private Attribute GetEffect3DBrush ()
  754. {
  755. return Effect3DBrush == null
  756. ? new Attribute (Color.Gray, Color.DarkGray)
  757. : (Attribute)Effect3DBrush;
  758. }
  759. private void AddRuneAt (ConsoleDriver driver, int col, int row, Rune ch)
  760. {
  761. if (col < driver.Cols && row < driver.Rows && col > 0 && driver.Contents [row, col, 2] == 0
  762. && Rune.ColumnWidth ((char)driver.Contents [row, col - 1, 0]) > 1) {
  763. driver.Contents [row, col, 1] = driver.GetAttribute ();
  764. return;
  765. }
  766. driver.Move (col, row);
  767. driver.AddRune (ch);
  768. }
  769. /// <summary>
  770. /// Drawn the view text from a <see cref="View"/>.
  771. /// </summary>
  772. public void DrawTitle (View view, Rect rect)
  773. {
  774. var driver = Application.Driver;
  775. if (BorderStyle != BorderStyle.None) {
  776. driver.SetAttribute (view.GetNormalColor ());
  777. if (view.HasFocus) {
  778. driver.SetAttribute (view.ColorScheme.HotNormal);
  779. }
  780. var padding = GetSumThickness ();
  781. driver.DrawWindowTitle (rect, view.Text,
  782. padding.Left, padding.Top, padding.Right, padding.Bottom);
  783. }
  784. driver.SetAttribute (view.GetNormalColor ());
  785. }
  786. /// <summary>
  787. /// Invoke the <see cref="BorderChanged"/> event.
  788. /// </summary>
  789. public virtual void OnBorderChanged ()
  790. {
  791. BorderChanged?.Invoke (this);
  792. }
  793. }
  794. }