ShadowView.cs 6.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201
  1. namespace Terminal.Gui.ViewBase;
  2. /// <summary>
  3. /// Draws a shadow on the right or bottom of the view. Used internally by <see cref="Margin"/>.
  4. /// </summary>
  5. internal class ShadowView : View
  6. {
  7. private ShadowStyle _shadowStyle;
  8. /// <inheritdoc/>
  9. protected override bool OnDrawingText () { return true; }
  10. /// <inheritdoc/>
  11. protected override bool OnClearingViewport ()
  12. {
  13. // Prevent clearing (so we can have transparency)
  14. return true;
  15. }
  16. /// <inheritdoc/>
  17. protected override bool OnDrawingContent (DrawContext? context)
  18. {
  19. switch (ShadowStyle)
  20. {
  21. case ShadowStyle.Opaque:
  22. if (Orientation == Orientation.Vertical)
  23. {
  24. DrawVerticalShadowOpaque (Viewport);
  25. }
  26. else
  27. {
  28. DrawHorizontalShadowOpaque (Viewport);
  29. }
  30. break;
  31. case ShadowStyle.Transparent:
  32. if (Orientation == Orientation.Vertical)
  33. {
  34. DrawVerticalShadowTransparent (Viewport);
  35. }
  36. else
  37. {
  38. DrawHorizontalShadowTransparent (Viewport);
  39. }
  40. break;
  41. }
  42. return true;
  43. }
  44. /// <summary>
  45. /// Gets or sets the orientation of the shadow.
  46. /// </summary>
  47. public Orientation Orientation { get; set; }
  48. public override ShadowStyle ShadowStyle
  49. {
  50. get => _shadowStyle;
  51. set
  52. {
  53. Visible = value != ShadowStyle.None;
  54. _shadowStyle = value;
  55. ViewportSettings |= ViewportSettingsFlags.TransparentMouse;
  56. }
  57. }
  58. private void DrawHorizontalShadowOpaque (Rectangle rectangle)
  59. {
  60. // Draw the start glyph
  61. SetAttribute (GetAttributeUnderLocation (ViewportToScreen (new Point (0, 0))));
  62. AddRune (0, 0, Glyphs.ShadowHorizontalStart);
  63. // Fill the rest of the rectangle with the glyph - note we skip the last since vertical will draw it
  64. for (var i = 1; i < rectangle.Width - 1; i++)
  65. {
  66. SetAttribute (GetAttributeUnderLocation (ViewportToScreen (new Point (i, 0))));
  67. AddRune (i, 0, Glyphs.ShadowHorizontal);
  68. }
  69. // Last is special
  70. SetAttribute (GetAttributeUnderLocation (ViewportToScreen (new Point (rectangle.Width - 1, 0))));
  71. AddRune (rectangle.Width - 1, 0, Glyphs.ShadowHorizontalEnd);
  72. }
  73. private void DrawHorizontalShadowTransparent (Rectangle viewport)
  74. {
  75. Rectangle screen = ViewportToScreen (Viewport);
  76. for (int r = Math.Max (0, screen.Y); r < screen.Y + screen.Height; r++)
  77. {
  78. for (int c = Math.Max (0, screen.X + 1); c < screen.X + screen.Width; c++)
  79. {
  80. Driver?.Move (c, r);
  81. SetAttribute (GetAttributeUnderLocation (new (c, r)));
  82. if (c < ScreenContents?.GetLength (1) && r < ScreenContents?.GetLength (0))
  83. {
  84. string grapheme = ScreenContents [r, c].Grapheme;
  85. AddStr (grapheme);
  86. if (grapheme.GetColumns () > 1)
  87. {
  88. c++;
  89. }
  90. }
  91. }
  92. }
  93. }
  94. private void DrawVerticalShadowOpaque (Rectangle viewport)
  95. {
  96. // Draw the start glyph
  97. SetAttribute (GetAttributeUnderLocation (ViewportToScreen (new Point (0, 0))));
  98. AddRune (0, 0, Glyphs.ShadowVerticalStart);
  99. // Fill the rest of the rectangle with the glyph
  100. for (var i = 1; i < viewport.Height - 1; i++)
  101. {
  102. SetAttribute (GetAttributeUnderLocation (ViewportToScreen (new Point (0, i))));
  103. AddRune (0, i, Glyphs.ShadowVertical);
  104. }
  105. }
  106. private void DrawVerticalShadowTransparent (Rectangle viewport)
  107. {
  108. Rectangle screen = ViewportToScreen (Viewport);
  109. // Fill in the rest of the rectangle
  110. for (int r = Math.Max (0, screen.Y); r < screen.Y + viewport.Height; r++)
  111. {
  112. for (int c = Math.Max (0, screen.X); c < screen.X + screen.Width; c++)
  113. {
  114. Driver?.Move (c, r);
  115. SetAttribute (GetAttributeUnderLocation (new (c, r)));
  116. if (ScreenContents is { } && screen.X < ScreenContents.GetLength (1) && r < ScreenContents.GetLength (0)
  117. && c < ScreenContents.GetLength (1) && r < ScreenContents.GetLength (0))
  118. {
  119. string grapheme = ScreenContents [r, c].Grapheme;
  120. AddStr (grapheme);
  121. if (grapheme.GetColumns () > 1)
  122. {
  123. c++;
  124. }
  125. }
  126. }
  127. }
  128. }
  129. // BUGBUG: This will never really work completely right by looking at an underlying cell and trying
  130. // BUGBUG: to do transparency by adjusting colors. Instead, it might be possible to use the A in argb for this.
  131. // BUGBUG: See https://github.com/gui-cs/Terminal.Gui/issues/4491
  132. private Attribute GetAttributeUnderLocation (Point location)
  133. {
  134. if (SuperView is not Adornment
  135. || location.X < 0
  136. || location.X >= App?.Screen.Width
  137. || location.Y < 0
  138. || location.Y >= App?.Screen.Height)
  139. {
  140. return Attribute.Default;
  141. }
  142. if (ScreenContents == null ||
  143. location.Y < 0 || location.Y >= ScreenContents.GetLength (0) ||
  144. location.X < 0 || location.X >= ScreenContents.GetLength (1))
  145. {
  146. return Attribute.Default;
  147. }
  148. Attribute attr = ScreenContents [location.Y, location.X].Attribute!.Value;
  149. var newAttribute =
  150. new Attribute (
  151. ShadowStyle == ShadowStyle.Opaque ? Color.Black : attr.Foreground.GetDimColor (),
  152. ShadowStyle == ShadowStyle.Opaque ? attr.Background : attr.Background.GetDimColor (0.05),
  153. attr.Style);
  154. // If the BG is DarkGray, GetDimColor gave up. Instead of using the attribute in the Driver under the shadow,
  155. // use the Normal attribute from the View under the shadow.
  156. if (newAttribute.Background == Color.DarkGray)
  157. {
  158. List<View?> currentViewsUnderMouse = GetViewsUnderLocation (location, ViewportSettingsFlags.Transparent);
  159. View? underView = currentViewsUnderMouse.LastOrDefault ();
  160. attr = underView?.GetAttributeForRole (VisualRole.Normal) ?? Attribute.Default;
  161. newAttribute = new (
  162. ShadowStyle == ShadowStyle.Opaque ? Color.Black : attr.Background.GetDimColor (),
  163. ShadowStyle == ShadowStyle.Opaque ? attr.Background : attr.Foreground.GetDimColor (0.25),
  164. attr.Style);
  165. }
  166. return newAttribute;
  167. }
  168. }