ComputedLayout.cs 12 KB

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