PosDim.cs 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564
  1. //
  2. // PosDim.cs: Pos and Dim objects for view dimensions.
  3. //
  4. // Authors:
  5. // Miguel de Icaza ([email protected])
  6. //
  7. using System;
  8. namespace Terminal.Gui {
  9. /// <summary>
  10. /// Describes the position of a <see cref="View"/> which can be an absolute value, a percentage, centered, or
  11. /// relative to the ending dimension. Integer values are implicitly convertible to
  12. /// an absolute <see cref="Pos"/>. These objects are created using the static methods Percent,
  13. /// AnchorEnd, and Center. The <see cref="Pos"/> objects can be combined with the addition and
  14. /// subtraction operators.
  15. /// </summary>
  16. /// <remarks>
  17. /// <para>
  18. /// Use the <see cref="Pos"/> objects on the X or Y properties of a view to control the position.
  19. /// </para>
  20. /// <para>
  21. /// These can be used to set the absolute position, when merely assigning an
  22. /// integer value (via the implicit integer to <see cref="Pos"/> conversion), and they can be combined
  23. /// to produce more useful layouts, like: Pos.Center - 3, which would shift the postion
  24. /// of the <see cref="View"/> 3 characters to the left after centering for example.
  25. /// </para>
  26. /// <para>
  27. /// It is possible to reference coordinates of another view by using the methods
  28. /// Left(View), Right(View), Bottom(View), Top(View). The X(View) and Y(View) are
  29. /// aliases to Left(View) and Top(View) respectively.
  30. /// </para>
  31. /// </remarks>
  32. public class Pos {
  33. internal virtual int Anchor (int width)
  34. {
  35. return 0;
  36. }
  37. class PosFactor : Pos {
  38. float factor;
  39. public PosFactor (float n)
  40. {
  41. this.factor = n;
  42. }
  43. internal override int Anchor (int width)
  44. {
  45. return (int)(width * factor);
  46. }
  47. public override string ToString ()
  48. {
  49. return $"Pos.Factor({factor})";
  50. }
  51. }
  52. /// <summary>
  53. /// Creates a percentage <see cref="Pos"/> object
  54. /// </summary>
  55. /// <returns>The percent <see cref="Pos"/> object.</returns>
  56. /// <param name="n">A value between 0 and 100 representing the percentage.</param>
  57. /// <example>
  58. /// This creates a <see cref="TextField"/>that is centered horizontally, is 50% of the way down,
  59. /// is 30% the height, and is 80% the width of the <see cref="View"/> it added to.
  60. /// <code>
  61. /// var textView = new TextView () {
  62. /// X = Pos.Center (),
  63. /// Y = Pos.Percent (50),
  64. /// Width = Dim.Percent (80),
  65. /// Height = Dim.Percent (30),
  66. /// };
  67. /// </code>
  68. /// </example>
  69. public static Pos Percent (float n)
  70. {
  71. if (n < 0 || n > 100)
  72. throw new ArgumentException ("Percent value must be between 0 and 100");
  73. return new PosFactor (n / 100);
  74. }
  75. static PosAnchorEnd endNoMargin;
  76. class PosAnchorEnd : Pos {
  77. int n;
  78. public PosAnchorEnd (int n)
  79. {
  80. this.n = n;
  81. }
  82. internal override int Anchor (int width)
  83. {
  84. return width - n;
  85. }
  86. public override string ToString ()
  87. {
  88. return $"Pos.AnchorEnd(margin={n})";
  89. }
  90. }
  91. /// <summary>
  92. /// Creates a <see cref="Pos"/> object that is anchored to the end (right side or bottom) of the dimension,
  93. /// useful to flush the layout from the right or bottom.
  94. /// </summary>
  95. /// <returns>The <see cref="Pos"/> object anchored to the end (the bottom or the right side).</returns>
  96. /// <param name="margin">Optional margin to place to the right or below.</param>
  97. /// <example>
  98. /// This sample shows how align a <see cref="Button"/> to the bottom-right of a <see cref="View"/>.
  99. /// <code>
  100. /// anchorButton.X = Pos.AnchorEnd () - (Pos.Right (anchorButton) - Pos.Left (anchorButton));
  101. /// anchorButton.Y = Pos.AnchorEnd () - 1;
  102. /// </code>
  103. /// </example>
  104. public static Pos AnchorEnd (int margin = 0)
  105. {
  106. if (margin < 0)
  107. throw new ArgumentException ("Margin must be positive");
  108. if (margin == 0) {
  109. if (endNoMargin == null)
  110. endNoMargin = new PosAnchorEnd (0);
  111. return endNoMargin;
  112. }
  113. return new PosAnchorEnd (margin);
  114. }
  115. internal class PosCenter : Pos {
  116. internal override int Anchor (int width)
  117. {
  118. return width / 2;
  119. }
  120. public override string ToString ()
  121. {
  122. return "Pos.Center";
  123. }
  124. }
  125. static PosCenter pCenter;
  126. /// <summary>
  127. /// Returns a <see cref="Pos"/> object that can be used to center the <see cref="View"/>
  128. /// </summary>
  129. /// <returns>The center Pos.</returns>
  130. /// <example>
  131. /// This creates a <see cref="TextField"/>that is centered horizontally, is 50% of the way down,
  132. /// is 30% the height, and is 80% the width of the <see cref="View"/> it added to.
  133. /// <code>
  134. /// var textView = new TextView () {
  135. /// X = Pos.Center (),
  136. /// Y = Pos.Percent (50),
  137. /// Width = Dim.Percent (80),
  138. /// Height = Dim.Percent (30),
  139. /// };
  140. /// </code>
  141. /// </example>
  142. public static Pos Center ()
  143. {
  144. if (pCenter == null)
  145. pCenter = new PosCenter ();
  146. return pCenter;
  147. }
  148. class PosAbsolute : Pos {
  149. int n;
  150. public PosAbsolute (int n) { this.n = n; }
  151. public override string ToString ()
  152. {
  153. return $"Pos.Absolute({n})";
  154. }
  155. internal override int Anchor (int width)
  156. {
  157. return n;
  158. }
  159. }
  160. /// <summary>
  161. /// Creates an Absolute <see cref="Pos"/> from the specified integer value.
  162. /// </summary>
  163. /// <returns>The Absolute <see cref="Pos"/>.</returns>
  164. /// <param name="n">The value to convert to the <see cref="Pos"/> .</param>
  165. public static implicit operator Pos (int n)
  166. {
  167. return new PosAbsolute (n);
  168. }
  169. /// <summary>
  170. /// Creates an Absolute <see cref="Pos"/> from the specified integer value.
  171. /// </summary>
  172. /// <returns>The Absolute <see cref="Pos"/>.</returns>
  173. /// <param name="n">The value to convert to the <see cref="Pos"/>.</param>
  174. public static Pos At (int n)
  175. {
  176. return new PosAbsolute (n);
  177. }
  178. class PosCombine : Pos {
  179. Pos left, right;
  180. bool add;
  181. public PosCombine (bool add, Pos left, Pos right)
  182. {
  183. this.left = left;
  184. this.right = right;
  185. this.add = add;
  186. }
  187. internal override int Anchor (int width)
  188. {
  189. var la = left.Anchor (width);
  190. var ra = right.Anchor (width);
  191. if (add)
  192. return la + ra;
  193. else
  194. return la - ra;
  195. }
  196. public override string ToString ()
  197. {
  198. return $"Pos.Combine ({left.ToString ()}{(add ? '+' : '-')}{right.ToString ()})";
  199. }
  200. }
  201. static PosCombine posCombine;
  202. /// <summary>
  203. /// Adds a <see cref="Terminal.Gui.Pos"/> to a <see cref="Terminal.Gui.Pos"/>, yielding a new <see cref="Pos"/>.
  204. /// </summary>
  205. /// <param name="left">The first <see cref="Terminal.Gui.Pos"/> to add.</param>
  206. /// <param name="right">The second <see cref="Terminal.Gui.Pos"/> to add.</param>
  207. /// <returns>The <see cref="Pos"/> that is the sum of the values of <c>left</c> and <c>right</c>.</returns>
  208. public static Pos operator + (Pos left, Pos right)
  209. {
  210. PosCombine newPos = new PosCombine (true, left, right);
  211. if (posCombine?.ToString () != newPos.ToString ()) {
  212. var view = left as PosView;
  213. if (view != null) {
  214. view.Target.SetNeedsLayout ();
  215. }
  216. }
  217. return posCombine = newPos;
  218. }
  219. /// <summary>
  220. /// Subtracts a <see cref="Terminal.Gui.Pos"/> from a <see cref="Terminal.Gui.Pos"/>, yielding a new <see cref="Pos"/>.
  221. /// </summary>
  222. /// <param name="left">The <see cref="Terminal.Gui.Pos"/> to subtract from (the minuend).</param>
  223. /// <param name="right">The <see cref="Terminal.Gui.Pos"/> to subtract (the subtrahend).</param>
  224. /// <returns>The <see cref="Pos"/> that is the <c>left</c> minus <c>right</c>.</returns>
  225. public static Pos operator - (Pos left, Pos right)
  226. {
  227. PosCombine newPos = new PosCombine (false, left, right);
  228. if (posCombine?.ToString () != newPos.ToString ()) {
  229. var view = left as PosView;
  230. if (view != null)
  231. view.Target.SetNeedsLayout ();
  232. }
  233. return posCombine = newPos;
  234. }
  235. internal class PosView : Pos {
  236. public View Target;
  237. int side;
  238. public PosView (View view, int side)
  239. {
  240. Target = view;
  241. this.side = side;
  242. }
  243. internal override int Anchor (int width)
  244. {
  245. switch (side) {
  246. case 0: return Target.Frame.X;
  247. case 1: return Target.Frame.Y;
  248. case 2: return Target.Frame.Right;
  249. case 3: return Target.Frame.Bottom;
  250. default:
  251. return 0;
  252. }
  253. }
  254. public override string ToString ()
  255. {
  256. string tside;
  257. switch (side) {
  258. case 0: tside = "x"; break;
  259. case 1: tside = "y"; break;
  260. case 2: tside = "right"; break;
  261. case 3: tside = "bottom"; break;
  262. default: tside = "unknown"; break;
  263. }
  264. return $"Pos.View(side={tside}, target={Target.ToString ()}";
  265. }
  266. }
  267. /// <summary>
  268. /// Returns a <see cref="Pos"/> object tracks the Left (X) position of the specified <see cref="View"/>.
  269. /// </summary>
  270. /// <returns>The <see cref="Pos"/> that depends on the other view.</returns>
  271. /// <param name="view">The <see cref="View"/> that will be tracked.</param>
  272. public static Pos Left (View view) => new PosView (view, 0);
  273. /// <summary>
  274. /// Returns a <see cref="Pos"/> object tracks the Left (X) position of the specified <see cref="View"/>.
  275. /// </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 X (View view) => new PosView (view, 0);
  279. /// <summary>
  280. /// Returns a <see cref="Pos"/> object tracks the Top (Y) position of the specified <see cref="View"/>.
  281. /// </summary>
  282. /// <returns>The <see cref="Pos"/> that depends on the other view.</returns>
  283. /// <param name="view">The <see cref="View"/> that will be tracked.</param>
  284. public static Pos Top (View view) => new PosView (view, 1);
  285. /// <summary>
  286. /// Returns a <see cref="Pos"/> object tracks the Top (Y) position of the specified <see cref="View"/>.
  287. /// </summary>
  288. /// <returns>The <see cref="Pos"/> that depends on the other view.</returns>
  289. /// <param name="view">The <see cref="View"/> that will be tracked.</param>
  290. public static Pos Y (View view) => new PosView (view, 1);
  291. /// <summary>
  292. /// Returns a <see cref="Pos"/> object tracks the Right (X+Width) coordinate of the specified <see cref="View"/>.
  293. /// </summary>
  294. /// <returns>The <see cref="Pos"/> that depends on the other view.</returns>
  295. /// <param name="view">The <see cref="View"/> that will be tracked.</param>
  296. public static Pos Right (View view) => new PosView (view, 2);
  297. /// <summary>
  298. /// Returns a <see cref="Pos"/> object tracks the Bottom (Y+Height) coordinate of the specified <see cref="View"/>
  299. /// </summary>
  300. /// <returns>The <see cref="Pos"/> that depends on the other view.</returns>
  301. /// <param name="view">The <see cref="View"/> that will be tracked.</param>
  302. public static Pos Bottom (View view) => new PosView (view, 3);
  303. }
  304. /// <summary>
  305. /// Dim properties of a <see cref="View"/> to control the position.
  306. /// </summary>
  307. /// <remarks>
  308. /// <para>
  309. /// Use the Dim objects on the Width or Height properties of a <see cref="View"/> to control the position.
  310. /// </para>
  311. /// <para>
  312. /// These can be used to set the absolute position, when merely assigning an
  313. /// integer value (via the implicit integer to Pos conversion), and they can be combined
  314. /// to produce more useful layouts, like: Pos.Center - 3, which would shift the postion
  315. /// of the <see cref="View"/> 3 characters to the left after centering for example.
  316. /// </para>
  317. /// </remarks>
  318. public class Dim {
  319. internal virtual int Anchor (int width)
  320. {
  321. return 0;
  322. }
  323. class DimFactor : Dim {
  324. float factor;
  325. public DimFactor (float n)
  326. {
  327. this.factor = n;
  328. }
  329. internal override int Anchor (int width)
  330. {
  331. return (int)(width * factor);
  332. }
  333. public override string ToString ()
  334. {
  335. return $"Dim.Factor({factor})";
  336. }
  337. public override int GetHashCode () => factor.GetHashCode ();
  338. public override bool Equals (object other) => other is DimFactor f && f.factor == factor;
  339. }
  340. /// <summary>
  341. /// Creates a percentage <see cref="Dim"/> object
  342. /// </summary>
  343. /// <returns>The percent <see cref="Dim"/> object.</returns>
  344. /// <param name="n">A value between 0 and 100 representing the percentage.</param>
  345. /// <example>
  346. /// This initializes a <see cref="TextField"/>that is centered horizontally, is 50% of the way down,
  347. /// is 30% the height, and is 80% the width of the <see cref="View"/> it added to.
  348. /// <code>
  349. /// var textView = new TextView () {
  350. /// X = Pos.Center (),
  351. /// Y = Pos.Percent (50),
  352. /// Width = Dim.Percent (80),
  353. /// Height = Dim.Percent (30),
  354. /// };
  355. /// </code>
  356. /// </example>
  357. public static Dim Percent (float n)
  358. {
  359. if (n < 0 || n > 100)
  360. throw new ArgumentException ("Percent value must be between 0 and 100");
  361. return new DimFactor (n / 100);
  362. }
  363. internal class DimAbsolute : Dim {
  364. int n;
  365. public DimAbsolute (int n) { this.n = n; }
  366. public override string ToString ()
  367. {
  368. return $"Dim.Absolute({n})";
  369. }
  370. internal override int Anchor (int width)
  371. {
  372. return n;
  373. }
  374. public override int GetHashCode () => n.GetHashCode ();
  375. public override bool Equals (object other) => other is DimAbsolute abs && abs.n == n;
  376. }
  377. internal class DimFill : Dim {
  378. int margin;
  379. public DimFill (int margin) { this.margin = margin; }
  380. public override string ToString ()
  381. {
  382. return $"Dim.Fill(margin={margin})";
  383. }
  384. internal override int Anchor (int width)
  385. {
  386. return width - margin;
  387. }
  388. public override int GetHashCode () => margin.GetHashCode ();
  389. public override bool Equals (object other) => other is DimFill fill && fill.margin == margin;
  390. }
  391. static DimFill zeroMargin;
  392. /// <summary>
  393. /// Initializes a new instance of the <see cref="Dim"/> class that fills the dimension, but leaves the specified number of colums for a margin.
  394. /// </summary>
  395. /// <returns>The Fill dimension.</returns>
  396. /// <param name="margin">Margin to use.</param>
  397. public static Dim Fill (int margin = 0)
  398. {
  399. if (margin == 0) {
  400. if (zeroMargin == null)
  401. zeroMargin = new DimFill (0);
  402. return zeroMargin;
  403. }
  404. return new DimFill (margin);
  405. }
  406. /// <summary>
  407. /// Creates an Absolute <see cref="Dim"/> from the specified integer value.
  408. /// </summary>
  409. /// <returns>The Absolute <see cref="Dim"/>.</returns>
  410. /// <param name="n">The value to convert to the pos.</param>
  411. public static implicit operator Dim (int n)
  412. {
  413. return new DimAbsolute (n);
  414. }
  415. /// <summary>
  416. /// Creates an Absolute <see cref="Dim"/> from the specified integer value.
  417. /// </summary>
  418. /// <returns>The Absolute <see cref="Dim"/>.</returns>
  419. /// <param name="n">The value to convert to the <see cref="Dim"/>.</param>
  420. public static Dim Sized (int n)
  421. {
  422. return new DimAbsolute (n);
  423. }
  424. class DimCombine : Dim {
  425. Dim left, right;
  426. bool add;
  427. public DimCombine (bool add, Dim left, Dim right)
  428. {
  429. this.left = left;
  430. this.right = right;
  431. this.add = add;
  432. }
  433. internal override int Anchor (int width)
  434. {
  435. var la = left.Anchor (width);
  436. var ra = right.Anchor (width);
  437. if (add)
  438. return la + ra;
  439. else
  440. return la - ra;
  441. }
  442. }
  443. /// <summary>
  444. /// Adds a <see cref="Terminal.Gui.Dim"/> to a <see cref="Terminal.Gui.Dim"/>, yielding a new <see cref="Dim"/>.
  445. /// </summary>
  446. /// <param name="left">The first <see cref="Terminal.Gui.Dim"/> to add.</param>
  447. /// <param name="right">The second <see cref="Terminal.Gui.Dim"/> to add.</param>
  448. /// <returns>The <see cref="Dim"/> that is the sum of the values of <c>left</c> and <c>right</c>.</returns>
  449. public static Dim operator + (Dim left, Dim right)
  450. {
  451. return new DimCombine (true, left, right);
  452. }
  453. /// <summary>
  454. /// Subtracts a <see cref="Terminal.Gui.Dim"/> from a <see cref="Terminal.Gui.Dim"/>, yielding a new <see cref="Dim"/>.
  455. /// </summary>
  456. /// <param name="left">The <see cref="Terminal.Gui.Dim"/> to subtract from (the minuend).</param>
  457. /// <param name="right">The <see cref="Terminal.Gui.Dim"/> to subtract (the subtrahend).</param>
  458. /// <returns>The <see cref="Dim"/> that is the <c>left</c> minus <c>right</c>.</returns>
  459. public static Dim operator - (Dim left, Dim right)
  460. {
  461. return new DimCombine (false, left, right);
  462. }
  463. internal class DimView : Dim {
  464. public View Target;
  465. int side;
  466. public DimView (View view, int side)
  467. {
  468. Target = view;
  469. this.side = side;
  470. }
  471. internal override int Anchor (int width)
  472. {
  473. switch (side) {
  474. case 0: return Target.Frame.Height;
  475. case 1: return Target.Frame.Width;
  476. default:
  477. return 0;
  478. }
  479. }
  480. }
  481. /// <summary>
  482. /// Returns a <see cref="Dim"/> object tracks the Width of the specified <see cref="View"/>.
  483. /// </summary>
  484. /// <returns>The <see cref="Dim"/> of the other <see cref="View"/>.</returns>
  485. /// <param name="view">The view that will be tracked.</param>
  486. public static Dim Width (View view) => new DimView (view, 1);
  487. /// <summary>
  488. /// Returns a <see cref="Dim"/> object tracks the Height of the specified <see cref="View"/>.
  489. /// </summary>
  490. /// <returns>The <see cref="Dim"/> of the other <see cref="View"/>.</returns>
  491. /// <param name="view">The view that will be tracked.</param>
  492. public static Dim Height (View view) => new DimView (view, 0);
  493. }
  494. }