Adornment.cs 8.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235
  1. namespace Terminal.Gui;
  2. // TODO: v2 - Missing 3D effect - 3D effects will be drawn by a mechanism separate from Adornments
  3. // TODO: v2 - If a Adornment has focus, navigation keys (e.g Command.NextView) should cycle through SubViews of the Adornments
  4. // QUESTION: How does a user navigate out of an Adornment to another Adornment, or back into the Parent's SubViews?
  5. /// <summary>
  6. /// Adornments are a special form of <see cref="View"/> that appear outside of the <see cref="View.Bounds"/>:
  7. /// <see cref="Margin"/>, <see cref="Border"/>, and <see cref="Padding"/>. They are defined using the
  8. /// <see cref="Thickness"/> class, which specifies the thickness of the sides of a rectangle.
  9. /// </summary>
  10. /// <remarsk>
  11. /// <para>
  12. /// There is no prevision for creating additional subclasses of Adornment. It is not abstract to enable unit
  13. /// testing.
  14. /// </para>
  15. /// <para>Each of <see cref="Margin"/>, <see cref="Border"/>, and <see cref="Padding"/> can be customized.</para>
  16. /// </remarsk>
  17. public class Adornment : View
  18. {
  19. private Thickness _thickness = Thickness.Empty;
  20. /// <inheritdoc/>
  21. public Adornment ()
  22. {
  23. /* Do nothing; A parameter-less constructor is required to support all views unit tests. */
  24. }
  25. /// <summary>Constructs a new adornment for the view specified by <paramref name="parent"/>.</summary>
  26. /// <param name="parent"></param>
  27. public Adornment (View parent) { Parent = parent; }
  28. /// <summary>Gets the rectangle that describes the inner area of the Adornment. The Location is always (0,0).</summary>
  29. public override Rectangle Bounds
  30. {
  31. get => new Rectangle (Point.Empty, Thickness?.GetInside (new (Point.Empty, Frame.Size)).Size ?? Frame.Size);
  32. // QUESTION: So why even have a setter then?
  33. set => throw new InvalidOperationException ("It makes no sense to set Bounds of a Thickness.");
  34. }
  35. /// <summary>The Parent of this Adornment (the View this Adornment surrounds).</summary>
  36. /// <remarks>
  37. /// Adornments are distinguished from typical View classes in that they are not sub-views, but have a parent/child
  38. /// relationship with their containing View.
  39. /// </remarks>
  40. public View Parent { get; set; }
  41. /// <summary>
  42. /// Adornments cannot be used as sub-views (see <see cref="Parent"/>); this method always throws an
  43. /// <see cref="InvalidOperationException"/>. TODO: Are we sure?
  44. /// </summary>
  45. public override View SuperView
  46. {
  47. get => null;
  48. set => throw new NotImplementedException ();
  49. }
  50. /// <summary>
  51. /// Adornments only render to their <see cref="Parent"/>'s or Parent's SuperView's LineCanvas, so setting this
  52. /// property throws an <see cref="InvalidOperationException"/>.
  53. /// </summary>
  54. public override bool SuperViewRendersLineCanvas
  55. {
  56. get => false; // throw new NotImplementedException ();
  57. set => throw new NotImplementedException ();
  58. }
  59. /// <summary>Defines the rectangle that the <see cref="Adornment"/> will use to draw its content.</summary>
  60. public Thickness Thickness
  61. {
  62. get => _thickness;
  63. set
  64. {
  65. Thickness prev = _thickness;
  66. _thickness = value;
  67. if (prev != _thickness)
  68. {
  69. Parent?.LayoutAdornments ();
  70. OnThicknessChanged (prev);
  71. }
  72. }
  73. }
  74. /// <inheritdoc/>
  75. public override void BoundsToScreen (int col, int row, out int rcol, out int rrow, bool clipped = true)
  76. {
  77. // Adornments are *Children* of a View, not SubViews. Thus View.BoundsToScreen will not work.
  78. // To get the screen-relative coordinates of a Adornment, we need to know who
  79. // the Parent is
  80. Rectangle parentFrame = Parent?.Frame ?? Frame;
  81. rrow = row + parentFrame.Y;
  82. rcol = col + parentFrame.X;
  83. // We now have rcol/rrow in coordinates relative to our View's SuperView. If our View's SuperView has
  84. // a SuperView, keep going...
  85. Parent?.SuperView?.BoundsToScreen (rcol, rrow, out rcol, out rrow, clipped);
  86. }
  87. /// <inheritdoc/>
  88. public override Rectangle FrameToScreen ()
  89. {
  90. if (Parent is null)
  91. {
  92. return Frame;
  93. }
  94. // Adornments are *Children* of a View, not SubViews. Thus View.FrameToScreen will not work.
  95. // To get the screen-relative coordinates of a Adornment, we need to know who
  96. // the Parent is
  97. Rectangle parent = Parent.FrameToScreen ();
  98. // We now have coordinates relative to our View. If our View's SuperView has
  99. // a SuperView, keep going...
  100. return new (new (parent.X + Frame.X, parent.Y + Frame.Y), Frame.Size);
  101. }
  102. /// <summary>Does nothing for Adornment</summary>
  103. /// <returns></returns>
  104. public override bool OnDrawAdornments () { return false; }
  105. /// <summary>Redraws the Adornments that comprise the <see cref="Adornment"/>.</summary>
  106. public override void OnDrawContent (Rectangle contentArea)
  107. {
  108. if (Thickness == Thickness.Empty)
  109. {
  110. return;
  111. }
  112. Rectangle screenBounds = BoundsToScreen (Frame);
  113. Attribute normalAttr = GetNormalColor ();
  114. // This just draws/clears the thickness, not the insides.
  115. Driver.SetAttribute (normalAttr);
  116. Thickness.Draw (screenBounds, (string)(Data ?? string.Empty));
  117. if (!string.IsNullOrEmpty (TextFormatter.Text))
  118. {
  119. if (TextFormatter is { })
  120. {
  121. TextFormatter.Size = Frame.Size;
  122. TextFormatter.NeedsFormat = true;
  123. }
  124. }
  125. TextFormatter?.Draw (screenBounds, normalAttr, normalAttr, Rectangle.Empty);
  126. //base.OnDrawContent (contentArea);
  127. }
  128. /// <summary>Does nothing for Adornment</summary>
  129. /// <returns></returns>
  130. public override bool OnRenderLineCanvas () { return false; }
  131. /// <summary>Called whenever the <see cref="Thickness"/> property changes.</summary>
  132. public virtual void OnThicknessChanged (Thickness previousThickness)
  133. {
  134. ThicknessChanged?.Invoke (
  135. this,
  136. new ThicknessEventArgs { Thickness = Thickness, PreviousThickness = previousThickness }
  137. );
  138. }
  139. /// <summary>Fired whenever the <see cref="Thickness"/> property changes.</summary>
  140. public event EventHandler<ThicknessEventArgs> ThicknessChanged;
  141. internal override Adornment CreateAdornment (Type adornmentType)
  142. {
  143. /* Do nothing - Adornments do not have Adornments */
  144. return null;
  145. }
  146. internal override void LayoutAdornments ()
  147. {
  148. /* Do nothing - Adornments do not have Adornments */
  149. }
  150. /// <inheritdoc/>
  151. public override bool OnMouseEvent (MouseEvent mouseEvent)
  152. {
  153. var args = new MouseEventEventArgs (mouseEvent);
  154. if (MouseEvent (mouseEvent))
  155. {
  156. return true;
  157. }
  158. if (mouseEvent.Flags == MouseFlags.Button1Clicked)
  159. {
  160. if (Parent.CanFocus && !Parent.HasFocus)
  161. {
  162. Parent.SetFocus ();
  163. Parent.SetNeedsDisplay ();
  164. }
  165. return OnMouseClick (args);
  166. }
  167. if (mouseEvent.Flags == MouseFlags.Button2Clicked)
  168. {
  169. return OnMouseClick (args);
  170. }
  171. if (mouseEvent.Flags == MouseFlags.Button3Clicked)
  172. {
  173. return OnMouseClick (args);
  174. }
  175. if (mouseEvent.Flags == MouseFlags.Button4Clicked)
  176. {
  177. return OnMouseClick (args);
  178. }
  179. return false;
  180. }
  181. /// <inheritdoc/>
  182. public override bool OnMouseEnter (MouseEvent mouseEvent)
  183. {
  184. var args = new MouseEventEventArgs (mouseEvent);
  185. return args.Handled || base.OnMouseEnter (mouseEvent);
  186. }
  187. /// <inheritdoc/>
  188. public override bool OnMouseLeave (MouseEvent mouseEvent)
  189. {
  190. var args = new MouseEventEventArgs (mouseEvent);
  191. return args.Handled || base.OnMouseLeave (mouseEvent);
  192. }
  193. }