TreeViewFileSystem.cs 23 KB

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