ComputedLayout.cs 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469
  1. using System;
  2. using System.Collections.Generic;
  3. using System.Linq;
  4. using Terminal.Gui;
  5. using static Terminal.Gui.Dialog;
  6. namespace UICatalog.Scenarios;
  7. /// <summary>
  8. /// This Scenario demonstrates how to use Terminal.Gui's Dim and Pos Layout System.
  9. /// </summary>
  10. [ScenarioMetadata ("Computed Layout", "Demonstrates the Computed (Dim and Pos) Layout System.")]
  11. [ScenarioCategory ("Layout")]
  12. public class ComputedLayout : Scenario
  13. {
  14. public override void Main ()
  15. {
  16. Application.Init ();
  17. Window app = new ()
  18. {
  19. Title = GetQuitKeyAndName (),
  20. };
  21. // Demonstrate using Dim to create a horizontal ruler that always measures the parent window's width
  22. const string rule = "|123456789";
  23. var horizontalRuler = new Label
  24. {
  25. X = 0,
  26. Y = 0,
  27. Width = Dim.Fill (),
  28. Height = 1,
  29. ColorScheme = Colors.ColorSchemes ["Error"],
  30. Text = rule
  31. };
  32. app.Add (horizontalRuler);
  33. // Demonstrate using Dim to create a vertical ruler that always measures the parent window's height
  34. const string vrule = "|\n1\n2\n3\n4\n5\n6\n7\n8\n9\n";
  35. var verticalRuler = new Label
  36. {
  37. X = 0,
  38. Y = 0,
  39. Width = 1,
  40. Height = Dim.Fill (),
  41. ColorScheme = Colors.ColorSchemes ["Error"],
  42. Text = vrule
  43. };
  44. app.LayoutComplete += (s, a) =>
  45. {
  46. if (horizontalRuler.Viewport.Width == 0 || horizontalRuler.Viewport.Height == 0)
  47. {
  48. return;
  49. }
  50. horizontalRuler.Text =
  51. rule.Repeat ((int)Math.Ceiling (horizontalRuler.Viewport.Width / (double)rule.Length)) [
  52. ..horizontalRuler.Viewport.Width];
  53. verticalRuler.Text =
  54. vrule.Repeat ((int)Math.Ceiling (verticalRuler.Viewport.Height * 2 / (double)rule.Length))
  55. [..(verticalRuler.Viewport.Height * 2)];
  56. };
  57. app.Add (verticalRuler);
  58. // Demonstrate At - Using Pos.At to locate a view in an absolute location
  59. var atButton = new Button { Text = "Absolute(2,1)", X = Pos.Absolute (2), Y = Pos.Absolute (1) };
  60. app.Add (atButton);
  61. // Throw in a literal absolute - Should function identically to above
  62. var absoluteButton = new Button { Text = "X = 30, Y = 1", X = 30, Y = 1 };
  63. app.Add (absoluteButton);
  64. // Demonstrate using Dim to create a window that fills the parent with a margin
  65. var margin = 10;
  66. var subWin = new Window { X = Pos.Center (), Y = 2, Width = Dim.Fill (margin), Height = 7 };
  67. subWin.Initialized += (s, a) =>
  68. {
  69. subWin.Title =
  70. $"{subWin.GetType ().Name} {{X={subWin.X},Y={subWin.Y},Width={subWin.Width},Height={subWin.Height}}}";
  71. };
  72. app.Add (subWin);
  73. var i = 1;
  74. var txt = "Resize the terminal to see computed layout in action.";
  75. List<Label> labelList = new ();
  76. labelList.Add (new Label { Text = "The lines below show different alignment" });
  77. labelList.Add (
  78. new Label
  79. {
  80. TextAlignment = Alignment.Start,
  81. Width = Dim.Fill (),
  82. X = 0,
  83. Y = Pos.Bottom (labelList.LastOrDefault ()),
  84. ColorScheme = Colors.ColorSchemes ["Dialog"],
  85. Text = $"{i++}-{txt}"
  86. }
  87. );
  88. labelList.Add (
  89. new Label
  90. {
  91. TextAlignment = Alignment.End,
  92. Width = Dim.Fill (),
  93. X = 0,
  94. Y = Pos.Bottom (labelList.LastOrDefault ()),
  95. ColorScheme = Colors.ColorSchemes ["Dialog"],
  96. Text = $"{i++}-{txt}"
  97. }
  98. );
  99. labelList.Add (
  100. new Label
  101. {
  102. TextAlignment = Alignment.Center,
  103. Width = Dim.Fill (),
  104. X = 0,
  105. Y = Pos.Bottom (labelList.LastOrDefault ()),
  106. ColorScheme = Colors.ColorSchemes ["Dialog"],
  107. Text = $"{i++}-{txt}"
  108. }
  109. );
  110. labelList.Add (
  111. new Label
  112. {
  113. TextAlignment = Alignment.Fill,
  114. Width = Dim.Fill (),
  115. X = 0,
  116. Y = Pos.Bottom (labelList.LastOrDefault ()),
  117. ColorScheme = Colors.ColorSchemes ["Dialog"],
  118. Text = $"{i++}-{txt}"
  119. }
  120. );
  121. subWin.Add (labelList.ToArray ());
  122. var frameView = new FrameView { X = 2, Y = Pos.Bottom (subWin), Width = 30, Height = 7 };
  123. frameView.Initialized += (sender, args) =>
  124. {
  125. var fv = sender as FrameView;
  126. fv.Title =
  127. $"{frameView.GetType ().Name} {{X={fv.X},Y={fv.Y},Width={fv.Width},Height={fv.Height}}}";
  128. };
  129. i = 1;
  130. labelList = new List<Label> ();
  131. labelList.Add (new Label { Text = "The lines below show different alignment" });
  132. labelList.Add (
  133. new Label
  134. {
  135. TextAlignment = Alignment.Start,
  136. Width = Dim.Fill (),
  137. X = 0,
  138. Y = Pos.Bottom (labelList.LastOrDefault ()),
  139. ColorScheme = Colors.ColorSchemes ["Dialog"],
  140. Text = $"{i++}-{txt}"
  141. }
  142. );
  143. labelList.Add (
  144. new Label
  145. {
  146. TextAlignment = Alignment.End,
  147. Width = Dim.Fill (),
  148. X = 0,
  149. Y = Pos.Bottom (labelList.LastOrDefault ()),
  150. ColorScheme = Colors.ColorSchemes ["Dialog"],
  151. Text = $"{i++}-{txt}"
  152. }
  153. );
  154. labelList.Add (
  155. new Label
  156. {
  157. TextAlignment = Alignment.Center,
  158. Width = Dim.Fill (),
  159. X = 0,
  160. Y = Pos.Bottom (labelList.LastOrDefault ()),
  161. ColorScheme = Colors.ColorSchemes ["Dialog"],
  162. Text = $"{i++}-{txt}"
  163. }
  164. );
  165. labelList.Add (
  166. new Label
  167. {
  168. TextAlignment = Alignment.Fill,
  169. Width = Dim.Fill (),
  170. X = 0,
  171. Y = Pos.Bottom (labelList.LastOrDefault ()),
  172. ColorScheme = Colors.ColorSchemes ["Dialog"],
  173. Text = $"{i++}-{txt}"
  174. }
  175. );
  176. frameView.Add (labelList.ToArray ());
  177. app.Add (frameView);
  178. frameView = new FrameView
  179. {
  180. X = Pos.Right (frameView), Y = Pos.Top (frameView), Width = Dim.Fill (), Height = 7
  181. };
  182. frameView.Initialized += (sender, args) =>
  183. {
  184. var fv = sender as FrameView;
  185. fv.Title =
  186. $"{frameView.GetType ().Name} {{X={fv.X},Y={fv.Y},Width={fv.Width},Height={fv.Height}}}";
  187. };
  188. labelList = new List<Label> ();
  189. labelList.Add (new Label { Text = "The lines below show different alignment" });
  190. labelList.Add (
  191. new Label
  192. {
  193. TextAlignment = Alignment.Start,
  194. Width = Dim.Fill (),
  195. X = 0,
  196. Y = Pos.Bottom (labelList.LastOrDefault ()),
  197. ColorScheme = Colors.ColorSchemes ["Dialog"],
  198. Text = $"{i++}-{txt}"
  199. }
  200. );
  201. labelList.Add (
  202. new Label
  203. {
  204. TextAlignment = Alignment.End,
  205. Width = Dim.Fill (),
  206. X = 0,
  207. Y = Pos.Bottom (labelList.LastOrDefault ()),
  208. ColorScheme = Colors.ColorSchemes ["Dialog"],
  209. Text = $"{i++}-{txt}"
  210. }
  211. );
  212. labelList.Add (
  213. new Label
  214. {
  215. TextAlignment = Alignment.Center,
  216. Width = Dim.Fill (),
  217. X = 0,
  218. Y = Pos.Bottom (labelList.LastOrDefault ()),
  219. ColorScheme = Colors.ColorSchemes ["Dialog"],
  220. Text = $"{i++}-{txt}"
  221. }
  222. );
  223. labelList.Add (
  224. new Label
  225. {
  226. TextAlignment = Alignment.Fill,
  227. Width = Dim.Fill (),
  228. X = 0,
  229. Y = Pos.Bottom (labelList.LastOrDefault ()),
  230. ColorScheme = Colors.ColorSchemes ["Dialog"],
  231. Text = $"{i++}-{txt}"
  232. }
  233. );
  234. frameView.Add (labelList.ToArray ());
  235. app.Add (frameView);
  236. // Demonstrate Dim & Pos using percentages - a TextField that is 30% height and 80% wide
  237. var textView = new TextView
  238. {
  239. X = Pos.Center (),
  240. Y = Pos.Percent (50),
  241. Width = Dim.Percent (80),
  242. Height = Dim.Percent (10),
  243. ColorScheme = Colors.ColorSchemes ["TopLevel"]
  244. };
  245. textView.Text =
  246. "This TextView should horizontally & vertically centered and \n10% of the screeen height, and 80% of its width.";
  247. app.Add (textView);
  248. var oddballButton = new Button
  249. {
  250. Text = "These buttons demo convoluted PosCombine scenarios",
  251. X = Pos.Center (),
  252. Y = Pos.Bottom (textView) + 1
  253. };
  254. app.Add (oddballButton);
  255. #region Issue2358
  256. // Demonstrate odd-ball Combine scenarios
  257. // Until https://github.com/gui-cs/Terminal.Gui/issues/2358 is fixed these won't work right
  258. oddballButton = new Button { Text = "Center + 0", X = Pos.Center () + 0, Y = Pos.Bottom (oddballButton) };
  259. app.Add (oddballButton);
  260. oddballButton = new Button { Text = "Center + 1", X = Pos.Center () + 1, Y = Pos.Bottom (oddballButton) };
  261. app.Add (oddballButton);
  262. oddballButton = new Button { Text = "0 + Center", X = 0 + Pos.Center (), Y = Pos.Bottom (oddballButton) };
  263. app.Add (oddballButton);
  264. oddballButton = new Button { Text = "1 + Center", X = 1 + Pos.Center (), Y = Pos.Bottom (oddballButton) };
  265. app.Add (oddballButton);
  266. oddballButton = new Button { Text = "Center - 1", X = Pos.Center () - 1, Y = Pos.Bottom (oddballButton) };
  267. app.Add (oddballButton);
  268. // This demonstrates nonsense: it the same as using Pos.AnchorEnd (100/2=50 + 100/2=50 = 100 - 50)
  269. // The `- Pos.Percent(5)` is there so at least something is visible
  270. oddballButton = new Button
  271. {
  272. Text = "Center + Center - Percent(50)",
  273. X = Pos.Center () + Pos.Center () - Pos.Percent (50),
  274. Y = Pos.Bottom (oddballButton)
  275. };
  276. app.Add (oddballButton);
  277. // This demonstrates nonsense: it the same as using Pos.AnchorEnd (100/2=50 + 100/2=50 = 100 - 50)
  278. // The `- Pos.Percent(5)` is there so at least something is visible
  279. oddballButton = new Button
  280. {
  281. Text = "Percent(50) + Center - Percent(50)",
  282. X = Pos.Percent (50) + Pos.Center () - Pos.Percent (50),
  283. Y = Pos.Bottom (oddballButton)
  284. };
  285. app.Add (oddballButton);
  286. // This demonstrates nonsense: it the same as using Pos.AnchorEnd (100/2=50 + 100/2=50 = 100 - 50)
  287. // The `- Pos.Percent(5)` is there so at least something is visible
  288. oddballButton = new Button
  289. {
  290. Text = "Center + Percent(50) - Percent(50)",
  291. X = Pos.Center () + Pos.Percent (50) - Pos.Percent (50),
  292. Y = Pos.Bottom (oddballButton)
  293. };
  294. app.Add (oddballButton);
  295. #endregion
  296. // This demonstrates nonsense: Same as At(0)
  297. oddballButton = new Button
  298. {
  299. Text = "Center - Center - Percent(50)",
  300. X = Pos.Center () + Pos.Center () - Pos.Percent (50),
  301. Y = Pos.Bottom (oddballButton)
  302. };
  303. app.Add (oddballButton);
  304. // This demonstrates combining Percents)
  305. oddballButton = new Button
  306. {
  307. Text = "Percent(40) + Percent(10)", X = Pos.Percent (40) + Pos.Percent (10), Y = Pos.Bottom (oddballButton)
  308. };
  309. app.Add (oddballButton);
  310. // Demonstrate AnchorEnd - Button is anchored to bottom/right
  311. var anchorButton = new Button { Text = "Button using AnchorEnd", Y = Pos.AnchorEnd () };
  312. anchorButton.X = Pos.AnchorEnd ();
  313. anchorButton.Accepting += (s, e) =>
  314. {
  315. // This demonstrates how to have a dynamically sized button
  316. // Each time the button is clicked the button's text gets longer
  317. // The call to app.LayoutSubviews causes the Computed layout to
  318. // get updated.
  319. anchorButton.Text += "!";
  320. app.LayoutSubviews ();
  321. };
  322. app.Add (anchorButton);
  323. // Demonstrate AnchorEnd(n)
  324. // This is intentionally convoluted to illustrate potential bugs.
  325. var anchorEndLabel1 = new Label
  326. {
  327. Text = "This Label should be the 3rd to last line (AnchorEnd (3)).",
  328. TextAlignment = Alignment.Center,
  329. ColorScheme = Colors.ColorSchemes ["Menu"],
  330. Width = Dim.Fill (5),
  331. X = 5,
  332. Y = Pos.AnchorEnd (3)
  333. };
  334. app.Add (anchorEndLabel1);
  335. // Demonstrate DimCombine (via AnchorEnd(n) - 1)
  336. // This is intentionally convoluted to illustrate potential bugs.
  337. var anchorEndLabel2 = new TextField
  338. {
  339. Text =
  340. "This TextField should be the 4th to last line (AnchorEnd (3) - 1).",
  341. TextAlignment = Alignment.Start,
  342. ColorScheme = Colors.ColorSchemes ["Menu"],
  343. Width = Dim.Fill (5),
  344. X = 5,
  345. Y = Pos.AnchorEnd (3) - 1 // Pos.Combine
  346. };
  347. app.Add (anchorEndLabel2);
  348. // Demonstrate AnchorEnd() in combination with Pos.Align to align a set of buttons centered across the
  349. // bottom - 1
  350. // This is intentionally convoluted to illustrate potential bugs.
  351. var leftButton = new Button
  352. {
  353. Text = "Left",
  354. X = Pos.Align (Alignment.Center),
  355. Y = Pos.AnchorEnd () - 1
  356. };
  357. leftButton.Accepting += (s, e) =>
  358. {
  359. // This demonstrates how to have a dynamically sized button
  360. // Each time the button is clicked the button's text gets longer
  361. // The call to app.LayoutSubviews causes the Computed layout to
  362. // get updated.
  363. leftButton.Text += "!";
  364. app.LayoutSubviews ();
  365. };
  366. // show positioning vertically using Pos.AnchorEnd
  367. var centerButton = new Button
  368. {
  369. Text = "Center",
  370. X = Pos.Align (Alignment.Center),
  371. Y = Pos.AnchorEnd (2),
  372. };
  373. centerButton.Accepting += (s, e) =>
  374. {
  375. // This demonstrates how to have a dynamically sized button
  376. // Each time the button is clicked the button's text gets longer
  377. // The call to app.LayoutSubviews causes the Computed layout to
  378. // get updated.
  379. centerButton.Text += "!";
  380. app.LayoutSubviews ();
  381. };
  382. // show positioning vertically using another window and Pos.Bottom
  383. var rightButton = new Button
  384. {
  385. Text = "Right",
  386. X = Pos.Align (Alignment.Center),
  387. Y = Pos.Y (centerButton)
  388. };
  389. rightButton.Accepting += (s, e) =>
  390. {
  391. // This demonstrates how to have a dynamically sized button
  392. // Each time the button is clicked the button's text gets longer
  393. // The call to app.LayoutSubviews causes the Computed layout to
  394. // get updated.
  395. rightButton.Text += "!";
  396. app.LayoutSubviews ();
  397. };
  398. View [] buttons = { leftButton, centerButton, rightButton };
  399. app.Add (leftButton);
  400. app.Add (centerButton);
  401. app.Add (rightButton);
  402. Application.Run (app);
  403. app.Dispose ();
  404. Application.Shutdown ();
  405. }
  406. }