Border.cs 35 KB

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