PosDim.cs 45 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094
  1. using System.Diagnostics;
  2. namespace Terminal.Gui;
  3. /// <summary>
  4. /// Describes the position of a <see cref="View"/> which can be an absolute value, a percentage, centered, or
  5. /// relative to the ending dimension. Integer values are implicitly convertible to an absolute <see cref="Pos"/>. These
  6. /// objects are created using the static methods Percent, AnchorEnd, and Center. The <see cref="Pos"/> objects can be
  7. /// combined with the addition and subtraction operators.
  8. /// </summary>
  9. /// <remarks>
  10. /// <para>Use the <see cref="Pos"/> objects on the X or Y properties of a view to control the position.</para>
  11. /// <para>
  12. /// These can be used to set the absolute position, when merely assigning an integer value (via the implicit
  13. /// integer to <see cref="Pos"/> conversion), and they can be combined to produce more useful layouts, like:
  14. /// Pos.Center - 3, which would shift the position of the <see cref="View"/> 3 characters to the left after
  15. /// centering for example.
  16. /// </para>
  17. /// <para>
  18. /// Reference coordinates of another view by using the methods Left(View), Right(View), Bottom(View), Top(View).
  19. /// The X(View) and Y(View) are aliases to Left(View) and Top(View) respectively.
  20. /// </para>
  21. /// <para>
  22. /// <list type="table">
  23. /// <listheader>
  24. /// <term>Pos Object</term> <description>Description</description>
  25. /// </listheader>
  26. /// <item>
  27. /// <term>
  28. /// <see cref="Pos.Function(Func{int})"/>
  29. /// </term>
  30. /// <description>
  31. /// Creates a <see cref="Pos"/> object that computes the position by executing the provided
  32. /// function. The function will be called every time the position is needed.
  33. /// </description>
  34. /// </item>
  35. /// <item>
  36. /// <term>
  37. /// <see cref="Pos.Percent(float)"/>
  38. /// </term>
  39. /// <description>
  40. /// Creates a <see cref="Pos"/> object that is a percentage of the width or height of the
  41. /// SuperView.
  42. /// </description>
  43. /// </item>
  44. /// <item>
  45. /// <term>
  46. /// <see cref="Pos.AnchorEnd()"/>
  47. /// </term>
  48. /// <description>
  49. /// Creates a <see cref="Pos"/> object that is anchored to the end (right side or bottom) of
  50. /// the dimension, useful to flush the layout from the right or bottom.
  51. /// </description>
  52. /// </item>
  53. /// <item>
  54. /// <term>
  55. /// <see cref="Pos.Center"/>
  56. /// </term>
  57. /// <description>Creates a <see cref="Pos"/> object that can be used to center the <see cref="View"/>.</description>
  58. /// </item>
  59. /// <item>
  60. /// <term>
  61. /// <see cref="Pos.At(int)"/>
  62. /// </term>
  63. /// <description>
  64. /// Creates a <see cref="Pos"/> object that is an absolute position based on the specified
  65. /// integer value.
  66. /// </description>
  67. /// </item>
  68. /// <item>
  69. /// <term>
  70. /// <see cref="Pos.Left"/>
  71. /// </term>
  72. /// <description>
  73. /// Creates a <see cref="Pos"/> object that tracks the Left (X) position of the specified
  74. /// <see cref="View"/>.
  75. /// </description>
  76. /// </item>
  77. /// <item>
  78. /// <term>
  79. /// <see cref="Pos.X(View)"/>
  80. /// </term>
  81. /// <description>
  82. /// Creates a <see cref="Pos"/> object that tracks the Left (X) position of the specified
  83. /// <see cref="View"/>.
  84. /// </description>
  85. /// </item>
  86. /// <item>
  87. /// <term>
  88. /// <see cref="Pos.Top(View)"/>
  89. /// </term>
  90. /// <description>
  91. /// Creates a <see cref="Pos"/> object that tracks the Top (Y) position of the specified
  92. /// <see cref="View"/>.
  93. /// </description>
  94. /// </item>
  95. /// <item>
  96. /// <term>
  97. /// <see cref="Pos.Y(View)"/>
  98. /// </term>
  99. /// <description>
  100. /// Creates a <see cref="Pos"/> object that tracks the Top (Y) position of the specified
  101. /// <see cref="View"/>.
  102. /// </description>
  103. /// </item>
  104. /// <item>
  105. /// <term>
  106. /// <see cref="Pos.Right(View)"/>
  107. /// </term>
  108. /// <description>
  109. /// Creates a <see cref="Pos"/> object that tracks the Right (X+Width) coordinate of the
  110. /// specified <see cref="View"/>.
  111. /// </description>
  112. /// </item>
  113. /// <item>
  114. /// <term>
  115. /// <see cref="Pos.Bottom(View)"/>
  116. /// </term>
  117. /// <description>
  118. /// Creates a <see cref="Pos"/> object that tracks the Bottom (Y+Height) coordinate of the
  119. /// specified <see cref="View"/>
  120. /// </description>
  121. /// </item>
  122. /// </list>
  123. /// </para>
  124. /// </remarks>
  125. public class Pos
  126. {
  127. /// <summary>
  128. /// Creates a <see cref="Pos"/> object that is anchored to the end (right side or
  129. /// bottom) of the SuperView, minus the respective dimension of the View. This is equivalent to using
  130. /// <see cref="Pos.AnchorEnd(int)"/>,
  131. /// with an offset equivalent to the View's respective dimension.
  132. /// </summary>
  133. /// <returns>The <see cref="Pos"/> object anchored to the end (the bottom or the right side) minus the View's dimension.</returns>
  134. /// <example>
  135. /// This sample shows how align a <see cref="Button"/> to the bottom-right the SuperView.
  136. /// <code>
  137. /// anchorButton.X = Pos.AnchorEnd ();
  138. /// anchorButton.Y = Pos.AnchorEnd ();
  139. /// </code>
  140. /// </example>
  141. public static Pos AnchorEnd () { return new PosAnchorEnd (); }
  142. /// <summary>
  143. /// Creates a <see cref="Pos"/> object that is anchored to the end (right side or bottom) of the SuperView,
  144. /// useful to flush the layout from the right or bottom. See also <see cref="Pos.AnchorEnd()"/>, which uses the view
  145. /// dimension to ensure the view is fully visible.
  146. /// </summary>
  147. /// <returns>The <see cref="Pos"/> object anchored to the end (the bottom or the right side).</returns>
  148. /// <param name="offset">The view will be shifted left or up by the amount specified.</param>
  149. /// <example>
  150. /// This sample shows how align a 10 column wide <see cref="Button"/> to the bottom-right the SuperView.
  151. /// <code>
  152. /// anchorButton.X = Pos.AnchorEnd (10);
  153. /// anchorButton.Y = 1
  154. /// </code>
  155. /// </example>
  156. public static Pos AnchorEnd (int offset)
  157. {
  158. if (offset < 0)
  159. {
  160. throw new ArgumentException (@"Must be positive", nameof (offset));
  161. }
  162. return new PosAnchorEnd (offset);
  163. }
  164. /// <summary>Creates a <see cref="Pos"/> object that is an absolute position based on the specified integer value.</summary>
  165. /// <returns>The Absolute <see cref="Pos"/>.</returns>
  166. /// <param name="n">The value to convert to the <see cref="Pos"/>.</param>
  167. public static Pos At (int n) { return new PosAbsolute (n); }
  168. /// <summary>Creates a <see cref="Pos"/> object that can be used to center the <see cref="View"/>.</summary>
  169. /// <returns>The center Pos.</returns>
  170. /// <example>
  171. /// This creates a <see cref="TextView"/> centered horizontally, is 50% of the way down, is 30% the height, and
  172. /// is 80% the width of the <see cref="View"/> it added to.
  173. /// <code>
  174. /// var textView = new TextView () {
  175. /// X = Pos.Center (),
  176. /// Y = Pos.Percent (50),
  177. /// Width = Dim.Percent (80),
  178. /// Height = Dim.Percent (30),
  179. /// };
  180. /// </code>
  181. /// </example>
  182. public static Pos Center () { return new PosCenter (); }
  183. /// <summary>Determines whether the specified object is equal to the current object.</summary>
  184. /// <param name="other">The object to compare with the current object. </param>
  185. /// <returns>
  186. /// <see langword="true"/> if the specified object is equal to the current object; otherwise,
  187. /// <see langword="false"/>.
  188. /// </returns>
  189. public override bool Equals (object other) { return other is Pos abs && abs == this; }
  190. /// <summary>
  191. /// Creates a <see cref="Pos"/> object that computes the position by executing the provided function. The function
  192. /// will be called every time the position is needed.
  193. /// </summary>
  194. /// <param name="function">The function to be executed.</param>
  195. /// <returns>The <see cref="Pos"/> returned from the function.</returns>
  196. public static Pos Function (Func<int> function) { return new PosFunc (function); }
  197. /// <summary>
  198. /// Creates a <see cref="Pos"/> object that justifies a set of views according to the specified justification.
  199. /// </summary>
  200. /// <param name="views"></param>
  201. /// <param name="justification"></param>
  202. /// <returns></returns>
  203. public static Pos Justify ( Justification justification) { return new PosJustify (justification); }
  204. /// <summary>Serves as the default hash function. </summary>
  205. /// <returns>A hash code for the current object.</returns>
  206. public override int GetHashCode () { return Anchor (0).GetHashCode (); }
  207. /// <summary>Adds a <see cref="Terminal.Gui.Pos"/> to a <see cref="Terminal.Gui.Pos"/>, yielding a new <see cref="Pos"/>.</summary>
  208. /// <param name="left">The first <see cref="Terminal.Gui.Pos"/> to add.</param>
  209. /// <param name="right">The second <see cref="Terminal.Gui.Pos"/> to add.</param>
  210. /// <returns>The <see cref="Pos"/> that is the sum of the values of <c>left</c> and <c>right</c>.</returns>
  211. public static Pos operator + (Pos left, Pos right)
  212. {
  213. if (left is PosAbsolute && right is PosAbsolute)
  214. {
  215. return new PosAbsolute (left.Anchor (0) + right.Anchor (0));
  216. }
  217. var newPos = new PosCombine (true, left, right);
  218. if (left is PosView view)
  219. {
  220. view.Target.SetNeedsLayout ();
  221. }
  222. return newPos;
  223. }
  224. /// <summary>Creates an Absolute <see cref="Pos"/> from the specified integer value.</summary>
  225. /// <returns>The Absolute <see cref="Pos"/>.</returns>
  226. /// <param name="n">The value to convert to the <see cref="Pos"/> .</param>
  227. public static implicit operator Pos (int n) { return new PosAbsolute (n); }
  228. /// <summary>
  229. /// Subtracts a <see cref="Terminal.Gui.Pos"/> from a <see cref="Terminal.Gui.Pos"/>, yielding a new
  230. /// <see cref="Pos"/>.
  231. /// </summary>
  232. /// <param name="left">The <see cref="Terminal.Gui.Pos"/> to subtract from (the minuend).</param>
  233. /// <param name="right">The <see cref="Terminal.Gui.Pos"/> to subtract (the subtrahend).</param>
  234. /// <returns>The <see cref="Pos"/> that is the <c>left</c> minus <c>right</c>.</returns>
  235. public static Pos operator - (Pos left, Pos right)
  236. {
  237. if (left is PosAbsolute && right is PosAbsolute)
  238. {
  239. return new PosAbsolute (left.Anchor (0) - right.Anchor (0));
  240. }
  241. var newPos = new PosCombine (false, left, right);
  242. if (left is PosView view)
  243. {
  244. view.Target.SetNeedsLayout ();
  245. }
  246. return newPos;
  247. }
  248. /// <summary>Creates a percentage <see cref="Pos"/> object</summary>
  249. /// <returns>The percent <see cref="Pos"/> object.</returns>
  250. /// <param name="percent">A value between 0 and 100 representing the percentage.</param>
  251. /// <example>
  252. /// This creates a <see cref="TextField"/> centered horizontally, is 50% of the way down, is 30% the height, and
  253. /// is 80% the width of the <see cref="View"/> it added to.
  254. /// <code>
  255. /// var textView = new TextField {
  256. /// X = Pos.Center (),
  257. /// Y = Pos.Percent (50),
  258. /// Width = Dim.Percent (80),
  259. /// Height = Dim.Percent (30),
  260. /// };
  261. /// </code>
  262. /// </example>
  263. public static Pos Percent (float percent)
  264. {
  265. if (percent is < 0 or > 100)
  266. {
  267. throw new ArgumentException ("Percent value must be between 0 and 100.");
  268. }
  269. return new PosFactor (percent / 100);
  270. }
  271. /// <summary>Creates a <see cref="Pos"/> object that tracks the Top (Y) position of the specified <see cref="View"/>.</summary>
  272. /// <returns>The <see cref="Pos"/> that depends on the other view.</returns>
  273. /// <param name="view">The <see cref="View"/> that will be tracked.</param>
  274. public static Pos Top (View view) { return new PosView (view, Side.Top); }
  275. /// <summary>Creates a <see cref="Pos"/> object that tracks the Top (Y) position of the specified <see cref="View"/>.</summary>
  276. /// <returns>The <see cref="Pos"/> that depends on the other view.</returns>
  277. /// <param name="view">The <see cref="View"/> that will be tracked.</param>
  278. public static Pos Y (View view) { return new PosView (view, Side.Top); }
  279. /// <summary>Creates a <see cref="Pos"/> object that tracks the Left (X) position of the specified <see cref="View"/>.</summary>
  280. /// <returns>The <see cref="Pos"/> that depends on the other view.</returns>
  281. /// <param name="view">The <see cref="View"/> that will be tracked.</param>
  282. public static Pos Left (View view) { return new PosView (view, Side.Left); }
  283. /// <summary>Creates a <see cref="Pos"/> object that tracks the Left (X) position of the specified <see cref="View"/>.</summary>
  284. /// <returns>The <see cref="Pos"/> that depends on the other view.</returns>
  285. /// <param name="view">The <see cref="View"/> that will be tracked.</param>
  286. public static Pos X (View view) { return new PosView (view, Side.Left); }
  287. /// <summary>
  288. /// Creates a <see cref="Pos"/> object that tracks the Bottom (Y+Height) coordinate of the specified
  289. /// <see cref="View"/>
  290. /// </summary>
  291. /// <returns>The <see cref="Pos"/> that depends on the other view.</returns>
  292. /// <param name="view">The <see cref="View"/> that will be tracked.</param>
  293. public static Pos Bottom (View view) { return new PosView (view, Side.Bottom); }
  294. /// <summary>
  295. /// Creates a <see cref="Pos"/> object that tracks the Right (X+Width) coordinate of the specified
  296. /// <see cref="View"/>.
  297. /// </summary>
  298. /// <returns>The <see cref="Pos"/> that depends on the other view.</returns>
  299. /// <param name="view">The <see cref="View"/> that will be tracked.</param>
  300. public static Pos Right (View view) { return new PosView (view, Side.Right); }
  301. /// <summary>
  302. /// Gets a position that is anchored to a certain point in the layout. This method is typically used
  303. /// internally by the layout system to determine where a View should be positioned.
  304. /// </summary>
  305. /// <param name="width">The width of the area where the View is being positioned (Superview.ContentSize).</param>
  306. /// <returns>
  307. /// An integer representing the calculated position. The way this position is calculated depends on the specific
  308. /// subclass of Pos that is used. For example, PosAbsolute returns a fixed position, PosAnchorEnd returns a
  309. /// position that is anchored to the end of the layout, and so on.
  310. /// </returns>
  311. internal virtual int Anchor (int width) { return 0; }
  312. /// <summary>
  313. /// Calculates and returns the position of a <see cref="View"/> object. It takes into account the dimension of the
  314. /// superview and the dimension of the view itself.
  315. /// </summary>
  316. /// <param name="superviewDimension">
  317. /// The dimension of the superview. This could be the width for x-coordinate calculation or the
  318. /// height for y-coordinate calculation.
  319. /// </param>
  320. /// <param name="dim">The dimension of the View. It could be the current width or height.</param>
  321. /// <param name="us">The View that holds this Pos object.</param>
  322. /// <param name="dimension">Width or Height</param>
  323. /// <returns>
  324. /// The calculated position of the View. The way this position is calculated depends on the specific subclass of Pos
  325. /// that
  326. /// is used.
  327. /// </returns>
  328. internal virtual int Calculate (int superviewDimension, Dim dim, View us, Dim.Dimension dimension)
  329. {
  330. return Anchor (superviewDimension);
  331. }
  332. internal class PosAbsolute (int n) : Pos
  333. {
  334. private readonly int _n = n;
  335. public override bool Equals (object other) { return other is PosAbsolute abs && abs._n == _n; }
  336. public override int GetHashCode () { return _n.GetHashCode (); }
  337. public override string ToString () { return $"Absolute({_n})"; }
  338. internal override int Anchor (int width) { return _n; }
  339. }
  340. internal class PosAnchorEnd : Pos
  341. {
  342. private readonly int _offset;
  343. public PosAnchorEnd () { UseDimForOffset = true; }
  344. public PosAnchorEnd (int offset) { _offset = offset; }
  345. public override bool Equals (object other) { return other is PosAnchorEnd anchorEnd && anchorEnd._offset == _offset; }
  346. public override int GetHashCode () { return _offset.GetHashCode (); }
  347. /// <summary>
  348. /// If true, the offset is the width of the view, if false, the offset is the offset value.
  349. /// </summary>
  350. internal bool UseDimForOffset { get; set; }
  351. public override string ToString () { return UseDimForOffset ? "AnchorEnd()" : $"AnchorEnd({_offset})"; }
  352. internal override int Anchor (int width)
  353. {
  354. if (UseDimForOffset)
  355. {
  356. return width;
  357. }
  358. return width - _offset;
  359. }
  360. internal override int Calculate (int superviewDimension, Dim dim, View us, Dim.Dimension dimension)
  361. {
  362. int newLocation = Anchor (superviewDimension);
  363. if (UseDimForOffset)
  364. {
  365. newLocation -= dim.Anchor (superviewDimension);
  366. }
  367. return newLocation;
  368. }
  369. }
  370. internal class PosCenter : Pos
  371. {
  372. public override string ToString () { return "Center"; }
  373. internal override int Anchor (int width) { return width / 2; }
  374. internal override int Calculate (int superviewDimension, Dim dim, View us, Dim.Dimension dimension)
  375. {
  376. int newDimension = Math.Max (dim.Calculate (0, superviewDimension, us, dimension), 0);
  377. return Anchor (superviewDimension - newDimension);
  378. }
  379. }
  380. internal class PosCombine (bool add, Pos left, Pos right) : Pos
  381. {
  382. internal bool _add = add;
  383. internal Pos _left = left, _right = right;
  384. public override string ToString () { return $"Combine({_left}{(_add ? '+' : '-')}{_right})"; }
  385. internal override int Anchor (int width)
  386. {
  387. int la = _left.Anchor (width);
  388. int ra = _right.Anchor (width);
  389. if (_add)
  390. {
  391. return la + ra;
  392. }
  393. return la - ra;
  394. }
  395. internal override int Calculate (int superviewDimension, Dim dim, View us, Dim.Dimension dimension)
  396. {
  397. int newDimension = dim.Calculate (0, superviewDimension, us, dimension);
  398. int left = _left.Calculate (superviewDimension, dim, us, dimension);
  399. int right = _right.Calculate (superviewDimension, dim, us, dimension);
  400. if (_add)
  401. {
  402. return left + right;
  403. }
  404. return left - right;
  405. }
  406. }
  407. internal class PosFactor (float factor) : Pos
  408. {
  409. private readonly float _factor = factor;
  410. public override bool Equals (object other) { return other is PosFactor f && f._factor == _factor; }
  411. public override int GetHashCode () { return _factor.GetHashCode (); }
  412. public override string ToString () { return $"Factor({_factor})"; }
  413. internal override int Anchor (int width) { return (int)(width * _factor); }
  414. }
  415. /// <summary>
  416. /// Enables justification of a set of views.
  417. /// </summary>
  418. public class PosJustify : Pos
  419. {
  420. internal readonly Justifier _justifier;
  421. internal int? _location;
  422. /// <summary>
  423. /// Enables justification of a set of views.
  424. /// </summary>
  425. /// <param name="views">The set of views to justify according to <paramref name="justification"/>.</param>
  426. /// <param name="justification"></param>
  427. public PosJustify (Justification justification)
  428. {
  429. _justifier = new ()
  430. {
  431. PutSpaceBetweenItems = false,
  432. Justification = justification,
  433. };
  434. }
  435. public override bool Equals (object other)
  436. {
  437. return other is PosJustify justify && justify._justifier == _justifier;
  438. }
  439. public override int GetHashCode () { return _justifier.GetHashCode (); }
  440. public override string ToString ()
  441. {
  442. return $"Justify(justification={_justifier.Justification})";
  443. }
  444. internal override int Anchor (int width)
  445. {
  446. return _location ?? 0 - width;
  447. }
  448. internal override int Calculate (int superviewDimension, Dim dim, View us, Dim.Dimension dimension)
  449. {
  450. if (_location.HasValue)
  451. {
  452. return _location.Value;
  453. }
  454. return 0;
  455. }
  456. }
  457. // Helper class to provide dynamic value by the execution of a function that returns an integer.
  458. internal class PosFunc (Func<int> n) : Pos
  459. {
  460. private readonly Func<int> _function = n;
  461. public override bool Equals (object other) { return other is PosFunc f && f._function () == _function (); }
  462. public override int GetHashCode () { return _function.GetHashCode (); }
  463. public override string ToString () { return $"PosFunc({_function ()})"; }
  464. internal override int Anchor (int width) { return _function (); }
  465. }
  466. /// <summary>
  467. /// Describes which side of the view to use for the position.
  468. /// </summary>
  469. public enum Side
  470. {
  471. /// <summary>
  472. /// The left (X) side of the view.
  473. /// </summary>
  474. Left = 0,
  475. /// <summary>
  476. /// The top (Y) side of the view.
  477. /// </summary>
  478. Top = 1,
  479. /// <summary>
  480. /// The right (X + Width) side of the view.
  481. /// </summary>
  482. Right = 2,
  483. /// <summary>
  484. /// The bottom (Y + Height) side of the view.
  485. /// </summary>
  486. Bottom = 3
  487. }
  488. internal class PosView (View view, Side side) : Pos
  489. {
  490. public readonly View Target = view;
  491. public override bool Equals (object other) { return other is PosView abs && abs.Target == Target; }
  492. public override int GetHashCode () { return Target.GetHashCode (); }
  493. public override string ToString ()
  494. {
  495. string sideString = side switch
  496. {
  497. Side.Left => "left",
  498. Side.Top => "top",
  499. Side.Right => "right",
  500. Side.Bottom => "bottom",
  501. _ => "unknown"
  502. };
  503. if (Target == null)
  504. {
  505. throw new NullReferenceException (nameof (Target));
  506. }
  507. return $"View(side={sideString},target={Target})";
  508. }
  509. internal override int Anchor (int width)
  510. {
  511. return side switch
  512. {
  513. Side.Left => Target.Frame.X,
  514. Side.Top => Target.Frame.Y,
  515. Side.Right => Target.Frame.Right,
  516. Side.Bottom => Target.Frame.Bottom,
  517. _ => 0
  518. };
  519. }
  520. }
  521. }
  522. /// <summary>
  523. /// <para>
  524. /// A Dim object describes the dimensions of a <see cref="View"/>. Dim is the type of the
  525. /// <see cref="View.Width"/> and <see cref="View.Height"/> properties of <see cref="View"/>. Dim objects enable
  526. /// Computed Layout (see <see cref="LayoutStyle.Computed"/>) to automatically manage the dimensions of a view.
  527. /// </para>
  528. /// <para>
  529. /// Integer values are implicitly convertible to an absolute <see cref="Dim"/>. These objects are created using
  530. /// the static methods described below. The <see cref="Dim"/> objects can be combined with the addition and
  531. /// subtraction operators.
  532. /// </para>
  533. /// </summary>
  534. /// <remarks>
  535. /// <para>
  536. /// <list type="table">
  537. /// <listheader>
  538. /// <term>Dim Object</term> <description>Description</description>
  539. /// </listheader>
  540. /// <item>
  541. /// <term>
  542. /// <see cref="Dim.Auto"/>
  543. /// </term>
  544. /// <description>
  545. /// Creates a <see cref="Dim"/> object that automatically sizes the view to fit
  546. /// the view's SubViews.
  547. /// </description>
  548. /// </item>
  549. /// <item>
  550. /// <term>
  551. /// <see cref="Dim.Function(Func{int})"/>
  552. /// </term>
  553. /// <description>
  554. /// Creates a <see cref="Dim"/> object that computes the dimension by executing the provided
  555. /// function. The function will be called every time the dimension is needed.
  556. /// </description>
  557. /// </item>
  558. /// <item>
  559. /// <term>
  560. /// <see cref="Dim.Percent(float, bool)"/>
  561. /// </term>
  562. /// <description>
  563. /// Creates a <see cref="Dim"/> object that is a percentage of the width or height of the
  564. /// SuperView.
  565. /// </description>
  566. /// </item>
  567. /// <item>
  568. /// <term>
  569. /// <see cref="Dim.Fill(int)"/>
  570. /// </term>
  571. /// <description>
  572. /// Creates a <see cref="Dim"/> object that fills the dimension from the View's X position
  573. /// to the end of the super view's width, leaving the specified number of columns for a margin.
  574. /// </description>
  575. /// </item>
  576. /// <item>
  577. /// <term>
  578. /// <see cref="Dim.Width(View)"/>
  579. /// </term>
  580. /// <description>
  581. /// Creates a <see cref="Dim"/> object that tracks the Width of the specified
  582. /// <see cref="View"/>.
  583. /// </description>
  584. /// </item>
  585. /// <item>
  586. /// <term>
  587. /// <see cref="Dim.Height(View)"/>
  588. /// </term>
  589. /// <description>
  590. /// Creates a <see cref="Dim"/> object that tracks the Height of the specified
  591. /// <see cref="View"/>.
  592. /// </description>
  593. /// </item>
  594. /// </list>
  595. /// </para>
  596. /// <para></para>
  597. /// </remarks>
  598. public class Dim
  599. {
  600. /// <summary>
  601. /// Specifies how <see cref="DimAuto"/> will compute the dimension.
  602. /// </summary>
  603. public enum DimAutoStyle
  604. {
  605. /// <summary>
  606. /// The dimension will be computed using both the view's <see cref="View.Text"/> and
  607. /// <see cref="View.Subviews"/> (whichever is larger).
  608. /// </summary>
  609. Auto,
  610. /// <summary>
  611. /// The Subview in <see cref="View.Subviews"/> with the largest corresponding position plus dimension
  612. /// will determine the dimension.
  613. /// The corresponding dimension of the view's <see cref="View.Text"/> will be ignored.
  614. /// </summary>
  615. Subviews,
  616. /// <summary>
  617. /// The corresponding dimension of the view's <see cref="View.Text"/>, formatted using the
  618. /// <see cref="View.TextFormatter"/> settings,
  619. /// will be used to determine the dimension.
  620. /// The corresponding dimensions of the <see cref="View.Subviews"/> will be ignored.
  621. /// </summary>
  622. Text
  623. }
  624. /// <summary>
  625. ///
  626. /// </summary>
  627. public enum Dimension
  628. {
  629. /// <summary>
  630. /// No dimension specified.
  631. /// </summary>
  632. None = 0,
  633. /// <summary>
  634. /// The height dimension.
  635. /// </summary>
  636. Height = 1,
  637. /// <summary>
  638. /// The width dimension.
  639. /// </summary>
  640. Width = 2
  641. }
  642. /// <summary>
  643. /// Creates a <see cref="Dim"/> object that automatically sizes the view to fit all of the view's SubViews and/or Text.
  644. /// </summary>
  645. /// <example>
  646. /// This initializes a <see cref="View"/> with two SubViews. The view will be automatically sized to fit the two
  647. /// SubViews.
  648. /// <code>
  649. /// var button = new Button () { Text = "Click Me!", X = 1, Y = 1, Width = 10, Height = 1 };
  650. /// var textField = new TextField { Text = "Type here", X = 1, Y = 2, Width = 20, Height = 1 };
  651. /// var view = new Window () { Title = "MyWindow", X = 0, Y = 0, Width = Dim.Auto (), Height = Dim.Auto () };
  652. /// view.Add (button, textField);
  653. /// </code>
  654. /// </example>
  655. /// <returns>The <see cref="Dim"/> object.</returns>
  656. /// <param name="style">
  657. /// Specifies how <see cref="DimAuto"/> will compute the dimension. The default is <see cref="DimAutoStyle.Auto"/>.
  658. /// </param>
  659. /// <param name="min">Specifies the minimum dimension that view will be automatically sized to.</param>
  660. /// <param name="max">Specifies the maximum dimension that view will be automatically sized to. NOT CURRENTLY SUPPORTED.</param>
  661. public static Dim Auto (DimAutoStyle style = DimAutoStyle.Auto, Dim min = null, Dim max = null)
  662. {
  663. if (max != null)
  664. {
  665. throw new NotImplementedException (@"max is not implemented");
  666. }
  667. return new DimAuto (style, min, max);
  668. }
  669. /// <summary>Determines whether the specified object is equal to the current object.</summary>
  670. /// <param name="other">The object to compare with the current object. </param>
  671. /// <returns>
  672. /// <see langword="true"/> if the specified object is equal to the current object; otherwise,
  673. /// <see langword="false"/>.
  674. /// </returns>
  675. public override bool Equals (object other) { return other is Dim abs && abs == this; }
  676. /// <summary>
  677. /// Creates a <see cref="Dim"/> object that fills the dimension, leaving the specified number of columns for a
  678. /// margin.
  679. /// </summary>
  680. /// <returns>The Fill dimension.</returns>
  681. /// <param name="margin">Margin to use.</param>
  682. public static Dim Fill (int margin = 0) { return new DimFill (margin); }
  683. /// <summary>
  684. /// Creates a function <see cref="Dim"/> object that computes the dimension by executing the provided function.
  685. /// The function will be called every time the dimension is needed.
  686. /// </summary>
  687. /// <param name="function">The function to be executed.</param>
  688. /// <returns>The <see cref="Dim"/> returned from the function.</returns>
  689. public static Dim Function (Func<int> function) { return new DimFunc (function); }
  690. /// <summary>Serves as the default hash function. </summary>
  691. /// <returns>A hash code for the current object.</returns>
  692. public override int GetHashCode () { return Anchor (0).GetHashCode (); }
  693. /// <summary>Creates a <see cref="Dim"/> object that tracks the Height of the specified <see cref="View"/>.</summary>
  694. /// <returns>The height <see cref="Dim"/> of the other <see cref="View"/>.</returns>
  695. /// <param name="view">The view that will be tracked.</param>
  696. public static Dim Height (View view) { return new DimView (view, Dimension.Height); }
  697. /// <summary>Adds a <see cref="Dim"/> to a <see cref="Dim"/>, yielding a new <see cref="Dim"/>.</summary>
  698. /// <param name="left">The first <see cref="Dim"/> to add.</param>
  699. /// <param name="right">The second <see cref="Dim"/> to add.</param>
  700. /// <returns>The <see cref="Dim"/> that is the sum of the values of <c>left</c> and <c>right</c>.</returns>
  701. public static Dim operator + (Dim left, Dim right)
  702. {
  703. if (left is DimAbsolute && right is DimAbsolute)
  704. {
  705. return new DimAbsolute (left.Anchor (0) + right.Anchor (0));
  706. }
  707. var newDim = new DimCombine (true, left, right);
  708. (left as DimView)?.Target.SetNeedsLayout ();
  709. return newDim;
  710. }
  711. /// <summary>Creates an Absolute <see cref="Dim"/> from the specified integer value.</summary>
  712. /// <returns>The Absolute <see cref="Dim"/>.</returns>
  713. /// <param name="n">The value to convert to the pos.</param>
  714. public static implicit operator Dim (int n) { return new DimAbsolute (n); }
  715. /// <summary>
  716. /// Subtracts a <see cref="Dim"/> from a <see cref="Dim"/>, yielding a new
  717. /// <see cref="Dim"/>.
  718. /// </summary>
  719. /// <param name="left">The <see cref="Dim"/> to subtract from (the minuend).</param>
  720. /// <param name="right">The <see cref="Dim"/> to subtract (the subtrahend).</param>
  721. /// <returns>The <see cref="Dim"/> that is the <c>left</c> minus <c>right</c>.</returns>
  722. public static Dim operator - (Dim left, Dim right)
  723. {
  724. if (left is DimAbsolute && right is DimAbsolute)
  725. {
  726. return new DimAbsolute (left.Anchor (0) - right.Anchor (0));
  727. }
  728. var newDim = new DimCombine (false, left, right);
  729. (left as DimView)?.Target.SetNeedsLayout ();
  730. return newDim;
  731. }
  732. /// <summary>Creates a percentage <see cref="Dim"/> object that is a percentage of the width or height of the SuperView.</summary>
  733. /// <returns>The percent <see cref="Dim"/> object.</returns>
  734. /// <param name="percent">A value between 0 and 100 representing the percentage.</param>
  735. /// <param name="usePosition">
  736. /// If <see langword="true"/> the dimension is computed using the View's position (<see cref="View.X"/> or
  737. /// <see cref="View.Y"/>).
  738. /// If <see langword="false"/> the dimension is computed using the View's <see cref="View.ContentSize"/>.
  739. /// </param>
  740. /// <example>
  741. /// This initializes a <see cref="TextField"/> that will be centered horizontally, is 50% of the way down, is 30% the
  742. /// height,
  743. /// and is 80% the width of the SuperView.
  744. /// <code>
  745. /// var textView = new TextField {
  746. /// X = Pos.Center (),
  747. /// Y = Pos.Percent (50),
  748. /// Width = Dim.Percent (80),
  749. /// Height = Dim.Percent (30),
  750. /// };
  751. /// </code>
  752. /// </example>
  753. public static Dim Percent (float percent, bool usePosition = false)
  754. {
  755. if (percent is < 0 or > 100)
  756. {
  757. throw new ArgumentException ("Percent value must be between 0 and 100");
  758. }
  759. return new DimFactor (percent / 100, usePosition);
  760. }
  761. /// <summary>Creates an Absolute <see cref="Dim"/> from the specified integer value.</summary>
  762. /// <returns>The Absolute <see cref="Dim"/>.</returns>
  763. /// <param name="n">The value to convert to the <see cref="Dim"/>.</param>
  764. public static Dim Sized (int n) { return new DimAbsolute (n); }
  765. /// <summary>Creates a <see cref="Dim"/> object that tracks the Width of the specified <see cref="View"/>.</summary>
  766. /// <returns>The width <see cref="Dim"/> of the other <see cref="View"/>.</returns>
  767. /// <param name="view">The view that will be tracked.</param>
  768. public static Dim Width (View view) { return new DimView (view, Dimension.Width); }
  769. /// <summary>
  770. /// Gets a dimension that is anchored to a certain point in the layout.
  771. /// This method is typically used internally by the layout system to determine the size of a View.
  772. /// </summary>
  773. /// <param name="width">The width of the area where the View is being sized (Superview.ContentSize).</param>
  774. /// <returns>
  775. /// An integer representing the calculated dimension. The way this dimension is calculated depends on the specific
  776. /// subclass of Dim that is used. For example, DimAbsolute returns a fixed dimension, DimFactor returns a
  777. /// dimension that is a certain percentage of the super view's size, and so on.
  778. /// </returns>
  779. internal virtual int Anchor (int width) { return 0; }
  780. /// <summary>
  781. /// Calculates and returns the dimension of a <see cref="View"/> object. It takes into account the location of the
  782. /// <see cref="View"/>, it's SuperView's ContentSize, and whether it should automatically adjust its size based on its content.
  783. /// </summary>
  784. /// <param name="location">
  785. /// The starting point from where the size calculation begins. It could be the left edge for width calculation or the
  786. /// top edge for height calculation.
  787. /// </param>
  788. /// <param name="superviewContentSize">The size of the SuperView's content. It could be width or height.</param>
  789. /// <param name="us">The View that holds this Pos object.</param>
  790. /// <param name="dimension">Width or Height</param>
  791. /// <returns>
  792. /// The calculated size of the View. The way this size is calculated depends on the specific subclass of Dim that
  793. /// is used.
  794. /// </returns>
  795. internal virtual int Calculate (int location, int superviewContentSize, View us, Dimension dimension)
  796. {
  797. return Math.Max (Anchor (superviewContentSize - location), 0);
  798. }
  799. internal class DimAbsolute (int n) : Dim
  800. {
  801. private readonly int _n = n;
  802. public override bool Equals (object other) { return other is DimAbsolute abs && abs._n == _n; }
  803. public override int GetHashCode () { return _n.GetHashCode (); }
  804. public override string ToString () { return $"Absolute({_n})"; }
  805. internal override int Anchor (int width) { return _n; }
  806. internal override int Calculate (int location, int superviewContentSize, View us, Dimension dimension)
  807. {
  808. // DimAbsolute.Anchor (int width) ignores width and returns n
  809. return Math.Max (Anchor (0), 0);
  810. }
  811. }
  812. internal class DimAuto (DimAutoStyle style, Dim min, Dim max) : Dim
  813. {
  814. internal readonly Dim _max = max;
  815. internal readonly Dim _min = min;
  816. internal readonly DimAutoStyle _style = style;
  817. internal int Size;
  818. public override bool Equals (object other) { return other is DimAuto auto && auto._min == _min && auto._max == _max && auto._style == _style; }
  819. public override int GetHashCode () { return HashCode.Combine (base.GetHashCode (), _min, _max, _style); }
  820. public override string ToString () { return $"Auto({_style},{_min},{_max})"; }
  821. internal override int Calculate (int location, int superviewContentSize, View us, Dimension dimension)
  822. {
  823. if (us == null)
  824. {
  825. return _max?.Anchor (0) ?? 0;
  826. }
  827. var textSize = 0;
  828. var subviewsSize = 0;
  829. int autoMin = _min?.Anchor (superviewContentSize) ?? 0;
  830. if (superviewContentSize < autoMin)
  831. {
  832. Debug.WriteLine ($"WARNING: DimAuto specifies a min size ({autoMin}), but the SuperView's bounds are smaller ({superviewContentSize}).");
  833. return superviewContentSize;
  834. }
  835. if (_style is Dim.DimAutoStyle.Text or Dim.DimAutoStyle.Auto)
  836. {
  837. textSize = int.Max (autoMin, dimension == Dimension.Width ? us.TextFormatter.Size.Width : us.TextFormatter.Size.Height);
  838. }
  839. if (_style is Dim.DimAutoStyle.Subviews or Dim.DimAutoStyle.Auto)
  840. {
  841. subviewsSize = us.Subviews.Count == 0
  842. ? 0
  843. : us.Subviews
  844. .Where (v => dimension == Dimension.Width ? v.X is not Pos.PosAnchorEnd : v.Y is not Pos.PosAnchorEnd)
  845. .Max (v => dimension == Dimension.Width ? v.Frame.X + v.Frame.Width : v.Frame.Y + v.Frame.Height);
  846. }
  847. int max = int.Max (textSize, subviewsSize);
  848. Thickness thickness = us.GetAdornmentsThickness ();
  849. if (dimension == Dimension.Width)
  850. {
  851. max += thickness.Horizontal;
  852. }
  853. else
  854. {
  855. max += thickness.Vertical;
  856. }
  857. max = int.Max (max, autoMin);
  858. return int.Min (max, _max?.Anchor (superviewContentSize) ?? superviewContentSize);
  859. }
  860. }
  861. internal class DimCombine (bool add, Dim left, Dim right) : Dim
  862. {
  863. internal bool _add = add;
  864. internal Dim _left = left, _right = right;
  865. public override string ToString () { return $"Combine({_left}{(_add ? '+' : '-')}{_right})"; }
  866. internal override int Anchor (int width)
  867. {
  868. int la = _left.Anchor (width);
  869. int ra = _right.Anchor (width);
  870. if (_add)
  871. {
  872. return la + ra;
  873. }
  874. return la - ra;
  875. }
  876. internal override int Calculate (int location, int superviewContentSize, View us, Dimension dimension)
  877. {
  878. int leftNewDim = _left.Calculate (location, superviewContentSize, us, dimension);
  879. int rightNewDim = _right.Calculate (location, superviewContentSize, us, dimension);
  880. int newDimension;
  881. if (_add)
  882. {
  883. newDimension = leftNewDim + rightNewDim;
  884. }
  885. else
  886. {
  887. newDimension = Math.Max (0, leftNewDim - rightNewDim);
  888. }
  889. return newDimension;
  890. }
  891. }
  892. internal class DimFactor (float factor, bool remaining = false) : Dim
  893. {
  894. private readonly float _factor = factor;
  895. private readonly bool _remaining = remaining;
  896. public override bool Equals (object other) { return other is DimFactor f && f._factor == _factor && f._remaining == _remaining; }
  897. public override int GetHashCode () { return _factor.GetHashCode (); }
  898. public bool IsFromRemaining () { return _remaining; }
  899. public override string ToString () { return $"Factor({_factor},{_remaining})"; }
  900. internal override int Anchor (int width) { return (int)(width * _factor); }
  901. internal override int Calculate (int location, int superviewContentSize, View us, Dimension dimension)
  902. {
  903. return _remaining ? Math.Max (Anchor (superviewContentSize - location), 0) : Anchor (superviewContentSize);
  904. }
  905. }
  906. internal class DimFill (int margin) : Dim
  907. {
  908. private readonly int _margin = margin;
  909. public override bool Equals (object other) { return other is DimFill fill && fill._margin == _margin; }
  910. public override int GetHashCode () { return _margin.GetHashCode (); }
  911. public override string ToString () { return $"Fill({_margin})"; }
  912. internal override int Anchor (int width) { return width - _margin; }
  913. }
  914. // Helper class to provide dynamic value by the execution of a function that returns an integer.
  915. internal class DimFunc (Func<int> n) : Dim
  916. {
  917. private readonly Func<int> _function = n;
  918. public override bool Equals (object other) { return other is DimFunc f && f._function () == _function (); }
  919. public override int GetHashCode () { return _function.GetHashCode (); }
  920. public override string ToString () { return $"DimFunc({_function ()})"; }
  921. internal override int Anchor (int width) { return _function (); }
  922. }
  923. internal class DimView : Dim
  924. {
  925. private readonly Dimension _side;
  926. internal DimView (View view, Dimension side)
  927. {
  928. Target = view;
  929. _side = side;
  930. }
  931. public View Target { get; init; }
  932. public override bool Equals (object other) { return other is DimView abs && abs.Target == Target; }
  933. public override int GetHashCode () { return Target.GetHashCode (); }
  934. public override string ToString ()
  935. {
  936. if (Target == null)
  937. {
  938. throw new NullReferenceException ();
  939. }
  940. string sideString = _side switch
  941. {
  942. Dimension.Height => "Height",
  943. Dimension.Width => "Width",
  944. _ => "unknown"
  945. };
  946. return $"View({sideString},{Target})";
  947. }
  948. internal override int Anchor (int width)
  949. {
  950. return _side switch
  951. {
  952. Dimension.Height => Target.Frame.Height,
  953. Dimension.Width => Target.Frame.Width,
  954. _ => 0
  955. };
  956. }
  957. }
  958. }