DimAuto.cs 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321
  1. #nullable enable
  2. using System.Drawing;
  3. namespace Terminal.Gui;
  4. /// <summary>
  5. /// Represents a dimension that automatically sizes the view to fit all the view's Content, SubViews, and/or Text.
  6. /// </summary>
  7. /// <remarks>
  8. /// <para>
  9. /// See <see cref="DimAutoStyle"/>.
  10. /// </para>
  11. /// <para>
  12. /// This is a low-level API that is typically used internally by the layout system. Use the various static
  13. /// methods on the <see cref="Dim"/> class to create <see cref="Dim"/> objects instead.
  14. /// </para>
  15. /// </remarks>
  16. public class DimAuto () : Dim
  17. {
  18. private readonly Dim? _maximumContentDim;
  19. /// <summary>
  20. /// Gets the maximum dimension the View's ContentSize will be fit to. NOT CURRENTLY SUPPORTED.
  21. /// </summary>
  22. // ReSharper disable once ConvertToAutoProperty
  23. public required Dim? MaximumContentDim
  24. {
  25. get => _maximumContentDim;
  26. init => _maximumContentDim = value;
  27. }
  28. private readonly Dim? _minimumContentDim;
  29. /// <summary>
  30. /// Gets the minimum dimension the View's ContentSize will be constrained to.
  31. /// </summary>
  32. // ReSharper disable once ConvertToAutoProperty
  33. public required Dim? MinimumContentDim
  34. {
  35. get => _minimumContentDim;
  36. init => _minimumContentDim = value;
  37. }
  38. private readonly DimAutoStyle _style;
  39. /// <summary>
  40. /// Gets the style of the DimAuto.
  41. /// </summary>
  42. // ReSharper disable once ConvertToAutoProperty
  43. public required DimAutoStyle Style
  44. {
  45. get => _style;
  46. init => _style = value;
  47. }
  48. /// <inheritdoc/>
  49. public override string ToString () { return $"Auto({Style},{MinimumContentDim},{MaximumContentDim})"; }
  50. internal override int Calculate (int location, int superviewContentSize, View us, Dimension dimension)
  51. {
  52. var textSize = 0;
  53. var subviewsSize = 0;
  54. int autoMin = MinimumContentDim?.GetAnchor (superviewContentSize) ?? 0;
  55. int autoMax = MaximumContentDim?.GetAnchor (superviewContentSize) ?? superviewContentSize;
  56. if (Style.FastHasFlags (DimAutoStyle.Text))
  57. {
  58. textSize = int.Max (autoMin, dimension == Dimension.Width ? us.TextFormatter.Size.Width : us.TextFormatter.Size.Height);
  59. }
  60. if (Style.FastHasFlags (DimAutoStyle.Content))
  61. {
  62. if (!us.ContentSizeTracksViewport)
  63. {
  64. // ContentSize was explicitly set. Ignore subviews.
  65. subviewsSize = dimension == Dimension.Width ? us.GetContentSize ().Width : us.GetContentSize ().Height;
  66. }
  67. else
  68. {
  69. // ContentSize was NOT explicitly set. Use subviews to determine size.
  70. // TODO: This whole body of code is a WIP (for https://github.com/gui-cs/Terminal.Gui/pull/3451).
  71. subviewsSize = 0;
  72. List<View> includedSubviews = us.Subviews.ToList (); //.Where (v => !v.ExcludeFromLayout).ToList ();
  73. List<View> subviews;
  74. #region Not Anchored and Are Not Dependent
  75. // Start with subviews that are not anchored to the end, aligned, or dependent on content size
  76. // [x] PosAnchorEnd
  77. // [x] PosAlign
  78. // [ ] PosCenter
  79. // [ ] PosPercent
  80. // [ ] PosView
  81. // [ ] PosFunc
  82. // [x] DimFill
  83. // [ ] DimPercent
  84. // [ ] DimFunc
  85. // [ ] DimView
  86. if (dimension == Dimension.Width)
  87. {
  88. subviews = includedSubviews.Where (
  89. v => v.X is not PosAnchorEnd
  90. && v.X is not PosAlign
  91. // && v.X is not PosCenter
  92. && v.Width is not DimAuto
  93. && v.Width is not DimFill)
  94. .ToList ();
  95. }
  96. else
  97. {
  98. subviews = includedSubviews.Where (
  99. v => v.Y is not PosAnchorEnd
  100. && v.Y is not PosAlign
  101. // && v.Y is not PosCenter
  102. && v.Height is not DimAuto
  103. && v.Height is not DimFill)
  104. .ToList ();
  105. }
  106. for (var i = 0; i < subviews.Count; i++)
  107. {
  108. View v = subviews [i];
  109. int size = dimension == Dimension.Width ? v.Frame.X + v.Frame.Width : v.Frame.Y + v.Frame.Height;
  110. if (size > subviewsSize)
  111. {
  112. // BUGBUG: Should we break here? Or choose min/max?
  113. subviewsSize = size;
  114. }
  115. }
  116. #endregion Not Anchored and Are Not Dependent
  117. #region Anchored
  118. // Now, handle subviews that are anchored to the end
  119. // [x] PosAnchorEnd
  120. if (dimension == Dimension.Width)
  121. {
  122. subviews = includedSubviews.Where (v => v.X is PosAnchorEnd).ToList ();
  123. }
  124. else
  125. {
  126. subviews = includedSubviews.Where (v => v.Y is PosAnchorEnd).ToList ();
  127. }
  128. int maxAnchorEnd = 0;
  129. for (var i = 0; i < subviews.Count; i++)
  130. {
  131. View v = subviews [i];
  132. maxAnchorEnd = dimension == Dimension.Width ? v.Frame.Width : v.Frame.Height;
  133. }
  134. subviewsSize += maxAnchorEnd;
  135. #endregion Anchored
  136. #region Auto
  137. if (dimension == Dimension.Width)
  138. {
  139. subviews = includedSubviews.Where (v => v.Width is DimAuto).ToList ();
  140. }
  141. else
  142. {
  143. subviews = includedSubviews.Where (v => v.Height is DimAuto).ToList ();
  144. }
  145. int maxAuto = 0;
  146. for (var i = 0; i < subviews.Count; i++)
  147. {
  148. View v = subviews [i];
  149. maxAuto = CalculateMinDimension (us, dimension);
  150. if (maxAuto > subviewsSize)
  151. {
  152. // BUGBUG: Should we break here? Or choose min/max?
  153. subviewsSize = maxAuto;
  154. }
  155. }
  156. // subviewsSize += maxAuto;
  157. #endregion Auto
  158. #region Center
  159. // Now, handle subviews that are Centered
  160. if (dimension == Dimension.Width)
  161. {
  162. subviews = us.Subviews.Where (v => v.X is PosCenter).ToList ();
  163. }
  164. else
  165. {
  166. subviews = us.Subviews.Where (v => v.Y is PosCenter).ToList ();
  167. }
  168. int maxCenter = 0;
  169. for (var i = 0; i < subviews.Count; i++)
  170. {
  171. View v = subviews [i];
  172. maxCenter = dimension == Dimension.Width ? v.Frame.Width : v.Frame.Height;
  173. if (maxCenter > subviewsSize)
  174. {
  175. // BUGBUG: Should we break here? Or choose min/max?
  176. subviewsSize = maxCenter;
  177. }
  178. }
  179. #endregion Center
  180. #region Are Dependent
  181. // Now, go back to those that are dependent on content size
  182. // [x] DimFill
  183. // [ ] DimPercent
  184. if (dimension == Dimension.Width)
  185. {
  186. subviews = includedSubviews.Where (v => v.Width is DimFill).ToList ();
  187. }
  188. else
  189. {
  190. subviews = includedSubviews.Where (v => v.Height is DimFill).ToList ();
  191. }
  192. int maxFill = 0;
  193. for (var i = 0; i < subviews.Count; i++)
  194. {
  195. View v = subviews [i];
  196. if (dimension == Dimension.Width)
  197. {
  198. v.SetRelativeLayout (new Size (autoMax - subviewsSize, 0));
  199. }
  200. else
  201. {
  202. v.SetRelativeLayout (new Size (0, autoMax - subviewsSize));
  203. }
  204. maxFill = dimension == Dimension.Width ? v.Frame.Width : v.Frame.Height;
  205. }
  206. subviewsSize += maxFill;
  207. #endregion Are Dependent
  208. }
  209. }
  210. // All sizes here are content-relative; ignoring adornments.
  211. // We take the largest of text and content.
  212. int max = int.Max (textSize, subviewsSize);
  213. // And, if min: is set, it wins if larger
  214. max = int.Max (max, autoMin);
  215. // Factor in adornments
  216. Thickness thickness = us.GetAdornmentsThickness ();
  217. max += dimension switch
  218. {
  219. Dimension.Width => thickness.Horizontal,
  220. Dimension.Height => thickness.Vertical,
  221. Dimension.None => 0,
  222. _ => throw new ArgumentOutOfRangeException (nameof (dimension), dimension, null)
  223. };
  224. return int.Min (max, autoMax);
  225. }
  226. internal int CalculateMinDimension (View us, Dimension dimension)
  227. {
  228. int min = dimension == Dimension.Width ? us.Frame.Width : us.Frame.Height;
  229. return min;
  230. }
  231. internal override bool ReferencesOtherViews ()
  232. {
  233. // BUGBUG: This is not correct. _contentSize may be null.
  234. return false; //_style.HasFlag (DimAutoStyle.Content);
  235. }
  236. /// <inheritdoc/>
  237. public override bool Equals (object? other)
  238. {
  239. if (other is not DimAuto auto)
  240. {
  241. return false;
  242. }
  243. return auto.MinimumContentDim == MinimumContentDim &&
  244. auto.MaximumContentDim == MaximumContentDim &&
  245. auto.Style == Style;
  246. }
  247. /// <inheritdoc/>
  248. public override int GetHashCode ()
  249. {
  250. return HashCode.Combine (MinimumContentDim, MaximumContentDim, Style);
  251. }
  252. }