Frame.cs 5.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174
  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;
  17. internal override void CreateFrames (){ /* Do nothing - Frames do not have Frames */ }
  18. /// <summary>
  19. /// The Parent of this Frame (the View that this Frame surrounds).
  20. /// </summary>
  21. public View Parent { get; set; }
  22. /// <summary>
  23. /// Frames cannot be used as sub-views, so this method always throws an <see cref="InvalidOperationException"/>.
  24. /// TODO: Are we sure?
  25. /// </summary>
  26. public override View SuperView {
  27. get {
  28. return null;
  29. }
  30. set {
  31. throw new NotImplementedException ();
  32. }
  33. }
  34. /// <inheritdoc/>
  35. public override void ViewToScreen (int col, int row, out int rcol, out int rrow, bool clipped = true)
  36. {
  37. // Frames are *Children* of a View, not SubViews. Thus View.ViewToScreen will not work.
  38. // To get the screen-relative coordinates of a Frame, we need to know who
  39. // the Parent is
  40. var parentFrame = Parent?.Frame ?? Frame;
  41. rrow = row + parentFrame.Y;
  42. rcol = col + parentFrame.X;
  43. // We now have rcol/rrow in coordinates relative to our SuperView. If our SuperView has
  44. // a SuperView, keep going...
  45. Parent?.SuperView?.ViewToScreen (rcol, rrow, out rcol, out rrow, clipped);
  46. }
  47. /// <summary>
  48. ///
  49. /// </summary>
  50. /// <param name="clipRect"></param>
  51. public virtual void OnDrawSubViews (Rect clipRect)
  52. {
  53. // TODO: Enable subviews of Frames (adornments).
  54. // if (Subviews == null) {
  55. // return;
  56. // }
  57. // foreach (var view in Subviews) {
  58. // // BUGBUG: v2 - shouldn't this be !view.LayoutNeeded? Why draw if layout is going to happen and we'll just draw again?
  59. // if (view.LayoutNeeded) {
  60. // view.LayoutSubviews ();
  61. // }
  62. // if ((view.Visible && !view.NeedDisplay.IsEmpty && view.Frame.Width > 0 && view.Frame.Height > 0) || view.ChildNeedsDisplay) {
  63. // view.Redraw (view.Bounds);
  64. // view.NeedDisplay = Rect.Empty;
  65. // // BUGBUG - v2 why does this need to be set to false?
  66. // // Shouldn't it be set when the subviews draw?
  67. // view.ChildNeedsDisplay = false;
  68. // }
  69. // }
  70. }
  71. /// <summary>
  72. /// Redraws the Frames that comprise the <see cref="Frame"/>.
  73. /// </summary>
  74. /// <param name="clipRect"></param>
  75. public override void Redraw (Rect clipRect)
  76. {
  77. if (Thickness == Thickness.Empty) return;
  78. //OnDrawContent (bounds);
  79. //OnDrawSubViews (bounds);
  80. //OnDrawContentComplete (bounds);
  81. if (ColorScheme != null) {
  82. Driver.SetAttribute (ColorScheme.Normal);
  83. }
  84. var prevClip = SetClip (Frame);
  85. var screenBounds = ViewToScreen (Frame);
  86. Thickness.Draw (screenBounds, (string)(Data != null ? Data : string.Empty));
  87. //OnDrawContent (bounds);
  88. if (BorderStyle != BorderStyle.None) {
  89. var lc = new LineCanvas ();
  90. lc.AddLine (screenBounds.Location, Frame.Width - 1, Orientation.Horizontal, BorderStyle);
  91. lc.AddLine (screenBounds.Location, Frame.Height - 1, Orientation.Vertical, BorderStyle);
  92. lc.AddLine (new Point (screenBounds.X, screenBounds.Y + screenBounds.Height - 1), screenBounds.Width - 1, Orientation.Horizontal, BorderStyle);
  93. lc.AddLine (new Point (screenBounds.X + screenBounds.Width - 1, screenBounds.Y), screenBounds.Height - 1, Orientation.Vertical, BorderStyle);
  94. foreach (var p in lc.GenerateImage (screenBounds)) {
  95. Driver.Move (p.Key.X, p.Key.Y);
  96. Driver.AddRune (p.Value);
  97. }
  98. if (!ustring.IsNullOrEmpty (Parent?.Title)) {
  99. Driver.SetAttribute (Parent.HasFocus ? Parent.GetHotNormalColor () : Parent.GetNormalColor ());
  100. Driver.DrawWindowTitle (screenBounds, Parent?.Title, 0, 0, 0, 0);
  101. }
  102. }
  103. Driver.Clip = prevClip;
  104. }
  105. // TODO: v2 - Frame.BorderStyle is temporary - Eventually the border will be drawn by a "BorderView" that is a subview of the Frame.
  106. /// <summary>
  107. ///
  108. /// </summary>
  109. public BorderStyle BorderStyle { get; set; } = BorderStyle.None;
  110. /// <summary>
  111. /// Defines the rectangle that the <see cref="Frame"/> will use to draw its content.
  112. /// </summary>
  113. public Thickness Thickness {
  114. get { return _thickness; }
  115. set {
  116. var prev = _thickness;
  117. _thickness = value;
  118. if (prev != _thickness) {
  119. OnThicknessChanged ();
  120. }
  121. }
  122. }
  123. /// <summary>
  124. /// Called whenever the <see cref="Thickness"/> property changes.
  125. /// </summary>
  126. public virtual void OnThicknessChanged ()
  127. {
  128. ThicknessChanged?.Invoke (this, new ThicknessEventArgs () { Thickness = Thickness });
  129. }
  130. /// <summary>
  131. /// Fired whenever the <see cref="Thickness"/> property changes.
  132. /// </summary>
  133. public event EventHandler<ThicknessEventArgs> ThicknessChanged;
  134. /// <summary>
  135. /// Gets the rectangle that describes the inner area of the frame. The Location is always (0,0).
  136. /// </summary>
  137. public override Rect Bounds {
  138. get {
  139. return Thickness?.GetInnerRect (new Rect (Point.Empty, Frame.Size)) ?? new Rect (Point.Empty, Frame.Size);
  140. }
  141. set {
  142. throw new InvalidOperationException ("It makes no sense to set Bounds of a Thickness.");
  143. }
  144. }
  145. }
  146. }