Border.cs 33 KB

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