diff --git a/.gitignore b/.gitignore
index 02d767004..837695307 100644
--- a/.gitignore
+++ b/.gitignore
@@ -24,3 +24,5 @@ UnitTests/TestResults
demo.*
*.deb
+
+*.tui/
\ No newline at end of file
diff --git a/README.md b/README.md
index d62088a11..636aa542b 100644
--- a/README.md
+++ b/README.md
@@ -40,10 +40,10 @@ _The Documentation matches the most recent Nuget release from the `main` branch
* **Cross Platform** - Windows, Mac, and Linux. Terminal drivers for Curses, [Windows Console](https://github.com/gui-cs/Terminal.Gui/issues/27), and the .NET Console mean apps will work well on both color and monochrome terminals.
* **Keyboard and Mouse Input** - Both keyboard and mouse input are supported, including support for drag & drop.
* **[Flexible Layout](https://gui-cs.github.io/Terminal.Gui/articles/overview.html#layout)** - Supports both *Absolute layout* and an innovative *Computed Layout* system. *Computed Layout* makes it easy to layout controls relative to each other and enables dynamic terminal UIs.
+* **[Configuration & Themes](https://gui-cs.github.io/Terminal.Gui/articles/config.html)** - Terminal.Gui supports a rich configuration system that allows end-user customization of how the UI looks (e.g. colors) and behaves (e.g. key-bindings).
* **Clipboard support** - Cut, Copy, and Paste of text provided through the [`Clipboard`](https://gui-cs.github.io/Terminal.Gui/api/Terminal.Gui/Terminal.Gui.Clipboard.html) class.
* **[Arbitrary Views](https://gui-cs.github.io/Terminal.Gui/api/Terminal.Gui/Terminal.Gui.View.html)** - All visible UI elements are subclasses of the `View` class, and these in turn can contain an arbitrary number of sub-views.
-* **Advanced App Features** - The [Mainloop](https://gui-cs.github.io/Terminal.Gui/api/Terminal.Gui/Terminal.Gui.MainLoop.html) supports processing events, idle handlers, timers, and monitoring file
-descriptors. Most classes are safe for threading.
+* **Advanced App Features** - The [Mainloop](https://gui-cs.github.io/Terminal.Gui/api/Terminal.Gui/Terminal.Gui.MainLoop.html) supports processing events, idle handlers, timers, and monitoring file descriptors. Most classes are safe for threading.
* **Reactive Extensions** - Use [reactive extensions](https://github.com/dotnet/reactive) and benefit from increased code readability, and the ability to apply the MVVM pattern and [ReactiveUI](https://www.reactiveui.net/) data bindings. See the [source code](https://github.com/gui-cs/Terminal.Gui/tree/master/ReactiveExample) of a sample app in order to learn how to achieve this.
## Showcase & Examples
diff --git a/Terminal.Gui UnitTests/ScenarioTests.cs b/Terminal.Gui UnitTests/ScenarioTests.cs
deleted file mode 100644
index f5f1dc57b..000000000
--- a/Terminal.Gui UnitTests/ScenarioTests.cs
+++ /dev/null
@@ -1,566 +0,0 @@
-using NStack;
-using System;
-using System.Collections.Generic;
-using System.Linq;
-using System.Reflection;
-using Terminal.Gui;
-using UICatalog;
-using Xunit;
-using Xunit.Abstractions;
-
-// Alias Console to MockConsole so we don't accidentally use Console
-using Console = Terminal.Gui.FakeConsole;
-
-namespace UICatalog {
- public class ScenarioTests {
- readonly ITestOutputHelper output;
-
- public ScenarioTests (ITestOutputHelper output)
- {
-#if DEBUG_IDISPOSABLE
- Responder.Instances.Clear ();
-#endif
- this.output = output;
- }
-
- int CreateInput (string input)
- {
- // Put a control-q in at the end
- FakeConsole.MockKeyPresses.Push (new ConsoleKeyInfo ('q', ConsoleKey.Q, shift: false, alt: false, control: true));
- foreach (var c in input.Reverse ()) {
- if (char.IsLetter (c)) {
- FakeConsole.MockKeyPresses.Push (new ConsoleKeyInfo (char.ToLower (c), (ConsoleKey)char.ToUpper (c), shift: char.IsUpper (c), alt: false, control: false));
- } else {
- FakeConsole.MockKeyPresses.Push (new ConsoleKeyInfo (c, (ConsoleKey)c, shift: false, alt: false, control: false));
- }
- }
- return FakeConsole.MockKeyPresses.Count;
- }
-
-
- ///
- ///
- /// This runs through all Scenarios defined in UI Catalog, calling Init, Setup, and Run.
- ///
- ///
- /// Should find any Scenarios which crash on load or do not respond to .
- ///
- ///
- [Fact]
- public void Run_All_Scenarios ()
- {
- List scenarios = Scenario.GetScenarios ();
- Assert.NotEmpty (scenarios);
-
- foreach (var scenario in scenarios) {
-
- output.WriteLine ($"Running Scenario '{scenario}'");
-
- Func closeCallback = (MainLoop loop) => {
- Application.RequestStop ();
- return false;
- };
-
- Application.Init (new FakeDriver (), new FakeMainLoop (() => FakeConsole.ReadKey (true)));
-
- // Close after a short period of time
- var token = Application.MainLoop.AddTimeout (TimeSpan.FromMilliseconds (100), closeCallback);
-
- scenario.Init (Colors.Base);
- scenario.Setup ();
- scenario.Run ();
- Application.Shutdown ();
-#if DEBUG_IDISPOSABLE
- foreach (var inst in Responder.Instances) {
- Assert.True (inst.WasDisposed);
- }
- Responder.Instances.Clear ();
-#endif
- }
-#if DEBUG_IDISPOSABLE
- foreach (var inst in Responder.Instances) {
- Assert.True (inst.WasDisposed);
- }
- Responder.Instances.Clear ();
-#endif
- }
-
- [Fact]
- public void Run_Generic ()
- {
- List scenarios = Scenario.GetScenarios ();
- Assert.NotEmpty (scenarios);
-
- var item = scenarios.FindIndex (s => s.GetName ().Equals ("Generic", StringComparison.OrdinalIgnoreCase));
- var generic = scenarios [item];
- // Setup some fake keypresses
- // Passing empty string will cause just a ctrl-q to be fired
- int stackSize = CreateInput ("");
-
- Application.Init (new FakeDriver (), new FakeMainLoop (() => FakeConsole.ReadKey (true)));
-
- int iterations = 0;
- Application.Iteration = () => {
- iterations++;
- // Stop if we run out of control...
- if (iterations == 10) {
- Application.RequestStop ();
- }
- };
-
- var ms = 1000;
- var abortCount = 0;
- Func abortCallback = (MainLoop loop) => {
- abortCount++;
- Application.RequestStop ();
- return false;
- };
- var token = Application.MainLoop.AddTimeout (TimeSpan.FromMilliseconds (ms), abortCallback);
-
- Application.Top.KeyPress += (View.KeyEventEventArgs args) => {
- Assert.Equal (Key.CtrlMask | Key.Q, args.KeyEvent.Key);
- };
-
- generic.Init (Colors.Base);
- generic.Setup ();
- // There is no need to call Application.Begin because Init already creates the Application.Top
- // If Application.RunState is used then the Application.RunLoop must also be used instead Application.Run.
- //var rs = Application.Begin (Application.Top);
- generic.Run ();
-
- //Application.End (rs);
-
- Assert.Equal (0, abortCount);
- // # of key up events should match # of iterations
- Assert.Equal (1, iterations);
- // Using variable in the left side of Assert.Equal/NotEqual give error. Must be used literals values.
- //Assert.Equal (stackSize, iterations);
-
- // Shutdown must be called to safely clean up Application if Init has been called
- Application.Shutdown ();
-
-#if DEBUG_IDISPOSABLE
- foreach (var inst in Responder.Instances) {
- Assert.True (inst.WasDisposed);
- }
- Responder.Instances.Clear ();
-#endif
- }
-
- [Fact]
- public void Run_All_Views_Tester_Scenario ()
- {
- Window _leftPane;
- ListView _classListView;
- FrameView _hostPane;
-
- Dictionary _viewClasses;
- View _curView = null;
-
- // Settings
- FrameView _settingsPane;
- CheckBox _computedCheckBox;
- FrameView _locationFrame;
- RadioGroup _xRadioGroup;
- TextField _xText;
- int _xVal = 0;
- RadioGroup _yRadioGroup;
- TextField _yText;
- int _yVal = 0;
-
- FrameView _sizeFrame;
- RadioGroup _wRadioGroup;
- TextField _wText;
- int _wVal = 0;
- RadioGroup _hRadioGroup;
- TextField _hText;
- int _hVal = 0;
- List posNames = new List { "Factor", "AnchorEnd", "Center", "Absolute" };
- List dimNames = new List { "Factor", "Fill", "Absolute" };
-
-
- Application.Init (new FakeDriver (), new FakeMainLoop (() => FakeConsole.ReadKey (true)));
-
- var Top = Application.Top;
-
- _viewClasses = GetAllViewClassesCollection ()
- .OrderBy (t => t.Name)
- .Select (t => new KeyValuePair (t.Name, t))
- .ToDictionary (t => t.Key, t => t.Value);
-
- _leftPane = new Window ("Classes") {
- X = 0,
- Y = 0,
- Width = 15,
- Height = Dim.Fill (1), // for status bar
- CanFocus = false,
- ColorScheme = Colors.TopLevel,
- };
-
- _classListView = new ListView (_viewClasses.Keys.ToList ()) {
- X = 0,
- Y = 0,
- Width = Dim.Fill (0),
- Height = Dim.Fill (0),
- AllowsMarking = false,
- ColorScheme = Colors.TopLevel,
- };
- _leftPane.Add (_classListView);
-
- _settingsPane = new FrameView ("Settings") {
- X = Pos.Right (_leftPane),
- Y = 0, // for menu
- Width = Dim.Fill (),
- Height = 10,
- CanFocus = false,
- ColorScheme = Colors.TopLevel,
- };
- _computedCheckBox = new CheckBox ("Computed Layout", true) { X = 0, Y = 0 };
- _settingsPane.Add (_computedCheckBox);
-
- var radioItems = new ustring [] { "Percent(x)", "AnchorEnd(x)", "Center", "At(x)" };
- _locationFrame = new FrameView ("Location (Pos)") {
- X = Pos.Left (_computedCheckBox),
- Y = Pos.Bottom (_computedCheckBox),
- Height = 3 + radioItems.Length,
- Width = 36,
- };
- _settingsPane.Add (_locationFrame);
-
- var label = new Label ("x:") { X = 0, Y = 0 };
- _locationFrame.Add (label);
- _xRadioGroup = new RadioGroup (radioItems) {
- X = 0,
- Y = Pos.Bottom (label),
- };
- _xText = new TextField ($"{_xVal}") { X = Pos.Right (label) + 1, Y = 0, Width = 4 };
- _locationFrame.Add (_xText);
-
- _locationFrame.Add (_xRadioGroup);
-
- radioItems = new ustring [] { "Percent(y)", "AnchorEnd(y)", "Center", "At(y)" };
- label = new Label ("y:") { X = Pos.Right (_xRadioGroup) + 1, Y = 0 };
- _locationFrame.Add (label);
- _yText = new TextField ($"{_yVal}") { X = Pos.Right (label) + 1, Y = 0, Width = 4 };
- _locationFrame.Add (_yText);
- _yRadioGroup = new RadioGroup (radioItems) {
- X = Pos.X (label),
- Y = Pos.Bottom (label),
- };
- _locationFrame.Add (_yRadioGroup);
-
- _sizeFrame = new FrameView ("Size (Dim)") {
- X = Pos.Right (_locationFrame),
- Y = Pos.Y (_locationFrame),
- Height = 3 + radioItems.Length,
- Width = 40,
- };
-
- radioItems = new ustring [] { "Percent(width)", "Fill(width)", "Sized(width)" };
- label = new Label ("width:") { X = 0, Y = 0 };
- _sizeFrame.Add (label);
- _wRadioGroup = new RadioGroup (radioItems) {
- X = 0,
- Y = Pos.Bottom (label),
- };
- _wText = new TextField ($"{_wVal}") { X = Pos.Right (label) + 1, Y = 0, Width = 4 };
- _sizeFrame.Add (_wText);
- _sizeFrame.Add (_wRadioGroup);
-
- radioItems = new ustring [] { "Percent(height)", "Fill(height)", "Sized(height)" };
- label = new Label ("height:") { X = Pos.Right (_wRadioGroup) + 1, Y = 0 };
- _sizeFrame.Add (label);
- _hText = new TextField ($"{_hVal}") { X = Pos.Right (label) + 1, Y = 0, Width = 4 };
- _sizeFrame.Add (_hText);
-
- _hRadioGroup = new RadioGroup (radioItems) {
- X = Pos.X (label),
- Y = Pos.Bottom (label),
- };
- _sizeFrame.Add (_hRadioGroup);
-
- _settingsPane.Add (_sizeFrame);
-
- _hostPane = new FrameView ("") {
- X = Pos.Right (_leftPane),
- Y = Pos.Bottom (_settingsPane),
- Width = Dim.Fill (),
- Height = Dim.Fill (1), // + 1 for status bar
- ColorScheme = Colors.Dialog,
- };
-
- _classListView.OpenSelectedItem += (a) => {
- _settingsPane.SetFocus ();
- };
- _classListView.SelectedItemChanged += (args) => {
- ClearClass (_curView);
- _curView = CreateClass (_viewClasses.Values.ToArray () [_classListView.SelectedItem]);
- };
-
- _computedCheckBox.Toggled += (previousState) => {
- if (_curView != null) {
- _curView.LayoutStyle = previousState ? LayoutStyle.Absolute : LayoutStyle.Computed;
- _hostPane.LayoutSubviews ();
- }
- };
-
- _xRadioGroup.SelectedItemChanged += (selected) => DimPosChanged (_curView);
-
- _xText.TextChanged += (args) => {
- try {
- _xVal = int.Parse (_xText.Text.ToString ());
- DimPosChanged (_curView);
- } catch {
-
- }
- };
-
- _yText.TextChanged += (args) => {
- try {
- _yVal = int.Parse (_yText.Text.ToString ());
- DimPosChanged (_curView);
- } catch {
-
- }
- };
-
- _yRadioGroup.SelectedItemChanged += (selected) => DimPosChanged (_curView);
-
- _wRadioGroup.SelectedItemChanged += (selected) => DimPosChanged (_curView);
-
- _wText.TextChanged += (args) => {
- try {
- _wVal = int.Parse (_wText.Text.ToString ());
- DimPosChanged (_curView);
- } catch {
-
- }
- };
-
- _hText.TextChanged += (args) => {
- try {
- _hVal = int.Parse (_hText.Text.ToString ());
- DimPosChanged (_curView);
- } catch {
-
- }
- };
-
- _hRadioGroup.SelectedItemChanged += (selected) => DimPosChanged (_curView);
-
- Top.Add (_leftPane, _settingsPane, _hostPane);
-
- Top.LayoutSubviews ();
-
- _curView = CreateClass (_viewClasses.First ().Value);
-
- int iterations = 0;
-
- Application.Iteration += () => {
- iterations++;
-
- if (iterations < _viewClasses.Count) {
- _classListView.MoveDown ();
- Assert.Equal (_curView.GetType ().Name,
- _viewClasses.Values.ToArray () [_classListView.SelectedItem].Name);
- } else {
- Application.RequestStop ();
- }
- };
-
- Application.Run ();
-
- Assert.Equal (_viewClasses.Count, iterations);
-
- Application.Shutdown ();
-
-
- void DimPosChanged (View view)
- {
- if (view == null) {
- return;
- }
-
- var layout = view.LayoutStyle;
-
- try {
- view.LayoutStyle = LayoutStyle.Absolute;
-
- switch (_xRadioGroup.SelectedItem) {
- case 0:
- view.X = Pos.Percent (_xVal);
- break;
- case 1:
- view.X = Pos.AnchorEnd (_xVal);
- break;
- case 2:
- view.X = Pos.Center ();
- break;
- case 3:
- view.X = Pos.At (_xVal);
- break;
- }
-
- switch (_yRadioGroup.SelectedItem) {
- case 0:
- view.Y = Pos.Percent (_yVal);
- break;
- case 1:
- view.Y = Pos.AnchorEnd (_yVal);
- break;
- case 2:
- view.Y = Pos.Center ();
- break;
- case 3:
- view.Y = Pos.At (_yVal);
- break;
- }
-
- switch (_wRadioGroup.SelectedItem) {
- case 0:
- view.Width = Dim.Percent (_wVal);
- break;
- case 1:
- view.Width = Dim.Fill (_wVal);
- break;
- case 2:
- view.Width = Dim.Sized (_wVal);
- break;
- }
-
- switch (_hRadioGroup.SelectedItem) {
- case 0:
- view.Height = Dim.Percent (_hVal);
- break;
- case 1:
- view.Height = Dim.Fill (_hVal);
- break;
- case 2:
- view.Height = Dim.Sized (_hVal);
- break;
- }
- } catch (Exception e) {
- MessageBox.ErrorQuery ("Exception", e.Message, "Ok");
- } finally {
- view.LayoutStyle = layout;
- }
- UpdateTitle (view);
- }
-
- void UpdateSettings (View view)
- {
- var x = view.X.ToString ();
- var y = view.Y.ToString ();
- _xRadioGroup.SelectedItem = posNames.IndexOf (posNames.Where (s => x.Contains (s)).First ());
- _yRadioGroup.SelectedItem = posNames.IndexOf (posNames.Where (s => y.Contains (s)).First ());
- _xText.Text = $"{view.Frame.X}";
- _yText.Text = $"{view.Frame.Y}";
-
- var w = view.Width.ToString ();
- var h = view.Height.ToString ();
- _wRadioGroup.SelectedItem = dimNames.IndexOf (dimNames.Where (s => w.Contains (s)).First ());
- _hRadioGroup.SelectedItem = dimNames.IndexOf (dimNames.Where (s => h.Contains (s)).First ());
- _wText.Text = $"{view.Frame.Width}";
- _hText.Text = $"{view.Frame.Height}";
- }
-
- void UpdateTitle (View view)
- {
- _hostPane.Title = $"{view.GetType ().Name} - {view.X.ToString ()}, {view.Y.ToString ()}, {view.Width.ToString ()}, {view.Height.ToString ()}";
- }
-
- List GetAllViewClassesCollection ()
- {
- List types = new List ();
- foreach (Type type in typeof (View).Assembly.GetTypes ()
- .Where (myType => myType.IsClass && !myType.IsAbstract && myType.IsPublic && myType.IsSubclassOf (typeof (View)))) {
- types.Add (type);
- }
- return types;
- }
-
- void ClearClass (View view)
- {
- // Remove existing class, if any
- if (view != null) {
- view.LayoutComplete -= LayoutCompleteHandler;
- _hostPane.Remove (view);
- view.Dispose ();
- _hostPane.Clear ();
- }
- }
-
- View CreateClass (Type type)
- {
- // If we are to create a generic Type
- if (type.IsGenericType) {
-
- // For each of the arguments
- List typeArguments = new List ();
-
- // use