Files
Terminal.Gui/Examples/UICatalog/Scenarios/TreeViewFileSystem.cs
Tig 7422385457 Fixes #4057 - MASSIVE! Fully implements ColorScheme->Scheme + VisualRole + Colors.->SchemeManager. (#4062)
* touching publish.yml

* ColorScheme->Scheme

* ColorScheme->Scheme 2

* Prototype of GetAttributeForRole

* Badly broke CM

* Further Badly broke CM

* Refactored CM big-time. View still broken

* All unit test pass again. Tons added. CM is still WIP, but Schemes is not mostly refactored and working.

* Actually:
All unit test pass again.
Tons added.
CM is still WIP, but Schemes is not mostly refactored and working.

* Bug fixes.
DeepMemberWiseClone cleanup

* Further cleanup of Scope<T>, ConfigProperty, etc.

* Made ConfigManager thread safe.

* WIP: Broken

* WIP: new deep clone impl

* WIP: new deep clone impl is done. Now fixing CM

* WIP:
- config.md
- Working on AOT clean up
- Core CM is broken; but known.

* WIP

* Merged.
Removed CM from Application.Init

* WIP

* More WIP; Less broke

* All CM unit tests pass... Not sure if it actually works though

* All unit tests pass... Themes are broken though in UI Cat

* CM Ready for review?

* Fixed failures due to TextStyles PR

* Working on Scheme/Attribute

* Working on Scheme/Attribute 2

* Working on Scheme/Attribute 3

* Working on Scheme/Attribute 4

* Working on Scheme/Attribute 5

* Working on Scheme/Attribute 6

* Added test to show how awful memory usage is

* Improved schema. Updated config.json

* Nade Scope<T> concurrentdictionary and added test to prove

* Made Themes ConcrurrentDictionary. Added bunches of tests

* Code cleanup

* Code cleanup 2

* Code cleanup 3

* Tweaking Scheme

* ClearJsonErrors

* ClearJsonErrors2

* Updated Attribute API

* It all (mostly) works!

* Skip odd unit test

* Messed with Themes

* Theme tweaks

* Code reorg. New .md stuff

* Fixed Enabled. Added mock driver

* Fixed a bunch of View.Enabled related issues

* Scheme -> Get/SetScheme()

* Cleanup

* Cleanup2

* Broke something

* Fixed everything

* Made CM.Enable better

* Text Style Scenario

* Added comments

* Fixed UI Catalog Theme Changing

* Fixed more dynamic CM update stuff

* Warning cleanup

* New Default Theme

* fixed unit test

* Refactoring Scheme and Attribute to fix inheritance

* more unit tests

* ConfigProperty is not updating schemes correctly

* All unit tests pass.
Code cleanup

* All unit tests pass.
Code cleanup2

* Fixed unit tests

* Upgraded TextField and TextView

* Fixed TextView !Enabled bug

* More updates to TextView. More unit tests for SchemeManager

* Upgraded CharMap

* API docs

* Fixe HexView API

* upgrade HexView

* Fixed shortcut KeyView

* Fixed more bugs. Added new themes

* updated themes

* upgraded Border

* Fixed themes memory usage...mostly

* Fixed themes memory usage...mostly2

* Fixed themes memory usage...2

* Fixed themes memory usage...3

* Added new colors

* Fixed GetHardCodedConfig bug

* Added Themes Scenario - WIP

* Added Themes Scenario

* Tweaked Themes Scenario

* Code cleanup

* Fixed json schmea

* updated deepdives

* updated deepdives

* Tweaked Themes Scenario

* Made Schemes a concurrent dict

* Test cleanup

* Thread safe ConfigProperty tests

* trying to make things more thread safe

* more trying to make things more thread safe

* Fixing bugs in shadowview

* Fixing bugs in shadowview 2

* Refactored GetViewsUnderMouse to GetViewsUnderLocation etc...

* Fixed dupe unit tests?

* Added better description of layout and coordiantes to deep dive

* Added better description of layout and coordiantes to deep dive

* Modified tests that call v2.AddTimeout; they were returning true which means restart the timer!
This was causing mac/linux unit test failures.
I think

* Fixed auto scheme.
Broke TextView/TextField selection

* Realized Attribute.IsExplicitlySet is stupid; just use nullable

* Fixed Attribute. Simplified. MOre theme testing

* Updated themes again

* GetViewsUnderMouse to GetViewsUnderLocation broke TransparentMouse.

* Fixing mouseunder bugs

* rewriting...

* All working again.
Shadows are now slick as snot.
GetViewsUnderLocation is rewritten to actually work and be readable.
Tons more low-level unit tests.
Margin is now actually ViewportSettings.Transparent.

* Code cleanup

* Code cleanup

* Code cleanup of color apis

* Fixed Hover/Highlight

* Update Examples/UICatalog/Scenarios/AllViewsTester.cs

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>

* Update Examples/UICatalog/Scenarios/CharacterMap/CharacterMap.cs

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>

* Update Examples/UICatalog/Scenarios/Clipping.cs

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>

* Fixed race condition?

* reverted

* Simplified Attribute API by removing events from SetAttributeForRole

* Removed recursion from GetViewsAtLocation

* Removed unneeded code

* Code clean up.
Fixed Scheme bug.

* reverted temporary disable

* Adjusted scheme algo

* Upgraded TextValidateField

* Fixed TextValidate bugs

* Tweaks

* Frameview rounded border by default

* API doc cleanup

* Readme fix

* Addressed tznind feeback

* Fixed more unit test issues by protecting Application statics from being set if Application.Initialized is not true

* Fixed more unit test issues by protecting Application statics from being set if Application.Initialized is not true 2

* cleanup

---------

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2025-05-29 13:55:54 -06:00

566 lines
22 KiB
C#

using System.IO.Abstractions;
using System.Text;
using Terminal.Gui;
namespace UICatalog.Scenarios;
[ScenarioMetadata ("File System Explorer", "Hierarchical file system explorer demonstrating TreeView.")]
[ScenarioCategory ("Controls")]
[ScenarioCategory ("TreeView")]
[ScenarioCategory ("Files and IO")]
public class TreeViewFileSystem : Scenario
{
private readonly FileSystemIconProvider _iconProvider = new ();
private DetailsFrame _detailsFrame;
private MenuItem _miArrowSymbols;
private MenuItem _miBasicIcons;
private MenuItem _miColoredSymbols;
private MenuItem _miCursor;
private MenuItem _miCustomColors;
private MenuItem _miFullPaths;
private MenuItem _miHighlightModelTextOnly;
private MenuItem _miInvertSymbols;
private MenuItem _miLeaveLastRow;
private MenuItem _miMultiSelect;
private MenuItem _miNerdIcons;
private MenuItem _miNoSymbols;
private MenuItem _miPlusMinus;
private MenuItem _miShowLines;
private MenuItem _miUnicodeIcons;
/// <summary>A tree view where nodes are files and folders</summary>
private TreeView<IFileSystemInfo> _treeViewFiles;
public override void Main ()
{
Application.Init ();
var win = new Window
{
Title = GetName (),
Y = 1, // menu
Height = Dim.Fill ()
};
var top = new Toplevel ();
var menu = new MenuBar
{
Menus =
[
new (
"_File",
new MenuItem []
{
new (
"_Quit",
$"{Application.QuitKey}",
() => Quit ()
)
}
),
new (
"_View",
new []
{
_miFullPaths =
new ("_Full Paths", "", () => SetFullName ())
{
Checked = false, CheckType = MenuItemCheckStyle.Checked
},
_miMultiSelect = new (
"_Multi Select",
"",
() => SetMultiSelect ()
)
{
Checked = true,
CheckType = MenuItemCheckStyle
.Checked
}
}
),
new (
"_Style",
new []
{
_miShowLines =
new ("_Show Lines", "", () => ShowLines ())
{
Checked = true, CheckType = MenuItemCheckStyle.Checked
},
null /*separator*/,
_miPlusMinus =
new (
"_Plus Minus Symbols",
"+ -",
() => SetExpandableSymbols (
(Rune)'+',
(Rune)'-'
)
) { Checked = true, CheckType = MenuItemCheckStyle.Radio },
_miArrowSymbols =
new (
"_Arrow Symbols",
"> v",
() => SetExpandableSymbols (
(Rune)'>',
(Rune)'v'
)
) { Checked = false, CheckType = MenuItemCheckStyle.Radio },
_miNoSymbols =
new (
"_No Symbols",
"",
() => SetExpandableSymbols (
default (Rune),
null
)
) { Checked = false, CheckType = MenuItemCheckStyle.Radio },
null /*separator*/,
_miColoredSymbols =
new (
"_Colored Symbols",
"",
() => ShowColoredExpandableSymbols ()
) { Checked = false, CheckType = MenuItemCheckStyle.Checked },
_miInvertSymbols =
new (
"_Invert Symbols",
"",
() => InvertExpandableSymbols ()
) { Checked = false, CheckType = MenuItemCheckStyle.Checked },
null /*separator*/,
_miBasicIcons =
new ("_Basic Icons", null, SetNoIcons)
{
Checked = false, CheckType = MenuItemCheckStyle.Radio
},
_miUnicodeIcons =
new ("_Unicode Icons", null, SetUnicodeIcons)
{
Checked = false, CheckType = MenuItemCheckStyle.Radio
},
_miNerdIcons =
new ("_Nerd Icons", null, SetNerdIcons)
{
Checked = false, CheckType = MenuItemCheckStyle.Radio
},
null /*separator*/,
_miLeaveLastRow =
new (
"_Leave Last Row",
"",
() => SetLeaveLastRow ()
) { Checked = true, CheckType = MenuItemCheckStyle.Checked },
_miHighlightModelTextOnly =
new (
"_Highlight Model Text Only",
"",
() => SetCheckHighlightModelTextOnly ()
) { Checked = false, CheckType = MenuItemCheckStyle.Checked },
null /*separator*/,
_miCustomColors =
new (
"C_ustom Colors Hidden Files",
"Yellow/Red",
() => SetCustomColors ()
) { Checked = false, CheckType = MenuItemCheckStyle.Checked },
null /*separator*/,
_miCursor = new (
"Curs_or (MultiSelect only)",
"",
() => SetCursor ()
) { Checked = false, CheckType = MenuItemCheckStyle.Checked }
}
)
]
};
top.Add (menu);
_treeViewFiles = new () { X = 0, Y = 0, Width = Dim.Percent (50), Height = Dim.Fill () };
_treeViewFiles.DrawLine += TreeViewFiles_DrawLine;
_treeViewFiles.VerticalScrollBar.AutoShow = false;
_detailsFrame = new (_iconProvider)
{
X = Pos.Right (_treeViewFiles), Y = 0, Width = Dim.Fill (), Height = Dim.Fill ()
};
win.Add (_detailsFrame);
_treeViewFiles.MouseClick += TreeViewFiles_MouseClick;
_treeViewFiles.KeyDown += TreeViewFiles_KeyPress;
_treeViewFiles.SelectionChanged += TreeViewFiles_SelectionChanged;
SetupFileTree ();
win.Add (_treeViewFiles);
top.Add (win);
_treeViewFiles.GoToFirst ();
_treeViewFiles.Expand ();
//SetupScrollBar ();
_treeViewFiles.SetFocus ();
UpdateIconCheckedness ();
Application.Run (top);
top.Dispose ();
Application.Shutdown ();
}
private string AspectGetter (IFileSystemInfo f) { return (_iconProvider.GetIconWithOptionalSpace (f) + f.Name).Trim (); }
private void InvertExpandableSymbols ()
{
_miInvertSymbols.Checked = !_miInvertSymbols.Checked;
_treeViewFiles.Style.InvertExpandSymbolColors = (bool)_miInvertSymbols.Checked;
_treeViewFiles.SetNeedsDraw ();
}
private void Quit () { Application.RequestStop (); }
private void SetCheckHighlightModelTextOnly ()
{
_treeViewFiles.Style.HighlightModelTextOnly = !_treeViewFiles.Style.HighlightModelTextOnly;
_miHighlightModelTextOnly.Checked = _treeViewFiles.Style.HighlightModelTextOnly;
_treeViewFiles.SetNeedsDraw ();
}
private void SetCursor ()
{
_miCursor.Checked = !_miCursor.Checked;
_treeViewFiles.CursorVisibility =
_miCursor.Checked == true ? CursorVisibility.Default : CursorVisibility.Invisible;
}
private void SetCustomColors ()
{
_miCustomColors.Checked = !_miCustomColors.Checked;
if (_miCustomColors.Checked == true)
{
_treeViewFiles.ColorGetter = m =>
{
if (m is IDirectoryInfo && m.Attributes.HasFlag (FileAttributes.Hidden))
{
return new ()
{
Focus = new (
Color.BrightRed,
_treeViewFiles.GetAttributeForRole (VisualRole.Focus).Background
),
Normal = new (
Color.BrightYellow,
_treeViewFiles.GetAttributeForRole (VisualRole.Normal).Background
)
};
;
}
if (m is IFileInfo && m.Attributes.HasFlag (FileAttributes.Hidden))
{
return new ()
{
Focus = new (
Color.BrightRed,
_treeViewFiles.GetAttributeForRole (VisualRole.Focus).Background
),
Normal = new (
Color.BrightYellow,
_treeViewFiles.GetAttributeForRole (VisualRole.Normal).Background
)
};
;
}
return null;
};
}
else
{
_treeViewFiles.ColorGetter = null;
}
_treeViewFiles.SetNeedsDraw ();
}
private void SetExpandableSymbols (Rune expand, Rune? collapse)
{
_miPlusMinus.Checked = expand.Value == '+';
_miArrowSymbols.Checked = expand.Value == '>';
_miNoSymbols.Checked = expand.Value == default (int);
_treeViewFiles.Style.ExpandableSymbol = expand;
_treeViewFiles.Style.CollapseableSymbol = collapse;
_treeViewFiles.SetNeedsDraw ();
}
private void SetFullName ()
{
_miFullPaths.Checked = !_miFullPaths.Checked;
if (_miFullPaths.Checked == true)
{
_treeViewFiles.AspectGetter = f => f.FullName;
}
else
{
_treeViewFiles.AspectGetter = f => f.Name;
}
_treeViewFiles.SetNeedsDraw ();
}
private void SetLeaveLastRow ()
{
_miLeaveLastRow.Checked = !_miLeaveLastRow.Checked;
_treeViewFiles.Style.LeaveLastRow = (bool)_miLeaveLastRow.Checked;
}
private void SetMultiSelect ()
{
_miMultiSelect.Checked = !_miMultiSelect.Checked;
_treeViewFiles.MultiSelect = (bool)_miMultiSelect.Checked;
}
private void SetNerdIcons ()
{
_iconProvider.UseNerdIcons = true;
UpdateIconCheckedness ();
}
private void SetNoIcons ()
{
_iconProvider.UseUnicodeCharacters = false;
_iconProvider.UseNerdIcons = false;
UpdateIconCheckedness ();
}
private void SetUnicodeIcons ()
{
_iconProvider.UseUnicodeCharacters = true;
UpdateIconCheckedness ();
}
private void SetupFileTree ()
{
// setup how to build tree
var fs = new FileSystem ();
IEnumerable<IDirectoryInfo> rootDirs =
DriveInfo.GetDrives ().Select (d => fs.DirectoryInfo.New (d.RootDirectory.FullName));
_treeViewFiles.TreeBuilder = new FileSystemTreeBuilder ();
_treeViewFiles.AddObjects (rootDirs);
// Determines how to represent objects as strings on the screen
_treeViewFiles.AspectGetter = AspectGetter;
_iconProvider.IsOpenGetter = _treeViewFiles.IsExpanded;
}
//private void SetupScrollBar ()
//{
// // When using scroll bar leave the last row of the control free (for over-rendering with scroll bar)
// _treeViewFiles.Style.LeaveLastRow = true;
// var scrollBar = new ScrollBarView (_treeViewFiles, true);
// scrollBar.ChangedPosition += (s, e) =>
// {
// _treeViewFiles.ScrollOffsetVertical = scrollBar.Position;
// if (_treeViewFiles.ScrollOffsetVertical != scrollBar.Position)
// {
// scrollBar.Position = _treeViewFiles.ScrollOffsetVertical;
// }
// _treeViewFiles.SetNeedsDraw ();
// };
// scrollBar.OtherScrollBarView.ChangedPosition += (s, e) =>
// {
// _treeViewFiles.ScrollOffsetHorizontal = scrollBar.OtherScrollBarView.Position;
// if (_treeViewFiles.ScrollOffsetHorizontal != scrollBar.OtherScrollBarView.Position)
// {
// scrollBar.OtherScrollBarView.Position = _treeViewFiles.ScrollOffsetHorizontal;
// }
// _treeViewFiles.SetNeedsDraw ();
// };
// _treeViewFiles.DrawingContent += (s, e) =>
// {
// scrollBar.Size = _treeViewFiles.ContentHeight;
// scrollBar.Position = _treeViewFiles.ScrollOffsetVertical;
// scrollBar.OtherScrollBarView.Size = _treeViewFiles.GetContentWidth (true);
// scrollBar.OtherScrollBarView.Position = _treeViewFiles.ScrollOffsetHorizontal;
// scrollBar.Refresh ();
// };
//}
private void ShowColoredExpandableSymbols ()
{
_miColoredSymbols.Checked = !_miColoredSymbols.Checked;
_treeViewFiles.Style.ColorExpandSymbol = (bool)_miColoredSymbols.Checked;
_treeViewFiles.SetNeedsDraw ();
}
private void ShowContextMenu (Point screenPoint, IFileSystemInfo forObject)
{
PopoverMenu? contextMenu = new ([new ("Properties", $"Show {forObject.Name} properties", () => ShowPropertiesOf (forObject))]);
// Registering with the PopoverManager will ensure that the context menu is closed when the view is no longer focused
// and the context menu is disposed when it is closed.
Application.Popover?.Register (contextMenu);
Application.Invoke (() => contextMenu?.MakeVisible (screenPoint));
}
private void ShowLines ()
{
_miShowLines.Checked = !_miShowLines.Checked;
_treeViewFiles.Style.ShowBranchLines = (bool)_miShowLines.Checked!;
_treeViewFiles.SetNeedsDraw ();
}
private void ShowPropertiesOf (IFileSystemInfo fileSystemInfo) { _detailsFrame.FileInfo = fileSystemInfo; }
private void TreeViewFiles_DrawLine (object sender, DrawTreeViewLineEventArgs<IFileSystemInfo> e)
{
// Render directory icons in yellow
if (e.Model is IDirectoryInfo d)
{
if (_iconProvider.UseNerdIcons || _iconProvider.UseUnicodeCharacters)
{
if (e.IndexOfModelText > 0 && e.IndexOfModelText < e.Cells.Count)
{
Cell cell = e.Cells [e.IndexOfModelText];
cell.Attribute = new Attribute (
Color.BrightYellow,
cell.Attribute!.Value.Background,
cell.Attribute!.Value.Style
);
}
}
}
}
private void TreeViewFiles_KeyPress (object sender, Key obj)
{
if (obj.KeyCode == (KeyCode.R | KeyCode.CtrlMask))
{
IFileSystemInfo selected = _treeViewFiles.SelectedObject;
// nothing is selected
if (selected == null)
{
return;
}
int? location = _treeViewFiles.GetObjectRow (selected);
//selected object is offscreen or somehow not found
if (location == null || location < 0 || location > _treeViewFiles.Frame.Height)
{
return;
}
ShowContextMenu (
new (
5 + _treeViewFiles.Frame.X,
location.Value + _treeViewFiles.Frame.Y + 2
),
selected
);
}
}
private void TreeViewFiles_MouseClick (object sender, MouseEventArgs obj)
{
// if user right clicks
if (obj.Flags.HasFlag (MouseFlags.Button3Clicked))
{
IFileSystemInfo rightClicked = _treeViewFiles.GetObjectOnRow (obj.Position.Y);
// nothing was clicked
if (rightClicked == null)
{
return;
}
ShowContextMenu (
new (
obj.Position.X + _treeViewFiles.Frame.X,
obj.Position.Y + _treeViewFiles.Frame.Y + 2
),
rightClicked
);
}
}
private void TreeViewFiles_SelectionChanged (object sender, SelectionChangedEventArgs<IFileSystemInfo> e) { ShowPropertiesOf (e.NewValue); }
private void UpdateIconCheckedness ()
{
_miBasicIcons.Checked = !_iconProvider.UseNerdIcons && !_iconProvider.UseUnicodeCharacters;
_miUnicodeIcons.Checked = _iconProvider.UseUnicodeCharacters;
_miNerdIcons.Checked = _iconProvider.UseNerdIcons;
_treeViewFiles.SetNeedsDraw ();
}
private class DetailsFrame : FrameView
{
private readonly FileSystemIconProvider _iconProvider;
private IFileSystemInfo _fileInfo;
public DetailsFrame (FileSystemIconProvider iconProvider)
{
Title = "Details";
Visible = true;
CanFocus = true;
_iconProvider = iconProvider;
}
public IFileSystemInfo FileInfo
{
get => _fileInfo;
set
{
_fileInfo = value;
StringBuilder sb = null;
if (_fileInfo is IFileInfo f)
{
Title = $"{_iconProvider.GetIconWithOptionalSpace (f)}{f.Name}".Trim ();
sb = new ();
sb.AppendLine ($"Path:\n {f.FullName}\n");
sb.AppendLine ($"Size:\n {f.Length:N0} bytes\n");
sb.AppendLine ($"Modified:\n {f.LastWriteTime}\n");
sb.AppendLine ($"Created:\n {f.CreationTime}");
}
if (_fileInfo is IDirectoryInfo dir)
{
Title = $"{_iconProvider.GetIconWithOptionalSpace (dir)}{dir.Name}".Trim ();
sb = new ();
sb.AppendLine ($"Path:\n {dir?.FullName}\n");
sb.AppendLine ($"Modified:\n {dir.LastWriteTime}\n");
sb.AppendLine ($"Created:\n {dir.CreationTime}\n");
}
Text = sb.ToString ();
}
}
}
}