Frame.cs 8.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241
  1. using NStack;
  2. using System;
  3. using System.Collections.Generic;
  4. using System.ComponentModel;
  5. using System.Xml.Linq;
  6. using Terminal.Gui.Graphs;
  7. namespace Terminal.Gui {
  8. // TODO: v2 - Missing 3D effect - 3D effects will be drawn by a mechanism separate from Frames
  9. // TODO: v2 - If a Frame has focus, navigation keys (e.g Command.NextView) should cycle through SubViews of the Frame
  10. // QUESTION: How does a user navigate out of a Frame to another Frame, or back into the Parent's SubViews?
  11. /// <summary>
  12. /// Frames are a special form of <see cref="View"/> that act as adornments; they appear outside of the <see cref="View.Bounds"/>
  13. /// enabling borders, menus, etc...
  14. /// </summary>
  15. public class Frame : View {
  16. private Thickness _thickness = Thickness.Empty;
  17. internal override void CreateFrames () { /* Do nothing - Frames do not have Frames */ }
  18. internal override void LayoutFrames () { /* Do nothing - Frames do not have Frames */ }
  19. /// <summary>
  20. /// The Parent of this Frame (the View this Frame surrounds).
  21. /// </summary>
  22. public View Parent { get; set; }
  23. /// <summary>
  24. /// Frames cannot be used as sub-views, so this method always throws an <see cref="InvalidOperationException"/>.
  25. /// TODO: Are we sure?
  26. /// </summary>
  27. public override View SuperView {
  28. get {
  29. return null;
  30. }
  31. set {
  32. throw new NotImplementedException ();
  33. }
  34. }
  35. /// <inheritdoc/>
  36. public override void ViewToScreen (int col, int row, out int rcol, out int rrow, bool clipped = true)
  37. {
  38. // Frames are *Children* of a View, not SubViews. Thus View.ViewToScreen will not work.
  39. // To get the screen-relative coordinates of a Frame, we need to know who
  40. // the Parent is
  41. var parentFrame = Parent?.Frame ?? Frame;
  42. rrow = row + parentFrame.Y;
  43. rcol = col + parentFrame.X;
  44. // We now have rcol/rrow in coordinates relative to our SuperView. If our SuperView has
  45. // a SuperView, keep going...
  46. Parent?.SuperView?.ViewToScreen (rcol, rrow, out rcol, out rrow, clipped);
  47. }
  48. /// <summary>
  49. ///
  50. /// </summary>
  51. /// <param name="clipRect"></param>
  52. public virtual void OnDrawSubViews (Rect clipRect)
  53. {
  54. // TODO: Enable subviews of Frames (adornments).
  55. // if (Subviews == null) {
  56. // return;
  57. // }
  58. // foreach (var view in Subviews) {
  59. // // BUGBUG: v2 - shouldn't this be !view.LayoutNeeded? Why draw if layout is going to happen and we'll just draw again?
  60. // if (view.LayoutNeeded) {
  61. // view.LayoutSubviews ();
  62. // }
  63. // if ((view.Visible && !view.NeedDisplay.IsEmpty && view.Frame.Width > 0 && view.Frame.Height > 0) || view.ChildNeedsDisplay) {
  64. // view.Redraw (view.Bounds);
  65. // view.NeedDisplay = Rect.Empty;
  66. // // BUGBUG - v2 why does this need to be set to false?
  67. // // Shouldn't it be set when the subviews draw?
  68. // view.ChildNeedsDisplay = false;
  69. // }
  70. // }
  71. }
  72. /// <summary>
  73. /// Redraws the Frames that comprise the <see cref="Frame"/>.
  74. /// </summary>
  75. /// <param name="bounds"></param>
  76. public override void Redraw (Rect bounds)
  77. {
  78. if (Thickness == Thickness.Empty) return;
  79. if (ColorScheme != null) {
  80. Driver.SetAttribute (ColorScheme.Normal);
  81. } else {
  82. Driver.SetAttribute (Parent.GetNormalColor ());
  83. }
  84. var prevClip = SetClip (Frame);
  85. var screenBounds = ViewToScreen (Frame);
  86. Thickness.Draw (screenBounds, (string)(Data != null ? Data : string.Empty));
  87. //OnDrawSubviews (bounds);
  88. // TODO: v2 - this will eventually be two controls: "BorderView" and "Label" (for the title)
  89. if (Id == "BorderFrame" && Thickness.Top > 0 && Frame.Width > 1 && !ustring.IsNullOrEmpty (Parent?.Title)) {
  90. var prevAttr = Driver.GetAttribute ();
  91. Driver.SetAttribute (Parent.HasFocus ? Parent.GetHotNormalColor () : Parent.GetNormalColor ());
  92. Driver.DrawWindowTitle (screenBounds, Parent?.Title, 0, 0, 0, 0);
  93. Driver.SetAttribute (prevAttr);
  94. }
  95. if (Id == "BorderFrame" && BorderStyle != BorderStyle.None) {
  96. var lc = new LineCanvas ();
  97. var drawTop = Thickness.Top > 0 && Frame.Width > 1 && Frame.Height > 1;
  98. var drawLeft = Thickness.Left > 0 && (Frame.Height > 1 || Thickness.Top == 0);
  99. var drawBottom = Thickness.Bottom > 0 && Frame.Width > 1;
  100. var drawRight = Thickness.Right > 0 && (Frame.Height > 1 || Thickness.Top == 0);
  101. if (drawTop) {
  102. // ╔╡Title╞═════╗
  103. // ╔╡╞═════╗
  104. if (Frame.Width < 4 || ustring.IsNullOrEmpty (Parent?.Title)) {
  105. // ╔╡╞╗ should be ╔══╗
  106. lc.AddLine (screenBounds.Location, Frame.Width - 1, Orientation.Horizontal, BorderStyle);
  107. } else {
  108. var titleWidth = Math.Min (Parent.Title.ConsoleWidth, Frame.Width - 4);
  109. // ╔╡Title╞═════╗
  110. // Add a short horiz line for ╔╡
  111. lc.AddLine (screenBounds.Location, 1, Orientation.Horizontal, BorderStyle);
  112. // Add a short vert line for ╔╡
  113. lc.AddLine (new Point (screenBounds.X + 1, screenBounds.Location.Y), 0, Orientation.Vertical, BorderStyle.Single);
  114. // Add a short vert line for ╞
  115. lc.AddLine (new Point (screenBounds.X + 1 + (titleWidth + 1), screenBounds.Location.Y), 0, Orientation.Vertical, BorderStyle.Single);
  116. // Add the right hand line for ╞═════╗
  117. lc.AddLine (new Point (screenBounds.X + 1 + (titleWidth + 1), screenBounds.Location.Y), Frame.Width - (titleWidth + 3), Orientation.Horizontal, BorderStyle);
  118. }
  119. }
  120. if (drawLeft) {
  121. lc.AddLine (screenBounds.Location, Frame.Height - 1, Orientation.Vertical, BorderStyle);
  122. }
  123. if (drawBottom) {
  124. lc.AddLine (new Point (screenBounds.X, screenBounds.Y + screenBounds.Height - 1), screenBounds.Width - 1, Orientation.Horizontal, BorderStyle);
  125. }
  126. if (drawRight) {
  127. lc.AddLine (new Point (screenBounds.X + screenBounds.Width - 1, screenBounds.Y), screenBounds.Height - 1, Orientation.Vertical, BorderStyle);
  128. }
  129. foreach (var p in lc.GenerateImage (screenBounds)) {
  130. Driver.Move (p.Key.X, p.Key.Y);
  131. Driver.AddRune (p.Value);
  132. }
  133. // TODO: This should be moved to LineCanvas as a new BorderStyle.Ruler
  134. if ((ConsoleDriver.Diagnostics & ConsoleDriver.DiagnosticFlags.FrameRuler) == ConsoleDriver.DiagnosticFlags.FrameRuler) {
  135. // Top
  136. var hruler = new Ruler () { Length = screenBounds.Width, Orientation = Orientation.Horizontal };
  137. if (drawTop) {
  138. hruler.Draw (new Point (screenBounds.X, screenBounds.Y));
  139. }
  140. // Redraw title
  141. if (drawTop && Id == "BorderFrame" && !ustring.IsNullOrEmpty (Parent?.Title)) {
  142. var prevAttr = Driver.GetAttribute ();
  143. Driver.SetAttribute (Parent.HasFocus ? Parent.GetHotNormalColor () : Parent.GetNormalColor ());
  144. Driver.DrawWindowTitle (screenBounds, Parent?.Title, 0, 0, 0, 0);
  145. Driver.SetAttribute (prevAttr);
  146. }
  147. //Left
  148. var vruler = new Ruler () { Length = screenBounds.Height - 2, Orientation = Orientation.Vertical };
  149. if (drawLeft) {
  150. vruler.Draw (new Point (screenBounds.X, screenBounds.Y + 1), 1);
  151. }
  152. // Bottom
  153. if (drawBottom) {
  154. hruler.Draw (new Point (screenBounds.X, screenBounds.Y + screenBounds.Height - 1));
  155. }
  156. // Right
  157. if (drawRight) {
  158. vruler.Draw (new Point (screenBounds.X + screenBounds.Width - 1, screenBounds.Y + 1), 1);
  159. }
  160. }
  161. }
  162. Driver.Clip = prevClip;
  163. }
  164. // TODO: v2 - Frame.BorderStyle is temporary - Eventually the border will be drawn by a "BorderView" that is a subview of the Frame.
  165. /// <summary>
  166. ///
  167. /// </summary>
  168. public BorderStyle BorderStyle { get; set; } = BorderStyle.None;
  169. /// <summary>
  170. /// Defines the rectangle that the <see cref="Frame"/> will use to draw its content.
  171. /// </summary>
  172. public Thickness Thickness {
  173. get { return _thickness; }
  174. set {
  175. var prev = _thickness;
  176. _thickness = value;
  177. if (prev != _thickness) {
  178. OnThicknessChanged ();
  179. }
  180. }
  181. }
  182. /// <summary>
  183. /// Called whenever the <see cref="Thickness"/> property changes.
  184. /// </summary>
  185. public virtual void OnThicknessChanged ()
  186. {
  187. ThicknessChanged?.Invoke (this, new ThicknessEventArgs () { Thickness = Thickness });
  188. }
  189. /// <summary>
  190. /// Fired whenever the <see cref="Thickness"/> property changes.
  191. /// </summary>
  192. public event EventHandler<ThicknessEventArgs> ThicknessChanged;
  193. /// <summary>
  194. /// Gets the rectangle that describes the inner area of the frame. The Location is always (0,0).
  195. /// </summary>
  196. public override Rect Bounds {
  197. get {
  198. return Thickness?.GetInside (new Rect (Point.Empty, Frame.Size)) ?? new Rect (Point.Empty, Frame.Size);
  199. }
  200. set {
  201. throw new InvalidOperationException ("It makes no sense to set Bounds of a Thickness.");
  202. }
  203. }
  204. }
  205. }