2
0

TreeViewFileSystem.cs 22 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564
  1. using System.IO.Abstractions;
  2. using System.Text;
  3. namespace UICatalog.Scenarios;
  4. [ScenarioMetadata ("File System Explorer", "Hierarchical file system explorer demonstrating TreeView.")]
  5. [ScenarioCategory ("Controls")]
  6. [ScenarioCategory ("TreeView")]
  7. [ScenarioCategory ("Files and IO")]
  8. public class TreeViewFileSystem : Scenario
  9. {
  10. private readonly FileSystemIconProvider _iconProvider = new ();
  11. private DetailsFrame _detailsFrame;
  12. private MenuItem _miArrowSymbols;
  13. private MenuItem _miBasicIcons;
  14. private MenuItem _miColoredSymbols;
  15. private MenuItem _miCursor;
  16. private MenuItem _miCustomColors;
  17. private MenuItem _miFullPaths;
  18. private MenuItem _miHighlightModelTextOnly;
  19. private MenuItem _miInvertSymbols;
  20. private MenuItem _miLeaveLastRow;
  21. private MenuItem _miMultiSelect;
  22. private MenuItem _miNerdIcons;
  23. private MenuItem _miNoSymbols;
  24. private MenuItem _miPlusMinus;
  25. private MenuItem _miShowLines;
  26. private MenuItem _miUnicodeIcons;
  27. /// <summary>A tree view where nodes are files and folders</summary>
  28. private TreeView<IFileSystemInfo> _treeViewFiles;
  29. public override void Main ()
  30. {
  31. Application.Init ();
  32. var win = new Window
  33. {
  34. Title = GetName (),
  35. Y = 1, // menu
  36. Height = Dim.Fill ()
  37. };
  38. var top = new Toplevel ();
  39. var menu = new MenuBar
  40. {
  41. Menus =
  42. [
  43. new (
  44. "_File",
  45. new MenuItem []
  46. {
  47. new (
  48. "_Quit",
  49. $"{Application.QuitKey}",
  50. () => Quit ()
  51. )
  52. }
  53. ),
  54. new (
  55. "_View",
  56. new []
  57. {
  58. _miFullPaths =
  59. new ("_Full Paths", "", () => SetFullName ())
  60. {
  61. Checked = false, CheckType = MenuItemCheckStyle.Checked
  62. },
  63. _miMultiSelect = new (
  64. "_Multi Select",
  65. "",
  66. () => SetMultiSelect ()
  67. )
  68. {
  69. Checked = true,
  70. CheckType = MenuItemCheckStyle
  71. .Checked
  72. }
  73. }
  74. ),
  75. new (
  76. "_Style",
  77. new []
  78. {
  79. _miShowLines =
  80. new ("_Show Lines", "", () => ShowLines ())
  81. {
  82. Checked = true, CheckType = MenuItemCheckStyle.Checked
  83. },
  84. null /*separator*/,
  85. _miPlusMinus =
  86. new (
  87. "_Plus Minus Symbols",
  88. "+ -",
  89. () => SetExpandableSymbols (
  90. (Rune)'+',
  91. (Rune)'-'
  92. )
  93. ) { Checked = true, CheckType = MenuItemCheckStyle.Radio },
  94. _miArrowSymbols =
  95. new (
  96. "_Arrow Symbols",
  97. "> v",
  98. () => SetExpandableSymbols (
  99. (Rune)'>',
  100. (Rune)'v'
  101. )
  102. ) { Checked = false, CheckType = MenuItemCheckStyle.Radio },
  103. _miNoSymbols =
  104. new (
  105. "_No Symbols",
  106. "",
  107. () => SetExpandableSymbols (
  108. default (Rune),
  109. null
  110. )
  111. ) { Checked = false, CheckType = MenuItemCheckStyle.Radio },
  112. null /*separator*/,
  113. _miColoredSymbols =
  114. new (
  115. "_Colored Symbols",
  116. "",
  117. () => ShowColoredExpandableSymbols ()
  118. ) { Checked = false, CheckType = MenuItemCheckStyle.Checked },
  119. _miInvertSymbols =
  120. new (
  121. "_Invert Symbols",
  122. "",
  123. () => InvertExpandableSymbols ()
  124. ) { Checked = false, CheckType = MenuItemCheckStyle.Checked },
  125. null /*separator*/,
  126. _miBasicIcons =
  127. new ("_Basic Icons", null, SetNoIcons)
  128. {
  129. Checked = false, CheckType = MenuItemCheckStyle.Radio
  130. },
  131. _miUnicodeIcons =
  132. new ("_Unicode Icons", null, SetUnicodeIcons)
  133. {
  134. Checked = false, CheckType = MenuItemCheckStyle.Radio
  135. },
  136. _miNerdIcons =
  137. new ("_Nerd Icons", null, SetNerdIcons)
  138. {
  139. Checked = false, CheckType = MenuItemCheckStyle.Radio
  140. },
  141. null /*separator*/,
  142. _miLeaveLastRow =
  143. new (
  144. "_Leave Last Row",
  145. "",
  146. () => SetLeaveLastRow ()
  147. ) { Checked = true, CheckType = MenuItemCheckStyle.Checked },
  148. _miHighlightModelTextOnly =
  149. new (
  150. "_Highlight Model Text Only",
  151. "",
  152. () => SetCheckHighlightModelTextOnly ()
  153. ) { Checked = false, CheckType = MenuItemCheckStyle.Checked },
  154. null /*separator*/,
  155. _miCustomColors =
  156. new (
  157. "C_ustom Colors Hidden Files",
  158. "Yellow/Red",
  159. () => SetCustomColors ()
  160. ) { Checked = false, CheckType = MenuItemCheckStyle.Checked },
  161. null /*separator*/,
  162. _miCursor = new (
  163. "Curs_or (MultiSelect only)",
  164. "",
  165. () => SetCursor ()
  166. ) { Checked = false, CheckType = MenuItemCheckStyle.Checked }
  167. }
  168. )
  169. ]
  170. };
  171. top.Add (menu);
  172. _treeViewFiles = new () { X = 0, Y = 0, Width = Dim.Percent (50), Height = Dim.Fill () };
  173. _treeViewFiles.DrawLine += TreeViewFiles_DrawLine;
  174. _treeViewFiles.VerticalScrollBar.AutoShow = false;
  175. _detailsFrame = new (_iconProvider)
  176. {
  177. X = Pos.Right (_treeViewFiles), Y = 0, Width = Dim.Fill (), Height = Dim.Fill ()
  178. };
  179. win.Add (_detailsFrame);
  180. _treeViewFiles.MouseClick += TreeViewFiles_MouseClick;
  181. _treeViewFiles.KeyDown += TreeViewFiles_KeyPress;
  182. _treeViewFiles.SelectionChanged += TreeViewFiles_SelectionChanged;
  183. SetupFileTree ();
  184. win.Add (_treeViewFiles);
  185. top.Add (win);
  186. _treeViewFiles.GoToFirst ();
  187. _treeViewFiles.Expand ();
  188. //SetupScrollBar ();
  189. _treeViewFiles.SetFocus ();
  190. UpdateIconCheckedness ();
  191. Application.Run (top);
  192. top.Dispose ();
  193. Application.Shutdown ();
  194. }
  195. private string AspectGetter (IFileSystemInfo f) { return (_iconProvider.GetIconWithOptionalSpace (f) + f.Name).Trim (); }
  196. private void InvertExpandableSymbols ()
  197. {
  198. _miInvertSymbols.Checked = !_miInvertSymbols.Checked;
  199. _treeViewFiles.Style.InvertExpandSymbolColors = (bool)_miInvertSymbols.Checked;
  200. _treeViewFiles.SetNeedsDraw ();
  201. }
  202. private void Quit () { Application.RequestStop (); }
  203. private void SetCheckHighlightModelTextOnly ()
  204. {
  205. _treeViewFiles.Style.HighlightModelTextOnly = !_treeViewFiles.Style.HighlightModelTextOnly;
  206. _miHighlightModelTextOnly.Checked = _treeViewFiles.Style.HighlightModelTextOnly;
  207. _treeViewFiles.SetNeedsDraw ();
  208. }
  209. private void SetCursor ()
  210. {
  211. _miCursor.Checked = !_miCursor.Checked;
  212. _treeViewFiles.CursorVisibility =
  213. _miCursor.Checked == true ? CursorVisibility.Default : CursorVisibility.Invisible;
  214. }
  215. private void SetCustomColors ()
  216. {
  217. _miCustomColors.Checked = !_miCustomColors.Checked;
  218. if (_miCustomColors.Checked == true)
  219. {
  220. _treeViewFiles.ColorGetter = m =>
  221. {
  222. if (m is IDirectoryInfo && m.Attributes.HasFlag (FileAttributes.Hidden))
  223. {
  224. return new ()
  225. {
  226. Focus = new (
  227. Color.BrightRed,
  228. _treeViewFiles.GetAttributeForRole (VisualRole.Focus).Background
  229. ),
  230. Normal = new (
  231. Color.BrightYellow,
  232. _treeViewFiles.GetAttributeForRole (VisualRole.Normal).Background
  233. )
  234. };
  235. ;
  236. }
  237. if (m is IFileInfo && m.Attributes.HasFlag (FileAttributes.Hidden))
  238. {
  239. return new ()
  240. {
  241. Focus = new (
  242. Color.BrightRed,
  243. _treeViewFiles.GetAttributeForRole (VisualRole.Focus).Background
  244. ),
  245. Normal = new (
  246. Color.BrightYellow,
  247. _treeViewFiles.GetAttributeForRole (VisualRole.Normal).Background
  248. )
  249. };
  250. ;
  251. }
  252. return null;
  253. };
  254. }
  255. else
  256. {
  257. _treeViewFiles.ColorGetter = null;
  258. }
  259. _treeViewFiles.SetNeedsDraw ();
  260. }
  261. private void SetExpandableSymbols (Rune expand, Rune? collapse)
  262. {
  263. _miPlusMinus.Checked = expand.Value == '+';
  264. _miArrowSymbols.Checked = expand.Value == '>';
  265. _miNoSymbols.Checked = expand.Value == default (int);
  266. _treeViewFiles.Style.ExpandableSymbol = expand;
  267. _treeViewFiles.Style.CollapseableSymbol = collapse;
  268. _treeViewFiles.SetNeedsDraw ();
  269. }
  270. private void SetFullName ()
  271. {
  272. _miFullPaths.Checked = !_miFullPaths.Checked;
  273. if (_miFullPaths.Checked == true)
  274. {
  275. _treeViewFiles.AspectGetter = f => f.FullName;
  276. }
  277. else
  278. {
  279. _treeViewFiles.AspectGetter = f => f.Name;
  280. }
  281. _treeViewFiles.SetNeedsDraw ();
  282. }
  283. private void SetLeaveLastRow ()
  284. {
  285. _miLeaveLastRow.Checked = !_miLeaveLastRow.Checked;
  286. _treeViewFiles.Style.LeaveLastRow = (bool)_miLeaveLastRow.Checked;
  287. }
  288. private void SetMultiSelect ()
  289. {
  290. _miMultiSelect.Checked = !_miMultiSelect.Checked;
  291. _treeViewFiles.MultiSelect = (bool)_miMultiSelect.Checked;
  292. }
  293. private void SetNerdIcons ()
  294. {
  295. _iconProvider.UseNerdIcons = true;
  296. UpdateIconCheckedness ();
  297. }
  298. private void SetNoIcons ()
  299. {
  300. _iconProvider.UseUnicodeCharacters = false;
  301. _iconProvider.UseNerdIcons = false;
  302. UpdateIconCheckedness ();
  303. }
  304. private void SetUnicodeIcons ()
  305. {
  306. _iconProvider.UseUnicodeCharacters = true;
  307. UpdateIconCheckedness ();
  308. }
  309. private void SetupFileTree ()
  310. {
  311. // setup how to build tree
  312. var fs = new FileSystem ();
  313. IEnumerable<IDirectoryInfo> rootDirs =
  314. DriveInfo.GetDrives ().Select (d => fs.DirectoryInfo.New (d.RootDirectory.FullName));
  315. _treeViewFiles.TreeBuilder = new FileSystemTreeBuilder ();
  316. _treeViewFiles.AddObjects (rootDirs);
  317. // Determines how to represent objects as strings on the screen
  318. _treeViewFiles.AspectGetter = AspectGetter;
  319. _iconProvider.IsOpenGetter = _treeViewFiles.IsExpanded;
  320. }
  321. //private void SetupScrollBar ()
  322. //{
  323. // // When using scroll bar leave the last row of the control free (for over-rendering with scroll bar)
  324. // _treeViewFiles.Style.LeaveLastRow = true;
  325. // var scrollBar = new ScrollBarView (_treeViewFiles, true);
  326. // scrollBar.ChangedPosition += (s, e) =>
  327. // {
  328. // _treeViewFiles.ScrollOffsetVertical = scrollBar.Position;
  329. // if (_treeViewFiles.ScrollOffsetVertical != scrollBar.Position)
  330. // {
  331. // scrollBar.Position = _treeViewFiles.ScrollOffsetVertical;
  332. // }
  333. // _treeViewFiles.SetNeedsDraw ();
  334. // };
  335. // scrollBar.OtherScrollBarView.ChangedPosition += (s, e) =>
  336. // {
  337. // _treeViewFiles.ScrollOffsetHorizontal = scrollBar.OtherScrollBarView.Position;
  338. // if (_treeViewFiles.ScrollOffsetHorizontal != scrollBar.OtherScrollBarView.Position)
  339. // {
  340. // scrollBar.OtherScrollBarView.Position = _treeViewFiles.ScrollOffsetHorizontal;
  341. // }
  342. // _treeViewFiles.SetNeedsDraw ();
  343. // };
  344. // _treeViewFiles.DrawingContent += (s, e) =>
  345. // {
  346. // scrollBar.Size = _treeViewFiles.ContentHeight;
  347. // scrollBar.Position = _treeViewFiles.ScrollOffsetVertical;
  348. // scrollBar.OtherScrollBarView.Size = _treeViewFiles.GetContentWidth (true);
  349. // scrollBar.OtherScrollBarView.Position = _treeViewFiles.ScrollOffsetHorizontal;
  350. // scrollBar.Refresh ();
  351. // };
  352. //}
  353. private void ShowColoredExpandableSymbols ()
  354. {
  355. _miColoredSymbols.Checked = !_miColoredSymbols.Checked;
  356. _treeViewFiles.Style.ColorExpandSymbol = (bool)_miColoredSymbols.Checked;
  357. _treeViewFiles.SetNeedsDraw ();
  358. }
  359. private void ShowContextMenu (Point screenPoint, IFileSystemInfo forObject)
  360. {
  361. PopoverMenu contextMenu = new ([new ("Properties", $"Show {forObject.Name} properties", () => ShowPropertiesOf (forObject))]);
  362. // Registering with the PopoverManager will ensure that the context menu is closed when the view is no longer focused
  363. // and the context menu is disposed when it is closed.
  364. _detailsFrame.App?.Popover?.Register (contextMenu);
  365. Application.Invoke (() => contextMenu?.MakeVisible (screenPoint));
  366. }
  367. private void ShowLines ()
  368. {
  369. _miShowLines.Checked = !_miShowLines.Checked;
  370. _treeViewFiles.Style.ShowBranchLines = (bool)_miShowLines.Checked!;
  371. _treeViewFiles.SetNeedsDraw ();
  372. }
  373. private void ShowPropertiesOf (IFileSystemInfo fileSystemInfo) { _detailsFrame.FileInfo = fileSystemInfo; }
  374. private void TreeViewFiles_DrawLine (object sender, DrawTreeViewLineEventArgs<IFileSystemInfo> e)
  375. {
  376. // Render directory icons in yellow
  377. if (e.Model is IDirectoryInfo d)
  378. {
  379. if (_iconProvider.UseNerdIcons || _iconProvider.UseUnicodeCharacters)
  380. {
  381. if (e.IndexOfModelText > 0 && e.IndexOfModelText < e.Cells.Count)
  382. {
  383. Cell cell = e.Cells [e.IndexOfModelText];
  384. cell.Attribute = new Attribute (
  385. Color.BrightYellow,
  386. cell.Attribute!.Value.Background,
  387. cell.Attribute!.Value.Style
  388. );
  389. }
  390. }
  391. }
  392. }
  393. private void TreeViewFiles_KeyPress (object sender, Key obj)
  394. {
  395. if (obj.KeyCode == (KeyCode.R | KeyCode.CtrlMask))
  396. {
  397. IFileSystemInfo selected = _treeViewFiles.SelectedObject;
  398. // nothing is selected
  399. if (selected == null)
  400. {
  401. return;
  402. }
  403. int? location = _treeViewFiles.GetObjectRow (selected);
  404. //selected object is offscreen or somehow not found
  405. if (location == null || location < 0 || location > _treeViewFiles.Frame.Height)
  406. {
  407. return;
  408. }
  409. ShowContextMenu (
  410. new (
  411. 5 + _treeViewFiles.Frame.X,
  412. location.Value + _treeViewFiles.Frame.Y + 2
  413. ),
  414. selected
  415. );
  416. }
  417. }
  418. private void TreeViewFiles_MouseClick (object sender, MouseEventArgs obj)
  419. {
  420. // if user right clicks
  421. if (obj.Flags.HasFlag (MouseFlags.Button3Clicked))
  422. {
  423. IFileSystemInfo rightClicked = _treeViewFiles.GetObjectOnRow (obj.Position.Y);
  424. // nothing was clicked
  425. if (rightClicked == null)
  426. {
  427. return;
  428. }
  429. ShowContextMenu (
  430. new (
  431. obj.Position.X + _treeViewFiles.Frame.X,
  432. obj.Position.Y + _treeViewFiles.Frame.Y + 2
  433. ),
  434. rightClicked
  435. );
  436. }
  437. }
  438. private void TreeViewFiles_SelectionChanged (object sender, SelectionChangedEventArgs<IFileSystemInfo> e) { ShowPropertiesOf (e.NewValue); }
  439. private void UpdateIconCheckedness ()
  440. {
  441. _miBasicIcons.Checked = !_iconProvider.UseNerdIcons && !_iconProvider.UseUnicodeCharacters;
  442. _miUnicodeIcons.Checked = _iconProvider.UseUnicodeCharacters;
  443. _miNerdIcons.Checked = _iconProvider.UseNerdIcons;
  444. _treeViewFiles.SetNeedsDraw ();
  445. }
  446. private class DetailsFrame : FrameView
  447. {
  448. private readonly FileSystemIconProvider _iconProvider;
  449. private IFileSystemInfo _fileInfo;
  450. public DetailsFrame (FileSystemIconProvider iconProvider)
  451. {
  452. Title = "Details";
  453. Visible = true;
  454. CanFocus = true;
  455. _iconProvider = iconProvider;
  456. }
  457. public IFileSystemInfo FileInfo
  458. {
  459. get => _fileInfo;
  460. set
  461. {
  462. _fileInfo = value;
  463. StringBuilder sb = null;
  464. if (_fileInfo is IFileInfo f)
  465. {
  466. Title = $"{_iconProvider.GetIconWithOptionalSpace (f)}{f.Name}".Trim ();
  467. sb = new ();
  468. sb.AppendLine ($"Path:\n {f.FullName}\n");
  469. sb.AppendLine ($"Size:\n {f.Length:N0} bytes\n");
  470. sb.AppendLine ($"Modified:\n {f.LastWriteTime}\n");
  471. sb.AppendLine ($"Created:\n {f.CreationTime}");
  472. }
  473. if (_fileInfo is IDirectoryInfo dir)
  474. {
  475. Title = $"{_iconProvider.GetIconWithOptionalSpace (dir)}{dir.Name}".Trim ();
  476. sb = new ();
  477. sb.AppendLine ($"Path:\n {dir?.FullName}\n");
  478. sb.AppendLine ($"Modified:\n {dir.LastWriteTime}\n");
  479. sb.AppendLine ($"Created:\n {dir.CreationTime}\n");
  480. }
  481. Text = sb.ToString ();
  482. }
  483. }
  484. }
  485. }