using NStack;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Globalization;
using System.Linq;
using System.Runtime.InteropServices;
using System.Text;
using Terminal.Gui;
using System.IO;
using System.Reflection;
using System.Threading;
using static Terminal.Gui.ConfigurationManager;
using System.Text.Json.Serialization;
#nullable enable
///
/// UI Catalog is a comprehensive sample library for Terminal.Gui. It provides a simple UI for adding to the catalog of scenarios.
///
///
///
/// UI Catalog attempts to satisfy the following goals:
///
///
///
/// -
///
/// Be an easy to use showcase for Terminal.Gui concepts and features.
///
///
/// -
///
/// Provide sample code that illustrates how to properly implement said concepts & features.
///
///
/// -
///
/// Make it easy for contributors to add additional samples in a structured way.
///
///
///
///
///
/// See the project README for more details (https://github.com/gui-cs/Terminal.Gui/tree/master/UICatalog/README.md).
///
///
namespace UICatalog {
///
/// UI Catalog is a comprehensive sample app and scenario library for
///
class UICatalogApp {
//[SerializableConfigurationProperty (Scope = typeof (AppScope), OmitClassName = true), JsonPropertyName ("UICatalog.StatusBar")]
//public static bool ShowStatusBar { get; set; } = true;
[SerializableConfigurationProperty (Scope = typeof (AppScope), OmitClassName = true), JsonPropertyName ("UICatalog.StatusBar")]
public static bool ShowStatusBar { get; set; } = true;
static readonly FileSystemWatcher _currentDirWatcher = new FileSystemWatcher ();
static readonly FileSystemWatcher _homeDirWatcher = new FileSystemWatcher ();
static void Main (string [] args)
{
Console.OutputEncoding = Encoding.Default;
if (Debugger.IsAttached) {
CultureInfo.DefaultThreadCurrentUICulture = CultureInfo.GetCultureInfo ("en-US");
}
_scenarios = Scenario.GetScenarios ();
_categories = Scenario.GetAllCategories ();
if (args.Length > 0 && args.Contains ("-usc")) {
_useSystemConsole = true;
args = args.Where (val => val != "-usc").ToArray ();
}
StartConfigFileWatcher ();
// If a Scenario name has been provided on the commandline
// run it and exit when done.
if (args.Length > 0) {
_topLevelColorScheme = "Base";
var item = _scenarios.FindIndex (s => s.GetName ().Equals (args [0], StringComparison.OrdinalIgnoreCase));
_selectedScenario = (Scenario)Activator.CreateInstance (_scenarios [item].GetType ())!;
Application.UseSystemConsole = _useSystemConsole;
Application.Init ();
_selectedScenario.Theme = _cachedTheme;
_selectedScenario.TopLevelColorScheme = _topLevelColorScheme;
_selectedScenario.Init ();
_selectedScenario.Setup ();
_selectedScenario.Run ();
_selectedScenario.Dispose ();
_selectedScenario = null;
Application.Shutdown ();
VerifyObjectsWereDisposed ();
return;
}
_aboutMessage = new StringBuilder ();
_aboutMessage.AppendLine (@"A comprehensive sample library for");
_aboutMessage.AppendLine (@"");
_aboutMessage.AppendLine (@" _______ _ _ _____ _ ");
_aboutMessage.AppendLine (@" |__ __| (_) | | / ____| (_) ");
_aboutMessage.AppendLine (@" | | ___ _ __ _ __ ___ _ _ __ __ _| || | __ _ _ _ ");
_aboutMessage.AppendLine (@" | |/ _ \ '__| '_ ` _ \| | '_ \ / _` | || | |_ | | | | | ");
_aboutMessage.AppendLine (@" | | __/ | | | | | | | | | | | (_| | || |__| | |_| | | ");
_aboutMessage.AppendLine (@" |_|\___|_| |_| |_| |_|_|_| |_|\__,_|_(_)_____|\__,_|_| ");
_aboutMessage.AppendLine (@"");
_aboutMessage.AppendLine (@"v2 - Work in Progress");
_aboutMessage.AppendLine (@"");
_aboutMessage.AppendLine (@"https://github.com/gui-cs/Terminal.Gui");
Scenario scenario;
while ((scenario = RunUICatalogTopLevel ()) != null) {
VerifyObjectsWereDisposed ();
ConfigurationManager.Themes!.Theme = _cachedTheme!;
ConfigurationManager.Apply ();
scenario.Theme = _cachedTheme;
scenario.TopLevelColorScheme = _topLevelColorScheme;
scenario.Init ();
scenario.Setup ();
scenario.Run ();
scenario.Dispose ();
// This call to Application.Shutdown brackets the Application.Init call
// made by Scenario.Init() above
Application.Shutdown ();
VerifyObjectsWereDisposed ();
}
StopConfigFileWatcher ();
VerifyObjectsWereDisposed ();
}
private static void StopConfigFileWatcher ()
{
_currentDirWatcher.EnableRaisingEvents = false;
_currentDirWatcher.Changed -= ConfigFileChanged;
_currentDirWatcher.Created -= ConfigFileChanged;
_homeDirWatcher.EnableRaisingEvents = false;
_homeDirWatcher.Changed -= ConfigFileChanged;
_homeDirWatcher.Created -= ConfigFileChanged;
}
private static void StartConfigFileWatcher ()
{
// Setup a file system watcher for `./.tui/`
_currentDirWatcher.NotifyFilter = NotifyFilters.LastWrite;
var f = new FileInfo (Assembly.GetExecutingAssembly ().Location);
var tuiDir = Path.Combine (f.Directory!.FullName, ".tui");
if (!Directory.Exists (tuiDir)) {
Directory.CreateDirectory (tuiDir);
}
_currentDirWatcher.Path = tuiDir;
_currentDirWatcher.Filter = "*config.json";
// Setup a file system watcher for `~/.tui/`
_homeDirWatcher.NotifyFilter = NotifyFilters.LastWrite;
f = new FileInfo (Environment.GetFolderPath (Environment.SpecialFolder.UserProfile));
tuiDir = Path.Combine (f.FullName, ".tui");
if (!Directory.Exists (tuiDir)) {
Directory.CreateDirectory (tuiDir);
}
_homeDirWatcher.Path = tuiDir;
_homeDirWatcher.Filter = "*config.json";
_currentDirWatcher.Changed += ConfigFileChanged;
//_currentDirWatcher.Created += ConfigFileChanged;
_currentDirWatcher.EnableRaisingEvents = true;
_homeDirWatcher.Changed += ConfigFileChanged;
//_homeDirWatcher.Created += ConfigFileChanged;
_homeDirWatcher.EnableRaisingEvents = true;
}
private static void ConfigFileChanged (object sender, FileSystemEventArgs e)
{
if (Application.Top == null) {
return;
}
// TOOD: THis is a hack. Figure out how to ensure that the file is fully written before reading it.
Thread.Sleep (500);
ConfigurationManager.Load ();
ConfigurationManager.Apply ();
}
///
/// Shows the UI Catalog selection UI. When the user selects a Scenario to run, the
/// UI Catalog main app UI is killed and the Scenario is run as though it were Application.Top.
/// When the Scenario exits, this function exits.
///
///
static Scenario RunUICatalogTopLevel ()
{
Application.UseSystemConsole = _useSystemConsole;
// Run UI Catalog UI. When it exits, if _selectedScenario is != null then
// a Scenario was selected. Otherwise, the user wants to quit UI Catalog.
Application.Init ();
if (_cachedTheme is null) {
_cachedTheme = ConfigurationManager.Themes?.Theme;
} else {
ConfigurationManager.Themes!.Theme = _cachedTheme;
ConfigurationManager.Apply ();
}
//Application.EnableConsoleScrolling = _enableConsoleScrolling;
Application.Run ();
Application.Shutdown ();
return _selectedScenario!;
}
static List? _scenarios;
static List? _categories;
// When a scenario is run, the main app is killed. These items
// are therefore cached so that when the scenario exits the
// main app UI can be restored to previous state
static int _cachedScenarioIndex = 0;
static int _cachedCategoryIndex = 0;
static string? _cachedTheme = string.Empty;
static StringBuilder? _aboutMessage = null;
// If set, holds the scenario the user selected
static Scenario? _selectedScenario = null;
static bool _useSystemConsole = false;
static ConsoleDriver.DiagnosticFlags _diagnosticFlags;
//static bool _enableConsoleScrolling = false;
static bool _isFirstRunning = true;
static string _topLevelColorScheme = string.Empty;
static MenuItem []? _themeMenuItems;
static MenuBarItem? _themeMenuBarItem;
///
/// This is the main UI Catalog app view. It is run fresh when the app loads (if a Scenario has not been passed on
/// the command line) and each time a Scenario ends.
///
public class UICatalogTopLevel : Toplevel {
public MenuItem? miIsMouseDisabled;
public MenuItem? miEnableConsoleScrolling;
public ListView CategoryListView;
public ListView ScenarioListView;
public StatusItem Capslock;
public StatusItem Numlock;
public StatusItem Scrolllock;
public StatusItem DriverName;
public StatusItem OS;
public UICatalogTopLevel ()
{
_themeMenuItems = CreateThemeMenuItems ();
_themeMenuBarItem = new MenuBarItem ("_Themes", _themeMenuItems);
MenuBar = new MenuBar (new MenuBarItem [] {
new MenuBarItem ("_File", new MenuItem [] {
new MenuItem ("_Quit", "Quit UI Catalog", () => RequestStop(), null, null)
}),
_themeMenuBarItem,
new MenuBarItem ("Diag_nostics", CreateDiagnosticMenuItems()),
new MenuBarItem ("_Help", new MenuItem [] {
new MenuItem ("_gui.cs API Overview", "", () => OpenUrl ("https://gui-cs.github.io/Terminal.Gui/articles/overview.html"), null, null, Key.F1),
new MenuItem ("gui.cs _README", "", () => OpenUrl ("https://github.com/gui-cs/Terminal.Gui"), null, null, Key.F2),
new MenuItem ("_About...",
"About UI Catalog", () => MessageBox.Query ("About UI Catalog", _aboutMessage!.ToString(), 0, false, "_Ok"), null, null, Key.CtrlMask | Key.A),
}),
});
Capslock = new StatusItem (Key.CharMask, "Caps", null);
Numlock = new StatusItem (Key.CharMask, "Num", null);
Scrolllock = new StatusItem (Key.CharMask, "Scroll", null);
DriverName = new StatusItem (Key.CharMask, "Driver:", null);
OS = new StatusItem (Key.CharMask, "OS:", null);
StatusBar = new StatusBar () {
Visible = UICatalogApp.ShowStatusBar
};
StatusBar.Items = new StatusItem [] {
new StatusItem(Application.QuitKey, $"~{Application.QuitKey} to quit", () => {
if (_selectedScenario is null){
// This causes GetScenarioToRun to return null
_selectedScenario = null;
RequestStop();
} else {
_selectedScenario.RequestStop();
}
}),
new StatusItem(Key.F10, "~F10~ Status Bar", () => {
StatusBar.Visible = !StatusBar.Visible;
//ContentPane!.Height = Dim.Fill(StatusBar.Visible ? 1 : 0);
LayoutSubviews();
SetSubViewNeedsDisplay();
}),
DriverName,
OS
};
//ContentPane = new TileView () {
// Id = "ContentPane",
// X = 0,
// Y = 1, // for menu
// Width = Dim.Fill (),
// Height = Dim.Fill (1),
// CanFocus = true,
// Shortcut = Key.CtrlMask | Key.C,
//};
//ContentPane.LineStyle = LineStyle.Single;
//ContentPane.SetSplitterPos (0, 25);
//ContentPane.ShortcutAction = () => ContentPane.SetFocus ();
CategoryListView = new ListView (_categories) {
X = 0,
Y = 1,
Width = Dim.Percent (30),
Height = Dim.Fill (1),
AllowsMarking = false,
CanFocus = true,
Title = "Categories",
BorderStyle = LineStyle.Single,
SuperViewRendersLineCanvas = true
};
CategoryListView.OpenSelectedItem += (s, a) => {
ScenarioListView!.SetFocus ();
};
CategoryListView.SelectedItemChanged += CategoryListView_SelectedChanged;
//ContentPane.Tiles.ElementAt (0).Title = "Categories";
//ContentPane.Tiles.ElementAt (0).MinSize = 2;
//ContentPane.Tiles.ElementAt (0).ContentView.Add (CategoryListView);
ScenarioListView = new ListView () {
X = Pos.Right(CategoryListView) - 1,
Y = 1,
Width = Dim.Fill (0),
Height = Dim.Fill (1),
AllowsMarking = false,
CanFocus = true,
Title = "Scenarios",
BorderStyle = LineStyle.Single,
SuperViewRendersLineCanvas = true
};
ScenarioListView.OpenSelectedItem += ScenarioListView_OpenSelectedItem;
//ContentPane.Tiles.ElementAt (1).Title = "Scenarios";
//ContentPane.Tiles.ElementAt (1).ContentView.Add (ScenarioListView);
//ContentPane.Tiles.ElementAt (1).MinSize = 2;
KeyDown += KeyDownHandler;
//Add (ContentPane);
Add (CategoryListView);
Add (ScenarioListView);
Add (MenuBar);
Add (StatusBar);
Loaded += LoadedHandler;
Unloaded += UnloadedHandler;
// Restore previous selections
CategoryListView.SelectedItem = _cachedCategoryIndex;
ScenarioListView.SelectedItem = _cachedScenarioIndex;
ConfigurationManager.Applied += ConfigAppliedHandler;
}
void LoadedHandler (object? sender, EventArgs? args)
{
ConfigChanged ();
miIsMouseDisabled!.Checked = Application.IsMouseDisabled;
miEnableConsoleScrolling!.Checked = Application.EnableConsoleScrolling;
DriverName.Title = $"Driver: {Driver.GetType ().Name}";
OS.Title = $"OS: {Microsoft.DotNet.PlatformAbstractions.RuntimeEnvironment.OperatingSystem} {Microsoft.DotNet.PlatformAbstractions.RuntimeEnvironment.OperatingSystemVersion}";
if (_selectedScenario != null) {
_selectedScenario = null;
_isFirstRunning = false;
}
if (!_isFirstRunning) {
ScenarioListView.SetFocus ();
}
StatusBar.VisibleChanged += (s, e) => {
UICatalogApp.ShowStatusBar = StatusBar.Visible;
var height = (StatusBar.Visible ? 1 : 0);
CategoryListView.Height = Dim.Fill (height);
ScenarioListView.Height = Dim.Fill (height);
// ContentPane.Height = Dim.Fill (height);
LayoutSubviews ();
SetSubViewNeedsDisplay ();
};
Loaded -= LoadedHandler;
}
private void UnloadedHandler (object? sender, EventArgs? args)
{
ConfigurationManager.Applied -= ConfigAppliedHandler;
Unloaded -= UnloadedHandler;
}
void ConfigAppliedHandler (object? sender, ConfigurationManagerEventArgs? a)
{
ConfigChanged ();
}
///
/// Launches the selected scenario, setting the global _selectedScenario
///
///
void ScenarioListView_OpenSelectedItem (object? sender, EventArgs? e)
{
if (_selectedScenario is null) {
// Save selected item state
_cachedCategoryIndex = CategoryListView.SelectedItem;
_cachedScenarioIndex = ScenarioListView.SelectedItem;
// Create new instance of scenario (even though Scenarios contains instances)
var sourceList = ScenarioListView.Source.ToList ();
_selectedScenario = (Scenario)Activator.CreateInstance (ScenarioListView.Source.ToList () [ScenarioListView.SelectedItem]!.GetType ())!;
// Tell the main app to stop
Application.RequestStop ();
}
}
List