DimAuto.cs 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343
  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) ?? int.MaxValue;
  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 (v => v.X is not PosAnchorEnd
  89. && v.X is not PosAlign
  90. // && v.X is not PosCenter
  91. && v.Width is not DimAuto
  92. && v.Width is not DimFill).ToList ();
  93. }
  94. else
  95. {
  96. subviews = includedSubviews.Where (v => v.Y is not PosAnchorEnd
  97. && v.Y is not PosAlign
  98. // && v.Y is not PosCenter
  99. && v.Height is not DimAuto
  100. && v.Height is not DimFill).ToList ();
  101. }
  102. for (var i = 0; i < subviews.Count; i++)
  103. {
  104. View v = subviews [i];
  105. int size = dimension == Dimension.Width ? v.Frame.X + v.Frame.Width : v.Frame.Y + v.Frame.Height;
  106. if (size > subviewsSize)
  107. {
  108. // BUGBUG: Should we break here? Or choose min/max?
  109. subviewsSize = size;
  110. }
  111. }
  112. #endregion Not Anchored and Are Not Dependent
  113. #region Anchored
  114. // Now, handle subviews that are anchored to the end
  115. // [x] PosAnchorEnd
  116. if (dimension == Dimension.Width)
  117. {
  118. subviews = includedSubviews.Where (v => v.X is PosAnchorEnd).ToList ();
  119. }
  120. else
  121. {
  122. subviews = includedSubviews.Where (v => v.Y is PosAnchorEnd).ToList ();
  123. }
  124. int maxAnchorEnd = 0;
  125. for (var i = 0; i < subviews.Count; i++)
  126. {
  127. View v = subviews [i];
  128. maxAnchorEnd = dimension == Dimension.Width ? v.Frame.Width : v.Frame.Height;
  129. }
  130. subviewsSize += maxAnchorEnd;
  131. #endregion Anchored
  132. //#region Aligned
  133. //// Now, handle subviews that are anchored to the end
  134. //// [x] PosAnchorEnd
  135. //int maxAlign = 0;
  136. //if (dimension == Dimension.Width)
  137. //{
  138. // // Use Linq to get a list of distinct GroupIds from the subviews
  139. // List<int> groupIds = includedSubviews.Select (v => v.X is PosAlign posAlign ? posAlign.GroupId : -1).Distinct ().ToList ();
  140. // foreach (var groupId in groupIds)
  141. // {
  142. // List<int> dimensionsList = new ();
  143. // // PERF: If this proves a perf issue, consider caching a ref to this list in each item
  144. // List<PosAlign?> posAlignsInGroup = includedSubviews.Where (
  145. // v =>
  146. // {
  147. // return dimension switch
  148. // {
  149. // Dimension.Width when v.X is PosAlign alignX => alignX.GroupId == groupId,
  150. // Dimension.Height when v.Y is PosAlign alignY => alignY.GroupId == groupId,
  151. // _ => false
  152. // };
  153. // })
  154. // .Select (v => dimension == Dimension.Width ? v.X as PosAlign : v.Y as PosAlign)
  155. // .ToList ();
  156. // if (posAlignsInGroup.Count == 0)
  157. // {
  158. // continue;
  159. // }
  160. // maxAlign = PosAlign.CalculateMinDimension (groupId, includedSubviews, dimension);
  161. // }
  162. //}
  163. //else
  164. //{
  165. // subviews = includedSubviews.Where (v => v.Y is PosAlign).ToList ();
  166. //}
  167. //subviewsSize = int.Max (subviewsSize, maxAlign);
  168. //#endregion Aligned
  169. #region Auto
  170. if (dimension == Dimension.Width)
  171. {
  172. subviews = includedSubviews.Where (v => v.Width is DimAuto).ToList ();
  173. }
  174. else
  175. {
  176. subviews = includedSubviews.Where (v => v.Height is DimAuto).ToList ();
  177. }
  178. int maxAuto = 0;
  179. for (var i = 0; i < subviews.Count; i++)
  180. {
  181. View v = subviews [i];
  182. //if (dimension == Dimension.Width)
  183. //{
  184. // v.SetRelativeLayout (new Size (autoMax - subviewsSize, 0));
  185. //}
  186. //else
  187. //{
  188. // v.SetRelativeLayout (new Size (0, autoMax - subviewsSize));
  189. //}
  190. maxAuto = dimension == Dimension.Width ? v.Frame.X + v.Frame.Width : v.Frame.Y + v.Frame.Height;
  191. if (maxAuto > subviewsSize)
  192. {
  193. // BUGBUG: Should we break here? Or choose min/max?
  194. subviewsSize = maxAuto;
  195. }
  196. }
  197. // subviewsSize += maxAuto;
  198. #endregion Auto
  199. //#region Center
  200. //// Now, handle subviews that are Centered
  201. //if (dimension == Dimension.Width)
  202. //{
  203. // subviews = us.Subviews.Where (v => v.X is PosCenter).ToList ();
  204. //}
  205. //else
  206. //{
  207. // subviews = us.Subviews.Where (v => v.Y is PosCenter).ToList ();
  208. //}
  209. //int maxCenter = 0;
  210. //for (var i = 0; i < subviews.Count; i++)
  211. //{
  212. // View v = subviews [i];
  213. // maxCenter = dimension == Dimension.Width ? v.Frame.Width : v.Frame.Height;
  214. //}
  215. //subviewsSize += maxCenter;
  216. //#endregion Center
  217. #region Are Dependent
  218. // Now, go back to those that are dependent on content size
  219. // [x] DimFill
  220. // [ ] DimPercent
  221. if (dimension == Dimension.Width)
  222. {
  223. subviews = includedSubviews.Where (v => v.Width is DimFill).ToList ();
  224. }
  225. else
  226. {
  227. subviews = includedSubviews.Where (v => v.Height is DimFill).ToList ();
  228. }
  229. int maxFill = 0;
  230. for (var i = 0; i < subviews.Count; i++)
  231. {
  232. View v = subviews [i];
  233. if (autoMax == int.MaxValue)
  234. {
  235. autoMax = superviewContentSize;
  236. }
  237. if (dimension == Dimension.Width)
  238. {
  239. v.SetRelativeLayout (new Size (autoMax - subviewsSize, 0));
  240. }
  241. else
  242. {
  243. v.SetRelativeLayout (new Size (0, autoMax - subviewsSize));
  244. }
  245. maxFill = dimension == Dimension.Width ? v.Frame.Width : v.Frame.Height;
  246. }
  247. subviewsSize += maxFill;
  248. #endregion Are Dependent
  249. }
  250. }
  251. // All sizes here are content-relative; ignoring adornments.
  252. // We take the largest of text and content.
  253. int max = int.Max (textSize, subviewsSize);
  254. // And, if min: is set, it wins if larger
  255. max = int.Max (max, autoMin);
  256. // And, if max: is set, it wins if smaller
  257. max = int.Min (max, autoMax);
  258. // Factor in adornments
  259. Thickness thickness = us.GetAdornmentsThickness ();
  260. max += dimension switch
  261. {
  262. Dimension.Width => thickness.Horizontal,
  263. Dimension.Height => thickness.Vertical,
  264. Dimension.None => 0,
  265. _ => throw new ArgumentOutOfRangeException (nameof (dimension), dimension, null)
  266. };
  267. return max;
  268. }
  269. internal override bool ReferencesOtherViews ()
  270. {
  271. // BUGBUG: This is not correct. _contentSize may be null.
  272. return false; //_style.HasFlag (DimAutoStyle.Content);
  273. }
  274. /// <inheritdoc/>
  275. public override bool Equals (object? other)
  276. {
  277. if (other is not DimAuto auto)
  278. {
  279. return false;
  280. }
  281. return auto.MinimumContentDim == MinimumContentDim &&
  282. auto.MaximumContentDim == MaximumContentDim &&
  283. auto.Style == Style;
  284. }
  285. /// <inheritdoc/>
  286. public override int GetHashCode ()
  287. {
  288. return HashCode.Combine (MinimumContentDim, MaximumContentDim, Style);
  289. }
  290. }