ComputedLayout.cs 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325
  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 {
  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 using the Computed (Dim and Pos) Layout System")]
  16. [ScenarioCategory ("Layout")]
  17. class ComputedLayout : Scenario {
  18. public override void Setup ()
  19. {
  20. var menu = new MenuBar (new MenuBarItem [] {
  21. new MenuBarItem ("_Settings", new MenuItem [] {
  22. null,
  23. new MenuItem ("_Quit", "", () => Quit()),
  24. }),
  25. new MenuBarItem ("_All Controls", "Tests all controls", () => DemoAllViewClasses() ),
  26. });
  27. Top.Add (menu);
  28. var statusBar = new StatusBar (new StatusItem [] {
  29. new StatusItem(Key.ControlQ, "~^Q~ Quit", () => Quit()),
  30. });
  31. Top.Add (statusBar);
  32. //Top.LayoutStyle = LayoutStyle.Computed;
  33. // Demonstrate using Dim to create a horizontal ruler that always measures the parent window's width
  34. // BUGBUG: Dim.Fill returns too big a value sometimes.
  35. const string rule = "|123456789";
  36. var horizontalRuler = new Label ("") {
  37. X = 0,
  38. Y = 0,
  39. Width = Dim.Fill (1), // BUGBUG: I don't think this should be needed; DimFill() should respect container's frame. X does.
  40. ColorScheme = Colors.Error
  41. };
  42. Win.Add (horizontalRuler);
  43. // Demonstrate using Dim to create a vertical ruler that always measures the parent window's height
  44. // TODO: Either build a custom control for this or implement linewrap in Label #352
  45. const string vrule = "|\n1\n2\n3\n4\n5\n6\n7\n8\n9\n";
  46. var verticalRuler = new Label ("") {
  47. X = 0,
  48. Y = 0,
  49. Width = 1,
  50. Height = Dim.Fill (),
  51. ColorScheme = Colors.Error
  52. };
  53. Win.LayoutComplete += (sender, a) => {
  54. horizontalRuler.Text = rule.Repeat ((int)Math.Ceiling ((double)(horizontalRuler.Bounds.Width) / (double)rule.Length)) [0..(horizontalRuler.Bounds.Width)];
  55. verticalRuler.Text = vrule.Repeat ((int)Math.Ceiling ((double)(verticalRuler.Bounds.Height * 2) / (double)rule.Length)) [0..(verticalRuler.Bounds.Height * 2)];
  56. };
  57. Win.Add (verticalRuler);
  58. // Demonstrate At - Absolute Layout using Pos
  59. var absoluteButton = new Button ("Absolute At(2,1)") {
  60. X = Pos.At (2),
  61. Y = Pos.At (1)
  62. };
  63. Win.Add (absoluteButton);
  64. // Demonstrate using Dim to create a window that fills the parent with a margin
  65. int margin = 10;
  66. var subWin = new Window ($"Centered Sub Window with {margin} character margin") {
  67. X = Pos.Center (),
  68. Y = 2,
  69. Width = Dim.Fill (margin),
  70. Height = 7
  71. };
  72. Win.Add (subWin);
  73. int i = 1;
  74. string txt = "Resize the terminal to see computed layout in action.";
  75. var labelList = new List<Label> ();
  76. labelList.Add (new Label ($"The lines below show different TextAlignments"));
  77. labelList.Add (new Label ($"{i++}-{txt}") { TextAlignment = Terminal.Gui.TextAlignment.Left, Width = Dim.Fill (), X = 0, Y = Pos.Bottom (labelList.LastOrDefault ()), ColorScheme = Colors.Dialog });
  78. labelList.Add (new Label ($"{i++}-{txt}") { TextAlignment = Terminal.Gui.TextAlignment.Right, Width = Dim.Fill (), X = 0, Y = Pos.Bottom (labelList.LastOrDefault ()), ColorScheme = Colors.Dialog });
  79. labelList.Add (new Label ($"{i++}-{txt}") { TextAlignment = Terminal.Gui.TextAlignment.Centered, Width = Dim.Fill (), X = 0, Y = Pos.Bottom (labelList.LastOrDefault ()), ColorScheme = Colors.Dialog });
  80. labelList.Add (new Label ($"{i++}-{txt}") { TextAlignment = Terminal.Gui.TextAlignment.Justified, Width = Dim.Fill (), X = 0, Y = Pos.Bottom (labelList.LastOrDefault ()), ColorScheme = Colors.Dialog });
  81. subWin.Add (labelList.ToArray ());
  82. // #522 repro?
  83. var frameView = new FrameView ($"Centered FrameView with {margin} character margin") {
  84. X = Pos.Center (),
  85. Y = Pos.Bottom (subWin),
  86. Width = Dim.Fill (margin),
  87. Height = 7
  88. };
  89. Win.Add (frameView);
  90. i = 1;
  91. labelList = new List<Label> ();
  92. labelList.Add (new Label ($"The lines below show different TextAlignments"));
  93. labelList.Add (new Label ($"{i++}-{txt}") { TextAlignment = Terminal.Gui.TextAlignment.Left, Width = Dim.Fill (), X = 0, Y = Pos.Bottom (labelList.LastOrDefault ()), ColorScheme = Colors.Dialog });
  94. labelList.Add (new Label ($"{i++}-{txt}") { TextAlignment = Terminal.Gui.TextAlignment.Right, Width = Dim.Fill (), X = 0, Y = Pos.Bottom (labelList.LastOrDefault ()), ColorScheme = Colors.Dialog });
  95. labelList.Add (new Label ($"{i++}-{txt}") { TextAlignment = Terminal.Gui.TextAlignment.Centered, Width = Dim.Fill (), X = 0, Y = Pos.Bottom (labelList.LastOrDefault ()), ColorScheme = Colors.Dialog });
  96. labelList.Add (new Label ($"{i++}-{txt}") { TextAlignment = Terminal.Gui.TextAlignment.Justified, Width = Dim.Fill (), X = 0, Y = Pos.Bottom (labelList.LastOrDefault ()), ColorScheme = Colors.Dialog });
  97. frameView.Add (labelList.ToArray ());
  98. // Demonstrate Dim & Pos using percentages - a TextField that is 30% height and 80% wide
  99. var textView = new TextView () {
  100. X = Pos.Center (),
  101. Y = Pos.Percent (50),
  102. Width = Dim.Percent (80),
  103. Height = Dim.Percent (30),
  104. ColorScheme = Colors.TopLevel,
  105. };
  106. textView.Text = "This text view should be half-way down the terminal,\n20% of its height, and 80% of its width.";
  107. Win.Add (textView);
  108. // Demonstrate AnchorEnd - Button is anchored to bottom/right
  109. var anchorButton = new Button ("Anchor End") {
  110. Y = Pos.AnchorEnd () - 1,
  111. };
  112. // TODO: Use Pos.Width instead of (Right-Left) when implemented (#502)
  113. anchorButton.X = Pos.AnchorEnd () - (Pos.Right (anchorButton) - Pos.Left (anchorButton));
  114. anchorButton.Clicked = () => {
  115. // Ths demonstrates how to have a dynamically sized button
  116. // Each time the button is clicked the button's text gets longer
  117. // The call to Win.LayoutSubviews causes the Computed layout to
  118. // get updated.
  119. anchorButton.Text += "!";
  120. Win.LayoutSubviews ();
  121. };
  122. Win.Add (anchorButton);
  123. // Centering multiple controls horizontally.
  124. // This is intentionally convoluted to illustrate potential bugs.
  125. var bottomLabel = new Label ("This should be the 2nd to last line (Bug #xxx).") {
  126. TextAlignment = Terminal.Gui.TextAlignment.Centered,
  127. ColorScheme = Colors.Menu,
  128. Width = Dim.Fill (),
  129. X = Pos.Center (),
  130. Y = Pos.Bottom (Win) - 4 // BUGBUG: -2 should be two lines above border; but it has to be -4
  131. };
  132. Win.Add (bottomLabel);
  133. // Show positioning vertically using Pos.Bottom
  134. // BUGBUG: -1 should be just above border; but it has to be -3
  135. var leftButton = new Button ("Left") {
  136. Y = Pos.Bottom (Win) - 3
  137. };
  138. leftButton.Clicked = () => {
  139. // Ths demonstrates how to have a dynamically sized button
  140. // Each time the button is clicked the button's text gets longer
  141. // The call to Win.LayoutSubviews causes the Computed layout to
  142. // get updated.
  143. leftButton.Text += "!";
  144. Win.LayoutSubviews ();
  145. };
  146. // show positioning vertically using Pos.AnchorEnd
  147. var centerButton = new Button ("Center") {
  148. X = Pos.Center (),
  149. Y = Pos.AnchorEnd () - 1
  150. };
  151. centerButton.Clicked = () => {
  152. // Ths demonstrates how to have a dynamically sized button
  153. // Each time the button is clicked the button's text gets longer
  154. // The call to Win.LayoutSubviews causes the Computed layout to
  155. // get updated.
  156. centerButton.Text += "!";
  157. Win.LayoutSubviews ();
  158. };
  159. // show positioning vertically using another window and Pos.Bottom
  160. var rightButton = new Button ("Right") {
  161. Y = Pos.Y (centerButton)
  162. };
  163. rightButton.Clicked = () => {
  164. // Ths demonstrates how to have a dynamically sized button
  165. // Each time the button is clicked the button's text gets longer
  166. // The call to Win.LayoutSubviews causes the Computed layout to
  167. // get updated.
  168. rightButton.Text += "!";
  169. Win.LayoutSubviews ();
  170. };
  171. // Center three buttons with 5 spaces between them
  172. // TODO: Use Pos.Width instead of (Right-Left) when implemented (#502)
  173. leftButton.X = Pos.Left (centerButton) - (Pos.Right (leftButton) - Pos.Left (leftButton)) - 5;
  174. rightButton.X = Pos.Right (centerButton) + 5;
  175. Win.Add (leftButton);
  176. Win.Add (centerButton);
  177. Win.Add (rightButton);
  178. }
  179. /// <summary>
  180. /// Displays a Dialog that uses a wizard (next/prev) idom to step through each class derived from View
  181. /// testing various Computed layout scenarios
  182. /// </summary>
  183. private void DemoAllViewClasses ()
  184. {
  185. List<Type> GetAllViewClassesCollection ()
  186. {
  187. List<Type> objects = new List<Type> ();
  188. foreach (Type type in typeof (View).Assembly.GetTypes ()
  189. .Where (myType => myType.IsClass && !myType.IsAbstract && myType.IsPublic && myType.IsSubclassOf (typeof (View)))) {
  190. objects.Add (type);
  191. }
  192. return objects;
  193. }
  194. var viewClasses = GetAllViewClassesCollection ().OrderByDescending (c => c.Name).ToList ();
  195. var curClass = 0;
  196. var closeBtn = new Button ("_Close") {
  197. Clicked = () => {
  198. Application.RequestStop ();
  199. },
  200. };
  201. var nextBtn = new Button ("_Next");
  202. var prevBtn = new Button ("_Previous");
  203. var dialog = new Dialog ("Demoing all View classs", new [] { prevBtn, nextBtn, closeBtn });
  204. var label = new Label ("Class:") {
  205. X = 0,
  206. Y = 0,
  207. };
  208. dialog.Add (label);
  209. var currentClassLabel = new Label ("") {
  210. X = Pos.Right (label) + 1,
  211. Y = Pos.Y (label),
  212. };
  213. dialog.Add (currentClassLabel);
  214. View curView = null;
  215. void SetCurrentClass ()
  216. {
  217. currentClassLabel.Text = $"{viewClasses [curClass].Name}";
  218. // Remove existing class, if any
  219. if (curView != null) {
  220. dialog.Remove (curView);
  221. curView = null;
  222. }
  223. // Instantiate view
  224. curView = (View)Activator.CreateInstance (viewClasses [curClass]);
  225. curView.X = Pos.Center ();
  226. curView.Y = Pos.Center ();
  227. curView.Width = Dim.Fill (5);
  228. curView.Height = Dim.Fill (5);
  229. // If the view supports a Text property, set it so we have something to look at
  230. if (viewClasses [curClass].GetProperty("Text") != null) {
  231. curView.GetType ().GetProperty ("Text")?.GetSetMethod ()?.Invoke (curView, new [] { ustring.Make("09/10/1966") });
  232. }
  233. // If the view supports a Title property, set it so we have something to look at
  234. if (viewClasses [curClass].GetProperty ("Title") != null) {
  235. curView.GetType ().GetProperty ("Title")?.GetSetMethod ()?.Invoke (curView, new [] { ustring.Make ("Test Title") });
  236. }
  237. dialog.Add (curView);
  238. dialog.LayoutSubviews ();
  239. }
  240. nextBtn.Clicked = () => {
  241. curClass++;
  242. if (curClass >= viewClasses.Count) {
  243. curClass = 0;
  244. }
  245. SetCurrentClass ();
  246. };
  247. prevBtn.Clicked = () => {
  248. if (curClass == 0) {
  249. curClass = viewClasses.Count - 1;
  250. } else {
  251. curClass--;
  252. }
  253. SetCurrentClass ();
  254. };
  255. SetCurrentClass ();
  256. Application.Run (dialog);
  257. }
  258. public override void Run ()
  259. {
  260. base.Run ();
  261. }
  262. private void Quit ()
  263. {
  264. Application.RequestStop ();
  265. }
  266. }
  267. internal static class StringExtensions {
  268. public static string Repeat (this string instr, int n)
  269. {
  270. if (n <= 0) {
  271. return null;
  272. }
  273. if (string.IsNullOrEmpty (instr) || n == 1) {
  274. return instr;
  275. }
  276. return new StringBuilder (instr.Length * n)
  277. .Insert (0, instr, n)
  278. .ToString ();
  279. }
  280. }
  281. }