ContentScrolling.cs 14 KB

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