Adornment.cs 5.9 KB

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