ComputedLayout.cs 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319
  1. using NStack;
  2. using System;
  3. using System.Collections.Generic;
  4. using System.Linq;
  5. using System.Reflection;
  6. using System.Text;
  7. using Terminal.Gui;
  8. namespace UICatalog.Scenarios {
  9. /// <summary>
  10. /// This Scenario demonstrates how to use Termina.gui's Dim and Pos Layout System.
  11. /// [x] - Using Dim.Fill to fill a window
  12. /// [x] - Using Dim.Fill and Dim.Pos to automatically align controls based on an initial control
  13. /// [ ] - ...
  14. /// </summary>
  15. [ScenarioMetadata (Name: "Computed Layout", Description: "Demonstrates the Computed (Dim and Pos) Layout System.")]
  16. [ScenarioCategory ("Layout")]
  17. public class ComputedLayout : Scenario {
  18. public override void Init (ColorScheme colorScheme)
  19. {
  20. Application.Init ();
  21. Application.Top.ColorScheme = colorScheme;
  22. }
  23. public override void Setup ()
  24. {
  25. // Demonstrate using Dim to create a horizontal ruler that always measures the parent window's width
  26. const string rule = "|123456789";
  27. var horizontalRuler = new Label ("") {
  28. AutoSize = false,
  29. X = 0,
  30. Y = 0,
  31. Width = Dim.Fill (),
  32. ColorScheme = Colors.Error
  33. };
  34. Application.Top.Add (horizontalRuler);
  35. // Demonstrate using Dim to create a vertical ruler that always measures the parent window's height
  36. const string vrule = "|\n1\n2\n3\n4\n5\n6\n7\n8\n9\n";
  37. var verticalRuler = new Label ("") {
  38. AutoSize = false,
  39. X = 0,
  40. Y = 0,
  41. Width = 1,
  42. Height = Dim.Fill (),
  43. ColorScheme = Colors.Error
  44. };
  45. Application.Top.LayoutComplete += (a) => {
  46. horizontalRuler.Text = rule.Repeat ((int)Math.Ceiling ((double)(horizontalRuler.Bounds.Width) / (double)rule.Length)) [0..(horizontalRuler.Bounds.Width)];
  47. verticalRuler.Text = vrule.Repeat ((int)Math.Ceiling ((double)(verticalRuler.Bounds.Height * 2) / (double)rule.Length)) [0..(verticalRuler.Bounds.Height*2)];
  48. };
  49. Application.Top.Add (verticalRuler);
  50. // Demonstrate At - Using Pos.At to locate a view in an absolute location
  51. var atButton = new Button ("At(2,1)") {
  52. X = Pos.At (2),
  53. Y = Pos.At (1)
  54. };
  55. Application.Top.Add (atButton);
  56. // Throw in a literal absolute - Should funciton identically to above
  57. var absoluteButton = new Button ("X = 30, Y = 1") {
  58. X = 30,
  59. Y = 1
  60. };
  61. Application.Top.Add (absoluteButton);
  62. // Demonstrate using Dim to create a window that fills the parent with a margin
  63. int margin = 10;
  64. var subWin = new Window () {
  65. X = Pos.Center (),
  66. Y = 2,
  67. Width = Dim.Fill (margin),
  68. Height = 7
  69. };
  70. subWin.Title = $"{subWin.GetType().Name} {{X={subWin.X},Y={subWin.Y},Width={subWin.Width},Height={subWin.Height}}}";
  71. Application.Top.Add (subWin);
  72. int i = 1;
  73. string txt = "Resize the terminal to see computed layout in action.";
  74. var labelList = new List<Label> ();
  75. labelList.Add (new Label ($"The lines below show different TextAlignments"));
  76. labelList.Add (new Label ($"{i++}-{txt}") { TextAlignment = Terminal.Gui.TextAlignment.Left, Width = Dim.Fill (), X = 0, Y = Pos.Bottom (labelList.LastOrDefault ()), ColorScheme = Colors.Dialog });
  77. labelList.Add (new Label ($"{i++}-{txt}") { TextAlignment = Terminal.Gui.TextAlignment.Right, Width = Dim.Fill (), X = 0, Y = Pos.Bottom (labelList.LastOrDefault ()), ColorScheme = Colors.Dialog });
  78. labelList.Add (new Label ($"{i++}-{txt}") { TextAlignment = Terminal.Gui.TextAlignment.Centered, Width = Dim.Fill (), X = 0, Y = Pos.Bottom (labelList.LastOrDefault ()), ColorScheme = Colors.Dialog });
  79. labelList.Add (new Label ($"{i++}-{txt}") { TextAlignment = Terminal.Gui.TextAlignment.Justified, Width = Dim.Fill (), X = 0, Y = Pos.Bottom (labelList.LastOrDefault ()), ColorScheme = Colors.Dialog });
  80. subWin.Add (labelList.ToArray ());
  81. // #522 repro?
  82. var frameView = new FrameView () {
  83. X = 2, //Pos.Center (),
  84. Y = Pos.Bottom (subWin),
  85. Width = 30,
  86. Height = 7
  87. };
  88. frameView.Title = $"{frameView.GetType ().Name} {{X={frameView.X},Y={frameView.Y},Width={frameView.Width},Height={frameView.Height}}}";
  89. i = 1;
  90. labelList = new List<Label> ();
  91. labelList.Add (new Label ($"The lines below show different TextAlignments"));
  92. labelList.Add (new Label ($"{i++}-{txt}") { TextAlignment = Terminal.Gui.TextAlignment.Left, Width = Dim.Fill (), X = 0, Y = Pos.Bottom (labelList.LastOrDefault ()), ColorScheme = Colors.Dialog });
  93. labelList.Add (new Label ($"{i++}-{txt}") { TextAlignment = Terminal.Gui.TextAlignment.Right, Width = Dim.Fill (), X = 0, Y = Pos.Bottom (labelList.LastOrDefault ()), ColorScheme = Colors.Dialog });
  94. labelList.Add (new Label ($"{i++}-{txt}") { TextAlignment = Terminal.Gui.TextAlignment.Centered, Width = Dim.Fill (), X = 0, Y = Pos.Bottom (labelList.LastOrDefault ()), ColorScheme = Colors.Dialog });
  95. labelList.Add (new Label ($"{i++}-{txt}") { TextAlignment = Terminal.Gui.TextAlignment.Justified, Width = Dim.Fill (), X = 0, Y = Pos.Bottom (labelList.LastOrDefault ()), ColorScheme = Colors.Dialog });
  96. frameView.Add (labelList.ToArray ());
  97. Application.Top.Add (frameView);
  98. frameView = new FrameView () {
  99. X = Pos.Right(frameView),
  100. Y = Pos.Top (frameView),
  101. Width = Dim.Fill(),
  102. Height = 7,
  103. };
  104. frameView.Title = $"{frameView.GetType ().Name} {{X={frameView.X},Y={frameView.Y},Width={frameView.Width},Height={frameView.Height}}}";
  105. Application.Top.Add (frameView);
  106. // Demonstrate Dim & Pos using percentages - a TextField that is 30% height and 80% wide
  107. var textView = new TextView () {
  108. X = Pos.Center (),
  109. Y = Pos.Percent (50),
  110. Width = Dim.Percent (80),
  111. Height = Dim.Percent (10),
  112. ColorScheme = Colors.TopLevel,
  113. };
  114. textView.Text = $"This TextView should horizontally & vertically centered and \n10% of the screeen height, and 80% of its width.";
  115. Application.Top.Add (textView);
  116. var oddballButton = new Button ("These buttons demo convoluted PosCombine scenarios") {
  117. X = Pos.Center (),
  118. Y = Pos.Bottom (textView) + 1
  119. };
  120. Application.Top.Add (oddballButton);
  121. #region Issue2358
  122. // Demonstrate odd-ball Combine scenarios
  123. // Until https://github.com/gui-cs/Terminal.Gui/issues/2358 is fixed these won't work right
  124. oddballButton = new Button ("Center + 0") {
  125. X = Pos.Center () + 0,
  126. Y = Pos.Bottom (oddballButton)
  127. };
  128. Application.Top.Add (oddballButton);
  129. oddballButton = new Button ("Center + 1") {
  130. X = Pos.Center () + 1,
  131. Y = Pos.Bottom (oddballButton)
  132. };
  133. Application.Top.Add (oddballButton);
  134. oddballButton = new Button ("0 + Center") {
  135. X = 0 + Pos.Center (),
  136. Y = Pos.Bottom (oddballButton)
  137. };
  138. Application.Top.Add (oddballButton);
  139. oddballButton = new Button ("1 + Center") {
  140. X = 1 + Pos.Center (),
  141. Y = Pos.Bottom (oddballButton)
  142. };
  143. Application.Top.Add (oddballButton);
  144. // This demonstrates nonsense: it the same as using Pos.AnchorEnd (100/2=50 + 100/2=50 = 100 - 50)
  145. // The `- Pos.Percent(5)` is there so at least something is visible
  146. oddballButton = new Button ("Center + Center - Percent(50)") {
  147. X = Pos.Center () + Pos.Center () - Pos.Percent(50),
  148. Y = Pos.Bottom (oddballButton)
  149. };
  150. Application.Top.Add (oddballButton);
  151. // This demonstrates nonsense: it the same as using Pos.AnchorEnd (100/2=50 + 100/2=50 = 100 - 50)
  152. // The `- Pos.Percent(5)` is there so at least something is visible
  153. oddballButton = new Button ("Percent(50) + Center - Percent(50)") {
  154. X = Pos.Percent (50) + Pos.Center () - Pos.Percent (50),
  155. Y = Pos.Bottom (oddballButton)
  156. };
  157. Application.Top.Add (oddballButton);
  158. // This demonstrates nonsense: it the same as using Pos.AnchorEnd (100/2=50 + 100/2=50 = 100 - 50)
  159. // The `- Pos.Percent(5)` is there so at least something is visible
  160. oddballButton = new Button ("Center + Percent(50) - Percent(50)") {
  161. X = Pos.Center () + Pos.Percent (50) - Pos.Percent (50),
  162. Y = Pos.Bottom (oddballButton)
  163. };
  164. Application.Top.Add (oddballButton);
  165. #endregion
  166. // This demonstrates nonsense: Same as At(0)
  167. oddballButton = new Button ("Center - Center - Percent(50)") {
  168. X = Pos.Center () + Pos.Center () - Pos.Percent (50),
  169. Y = Pos.Bottom (oddballButton)
  170. };
  171. Application.Top.Add (oddballButton);
  172. // This demonstrates combining Percents)
  173. oddballButton = new Button ("Percent(40) + Percent(10)") {
  174. X = Pos.Percent (40) + Pos.Percent(10),
  175. Y = Pos.Bottom (oddballButton)
  176. };
  177. Application.Top.Add (oddballButton);
  178. // Demonstrate AnchorEnd - Button is anchored to bottom/right
  179. var anchorButton = new Button ("Button using AnchorEnd") {
  180. Y = Pos.AnchorEnd () - 1,
  181. };
  182. anchorButton.X = Pos.AnchorEnd () - (Pos.Right (anchorButton) - Pos.Left (anchorButton));
  183. anchorButton.Clicked += () => {
  184. // Ths demonstrates how to have a dynamically sized button
  185. // Each time the button is clicked the button's text gets longer
  186. // The call to Application.Top.LayoutSubviews causes the Computed layout to
  187. // get updated.
  188. anchorButton.Text += "!";
  189. Application.Top.LayoutSubviews ();
  190. };
  191. Application.Top.Add (anchorButton);
  192. // Demonstrate AnchorEnd(n)
  193. // This is intentionally convoluted to illustrate potential bugs.
  194. var anchorEndLabel1 = new Label ("This Label should be the 2nd to last line (AnchorEnd (2)).") {
  195. TextAlignment = Terminal.Gui.TextAlignment.Centered,
  196. ColorScheme = Colors.Menu,
  197. Width = Dim.Fill (5),
  198. X = 5,
  199. Y = Pos.AnchorEnd (2)
  200. };
  201. Application.Top.Add (anchorEndLabel1);
  202. // Demonstrate DimCombine (via AnchorEnd(n) - 1)
  203. // This is intentionally convoluted to illustrate potential bugs.
  204. var anchorEndLabel2 = new TextField ("This TextField should be the 3rd to last line (AnchorEnd (2) - 1).") {
  205. TextAlignment = Terminal.Gui.TextAlignment.Left,
  206. ColorScheme = Colors.Menu,
  207. Width = Dim.Fill (5),
  208. X = 5,
  209. Y = Pos.AnchorEnd (2) - 1 // Pos.Combine
  210. };
  211. Application.Top.Add (anchorEndLabel2);
  212. // Show positioning vertically using Pos.AnchorEnd via Pos.Combine
  213. var leftButton = new Button ("Left") {
  214. Y = Pos.AnchorEnd () - 1 // Pos.Combine
  215. };
  216. leftButton.Clicked += () => {
  217. // Ths demonstrates how to have a dynamically sized button
  218. // Each time the button is clicked the button's text gets longer
  219. // The call to Application.Top.LayoutSubviews causes the Computed layout to
  220. // get updated.
  221. leftButton.Text += "!";
  222. Application.Top.LayoutSubviews ();
  223. };
  224. // show positioning vertically using Pos.AnchorEnd
  225. var centerButton = new Button ("Center") {
  226. X = Pos.Center (),
  227. Y = Pos.AnchorEnd (1) // Pos.AnchorEnd(1)
  228. };
  229. centerButton.Clicked += () => {
  230. // Ths demonstrates how to have a dynamically sized button
  231. // Each time the button is clicked the button's text gets longer
  232. // The call to Application.Top.LayoutSubviews causes the Computed layout to
  233. // get updated.
  234. centerButton.Text += "!";
  235. Application.Top.LayoutSubviews ();
  236. };
  237. // show positioning vertically using another window and Pos.Bottom
  238. var rightButton = new Button ("Right") {
  239. Y = Pos.Y (centerButton)
  240. };
  241. rightButton.Clicked += () => {
  242. // Ths demonstrates how to have a dynamically sized button
  243. // Each time the button is clicked the button's text gets longer
  244. // The call to Application.Top.LayoutSubviews causes the Computed layout to
  245. // get updated.
  246. rightButton.Text += "!";
  247. Application.Top.LayoutSubviews ();
  248. };
  249. // Center three buttons with 5 spaces between them
  250. leftButton.X = Pos.Left (centerButton) - (Pos.Right (leftButton) - Pos.Left (leftButton)) - 5;
  251. rightButton.X = Pos.Right (centerButton) + 5;
  252. Application.Top.Add (leftButton);
  253. Application.Top.Add (centerButton);
  254. Application.Top.Add (rightButton);
  255. }
  256. public override void Run ()
  257. {
  258. base.Run ();
  259. }
  260. private void Quit ()
  261. {
  262. Application.RequestStop ();
  263. }
  264. }
  265. internal static class StringExtensions {
  266. public static string Repeat (this string instr, int n)
  267. {
  268. if (n <= 0) {
  269. return null;
  270. }
  271. if (string.IsNullOrEmpty (instr) || n == 1) {
  272. return instr;
  273. }
  274. return new StringBuilder (instr.Length * n)
  275. .Insert (0, instr, n)
  276. .ToString ();
  277. }
  278. }
  279. }