Border.cs 33 KB

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