Selaa lähdekoodia

merge develop

Charlie Kindel 2 vuotta sitten
vanhempi
commit
60e70f331f
71 muutettua tiedostoa jossa 2488 lisäystä ja 1977 poistoa
  1. 65 50
      Example/Example.cs
  2. 0 3
      Example/Example.csproj
  3. 65 50
      README.md
  4. 11 7
      Terminal.Gui/ConsoleDrivers/WindowsDriver.cs
  5. 240 100
      Terminal.Gui/Core/Application.cs
  6. 3 2
      Terminal.Gui/Core/ConsoleDriver.cs
  7. 2 2
      Terminal.Gui/Core/MainLoop.cs
  8. 4 4
      Terminal.Gui/Core/Toplevel.cs
  9. 1 1
      Terminal.Gui/Core/View.cs
  10. 31 30
      Terminal.Gui/README.md
  11. 4 85
      Terminal.Gui/Terminal.Gui.csproj
  12. 1 2
      Terminal.Gui/Views/Button.cs
  13. 7 1
      Terminal.Gui/Views/GraphView.cs
  14. 14 14
      Terminal.Gui/Views/ScrollBarView.cs
  15. 3 2
      Terminal.Gui/Views/ScrollView.cs
  16. 3 24
      Terminal.Gui/Views/TabView.cs
  17. 1 1
      Terminal.Gui/Windows/Wizard.cs
  18. 10 6
      UICatalog/Properties/launchSettings.json
  19. 15 50
      UICatalog/README.md
  20. 8 14
      UICatalog/Scenario.cs
  21. 10 24
      UICatalog/Scenarios/AllViewsTester.cs
  22. 1 8
      UICatalog/Scenarios/BackgroundWorkerCollection.cs
  23. 4 6
      UICatalog/Scenarios/BordersComparisons.cs
  24. 2 2
      UICatalog/Scenarios/Buttons.cs
  25. 75 87
      UICatalog/Scenarios/CharacterMap.cs
  26. 2 2
      UICatalog/Scenarios/ClassExplorer.cs
  27. 4 7
      UICatalog/Scenarios/Clipping.cs
  28. 8 9
      UICatalog/Scenarios/CollectionNavigatorTester.cs
  29. 2 2
      UICatalog/Scenarios/ComputedLayout.cs
  30. 1 1
      UICatalog/Scenarios/ContextMenus.cs
  31. 3 3
      UICatalog/Scenarios/CsvEditor.cs
  32. 2 2
      UICatalog/Scenarios/Dialogs.cs
  33. 2 3
      UICatalog/Scenarios/DynamicMenuBar.cs
  34. 2 3
      UICatalog/Scenarios/DynamicStatusBar.cs
  35. 7 7
      UICatalog/Scenarios/Editor.cs
  36. 76 0
      UICatalog/Scenarios/Generic - Copy.cs
  37. 3 3
      UICatalog/Scenarios/GraphViewExample.cs
  38. 2 2
      UICatalog/Scenarios/HexEditor.cs
  39. 3 3
      UICatalog/Scenarios/InteractiveTree.cs
  40. 6 7
      UICatalog/Scenarios/Keys.cs
  41. 3 3
      UICatalog/Scenarios/LabelsAsButtons.cs
  42. 3 3
      UICatalog/Scenarios/LineViewExample.cs
  43. 2 2
      UICatalog/Scenarios/MessageBoxes.cs
  44. 3 3
      UICatalog/Scenarios/MultiColouredTable.cs
  45. 5 6
      UICatalog/Scenarios/Notepad.cs
  46. 2 2
      UICatalog/Scenarios/ProgressBarStyles.cs
  47. 1 1
      UICatalog/Scenarios/RuneWidthGreaterThanOne.cs
  48. 4 4
      UICatalog/Scenarios/Scrolling.cs
  49. 2 2
      UICatalog/Scenarios/SingleBackgroundWorker.cs
  50. 3 3
      UICatalog/Scenarios/SyntaxHighlighting.cs
  51. 3 7
      UICatalog/Scenarios/TabViewExample.cs
  52. 4 4
      UICatalog/Scenarios/TableEditor.cs
  53. 1 1
      UICatalog/Scenarios/TextFormatterDemo.cs
  54. 2 2
      UICatalog/Scenarios/TextViewAutocompletePopup.cs
  55. 2 2
      UICatalog/Scenarios/Threading.cs
  56. 3 3
      UICatalog/Scenarios/TreeUseCases.cs
  57. 2 2
      UICatalog/Scenarios/TreeViewFileSystem.cs
  58. 2 2
      UICatalog/Scenarios/Unicode.cs
  59. 3 11
      UICatalog/Scenarios/WindowsAndFrameViews.cs
  60. 5 5
      UICatalog/Scenarios/WizardAsView.cs
  61. 2 2
      UICatalog/Scenarios/Wizards.cs
  62. 402 390
      UICatalog/UICatalog.cs
  63. 256 856
      UnitTests/ApplicationTests.cs
  64. 174 3
      UnitTests/MainLoopTests.cs
  65. 695 0
      UnitTests/MdiTests.cs
  66. 105 0
      UnitTests/RunStateTests.cs
  67. 9 3
      UnitTests/ScenarioTests.cs
  68. 83 0
      UnitTests/SynchronizatonContextTests.cs
  69. 2 2
      UnitTests/TextFormatterTests.cs
  70. 2 0
      UnitTests/ToplevelTests.cs
  71. 0 24
      UnitTests/ViewTests.cs

+ 65 - 50
Example/Example.cs

@@ -5,53 +5,68 @@
 
 using Terminal.Gui;
 
-// Initialize the console
-Application.Init();
-
-// Creates the top-level window with border and title
-var win = new Window("Example App (Ctrl+Q to quit)");
-
-// Create input components and labels
-
-var usernameLabel = new Label("Username:");
-var usernameText = new TextField("")
-{
-    // Position text field adjacent to label
-    X = Pos.Right(usernameLabel) + 1,
-
-    // Fill remaining horizontal space with a margin of 1
-    Width = Dim.Fill(1),
-};
-
-var passwordLabel = new Label(0,2,"Password:");
-var passwordText = new TextField("")
-{
-    Secret = true,
-    // align with the text box above
-    X = Pos.Left(usernameText),
-    Y = 2,
-    Width = Dim.Fill(1),
-};
-
-// Create login button
-var btnLogin = new Button("Login")
-{
-    Y = 4,
-    // center the login button horizontally
-    X = Pos.Center(),
-    IsDefault = true,
-};
-
-// When login button is clicked display a message popup
-btnLogin.Clicked += () => MessageBox.Query("Logging In", "Login Successful", "Ok");
-
-// Add all the views to the window
-win.Add(
-    usernameLabel, usernameText, passwordLabel, passwordText,btnLogin
-);
-
-// Show the application
-Application.Run(win);
-
-// After the application exits, release and reset console for clean shutdown
-Application.Shutdown();
+Application.Run<ExampleWindow> ();
+
+System.Console.WriteLine ($"Username: {((ExampleWindow)Application.Top).usernameText.Text}");
+
+// Before the application exits, reset Terminal.Gui for clean shutdown
+Application.Shutdown ();
+
+// Defines a top-level window with border and title
+public class ExampleWindow : Window {
+	public TextField usernameText;
+	
+	public ExampleWindow ()
+	{
+		Title = "Example App (Ctrl+Q to quit)";
+
+		// Create input components and labels
+		var usernameLabel = new Label () { 
+			Text = "Username:" 
+		};
+
+		usernameText = new TextField ("") {
+			// Position text field adjacent to the label
+			X = Pos.Right (usernameLabel) + 1,
+
+			// Fill remaining horizontal space
+			Width = Dim.Fill (),
+		};
+
+		var passwordLabel = new Label () {
+			Text = "Password:",
+			X = Pos.Left (usernameLabel),
+			Y = Pos.Bottom (usernameLabel) + 1
+		};
+
+		var passwordText = new TextField ("") {
+			Secret = true,
+			// align with the text box above
+			X = Pos.Left (usernameText),
+			Y = Pos.Top (passwordLabel),
+			Width = Dim.Fill (),
+		};
+
+		// Create login button
+		var btnLogin = new Button () {
+			Text = "Login",
+			Y = Pos.Bottom(passwordLabel) + 1,
+			// center the login button horizontally
+			X = Pos.Center (),
+			IsDefault = true,
+		};
+
+		// When login button is clicked display a message popup
+		btnLogin.Clicked += () => {
+			if (usernameText.Text == "admin" && passwordText.Text == "password") {
+				MessageBox.Query ("Logging In", "Login Successful", "Ok");
+				Application.RequestStop ();
+			} else {
+				MessageBox.ErrorQuery ("Logging In", "Incorrect username or password", "Ok");
+			}
+		};
+
+		// Add the views to the Window
+		Add (usernameLabel, usernameText, passwordLabel, passwordText, btnLogin);
+	}
+}

+ 0 - 3
Example/Example.csproj

@@ -10,9 +10,6 @@
     <Version>1.0</Version>
     <InformationalVersion>1.0</InformationalVersion>
   </PropertyGroup>
-  <ItemGroup>
-    <PackageReference Include="NStack.Core" Version="1.0.3" />
-  </ItemGroup>
   <ItemGroup>
     <ProjectReference Include="..\Terminal.Gui\Terminal.Gui.csproj" />
   </ItemGroup>

+ 65 - 50
README.md

@@ -68,56 +68,71 @@ The following example shows a basic Terminal.Gui application written in C#:
 
 using Terminal.Gui;
 
-// Initialize the console
-Application.Init();
-
-// Creates the top-level window with border and title
-var win = new Window("Example App (Ctrl+Q to quit)");
-
-// Create input components and labels
-
-var usernameLabel = new Label("Username:");
-var usernameText = new TextField("")
-{
-    // Position text field adjacent to label
-    X = Pos.Right(usernameLabel) + 1,
-
-    // Fill remaining horizontal space with a margin of 1
-    Width = Dim.Fill(1),
-};
-
-var passwordLabel = new Label(0,2,"Password:");
-var passwordText = new TextField("")
-{
-    Secret = true,
-    // align with the text box above
-    X = Pos.Left(usernameText),
-    Y = 2,
-    Width = Dim.Fill(1),
-};
-
-// Create login button
-var btnLogin = new Button("Login")
-{
-    Y = 4,
-    // center the login button horizontally
-    X = Pos.Center(),
-    IsDefault = true,
-};
-
-// When login button is clicked display a message popup
-btnLogin.Clicked += () => MessageBox.Query("Logging In", "Login Successful", "Ok");
-
-// Add all the views to the window
-win.Add(
-    usernameLabel, usernameText, passwordLabel, passwordText,btnLogin
-);
-
-// Show the application
-Application.Run(win);
-
-// After the application exits, release and reset console for clean shutdown
-Application.Shutdown();
+Application.Run<ExampleWindow> ();
+
+System.Console.WriteLine ($"Username: {((ExampleWindow)Application.Top).usernameText.Text}");
+
+// Before the application exits, reset Terminal.Gui for clean shutdown
+Application.Shutdown ();
+
+// Defines a top-level window with border and title
+public class ExampleWindow : Window {
+	public TextField usernameText;
+	
+	public ExampleWindow ()
+	{
+		Title = "Example App (Ctrl+Q to quit)";
+
+		// Create input components and labels
+		var usernameLabel = new Label () { 
+			Text = "Username:" 
+		};
+
+		usernameText = new TextField ("") {
+			// Position text field adjacent to the label
+			X = Pos.Right (usernameLabel) + 1,
+
+			// Fill remaining horizontal space
+			Width = Dim.Fill (),
+		};
+
+		var passwordLabel = new Label () {
+			Text = "Password:",
+			X = Pos.Left (usernameLabel),
+			Y = Pos.Bottom (usernameLabel) + 1
+		};
+
+		var passwordText = new TextField ("") {
+			Secret = true,
+			// align with the text box above
+			X = Pos.Left (usernameText),
+			Y = Pos.Top (passwordLabel),
+			Width = Dim.Fill (),
+		};
+
+		// Create login button
+		var btnLogin = new Button () {
+			Text = "Login",
+			Y = Pos.Bottom(passwordLabel) + 1,
+			// center the login button horizontally
+			X = Pos.Center (),
+			IsDefault = true,
+		};
+
+		// When login button is clicked display a message popup
+		btnLogin.Clicked += () => {
+			if (usernameText.Text == "admin" && passwordText.Text == "password") {
+				MessageBox.Query ("Logging In", "Login Successful", "Ok");
+				Application.RequestStop ();
+			} else {
+				MessageBox.ErrorQuery ("Logging In", "Incorrect username or password", "Ok");
+			}
+		};
+
+		// Add the views to the Window
+		Add (usernameLabel, usernameText, passwordLabel, passwordText, btnLogin);
+	}
+}
 ```
 
 When run the application looks as follows:

+ 11 - 7
Terminal.Gui/ConsoleDrivers/WindowsDriver.cs

@@ -1451,16 +1451,20 @@ namespace Terminal.Gui {
 		{
 			TerminalResized = terminalResized;
 
-			var winSize = WinConsole.GetConsoleOutputWindow (out Point pos);
-			cols = winSize.Width;
-			rows = winSize.Height;
+			try {
+				var winSize = WinConsole.GetConsoleOutputWindow (out Point pos);
+				cols = winSize.Width;
+				rows = winSize.Height;
 
-			WindowsConsole.SmallRect.MakeEmpty (ref damageRegion);
+				WindowsConsole.SmallRect.MakeEmpty (ref damageRegion);
 
-			ResizeScreen ();
-			UpdateOffScreen ();
+				ResizeScreen ();
+				UpdateOffScreen ();
 
-			CreateColors ();
+				CreateColors ();
+			} catch (Win32Exception e) {
+				throw new InvalidOperationException ("The Windows Console output window is not available.", e);
+			}
 		}
 
 		public override void ResizeScreen ()

+ 240 - 100
Terminal.Gui/Core/Application.cs

@@ -39,6 +39,7 @@ namespace Terminal.Gui {
 	/// };
 	/// Application.Top.Add(win);
 	/// Application.Run();
+	/// Application.Shutdown();
 	/// </code>
 	/// </example>
 	/// <remarks>
@@ -222,19 +223,28 @@ namespace Terminal.Gui {
 		public static bool ExitRunLoopAfterFirstIteration { get; set; } = false;
 
 		/// <summary>
-		/// Notify that a new <see cref="RunState"/> token was created,
-		/// used if <see cref="ExitRunLoopAfterFirstIteration"/> is true.
+		/// Notify that a new <see cref="RunState"/> was created (<see cref="Begin(Toplevel)"/> was called). The token is created in 
+		/// <see cref="Begin(Toplevel)"/> and this event will be fired before that function exits.
 		/// </summary>
+		/// <remarks>
+		///	If <see cref="ExitRunLoopAfterFirstIteration"/> is <see langword="true"/> callers to
+		///	<see cref="Begin(Toplevel)"/> must also subscribe to <see cref="NotifyStopRunState"/>
+		///	and manually dispose of the <see cref="RunState"/> token when the application is done.
+		/// </remarks>
 		public static event Action<RunState> NotifyNewRunState;
 
 		/// <summary>
-		/// Notify that a existent <see cref="RunState"/> token is stopping,
-		/// used if <see cref="ExitRunLoopAfterFirstIteration"/> is true.
+		/// Notify that a existent <see cref="RunState"/> is stopping (<see cref="End(RunState)"/> was called).
 		/// </summary>
+		/// <remarks>
+		///	If <see cref="ExitRunLoopAfterFirstIteration"/> is <see langword="true"/> callers to
+		///	<see cref="Begin(Toplevel)"/> must also subscribe to <see cref="NotifyStopRunState"/>
+		///	and manually dispose of the <see cref="RunState"/> token when the application is done.
+		/// </remarks>
 		public static event Action<Toplevel> NotifyStopRunState;
 
 		/// <summary>
-		///   This event is raised on each iteration of the <see cref="MainLoop"/> 
+		///   This event is raised on each iteration of the <see cref="MainLoop"/>. 
 		/// </summary>
 		/// <remarks>
 		///   See also <see cref="Timeout"/>
@@ -299,36 +309,59 @@ namespace Terminal.Gui {
 		/// </summary>
 		public static bool UseSystemConsole;
 
+		// For Unit testing - ignores UseSystemConsole
+		internal static bool ForceFakeConsole;
+
 		/// <summary>
 		/// Initializes a new instance of <see cref="Terminal.Gui"/> Application. 
 		/// </summary>
-		/// <remarks>
 		/// <para>
 		/// Call this method once per instance (or after <see cref="Shutdown"/> has been called).
 		/// </para>
 		/// <para>
-		/// Loads the right <see cref="ConsoleDriver"/> for the platform.
+		/// This function loads the right <see cref="ConsoleDriver"/> for the platform, 
+		/// Creates a <see cref="Toplevel"/>. and assigns it to <see cref="Top"/>
 		/// </para>
 		/// <para>
-		/// Creates a <see cref="Toplevel"/> and assigns it to <see cref="Top"/>
+		/// <see cref="Shutdown"/> must be called when the application is closing (typically after <see cref="Run(Func{Exception, bool})"/> has 
+		/// returned) to ensure resources are cleaned up and terminal settings restored.
 		/// </para>
-		/// </remarks>
-		public static void Init (ConsoleDriver driver = null, IMainLoopDriver mainLoopDriver = null) => Init (() => Toplevel.Create (), driver, mainLoopDriver);
+		/// <para>
+		/// The <see cref="Run{T}(Func{Exception, bool}, ConsoleDriver, IMainLoopDriver)"/> function 
+		/// combines <see cref="Init(ConsoleDriver, IMainLoopDriver)"/> and <see cref="Run(Toplevel, Func{Exception, bool})"/>
+		/// into a single call. An applciation cam use <see cref="Run{T}(Func{Exception, bool}, ConsoleDriver, IMainLoopDriver)"/> 
+		/// without explicitly calling <see cref="Init(ConsoleDriver, IMainLoopDriver)"/>.
+		/// </para>
+		/// <param name="driver">
+		/// The <see cref="ConsoleDriver"/> to use. If not specified the default driver for the
+		/// platform will be used (see <see cref="WindowsDriver"/>, <see cref="CursesDriver"/>, and <see cref="NetDriver"/>).</param>
+		/// <param name="mainLoopDriver">
+		/// Specifies the <see cref="MainLoop"/> to use. 
+		/// Must not be <see langword="null"/> if <paramref name="driver"/> is not <see langword="null"/>.
+		/// </param>
+		public static void Init (ConsoleDriver driver = null, IMainLoopDriver mainLoopDriver = null) => InternalInit (() => Toplevel.Create (), driver, mainLoopDriver);
 
 		internal static bool _initialized = false;
 		internal static int _mainThreadId = -1;
 
-		/// <summary>
-		/// Initializes the Terminal.Gui application
-		/// </summary>
-		static void Init (Func<Toplevel> topLevelFactory, ConsoleDriver driver = null, IMainLoopDriver mainLoopDriver = null)
+		// INTERNAL function for initializing an app with a Toplevel factory object, driver, and mainloop.
+		//
+		// Called from:
+		// 
+		// Init() - When the user wants to use the default Toplevel. calledViaRunT will be false, causing all state to be reset.
+		// Run<T>() - When the user wants to use a custom Toplevel. calledViaRunT will be true, enabling Run<T>() to be called without calling Init first.
+		// Unit Tests - To initialize the app with a custom Toplevel, using the FakeDriver. calledViaRunT will be false, causing all state to be reset.
+		// 
+		// calledViaRunT: If false (default) all state will be reset. If true the state will not be reset.
+		internal static void InternalInit (Func<Toplevel> topLevelFactory, ConsoleDriver driver = null, IMainLoopDriver mainLoopDriver = null, bool calledViaRunT = false)
 		{
 			if (_initialized && driver == null) return;
 
 			if (_initialized) {
-				throw new InvalidOperationException ("Init must be bracketed by Shutdown");
+				throw new InvalidOperationException ("Init has already been called and must be bracketed by Shutdown.");
 			}
 
+			// Note in this case, we don't verify the type of the Toplevel created by new T(). 
 			// Used only for start debugging on Unix.
 			//#if DEBUG
 			//			while (!System.Diagnostics.Debugger.IsAttached) {
@@ -337,23 +370,29 @@ namespace Terminal.Gui {
 			//			System.Diagnostics.Debugger.Break ();
 			//#endif
 
-			// Reset all class variables (Application is a singleton).
-			ResetState ();
+			if (!calledViaRunT) {
+				// Reset all class variables (Application is a singleton).
+				ResetState ();
+			}
 
-			// This supports Unit Tests and the passing of a mock driver/loopdriver
+			// FakeDriver (for UnitTests)
 			if (driver != null) {
 				if (mainLoopDriver == null) {
-					throw new ArgumentNullException ("mainLoopDriver cannot be null if driver is provided.");
+					throw new ArgumentNullException ("InternalInit mainLoopDriver cannot be null if driver is provided.");
+				}
+				if (!(driver is FakeDriver)) {
+					throw new InvalidOperationException ("InternalInit can only be called with FakeDriver.");
 				}
 				Driver = driver;
-				Driver.Init (TerminalResized);
-				MainLoop = new MainLoop (mainLoopDriver);
-				SynchronizationContext.SetSynchronizationContext (new MainLoopSyncContext (MainLoop));
 			}
 
 			if (Driver == null) {
 				var p = Environment.OSVersion.Platform;
-				if (UseSystemConsole) {
+				if (ForceFakeConsole) {
+					// For Unit Testing only
+					Driver = new FakeDriver ();
+					mainLoopDriver = new FakeMainLoop (() => FakeConsole.ReadKey (true));
+				} else if (UseSystemConsole) {
 					Driver = new NetDriver ();
 					mainLoopDriver = new NetMainLoop (Driver);
 				} else if (p == PlatformID.Win32NT || p == PlatformID.Win32S || p == PlatformID.Win32Windows) {
@@ -363,10 +402,21 @@ namespace Terminal.Gui {
 					mainLoopDriver = new UnixMainLoop ();
 					Driver = new CursesDriver ();
 				}
+			}
+			MainLoop = new MainLoop (mainLoopDriver);
+
+			try {
 				Driver.Init (TerminalResized);
-				MainLoop = new MainLoop (mainLoopDriver);
-				SynchronizationContext.SetSynchronizationContext (new MainLoopSyncContext (MainLoop));
+			} catch (InvalidOperationException ex) {
+				// This is a case where the driver is unable to initialize the console.
+				// This can happen if the console is already in use by another process or
+				// if running in unit tests.
+				// In this case, we want to throw a more specific exception.
+				throw new InvalidOperationException ("Unable to initialize the console. This can happen if the console is already in use by another process or in unit tests.", ex);
 			}
+			
+			SynchronizationContext.SetSynchronizationContext (new MainLoopSyncContext (MainLoop));
+
 			Top = topLevelFactory ();
 			Current = Top;
 			supportedCultures = GetSupportedCultures ();
@@ -375,7 +425,7 @@ namespace Terminal.Gui {
 		}
 
 		/// <summary>
-		/// Captures the execution state for the provided <see cref="Toplevel"/>  view.
+		/// Captures the execution state for the provided <see cref="Toplevel"/> view.
 		/// </summary>
 		public class RunState : IDisposable {
 			/// <summary>
@@ -391,31 +441,61 @@ namespace Terminal.Gui {
 			/// </summary>
 			public Toplevel Toplevel { get; internal set; }
 
+#if DEBUG_IDISPOSABLE
+			/// <summary>
+			/// For debug purposes to verify objects are being disposed properly
+			/// </summary>
+			public bool WasDisposed = false;
+			/// <summary>
+			/// For debug purposes to verify objects are being disposed properly
+			/// </summary>
+			public int DisposedCount = 0;
 			/// <summary>
-			/// Releases alTop = l resource used by the <see cref="Application.RunState"/> object.
+			/// For debug purposes
 			/// </summary>
-			/// <remarks>Call <see cref="Dispose()"/> when you are finished using the <see cref="Application.RunState"/>. The
+			public static List<RunState> Instances = new List<RunState> ();
+			/// <summary>
+			/// For debug purposes
+			/// </summary>
+			public RunState ()
+			{
+				Instances.Add (this);
+			}
+#endif
+
+			/// <summary>
+			/// Releases all resource used by the <see cref="Application.RunState"/> object.
+			/// </summary>
+			/// <remarks>
+			/// Call <see cref="Dispose()"/> when you are finished using the <see cref="Application.RunState"/>. 
+			/// </remarks>
+			/// <remarks>
 			/// <see cref="Dispose()"/> method leaves the <see cref="Application.RunState"/> in an unusable state. After
 			/// calling <see cref="Dispose()"/>, you must release all references to the
 			/// <see cref="Application.RunState"/> so the garbage collector can reclaim the memory that the
-			/// <see cref="Application.RunState"/> was occupying.</remarks>
+			/// <see cref="Application.RunState"/> was occupying.
+			/// </remarks>
 			public void Dispose ()
 			{
 				Dispose (true);
 				GC.SuppressFinalize (this);
+#if DEBUG_IDISPOSABLE
+				WasDisposed = true;
+#endif
 			}
 
 			/// <summary>
-			/// Dispose the specified disposing.
+			/// Releases all resource used by the <see cref="Application.RunState"/> object.
 			/// </summary>
-			/// <returns>The dispose.</returns>
-			/// <param name="disposing">If set to <c>true</c> disposing.</param>
+			/// <param name="disposing">If set to <see langword="true"/> we are disposing and should dispose held objects.</param>
 			protected virtual void Dispose (bool disposing)
 			{
 				if (Toplevel != null && disposing) {
-					End (Toplevel);
-					Toplevel.Dispose ();
-					Toplevel = null;
+					throw new InvalidOperationException ("You must clean up (Dispose) the Toplevel before calling Application.RunState.Dispose");
+					// BUGBUG: It's insidious that we call EndFirstTopLevel here so I moved it to End.
+					//EndFirstTopLevel (Toplevel);
+					//Toplevel.Dispose ();
+					//Toplevel = null;
 				}
 			}
 		}
@@ -802,8 +882,8 @@ namespace Terminal.Gui {
 		/// <summary>
 		/// Building block API: Prepares the provided <see cref="Toplevel"/>  for execution.
 		/// </summary>
-		/// <returns>The runstate handle that needs to be passed to the <see cref="End(RunState)"/> method upon completion.</returns>
-		/// <param name="toplevel">Toplevel to prepare execution for.</param>
+		/// <returns>The <see cref="RunState"/> handle that needs to be passed to the <see cref="End(RunState)"/> method upon completion.</returns>
+		/// <param name="toplevel">The <see cref="Toplevel"/> to prepare execution for.</param>
 		/// <remarks>
 		///  This method prepares the provided toplevel for running with the focus,
 		///  it adds this to the list of toplevels, sets up the mainloop to process the
@@ -816,13 +896,12 @@ namespace Terminal.Gui {
 		{
 			if (toplevel == null) {
 				throw new ArgumentNullException (nameof (toplevel));
-			} else if (toplevel.IsMdiContainer && MdiTop != null) {
+			} else if (toplevel.IsMdiContainer && MdiTop != toplevel && MdiTop != null) {
 				throw new InvalidOperationException ("Only one Mdi Container is allowed.");
 			}
 
 			var rs = new RunState (toplevel);
-
-			Init ();
+			
 			if (toplevel is ISupportInitializeNotification initializableNotification &&
 			    !initializableNotification.IsInitialized) {
 				initializableNotification.BeginInit ();
@@ -833,6 +912,13 @@ namespace Terminal.Gui {
 			}
 
 			lock (toplevels) {
+				// If Top was already initialized with Init, and Begin has never been called
+				// Top was not added to the toplevels Stack. It will thus never get disposed.
+				// Clean it up here:
+				if (Top != null && toplevel != Top && !toplevels.Contains(Top)) {
+					Top.Dispose ();
+					Top = null;
+				}
 				if (string.IsNullOrEmpty (toplevel.Id.ToString ())) {
 					var count = 1;
 					var id = (toplevels.Count + count).ToString ();
@@ -854,7 +940,8 @@ namespace Terminal.Gui {
 					throw new ArgumentException ("There are duplicates toplevels Id's");
 				}
 			}
-			if (toplevel.IsMdiContainer) {
+			// Fix $520 - Set Top = toplevel if Top == null
+			if (Top == null || toplevel.IsMdiContainer) {
 				Top = toplevel;
 			}
 
@@ -893,13 +980,14 @@ namespace Terminal.Gui {
 				Driver.Refresh ();
 			}
 
+			NotifyNewRunState?.Invoke (rs);
 			return rs;
 		}
 
 		/// <summary>
-		/// Building block API: completes the execution of a <see cref="Toplevel"/>  that was started with <see cref="Begin(Toplevel)"/> .
+		/// Building block API: completes the execution of a <see cref="Toplevel"/> that was started with <see cref="Begin(Toplevel)"/> .
 		/// </summary>
-		/// <param name="runState">The runstate returned by the <see cref="Begin(Toplevel)"/> method.</param>
+		/// <param name="runState">The <see cref="RunState"/> returned by the <see cref="Begin(Toplevel)"/> method.</param>
 		public static void End (RunState runState)
 		{
 			if (runState == null)
@@ -910,12 +998,52 @@ namespace Terminal.Gui {
 			} else {
 				runState.Toplevel.OnUnloaded ();
 			}
+
+			// End the RunState.Toplevel 
+			// First, take it off the toplevel Stack
+			if (toplevels.Count > 0) {
+				if (toplevels.Peek () != runState.Toplevel) {
+					// If there the top of the stack is not the RunState.Toplevel then
+					// this call to End is not balanced with the call to Begin that started the RunState
+					throw new ArgumentException ("End must be balanced with calls to Begin");
+				}
+				toplevels.Pop ();
+			}
+
+			// Notify that it is closing
+			runState.Toplevel?.OnClosed (runState.Toplevel);
+
+			// If there is a MdiTop that is not the RunState.Toplevel then runstate.TopLevel 
+			// is a child of MidTop and we should notify the MdiTop that it is closing
+			if (MdiTop != null && !(runState.Toplevel).Modal && runState.Toplevel != MdiTop) {
+				MdiTop.OnChildClosed (runState.Toplevel);
+			}
+
+			// Set Current and Top to the next TopLevel on the stack
+			if (toplevels.Count == 0) {
+				Current = null;
+			} else {
+				Current = toplevels.Peek ();
+				if (toplevels.Count == 1 && Current == MdiTop) {
+					MdiTop.OnAllChildClosed ();
+				} else {
+					SetCurrentAsTop ();
+				}
+				Refresh ();
+			}
+
+			runState.Toplevel?.Dispose ();
+			runState.Toplevel = null;
 			runState.Dispose ();
 		}
 
 		/// <summary>
-		/// Shutdown an application initialized with <see cref="Init(ConsoleDriver, IMainLoopDriver)"/>
+		/// Shutdown an application initialized with <see cref="Init(ConsoleDriver, IMainLoopDriver)"/>.
 		/// </summary>
+		/// <remarks>
+		/// Shutdown must be called for every call to <see cref="Init(ConsoleDriver, IMainLoopDriver)"/> or <see cref="Application.Run(Toplevel, Func{Exception, bool})"/>
+		/// to ensure all resources are cleaned up (Disposed) and terminal settings are restored.
+		/// </remarks>
 		public static void Shutdown ()
 		{
 			ResetState ();
@@ -930,15 +1058,17 @@ namespace Terminal.Gui {
 			// Shutdown is the bookend for Init. As such it needs to clean up all resources
 			// Init created. Apps that do any threading will need to code defensively for this.
 			// e.g. see Issue #537
-			// TODO: Some of this state is actually related to Begin/End (not Init/Shutdown) and should be moved to `RunState` (#520)
 			foreach (var t in toplevels) {
 				t.Running = false;
 				t.Dispose ();
 			}
 			toplevels.Clear ();
 			Current = null;
+			Top?.Dispose ();
 			Top = null;
 
+			// BUGBUG: MdiTop is not cleared here, but it should be?
+
 			MainLoop = null;
 			Driver?.End ();
 			Driver = null;
@@ -990,40 +1120,17 @@ namespace Terminal.Gui {
 			Driver.Refresh ();
 		}
 
-		internal static void End (View view)
-		{
-			if (toplevels.Peek () != view)
-				throw new ArgumentException ("The view that you end with must be balanced");
-			toplevels.Pop ();
-
-			(view as Toplevel)?.OnClosed ((Toplevel)view);
-
-			if (MdiTop != null && !((Toplevel)view).Modal && view != MdiTop) {
-				MdiTop.OnChildClosed (view as Toplevel);
-			}
 
-			if (toplevels.Count == 0) {
-				Current = null;
-			} else {
-				Current = toplevels.Peek ();
-				if (toplevels.Count == 1 && Current == MdiTop) {
-					MdiTop.OnAllChildClosed ();
-				} else {
-					SetCurrentAsTop ();
-				}
-				Refresh ();
-			}
-		}
 
 		/// <summary>
-		///   Building block API: Runs the main loop for the created dialog
+		///   Building block API: Runs the <see cref="MainLoop"/> for the created <see cref="Toplevel"/>.
 		/// </summary>
 		/// <remarks>
-		///   Use the wait parameter to control whether this is a
-		///   blocking or non-blocking call.
+		///   Use the <paramref name="wait"/> parameter to control whether this is a blocking or non-blocking call.
 		/// </remarks>
-		/// <param name="state">The state returned by the Begin method.</param>
-		/// <param name="wait">By default this is true which will execute the runloop waiting for events, if you pass false, you can use this method to run a single iteration of the events.</param>
+		/// <param name="state">The state returned by the <see cref="Begin(Toplevel)"/> method.</param>
+		/// <param name="wait">By default this is <see langword="true"/> which will execute the runloop waiting for events, 
+		/// if set to <see langword="false"/>, a single iteration will execute.</param>
 		public static void RunLoop (RunState state, bool wait = true)
 		{
 			if (state == null)
@@ -1033,18 +1140,21 @@ namespace Terminal.Gui {
 
 			bool firstIteration = true;
 			for (state.Toplevel.Running = true; state.Toplevel.Running;) {
-				if (ExitRunLoopAfterFirstIteration && !firstIteration)
+				if (ExitRunLoopAfterFirstIteration && !firstIteration) {
 					return;
+				}
 				RunMainLoopIteration (ref state, wait, ref firstIteration);
 			}
 		}
 
 		/// <summary>
-		/// Run one iteration of the MainLoop.
+		/// Run one iteration of the <see cref="MainLoop"/>.
 		/// </summary>
-		/// <param name="state">The state returned by the Begin method.</param>
-		/// <param name="wait">If will execute the runloop waiting for events.</param>
-		/// <param name="firstIteration">If it's the first run loop iteration.</param>
+		/// <param name="state">The state returned by <see cref="Begin(Toplevel)"/>.</param>
+		/// <param name="wait">If <see langword="true"/> will execute the runloop waiting for events. If <see langword="true"/>
+		/// will return after a single iteration.</param>
+		/// <param name="firstIteration">Set to <see langword="true"/> if this is the first run loop iteration. Upon return,
+		/// it will be set to <see langword="false"/> if at least one iteration happened.</param>
 		public static void RunMainLoopIteration (ref RunState state, bool wait, ref bool firstIteration)
 		{
 			if (MainLoop.EventsPending (wait)) {
@@ -1145,30 +1255,57 @@ namespace Terminal.Gui {
 		}
 
 		/// <summary>
-		/// Runs the application by calling <see cref="Run(Toplevel, Func{Exception, bool})"/> with the value of <see cref="Top"/>
+		/// Runs the application by calling <see cref="Run(Toplevel, Func{Exception, bool})"/> with the value of <see cref="Top"/>.
 		/// </summary>
+		/// <remarks>
+		/// See <see cref="Run(Toplevel, Func{Exception, bool})"/> for more details.
+		/// </remarks>
 		public static void Run (Func<Exception, bool> errorHandler = null)
 		{
 			Run (Top, errorHandler);
 		}
 
 		/// <summary>
-		/// Runs the application by calling <see cref="Run(Toplevel, Func{Exception, bool})"/> with a new instance of the specified <see cref="Toplevel"/>-derived class
+		/// Runs the application by calling <see cref="Run(Toplevel, Func{Exception, bool})"/> 
+		/// with a new instance of the specified <see cref="Toplevel"/>-derived class.
+		/// <para>
+		/// Calling <see cref="Init(ConsoleDriver, IMainLoopDriver)"/> first is not needed as this function will initialze the application.
+		/// </para>
+		/// <para>
+		/// <see cref="Shutdown"/> must be called when the application is closing (typically after Run> has 
+		/// returned) to ensure resources are cleaned up and terminal settings restored.
+		/// </para>
 		/// </summary>
-		public static void Run<T> (Func<Exception, bool> errorHandler = null) where T : Toplevel, new()
+		/// <remarks>
+		/// See <see cref="Run(Toplevel, Func{Exception, bool})"/> for more details.
+		/// </remarks>
+		/// <param name="errorHandler"></param>
+		/// <param name="driver">The <see cref="ConsoleDriver"/> to use. If not specified the default driver for the
+		/// platform will be used (<see cref="WindowsDriver"/>, <see cref="CursesDriver"/>, or <see cref="NetDriver"/>).
+		/// This parameteter must be <see langword="null"/> if <see cref="Init(ConsoleDriver, IMainLoopDriver)"/> has already been called. 
+		/// </param>
+		/// <param name="mainLoopDriver">Specifies the <see cref="MainLoop"/> to use.</param>
+		public static void Run<T> (Func<Exception, bool> errorHandler = null, ConsoleDriver driver = null, IMainLoopDriver mainLoopDriver = null) where T : Toplevel, new()
 		{
-			if (_initialized && Driver != null) {
-				var top = new T ();
-				var type = top.GetType ().BaseType;
-				while (type != typeof (Toplevel) && type != typeof (object)) {
-					type = type.BaseType;
-				}
-				if (type != typeof (Toplevel)) {
-					throw new ArgumentException ($"{top.GetType ().Name} must be derived from TopLevel");
+			if (_initialized) {
+				if (Driver != null) {
+					// Init() has been called and we have a driver, so just run the app.
+					var top = new T ();
+					var type = top.GetType ().BaseType;
+					while (type != typeof (Toplevel) && type != typeof (object)) {
+						type = type.BaseType;
+					}
+					if (type != typeof (Toplevel)) {
+						throw new ArgumentException ($"{top.GetType ().Name} must be derived from TopLevel");
+					}
+					Run (top, errorHandler);
+				} else {
+					// This codepath should be impossible because Init(null, null) will select the platform default driver
+					throw new InvalidOperationException ("Init() completed without a driver being set (this should be impossible); Run<T>() cannot be called.");
 				}
-				Run (top, errorHandler);
 			} else {
-				Init (() => new T ());
+				// Init() has NOT been called.
+				InternalInit (() => new T (), driver, mainLoopDriver, calledViaRunT: true);
 				Run (Top, errorHandler);
 			}
 		}
@@ -1192,16 +1329,19 @@ namespace Terminal.Gui {
 		///   <para>
 		///     Alternatively, to have a program control the main loop and 
 		///     process events manually, call <see cref="Begin(Toplevel)"/> to set things up manually and then
-		///     repeatedly call <see cref="RunLoop(RunState, bool)"/> with the wait parameter set to false.   By doing this
+		///     repeatedly call <see cref="RunLoop(RunState, bool)"/> with the wait parameter set to false. By doing this
 		///     the <see cref="RunLoop(RunState, bool)"/> method will only process any pending events, timers, idle handlers and
 		///     then return control immediately.
 		///   </para>
 		///   <para>
-		///     When <paramref name="errorHandler"/> is null the exception is rethrown, when it returns true the application is resumed and when false method exits gracefully.
+		///     RELEASE builds only: When <paramref name="errorHandler"/> is <see langword="null"/> any exeptions will be rethrown.  
+		///     Otheriwse, if <paramref name="errorHandler"/> will be called. If <paramref name="errorHandler"/> 
+		///     returns <see langword="true"/> the <see cref="RunLoop(RunState, bool)"/> will resume; otherwise 
+		///     this method will exit.
 		///   </para>
 		/// </remarks>
 		/// <param name="view">The <see cref="Toplevel"/> to run modally.</param>
-		/// <param name="errorHandler">Handler for any unhandled exceptions (resumes when returns true, rethrows when null).</param>
+		/// <param name="errorHandler">RELEASE builds only: Handler for any unhandled exceptions (resumes when returns true, rethrows when null).</param>
 		public static void Run (Toplevel view, Func<Exception, bool> errorHandler = null)
 		{
 			var resume = true;
@@ -1211,13 +1351,12 @@ namespace Terminal.Gui {
 #endif
 				resume = false;
 				var runToken = Begin (view);
+				// If ExitRunLoopAfterFirstIteration is true then the user must dispose of the runToken
+				// by using NotifyStopRunState event.
 				RunLoop (runToken);
-				if (!ExitRunLoopAfterFirstIteration)
+				if (!ExitRunLoopAfterFirstIteration) {
 					End (runToken);
-				else
-					// If ExitRunLoopAfterFirstIteration is true then the user must deal his disposing when it ends
-					// by using NotifyStopRunState event.
-					NotifyNewRunState?.Invoke (runToken);
+				}
 #if !DEBUG
 				}
 				catch (Exception error)
@@ -1310,8 +1449,9 @@ namespace Terminal.Gui {
 
 		static void OnNotifyStopRunState (Toplevel top)
 		{
-			if (ExitRunLoopAfterFirstIteration)
+			if (ExitRunLoopAfterFirstIteration) {
 				NotifyStopRunState?.Invoke (top);
+			}
 		}
 
 		/// <summary>

+ 3 - 2
Terminal.Gui/Core/ConsoleDriver.cs

@@ -683,7 +683,7 @@ namespace Terminal.Gui {
 		public abstract void Move (int col, int row);
 		
 		/// <summary>
-		/// Adds the specified rune to the display at the current cursor position
+		/// Adds the specified rune to the display at the current cursor position.
 		/// </summary>
 		/// <param name="rune">Rune to add.</param>
 		public abstract void AddRune (Rune rune);
@@ -717,10 +717,11 @@ namespace Terminal.Gui {
 			col >= 0 && row >= 0 && col < Cols && row < Rows && clip.Contains (col, row);
 
 		/// <summary>
-		/// Adds the specified
+		/// Adds the <paramref name="str"/> to the display at the cursor position.
 		/// </summary>
 		/// <param name="str">String.</param>
 		public abstract void AddStr (ustring str);
+
 		/// <summary>
 		/// Prepare the driver and set the key and mouse events handlers.
 		/// </summary>

+ 2 - 2
Terminal.Gui/Core/MainLoop.cs

@@ -94,8 +94,8 @@ namespace Terminal.Gui {
 		public IMainLoopDriver Driver { get; }
 
 		/// <summary>
-		/// Invoked when a new timeout is added to be used on the case
-		/// if <see cref="Application.ExitRunLoopAfterFirstIteration"/> is true,
+		/// Invoked when a new timeout is added. To be used in the case
+		/// when <see cref="Application.ExitRunLoopAfterFirstIteration"/> is <see langword="true"/>.
 		/// </summary>
 		public event Action<long> TimeoutAdded;
 

+ 4 - 4
Terminal.Gui/Core/Toplevel.cs

@@ -44,7 +44,7 @@ namespace Terminal.Gui {
 		public bool Running { get; set; }
 
 		/// <summary>
-		/// Invoked when the Toplevel <see cref="Application.RunState"/> has begin loaded.
+		/// Invoked when the Toplevel <see cref="Application.RunState"/> has begun to be loaded.
 		/// A Loaded event handler is a good place to finalize initialization before calling 
 		/// <see cref="Application.RunLoop(Application.RunState, bool)"/>.
 		/// </summary>
@@ -77,13 +77,13 @@ namespace Terminal.Gui {
 
 		/// <summary>
 		/// Invoked when a child of the Toplevel <see cref="Application.RunState"/> is closed by  
-		/// <see cref="Application.End(View)"/>.
+		/// <see cref="Application.End(Application.RunState)"/>.
 		/// </summary>
 		public event Action<Toplevel> ChildClosed;
 
 		/// <summary>
 		/// Invoked when the last child of the Toplevel <see cref="Application.RunState"/> is closed from 
-		/// by <see cref="Application.End(View)"/>.
+		/// by <see cref="Application.End(Application.RunState)"/>.
 		/// </summary>
 		public event Action AllChildClosed;
 
@@ -94,7 +94,7 @@ namespace Terminal.Gui {
 		public event Action<ToplevelClosingEventArgs> Closing;
 
 		/// <summary>
-		/// Invoked when the Toplevel's <see cref="Application.RunState"/> is closed by <see cref="Application.End(View)"/>.
+		/// Invoked when the Toplevel's <see cref="Application.RunState"/> is closed by <see cref="Application.End(Application.RunState)"/>.
 		/// </summary>
 		public event Action<Toplevel> Closed;
 

+ 1 - 1
Terminal.Gui/Core/View.cs

@@ -1496,7 +1496,7 @@ namespace Terminal.Gui {
 			if (Border != null) {
 				Border.DrawContent (this);
 			} else if (ustring.IsNullOrEmpty (TextFormatter.Text) &&
-				(GetType ().IsPublic || GetType ().IsNestedPublic) && !IsOverridden (this, "Redraw") &&
+				(GetType ().IsNestedPublic) && !IsOverridden (this, "Redraw") &&
 				(!NeedDisplay.IsEmpty || ChildNeedsDisplay || LayoutNeeded)) {
 
 				Clear ();

+ 31 - 30
Terminal.Gui/README.md

@@ -1,24 +1,24 @@
 # Terminal.Gui Project
 
-Contains all files required to build the **Terminal.Gui** library (and NuGet package).
+All files required to build the **Terminal.Gui** library (and NuGet package).
 
 ## Project Folder Structure
 
 - `Terminal.Gui.sln` - The Visual Studio solution
 - `Core/` - Source files for all types that comprise the core building blocks of **Terminal-Gui** 
     - `Application` - A `static` class that provides the base 'application driver'. Given it defines a **Terminal.Gui** application it is both logically and literally (because `static`) a singleton. It has direct dependencies on `MainLoop`, `Events.cs` `NetDriver`, `CursesDriver`, `WindowsDriver`, `Responder`, `View`, and `TopLevel` (and nothing else).
-    - `MainLoop` - Defines `IMainLoopDriver` and implements the and `MainLoop` class.
+    - `MainLoop` - Defines `IMainLoopDriver` and implements the `MainLoop` class.
     - `ConsoleDriver` - Definition for the Console Driver API.
-    - `Events.cs` - Defines keyboard and mouse related structs & classes. 
+    - `Events.cs` - Defines keyboard and mouse-related structs & classes. 
     - `PosDim.cs` - Implements *Computed Layout* system. These classes have deep dependencies on `View`.
     - `Responder` - Base class for the windowing class hierarchy. Implements support for keyboard & mouse input.
     - `View` - Derived from `Responder`, the base class for non-modal visual elements such as controls.
     - `Toplevel` - Derived from `View`, the base class for modal visual elements such as top-level windows and dialogs. Supports the concept of `MenuBar` and `StatusBar`.
-    - `Window` - Derived from `TopLevel`; implements top level views with a visible frame and Title.
+    - `Window` - Derived from `TopLevel`; implements toplevel views with a visible frame and Title.
 - `Types/` - A folder (not namespace) containing implementations of `Point`, `Rect`, and `Size` which are ancient versions of the modern `System.Drawing.Point`, `System.Drawing.Size`, and `System.Drawning.Rectangle`.
 - `ConsoleDrivers/` - Source files for the three `ConsoleDriver`-based drivers: .NET: `NetDriver`, Unix & Mac: `UnixDriver`, and Windows: `WindowsDriver`.
 - `Views/` - A folder (not namespace) containing the source for all built-in classes that drive from `View` (non-modals). 
-- `Windows/` - A folder (not namespace) containing the source all built-in classes that derive from `Window`.
+- `Windows/` - A folder (not namespace) containing the source of all built-in classes that derive from `Window`.
 
 ## Version numbers
 
@@ -55,43 +55,37 @@ The `tag` must be of the form `v<major>.<minor>.<patch>`, e.g. `v2.3.4`.
 
 `patch` can indicate pre-release or not (e.g. `pre`, `beta`, `rc`, etc...). 
 
-### 1) Generate release notes with the list of PRs since the last release 
+### 1) Verify the `develop` branch is ready for release
 
-Use `gh` to get a list with just titles to make it easy to paste into release notes: 
-
-```powershell
-gh pr list --limit 500 --search "is:pr is:closed is:merged closed:>=2021-05-18"
-```
+* Ensure everything is committed and pushed to the `develop` branch
+* Ensure your local `develop` branch is up-to-date with `upstream/develop`
 
-Use the output to update `./Terminal.Gui/Terminal.Gui.csproj` with latest release notes
+### 2) Create a pull request for the release in the `develop` branch
 
-### 2) Update the API documentation
-
-See `./docfx/README.md`.
-
-### 3) Create a PR for the release in the `develop` branch
-
-The PR title should be "Release v2.3.4"
+The PR title should be of the form "Release v2.3.4"
 
 ```powershell
 git checkout develop
-git pull -all
+git pull upstream develop
 git checkout -b v_2_3_4
+git merge develop
 git add .
 git commit -m "Release v2.3.4"
 git push
 ```
 
-### 4) On github.com, verify the build action worked on your fork, then merge the PR
+Go to the link printed by `git push` and fill out the Pull Request.
+
+### 3) On github.com, verify the build action worked on your fork, then merge the PR
 
-### 5) Pull the merged `develop` from `upstream`
+### 4) Pull the merged `develop` from `upstream`
 
 ```powershell
 git checkout develop
 git pull upstream develop
 ```
 
-### 6) Merge `develop` into `main`
+### 5) Merge `develop` into `main`
 
 ```powershell
 git checkout main
@@ -101,13 +95,13 @@ git merge develop
 
 Fix any merge errors.
 
-### 7) Create a new annotated tag for the release
+### 6) Create a new annotated tag for the release on `main`
 
 ```powershell
 git tag v2.3.4 -a -m "Release v2.3.4"
 ```       
 
-### 8) Push the new tag to `main` on `origin`
+### 7) Push the new tag to `main` on `upstream`
 
 ```powershell
 git push --atomic upstream main v2.3.4
@@ -115,16 +109,23 @@ git push --atomic upstream main v2.3.4
 
 *See https://stackoverflow.com/a/3745250/297526*
 
-### 9) Monitor Github actions to ensure the Nuget publishing worked.
+### 8) Monitor Github Actions to ensure the Nuget publishing worked.
 
-### 10) Check Nuget to see the new package version (wait a few minutes): 
+https://github.com/gui-cs/Terminal.Gui/actions
+
+### 9) Check Nuget to see the new package version (wait a few minutes) 
 https://www.nuget.org/packages/Terminal.Gui
 
-### 11) Add a new Release in Github: https://github.com/gui-cs/Terminal.Gui/releases
+### 10) Add a new Release in Github: https://github.com/gui-cs/Terminal.Gui/releases
+
+Generate release notes with the list of PRs since the last release 
 
-### 12) Tweet about it
+Use `gh` to get a list with just titles to make it easy to paste into release notes: 
 
-### 13) Update the `develop` branch
+```powershell
+gh pr list --limit 500 --search "is:pr is:closed is:merged closed:>=2021-05-18"
+```
+### 11) Update the `develop` branch with the new version
 
 ```powershell
 git checkout develop

+ 4 - 85
Terminal.Gui/Terminal.Gui.csproj

@@ -17,7 +17,7 @@
   </PropertyGroup>
   <ItemGroup>
     <PackageReference Include="Microsoft.NETFramework.ReferenceAssemblies" Version="1.0.3" PrivateAssets="all" />
-    <PackageReference Include="NStack.Core" Version="1.0.3" />
+    <PackageReference Include="NStack.Core" Version="1.0.5" />
     <InternalsVisibleTo Include="UnitTests" />
   </ItemGroup>
   <!-- Uncomment the RestoreSources element to have dotnet restore pull NStack from a local dir for testing -->
@@ -74,93 +74,12 @@
     <PackageIcon>logo.png</PackageIcon>
     <PackageReadmeFile>README.md</PackageReadmeFile>
     <PackageTags>csharp, terminal, c#, f#, gui, toolkit, console, tui</PackageTags>
-    <Description>Cross Platform Terminal UI toolkit for .NET</Description>
+    <Description>Cross platform Terminal UI toolkit for .NET</Description>
     <Owners>Miguel de Icaza, Charlie Kindel</Owners>
     <Summary>A toolkit for building rich console apps for .NET that works on Windows, Mac, and Linux/Unix.</Summary>
-    <Title>Terminal.Gui - Cross Platform Terminal user interface toolkit for .NET</Title>
+    <Title>Terminal.Gui - Cross platform Terminal User Interface (TUI) toolkit for .NET</Title>
     <PackageReleaseNotes>
-      Release v1.8.1
-      * Fixes #2053. MessageBox.Query not wrapping correctly 
-      
-      Release v1.8.0
-      * Fixes #2043. Update to NStack v1.0.3
-      * Fixes #2045. TrySetClipboardData test must be enclosed with a lock.
-      * Fixes #2025. API Docs are now generated via Github Action - View Source Works
-      * Fixes #1991. Broken link in README
-      * Fixes #2026. Added ClearOnVisibleFalse to flag if the view must be cleared or not.
-      * Fixes #2017 and #2013. MainLoopTests.InvokeLeakTest failures
-      * Fixes #2014. Application mouseGrabView is run twice if return true.
-      * Fixes #2011. Wizard no longer needs to set specific colors, because #1971 has been fixed.
-      * Fixes #2006. ProgressBarStyles isn't disposing the _fractionTimer on quitting if running.
-      * Fixes #2004. TextFormatter.Justified not adding the extra spaces.
-      * Fixes #2002. Added feature to fill the remaining width with spaces.
-      * Fixes #1999. Prevents the mouseGrabView being executed with a null view.
-      * Fixes #1994. BREAKING CHANGE. Ensure only a single IdleHandlers list can exist.
-      * Fixes #1979. MessageBox.Query not wrapping since 1.7.1
-      * Fixes #1989. ListView: Ensures SelectedItem visibility on MoveDown and MoveUp.
-      * Fixes #1987. Textview insert text newline fix
-      * Fixes #1984. Setting Label.Visible to false does not hide the Label
-      * Fixes #820. Added HideDropdownListOnClick property.
-      * Fixes #1981. Added SplitNewLine method to the TextFormatter.
-      * Fixes #1973. Avoid positioning Submenus off screen.
-      * Added abstract MakeColor and CreateColors to create the colors at once.
-      * Fixes #1800. TextView now uses the same colors as TextField.
-      * Fixes #1969. ESC on CursesDriver take to long to being processed.
-      * Fixes #1967. New keys for DeleteAll on TextField and TextView.
-      * Fixes #1962 - Change KeyBindings to allow chaining commands
-      * Fixes #1961 Null reference in Keybindings Scenario and hotkey collision
-      * Fixes #1963. Only remove one character on backspace when wordwrap is on
-      * Fixes #1959. GoToEnd should not fail on an empty TreeView
-      * Fixes #1953. TextView cursor position is not updating by mouse.
-      * Fixes #1951. TextView with selected text doesn't scroll beyond the cursor position.
-      * Fixes #1948. Get unwrapped cursor position when word wrap is enabled on TextView.
-      * Ensures that the isButtonShift flag is disabled in all situations.
-      * Fixes #1943. Mouse ButtonShift is not preserving the text selected.
-
-      Release v1.7.2
-      * Fixes #1773. Base color scheme for ListView hard to read
-      * Fixes #1934. WindowsDriver crash when the height is less than 1 with the VS Debugger
-
-      Release v1.7.1
-      * Fixes #1930. Trailing whitespace makes MessageBox.Query buttons disappear.
-      * Fixes #1921. Mouse continuous button pressed is not working on ScrollView.
-      * Fixes #1924. Wizard: Selected help text is unreadable
-
-      Release v1.7.0
-      * Moved Terminal.Gui (and NStack) to the github.com/gui-cs organization.
-      * Adds multi-step Wizard View for setup experiences (#124)
-      * The synchronization context method Send is now blocking (#1854).
-      * Fixes #1917. Sometimes Clipboard.IsSupported doesn't return the correct
-      * Fixes #1893: Fix URLs to match gui-cs Org
-      * Fixes #1883. Child TopLevels now get Loaded/Ready events.
-      * Fixes #1867, #1866, #1796. TextView enhancements for ReadOnly and WordWrap.
-      * Fixes #1861. Border: Title property is preferable to Text.
-      * Fixes #1855. Window and Frame content view without the margin frame.
-      * Fixes #1848. Mouse clicks in Windows Terminal.
-      * Fixes #1846. TabView now clips to the draw bounds.
-      * Fix TableView multi selections extending to -1 indexes
-      * Fixes #1837. Setting Unix clipboard freezes.
-      * Fixes #1839. Process WindowsDriver click event if location is the same after pressed and released.
-      * Fixes #1830. If "libcoreclr.so" is not present then "libncursesw.so" will be used.
-      * Fixes #1816. MessageBox: Hides underlying dialog when visible
-      * Fixes #1815. Now returns false if WSL clipboard isn't supported.
-      * Fixes #1825. Parent MenuItem stay focused if child MenuItem is empty.
-      * Fixes #1812, #1797, #1791. AutoSize fixes.
-      * Fixes #1818. Adds Title change events to Window.
-      * Fixes #1810. Dialog: Closing event is not fired when ESC is pressed to close dialog.
-      * Fixes #1793. ScrollBarView is hiding if the host fit the available space.
-      * Added Pos/Dim Function feature to automate layout.
-      * Fixes #1786. Windows Terminal is reporting well on mouse button pressed + mouse movement.
-      * Fixes #1777 - Dialog button justification. Adds unit tests.
-      * Fixes #1739. Setting menu UseKeysUpDownAsKeysLeftRight as false by default.
-      * Fixes #1772. Avoids WindowsDriver flickering when resizing.
-      * Fixed TableView always showing selected cell(s) even when not focused
-      * Fixes #1769. Supports a minimum view size for non-automatic size views.
-      * Exposes APIs to support upcoming Web console feature
-      * Fixes some scrolling performance issues
-      * Fixes #1763. Allowing read console inputs before idle handlers.
-      * TableView unicode scenario usability
-      * Added unicode testing code to TableEditor
+      See: https://github.com/gui-cs/Terminal.Gui/releases
     </PackageReleaseNotes>
   </PropertyGroup>
 </Project>

+ 1 - 2
Terminal.Gui/Views/Button.cs

@@ -248,8 +248,7 @@ namespace Terminal.Gui {
 		///<inheritdoc/>
 		public override bool MouseEvent (MouseEvent me)
 		{
-			if (me.Flags == MouseFlags.Button1Clicked || me.Flags == MouseFlags.Button1DoubleClicked ||
-				me.Flags == MouseFlags.Button1TripleClicked) {
+			if (me.Flags == MouseFlags.Button1Clicked) {
 				if (CanFocus && Enabled) {
 					if (!HasFocus) {
 						SetFocus ();

+ 7 - 1
Terminal.Gui/Views/GraphView.cs

@@ -240,7 +240,13 @@ namespace Terminal.Gui {
 				);
 		}
 
-
+		/// <inheritdoc/>
+		/// <remarks>Also ensures that cursor is invisible after entering the <see cref="GraphView"/>.</remarks>
+		public override bool OnEnter (View view)
+		{
+			Driver.SetCursorVisibility (CursorVisibility.Invisible);
+			return base.OnEnter (view);
+		}
 
 		/// <inheritdoc/>
 		public override bool ProcessKey (KeyEvent keyEvent)

+ 14 - 14
Terminal.Gui/Views/ScrollBarView.cs

@@ -462,7 +462,7 @@ namespace Terminal.Gui {
 				return;
 			}
 
-			Driver.SetAttribute (GetNormalColor ());
+			Driver.SetAttribute (Host.HasFocus ? ColorScheme.Focus : GetNormalColor ());
 
 			if ((vertical && Bounds.Height == 0) || (!vertical && Bounds.Width == 0)) {
 				return;
@@ -613,13 +613,13 @@ namespace Terminal.Gui {
 		int posBarOffset;
 
 		///<inheritdoc/>
-		public override bool MouseEvent (MouseEvent me)
+		public override bool MouseEvent (MouseEvent mouseEvent)
 		{
-			if (me.Flags != MouseFlags.Button1Pressed && me.Flags != MouseFlags.Button1DoubleClicked &&
-				!me.Flags.HasFlag (MouseFlags.Button1Pressed | MouseFlags.ReportMousePosition) &&
-				me.Flags != MouseFlags.Button1Released && me.Flags != MouseFlags.WheeledDown &&
-				me.Flags != MouseFlags.WheeledUp && me.Flags != MouseFlags.WheeledRight &&
-				me.Flags != MouseFlags.WheeledLeft && me.Flags != MouseFlags.Button1TripleClicked) {
+			if (mouseEvent.Flags != MouseFlags.Button1Pressed && mouseEvent.Flags != MouseFlags.Button1DoubleClicked &&
+				!mouseEvent.Flags.HasFlag (MouseFlags.Button1Pressed | MouseFlags.ReportMousePosition) &&
+				mouseEvent.Flags != MouseFlags.Button1Released && mouseEvent.Flags != MouseFlags.WheeledDown &&
+				mouseEvent.Flags != MouseFlags.WheeledUp && mouseEvent.Flags != MouseFlags.WheeledRight &&
+				mouseEvent.Flags != MouseFlags.WheeledLeft && mouseEvent.Flags != MouseFlags.Button1TripleClicked) {
 				return false;
 			}
 
@@ -630,24 +630,24 @@ namespace Terminal.Gui {
 				Host.SetFocus ();
 			}
 
-			int location = vertical ? me.Y : me.X;
+			int location = vertical ? mouseEvent.Y : mouseEvent.X;
 			int barsize = vertical ? Bounds.Height : Bounds.Width;
 			int posTopLeftTee = vertical ? posTopTee + 1 : posLeftTee + 1;
 			int posBottomRightTee = vertical ? posBottomTee + 1 : posRightTee + 1;
 			barsize -= 2;
 			var pos = Position;
 
-			if (me.Flags != MouseFlags.Button1Released
+			if (mouseEvent.Flags != MouseFlags.Button1Released
 				&& (Application.MouseGrabView == null || Application.MouseGrabView != this)) {
 				Application.GrabMouse (this);
-			} else if (me.Flags == MouseFlags.Button1Released && Application.MouseGrabView != null && Application.MouseGrabView == this) {
+			} else if (mouseEvent.Flags == MouseFlags.Button1Released && Application.MouseGrabView != null && Application.MouseGrabView == this) {
 				lastLocation = -1;
 				Application.UngrabMouse ();
 				return true;
 			}
-			if (showScrollIndicator && (me.Flags == MouseFlags.WheeledDown || me.Flags == MouseFlags.WheeledUp ||
-				me.Flags == MouseFlags.WheeledRight || me.Flags == MouseFlags.WheeledLeft)) {
-				return Host.MouseEvent (me);
+			if (showScrollIndicator && (mouseEvent.Flags == MouseFlags.WheeledDown || mouseEvent.Flags == MouseFlags.WheeledUp ||
+				mouseEvent.Flags == MouseFlags.WheeledRight || mouseEvent.Flags == MouseFlags.WheeledLeft)) {
+				return Host.MouseEvent (mouseEvent);
 			}
 
 			if (location == 0) {
@@ -668,7 +668,7 @@ namespace Terminal.Gui {
 				//}
 
 				if (lastLocation > -1 || (location >= posTopLeftTee && location <= posBottomRightTee
-				&& me.Flags.HasFlag (MouseFlags.Button1Pressed | MouseFlags.ReportMousePosition))) {
+				&& mouseEvent.Flags.HasFlag (MouseFlags.Button1Pressed | MouseFlags.ReportMousePosition))) {
 					if (lastLocation == -1) {
 						lastLocation = location;
 						posBarOffset = keepContentAlwaysInViewport ? Math.Max (location - posTopLeftTee, 1) : 0;

+ 3 - 2
Terminal.Gui/Views/ScrollView.cs

@@ -32,6 +32,7 @@ namespace Terminal.Gui {
 		private class ContentView : View {
 			public ContentView (Rect frame) : base (frame)
 			{
+				CanFocus = true;
 			}
 		}
 
@@ -325,7 +326,7 @@ namespace Terminal.Gui {
 		{
 			Driver.SetAttribute (GetNormalColor ());
 			SetViewsNeedsDisplay ();
-			Clear ();
+			//Clear ();
 
 			var savedClip = ClipToBounds ();
 			OnDrawContent (new Rect (ContentOffset,
@@ -507,7 +508,7 @@ namespace Terminal.Gui {
 		{
 			if (me.Flags != MouseFlags.WheeledDown && me.Flags != MouseFlags.WheeledUp &&
 				me.Flags != MouseFlags.WheeledRight && me.Flags != MouseFlags.WheeledLeft &&
-				me.Flags != MouseFlags.Button1Pressed && me.Flags != MouseFlags.Button1Clicked &&
+//				me.Flags != MouseFlags.Button1Pressed && me.Flags != MouseFlags.Button1Clicked &&
 				!me.Flags.HasFlag (MouseFlags.Button1Pressed | MouseFlags.ReportMousePosition)) {
 				return false;
 			}

+ 3 - 24
Terminal.Gui/Views/TabView.cs

@@ -466,31 +466,10 @@ namespace Terminal.Gui {
 				Width = Dim.Fill ();
 			}
 
-			/// <summary>
-			/// Positions the cursor at the start of the currently selected tab
-			/// </summary>
-			public override void PositionCursor ()
+			public override bool OnEnter (View view)
 			{
-				base.PositionCursor ();
-
-				var selected = host.CalculateViewport (Bounds).FirstOrDefault (t => Equals (host.SelectedTab, t.Tab));
-
-				if (selected == null) {
-					return;
-				}
-
-				int y;
-
-				if (host.Style.TabsOnBottom) {
-					y = 1;
-				} else {
-					y = host.Style.ShowTopLine ? 1 : 0;
-				}
-
-				Move (selected.X, y);
-
-
-
+				Driver.SetCursorVisibility (CursorVisibility.Invisible);
+				return base.OnEnter (view);
 			}
 
 			public override void Redraw (Rect bounds)

+ 1 - 1
Terminal.Gui/Windows/Wizard.cs

@@ -159,7 +159,7 @@ namespace Terminal.Gui {
 			public event Action<TitleEventArgs> TitleChanged;
 
 			// The contentView works like the ContentView in FrameView.
-			private View contentView = new View ();
+			private View contentView = new View () { Data = "WizardContentView" };
 
 			/// <summary>
 			/// Sets or gets help text for the <see cref="WizardStep"/>.If <see cref="WizardStep.HelpText"/> is empty

+ 10 - 6
UICatalog/Properties/launchSettings.json

@@ -23,14 +23,18 @@
       "commandName": "Project",
       "commandLineArgs": "WizardAsView"
     },
-    "Issue1719Repro": {
-      "commandName": "Project",
-      "commandLineArgs": "\"ProgressBar Styles\""
-    },
     "VkeyPacketSimulator": {
       "commandName": "Project",
       "commandLineArgs": "VkeyPacketSimulator"
     },
+    "CollectionNavigatorTester": {
+      "commandName": "Project",
+      "commandLineArgs": "\"Search Collection Nav\""
+    },
+    "Charmap": {
+      "commandName": "Project",
+      "commandLineArgs": "\"Character Map\""
+    },
     "WSL2": {
       "commandName": "Executable",
       "executablePath": "wsl",
@@ -45,9 +49,9 @@
       "commandName": "WSL2",
       "distributionName": ""
     },
-    "CollectionNavigatorTester": {
+    "All Views Tester": {
       "commandName": "Project",
-      "commandLineArgs": "\"Search Collection Nav\""
+      "commandLineArgs": "\"All Views Tester\""
     }
   }
 }

+ 15 - 50
UICatalog/README.md

@@ -2,8 +2,9 @@
 
 UI Catalog is a comprehensive sample library for Terminal.Gui. It attempts to satisfy the following goals:
 
-1. Be an easy to use showcase for Terminal.Gui concepts and features.
-2. Provide sample code that illustrates how to properly implement said concepts & features.
+1. Be an easy-to-use showcase for Terminal.Gui concepts and features.
+2. Provide sample code that illustrates how to properly implement 
+said concepts & features.
 3. Make it easy for contributors to add additional samples in a structured way.
 
 ![screenshot](screenshot.png)
@@ -51,7 +52,7 @@ To add a new **Scenario** simply:
 4. Implement the `Setup` override which will be called when a user selects the scenario to run.
 5. Optionally, implement the `Init` and/or `Run` overrides to provide a custom implementation.
 
-The sample below is provided in the `Scenarios` directory as a generic sample that can be copied and re-named:
+The sample below is provided in the `.\UICatalog\Scenarios` directory as a generic sample that can be copied and re-named:
 
 ```csharp
 using Terminal.Gui;
@@ -73,59 +74,23 @@ namespace UICatalog {
 }
 ```
 
-`Scenario` provides a `Toplevel` and `Window` the provides a canvas for the Scenario to operate. The default `Window` shows the Scenario name and supports exiting the Scenario through the `Esc` key. 
+`Scenario` provides `Win`, a `Window` object that provides a canvas for the Scenario to operate. 
 
-![screenshot](generic_screenshot.png)
-
-To build a more advanced scenario, where control of the `Toplevel` and `Window` is needed (e.g. for scenarios using `MenuBar` or `StatusBar`), simply set the `Top` and `Window` properties as appropriate, as seen in the `UnicodeInMenu` scenario:
+The default `Window` shows the Scenario name and supports exiting the Scenario through the `Esc` key. 
 
-```csharp
-using Terminal.Gui;
+![screenshot](generic_screenshot.png)
 
-namespace UICatalog {
-	[ScenarioMetadata (Name: "Unicode In Menu", Description: "Unicode menus per PR #204")]
-	[ScenarioCategory ("Text")]
-	[ScenarioCategory ("Controls")]
-	class UnicodeInMenu : Scenario {
-		public override void Setup ()
-		{
-			Top = new Toplevel (new Rect (0, 0, Application.Driver.Cols, Application.Driver.Rows));
-			var menu = new MenuBar (new MenuBarItem [] {
-				new MenuBarItem ("_Файл", new MenuItem [] {
-					new MenuItem ("_Создать", "Creates new file", null),
-					new MenuItem ("_Открыть", "", null),
-					new MenuItem ("Со_хранить", "", null),
-					new MenuItem ("_Выход", "", () => Application.RequestStop() )
-				}),
-				new MenuBarItem ("_Edit", new MenuItem [] {
-					new MenuItem ("_Copy", "", null),
-					new MenuItem ("C_ut", "", null),
-					new MenuItem ("_Paste", "", null)
-				})
-			});
-			Top.Add (menu);
-
-			Win = new Window ($"Scenario: {GetName ()}") {
-				X = 0,
-				Y = 1,
-				Width = Dim.Fill (),
-				Height = Dim.Fill ()
-			};
-			Top.Add (Win);
-		}
-	}
-}
-```
+To build a more advanced scenario, where control of the `Toplevel` and `Window` is needed (e.g. for scenarios using `MenuBar` or `StatusBar`), simply use `Application.Top` per normal Terminal.Gui programming, as seen in the `Notepad` scenario.
 
-For complete control, the `Init` and `Run` overrides can be implemented. The `base.Init` assigns `Application.Top` to `Top` and creates `Win`. The `base.Run` simply calls `Application.Run(Top)`.
+For complete control, the `Init` and `Run` overrides can be implemented. The `base.Init` creates `Win`. The `base.Run` simply calls `Application.Run(Application.Top)`.
 
 ## Contribution Guidelines
 
-- Provide a terse, descriptive name for `Scenarios`. Keep them short; the `ListView` that displays them dynamically sizes the column width and long names will make it hard for people to use.
-- Provide a clear description.
+- Provide a terse, descriptive `Name` for `Scenarios`. Keep them short.
+- Provide a clear `Description`.
 - Comment `Scenario` code to describe to others why it's a useful `Scenario`.
-- Annotate `Scenarios` with `[ScenarioCategory]` attributes. Try to minimize the number of new categories created.
-- Use the `Bug Rero` Category for `Scnarios` that reproduce bugs. 
+- Annotate `Scenarios` with `[ScenarioCategory]` attributes. Minimize the number of new categories created.
+- Use the `Bug Repo` Category for `Scenarios` that reproduce bugs. 
 	- Include the Github Issue # in the Description.
-	- Once the bug has been fixed in `master` submit another PR to remove the `Scenario` (or modify it to provide a good regression test).
-- Tag bugs or suggestions for `UI Catalog` as [`Terminal.Gui` Github Issues](https://github.com/gui-cs/Terminal.Gui/issues) with "UICatalog: ".
+	- Once the bug has been fixed in `develop` submit another PR to remove the `Scenario` (or modify it to provide a good regression test/sample).
+- Tag bugs or suggestions for `UI Catalog` as [`Terminal.Gui` Github Issues](https://github.com/gui-cs/Terminal.Gui/issues) with "UICatalog: ".

+ 8 - 14
UICatalog/Scenario.cs

@@ -48,12 +48,7 @@ namespace UICatalog {
 		private bool _disposedValue;
 
 		/// <summary>
-		/// The Top level for the <see cref="Scenario"/>. This should be set to <see cref="Terminal.Gui.Application.Top"/> in most cases.
-		/// </summary>
-		public Toplevel Top { get; set; }
-
-		/// <summary>
-		/// The Window for the <see cref="Scenario"/>. This should be set within the <see cref="Terminal.Gui.Application.Top"/> in most cases.
+		/// The Window for the <see cref="Scenario"/>. This should be set to <see cref="Terminal.Gui.Application.Top"/> in most cases.
 		/// </summary>
 		public Window Win { get; set; }
 
@@ -63,22 +58,21 @@ namespace UICatalog {
 		/// the Scenario picker UI.
 		/// Override <see cref="Init"/> to provide any <see cref="Terminal.Gui.Toplevel"/> behavior needed.
 		/// </summary>
-		/// <param name="top">The Toplevel created by the UI Catalog host.</param>
 		/// <param name="colorScheme">The colorscheme to use.</param>
 		/// <remarks>
 		/// <para>
-		/// The base implementation calls <see cref="Application.Init"/>, sets <see cref="Top"/> to the passed in <see cref="Toplevel"/>, creates a <see cref="Window"/> for <see cref="Win"/> and adds it to <see cref="Top"/>.
+		/// The base implementation calls <see cref="Application.Init"/> and creates a <see cref="Window"/> for <see cref="Win"/> 
+		/// and adds it to <see cref="Application.Top"/>.
 		/// </para>
 		/// <para>
-		/// Overrides that do not call the base.<see cref="Run"/>, must call <see cref="Application.Init"/> before creating any views or calling other Terminal.Gui APIs.
+		/// Overrides that do not call the base.<see cref="Run"/>, must call <see cref="Application.Init"/> 
+		/// before creating any views or calling other Terminal.Gui APIs.
 		/// </para>
 		/// </remarks>
-		public virtual void Init (Toplevel top, ColorScheme colorScheme)
+		public virtual void Init (ColorScheme colorScheme)
 		{
 			Application.Init ();
 
-			Top = top != null ? top : Application.Top;
-
 			Win = new Window ($"CTRL-Q to Close - Scenario: {GetName ()}") {
 				X = 0,
 				Y = 0,
@@ -86,7 +80,7 @@ namespace UICatalog {
 				Height = Dim.Fill (),
 				ColorScheme = colorScheme,
 			};
-			Top.Add (Win);
+			Application.Top.Add (Win);
 		}
 
 		/// <summary>
@@ -201,7 +195,7 @@ namespace UICatalog {
 		public virtual void Run ()
 		{
 			// Must explicit call Application.Shutdown method to shutdown.
-			Application.Run (Top);
+			Application.Run (Application.Top);
 		}
 
 		/// <summary>

+ 10 - 24
UICatalog/Scenarios/AllViewsTester.cs

@@ -14,7 +14,7 @@ namespace UICatalog.Scenarios {
 	[ScenarioCategory ("Tests")]
 	[ScenarioCategory ("Top Level Windows")]
 	public class AllViewsTester : Scenario {
-		Window _leftPane;
+		FrameView _leftPane;
 		ListView _classListView;
 		FrameView _hostPane;
 
@@ -40,42 +40,33 @@ namespace UICatalog.Scenarios {
 		TextField _hText;
 		int _hVal = 0;
 
-		public override void Init (Toplevel top, ColorScheme colorScheme)
+		public override void Init (ColorScheme colorScheme)
 		{
 			Application.Init ();
-
-			Top = top != null ? top : Application.Top;
-
-			//Win = new Window ($"CTRL-Q to Close - Scenario: {GetName ()}") {
-			//	X = 0,
-			//	Y = 0,
-			//	Width = Dim.Fill (),
-			//	Height = Dim.Fill ()
-			//};
-			//Top.Add (Win);
+			// Don't create a sub-win; just use Applicatiion.Top
 		}
-
+		
 		public override void Setup ()
 		{
 			var statusBar = new StatusBar (new StatusItem [] {
 				new StatusItem(Key.CtrlMask | Key.Q, "~^Q~ Quit", () => Quit()),
 				new StatusItem(Key.F2, "~F2~ Toggle Frame Ruler", () => {
 					ConsoleDriver.Diagnostics ^= ConsoleDriver.DiagnosticFlags.FrameRuler;
-					Top.SetNeedsDisplay ();
+					Application.Top.SetNeedsDisplay ();
 				}),
 				new StatusItem(Key.F3, "~F3~ Toggle Frame Padding", () => {
 					ConsoleDriver.Diagnostics ^= ConsoleDriver.DiagnosticFlags.FramePadding;
-					Top.SetNeedsDisplay ();
+					Application.Top.SetNeedsDisplay ();
 				}),
 			});
-			Top.Add (statusBar);
+			Application.Top.Add (statusBar);
 
 			_viewClasses = GetAllViewClassesCollection ()
 				.OrderBy (t => t.Name)
 				.Select (t => new KeyValuePair<string, Type> (t.Name, t))
 				.ToDictionary (t => t.Key, t => t.Value);
 
-			_leftPane = new Window ("Classes") {
+			_leftPane = new FrameView ("Classes") {
 				X = 0,
 				Y = 0,
 				Width = 15,
@@ -241,9 +232,9 @@ namespace UICatalog.Scenarios {
 				ColorScheme = Colors.Dialog,
 			};
 
-			Top.Add (_leftPane, _settingsPane, _hostPane);
+			Application.Top.Add (_leftPane, _settingsPane, _hostPane);
 
-			Top.LayoutSubviews ();
+			Application.Top.LayoutSubviews ();
 
 			_curView = CreateClass (_viewClasses.First ().Value);
 		}
@@ -438,11 +429,6 @@ namespace UICatalog.Scenarios {
 			UpdateTitle (_curView);
 		}
 
-		public override void Run ()
-		{
-			base.Run ();
-		}
-
 		private void Quit ()
 		{
 			Application.RequestStop ();

+ 1 - 8
UICatalog/Scenarios/BackgroundWorkerCollection.cs

@@ -12,17 +12,10 @@ namespace UICatalog.Scenarios {
 	[ScenarioCategory ("Dialogs")]
 	[ScenarioCategory ("Controls")]
 	public class BackgroundWorkerCollection : Scenario {
-		public override void Init (Toplevel top, ColorScheme colorScheme)
-		{
-			Application.Top.Dispose ();
-
-			Application.Run<MdiMain> ();
-
-			Application.Top.Dispose ();
-		}
 
 		public override void Run ()
 		{
+			Application.Run<MdiMain> ();
 		}
 
 		class MdiMain : Toplevel {

+ 4 - 6
UICatalog/Scenarios/BordersComparisons.cs

@@ -5,12 +5,10 @@ namespace UICatalog.Scenarios {
 	[ScenarioCategory ("Layout")]
 	[ScenarioCategory ("Borders")]
 	public class BordersComparisons : Scenario {
-		public override void Init (Toplevel top, ColorScheme colorScheme)
+		public override void Init (ColorScheme colorScheme)
 		{
 			Application.Init ();
 
-			top = Application.Top;
-
 			var borderStyle = BorderStyle.Double;
 			var drawMarginFrame = false;
 			var borderThickness = new Thickness (1, 2, 3, 4);
@@ -53,7 +51,7 @@ namespace UICatalog.Scenarios {
 				Width = 10
 			};
 			win.Add (tf1, button, label, tv, tf2);
-			top.Add (win);
+			Application.Top.Add (win);
 
 			var top2 = new Border.ToplevelContainer (new Rect (50, 5, 40, 20),
 				new Border () {
@@ -92,7 +90,7 @@ namespace UICatalog.Scenarios {
 				Width = 10
 			};
 			top2.Add (tf3, button2, label2, tv2, tf4);
-			top.Add (top2);
+			Application.Top.Add (top2);
 
 			var frm = new FrameView (new Rect (95, 5, 40, 20), "Test3", null,
 				new Border () {
@@ -128,7 +126,7 @@ namespace UICatalog.Scenarios {
 				Width = 10
 			};
 			frm.Add (tf5, button3, label3, tv3, tf6);
-			top.Add (frm);
+			Application.Top.Add (frm);
 
 			Application.Run ();
 		}

+ 2 - 2
UICatalog/Scenarios/Buttons.cs

@@ -56,7 +56,7 @@ namespace UICatalog.Scenarios {
 
 			//View prev = colorButtonsLabel;
 
-			//With this method there is no need to call Top.Ready += () => Top.Redraw (Top.Bounds);
+			//With this method there is no need to call Application.TopReady += () => Application.TopRedraw (Top.Bounds);
 			var x = Pos.Right (colorButtonsLabel) + 2;
 			foreach (var colorScheme in Colors.ColorSchemes) {
 				var colorButton = new Button ($"{colorScheme.Key}") {
@@ -272,7 +272,7 @@ namespace UICatalog.Scenarios {
 				}
 			};
 
-			Top.Ready += () => radioGroup.Refresh ();
+			Application.Top.Ready += () => radioGroup.Refresh ();
 		}
 	}
 }

+ 75 - 87
UICatalog/Scenarios/CharacterMap.cs

@@ -2,6 +2,7 @@
 //#define BASE_DRAW_CONTENT
 
 using NStack;
+using System;
 using System.Collections.Generic;
 using System.Linq;
 using System.Text;
@@ -11,11 +12,12 @@ using Rune = System.Rune;
 namespace UICatalog.Scenarios {
 	/// <summary>
 	/// This Scenario demonstrates building a custom control (a class deriving from View) that:
-	///   - Provides a simple "Character Map" application (like Windows' charmap.exe).
+	///   - Provides a "Character Map" application (like Windows' charmap.exe).
 	///   - Helps test unicode character rendering in Terminal.Gui
 	///   - Illustrates how to use ScrollView to do infinite scrolling
 	/// </summary>
-	[ScenarioMetadata (Name: "Character Map", Description: "A Unicode character set viewier built as a custom control using the ScrollView control.")]
+	[ScenarioMetadata (Name: "Character Map",
+		Description: "A Unicode character set viewier built as a custom control using the ScrollView control.")]
 	[ScenarioCategory ("Text and Formatting")]
 	[ScenarioCategory ("Controls")]
 	[ScenarioCategory ("ScrollView")]
@@ -26,28 +28,15 @@ namespace UICatalog.Scenarios {
 			_charMap = new CharMap () {
 				X = 0,
 				Y = 0,
-				Width = CharMap.RowWidth + 2,
 				Height = Dim.Fill (),
-				Start = 0x2500,
-				ColorScheme = Colors.Dialog,
-				CanFocus = true,
 			};
 
-			Win.Add (_charMap);
-			var label = new Label ("Jump To Unicode Block:") { X = Pos.Right (_charMap) + 1, Y = Pos.Y (_charMap) };
-			Win.Add (label);
-
-			(ustring radioLabel, int start, int end) CreateRadio (ustring title, int start, int end)
-			{
-				return ($"{title} (U+{start:x5}-{end:x5})", start, end);
-			}
-
 			var radioItems = new (ustring radioLabel, int start, int end) [] {
-				CreateRadio("ASCII Control Characterss", 0x00, 0x1F),
+				CreateRadio("ASCII Control Characters", 0x00, 0x1F),
 				CreateRadio("C0 Control Characters", 0x80, 0x9f),
 				CreateRadio("Hangul Jamo", 0x1100, 0x11ff),	// This is where wide chars tend to start
 				CreateRadio("Currency Symbols", 0x20A0, 0x20CF),
-				CreateRadio("Letterlike Symbols", 0x2100, 0x214F),
+				CreateRadio("Letter-like Symbols", 0x2100, 0x214F),
 				CreateRadio("Arrows", 0x2190, 0x21ff),
 				CreateRadio("Mathematical symbols", 0x2200, 0x22ff),
 				CreateRadio("Miscellaneous Technical", 0x2300, 0x23ff),
@@ -55,17 +44,25 @@ namespace UICatalog.Scenarios {
 				CreateRadio("Miscellaneous Symbols", 0x2600, 0x26ff),
 				CreateRadio("Dingbats", 0x2700, 0x27ff),
 				CreateRadio("Braille", 0x2800, 0x28ff),
-				CreateRadio("Miscellaneous Symbols and Arrows", 0x2b00, 0x2bff),
-				CreateRadio("Alphabetic Presentation Forms", 0xFB00, 0xFb4f),
-				CreateRadio("Cuneiform Numbers and Punctuation", 0x12400, 0x1240f),
+				CreateRadio("Miscellaneous Symbols & Arrows", 0x2b00, 0x2bff),
+				CreateRadio("Alphabetic Pres. Forms", 0xFB00, 0xFb4f),
+				CreateRadio("Cuneiform Num. and Punct.", 0x12400, 0x1240f),
 				CreateRadio("Chess Symbols", 0x1FA00, 0x1FA0f),
 				CreateRadio("End", CharMap.MaxCodePointVal - 16, CharMap.MaxCodePointVal),
 			};
+			(ustring radioLabel, int start, int end) CreateRadio (ustring title, int start, int end)
+			{
+				return ($"{title} (U+{start:x5}-{end:x5})", start, end);
+			}
+
+			Win.Add (_charMap);
+			var label = new Label ("Jump To Unicode Block:") { X = Pos.Right (_charMap) + 1, Y = Pos.Y (_charMap) };
+			Win.Add (label);
 
 			var jumpList = new RadioGroup (radioItems.Select (t => t.radioLabel).ToArray ()) {
 				X = Pos.X (label),
 				Y = Pos.Bottom (label),
-				Width = Dim.Fill (),
+				Width = radioItems.Max (r => r.radioLabel.Length) + 3,
 				SelectedItem = 8
 			};
 			jumpList.SelectedItemChanged += (args) => {
@@ -76,11 +73,9 @@ namespace UICatalog.Scenarios {
 
 			jumpList.Refresh ();
 			jumpList.SetFocus ();
-		}
 
-		public override void Run ()
-		{
-			base.Run ();
+			_charMap.Width = Dim.Fill () - jumpList.Width;
+
 		}
 	}
 
@@ -98,97 +93,90 @@ namespace UICatalog.Scenarios {
 				SetNeedsDisplay ();
 			}
 		}
+
 		int _start = 0x2500;
 
-		public const int H_SPACE = 2;
-		public const int V_SPACE = 2;
+		public const int COLUMN_WIDTH = 3;
+		public const int ROW_HEIGHT = 1;
 
 		public static int MaxCodePointVal => 0x10FFFF;
 
-		// Row Header + space + (space + char + space)
-		public static int RowHeaderWidth => $"U+{MaxCodePointVal:x5}".Length;
-		public static int RowWidth => RowHeaderWidth + (H_SPACE * 16);
+		public static int RowLabelWidth => $"U+{MaxCodePointVal:x5}".Length;
+		public static int RowWidth => RowLabelWidth + (COLUMN_WIDTH * 16);
 
 		public CharMap ()
 		{
+			ColorScheme = Colors.Dialog;
+			CanFocus = true;
+
 			ContentSize = new Size (CharMap.RowWidth, MaxCodePointVal / 16);
 			ShowVerticalScrollIndicator = true;
 			ShowHorizontalScrollIndicator = false;
 			LayoutComplete += (args) => {
-				if (Bounds.Width <= RowWidth) {
+				if (Bounds.Width < RowWidth) {
 					ShowHorizontalScrollIndicator = true;
 				} else {
 					ShowHorizontalScrollIndicator = false;
+					// Snap 1st column into view if it's been scrolled horizontally 
+					ContentOffset = new Point (0, ContentOffset.Y);
+					SetNeedsDisplay ();
 				}
 			};
-#if DRAW_CONTENT
-
 			DrawContent += CharMap_DrawContent;
-#endif
+
+			AddCommand (Command.ScrollUp, () => { ScrollUp (1); return true; });
+			AddCommand (Command.ScrollDown, () => { ScrollDown (1); return true; });
+			AddCommand (Command.ScrollLeft, () => { ScrollLeft (1); return true; });
+			AddCommand (Command.ScrollRight, () => { ScrollRight (1); return true; });
 		}
 
 		private void CharMap_DrawContent (Rect viewport)
 		{
-			//Rune ReplaceNonPrintables (Rune c)
-			//{
-			//	if (c < 0x20) {
-			//		return new Rune (c + 0x2400);         // U+25A1 □ WHITE SQUARE
-			//	} else {
-			//		return c;
-			//	}
-			//}
-
-			ContentSize = new Size (CharMap.RowWidth, MaxCodePointVal / 16 + Frame.Height - 1);
-
-			for (int header = 0; header < 16; header++) {
-				Move (viewport.X + RowHeaderWidth + (header * H_SPACE), 0);
-				Driver.AddStr ($" {header:x} ");
+			var oldClip = Driver.Clip;
+			Driver.Clip = Frame;
+			// Redraw doesn't know about the scroll indicators, so if off, add one to height
+			if (!ShowHorizontalScrollIndicator) {
+				Driver.Clip = new Rect (Frame.X, Frame.Y, Frame.Width, Frame.Height + 1);
+			}
+			Driver.SetAttribute (HasFocus ? ColorScheme.HotFocus : ColorScheme.Focus);
+			Move (0, 0);
+			Driver.AddStr (new string (' ', RowLabelWidth + 1));
+			for (int hexDigit = 0; hexDigit < 16; hexDigit++) {
+				var x = ContentOffset.X + RowLabelWidth + (hexDigit * COLUMN_WIDTH);
+				if (x > RowLabelWidth - 2) {
+					Move (x, 0);
+					Driver.AddStr ($" {hexDigit:x} ");
+				}
 			}
-			for (int row = 0, y = 0; row < viewport.Height / 2 - 1; row++, y += V_SPACE) {
-				int val = (-viewport.Y + row) * 16;
+			//Move (RowWidth, 0);
+			//Driver.AddRune (' ');
+
+			var firstColumnX = viewport.X + RowLabelWidth;
+			for (int row = -ContentOffset.Y, y = 0; row <= (-ContentOffset.Y) + (Bounds.Height / ROW_HEIGHT); row++, y += ROW_HEIGHT) {
+				int val = (row) * 16;
+				Driver.SetAttribute (GetNormalColor ());
+				Move (firstColumnX, y + 1);
+				Driver.AddStr (new string (' ', 16 * COLUMN_WIDTH));
 				if (val < MaxCodePointVal) {
-					var rowLabel = $"U+{val / 16:x4}x";
-					Move (0, y + 1);
-					Driver.AddStr (rowLabel);
-					var prevColWasWide = false;
+					Driver.SetAttribute (GetNormalColor ());
 					for (int col = 0; col < 16; col++) {
-						var rune = new Rune ((uint)((uint)(-viewport.Y + row) * 16 + col));
-						Move (viewport.X + RowHeaderWidth + (col * H_SPACE) + (prevColWasWide ? 0 : 1), y + 1);
-						if (rune >= 0x00D800 && rune <= 0x00DFFF) {
-							if (col == 0) {
-								Driver.AddStr ("Reserved to surrogate pairs.");
-							}
-							continue;
-						}
+						var rune = new Rune ((uint)((uint)val + col));
+						//if (rune >= 0x00D800 && rune <= 0x00DFFF) {
+						//	if (col == 0) {
+						//		Driver.AddStr ("Reserved for surrogate pairs.");
+						//	}
+						//	continue;
+						//}						
+						Move (firstColumnX + (col * COLUMN_WIDTH) + 1, y + 1);
 						Driver.AddRune (rune);
-						//prevColWasWide = Rune.ColumnWidth (rune) > 1;
 					}
+					Move (0, y + 1);
+					Driver.SetAttribute (HasFocus ? ColorScheme.HotFocus : ColorScheme.Focus);
+					var rowLabel = $"U+{val / 16:x4}x ";
+					Driver.AddStr (rowLabel);
 				}
 			}
-		}
-#if BASE_DRAW_CONTENT
-		public override void OnDrawContent (Rect viewport)
-		{
-			CharMap_DrawContent (viewport);
-			base.OnDrawContent (viewport);
-		}
-#endif
-
-		public override bool ProcessKey (KeyEvent kb)
-		{
-			if (kb.Key == Key.PageDown) {
-				ContentOffset = new Point (0, ContentOffset.Y - Bounds.Height / 2 + 1);
-				return true;
-			}
-			if (kb.Key == Key.PageUp) {
-				if (ContentOffset.Y + Bounds.Height / 2 - 1 < 0) {
-					ContentOffset = new Point (0, ContentOffset.Y + Bounds.Height / 2 - 1);
-				} else {
-					ContentOffset = Point.Empty;
-				}
-				return true;
-			}
-			return base.ProcessKey (kb);
+			Driver.Clip = oldClip;
 		}
 
 		protected override void Dispose (bool disposing)

+ 2 - 2
UICatalog/Scenarios/ClassExplorer.cs

@@ -60,7 +60,7 @@ namespace UICatalog.Scenarios {
 			Win.Title = this.GetName ();
 			Win.Y = 1; // menu
 			Win.Height = Dim.Fill (1); // status bar
-			Top.LayoutSubviews ();
+			Application.Top.LayoutSubviews ();
 
 			var menu = new MenuBar (new MenuBarItem [] {
 				new MenuBarItem ("_File", new MenuItem [] {
@@ -80,7 +80,7 @@ namespace UICatalog.Scenarios {
 					},
 				}) 
 			});
-			Top.Add (menu);
+			Application.Top.Add (menu);
 
 			treeView = new TreeView<object> () {
 				X = 0,

+ 4 - 7
UICatalog/Scenarios/Clipping.cs

@@ -7,13 +7,10 @@ namespace UICatalog.Scenarios {
 
 	public class Clipping : Scenario {
 
-		public override void Init (Toplevel top, ColorScheme colorScheme)
+		public override void Init (ColorScheme colorScheme)
 		{
 			Application.Init ();
-
-			Top = top != null ? top : Application.Top;
-
-			Top.ColorScheme = Colors.Base;
+			Application.Top.ColorScheme = Colors.Base;
 		}
 
 		public override void Setup ()
@@ -26,7 +23,7 @@ namespace UICatalog.Scenarios {
 				X = 0, Y = 0,
 				//ColorScheme = Colors.Dialog
 			};
-			Top.Add (label);
+			Application.Top.Add (label);
 
 			var scrollView = new ScrollView (new Rect (3, 3, 50, 20));
 			scrollView.ColorScheme = Colors.Menu;
@@ -69,7 +66,7 @@ namespace UICatalog.Scenarios {
 
 			scrollView.Add (embedded1);
 
-			Top.Add (scrollView);
+			Application.Top.Add (scrollView);
 		}
 	}
 }

+ 8 - 9
UICatalog/Scenarios/CollectionNavigatorTester.cs

@@ -15,11 +15,10 @@ namespace UICatalog.Scenarios {
 	public class CollectionNavigatorTester : Scenario {
 
 		// Don't create a Window, just return the top-level view
-		public override void Init (Toplevel top, ColorScheme colorScheme)
+		public override void Init (ColorScheme colorScheme)
 		{
 			Application.Init ();
-			Top = top != null ? top : Application.Top;
-			Top.ColorScheme = Colors.Base;
+			Application.Top.ColorScheme = Colors.Base;
 		}
 
 		System.Collections.Generic.List<string> _items = new string [] {
@@ -103,7 +102,7 @@ namespace UICatalog.Scenarios {
 				new MenuBarItem("_Quit", "CTRL-Q", () => Quit()),
 			});
 
-			Top.Add (menu);
+			Application.Top.Add (menu);
 
 			_items.Sort (StringComparer.OrdinalIgnoreCase);
 
@@ -113,7 +112,7 @@ namespace UICatalog.Scenarios {
 				Y = 1,
 				Height = Dim.Fill ()
 			};
-			Top.Add (vsep);
+			Application.Top.Add (vsep);
 			CreateTreeView ();
 		}
 
@@ -129,7 +128,7 @@ namespace UICatalog.Scenarios {
 				Width = Dim.Percent (50),
 				Height = 1,
 			};
-			Top.Add (label);
+			Application.Top.Add (label);
 
 			_listView = new ListView () {
 				X = 0,
@@ -139,7 +138,7 @@ namespace UICatalog.Scenarios {
 				AllowsMarking = false,
 				AllowsMultipleSelection = false,
 			};
-			Top.Add (_listView);
+			Application.Top.Add (_listView);
 
 			_listView.SetSource (_items);
 
@@ -160,7 +159,7 @@ namespace UICatalog.Scenarios {
 				Width = Dim.Percent	 (50),
 				Height = 1,
 			};
-			Top.Add (label);
+			Application.Top.Add (label);
 
 			_treeView = new TreeView () {
 				X = Pos.Right (_listView) + 1,
@@ -169,7 +168,7 @@ namespace UICatalog.Scenarios {
 				Height = Dim.Fill ()
 			};
 			_treeView.Style.HighlightModelTextOnly = true;
-			Top.Add (_treeView);
+			Application.Top.Add (_treeView);
 
 			var root = new TreeNode ("IsLetterOrDigit examples");
 			root.Children = _items.Where (i => char.IsLetterOrDigit (i [0])).Select (i => new TreeNode (i)).Cast<ITreeNode> ().ToList ();

+ 2 - 2
UICatalog/Scenarios/ComputedLayout.cs

@@ -25,12 +25,12 @@ namespace UICatalog.Scenarios {
 					new MenuItem ("_Quit", "", () => Quit()),
 				}),
 			});
-			Top.Add (menu);
+			Application.Top.Add (menu);
 
 			var statusBar = new StatusBar (new StatusItem [] {
 				new StatusItem(Key.CtrlMask | Key.Q, "~^Q~ Quit", () => Quit()),
 			});
-			Top.Add (statusBar);
+			Application.Top.Add (statusBar);
 
 			//Top.LayoutStyle = LayoutStyle.Computed;
 			// Demonstrate using Dim to create a horizontal ruler that always measures the parent window's width

+ 1 - 1
UICatalog/Scenarios/ContextMenus.cs

@@ -81,7 +81,7 @@ namespace UICatalog.Scenarios {
 
 			Win.WantMousePositionReports = true;
 
-			Top.Closed += (_) => {
+			Application.Top.Closed += (_) => {
 				Thread.CurrentThread.CurrentUICulture = new CultureInfo ("en-US");
 				Application.RootMouseEvent -= Application_RootMouseEvent;
 			};

+ 3 - 3
UICatalog/Scenarios/CsvEditor.cs

@@ -34,7 +34,7 @@ namespace UICatalog.Scenarios {
 			Win.Title = this.GetName();
 			Win.Y = 1; // menu
 			Win.Height = Dim.Fill (1); // status bar
-			Top.LayoutSubviews ();
+			Application.Top.LayoutSubviews ();
 
 			this.tableView = new TableView () {
 				X = 0,
@@ -70,14 +70,14 @@ namespace UICatalog.Scenarios {
 					miCentered = new MenuItem ("_Set Format Pattern", "", () => SetFormat()),
 				})
 			});
-			Top.Add (menu);
+			Application.Top.Add (menu);
 
 			var statusBar = new StatusBar (new StatusItem [] {
 				new StatusItem(Key.CtrlMask | Key.O, "~^O~ Open", () => Open()),
 				new StatusItem(Key.CtrlMask | Key.S, "~^S~ Save", () => Save()),
 				new StatusItem(Key.CtrlMask | Key.Q, "~^Q~ Quit", () => Quit()),
 			});
-			Top.Add (statusBar);
+			Application.Top.Add (statusBar);
 
 			Win.Add (tableView);
 

+ 2 - 2
UICatalog/Scenarios/Dialogs.cs

@@ -116,9 +116,9 @@ namespace UICatalog.Scenarios {
 			{
 				frame.Height = Dim.Height (widthEdit) + Dim.Height (heightEdit) + Dim.Height (titleEdit)
 					+ Dim.Height (numButtonsEdit) + Dim.Height (styleRadioGroup) + Dim.Height(glyphsNotWords) + 2;
-				Top.Loaded -= Top_Loaded;
+				Application.Top.Loaded -= Top_Loaded;
 			}
-			Top.Loaded += Top_Loaded;
+			Application.Top.Loaded += Top_Loaded;
 
 			label = new Label ("Button Pressed:") {
 				X = Pos.Center (),

+ 2 - 3
UICatalog/Scenarios/DynamicMenuBar.cs

@@ -13,11 +13,10 @@ namespace UICatalog.Scenarios {
 	[ScenarioCategory ("Top Level Windows")]
 	[ScenarioCategory ("Menus")]
 	public class DynamicMenuBar : Scenario {
-		public override void Init (Toplevel top, ColorScheme colorScheme)
+		public override void Init (ColorScheme colorScheme)
 		{
 			Application.Init ();
-			Top = Application.Top;
-			Top.Add (new DynamicMenuBarSample ($"CTRL-Q to Close - Scenario: {GetName ()}"));
+			Application.Top.Add (new DynamicMenuBarSample ($"CTRL-Q to Close - Scenario: {GetName ()}"));
 		}
 
 		public class DynamicMenuItemList {

+ 2 - 3
UICatalog/Scenarios/DynamicStatusBar.cs

@@ -12,11 +12,10 @@ namespace UICatalog.Scenarios {
 	[ScenarioMetadata (Name: "Dynamic StatusBar", Description: "Demonstrates how to add and remove a StatusBar and change items dynamically.")]
 	[ScenarioCategory ("Top Level Windows")]
 	public class DynamicStatusBar : Scenario {
-		public override void Init (Toplevel top, ColorScheme colorScheme)
+		public override void Init (ColorScheme colorScheme)
 		{
 			Application.Init ();
-			Top = Application.Top;
-			Top.Add (new DynamicStatusBarSample ($"CTRL-Q to Close - Scenario: {GetName ()}"));
+			Application.Top.Add (new DynamicStatusBarSample ($"CTRL-Q to Close - Scenario: {GetName ()}"));
 		}
 
 		public class DynamicStatusItemList {

+ 7 - 7
UICatalog/Scenarios/Editor.cs

@@ -30,12 +30,12 @@ namespace UICatalog.Scenarios {
 		private TabView _tabView;
 		private MenuItem _miForceMinimumPosToZero;
 		private bool _forceMinimumPosToZero = true;
-		private readonly List<CultureInfo> _cultureInfos = Application.SupportedCultures;
+		private List<CultureInfo> _cultureInfos;
 
-		public override void Init (Toplevel top, ColorScheme colorScheme)
+		public override void Init (ColorScheme colorScheme)
 		{
 			Application.Init ();
-			Top = top != null ? top : Application.Top;
+			_cultureInfos = Application.SupportedCultures;
 
 			Win = new Window (_fileName ?? "Untitled") {
 				X = 0,
@@ -44,7 +44,7 @@ namespace UICatalog.Scenarios {
 				Height = Dim.Fill (),
 				ColorScheme = colorScheme,
 			};
-			Top.Add (Win);
+			Application.Top.Add (Win);
 
 			_textView = new TextView () {
 				X = 0,
@@ -114,7 +114,7 @@ namespace UICatalog.Scenarios {
 				})
 			});
 
-			Top.Add (menu);
+			Application.Top.Add (menu);
 
 			var statusBar = new StatusBar (new StatusItem [] {
 				siCursorPosition,
@@ -124,7 +124,7 @@ namespace UICatalog.Scenarios {
 				new StatusItem(Key.CtrlMask | Key.Q, "~^Q~ Quit", () => Quit()),
 				new StatusItem(Key.Null, $"OS Clipboard IsSupported : {Clipboard.IsSupported}", null)
 			});
-			Top.Add (statusBar);
+			Application.Top.Add (statusBar);
 
 			_scrollBar = new ScrollBarView (_textView, true);
 
@@ -196,7 +196,7 @@ namespace UICatalog.Scenarios {
 				}
 			};
 
-			Top.Closed += (_) => Thread.CurrentThread.CurrentUICulture = new CultureInfo ("en-US");
+			Application.Top.Closed += (_) => Thread.CurrentThread.CurrentUICulture = new CultureInfo ("en-US");
 		}
 
 		private void DisposeWinDialog ()

+ 76 - 0
UICatalog/Scenarios/Generic - Copy.cs

@@ -0,0 +1,76 @@
+using Terminal.Gui;
+
+namespace UICatalog.Scenarios {
+	[ScenarioMetadata (Name: "Run<T> Example", Description: "Illustrates using Application.Run<T> to run a custom class")]
+	[ScenarioCategory ("Top Level Windows")]
+	public class RunTExample : Scenario {
+		public override void Setup ()
+		{
+			// No need to call Init if Application.Run<T> is used
+		}
+
+		public override void Run ()
+		{
+			Application.Run<ExampleWindow> ();
+		}
+
+		public class ExampleWindow : Window {
+			public TextField usernameText;
+
+			public ExampleWindow ()
+			{
+				Title = "Example App (Ctrl+Q to quit)";
+
+				// Create input components and labels
+				var usernameLabel = new Label () {
+					Text = "Username:"
+				};
+
+				usernameText = new TextField ("") {
+					// Position text field adjacent to the label
+					X = Pos.Right (usernameLabel) + 1,
+
+					// Fill remaining horizontal space
+					Width = Dim.Fill (),
+				};
+
+				var passwordLabel = new Label () {
+					Text = "Password:",
+					X = Pos.Left (usernameLabel),
+					Y = Pos.Bottom (usernameLabel) + 1
+				};
+
+				var passwordText = new TextField ("") {
+					Secret = true,
+					// align with the text box above
+					X = Pos.Left (usernameText),
+					Y = Pos.Top (passwordLabel),
+					Width = Dim.Fill (),
+				};
+
+				// Create login button
+				var btnLogin = new Button () {
+					Text = "Login",
+					Y = Pos.Bottom (passwordLabel) + 1,
+					// center the login button horizontally
+					X = Pos.Center (),
+					IsDefault = true,
+				};
+
+				// When login button is clicked display a message popup
+				btnLogin.Clicked += () => {
+					if (usernameText.Text == "admin" && passwordText.Text == "password") {
+						MessageBox.Query ("Login Successful", $"Username: {usernameText.Text}", "Ok");
+						Application.RequestStop ();
+					} else {
+						MessageBox.ErrorQuery ("Error Logging In", "Incorrect username or password (hint: admin/password)", "Ok");
+					}
+				};
+
+				// Add the views to the Window
+				Add (usernameLabel, usernameText, passwordLabel, passwordText, btnLogin);
+			}
+		}
+
+	}
+}

+ 3 - 3
UICatalog/Scenarios/GraphViewExample.cs

@@ -23,7 +23,7 @@ namespace UICatalog.Scenarios {
 			Win.Title = this.GetName ();
 			Win.Y = 1; // menu
 			Win.Height = Dim.Fill (1); // status bar
-			Top.LayoutSubviews ();
+			Application.Top.LayoutSubviews ();
 
 			graphs = new Action [] {
 				 ()=>SetupPeriodicTableScatterPlot(),    //0
@@ -59,7 +59,7 @@ namespace UICatalog.Scenarios {
 				}),
 
 				});
-			Top.Add (menu);
+			Application.Top.Add (menu);
 
 			graphView = new GraphView () {
 				X = 1,
@@ -92,7 +92,7 @@ namespace UICatalog.Scenarios {
 				new StatusItem(Key.CtrlMask | Key.Q, "~^Q~ Quit", () => Quit()),
 				new StatusItem(Key.CtrlMask | Key.G, "~^G~ Next", ()=>graphs[currentGraph++%graphs.Length]()),
 			});
-			Top.Add (statusBar);
+			Application.Top.Add (statusBar);
 		}
 
 		private void MultiBarGraph ()

+ 2 - 2
UICatalog/Scenarios/HexEditor.cs

@@ -52,7 +52,7 @@ namespace UICatalog.Scenarios {
 					miAllowEdits = new MenuItem ("_AllowEdits", "", () => ToggleAllowEdits ()){Checked = _hexView.AllowEdits, CheckType = MenuItemCheckStyle.Checked}
 				})
 			});
-			Top.Add (menu);
+			Application.Top.Add (menu);
 
 			statusBar = new StatusBar (new StatusItem [] {
 				new StatusItem(Key.F2, "~F2~ Open", () => Open()),
@@ -61,7 +61,7 @@ namespace UICatalog.Scenarios {
 				siPositionChanged = new StatusItem(Key.Null,
 					$"Position: {_hexView.Position} Line: {_hexView.CursorPosition.Y} Col: {_hexView.CursorPosition.X} Line length: {_hexView.BytesPerLine}", () => {})
 			});
-			Top.Add (statusBar);
+			Application.Top.Add (statusBar);
 		}
 
 		private void _hexView_PositionChanged (HexView.HexViewEventArgs obj)

+ 3 - 3
UICatalog/Scenarios/InteractiveTree.cs

@@ -20,14 +20,14 @@ namespace UICatalog.Scenarios {
 			Win.Title = this.GetName ();
 			Win.Y = 1; // menu
 			Win.Height = Dim.Fill (1); // status bar
-			Top.LayoutSubviews ();
+			Application.Top.LayoutSubviews ();
 
 			var menu = new MenuBar (new MenuBarItem [] {
 				new MenuBarItem ("_File", new MenuItem [] {
 					new MenuItem ("_Quit", "", () => Quit()),
 				})
 				});
-			Top.Add (menu);
+			Application.Top.Add (menu);
 
 			treeView = new TreeView () {
 				X = 0,
@@ -45,7 +45,7 @@ namespace UICatalog.Scenarios {
 				new StatusItem(Key.CtrlMask | Key.T, "~^T~ Add Root", () => AddRootNode()),
 				new StatusItem(Key.CtrlMask | Key.R, "~^R~ Rename Node", () => RenameNode()),
 			});
-			Top.Add (statusBar);
+			Application.Top.Add (statusBar);
 
 		}
 

+ 6 - 7
UICatalog/Scenarios/Keys.cs

@@ -48,10 +48,9 @@ namespace UICatalog.Scenarios {
 			}
 		}
 
-		public override void Init (Toplevel top, ColorScheme colorScheme)
+		public override void Init (ColorScheme colorScheme)
 		{
 			Application.Init ();
-			Top = top != null ? top : Application.Top;
 			
 			Win = new TestWindow ($"CTRL-Q to Close - Scenario: {GetName ()}") {
 				X = 0,
@@ -60,7 +59,7 @@ namespace UICatalog.Scenarios {
 				Height = Dim.Fill (),
 				ColorScheme = colorScheme,
 			};
-			Top.Add (Win);
+			Application.Top.Add (Win);
 		}
 
 		public override void Setup ()
@@ -107,7 +106,7 @@ namespace UICatalog.Scenarios {
 				Shift = true
 			});
 			var maxLogEntry = $"Key{"",-5}: {fakeKeyPress}".Length;
-			var yOffset = (Top == Application.Top ? 1 : 6);
+			var yOffset = (Application.Top == Application.Top ? 1 : 6);
 			var keyStrokelist = new List<string> ();
 			var keyStrokeListView = new ListView (keyStrokelist) {
 				X = 0,
@@ -126,7 +125,7 @@ namespace UICatalog.Scenarios {
 			Win.Add (processKeyLogLabel);
 
 			maxLogEntry = $"{fakeKeyPress}".Length;
-			yOffset = (Top == Application.Top ? 1 : 6);
+			yOffset = (Application.Top == Application.Top ? 1 : 6);
 			var processKeyListView = new ListView (((TestWindow)Win)._processKeyList) {
 				X = Pos.Left (processKeyLogLabel),
 				Y = Pos.Top (processKeyLogLabel) + yOffset,
@@ -144,7 +143,7 @@ namespace UICatalog.Scenarios {
 			};
 			Win.Add (processHotKeyLogLabel);
 
-			yOffset = (Top == Application.Top ? 1 : 6);
+			yOffset = (Application.Top == Application.Top ? 1 : 6);
 			var processHotKeyListView = new ListView (((TestWindow)Win)._processHotKeyList) {
 				X = Pos.Left (processHotKeyLogLabel),
 				Y = Pos.Top (processHotKeyLogLabel) + yOffset,
@@ -162,7 +161,7 @@ namespace UICatalog.Scenarios {
 			};
 			Win.Add (processColdKeyLogLabel);
 
-			yOffset = (Top == Application.Top ? 1 : 6);
+			yOffset = (Application.Top == Application.Top ? 1 : 6);
 			var processColdKeyListView = new ListView (((TestWindow)Win)._processColdKeyList) {
 				X = Pos.Left (processColdKeyLogLabel),
 				Y = Pos.Top (processColdKeyLogLabel) + yOffset,

+ 3 - 3
UICatalog/Scenarios/LabelsAsButtons.cs

@@ -59,7 +59,7 @@ namespace UICatalog.Scenarios {
 			};
 			Win.Add (colorLabelsLabel);
 
-			//With this method there is no need to call Top.Ready += () => Top.Redraw (Top.Bounds);
+			//With this method there is no need to call Application.TopReady += () => Application.TopRedraw (Top.Bounds);
 			var x = Pos.Right (colorLabelsLabel) + 2;
 			foreach (var colorScheme in Colors.ColorSchemes) {
 				var colorLabel = new Label ($"{colorScheme.Key}") {
@@ -73,7 +73,7 @@ namespace UICatalog.Scenarios {
 				Win.Add (colorLabel);
 				x += colorLabel.Text.Length + 2;
 			}
-			Top.Ready += () => Top.Redraw (Top.Bounds);
+			Application.Top.Ready += () => Application.Top.Redraw (Application.Top.Bounds);
 
 			Label Label;
 			Win.Add (Label = new Label ("A super long _Label that will probably expose a bug in clipping or wrapping of text. Will it?") {
@@ -306,7 +306,7 @@ namespace UICatalog.Scenarios {
 				}
 			};
 
-			Top.Ready += () => radioGroup.Refresh ();
+			Application.Top.Ready += () => radioGroup.Refresh ();
 		}
 	}
 }

+ 3 - 3
UICatalog/Scenarios/LineViewExample.cs

@@ -17,14 +17,14 @@ namespace UICatalog.Scenarios {
 			Win.Title = this.GetName ();
 			Win.Y = 1; // menu
 			Win.Height = Dim.Fill (1); // status bar
-			Top.LayoutSubviews ();
+			Application.Top.LayoutSubviews ();
 
 			var menu = new MenuBar (new MenuBarItem [] {
 			new MenuBarItem ("_File", new MenuItem [] {
 				new MenuItem ("_Quit", "", () => Quit()),
 			})
 			});
-			Top.Add (menu);
+			Application.Top.Add (menu);
 
 
 			Win.Add (new Label ("Regular Line") { Y = 0 });
@@ -94,7 +94,7 @@ namespace UICatalog.Scenarios {
 			var statusBar = new StatusBar (new StatusItem [] {
 				new StatusItem(Key.CtrlMask | Key.Q, "~^Q~ Quit", () => Quit())
 			});
-			Top.Add (statusBar);
+			Application.Top.Add (statusBar);
 
 		}
 

+ 2 - 2
UICatalog/Scenarios/MessageBoxes.cs

@@ -156,9 +156,9 @@ namespace UICatalog.Scenarios {
 			{
 				frame.Height = Dim.Height (widthEdit) + Dim.Height (heightEdit) + Dim.Height (titleEdit) + Dim.Height (messageEdit)
 				+ Dim.Height (numButtonsEdit) + Dim.Height (defaultButtonEdit) + Dim.Height (styleRadioGroup) + 2 + Dim.Height (ckbEffect3D);
-				Top.Loaded -= Top_Loaded;
+				Application.Top.Loaded -= Top_Loaded;
 			}
-			Top.Loaded += Top_Loaded;
+			Application.Top.Loaded += Top_Loaded;
 
 			label = new Label ("Button Pressed:") {
 				X = Pos.Center (),

+ 3 - 3
UICatalog/Scenarios/MultiColouredTable.cs

@@ -16,7 +16,7 @@ namespace UICatalog.Scenarios {
 			Win.Title = this.GetName ();
 			Win.Y = 1; // menu
 			Win.Height = Dim.Fill (1); // status bar
-			Top.LayoutSubviews ();
+			Application.Top.LayoutSubviews ();
 
 			this.tableView = new TableViewColors () {
 				X = 0,
@@ -30,12 +30,12 @@ namespace UICatalog.Scenarios {
 					new MenuItem ("_Quit", "", () => Quit()),
 				}),
 			});
-			Top.Add (menu);
+			Application.Top.Add (menu);
 
 			var statusBar = new StatusBar (new StatusItem [] {
 				new StatusItem(Key.CtrlMask | Key.Q, "~^Q~ Quit", () => Quit()),
 			});
-			Top.Add (statusBar);
+			Application.Top.Add (statusBar);
 
 			Win.Add (tableView);
 

+ 5 - 6
UICatalog/Scenarios/Notepad.cs

@@ -11,11 +11,10 @@ namespace UICatalog.Scenarios {
 		private int numbeOfNewTabs = 1;
 
 		// Don't create a Window, just return the top-level view
-		public override void Init (Toplevel top, ColorScheme colorScheme)
+		public override void Init (ColorScheme colorScheme)
 		{
 			Application.Init ();
-			Top = top != null ? top : Application.Top;
-			Top.ColorScheme = Colors.Base;
+			Application.Top.ColorScheme = Colors.Base;
 		}
 
 		public override void Setup ()
@@ -30,7 +29,7 @@ namespace UICatalog.Scenarios {
 					new MenuItem ("_Quit", "", () => Quit()),
 				})
 				});
-			Top.Add (menu);
+			Application.Top.Add (menu);
 
 			tabView = new TabView () {
 				X = 0,
@@ -42,7 +41,7 @@ namespace UICatalog.Scenarios {
 			tabView.Style.ShowBorder = true;
 			tabView.ApplyStyleChanges ();
 
-			Top.Add (tabView);
+			Application.Top.Add (tabView);
 
 			var lenStatusItem = new StatusItem (Key.CharMask, "Len: ", null);
 			var statusBar = new StatusBar (new StatusItem [] {
@@ -59,7 +58,7 @@ namespace UICatalog.Scenarios {
 
 			tabView.SelectedTabChanged += (s, e) => lenStatusItem.Title = $"Len:{(e.NewTab?.View?.Text?.Length ?? 0)}";
 
-			Top.Add (statusBar);
+			Application.Top.Add (statusBar);
 
 			New ();
 		}

+ 2 - 2
UICatalog/Scenarios/ProgressBarStyles.cs

@@ -131,7 +131,7 @@ namespace UICatalog.Scenarios {
 				Application.MainLoop.Driver.Wakeup ();
 			}, null, 0, 300);
 
-			Top.Unloaded += Top_Unloaded;
+			Application.Top.Unloaded += Top_Unloaded;
 
 			void Top_Unloaded ()
 			{
@@ -143,7 +143,7 @@ namespace UICatalog.Scenarios {
 					_pulseTimer.Dispose ();
 					_pulseTimer = null;
 				}
-				Top.Unloaded -= Top_Unloaded;
+				Application.Top.Unloaded -= Top_Unloaded;
 			}
 		}
 	}

+ 1 - 1
UICatalog/Scenarios/RuneWidthGreaterThanOne.cs

@@ -16,7 +16,7 @@ namespace UICatalog.Scenarios {
 		private Window _win;
 		private string _lastRunesUsed;
 
-		public override void Init (Toplevel top, ColorScheme colorScheme)
+		public override void Init (ColorScheme colorScheme)
 		{
 			Application.Init ();
 

+ 4 - 4
UICatalog/Scenarios/Scrolling.cs

@@ -156,9 +156,9 @@ namespace UICatalog.Scenarios {
 				horizontalRuler.Text = rule.Repeat ((int)Math.Ceiling ((double)(horizontalRuler.Bounds.Width) / (double)rule.Length)) [0..(horizontalRuler.Bounds.Width)] +
 					"\n" + "|         ".Repeat ((int)Math.Ceiling ((double)(horizontalRuler.Bounds.Width) / (double)rule.Length)) [0..(horizontalRuler.Bounds.Width)];
 				verticalRuler.Text = vrule.Repeat ((int)Math.Ceiling ((double)(verticalRuler.Bounds.Height * 2) / (double)rule.Length)) [0..(verticalRuler.Bounds.Height * 2)];
-				Top.Loaded -= Top_Loaded;
+				Application.Top.Loaded -= Top_Loaded;
 			}
-			Top.Loaded += Top_Loaded;
+			Application.Top.Loaded += Top_Loaded;
 
 			var pressMeButton = new Button ("Press me!") {
 				X = 3,
@@ -313,9 +313,9 @@ namespace UICatalog.Scenarios {
 			void Top_Unloaded ()
 			{
 				pulsing = false;
-				Top.Unloaded -= Top_Unloaded;
+				Application.Top.Unloaded -= Top_Unloaded;
 			}
-			Top.Unloaded += Top_Unloaded;
+			Application.Top.Unloaded += Top_Unloaded;
 		}
 	}
 }

+ 2 - 2
UICatalog/Scenarios/SingleBackgroundWorker.cs

@@ -11,11 +11,11 @@ namespace UICatalog.Scenarios {
 	public class SingleBackgroundWorker : Scenario {
 		public override void Run ()
 		{
-			Top.Dispose ();
+			Application.Top.Dispose ();
 
 			Application.Run<MainApp> ();
 
-			Top.Dispose ();
+			Application.Top.Dispose ();
 		}
 
 		public class MainApp : Toplevel {

+ 3 - 3
UICatalog/Scenarios/SyntaxHighlighting.cs

@@ -21,7 +21,7 @@ namespace UICatalog.Scenarios {
 			Win.Title = this.GetName ();
 			Win.Y = 1; // menu
 			Win.Height = Dim.Fill (1); // status bar
-			Top.LayoutSubviews ();
+			Application.Top.LayoutSubviews ();
 
 			var menu = new MenuBar (new MenuBarItem [] {
 			new MenuBarItem ("_File", new MenuItem [] {
@@ -29,7 +29,7 @@ namespace UICatalog.Scenarios {
 				new MenuItem ("_Quit", "", () => Quit()),
 			})
 			});
-			Top.Add (menu);
+			Application.Top.Add (menu);
 
 			textView = new SqlTextView () {
 				X = 0,
@@ -49,7 +49,7 @@ namespace UICatalog.Scenarios {
 			});
 
 
-			Top.Add (statusBar);
+			Application.Top.Add (statusBar);
 		}
 
 		private void WordWrap ()

+ 3 - 7
UICatalog/Scenarios/TabViewExample.cs

@@ -24,7 +24,7 @@ namespace UICatalog.Scenarios {
 			Win.Title = this.GetName ();
 			Win.Y = 1; // menu
 			Win.Height = Dim.Fill (1); // status bar
-			Top.LayoutSubviews ();
+			Application.Top.LayoutSubviews ();
 
 			var menu = new MenuBar (new MenuBarItem [] {
 				new MenuBarItem ("_File", new MenuItem [] {
@@ -50,7 +50,7 @@ namespace UICatalog.Scenarios {
 
 					})
 				});
-			Top.Add (menu);
+			Application.Top.Add (menu);
 
 			tabView = new TabView () {
 				X = 0,
@@ -85,7 +85,6 @@ namespace UICatalog.Scenarios {
 				Height = Dim.Fill (),
 			};
 
-
 			frameRight.Add (new TextView () {
 				Text = "This demos the tabs control\nSwitch between tabs using cursor keys",
 				Width = Dim.Fill (),
@@ -94,8 +93,6 @@ namespace UICatalog.Scenarios {
 
 			Win.Add (frameRight);
 
-
-
 			var frameBelow = new FrameView ("Bottom Frame") {
 				X = 0,
 				Y = Pos.Bottom (tabView),
@@ -103,7 +100,6 @@ namespace UICatalog.Scenarios {
 				Height = Dim.Fill (),
 			};
 
-
 			frameBelow.Add (new TextView () {
 				Text = "This frame exists to check you can still tab here\nand that the tab control doesn't overspill it's bounds",
 				Width = Dim.Fill (),
@@ -115,7 +111,7 @@ namespace UICatalog.Scenarios {
 			var statusBar = new StatusBar (new StatusItem [] {
 				new StatusItem(Key.CtrlMask | Key.Q, "~^Q~ Quit", () => Quit()),
 			});
-			Top.Add (statusBar);
+			Application.Top.Add (statusBar);
 		}
 
 		private void AddBlankTab ()

+ 4 - 4
UICatalog/Scenarios/TableEditor.cs

@@ -38,7 +38,7 @@ namespace UICatalog.Scenarios {
 			Win.Title = this.GetName();
 			Win.Y = 1; // menu
 			Win.Height = Dim.Fill (1); // status bar
-			Top.LayoutSubviews ();
+			Application.Top.LayoutSubviews ();
 
 			this.tableView = new TableView () {
 				X = 0,
@@ -78,9 +78,9 @@ namespace UICatalog.Scenarios {
 					new MenuItem ("_Set All MinAcceptableWidth=1", "",SetMinAcceptableWidthToOne),
 				}),
 			});
-		
 
-		Top.Add (menu);
+
+			Application.Top.Add (menu);
 
 			var statusBar = new StatusBar (new StatusItem [] {
 				new StatusItem(Key.F2, "~F2~ OpenExample", () => OpenExample(true)),
@@ -88,7 +88,7 @@ namespace UICatalog.Scenarios {
 				new StatusItem(Key.F4, "~F4~ OpenSimple", () => OpenSimple(true)),
 				new StatusItem(Key.CtrlMask | Key.Q, "~^Q~ Quit", () => Quit()),
 			});
-			Top.Add (statusBar);
+			Application.Top.Add (statusBar);
 
 			Win.Add (tableView);
 

+ 1 - 1
UICatalog/Scenarios/TextFormatterDemo.cs

@@ -42,7 +42,7 @@ namespace UICatalog.Scenarios {
 			blockText.Text = ustring.Make (block.ToString ()); // .Replace(" ", "\u00A0"); // \u00A0 is 'non-breaking space
 			Win.Add (blockText);
 
-			var unicodeCheckBox = new CheckBox ("Unicode", Top.HotKeySpecifier == (Rune)' ') {
+			var unicodeCheckBox = new CheckBox ("Unicode", Application.Top.HotKeySpecifier == (Rune)' ') {
 				X = 0,
 				Y = Pos.Bottom (blockText) + 1,
 			};

+ 2 - 2
UICatalog/Scenarios/TextViewAutocompletePopup.cs

@@ -33,7 +33,7 @@ namespace UICatalog.Scenarios {
 					new MenuItem ("_Quit", "", () => Quit())
 				})
 			});
-			Top.Add (menu);
+			Application.Top.Add (menu);
 
 			textViewTopLeft = new TextView () {
 				Width = width,
@@ -89,7 +89,7 @@ namespace UICatalog.Scenarios {
 				siMultiline = new StatusItem(Key.Null, "", null),
 				siWrap = new StatusItem(Key.Null, "", null)
 			});
-			Top.Add (statusBar);
+			Application.Top.Add (statusBar);
 
 			Win.LayoutStarted += Win_LayoutStarted;
 		}

+ 2 - 2
UICatalog/Scenarios/Threading.cs

@@ -96,9 +96,9 @@ namespace UICatalog.Scenarios {
 			void Top_Loaded ()
 			{
 				_btnActionCancel.SetFocus ();
-				Top.Loaded -= Top_Loaded;
+				Application.Top.Loaded -= Top_Loaded;
 			}
-			Top.Loaded += Top_Loaded;
+			Application.Top.Loaded += Top_Loaded;
 		}
 
 		private async void LoadData ()

+ 3 - 3
UICatalog/Scenarios/TreeUseCases.cs

@@ -17,7 +17,7 @@ namespace UICatalog.Scenarios {
 			Win.Title = this.GetName ();
 			Win.Y = 1; // menu
 			Win.Height = Dim.Fill (1); // status bar
-			Top.LayoutSubviews ();
+			Application.Top.LayoutSubviews ();
 
 			var menu = new MenuBar (new MenuBarItem [] {
 				new MenuBarItem ("_File", new MenuItem [] {
@@ -31,13 +31,13 @@ namespace UICatalog.Scenarios {
 				}),
 			});
 
-			Top.Add (menu);
+			Application.Top.Add (menu);
 
 			var statusBar = new StatusBar (new StatusItem [] {
 				new StatusItem(Key.CtrlMask | Key.Q, "~^Q~ Quit", () => Quit()),
 			});
 
-			Top.Add (statusBar);
+			Application.Top.Add (statusBar);
 
 			// Start with the most basic use case
 			LoadSimpleNodes ();

+ 2 - 2
UICatalog/Scenarios/TreeViewFileSystem.cs

@@ -34,7 +34,7 @@ namespace UICatalog.Scenarios {
 			Win.Title = this.GetName ();
 			Win.Y = 1; // menu
 			Win.Height = Dim.Fill ();
-			Top.LayoutSubviews ();
+			Application.Top.LayoutSubviews ();
 
 			var menu = new MenuBar (new MenuBarItem [] {
 				new MenuBarItem ("_File", new MenuItem [] {
@@ -65,7 +65,7 @@ namespace UICatalog.Scenarios {
 					miCursor = new MenuItem ("Curs_or (MultiSelect only)", "", () => SetCursor()){Checked = false, CheckType = MenuItemCheckStyle.Checked},
 				}),
 			});
-			Top.Add (menu);
+			Application.Top.Add (menu);
 
 			treeViewFiles = new TreeView<FileSystemInfo> () {
 				X = 0,

+ 2 - 2
UICatalog/Scenarios/Unicode.cs

@@ -34,14 +34,14 @@ namespace UICatalog.Scenarios {
 					new MenuItem ("_Paste", "", null)
 				})
 			});
-			Top.Add (menu);
+			Application.Top.Add (menu);
 
 			var statusBar = new StatusBar (new StatusItem [] {
 				new StatusItem (Key.CtrlMask | Key.Q, "~^Q~ Выход", () => Application.RequestStop()),
 				new StatusItem (Key.Unknown, "~F2~ Создать", null),
 				new StatusItem(Key.Unknown, "~F3~ Со_хранить", null),
 			});
-			Top.Add (statusBar);
+			Application.Top.Add (statusBar);
 
 			var label = new Label ("Label:") { X = 0, Y = 1 };
 			Win.Add (label);

+ 3 - 11
UICatalog/Scenarios/WindowsAndFrameViews.cs

@@ -6,13 +6,6 @@ namespace UICatalog.Scenarios {
 	[ScenarioMetadata (Name: "Windows & FrameViews", Description: "Shows Windows, sub-Windows, and FrameViews.")]
 	[ScenarioCategory ("Layout")]
 	public class WindowsAndFrameViews : Scenario {
-		public override void Init (Toplevel top, ColorScheme colorScheme)
-		{
-			Application.Init ();
-
-			Top = top != null ? top : Application.Top;
-		}
-
 		public override void RequestStop ()
 		{
 			base.RequestStop ();
@@ -67,7 +60,7 @@ namespace UICatalog.Scenarios {
 				Y = Pos.AnchorEnd (1),
 				ColorScheme = Colors.Error
 			});
-			Top.Add (Win);
+			Application.Top.Add (Win);
 			listWin.Add (Win);
 
 			for (var i = 0; i < 3; i++) {
@@ -114,11 +107,10 @@ namespace UICatalog.Scenarios {
 				});
 				win.Add (frameView);
 
-				Top.Add (win);
+				Application.Top.Add (win);
 				listWin.Add (win);
 			}
 
-
 			FrameView frame = null;
 			frame = new FrameView ($"This is a FrameView") {
 				X = margin,
@@ -176,7 +168,7 @@ namespace UICatalog.Scenarios {
 
 			frame.Add (subFrameViewofFV);
 
-			Top.Add (frame);
+			Application.Top.Add (frame);
 			listWin.Add (frame);
 		}
 	}

+ 5 - 5
UICatalog/Scenarios/WizardAsView.cs

@@ -10,9 +10,9 @@ namespace UICatalog.Scenarios {
 	[ScenarioCategory ("Wizards")]
 	public class WizardAsView : Scenario {
 
-		public override void Init (Toplevel top, ColorScheme colorScheme)
+		public override void Init (ColorScheme colorScheme)
 		{
-			Top = Application.Top;
+			Application.Init ();
 
 			var menu = new MenuBar (new MenuBarItem [] {
 				new MenuBarItem ("_File", new MenuItem [] {
@@ -21,7 +21,7 @@ namespace UICatalog.Scenarios {
 					new MenuItem ("_Shutdown Server...", "", () => MessageBox.Query ("Wizaard", "Are you sure you want to cancel setup and shutdown?", "Ok", "Cancel")),
 				})
 			});
-			Top.Add (menu);
+			Application.Top.Add (menu);
 
 			// No need for a Title because the border is disabled
 			var wizard = new Wizard () {
@@ -93,8 +93,8 @@ namespace UICatalog.Scenarios {
 			wizard.AddStep (lastStep);
 			lastStep.HelpText = "The wizard is complete!\n\nPress the Finish button to continue.\n\nPressing Esc will cancel.";
 
-			Top.Add (wizard);
-			Application.Run (Top);
+			Application.Top.Add (wizard);
+			Application.Run (Application.Top);
 		}
 
 		public override void Run ()

+ 2 - 2
UICatalog/Scenarios/Wizards.cs

@@ -73,9 +73,9 @@ namespace UICatalog.Scenarios {
 			void Top_Loaded ()
 			{
 				frame.Height = Dim.Height (widthEdit) + Dim.Height (heightEdit) + Dim.Height (titleEdit) + 2;
-				Top.Loaded -= Top_Loaded;
+				Application.Top.Loaded -= Top_Loaded;
 			}
-			Top.Loaded += Top_Loaded;
+			Application.Top.Loaded += Top_Loaded;
 
 			label = new Label ("Action:") {
 				X = Pos.Center (),

+ 402 - 390
UICatalog/UICatalog.cs

@@ -44,35 +44,7 @@ namespace UICatalog {
 	/// <summary>
 	/// UI Catalog is a comprehensive sample app and scenario library for <see cref="Terminal.Gui"/>
 	/// </summary>
-	public class UICatalogApp {
-		private static int _nameColumnWidth;
-		private static FrameView _leftPane;
-		private static List<string> _categories;
-		private static ListView _categoryListView;
-		private static FrameView _rightPane;
-		private static List<Scenario> _scenarios;
-		private static ListView _scenarioListView;
-		private static StatusBar _statusBar;
-		private static StatusItem _capslock;
-		private static StatusItem _numlock;
-		private static StatusItem _scrolllock;
-
-		// If set, holds the scenario the user selected
-		private static Scenario _selectedScenario = null;
-		
-		private static bool _useSystemConsole = false;
-		private static ConsoleDriver.DiagnosticFlags _diagnosticFlags;
-		private static bool _heightAsBuffer = false;
-		private static bool _isFirstRunning = true;
-
-		// 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
-		private static int _cachedScenarioIndex = 0;
-		private static int _cachedCategoryIndex = 0;
-
-		private static StringBuilder _aboutMessage;
-
+	class UICatalogApp {
 		static void Main (string [] args)
 		{
 			Console.OutputEncoding = Encoding.Default;
@@ -82,17 +54,22 @@ namespace UICatalog {
 			}
 
 			_scenarios = Scenario.GetScenarios ();
+			_categories = Scenario.GetAllCategories ();
+			_nameColumnWidth = _scenarios.OrderByDescending (s => s.GetName ().Length).FirstOrDefault ().GetName ().Length;
 
 			if (args.Length > 0 && args.Contains ("-usc")) {
 				_useSystemConsole = true;
 				args = args.Where (val => val != "-usc").ToArray ();
 			}
+
+			// If a Scenario name has been provided on the commandline
+			// run it and exit when done.
 			if (args.Length > 0) {
 				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.Init (Application.Top, _colorScheme);
+				_selectedScenario.Init (_colorScheme);
 				_selectedScenario.Setup ();
 				_selectedScenario.Run ();
 				_selectedScenario = null;
@@ -113,43 +90,19 @@ namespace UICatalog {
 			_aboutMessage.AppendLine (@"https://github.com/gui-cs/Terminal.Gui");
 
 			Scenario scenario;
-			while ((scenario = SelectScenario ()) != null) {
-#if DEBUG_IDISPOSABLE
-				// Validate there are no outstanding Responder-based instances 
-				// after a scenario was selected to run. This proves the main UI Catalog
-				// 'app' closed cleanly.
-				foreach (var inst in Responder.Instances) {
-					Debug.Assert (inst.WasDisposed);
-				}
-				Responder.Instances.Clear ();
-#endif
-
-				scenario.Init (Application.Top, _colorScheme);
+			while ((scenario = RunUICatalogTopLevel ()) != null) {
+				VerifyObjectsWereDisposed ();
+				scenario.Init (_colorScheme);
 				scenario.Setup ();
 				scenario.Run ();
 
 				// This call to Application.Shutdown brackets the Application.Init call
-				// made by Scenario.Init()
+				// made by Scenario.Init() above
 				Application.Shutdown ();
 
-#if DEBUG_IDISPOSABLE
-				// After the scenario runs, validate all Responder-based instances
-				// were disposed. This proves the scenario 'app' closed cleanly.
-				foreach (var inst in Responder.Instances) {
-					Debug.Assert (inst.WasDisposed);
-				}
-				Responder.Instances.Clear ();
-#endif
-			}
-
-#if DEBUG_IDISPOSABLE
-			// This proves that when the user exited the UI Catalog app
-			// it cleaned up properly.
-			foreach (var inst in Responder.Instances) {
-				Debug.Assert (inst.WasDisposed);
+				VerifyObjectsWereDisposed ();
 			}
-			Responder.Instances.Clear ();
-#endif
+			VerifyObjectsWereDisposed ();
 		}
 
 		/// <summary>
@@ -158,389 +111,448 @@ namespace UICatalog {
 		/// When the Scenario exits, this function exits.
 		/// </summary>
 		/// <returns></returns>
-		private static Scenario SelectScenario ()
+		static Scenario RunUICatalogTopLevel ()
 		{
 			Application.UseSystemConsole = _useSystemConsole;
-			Application.Init ();
-			if (_colorScheme == null) {
-				// `Colors` is not initilized until the ConsoleDriver is loaded by 
-				// Application.Init. Set it only the first time though so it is
-				// preserved between running multiple Scenarios
-				_colorScheme = Colors.Base;
-			}
-			Application.HeightAsBuffer = _heightAsBuffer;
-
-			var menu = new MenuBar (new MenuBarItem [] {
-				new MenuBarItem ("_File", new MenuItem [] {
-					new MenuItem ("_Quit", "Quit UI Catalog", () => Application.RequestStop(), null, null, Key.Q | Key.CtrlMask)
-				}),
-				new MenuBarItem ("_Color Scheme", CreateColorSchemeMenuItems()),
-				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(), "_Ok"), null, null, Key.CtrlMask | Key.A),
-				}),
-			});
-
-			_leftPane = new FrameView ("Categories") {
-				X = 0,
-				Y = 1, // for menu
-				Width = 25,
-				Height = Dim.Fill (1),
-				CanFocus = true,
-				Shortcut = Key.CtrlMask | Key.C
-			};
-			_leftPane.Title = $"{_leftPane.Title} ({_leftPane.ShortcutTag})";
-			_leftPane.ShortcutAction = () => _leftPane.SetFocus ();
-
-			_categories = Scenario.GetAllCategories ();
-			_categoryListView = new ListView (_categories) {
-				X = 0,
-				Y = 0,
-				Width = Dim.Fill (0),
-				Height = Dim.Fill (0),
-				AllowsMarking = false,
-				CanFocus = true,
-			};
-			_categoryListView.OpenSelectedItem += (a) => {
-				_rightPane.SetFocus ();
-			};
-			_categoryListView.SelectedItemChanged += CategoryListView_SelectedChanged;
-			_leftPane.Add (_categoryListView);
-
-			_rightPane = new FrameView ("Scenarios") {
-				X = 25,
-				Y = 1, // for menu
-				Width = Dim.Fill (),
-				Height = Dim.Fill (1),
-				CanFocus = true,
-				Shortcut = Key.CtrlMask | Key.S
-			};
-			_rightPane.Title = $"{_rightPane.Title} ({_rightPane.ShortcutTag})";
-			_rightPane.ShortcutAction = () => _rightPane.SetFocus ();
-
-			_nameColumnWidth = _scenarios.OrderByDescending (s => s.GetName ().Length).FirstOrDefault ().GetName ().Length;
-
-			_scenarioListView = new ListView () {
-				X = 0,
-				Y = 0,
-				Width = Dim.Fill (0),
-				Height = Dim.Fill (0),
-				AllowsMarking = false,
-				CanFocus = true,
-			};
-
-			_scenarioListView.OpenSelectedItem += _scenarioListView_OpenSelectedItem;
-			_rightPane.Add (_scenarioListView);
-
-			_capslock = new StatusItem (Key.CharMask, "Caps", null);
-			_numlock = new StatusItem (Key.CharMask, "Num", null);
-			_scrolllock = new StatusItem (Key.CharMask, "Scroll", null);
-
-			_statusBar = new StatusBar () {
-				Visible = true,
-			};
-			_statusBar.Items = new StatusItem [] {
-				_capslock,
-				_numlock,
-				_scrolllock,
-				new StatusItem(Key.Q | Key.CtrlMask, "~CTRL-Q~ Quit", () => {
-					if (_selectedScenario is null){
-						// This causes GetScenarioToRun to return null
-						_selectedScenario = null;
-						Application.RequestStop();
-					} else {
-						_selectedScenario.RequestStop();
-					}
-				}),
-				new StatusItem(Key.F10, "~F10~ Hide/Show Status Bar", () => {
-					_statusBar.Visible = !_statusBar.Visible;
-					_leftPane.Height = Dim.Fill(_statusBar.Visible ? 1 : 0);
-					_rightPane.Height = Dim.Fill(_statusBar.Visible ? 1 : 0);
-					Application.Top.LayoutSubviews();
-					Application.Top.SetChildNeedsDisplay();
-				}),
-				new StatusItem (Key.CharMask, Application.Driver.GetType ().Name, null),
-			};
-
-			Application.Top.ColorScheme = _colorScheme;
-			Application.Top.KeyDown += KeyDownHandler;
-			Application.Top.Add (menu);
-			Application.Top.Add (_leftPane);
-			Application.Top.Add (_rightPane);
-			Application.Top.Add (_statusBar);
-
-			void TopHandler ()
-			{
-				if (_selectedScenario != null) {
-					_selectedScenario = null;
-					_isFirstRunning = false;
-				}
-				if (!_isFirstRunning) {
-					_rightPane.SetFocus ();
-				}
-				Application.Top.Loaded -= TopHandler;
-			}
-			Application.Top.Loaded += TopHandler;
-
-			// Restore previous selections
-			_categoryListView.SelectedItem = _cachedCategoryIndex;
-			_scenarioListView.SelectedItem = _cachedScenarioIndex;
 
 			// Run UI Catalog UI. When it exits, if _selectedScenario is != null then
 			// a Scenario was selected. Otherwise, the user wants to exit UI Catalog.
-			Application.Run (Application.Top);
+			Application.Init ();
+			Application.Run<UICatalogTopLevel> ();
 			Application.Shutdown ();
 
 			return _selectedScenario;
 		}
 
+		static List<Scenario> _scenarios;
+		static List<string> _categories;
+		static int _nameColumnWidth;
+		// 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 StringBuilder _aboutMessage;
+
+		// If set, holds the scenario the user selected
+		static Scenario _selectedScenario = null;
+
+		static bool _useSystemConsole = false;
+		static ConsoleDriver.DiagnosticFlags _diagnosticFlags;
+		static bool _heightAsBuffer = false;
+		static bool _isFirstRunning = true;
+		static ColorScheme _colorScheme;
 
 		/// <summary>
-		/// Launches the selected scenario, setting the global _selectedScenario
+		/// 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.
 		/// </summary>
-		/// <param name="e"></param>
-		private static void _scenarioListView_OpenSelectedItem (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)
-				_selectedScenario = (Scenario)Activator.CreateInstance (_scenarioListView.Source.ToList () [_scenarioListView.SelectedItem].GetType ());
-
-				// Tell the main app to stop
-				Application.RequestStop ();
-			}
-		}
+		class UICatalogTopLevel : Toplevel {
+			public MenuItem miIsMouseDisabled;
+			public MenuItem miHeightAsBuffer;
+	    
+			public FrameView LeftPane;
+			public ListView CategoryListView;
+			public FrameView RightPane;
+			public ListView ScenarioListView;
+	    
+			public StatusItem Capslock;
+			public StatusItem Numlock;
+			public StatusItem Scrolllock;
+			public StatusItem DriverName;
+
+			public UICatalogTopLevel ()
+			{
+				ColorScheme = _colorScheme;
+				MenuBar = new MenuBar (new MenuBarItem [] {
+					new MenuBarItem ("_File", new MenuItem [] {
+						new MenuItem ("_Quit", "Quit UI Catalog", () => RequestStop(), null, null, Key.Q | Key.CtrlMask)
+					}),
+					new MenuBarItem ("_Color Scheme", CreateColorSchemeMenuItems()),
+					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(), "_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);
+
+				StatusBar = new StatusBar () {
+					Visible = true,
+				};
+				StatusBar.Items = new StatusItem [] {
+					Capslock,
+					Numlock,
+					Scrolllock,
+					new StatusItem(Key.Q | Key.CtrlMask, "~CTRL-Q~ Quit", () => {
+						if (_selectedScenario is null){
+							// This causes GetScenarioToRun to return null
+							_selectedScenario = null;
+							RequestStop();
+						} else {
+							_selectedScenario.RequestStop();
+						}
+					}),
+					new StatusItem(Key.F10, "~F10~ Hide/Show Status Bar", () => {
+						StatusBar.Visible = !StatusBar.Visible;
+						LeftPane.Height = Dim.Fill(StatusBar.Visible ? 1 : 0);
+						RightPane.Height = Dim.Fill(StatusBar.Visible ? 1 : 0);
+						LayoutSubviews();
+						SetChildNeedsDisplay();
+					}),
+					DriverName,
+				};
 
-		static List<MenuItem []> CreateDiagnosticMenuItems ()
-		{
-			List<MenuItem []> menuItems = new List<MenuItem []> ();
-			menuItems.Add (CreateDiagnosticFlagsMenuItems ());
-			menuItems.Add (new MenuItem [] { null });
-			menuItems.Add (CreateSizeStyle ());
-			menuItems.Add (CreateDisabledEnabledMouse ());
-			menuItems.Add (CreateKeybindings ());
-			return menuItems;
-		}
+				LeftPane = new FrameView ("Categories") {
+					X = 0,
+					Y = 1, // for menu
+					Width = 25,
+					Height = Dim.Fill (1),
+					CanFocus = true,
+					Shortcut = Key.CtrlMask | Key.C
+				};
+				LeftPane.Title = $"{LeftPane.Title} ({LeftPane.ShortcutTag})";
+				LeftPane.ShortcutAction = () => LeftPane.SetFocus ();
+
+				CategoryListView = new ListView (_categories) {
+					X = 0,
+					Y = 0,
+					Width = Dim.Fill (0),
+					Height = Dim.Fill (0),
+					AllowsMarking = false,
+					CanFocus = true,
+				};
+				CategoryListView.OpenSelectedItem += (a) => {
+					RightPane.SetFocus ();
+				};
+				CategoryListView.SelectedItemChanged += CategoryListView_SelectedChanged;
+				LeftPane.Add (CategoryListView);
+
+				RightPane = new FrameView ("Scenarios") {
+					X = 25,
+					Y = 1, // for menu
+					Width = Dim.Fill (),
+					Height = Dim.Fill (1),
+					CanFocus = true,
+					Shortcut = Key.CtrlMask | Key.S
+				};
+				RightPane.Title = $"{RightPane.Title} ({RightPane.ShortcutTag})";
+				RightPane.ShortcutAction = () => RightPane.SetFocus ();
+
+				ScenarioListView = new ListView () {
+					X = 0,
+					Y = 0,
+					Width = Dim.Fill (0),
+					Height = Dim.Fill (0),
+					AllowsMarking = false,
+					CanFocus = true,
+				};
 
-		private static MenuItem [] CreateDisabledEnabledMouse ()
-		{
-			List<MenuItem> menuItems = new List<MenuItem> ();
-			var item = new MenuItem ();
-			item.Title = "_Disable Mouse";
-			item.Shortcut = Key.CtrlMask | Key.AltMask | (Key)item.Title.ToString ().Substring (1, 1) [0];
-			item.CheckType |= MenuItemCheckStyle.Checked;
-			item.Checked = Application.IsMouseDisabled;
-			item.Action += () => {
-				item.Checked = Application.IsMouseDisabled = !item.Checked;
-			};
-			menuItems.Add (item);
-
-			return menuItems.ToArray ();
-		}
-		private static MenuItem [] CreateKeybindings ()
-		{
+				ScenarioListView.OpenSelectedItem += ScenarioListView_OpenSelectedItem;
+				RightPane.Add (ScenarioListView);
 
-			List<MenuItem> menuItems = new List<MenuItem> ();
-			var item = new MenuItem ();
-			item.Title = "_Key Bindings";
-			item.Help = "Change which keys do what";
-			item.Action += () => {
-				var dlg = new KeyBindingsDialog ();
-				Application.Run (dlg);
-			};
+				KeyDown += KeyDownHandler;
+				Add (MenuBar);
+				Add (LeftPane);
+				Add (RightPane);
+				Add (StatusBar);
 
-			menuItems.Add (null);
-			menuItems.Add (item);
+				Loaded += LoadedHandler;
 
-			return menuItems.ToArray ();
-		}
+				// Restore previous selections
+				CategoryListView.SelectedItem = _cachedCategoryIndex;
+				ScenarioListView.SelectedItem = _cachedScenarioIndex;
+			}
 
-		static MenuItem [] CreateSizeStyle ()
-		{
-			List<MenuItem> menuItems = new List<MenuItem> ();
-			var item = new MenuItem ();
-			item.Title = "_Height As Buffer";
-			item.Shortcut = Key.CtrlMask | Key.AltMask | (Key)item.Title.ToString ().Substring (1, 1) [0];
-			item.CheckType |= MenuItemCheckStyle.Checked;
-			item.Checked = Application.HeightAsBuffer;
-			item.Action += () => {
-				item.Checked = !item.Checked;
-				_heightAsBuffer = item.Checked;
+			void LoadedHandler ()
+			{
 				Application.HeightAsBuffer = _heightAsBuffer;
-			};
-			menuItems.Add (item);
 
-			return menuItems.ToArray ();
-		}
+				if (_colorScheme == null) {
+					ColorScheme = _colorScheme = Colors.Base;
+				}
 
-		static MenuItem [] CreateDiagnosticFlagsMenuItems ()
-		{
-			const string OFF = "Diagnostics: _Off";
-			const string FRAME_RULER = "Diagnostics: Frame _Ruler";
-			const string FRAME_PADDING = "Diagnostics: _Frame Padding";
-			var index = 0;
+				miIsMouseDisabled.Checked = Application.IsMouseDisabled;
+				miHeightAsBuffer.Checked = Application.HeightAsBuffer;
+				DriverName.Title = $"Driver: {Driver.GetType ().Name}";
 
-			List<MenuItem> menuItems = new List<MenuItem> ();
-			foreach (Enum diag in Enum.GetValues (_diagnosticFlags.GetType ())) {
-				var item = new MenuItem ();
-				item.Title = GetDiagnosticsTitle (diag);
-				item.Shortcut = Key.AltMask + index.ToString () [0];
-				index++;
-				item.CheckType |= MenuItemCheckStyle.Checked;
-				if (GetDiagnosticsTitle (ConsoleDriver.DiagnosticFlags.Off) == item.Title) {
-					item.Checked = (_diagnosticFlags & (ConsoleDriver.DiagnosticFlags.FramePadding
-					| ConsoleDriver.DiagnosticFlags.FrameRuler)) == 0;
-				} else {
-					item.Checked = _diagnosticFlags.HasFlag (diag);
+				if (_selectedScenario != null) {
+					_selectedScenario = null;
+					_isFirstRunning = false;
 				}
-				item.Action += () => {
-					var t = GetDiagnosticsTitle (ConsoleDriver.DiagnosticFlags.Off);
-					if (item.Title == t && !item.Checked) {
-						_diagnosticFlags &= ~(ConsoleDriver.DiagnosticFlags.FramePadding | ConsoleDriver.DiagnosticFlags.FrameRuler);
-						item.Checked = true;
-					} else if (item.Title == t && item.Checked) {
-						_diagnosticFlags |= (ConsoleDriver.DiagnosticFlags.FramePadding | ConsoleDriver.DiagnosticFlags.FrameRuler);
-						item.Checked = false;
-					} else {
-						var f = GetDiagnosticsEnumValue (item.Title);
-						if (_diagnosticFlags.HasFlag (f)) {
-							SetDiagnosticsFlag (f, false);
-						} else {
-							SetDiagnosticsFlag (f, true);
-						}
-					}
-					foreach (var menuItem in menuItems) {
-						if (menuItem.Title == t) {
-							menuItem.Checked = !_diagnosticFlags.HasFlag (ConsoleDriver.DiagnosticFlags.FrameRuler)
-								&& !_diagnosticFlags.HasFlag (ConsoleDriver.DiagnosticFlags.FramePadding);
-						} else if (menuItem.Title != t) {
-							menuItem.Checked = _diagnosticFlags.HasFlag (GetDiagnosticsEnumValue (menuItem.Title));
-						}
-					}
-					ConsoleDriver.Diagnostics = _diagnosticFlags;
-					Application.Top.SetNeedsDisplay ();
-				};
-				menuItems.Add (item);
+				if (!_isFirstRunning) {
+					RightPane.SetFocus ();
+				}
+				Loaded -= LoadedHandler;
 			}
-			return menuItems.ToArray ();
 
-			string GetDiagnosticsTitle (Enum diag)
+			/// <summary>
+			/// Launches the selected scenario, setting the global _selectedScenario
+			/// </summary>
+			/// <param name="e"></param>
+			void ScenarioListView_OpenSelectedItem (EventArgs e)
 			{
-				switch (Enum.GetName (_diagnosticFlags.GetType (), diag)) {
-				case "Off":
-					return OFF;
-				case "FrameRuler":
-					return FRAME_RULER;
-				case "FramePadding":
-					return FRAME_PADDING;
+				if (_selectedScenario is null) {
+					// Save selected item state
+					_cachedCategoryIndex = CategoryListView.SelectedItem;
+					_cachedScenarioIndex = ScenarioListView.SelectedItem;
+					// Create new instance of scenario (even though Scenarios contains instances)
+					_selectedScenario = (Scenario)Activator.CreateInstance (ScenarioListView.Source.ToList () [ScenarioListView.SelectedItem].GetType ());
+
+					// Tell the main app to stop
+					Application.RequestStop ();
 				}
-				return "";
 			}
 
-			Enum GetDiagnosticsEnumValue (ustring title)
+			List<MenuItem []> CreateDiagnosticMenuItems ()
 			{
-				switch (title.ToString ()) {
-				case FRAME_RULER:
-					return ConsoleDriver.DiagnosticFlags.FrameRuler;
-				case FRAME_PADDING:
-					return ConsoleDriver.DiagnosticFlags.FramePadding;
-				}
-				return null;
+				List<MenuItem []> menuItems = new List<MenuItem []> ();
+				menuItems.Add (CreateDiagnosticFlagsMenuItems ());
+				menuItems.Add (new MenuItem [] { null });
+				menuItems.Add (CreateHeightAsBufferMenuItems ());
+				menuItems.Add (CreateDisabledEnabledMouseItems ());
+				menuItems.Add (CreateKeybindingsMenuItems ());
+				return menuItems;
 			}
 
-			void SetDiagnosticsFlag (Enum diag, bool add)
+			MenuItem [] CreateDisabledEnabledMouseItems ()
 			{
-				switch (diag) {
-				case ConsoleDriver.DiagnosticFlags.FrameRuler:
-					if (add) {
-						_diagnosticFlags |= ConsoleDriver.DiagnosticFlags.FrameRuler;
-					} else {
-						_diagnosticFlags &= ~ConsoleDriver.DiagnosticFlags.FrameRuler;
-					}
-					break;
-				case ConsoleDriver.DiagnosticFlags.FramePadding:
-					if (add) {
-						_diagnosticFlags |= ConsoleDriver.DiagnosticFlags.FramePadding;
-					} else {
-						_diagnosticFlags &= ~ConsoleDriver.DiagnosticFlags.FramePadding;
-					}
-					break;
-				default:
-					_diagnosticFlags = default;
-					break;
-				}
+				List<MenuItem> menuItems = new List<MenuItem> ();
+				miIsMouseDisabled = new MenuItem ();
+				miIsMouseDisabled.Title = "_Disable Mouse";
+				miIsMouseDisabled.Shortcut = Key.CtrlMask | Key.AltMask | (Key)miIsMouseDisabled.Title.ToString ().Substring (1, 1) [0];
+				miIsMouseDisabled.CheckType |= MenuItemCheckStyle.Checked;
+				miIsMouseDisabled.Action += () => {
+					miIsMouseDisabled.Checked = Application.IsMouseDisabled = !miIsMouseDisabled.Checked;
+				};
+				menuItems.Add (miIsMouseDisabled);
+
+				return menuItems.ToArray ();
 			}
-		}
 
-		static ColorScheme _colorScheme;
-		static MenuItem [] CreateColorSchemeMenuItems ()
-		{
-			List<MenuItem> menuItems = new List<MenuItem> ();
-			foreach (var sc in Colors.ColorSchemes) {
+			MenuItem [] CreateKeybindingsMenuItems ()
+			{
+				List<MenuItem> menuItems = new List<MenuItem> ();
 				var item = new MenuItem ();
-				item.Title = $"_{sc.Key}";
-				item.Shortcut = Key.AltMask | (Key)sc.Key.Substring (0, 1) [0];
-				item.CheckType |= MenuItemCheckStyle.Radio;
-				item.Checked = sc.Value == _colorScheme;
+				item.Title = "_Key Bindings";
+				item.Help = "Change which keys do what";
 				item.Action += () => {
-					Application.Top.ColorScheme = _colorScheme = sc.Value;
-					Application.Top?.SetNeedsDisplay ();
-					foreach (var menuItem in menuItems) {
-						menuItem.Checked = menuItem.Title.Equals ($"_{sc.Key}") && sc.Value == _colorScheme;
-					}
+					var dlg = new KeyBindingsDialog ();
+					Application.Run (dlg);
 				};
+
+				menuItems.Add (null);
 				menuItems.Add (item);
+
+				return menuItems.ToArray ();
 			}
-			return menuItems.ToArray ();
-		}
 
-		private static void KeyDownHandler (View.KeyEventEventArgs a)
-		{
-			if (a.KeyEvent.IsCapslock) {
-				_capslock.Title = "Caps: On";
-				_statusBar.SetNeedsDisplay ();
-			} else {
-				_capslock.Title = "Caps: Off";
-				_statusBar.SetNeedsDisplay ();
+			MenuItem [] CreateHeightAsBufferMenuItems ()
+			{
+				List<MenuItem> menuItems = new List<MenuItem> ();
+				miHeightAsBuffer = new MenuItem ();
+				miHeightAsBuffer.Title = "_Height As Buffer";
+				miHeightAsBuffer.Shortcut = Key.CtrlMask | Key.AltMask | (Key)miHeightAsBuffer.Title.ToString ().Substring (1, 1) [0];
+				miHeightAsBuffer.CheckType |= MenuItemCheckStyle.Checked;
+				miHeightAsBuffer.Action += () => {
+					miHeightAsBuffer.Checked = !miHeightAsBuffer.Checked;
+					Application.HeightAsBuffer = miHeightAsBuffer.Checked;
+				};
+				menuItems.Add (miHeightAsBuffer);
+
+				return menuItems.ToArray ();
 			}
 
-			if (a.KeyEvent.IsNumlock) {
-				_numlock.Title = "Num: On";
-				_statusBar.SetNeedsDisplay ();
-			} else {
-				_numlock.Title = "Num: Off";
-				_statusBar.SetNeedsDisplay ();
+			MenuItem [] CreateDiagnosticFlagsMenuItems ()
+			{
+				const string OFF = "Diagnostics: _Off";
+				const string FRAME_RULER = "Diagnostics: Frame _Ruler";
+				const string FRAME_PADDING = "Diagnostics: _Frame Padding";
+				var index = 0;
+
+				List<MenuItem> menuItems = new List<MenuItem> ();
+				foreach (Enum diag in Enum.GetValues (_diagnosticFlags.GetType ())) {
+					var item = new MenuItem ();
+					item.Title = GetDiagnosticsTitle (diag);
+					item.Shortcut = Key.AltMask + index.ToString () [0];
+					index++;
+					item.CheckType |= MenuItemCheckStyle.Checked;
+					if (GetDiagnosticsTitle (ConsoleDriver.DiagnosticFlags.Off) == item.Title) {
+						item.Checked = (_diagnosticFlags & (ConsoleDriver.DiagnosticFlags.FramePadding
+						| ConsoleDriver.DiagnosticFlags.FrameRuler)) == 0;
+					} else {
+						item.Checked = _diagnosticFlags.HasFlag (diag);
+					}
+					item.Action += () => {
+						var t = GetDiagnosticsTitle (ConsoleDriver.DiagnosticFlags.Off);
+						if (item.Title == t && !item.Checked) {
+							_diagnosticFlags &= ~(ConsoleDriver.DiagnosticFlags.FramePadding | ConsoleDriver.DiagnosticFlags.FrameRuler);
+							item.Checked = true;
+						} else if (item.Title == t && item.Checked) {
+							_diagnosticFlags |= (ConsoleDriver.DiagnosticFlags.FramePadding | ConsoleDriver.DiagnosticFlags.FrameRuler);
+							item.Checked = false;
+						} else {
+							var f = GetDiagnosticsEnumValue (item.Title);
+							if (_diagnosticFlags.HasFlag (f)) {
+								SetDiagnosticsFlag (f, false);
+							} else {
+								SetDiagnosticsFlag (f, true);
+							}
+						}
+						foreach (var menuItem in menuItems) {
+							if (menuItem.Title == t) {
+								menuItem.Checked = !_diagnosticFlags.HasFlag (ConsoleDriver.DiagnosticFlags.FrameRuler)
+									&& !_diagnosticFlags.HasFlag (ConsoleDriver.DiagnosticFlags.FramePadding);
+							} else if (menuItem.Title != t) {
+								menuItem.Checked = _diagnosticFlags.HasFlag (GetDiagnosticsEnumValue (menuItem.Title));
+							}
+						}
+						ConsoleDriver.Diagnostics = _diagnosticFlags;
+						Application.Top.SetNeedsDisplay ();
+					};
+					menuItems.Add (item);
+				}
+				return menuItems.ToArray ();
+
+				string GetDiagnosticsTitle (Enum diag)
+				{
+					switch (Enum.GetName (_diagnosticFlags.GetType (), diag)) {
+					case "Off":
+						return OFF;
+					case "FrameRuler":
+						return FRAME_RULER;
+					case "FramePadding":
+						return FRAME_PADDING;
+					}
+					return "";
+				}
+
+				Enum GetDiagnosticsEnumValue (ustring title)
+				{
+					switch (title.ToString ()) {
+					case FRAME_RULER:
+						return ConsoleDriver.DiagnosticFlags.FrameRuler;
+					case FRAME_PADDING:
+						return ConsoleDriver.DiagnosticFlags.FramePadding;
+					}
+					return null;
+				}
+
+				void SetDiagnosticsFlag (Enum diag, bool add)
+				{
+					switch (diag) {
+					case ConsoleDriver.DiagnosticFlags.FrameRuler:
+						if (add) {
+							_diagnosticFlags |= ConsoleDriver.DiagnosticFlags.FrameRuler;
+						} else {
+							_diagnosticFlags &= ~ConsoleDriver.DiagnosticFlags.FrameRuler;
+						}
+						break;
+					case ConsoleDriver.DiagnosticFlags.FramePadding:
+						if (add) {
+							_diagnosticFlags |= ConsoleDriver.DiagnosticFlags.FramePadding;
+						} else {
+							_diagnosticFlags &= ~ConsoleDriver.DiagnosticFlags.FramePadding;
+						}
+						break;
+					default:
+						_diagnosticFlags = default;
+						break;
+					}
+				}
 			}
 
-			if (a.KeyEvent.IsScrolllock) {
-				_scrolllock.Title = "Scroll: On";
-				_statusBar.SetNeedsDisplay ();
-			} else {
-				_scrolllock.Title = "Scroll: Off";
-				_statusBar.SetNeedsDisplay ();
+			MenuItem [] CreateColorSchemeMenuItems ()
+			{
+				List<MenuItem> menuItems = new List<MenuItem> ();
+				foreach (var sc in Colors.ColorSchemes) {
+					var item = new MenuItem ();
+					item.Title = $"_{sc.Key}";
+					item.Shortcut = Key.AltMask | (Key)sc.Key.Substring (0, 1) [0];
+					item.CheckType |= MenuItemCheckStyle.Radio;
+					item.Checked = sc.Value == _colorScheme;
+					item.Action += () => {
+						ColorScheme = _colorScheme = sc.Value;
+						SetNeedsDisplay ();
+						foreach (var menuItem in menuItems) {
+							menuItem.Checked = menuItem.Title.Equals ($"_{sc.Key}") && sc.Value == _colorScheme;
+						}
+					};
+					menuItems.Add (item);
+				}
+				return menuItems.ToArray ();
+			}
+
+			void KeyDownHandler (View.KeyEventEventArgs a)
+			{
+				if (a.KeyEvent.IsCapslock) {
+					Capslock.Title = "Caps: On";
+					StatusBar.SetNeedsDisplay ();
+				} else {
+					Capslock.Title = "Caps: Off";
+					StatusBar.SetNeedsDisplay ();
+				}
+
+				if (a.KeyEvent.IsNumlock) {
+					Numlock.Title = "Num: On";
+					StatusBar.SetNeedsDisplay ();
+				} else {
+					Numlock.Title = "Num: Off";
+					StatusBar.SetNeedsDisplay ();
+				}
+
+				if (a.KeyEvent.IsScrolllock) {
+					Scrolllock.Title = "Scroll: On";
+					StatusBar.SetNeedsDisplay ();
+				} else {
+					Scrolllock.Title = "Scroll: Off";
+					StatusBar.SetNeedsDisplay ();
+				}
+			}
+
+			void CategoryListView_SelectedChanged (ListViewItemEventArgs e)
+			{
+				var item = _categories [e.Item];
+				List<Scenario> newlist;
+				if (e.Item == 0) {
+					// First category is "All"
+					newlist = _scenarios;
+
+				} else {
+					newlist = _scenarios.Where (s => s.GetCategories ().Contains (item)).ToList ();
+				}
+				ScenarioListView.SetSource (newlist.ToList ());
 			}
 		}
 
-		private static void CategoryListView_SelectedChanged (ListViewItemEventArgs e)
+		static void VerifyObjectsWereDisposed ()
 		{
-			var item = _categories [e.Item];
-			List<Scenario> newlist;
-			if (e.Item == 0) {
-				// First category is "All"
-				newlist = _scenarios;
-
-			} else {
-				newlist = _scenarios.Where (s => s.GetCategories ().Contains (item)).ToList ();
+#if DEBUG_IDISPOSABLE
+			// Validate there are no outstanding Responder-based instances 
+			// after a scenario was selected to run. This proves the main UI Catalog
+			// 'app' closed cleanly.
+			foreach (var inst in Responder.Instances) {
+				Debug.Assert (inst.WasDisposed);
 			}
-			_scenarioListView.SetSource (newlist.ToList ());
+			Responder.Instances.Clear ();
+
+			// Validate there are no outstanding Application.RunState-based instances 
+			// after a scenario was selected to run. This proves the main UI Catalog
+			// 'app' closed cleanly.
+			foreach (var inst in Application.RunState.Instances) {
+				Debug.Assert (inst.WasDisposed);
+			}
+			Application.RunState.Instances.Clear ();
+#endif
 		}
 
-		private static void OpenUrl (string url)
+		static void OpenUrl (string url)
 		{
 			try {
 				if (RuntimeInformation.IsOSPlatform (OSPlatform.Windows)) {

Tiedoston diff-näkymää rajattu, sillä se on liian suuri
+ 256 - 856
UnitTests/ApplicationTests.cs


+ 174 - 3
UnitTests/MainLoopTests.cs

@@ -578,9 +578,9 @@ namespace Terminal.Gui.Core {
 			TextField tf = new ();
 			Application.Top.Add (tf);
 
-			const int numPasses = 10;
-			const int numIncrements = 10000;
-			const int pollMs = 20000;
+			const int numPasses = 5;
+			const int numIncrements = 5000;
+			const int pollMs = 10000;
 
 			var task = Task.Run (() => RunTest (r, tf, numPasses, numIncrements, pollMs));
 
@@ -591,5 +591,176 @@ namespace Terminal.Gui.Core {
 
 			Assert.Equal ((numIncrements * numPasses), tbCounter);
 		}
+
+		private static int total;
+		private static Button btn;
+		private static string clickMe;
+		private static string cancel;
+		private static string pewPew;
+		private static int zero;
+		private static int one;
+		private static int two;
+		private static int three;
+		private static int four;
+		private static bool taskCompleted;
+
+		[Theory, AutoInitShutdown]
+		[MemberData (nameof (TestAddIdle))]
+		public void Mainloop_Invoke_Or_AddIdle_Can_Be_Used_For_Events_Or_Actions (Action action, string pclickMe, string pcancel, string ppewPew, int pzero, int pone, int ptwo, int pthree, int pfour)
+		{
+			total = 0;
+			btn = null;
+			clickMe = pclickMe;
+			cancel = pcancel;
+			pewPew = ppewPew;
+			zero = pzero;
+			one = pone;
+			two = ptwo;
+			three = pthree;
+			four = pfour;
+			taskCompleted = false;
+
+			var btnLaunch = new Button ("Open Window");
+
+			btnLaunch.Clicked += () => action ();
+
+			Application.Top.Add (btnLaunch);
+
+			var iterations = -1;
+
+			Application.Iteration += () => {
+				iterations++;
+				if (iterations == 0) {
+					Assert.Null (btn);
+					Assert.Equal (zero, total);
+					Assert.True (btnLaunch.ProcessKey (new KeyEvent (Key.Enter, null)));
+					if (btn == null) {
+						Assert.Null (btn);
+						Assert.Equal (zero, total);
+					} else {
+						Assert.Equal (clickMe, btn.Text);
+						Assert.Equal (four, total);
+					}
+				} else if (iterations == 1) {
+					Assert.Equal (clickMe, btn.Text);
+					Assert.Equal (zero, total);
+					Assert.True (btn.ProcessKey (new KeyEvent (Key.Enter, null)));
+					Assert.Equal (cancel, btn.Text);
+					Assert.Equal (one, total);
+				} else if (taskCompleted) {
+					Application.RequestStop ();
+				}
+			};
+
+			Application.Run ();
+
+			Assert.True (taskCompleted);
+			Assert.Equal (clickMe, btn.Text);
+			Assert.Equal (four, total);
+		}
+
+		public static IEnumerable<object []> TestAddIdle {
+			get {
+				// Goes fine
+				Action a1 = StartWindow;
+				yield return new object [] { a1, "Click Me", "Cancel", "Pew Pew", 0, 1, 2, 3, 4 };
+
+				// Also goes fine
+				Action a2 = () => Application.MainLoop.Invoke (StartWindow);
+				yield return new object [] { a2, "Click Me", "Cancel", "Pew Pew", 0, 1, 2, 3, 4 };
+			}
+		}
+
+		private static void StartWindow ()
+		{
+			var startWindow = new Window {
+				Modal = true
+			};
+
+			btn = new Button {
+				Text = "Click Me"
+			};
+
+			btn.Clicked += RunAsyncTest;
+
+			var totalbtn = new Button () {
+				X = Pos.Right (btn),
+				Text = "total"
+			};
+
+			totalbtn.Clicked += () => {
+				MessageBox.Query ("Count", $"Count is {total}", "Ok");
+			};
+
+			startWindow.Add (btn);
+			startWindow.Add (totalbtn);
+
+			Application.Run (startWindow);
+
+			Assert.Equal (clickMe, btn.Text);
+			Assert.Equal (four, total);
+
+			Application.RequestStop ();
+		}
+
+		private static async void RunAsyncTest ()
+		{
+			Assert.Equal (clickMe, btn.Text);
+			Assert.Equal (zero, total);
+
+			btn.Text = "Cancel";
+			Interlocked.Increment (ref total);
+			btn.SetNeedsDisplay ();
+
+			await Task.Run (() => {
+				try {
+					Assert.Equal (cancel, btn.Text);
+					Assert.Equal (one, total);
+
+					RunSql ();
+				} finally {
+					SetReadyToRun ();
+				}
+			}).ContinueWith (async (s, e) => {
+
+				await Task.Delay (1000);
+				Assert.Equal (clickMe, btn.Text);
+				Assert.Equal (three, total);
+
+				Interlocked.Increment (ref total);
+
+				Assert.Equal (clickMe, btn.Text);
+				Assert.Equal (four, total);
+
+				taskCompleted = true;
+
+			}, TaskScheduler.FromCurrentSynchronizationContext ());
+		}
+
+		private static void RunSql ()
+		{
+			Thread.Sleep (100);
+			Assert.Equal (cancel, btn.Text);
+			Assert.Equal (one, total);
+
+			Application.MainLoop.Invoke (() => {
+				btn.Text = "Pew Pew";
+				Interlocked.Increment (ref total);
+				btn.SetNeedsDisplay ();
+			});
+		}
+
+		private static void SetReadyToRun ()
+		{
+			Thread.Sleep (100);
+			Assert.Equal (pewPew, btn.Text);
+			Assert.Equal (two, total);
+
+			Application.MainLoop.Invoke (() => {
+				btn.Text = "Click Me";
+				Interlocked.Increment (ref total);
+				btn.SetNeedsDisplay ();
+			});
+		}
 	}
 }

+ 695 - 0
UnitTests/MdiTests.cs

@@ -0,0 +1,695 @@
+using System;
+using System.Diagnostics;
+using System.Linq;
+using System.Threading;
+using System.Threading.Tasks;
+using Xunit;
+
+// Alias Console to MockConsole so we don't accidentally use Console
+using Console = Terminal.Gui.FakeConsole;
+
+namespace Terminal.Gui.Core {
+	public class MdiTests {
+		public MdiTests ()
+		{
+#if DEBUG_IDISPOSABLE
+			Responder.Instances.Clear ();
+			Application.RunState.Instances.Clear ();
+#endif
+		}
+
+
+		[Fact]
+		public void Dispose_Toplevel_IsMdiContainer_False_With_Begin_End ()
+		{
+			Application.Init (new FakeDriver (), new FakeMainLoop (() => FakeConsole.ReadKey (true)));
+
+			var top = new Toplevel ();
+			var rs = Application.Begin (top);
+			Application.End (rs);
+
+			Application.Shutdown ();
+
+			Assert.Equal (2, Responder.Instances.Count);
+			Assert.True (Responder.Instances [0].WasDisposed);
+			Assert.True (Responder.Instances [1].WasDisposed);
+		}
+
+		[Fact]
+		public void Dispose_Toplevel_IsMdiContainer_True_With_Begin ()
+		{
+			Application.Init (new FakeDriver (), new FakeMainLoop (() => FakeConsole.ReadKey (true)));
+
+			var mdi = new Toplevel { IsMdiContainer = true };
+			var rs = Application.Begin (mdi);
+			Application.End (rs);
+
+			Application.Shutdown ();
+
+			Assert.Equal (2, Responder.Instances.Count);
+			Assert.True (Responder.Instances [0].WasDisposed);
+			Assert.True (Responder.Instances [1].WasDisposed);
+		}
+
+		[Fact, AutoInitShutdown]
+		public void Application_RequestStop_With_Params_On_A_Not_MdiContainer_Always_Use_Application_Current ()
+		{
+			var top1 = new Toplevel ();
+			var top2 = new Toplevel ();
+			var top3 = new Window ();
+			var top4 = new Window ();
+			var d = new Dialog ();
+
+			// top1, top2, top3, d1 = 4
+			var iterations = 4;
+
+			top1.Ready += () => {
+				Assert.Null (Application.MdiChildes);
+				Application.Run (top2);
+			};
+			top2.Ready += () => {
+				Assert.Null (Application.MdiChildes);
+				Application.Run (top3);
+			};
+			top3.Ready += () => {
+				Assert.Null (Application.MdiChildes);
+				Application.Run (top4);
+			};
+			top4.Ready += () => {
+				Assert.Null (Application.MdiChildes);
+				Application.Run (d);
+			};
+
+			d.Ready += () => {
+				Assert.Null (Application.MdiChildes);
+				// This will close the d because on a not MdiContainer the Application.Current it always used.
+				Application.RequestStop (top1);
+				Assert.True (Application.Current == d);
+			};
+
+			d.Closed += (e) => Application.RequestStop (top1);
+
+			Application.Iteration += () => {
+				Assert.Null (Application.MdiChildes);
+				if (iterations == 4) {
+					Assert.True (Application.Current == d);
+				} else if (iterations == 3) {
+					Assert.True (Application.Current == top4);
+				} else if (iterations == 2) {
+					Assert.True (Application.Current == top3);
+				} else if (iterations == 1) {
+					Assert.True (Application.Current == top2);
+				} else {
+					Assert.True (Application.Current == top1);
+				}
+				Application.RequestStop (top1);
+				iterations--;
+			};
+
+			Application.Run (top1);
+
+			Assert.Null (Application.MdiChildes);
+		}
+
+		class Mdi : Toplevel {
+			public Mdi ()
+			{
+				IsMdiContainer = true;
+			}
+		}
+
+		[Fact]
+		[AutoInitShutdown]
+		public void MdiContainer_With_Toplevel_RequestStop_Balanced ()
+		{
+			var mdi = new Mdi ();
+			var c1 = new Toplevel ();
+			var c2 = new Window ();
+			var c3 = new Window ();
+			var d = new Dialog ();
+
+			// MdiChild = c1, c2, c3
+			// d1 = 1
+			var iterations = 4;
+
+			mdi.Ready += () => {
+				Assert.Empty (Application.MdiChildes);
+				Application.Run (c1);
+			};
+			c1.Ready += () => {
+				Assert.Single (Application.MdiChildes);
+				Application.Run (c2);
+			};
+			c2.Ready += () => {
+				Assert.Equal (2, Application.MdiChildes.Count);
+				Application.Run (c3);
+			};
+			c3.Ready += () => {
+				Assert.Equal (3, Application.MdiChildes.Count);
+				Application.Run (d);
+			};
+
+			// More easy because the Mdi Container handles all at once
+			d.Ready += () => {
+				Assert.Equal (3, Application.MdiChildes.Count);
+				// This will not close the MdiContainer because d is a modal toplevel and will be closed.
+				mdi.RequestStop ();
+			};
+
+			// Now this will close the MdiContainer propagating through the MdiChildes.
+			d.Closed += (e) => {
+				mdi.RequestStop ();
+			};
+
+			Application.Iteration += () => {
+				if (iterations == 4) {
+					// The Dialog was not closed before and will be closed now.
+					Assert.True (Application.Current == d);
+					Assert.False (d.Running);
+				} else {
+					Assert.Equal (iterations, Application.MdiChildes.Count);
+					for (int i = 0; i < iterations; i++) {
+						Assert.Equal ((iterations - i + 1).ToString (), Application.MdiChildes [i].Id);
+					}
+				}
+				iterations--;
+			};
+
+			Application.Run (mdi);
+
+			Assert.Empty (Application.MdiChildes);
+		}
+
+		[Fact]
+		[AutoInitShutdown]
+		public void MdiContainer_With_Application_RequestStop_MdiTop_With_Params ()
+		{
+			var mdi = new Mdi ();
+			var c1 = new Toplevel ();
+			var c2 = new Window ();
+			var c3 = new Window ();
+			var d = new Dialog ();
+
+			// MdiChild = c1, c2, c3
+			// d1 = 1
+			var iterations = 4;
+
+			mdi.Ready += () => {
+				Assert.Empty (Application.MdiChildes);
+				Application.Run (c1);
+			};
+			c1.Ready += () => {
+				Assert.Single (Application.MdiChildes);
+				Application.Run (c2);
+			};
+			c2.Ready += () => {
+				Assert.Equal (2, Application.MdiChildes.Count);
+				Application.Run (c3);
+			};
+			c3.Ready += () => {
+				Assert.Equal (3, Application.MdiChildes.Count);
+				Application.Run (d);
+			};
+
+			// Also easy because the Mdi Container handles all at once
+			d.Ready += () => {
+				Assert.Equal (3, Application.MdiChildes.Count);
+				// This will not close the MdiContainer because d is a modal toplevel
+				Application.RequestStop (mdi);
+			};
+
+			// Now this will close the MdiContainer propagating through the MdiChildes.
+			d.Closed += (e) => Application.RequestStop (mdi);
+
+			Application.Iteration += () => {
+				if (iterations == 4) {
+					// The Dialog was not closed before and will be closed now.
+					Assert.True (Application.Current == d);
+					Assert.False (d.Running);
+				} else {
+					Assert.Equal (iterations, Application.MdiChildes.Count);
+					for (int i = 0; i < iterations; i++) {
+						Assert.Equal ((iterations - i + 1).ToString (), Application.MdiChildes [i].Id);
+					}
+				}
+				iterations--;
+			};
+
+			Application.Run (mdi);
+
+			Assert.Empty (Application.MdiChildes);
+		}
+
+		[Fact]
+		[AutoInitShutdown]
+		public void MdiContainer_With_Application_RequestStop_MdiTop_Without_Params ()
+		{
+			var mdi = new Mdi ();
+			var c1 = new Toplevel ();
+			var c2 = new Window ();
+			var c3 = new Window ();
+			var d = new Dialog ();
+
+			// MdiChild = c1, c2, c3 = 3
+			// d1 = 1
+			var iterations = 4;
+
+			mdi.Ready += () => {
+				Assert.Empty (Application.MdiChildes);
+				Application.Run (c1);
+			};
+			c1.Ready += () => {
+				Assert.Single (Application.MdiChildes);
+				Application.Run (c2);
+			};
+			c2.Ready += () => {
+				Assert.Equal (2, Application.MdiChildes.Count);
+				Application.Run (c3);
+			};
+			c3.Ready += () => {
+				Assert.Equal (3, Application.MdiChildes.Count);
+				Application.Run (d);
+			};
+
+			//More harder because it's sequential.
+			d.Ready += () => {
+				Assert.Equal (3, Application.MdiChildes.Count);
+				// Close the Dialog
+				Application.RequestStop ();
+			};
+
+			// Now this will close the MdiContainer propagating through the MdiChildes.
+			d.Closed += (e) => Application.RequestStop (mdi);
+
+			Application.Iteration += () => {
+				if (iterations == 4) {
+					// The Dialog still is the current top and we can't request stop to MdiContainer
+					// because we are not using parameter calls.
+					Assert.True (Application.Current == d);
+					Assert.False (d.Running);
+				} else {
+					Assert.Equal (iterations, Application.MdiChildes.Count);
+					for (int i = 0; i < iterations; i++) {
+						Assert.Equal ((iterations - i + 1).ToString (), Application.MdiChildes [i].Id);
+					}
+				}
+				iterations--;
+			};
+
+			Application.Run (mdi);
+
+			Assert.Empty (Application.MdiChildes);
+		}
+
+		[Fact]
+		[AutoInitShutdown]
+		public void IsMdiChild_Testing ()
+		{
+			var mdi = new Mdi ();
+			var c1 = new Toplevel ();
+			var c2 = new Window ();
+			var c3 = new Window ();
+			var d = new Dialog ();
+
+			Application.Iteration += () => {
+				Assert.False (mdi.IsMdiChild);
+				Assert.True (c1.IsMdiChild);
+				Assert.True (c2.IsMdiChild);
+				Assert.True (c3.IsMdiChild);
+				Assert.False (d.IsMdiChild);
+
+				mdi.RequestStop ();
+			};
+
+			Application.Run (mdi);
+		}
+
+		[Fact]
+		[AutoInitShutdown]
+		public void Modal_Toplevel_Can_Open_Another_Modal_Toplevel_But_RequestStop_To_The_Caller_Also_Sets_Current_Running_To_False_Too ()
+		{
+			var mdi = new Mdi ();
+			var c1 = new Toplevel ();
+			var c2 = new Window ();
+			var c3 = new Window ();
+			var d1 = new Dialog ();
+			var d2 = new Dialog ();
+
+			// MdiChild = c1, c2, c3 = 3
+			// d1, d2 = 2
+			var iterations = 5;
+
+			mdi.Ready += () => {
+				Assert.Empty (Application.MdiChildes);
+				Application.Run (c1);
+			};
+			c1.Ready += () => {
+				Assert.Single (Application.MdiChildes);
+				Application.Run (c2);
+			};
+			c2.Ready += () => {
+				Assert.Equal (2, Application.MdiChildes.Count);
+				Application.Run (c3);
+			};
+			c3.Ready += () => {
+				Assert.Equal (3, Application.MdiChildes.Count);
+				Application.Run (d1);
+			};
+			d1.Ready += () => {
+				Assert.Equal (3, Application.MdiChildes.Count);
+				Application.Run (d2);
+			};
+
+			d2.Ready += () => {
+				Assert.Equal (3, Application.MdiChildes.Count);
+				Assert.True (Application.Current == d2);
+				Assert.True (Application.Current.Running);
+				// Trying to close the Dialog1
+				d1.RequestStop ();
+			};
+
+			// Now this will close the MdiContainer propagating through the MdiChildes.
+			d1.Closed += (e) => {
+				Assert.True (Application.Current == d1);
+				Assert.False (Application.Current.Running);
+				mdi.RequestStop ();
+			};
+
+			Application.Iteration += () => {
+				if (iterations == 5) {
+					// The Dialog2 still is the current top and we can't request stop to MdiContainer
+					// because Dialog2 and Dialog1 must be closed first.
+					// Dialog2 will be closed in this iteration.
+					Assert.True (Application.Current == d2);
+					Assert.False (Application.Current.Running);
+					Assert.False (d1.Running);
+				} else if (iterations == 4) {
+					// Dialog1 will be closed in this iteration.
+					Assert.True (Application.Current == d1);
+					Assert.False (Application.Current.Running);
+				} else {
+					Assert.Equal (iterations, Application.MdiChildes.Count);
+					for (int i = 0; i < iterations; i++) {
+						Assert.Equal ((iterations - i + 1).ToString (), Application.MdiChildes [i].Id);
+					}
+				}
+				iterations--;
+			};
+
+			Application.Run (mdi);
+
+			Assert.Empty (Application.MdiChildes);
+		}
+
+		[Fact]
+		[AutoInitShutdown]
+		public void Modal_Toplevel_Can_Open_Another_Not_Modal_Toplevel_But_RequestStop_To_The_Caller_Also_Sets_Current_Running_To_False_Too ()
+		{
+			var mdi = new Mdi ();
+			var c1 = new Toplevel ();
+			var c2 = new Window ();
+			var c3 = new Window ();
+			var d1 = new Dialog ();
+			var c4 = new Toplevel ();
+
+			// MdiChild = c1, c2, c3, c4 = 4
+			// d1 = 1
+			var iterations = 5;
+
+			mdi.Ready += () => {
+				Assert.Empty (Application.MdiChildes);
+				Application.Run (c1);
+			};
+			c1.Ready += () => {
+				Assert.Single (Application.MdiChildes);
+				Application.Run (c2);
+			};
+			c2.Ready += () => {
+				Assert.Equal (2, Application.MdiChildes.Count);
+				Application.Run (c3);
+			};
+			c3.Ready += () => {
+				Assert.Equal (3, Application.MdiChildes.Count);
+				Application.Run (d1);
+			};
+			d1.Ready += () => {
+				Assert.Equal (3, Application.MdiChildes.Count);
+				Application.Run (c4);
+			};
+
+			c4.Ready += () => {
+				Assert.Equal (4, Application.MdiChildes.Count);
+				// Trying to close the Dialog1
+				d1.RequestStop ();
+			};
+
+			// Now this will close the MdiContainer propagating through the MdiChildes.
+			d1.Closed += (e) => {
+				mdi.RequestStop ();
+			};
+
+			Application.Iteration += () => {
+				if (iterations == 5) {
+					// The Dialog2 still is the current top and we can't request stop to MdiContainer
+					// because Dialog2 and Dialog1 must be closed first.
+					// Using request stop here will call the Dialog again without need
+					Assert.True (Application.Current == d1);
+					Assert.False (Application.Current.Running);
+					Assert.True (c4.Running);
+				} else {
+					Assert.Equal (iterations, Application.MdiChildes.Count);
+					for (int i = 0; i < iterations; i++) {
+						Assert.Equal ((iterations - i + (iterations == 4 && i == 0 ? 2 : 1)).ToString (),
+							Application.MdiChildes [i].Id);
+					}
+				}
+				iterations--;
+			};
+
+			Application.Run (mdi);
+
+			Assert.Empty (Application.MdiChildes);
+		}
+
+		[Fact]
+		[AutoInitShutdown]
+		public void MoveCurrent_Returns_False_If_The_Current_And_Top_Parameter_Are_Both_With_Running_Set_To_False ()
+		{
+			var mdi = new Mdi ();
+			var c1 = new Toplevel ();
+			var c2 = new Window ();
+			var c3 = new Window ();
+
+			// MdiChild = c1, c2, c3
+			var iterations = 3;
+
+			mdi.Ready += () => {
+				Assert.Empty (Application.MdiChildes);
+				Application.Run (c1);
+			};
+			c1.Ready += () => {
+				Assert.Single (Application.MdiChildes);
+				Application.Run (c2);
+			};
+			c2.Ready += () => {
+				Assert.Equal (2, Application.MdiChildes.Count);
+				Application.Run (c3);
+			};
+			c3.Ready += () => {
+				Assert.Equal (3, Application.MdiChildes.Count);
+				c3.RequestStop ();
+				c1.RequestStop ();
+			};
+			// Now this will close the MdiContainer propagating through the MdiChildes.
+			c1.Closed += (e) => {
+				mdi.RequestStop ();
+			};
+			Application.Iteration += () => {
+				if (iterations == 3) {
+					// The Current still is c3 because Current.Running is false.
+					Assert.True (Application.Current == c3);
+					Assert.False (Application.Current.Running);
+					// But the childes order were reorder by Running = false
+					Assert.True (Application.MdiChildes [0] == c3);
+					Assert.True (Application.MdiChildes [1] == c1);
+					Assert.True (Application.MdiChildes [^1] == c2);
+				} else if (iterations == 2) {
+					// The Current is c1 and Current.Running is false.
+					Assert.True (Application.Current == c1);
+					Assert.False (Application.Current.Running);
+					Assert.True (Application.MdiChildes [0] == c1);
+					Assert.True (Application.MdiChildes [^1] == c2);
+				} else if (iterations == 1) {
+					// The Current is c2 and Current.Running is false.
+					Assert.True (Application.Current == c2);
+					Assert.False (Application.Current.Running);
+					Assert.True (Application.MdiChildes [^1] == c2);
+				} else {
+					// The Current is mdi.
+					Assert.True (Application.Current == mdi);
+					Assert.Empty (Application.MdiChildes);
+				}
+				iterations--;
+			};
+
+			Application.Run (mdi);
+
+			Assert.Empty (Application.MdiChildes);
+		}
+
+		[Fact]
+		[AutoInitShutdown]
+		public void MdiContainer_Throws_If_More_Than_One ()
+		{
+			var mdi = new Mdi ();
+			var mdi2 = new Mdi ();
+
+			mdi.Ready += () => {
+				Assert.Throws<InvalidOperationException> (() => Application.Run (mdi2));
+				mdi.RequestStop ();
+			};
+
+			Application.Run (mdi);
+		}
+
+		[Fact]
+		[AutoInitShutdown]
+		public void MdiContainer_Open_And_Close_Modal_And_Open_Not_Modal_Toplevels_Randomly ()
+		{
+			var mdi = new Mdi ();
+			var logger = new Toplevel ();
+
+			var iterations = 1; // The logger
+			var running = true;
+			var stageCompleted = true;
+			var allStageClosed = false;
+			var mdiRequestStop = false;
+
+			mdi.Ready += () => {
+				Assert.Empty (Application.MdiChildes);
+				Application.Run (logger);
+			};
+
+			logger.Ready += () => Assert.Single (Application.MdiChildes);
+
+			Application.Iteration += () => {
+				if (stageCompleted && running) {
+					stageCompleted = false;
+					var stage = new Window () { Modal = true };
+
+					stage.Ready += () => {
+						Assert.Equal (iterations, Application.MdiChildes.Count);
+						stage.RequestStop ();
+					};
+
+					stage.Closed += (_) => {
+						if (iterations == 11) {
+							allStageClosed = true;
+						}
+						Assert.Equal (iterations, Application.MdiChildes.Count);
+						if (running) {
+							stageCompleted = true;
+
+							var rpt = new Window ();
+
+							rpt.Ready += () => {
+								iterations++;
+								Assert.Equal (iterations, Application.MdiChildes.Count);
+							};
+
+							Application.Run (rpt);
+						}
+					};
+
+					Application.Run (stage);
+
+				} else if (iterations == 11 && running) {
+					running = false;
+					Assert.Equal (iterations, Application.MdiChildes.Count);
+
+				} else if (!mdiRequestStop && running && !allStageClosed) {
+					Assert.Equal (iterations, Application.MdiChildes.Count);
+
+				} else if (!mdiRequestStop && !running && allStageClosed) {
+					Assert.Equal (iterations, Application.MdiChildes.Count);
+					mdiRequestStop = true;
+					mdi.RequestStop ();
+				} else {
+					Assert.Empty (Application.MdiChildes);
+				}
+			};
+
+			Application.Run (mdi);
+
+			Assert.Empty (Application.MdiChildes);
+		}
+
+		[Fact]
+		[AutoInitShutdown]
+		public void AllChildClosed_Event_Test ()
+		{
+			var mdi = new Mdi ();
+			var c1 = new Toplevel ();
+			var c2 = new Window ();
+			var c3 = new Window ();
+
+			// MdiChild = c1, c2, c3
+			var iterations = 3;
+
+			mdi.Ready += () => {
+				Assert.Empty (Application.MdiChildes);
+				Application.Run (c1);
+			};
+			c1.Ready += () => {
+				Assert.Single (Application.MdiChildes);
+				Application.Run (c2);
+			};
+			c2.Ready += () => {
+				Assert.Equal (2, Application.MdiChildes.Count);
+				Application.Run (c3);
+			};
+			c3.Ready += () => {
+				Assert.Equal (3, Application.MdiChildes.Count);
+				c3.RequestStop ();
+				c2.RequestStop ();
+				c1.RequestStop ();
+			};
+			// Now this will close the MdiContainer when all MdiChildes was closed
+			mdi.AllChildClosed += () => {
+				mdi.RequestStop ();
+			};
+			Application.Iteration += () => {
+				if (iterations == 3) {
+					// The Current still is c3 because Current.Running is false.
+					Assert.True (Application.Current == c3);
+					Assert.False (Application.Current.Running);
+					// But the childes order were reorder by Running = false
+					Assert.True (Application.MdiChildes [0] == c3);
+					Assert.True (Application.MdiChildes [1] == c2);
+					Assert.True (Application.MdiChildes [^1] == c1);
+				} else if (iterations == 2) {
+					// The Current is c2 and Current.Running is false.
+					Assert.True (Application.Current == c2);
+					Assert.False (Application.Current.Running);
+					Assert.True (Application.MdiChildes [0] == c2);
+					Assert.True (Application.MdiChildes [^1] == c1);
+				} else if (iterations == 1) {
+					// The Current is c1 and Current.Running is false.
+					Assert.True (Application.Current == c1);
+					Assert.False (Application.Current.Running);
+					Assert.True (Application.MdiChildes [^1] == c1);
+				} else {
+					// The Current is mdi.
+					Assert.True (Application.Current == mdi);
+					Assert.False (Application.Current.Running);
+					Assert.Empty (Application.MdiChildes);
+				}
+				iterations--;
+			};
+
+			Application.Run (mdi);
+
+			Assert.Empty (Application.MdiChildes);
+		}
+	}
+}

+ 105 - 0
UnitTests/RunStateTests.cs

@@ -0,0 +1,105 @@
+using System;
+using System.Diagnostics;
+using System.Linq;
+using System.Threading;
+using System.Threading.Tasks;
+using Xunit;
+
+// Alias Console to MockConsole so we don't accidentally use Console
+using Console = Terminal.Gui.FakeConsole;
+
+namespace Terminal.Gui.Core {
+	/// <summary>
+	/// These tests focus on Application.RunState and the various ways it can be changed.
+	/// </summary>
+	public class RunStateTests {
+		public RunStateTests ()
+		{
+#if DEBUG_IDISPOSABLE
+			Responder.Instances.Clear ();
+			Application.RunState.Instances.Clear ();
+#endif
+		}
+
+		[Fact]
+		public void New_Creates_RunState ()
+		{
+			var rs = new Application.RunState (null);
+			Assert.Null (rs.Toplevel);
+			
+			var top = new Toplevel ();
+			rs = new Application.RunState (top);
+			Assert.Equal (top, rs.Toplevel);
+		}
+
+		[Fact]
+		public void Dispose_Cleans_Up_RunState ()
+		{
+			var rs = new Application.RunState (null);
+			Assert.NotNull (rs);
+
+			// Should not throw because Toplevel was null
+			rs.Dispose ();
+			Assert.True (rs.WasDisposed);
+
+			var top = new Toplevel ();
+			rs = new Application.RunState (top);
+			Assert.NotNull (rs);
+
+			// Should throw because Toplevel was not cleaned up
+			Assert.Throws<InvalidOperationException> (() => rs.Dispose ());
+
+			rs.Toplevel.Dispose ();
+			rs.Toplevel = null;
+			rs.Dispose ();
+			Assert.True (rs.WasDisposed);
+			Assert.True (top.WasDisposed);
+		}
+
+		void Init ()
+		{
+			Application.Init (new FakeDriver (), new FakeMainLoop (() => FakeConsole.ReadKey (true)));
+			Assert.NotNull (Application.Driver);
+			Assert.NotNull (Application.MainLoop);
+			Assert.NotNull (SynchronizationContext.Current);
+		}
+
+		void Shutdown ()
+		{
+			Application.Shutdown ();
+			// Validate there are no outstanding RunState-based instances left
+			foreach (var inst in Application.RunState.Instances) {
+				Assert.True (inst.WasDisposed);
+			}
+		}
+
+		[Fact]
+		public void Begin_End_Cleans_Up_RunState ()
+		{
+			// Setup Mock driver
+			Init ();
+
+			// Test null Toplevel
+			Assert.Throws<ArgumentNullException> (() => Application.Begin (null));
+
+			var top = new Toplevel ();
+			var rs = Application.Begin (top);
+			Assert.NotNull (rs);
+			Assert.Equal (top, Application.Current);
+			Application.End (rs);
+
+			Assert.Null (Application.Current);
+			Assert.NotNull (Application.Top);
+			Assert.NotNull (Application.MainLoop);
+			Assert.NotNull (Application.Driver);
+
+			Shutdown ();
+
+			Assert.True (rs.WasDisposed);
+
+			Assert.Null (Application.Top);
+			Assert.Null (Application.MainLoop);
+			Assert.Null (Application.Driver);
+		}
+	}
+}

+ 9 - 3
UnitTests/ScenarioTests.cs

@@ -64,12 +64,18 @@ namespace UICatalog {
 				Application.Init (new FakeDriver (), new FakeMainLoop (() => FakeConsole.ReadKey (true)));
 
 				// Close after a short period of time
-				var token = Application.MainLoop.AddTimeout (TimeSpan.FromMilliseconds (200), closeCallback);
+				var token = Application.MainLoop.AddTimeout (TimeSpan.FromMilliseconds (100), closeCallback);
 
-				scenario.Init (Application.Top, Colors.Base);
+				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) {
@@ -115,7 +121,7 @@ namespace UICatalog {
 				Assert.Equal (Key.CtrlMask | Key.Q, args.KeyEvent.Key);
 			};
 
-			generic.Init (Application.Top, Colors.Base);
+			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.

+ 83 - 0
UnitTests/SynchronizatonContextTests.cs

@@ -0,0 +1,83 @@
+using System;
+using System.Collections.Generic;
+using System.Diagnostics.CodeAnalysis;
+using System.Globalization;
+using System.Linq;
+using System.Runtime.InteropServices.ComTypes;
+using System.Threading;
+using System.Threading.Tasks;
+using Terminal.Gui;
+using Xunit;
+using Xunit.Sdk;
+
+// Alias Console to MockConsole so we don't accidentally use Console
+using Console = Terminal.Gui.FakeConsole;
+
+namespace Terminal.Gui.Core {
+	public class SyncrhonizationContextTests {
+
+		[Fact, AutoInitShutdown]
+		public void SynchronizationContext_Post ()
+		{
+			var context = SynchronizationContext.Current;
+
+			var success = false;
+			Task.Run (() => {
+				Thread.Sleep (1_000);
+
+				// non blocking
+				context.Post (
+					delegate (object o) {
+						success = true;
+
+						// then tell the application to quit
+						Application.MainLoop.Invoke (() => Application.RequestStop ());
+					}, null);
+				Assert.False (success);
+			});
+
+			// blocks here until the RequestStop is processed at the end of the test
+			Application.Run ();
+			Assert.True (success);
+		}
+
+		[Fact, AutoInitShutdown]
+		public void SynchronizationContext_Send ()
+		{
+			var context = SynchronizationContext.Current;
+
+			var success = false;
+			Task.Run (() => {
+				Thread.Sleep (1_000);
+
+				// blocking
+				context.Send (
+					delegate (object o) {
+						success = true;
+
+						// then tell the application to quit
+						Application.MainLoop.Invoke (() => Application.RequestStop ());
+					}, null);
+				Assert.True (success);
+			});
+
+			// blocks here until the RequestStop is processed at the end of the test
+			Application.Run ();
+			Assert.True (success);
+
+		}
+
+		[Fact, AutoInitShutdown]
+		public void SynchronizationContext_CreateCopy ()
+		{
+			var context = SynchronizationContext.Current;
+			Assert.NotNull (context);
+
+			var contextCopy = context.CreateCopy ();
+			Assert.NotNull (contextCopy);
+
+			Assert.NotEqual (context, contextCopy);
+		}
+
+	}
+}

+ 2 - 2
UnitTests/TextFormatterTests.cs

@@ -2841,12 +2841,12 @@ namespace Terminal.Gui.Core {
 
 			c = new System.Rune (31);
 			Assert.Equal (-1, Rune.ColumnWidth (c));        // non printable character
-			Assert.Equal (-1, ustring.Make (c).ConsoleWidth);
+			Assert.Equal (0, ustring.Make (c).ConsoleWidth);// ConsoleWidth only returns zero or greater than zero
 			Assert.Equal (1, ustring.Make (c).Length);
 
 			c = new System.Rune (127);
 			Assert.Equal (-1, Rune.ColumnWidth (c));       // non printable character
-			Assert.Equal (-1, ustring.Make (c).ConsoleWidth);
+			Assert.Equal (0, ustring.Make (c).ConsoleWidth);
 			Assert.Equal (1, ustring.Make (c).Length);
 		}
 

+ 2 - 0
UnitTests/ToplevelTests.cs

@@ -431,6 +431,7 @@ namespace Terminal.Gui.Core {
 			var top = Application.Top;
 			Assert.Null (Application.MdiTop);
 			top.IsMdiContainer = true;
+			Application.Begin (top);
 			Assert.Equal (Application.Top, Application.MdiTop);
 
 			var isRunning = true;
@@ -469,6 +470,7 @@ namespace Terminal.Gui.Core {
 			Assert.Null (top.MostFocused);
 			Assert.Equal (win1.Subviews [0], win1.Focused);
 			Assert.Equal (tf1W1, win1.MostFocused);
+			Assert.True (win1.IsMdiChild);
 			Assert.Single (Application.MdiChildes);
 			Application.Begin (win2);
 			Assert.Equal (new Rect (0, 0, 40, 25), win2.Frame);

+ 0 - 24
UnitTests/ViewTests.cs

@@ -4062,29 +4062,5 @@ This is a tes
 			Assert.False (view.IsKeyPress);
 			Assert.True (view.IsKeyUp);
 		}
-
-		[Fact, AutoInitShutdown]
-		public void IsOverridden_False_IfNotOverriden ()
-		{
-			var view = new DerivedView () { Text = "DerivedView does not override MouseEvent", Width = 10, Height = 10 };
-
-			Assert.False (View.IsOverridden (view, "MouseEvent"));
-
-			var view2 = new Button () { Text = "Button does not overrides OnKeyDown", Width = 10, Height = 10 };
-
-			Assert.False (View.IsOverridden (view2, "OnKeyDown"));
-		}
-
-		[Fact, AutoInitShutdown]
-		public void IsOverridden_True_IfOverriden ()
-		{
-			var view = new Button () { Text = "Button overrides MouseEvent", Width = 10, Height = 10 };
-
-			Assert.True (View.IsOverridden (view, "MouseEvent"));
-
-			var view2 = new DerivedView () { Text = "DerivedView overrides OnKeyDown", Width = 10, Height = 10 };
-
-			Assert.True (View.IsOverridden (view2, "OnKeyDown"));
-		}
 	}
 }

Kaikkia tiedostoja ei voida näyttää, sillä liian monta tiedostoa muuttui tässä diffissä