ContentScrolling.cs 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404
  1. using System;
  2. using System.ComponentModel;
  3. using System.Linq;
  4. using Terminal.Gui;
  5. using static UICatalog.Scenarios.Adornments;
  6. namespace UICatalog.Scenarios;
  7. [ScenarioMetadata ("Content Scrolling", "Demonstrates using View.Viewport and View.ContentSize to scroll content.")]
  8. [ScenarioCategory ("Layout")]
  9. [ScenarioCategory ("Drawing")]
  10. [ScenarioCategory ("Scrolling")]
  11. public class ContentScrolling : Scenario
  12. {
  13. private ViewDiagnosticFlags _diagnosticFlags;
  14. public class ScrollingDemoView : FrameView
  15. {
  16. public ScrollingDemoView ()
  17. {
  18. Width = Dim.Fill ();
  19. Height = Dim.Fill ();
  20. ColorScheme = Colors.ColorSchemes ["Base"];
  21. Text = "Text (ScrollingDemoView.Text). This is long text.\nThe second line.\n3\n4\n5th line\nLine 6. This is a longer line that should wrap automatically.";
  22. CanFocus = true;
  23. BorderStyle = LineStyle.Rounded;
  24. Arrangement = ViewArrangement.Fixed;
  25. ContentSize = new (60, 40);
  26. ViewportSettings |= ViewportSettings.ClearContentOnly;
  27. ViewportSettings |= ViewportSettings.ClipContentOnly;
  28. // Things this view knows how to do
  29. AddCommand (Command.ScrollDown, () => ScrollVertical (1));
  30. AddCommand (Command.ScrollUp, () => ScrollVertical (-1));
  31. AddCommand (Command.ScrollRight, () => ScrollHorizontal (1));
  32. AddCommand (Command.ScrollLeft, () => ScrollHorizontal (-1));
  33. // Default keybindings for all ListViews
  34. KeyBindings.Add (Key.CursorUp, Command.ScrollUp);
  35. KeyBindings.Add (Key.CursorDown, Command.ScrollDown);
  36. KeyBindings.Add (Key.CursorLeft, Command.ScrollLeft);
  37. KeyBindings.Add (Key.CursorRight, Command.ScrollRight);
  38. // Add a status label to the border that shows Viewport and ContentSize values. Bit of a hack.
  39. // TODO: Move to Padding with controls
  40. Border.Add (new Label { AutoSize = false, X = 20 });
  41. LayoutComplete += VirtualDemoView_LayoutComplete;
  42. MouseEvent += VirtualDemoView_MouseEvent;
  43. }
  44. private void VirtualDemoView_MouseEvent (object sender, MouseEventEventArgs e)
  45. {
  46. if (e.MouseEvent.Flags == MouseFlags.WheeledDown)
  47. {
  48. ScrollVertical (1);
  49. return;
  50. }
  51. if (e.MouseEvent.Flags == MouseFlags.WheeledUp)
  52. {
  53. ScrollVertical (-1);
  54. return;
  55. }
  56. if (e.MouseEvent.Flags == MouseFlags.WheeledRight)
  57. {
  58. ScrollHorizontal (1);
  59. return;
  60. }
  61. if (e.MouseEvent.Flags == MouseFlags.WheeledLeft)
  62. {
  63. ScrollHorizontal (-1);
  64. }
  65. }
  66. private void VirtualDemoView_LayoutComplete (object sender, LayoutEventArgs e)
  67. {
  68. Label status = Border.Subviews.OfType<Label> ().FirstOrDefault ();
  69. if (status is { })
  70. {
  71. status.Title = $"Frame: {Frame}\n\nViewport: {Viewport}, ContentSize = {ContentSize}";
  72. status.Width = Border.Frame.Width - status.Frame.X - Border.Thickness.Right;
  73. status.Height = Border.Thickness.Top;
  74. }
  75. SetNeedsDisplay ();
  76. }
  77. }
  78. public override void Main ()
  79. {
  80. Application.Init ();
  81. _diagnosticFlags = View.Diagnostics;
  82. Window app = new ()
  83. {
  84. Title = $"{Application.QuitKey} to Quit - Scenario: {GetName ()}",
  85. // Use a different colorscheme so ViewSettings.ClearContentOnly is obvious
  86. ColorScheme = Colors.ColorSchemes ["Toplevel"]
  87. };
  88. var editor = new AdornmentsEditor ();
  89. app.Add (editor);
  90. var view = new ScrollingDemoView
  91. {
  92. Title = "Demo View",
  93. X = Pos.Right(editor),
  94. Width = Dim.Fill (),
  95. Height = Dim.Fill ()
  96. };
  97. app.Add (view);
  98. // Add Scroll Setting UI to Padding
  99. view.Padding.Thickness = new (0, 3, 0, 0);
  100. view.Padding.ColorScheme = Colors.ColorSchemes ["Error"];
  101. var cbAllowNegativeX = new CheckBox
  102. {
  103. Title = "Allow _X < 0",
  104. Y = 0,
  105. CanFocus = false
  106. };
  107. cbAllowNegativeX.Checked = view.ViewportSettings.HasFlag (ViewportSettings.AllowNegativeX);
  108. cbAllowNegativeX.Toggled += AllowNegativeX_Toggled;
  109. void AllowNegativeX_Toggled (object sender, StateEventArgs<bool?> e)
  110. {
  111. if (e.NewValue == true)
  112. {
  113. view.ViewportSettings |= ViewportSettings.AllowNegativeX;
  114. }
  115. else
  116. {
  117. view.ViewportSettings &= ~ViewportSettings.AllowNegativeX;
  118. }
  119. }
  120. view.Padding.Add (cbAllowNegativeX);
  121. var cbAllowNegativeY = new CheckBox
  122. {
  123. Title = "Allow _Y < 0",
  124. X = Pos.Right (cbAllowNegativeX) + 1,
  125. Y = 0,
  126. CanFocus = false
  127. };
  128. cbAllowNegativeY.Checked = view.ViewportSettings.HasFlag (ViewportSettings.AllowNegativeY);
  129. cbAllowNegativeY.Toggled += AllowNegativeY_Toggled;
  130. void AllowNegativeY_Toggled (object sender, StateEventArgs<bool?> e)
  131. {
  132. if (e.NewValue == true)
  133. {
  134. view.ViewportSettings |= ViewportSettings.AllowNegativeY;
  135. }
  136. else
  137. {
  138. view.ViewportSettings &= ~ViewportSettings.AllowNegativeY;
  139. }
  140. }
  141. view.Padding.Add (cbAllowNegativeY);
  142. var cbAllowXGreaterThanContentWidth = new CheckBox
  143. {
  144. Title = "All_ow X > Content",
  145. Y = Pos.Bottom (cbAllowNegativeX),
  146. CanFocus = false
  147. };
  148. cbAllowXGreaterThanContentWidth.Checked = view.ViewportSettings.HasFlag (ViewportSettings.AllowXGreaterThanContentWidth);
  149. cbAllowXGreaterThanContentWidth.Toggled += AllowXGreaterThanContentWidth_Toggled;
  150. void AllowXGreaterThanContentWidth_Toggled (object sender, StateEventArgs<bool?> e)
  151. {
  152. if (e.NewValue == true)
  153. {
  154. view.ViewportSettings |= ViewportSettings.AllowXGreaterThanContentWidth;
  155. }
  156. else
  157. {
  158. view.ViewportSettings &= ~ViewportSettings.AllowXGreaterThanContentWidth;
  159. }
  160. }
  161. view.Padding.Add (cbAllowXGreaterThanContentWidth);
  162. var cbAllowYGreaterThanContentHeight = new CheckBox
  163. {
  164. Title = "Allo_w Y > Content",
  165. X = Pos.Right (cbAllowXGreaterThanContentWidth) + 1,
  166. Y = Pos.Bottom (cbAllowNegativeX),
  167. CanFocus = false
  168. };
  169. cbAllowYGreaterThanContentHeight.Checked = view.ViewportSettings.HasFlag (ViewportSettings.AllowYGreaterThanContentHeight);
  170. cbAllowYGreaterThanContentHeight.Toggled += AllowYGreaterThanContentHeight_Toggled;
  171. void AllowYGreaterThanContentHeight_Toggled (object sender, StateEventArgs<bool?> e)
  172. {
  173. if (e.NewValue == true)
  174. {
  175. view.ViewportSettings |= ViewportSettings.AllowYGreaterThanContentHeight;
  176. }
  177. else
  178. {
  179. view.ViewportSettings &= ~ViewportSettings.AllowYGreaterThanContentHeight;
  180. }
  181. }
  182. view.Padding.Add (cbAllowYGreaterThanContentHeight);
  183. var labelContentSize = new Label
  184. {
  185. Title = "_ContentSize:",
  186. Y = Pos.Bottom (cbAllowYGreaterThanContentHeight)
  187. };
  188. var contentSizeWidth = new Buttons.NumericUpDown<int>
  189. {
  190. Value = view.ContentSize.Width,
  191. X = Pos.Right (labelContentSize) + 1,
  192. Y = Pos.Top (labelContentSize)
  193. };
  194. contentSizeWidth.ValueChanging += ContentSizeWidth_ValueChanged;
  195. void ContentSizeWidth_ValueChanged (object sender, StateEventArgs<int> e)
  196. {
  197. if (e.NewValue < 0)
  198. {
  199. e.Cancel = true;
  200. return;
  201. }
  202. view.ContentSize = view.ContentSize with { Width = e.NewValue };
  203. }
  204. var labelComma = new Label
  205. {
  206. Title = ",",
  207. X = Pos.Right (contentSizeWidth),
  208. Y = Pos.Top (labelContentSize)
  209. };
  210. var contentSizeHeight = new Buttons.NumericUpDown<int>
  211. {
  212. Value = view.ContentSize.Height,
  213. X = Pos.Right (labelComma) + 1,
  214. Y = Pos.Top (labelContentSize),
  215. CanFocus = false
  216. };
  217. contentSizeHeight.ValueChanging += ContentSizeHeight_ValueChanged;
  218. void ContentSizeHeight_ValueChanged (object sender, StateEventArgs<int> e)
  219. {
  220. if (e.NewValue < 0)
  221. {
  222. e.Cancel = true;
  223. return;
  224. }
  225. view.ContentSize = view.ContentSize with { Height = e.NewValue };
  226. }
  227. var cbClearOnlyVisible = new CheckBox
  228. {
  229. Title = "ClearContentOnly",
  230. X = Pos.Right (contentSizeHeight) + 1,
  231. Y = Pos.Top (labelContentSize),
  232. CanFocus = false
  233. };
  234. cbClearOnlyVisible.Checked = view.ViewportSettings.HasFlag (ViewportSettings.ClearContentOnly);
  235. cbClearOnlyVisible.Toggled += ClearVisibleContentOnly_Toggled;
  236. void ClearVisibleContentOnly_Toggled (object sender, StateEventArgs<bool?> e)
  237. {
  238. if (e.NewValue == true)
  239. {
  240. view.ViewportSettings |= ViewportSettings.ClearContentOnly;
  241. }
  242. else
  243. {
  244. view.ViewportSettings &= ~ViewportSettings.ClearContentOnly;
  245. }
  246. }
  247. var cbDoNotClipContent = new CheckBox
  248. {
  249. Title = "ClipContentOnly",
  250. X = Pos.Right (cbClearOnlyVisible) + 1,
  251. Y = Pos.Top (labelContentSize),
  252. CanFocus = false
  253. };
  254. cbDoNotClipContent.Checked = view.ViewportSettings.HasFlag (ViewportSettings.ClipContentOnly);
  255. cbDoNotClipContent.Toggled += ClipVisibleContentOnly_Toggled;
  256. void ClipVisibleContentOnly_Toggled (object sender, StateEventArgs<bool?> e)
  257. {
  258. if (e.NewValue == true)
  259. {
  260. view.ViewportSettings |= ViewportSettings.ClipContentOnly;
  261. }
  262. else
  263. {
  264. view.ViewportSettings &= ~ViewportSettings.ClipContentOnly;
  265. }
  266. }
  267. view.Padding.Add (labelContentSize, contentSizeWidth, labelComma, contentSizeHeight, cbClearOnlyVisible, cbDoNotClipContent);
  268. // Add demo views to show that things work correctly
  269. var textField = new TextField { X = 20, Y = 7, Width = 15, Text = "Test TextField" };
  270. var colorPicker = new ColorPicker { Title = "BG", BoxHeight = 1, BoxWidth = 1, X = Pos.AnchorEnd (11), Y = 10 };
  271. colorPicker.BorderStyle = LineStyle.RoundedDotted;
  272. colorPicker.ColorChanged += (s, e) =>
  273. {
  274. colorPicker.SuperView.ColorScheme = new (colorPicker.SuperView.ColorScheme)
  275. {
  276. Normal = new (
  277. colorPicker.SuperView.ColorScheme.Normal.Foreground,
  278. e.Color
  279. )
  280. };
  281. };
  282. var textView = new TextView
  283. {
  284. X = Pos.Center (),
  285. Y = 10,
  286. Title = "TextView",
  287. Text = "I have a 3 row top border.\nMy border inherits from the SuperView.\nI have 3 lines of text with room for 2.",
  288. AllowsTab = false,
  289. Width = 30,
  290. Height = 6 // TODO: Use Dim.Auto
  291. };
  292. textView.Border.Thickness = new (1, 3, 1, 1);
  293. var charMap = new CharMap
  294. {
  295. X = Pos.Center (),
  296. Y = Pos.Bottom (textView) + 1,
  297. Width = 30,
  298. Height = 10
  299. };
  300. charMap.Accept += (s, e) =>
  301. MessageBox.Query (20, 7, "Hi", $"Am I a {view.GetType ().Name}?", "Yes", "No");
  302. var buttonAnchoredRight = new Button
  303. {
  304. X = Pos.AnchorEnd (10), Y = 0, Text = "Button"
  305. };
  306. var labelAnchoredBottomLeft = new Label
  307. {
  308. AutoSize = false,
  309. Y = Pos.AnchorEnd (3),
  310. Width = 25,
  311. Height = Dim.Fill (),
  312. Text = "Label\nY=AnchorEnd(3),Height=Dim.Fill()"
  313. };
  314. view.Margin.Data = "Margin";
  315. view.Margin.Thickness = new (0);
  316. view.Border.Data = "Border";
  317. view.Border.Thickness = new (3);
  318. view.Padding.Data = "Padding";
  319. view.Add (buttonAnchoredRight, textField, colorPicker, charMap, textView, labelAnchoredBottomLeft);
  320. var longLabel = new Label
  321. {
  322. Id = "label2",
  323. X = 0,
  324. Y = 30,
  325. Text =
  326. "This label is long. It should clip to the ContentArea if ClipContentOnly is set. This is a virtual scrolling demo. Use the arrow keys and/or mouse wheel to scroll the content."
  327. };
  328. longLabel.TextFormatter.WordWrap = true;
  329. view.Add (longLabel);
  330. editor.Initialized += (s, e) => { editor.ViewToEdit = view; };
  331. app.Closed += (s, e) => View.Diagnostics = _diagnosticFlags;
  332. Application.Run (app);
  333. app.Dispose ();
  334. Application.Shutdown ();
  335. }
  336. }