Ver código fonte

Merge branch 'master' into table-view

tznind 4 anos atrás
pai
commit
c02a238ef0
39 arquivos alterados com 4005 adições e 1170 exclusões
  1. 1 1
      Example/Example.csproj
  2. 3 1
      Example/demo.cs
  3. 9 1
      README.md
  4. 4 4
      ReactiveExample/ReactiveExample.csproj
  5. 48 26
      Terminal.Gui/ConsoleDrivers/CursesDriver/CursesDriver.cs
  6. 1 2
      Terminal.Gui/ConsoleDrivers/CursesDriver/handles.cs
  7. 15 3
      Terminal.Gui/ConsoleDrivers/FakeDriver/FakeDriver.cs
  8. 1087 48
      Terminal.Gui/ConsoleDrivers/NetDriver.cs
  9. 533 265
      Terminal.Gui/ConsoleDrivers/WindowsDriver.cs
  10. 86 34
      Terminal.Gui/Core/Application.cs
  11. 96 68
      Terminal.Gui/Core/ConsoleDriver.cs
  12. 1 1
      Terminal.Gui/Core/Event.cs
  13. 51 34
      Terminal.Gui/Core/Toplevel.cs
  14. 80 17
      Terminal.Gui/Core/View.cs
  15. 17 6
      Terminal.Gui/Core/Window.cs
  16. 1 1
      Terminal.Gui/Terminal.Gui.csproj
  17. 3 12
      Terminal.Gui/Views/Button.cs
  18. 0 1
      Terminal.Gui/Views/FrameView.cs
  19. 155 30
      Terminal.Gui/Views/ListView.cs
  20. 35 5
      Terminal.Gui/Views/Menu.cs
  21. 662 0
      Terminal.Gui/Views/ScrollBarView.cs
  22. 36 334
      Terminal.Gui/Views/ScrollView.cs
  23. 16 92
      Terminal.Gui/Views/TextField.cs
  24. 298 129
      Terminal.Gui/Views/TextView.cs
  25. 1 1
      Terminal.Gui/Windows/Dialog.cs
  26. 2 2
      Terminal.Gui/Windows/FileDialog.cs
  27. 29 14
      UICatalog/Scenarios/CharacterMap.cs
  28. 5 5
      UICatalog/Scenarios/Clipping.cs
  29. 43 0
      UICatalog/Scenarios/Editor.cs
  30. 64 10
      UICatalog/Scenarios/ListViewWithSelection.cs
  31. 53 0
      UICatalog/Scenarios/ListsAndCombos.cs
  32. 1 1
      UICatalog/Scenarios/Scrolling.cs
  33. 7 1
      UICatalog/Scenarios/Threading.cs
  34. 1 1
      UICatalog/Scenarios/WindowsAndFrameViews.cs
  35. 89 8
      UICatalog/UICatalog.cs
  36. 27 7
      UnitTests/ApplicationTests.cs
  37. 408 0
      UnitTests/ScrollBarViewTests.cs
  38. 1 1
      UnitTests/UnitTests.csproj
  39. 36 4
      UnitTests/ViewTests.cs

+ 1 - 1
Example/Example.csproj

@@ -6,7 +6,7 @@
   </PropertyGroup>
   </PropertyGroup>
 
 
   <ItemGroup>
   <ItemGroup>
-    <PackageReference Include="NStack.Core" Version="0.15.0" />
+    <PackageReference Include="NStack.Core" Version="0.16.0" />
   </ItemGroup>
   </ItemGroup>
 
 
   <ItemGroup>
   <ItemGroup>

+ 3 - 1
Example/demo.cs

@@ -562,9 +562,11 @@ static class Demo {
 		if (Debugger.IsAttached)
 		if (Debugger.IsAttached)
 			CultureInfo.DefaultThreadCurrentUICulture = CultureInfo.GetCultureInfo ("en-US");
 			CultureInfo.DefaultThreadCurrentUICulture = CultureInfo.GetCultureInfo ("en-US");
 
 
-		//Application.UseSystemConsole = true;
+		Application.UseSystemConsole = true;
 
 
 		Application.Init();
 		Application.Init();
+		Application.HeightAsBuffer = true;
+		//ConsoleDriver.Diagnostics = ConsoleDriver.DiagnosticFlags.FramePadding | ConsoleDriver.DiagnosticFlags.FrameRuler;
 
 
 		var top = Application.Top;
 		var top = Application.Top;
 
 

+ 9 - 1
README.md

@@ -78,7 +78,7 @@ You can force the use of `System.Console` on Unix as well; see `Core.cs`.
 ## Showcase & Examples
 ## Showcase & Examples
 
 
 * **[UI Catalog](https://github.com/migueldeicaza/gui.cs/tree/master/UICatalog)** - The UI Catalog project provides an easy to use and extend sample illustrating the capabilities of **Terminal.Gui**. Run `dotnet run` in the `UICatalog` directory to run the UI Catalog.
 * **[UI Catalog](https://github.com/migueldeicaza/gui.cs/tree/master/UICatalog)** - The UI Catalog project provides an easy to use and extend sample illustrating the capabilities of **Terminal.Gui**. Run `dotnet run` in the `UICatalog` directory to run the UI Catalog.
-* **[Reactive Example](https://github.com/migueldeicaza/gui.cs/tree/master/ReactiveExample)** - A sample app that shows how to use `System.Reactive` and `ReactiveUI` with `Terminal.Gui`. The app uses the MVVM architecture that may seem familiar to folks coming from WPF, Xamarin Forms, UWP, Avalonia, or Windows Forms. In this app, we implement the data bindings using ReactiveUI `WhenAnyValue` syntax and [Pharmacist](https://github.com/reactiveui/pharmacist)  a tool that converts all events in a NuGet package into observable wrappers.
+* **[Reactive Example](https://github.com/migueldeicaza/gui.cs/tree/master/ReactiveExample)** - A sample app that shows how to use `System.Reactive` and `ReactiveUI` with `Terminal.Gui`. The app uses the MVVM architecture that may seem familiar to folks coming from WPF, Xamarin Forms, UWP, Avalonia, or Windows Forms. In this app, we implement the data bindings using ReactiveUI `WhenAnyValue` syntax and [Pharmacist](https://github.com/reactiveui/pharmacist) — a tool that converts all events in a NuGet package into observable wrappers.
 * **[Example (aka `demo.cs`)](https://github.com/migueldeicaza/gui.cs/tree/master/Example)** - Run `dotnet run` in the `Example` directory to run the simple demo.
 * **[Example (aka `demo.cs`)](https://github.com/migueldeicaza/gui.cs/tree/master/Example)** - Run `dotnet run` in the `Example` directory to run the simple demo.
 * **[Standalone Example](https://github.com/migueldeicaza/gui.cs/tree/master/StandaloneExample)** - A trivial .NET core sample application can be found in the `StandaloneExample` directory. Run `dotnet run` in directory to test.
 * **[Standalone Example](https://github.com/migueldeicaza/gui.cs/tree/master/StandaloneExample)** - A trivial .NET core sample application can be found in the `StandaloneExample` directory. Run `dotnet run` in directory to test.
 * **[F# Example](https://github.com/migueldeicaza/gui.cs/tree/master/FSharpExample)** - An example showing how to build a Terminal.Gui app using F#.
 * **[F# Example](https://github.com/migueldeicaza/gui.cs/tree/master/FSharpExample)** - An example showing how to build a Terminal.Gui app using F#.
@@ -185,6 +185,14 @@ The example above shows how to add views using both styles of layout supported b
 
 
 Use NuGet to install the `Terminal.Gui` NuGet package: https://www.nuget.org/packages/Terminal.Gui
 Use NuGet to install the `Terminal.Gui` NuGet package: https://www.nuget.org/packages/Terminal.Gui
 
 
+### Installation in .NET Core Projects
+
+To install Terminal.Gui into a .NET Core project, use the `dotnet` CLI tool with following command.
+
+```
+dotnet package add Terminal.Gui
+```
+
 ## Running and Building
 ## Running and Building
 
 
 * Windows, Mac, and Linux - Build and run using the .NET SDK command line tools (`dotnet build` in the root directory). Run `UICatalog` with `dotnet ./UICatalog/bin/Debug/net5.0/UICatalog.dll` or by directly executing `./UICatalog/bin/Debug/net5.0/UICatalog.exe`.
 * Windows, Mac, and Linux - Build and run using the .NET SDK command line tools (`dotnet build` in the root directory). Run `UICatalog` with `dotnet ./UICatalog/bin/Debug/net5.0/UICatalog.dll` or by directly executing `./UICatalog/bin/Debug/net5.0/UICatalog.exe`.

+ 4 - 4
ReactiveExample/ReactiveExample.csproj

@@ -4,10 +4,10 @@
         <TargetFramework>netcoreapp3.1</TargetFramework>
         <TargetFramework>netcoreapp3.1</TargetFramework>
     </PropertyGroup>
     </PropertyGroup>
     <ItemGroup>
     <ItemGroup>
-        <PackageReference Include="Pharmacist.MsBuild" Version="1.8.1" PrivateAssets="all" />
-        <PackageReference Include="Pharmacist.Common" Version="1.8.1" />
+        <PackageReference Include="Pharmacist.MsBuild" Version="2.0.6" PrivateAssets="all" />
+        <PackageReference Include="Pharmacist.Common" Version="2.0.6" />
         <PackageReference Include="Terminal.Gui" Version="1.0.0-pre.*" />
         <PackageReference Include="Terminal.Gui" Version="1.0.0-pre.*" />
-        <PackageReference Include="ReactiveUI.Fody" Version="11.5.35" />
-        <PackageReference Include="ReactiveUI" Version="11.5.35" />
+        <PackageReference Include="ReactiveUI.Fody" Version="13.0.1" />
+        <PackageReference Include="ReactiveUI" Version="13.0.1" />
     </ItemGroup>
     </ItemGroup>
 </Project>
 </Project>

+ 48 - 26
Terminal.Gui/ConsoleDrivers/CursesDriver/CursesDriver.cs

@@ -20,6 +20,7 @@ namespace Terminal.Gui {
 		public override int Cols => Curses.Cols;
 		public override int Cols => Curses.Cols;
 		public override int Rows => Curses.Lines;
 		public override int Rows => Curses.Lines;
 		public override int Top => 0;
 		public override int Top => 0;
+		public override bool HeightAsBuffer { get; set; }
 
 
 		// Current row, and current col, tracked by Move/AddRune only
 		// Current row, and current col, tracked by Move/AddRune only
 		int ccol, crow;
 		int ccol, crow;
@@ -39,7 +40,7 @@ namespace Terminal.Gui {
 		}
 		}
 
 
 		static bool sync = false;
 		static bool sync = false;
-		public override void AddRune (Rune rune) 
+		public override void AddRune (Rune rune)
 		{
 		{
 			if (Clip.Contains (ccol, crow)) {
 			if (Clip.Contains (ccol, crow)) {
 				if (needMove) {
 				if (needMove) {
@@ -70,6 +71,7 @@ namespace Terminal.Gui {
 		public override void Refresh () {
 		public override void Refresh () {
 			Curses.refresh ();
 			Curses.refresh ();
 			if (Curses.CheckWinChange ()) {
 			if (Curses.CheckWinChange ()) {
+				Clip = new Rect (0, 0, Cols, Rows);
 				TerminalResized?.Invoke ();
 				TerminalResized?.Invoke ();
 			}
 			}
 		}
 		}
@@ -89,7 +91,14 @@ namespace Terminal.Gui {
 		}
 		}
 
 
 		public override void UpdateScreen () => window.redrawwin ();
 		public override void UpdateScreen () => window.redrawwin ();
-		public override void SetAttribute (Attribute c) => Curses.attrset (c.value);
+
+		int currentAttribute;
+
+		public override void SetAttribute (Attribute c) {
+			currentAttribute = c.Value;
+			Curses.attrset (currentAttribute);
+		}
+
 		public Curses.Window window;
 		public Curses.Window window;
 
 
 		static short last_color_pair = 16;
 		static short last_color_pair = 16;
@@ -103,7 +112,11 @@ namespace Terminal.Gui {
 		public static Attribute MakeColor (short foreground, short background)
 		public static Attribute MakeColor (short foreground, short background)
 		{
 		{
 			Curses.InitColorPair (++last_color_pair, foreground, background);
 			Curses.InitColorPair (++last_color_pair, foreground, background);
-			return new Attribute () { value = Curses.ColorPair (last_color_pair) };
+			return new Attribute (
+				value: Curses.ColorPair (last_color_pair),
+				foreground: (Color)foreground,
+				background: (Color)background
+				);
 		}
 		}
 
 
 		int [,] colorPairs = new int [16, 16];
 		int [,] colorPairs = new int [16, 16];
@@ -256,24 +269,7 @@ namespace Terminal.Gui {
 				}
 				}
 
 
 				if ((mouseFlag & MouseFlags.ReportMousePosition) == 0) {
 				if ((mouseFlag & MouseFlags.ReportMousePosition) == 0) {
-					Task.Run (async () => {
-						while (IsButtonPressed && LastMouseButtonPressed != null) {
-							await Task.Delay (100);
-							var me = new MouseEvent () {
-								X = cev.X,
-								Y = cev.Y,
-								Flags = mouseFlag
-							};
-
-							var view = Application.wantContinuousButtonPressedView;
-							if (view == null)
-								break;
-							if (IsButtonPressed && LastMouseButtonPressed != null && (mouseFlag & MouseFlags.ReportMousePosition) == 0) {
-								mouseHandler (me);
-								//mainLoop.Driver.Wakeup ();
-							}
-						}
-					});
+					ProcessContinuousButtonPressedAsync (cev, mouseFlag).ConfigureAwait (false);
 				}
 				}
 
 
 
 
@@ -329,7 +325,7 @@ namespace Terminal.Gui {
 			};
 			};
 		}
 		}
 
 
-		private MouseFlags ProcessButtonClickedEvent (Curses.MouseEvent cev)
+		MouseFlags ProcessButtonClickedEvent (Curses.MouseEvent cev)
 		{
 		{
 			LastMouseButtonPressed = cev.ButtonState;
 			LastMouseButtonPressed = cev.ButtonState;
 			var mf = GetButtonState (cev, true);
 			var mf = GetButtonState (cev, true);
@@ -345,7 +341,7 @@ namespace Terminal.Gui {
 			return mf;
 			return mf;
 		}
 		}
 
 
-		private MouseFlags ProcessButtonReleasedEvent (Curses.MouseEvent cev)
+		MouseFlags ProcessButtonReleasedEvent (Curses.MouseEvent cev)
 		{
 		{
 			var mf = MapCursesButton (cev.ButtonState);
 			var mf = MapCursesButton (cev.ButtonState);
 			if (!cancelButtonClicked && LastMouseButtonPressed == null && !isReportMousePosition) {
 			if (!cancelButtonClicked && LastMouseButtonPressed == null && !isReportMousePosition) {
@@ -358,6 +354,26 @@ namespace Terminal.Gui {
 			return mf;
 			return mf;
 		}
 		}
 
 
+		async Task ProcessContinuousButtonPressedAsync (Curses.MouseEvent cev, MouseFlags mouseFlag)
+		{
+			while (IsButtonPressed && LastMouseButtonPressed != null) {
+				await Task.Delay (100);
+				var me = new MouseEvent () {
+					X = cev.X,
+					Y = cev.Y,
+					Flags = mouseFlag
+				};
+
+				var view = Application.wantContinuousButtonPressedView;
+				if (view == null)
+					break;
+				if (IsButtonPressed && LastMouseButtonPressed != null && (mouseFlag & MouseFlags.ReportMousePosition) == 0) {
+					mouseHandler (me);
+					//mainLoop.Driver.Wakeup ();
+				}
+			}
+		}
+
 		MouseFlags GetButtonState (Curses.MouseEvent cev, bool pressed = false)
 		MouseFlags GetButtonState (Curses.MouseEvent cev, bool pressed = false)
 		{
 		{
 			MouseFlags mf = default;
 			MouseFlags mf = default;
@@ -633,6 +649,7 @@ namespace Terminal.Gui {
 				}
 				}
 				keyDownHandler (new KeyEvent (k, MapKeyModifiers (k)));
 				keyDownHandler (new KeyEvent (k, MapKeyModifiers (k)));
 				keyHandler (new KeyEvent (k, MapKeyModifiers (k)));
 				keyHandler (new KeyEvent (k, MapKeyModifiers (k)));
+				keyUpHandler (new KeyEvent (k, MapKeyModifiers (k)));
 			}
 			}
 			// Cause OnKeyUp and OnKeyPressed. Note that the special handling for ESC above 
 			// Cause OnKeyUp and OnKeyPressed. Note that the special handling for ESC above 
 			// will not impact KeyUp.
 			// will not impact KeyUp.
@@ -645,16 +662,16 @@ namespace Terminal.Gui {
 		}
 		}
 
 
 		Action<MouseEvent> mouseHandler;
 		Action<MouseEvent> mouseHandler;
-		MainLoop mainLoop;
 
 
 		public override void PrepareToRun (MainLoop mainLoop, Action<KeyEvent> keyHandler, Action<KeyEvent> keyDownHandler, Action<KeyEvent> keyUpHandler, Action<MouseEvent> mouseHandler)
 		public override void PrepareToRun (MainLoop mainLoop, Action<KeyEvent> keyHandler, Action<KeyEvent> keyDownHandler, Action<KeyEvent> keyUpHandler, Action<MouseEvent> mouseHandler)
 		{
 		{
 			// Note: Curses doesn't support keydown/up events and thus any passed keyDown/UpHandlers will never be called
 			// Note: Curses doesn't support keydown/up events and thus any passed keyDown/UpHandlers will never be called
 			Curses.timeout (0);
 			Curses.timeout (0);
 			this.mouseHandler = mouseHandler;
 			this.mouseHandler = mouseHandler;
-			this.mainLoop = mainLoop;
 
 
-			(mainLoop.Driver as UnixMainLoop).AddWatch (0, UnixMainLoop.Condition.PollIn, x => {
+			var mLoop = mainLoop.Driver as UnixMainLoop;
+
+			mLoop.AddWatch (0, UnixMainLoop.Condition.PollIn, x => {
 				ProcessInput (keyHandler, keyDownHandler, keyUpHandler, mouseHandler);
 				ProcessInput (keyHandler, keyDownHandler, keyUpHandler, mouseHandler);
 				return true;
 				return true;
 			});
 			});
@@ -846,6 +863,11 @@ namespace Terminal.Gui {
 			//mouseGrabbed = false;
 			//mouseGrabbed = false;
 			//Curses.mouseinterval (lastMouseInterval);
 			//Curses.mouseinterval (lastMouseInterval);
 		}
 		}
+
+		public override Attribute GetAttribute ()
+		{
+			return currentAttribute;
+		}
 	}
 	}
 
 
 	internal static class Platform {
 	internal static class Platform {

+ 1 - 2
Terminal.Gui/ConsoleDrivers/CursesDriver/handles.cs

@@ -28,9 +28,8 @@
 using System;
 using System;
 
 
 namespace Unix.Terminal {
 namespace Unix.Terminal {
-
-	public partial class Curses {
 #pragma warning disable CS1591 // Missing XML comment for publicly visible type or member
 #pragma warning disable CS1591 // Missing XML comment for publicly visible type or member
+	public partial class Curses {
 		public class Window {
 		public class Window {
 			public readonly IntPtr Handle;
 			public readonly IntPtr Handle;
 			static Window curscr;
 			static Window curscr;

+ 15 - 3
Terminal.Gui/ConsoleDrivers/FakeDriver/FakeDriver.cs

@@ -20,6 +20,7 @@ namespace Terminal.Gui {
 		public override int Cols => cols;
 		public override int Cols => cols;
 		public override int Rows => rows;
 		public override int Rows => rows;
 		public override int Top => 0;
 		public override int Top => 0;
+		public override bool HeightAsBuffer { get; set; }
 
 
 		// The format is rows, columns and 3 values on the last column: Rune, Attribute and Dirty Flag
 		// The format is rows, columns and 3 values on the last column: Rune, Attribute and Dirty Flag
 		int [,,] contents;
 		int [,,] contents;
@@ -112,7 +113,11 @@ namespace Terminal.Gui {
 		static Attribute MakeColor (ConsoleColor f, ConsoleColor b)
 		static Attribute MakeColor (ConsoleColor f, ConsoleColor b)
 		{
 		{
 			// Encode the colors into the int value.
 			// Encode the colors into the int value.
-			return new Attribute () { value = ((((int)f) & 0xffff) << 16) | (((int)b) & 0xffff) };
+			return new Attribute (
+				value: ((((int)f) & 0xffff) << 16) | (((int)b) & 0xffff),
+				foreground: (Color)f,
+				background: (Color)b
+				);
 		}
 		}
 
 
 		public override void Init (Action terminalResized)
 		public override void Init (Action terminalResized)
@@ -248,7 +253,7 @@ namespace Terminal.Gui {
 		int currentAttribute;
 		int currentAttribute;
 		public override void SetAttribute (Attribute c)
 		public override void SetAttribute (Attribute c)
 		{
 		{
-			currentAttribute = c.value;
+			currentAttribute = c.Value;
 		}
 		}
 
 
 		Key MapKey (ConsoleKeyInfo keyInfo)
 		Key MapKey (ConsoleKeyInfo keyInfo)
@@ -386,12 +391,18 @@ namespace Terminal.Gui {
 
 
 				keyHandler (new KeyEvent (map, keyModifiers));
 				keyHandler (new KeyEvent (map, keyModifiers));
 				keyUpHandler (new KeyEvent (map, keyModifiers));
 				keyUpHandler (new KeyEvent (map, keyModifiers));
+				keyModifiers = new KeyModifiers ();
 			};
 			};
 		}
 		}
 
 
+		public override Attribute GetAttribute ()
+		{
+			return currentAttribute;
+		}
+
+		#region Unused
 		public override void SetColors (ConsoleColor foreground, ConsoleColor background)
 		public override void SetColors (ConsoleColor foreground, ConsoleColor background)
 		{
 		{
-			throw new NotImplementedException ();
 		}
 		}
 
 
 		public override void SetColors (short foregroundColorId, short backgroundColorId)
 		public override void SetColors (short foregroundColorId, short backgroundColorId)
@@ -406,6 +417,7 @@ namespace Terminal.Gui {
 		public override void UncookMouse ()
 		public override void UncookMouse ()
 		{
 		{
 		}
 		}
+		#endregion
 #pragma warning restore CS1591 // Missing XML comment for publicly visible type or member
 #pragma warning restore CS1591 // Missing XML comment for publicly visible type or member
 	}
 	}
 }
 }

Diferenças do arquivo suprimidas por serem muito extensas
+ 1087 - 48
Terminal.Gui/ConsoleDrivers/NetDriver.cs


+ 533 - 265
Terminal.Gui/ConsoleDrivers/WindowsDriver.cs

@@ -59,31 +59,40 @@ namespace Terminal.Gui {
 		public bool WriteToConsole (CharInfo [] charInfoBuffer, Coord coords, SmallRect window)
 		public bool WriteToConsole (CharInfo [] charInfoBuffer, Coord coords, SmallRect window)
 		{
 		{
 			if (ScreenBuffer == IntPtr.Zero) {
 			if (ScreenBuffer == IntPtr.Zero) {
-				ScreenBuffer = CreateConsoleScreenBuffer (
-					DesiredAccess.GenericRead | DesiredAccess.GenericWrite,
-					ShareMode.FileShareRead | ShareMode.FileShareWrite,
-					IntPtr.Zero,
-					1,
-					IntPtr.Zero
-				);
-				if (ScreenBuffer == INVALID_HANDLE_VALUE) {
-					var err = Marshal.GetLastWin32Error ();
-
-					if (err != 0)
-						throw new System.ComponentModel.Win32Exception (err);
-				}
+				window = ReadFromConsoleOutput (new Size (Console.WindowWidth, Console.WindowHeight), coords, window);
+			}
+
+			return WriteConsoleOutput (ScreenBuffer, charInfoBuffer, coords, new Coord () { X = window.Left, Y = window.Top }, ref window);
+		}
+
+		public SmallRect ReadFromConsoleOutput (Size size, Coord coords, SmallRect window)
+		{
+			ScreenBuffer = CreateConsoleScreenBuffer (
+				DesiredAccess.GenericRead | DesiredAccess.GenericWrite,
+				ShareMode.FileShareRead | ShareMode.FileShareWrite,
+				IntPtr.Zero,
+				1,
+				IntPtr.Zero
+			);
+			if (ScreenBuffer == INVALID_HANDLE_VALUE) {
+				var err = Marshal.GetLastWin32Error ();
 
 
-				if (!SetConsoleActiveScreenBuffer (ScreenBuffer)) {
-					var err = Marshal.GetLastWin32Error ();
+				if (err != 0 && HeightAsBuffer) {
 					throw new System.ComponentModel.Win32Exception (err);
 					throw new System.ComponentModel.Win32Exception (err);
 				}
 				}
+			}
 
 
-				OriginalStdOutChars = new CharInfo [Console.WindowHeight * Console.WindowWidth];
-
-				ReadConsoleOutput (OutputHandle, OriginalStdOutChars, coords, new Coord () { X = 0, Y = 0 }, ref window);
+			if (!SetConsoleActiveScreenBuffer (ScreenBuffer)) {
+				var err = Marshal.GetLastWin32Error ();
+				if (HeightAsBuffer) {
+					throw new System.ComponentModel.Win32Exception (err);
+				}
 			}
 			}
 
 
-			return WriteConsoleOutput (ScreenBuffer, charInfoBuffer, coords, new Coord () { X = window.Left, Y = window.Top }, ref window);
+			OriginalStdOutChars = new CharInfo [size.Height * size.Width];
+			ReadConsoleOutput (OutputHandle, OriginalStdOutChars, coords, new Coord () { X = 0, Y = 0 }, ref window);
+
+			return window;
 		}
 		}
 
 
 		public bool SetCursorPosition (Coord position)
 		public bool SetCursorPosition (Coord position)
@@ -120,6 +129,8 @@ namespace Terminal.Gui {
 			}
 			}
 		}
 		}
 
 
+		public bool HeightAsBuffer { get; set; }
+
 		[Flags]
 		[Flags]
 		public enum ConsoleModes : uint {
 		public enum ConsoleModes : uint {
 			EnableProcessedInput = 1,
 			EnableProcessedInput = 1,
@@ -207,7 +218,7 @@ namespace Terminal.Gui {
 			public override string ToString () => $"({X},{Y})";
 			public override string ToString () => $"({X},{Y})";
 		};
 		};
 
 
-		internal struct WindowBufferSizeRecord {
+		public struct WindowBufferSizeRecord {
 			public Coordinate size;
 			public Coordinate size;
 
 
 			public WindowBufferSizeRecord (short x, short y)
 			public WindowBufferSizeRecord (short x, short y)
@@ -355,6 +366,20 @@ namespace Terminal.Gui {
 			}
 			}
 		}
 		}
 
 
+		[StructLayout (LayoutKind.Sequential)]
+		public struct ConsoleKeyInfoEx {
+			public ConsoleKeyInfo consoleKeyInfo;
+			public bool CapsLock;
+			public bool NumLock;
+
+			public ConsoleKeyInfoEx (ConsoleKeyInfo consoleKeyInfo, bool capslock, bool numlock)
+			{
+				this.consoleKeyInfo = consoleKeyInfo;
+				CapsLock = capslock;
+				NumLock = numlock;
+			}
+		}
+
 		[DllImport ("kernel32.dll", SetLastError = true)]
 		[DllImport ("kernel32.dll", SetLastError = true)]
 		static extern IntPtr GetStdHandle (int nStdHandle);
 		static extern IntPtr GetStdHandle (int nStdHandle);
 
 
@@ -431,231 +456,147 @@ namespace Terminal.Gui {
 
 
 				return numberEventsRead == 0
 				return numberEventsRead == 0
 					? null
 					? null
-					: new [] {Marshal.PtrToStructure<InputRecord> (pRecord)};
+					: new [] { Marshal.PtrToStructure<InputRecord> (pRecord) };
 			} catch (Exception) {
 			} catch (Exception) {
 				return null;
 				return null;
 			} finally {
 			} finally {
 				Marshal.FreeHGlobal (pRecord);
 				Marshal.FreeHGlobal (pRecord);
 			}
 			}
 		}
 		}
-#if false	// Not needed on the constructor. Perhaps could be used on resizing. To study.
-		[DllImport ("kernel32.dll", ExactSpelling = true)]
-		private static extern IntPtr GetConsoleWindow ();
 
 
-		[DllImport ("user32.dll", CharSet = CharSet.Auto, SetLastError = true)]
-		private static extern bool ShowWindow (IntPtr hWnd, int nCmdShow);
-
-		public const int HIDE = 0;
-		public const int MAXIMIZE = 3;
-		public const int MINIMIZE = 6;
-		public const int RESTORE = 9;
+		// Not needed on the constructor. Perhaps could be used on resizing. To study.
+		[DllImport ("kernel32.dll", ExactSpelling = true)]
+		static extern IntPtr GetConsoleWindow ();
 
 
-		internal void ShowWindow (int state)
+		internal IntPtr GetConsole ()
 		{
 		{
-			IntPtr thisConsole = GetConsoleWindow ();
-			ShowWindow (thisConsole, state);
-		}
+			return GetConsoleWindow ();
+		}
+
+		[DllImport ("user32.dll")]
+		[return: MarshalAs (UnmanagedType.Bool)]
+		static extern bool GetWindowPlacement (IntPtr hWnd, ref WindowPlacement lpwndpl);
+
+		[DllImport ("user32.dll", SetLastError = true)]
+		[return: MarshalAs (UnmanagedType.Bool)]
+		static extern bool SetWindowPlacement (IntPtr hWnd, [In] ref WindowPlacement lpwndpl);
+
+		internal struct WindowPlacement {
+			public int length;
+			public int flags;
+			public int showCmd;
+			public System.Drawing.Point ptMinPosition;
+			public System.Drawing.Point ptMaxPosition;
+			public System.Drawing.Rectangle rcNormalPosition;
+#if _MAC
+			public System.Drawing.Rectangle rcDevice;
 #endif
 #endif
-#if false // See: https://github.com/migueldeicaza/gui.cs/issues/357
-		[StructLayout (LayoutKind.Sequential)]
-		public struct SMALL_RECT {
-			public short Left;
-			public short Top;
-			public short Right;
-			public short Bottom;
-
-			public SMALL_RECT (short Left, short Top, short Right, short Bottom)
-			{
-				this.Left = Left;
-				this.Top = Top;
-				this.Right = Right;
-				this.Bottom = Bottom;
-			}
 		}
 		}
 
 
-		[StructLayout (LayoutKind.Sequential)]
-		public struct CONSOLE_SCREEN_BUFFER_INFO {
-			public int dwSize;
-			public int dwCursorPosition;
-			public short wAttributes;
-			public SMALL_RECT srWindow;
-			public int dwMaximumWindowSize;
-		}
+		// flags
+		public const int WPF_SET_MIN_POSITION = 1;
+		public const int WPF_RESTORE_TO_MAXIMIZED = 2;
+		public const int WPF_ASYNC_WINDOWPLACEMENT = 4;
 
 
-		[DllImport ("kernel32.dll", SetLastError = true)]
-		static extern bool GetConsoleScreenBufferInfo (IntPtr hConsoleOutput, out CONSOLE_SCREEN_BUFFER_INFO ConsoleScreenBufferInfo);
+		// showCmd
+		public const int HIDE = 0;
+		public const int SHOW_NORMAL = 1;
+		public const int SHOW_MINIMIZED = 2;
+		public const int SHOW_MAXIMIZED = 3;
+		public const int SHOW_NOACTIVATE = 4;
+		public const int SHOW = 5;
+		public const int MINIMIZE = 6;
+		public const int SHOW_MIN_NOACTIVE = 7;
+		public const int SHOW_NA = 8;
+		public const int RESTORE = 9;
+		public const int SHOW_DEFAULT = 10;
+		public const int FORCE_MINIMIZE = 11;
 
 
-		// Theoretically GetConsoleScreenBuffer height should give the console Windoww size
-		// It does not work, however, and always returns the size the window was initially created at
-		internal Size GetWindowSize ()
+		internal bool GetWindow (IntPtr handle, ref WindowPlacement placement)
 		{
 		{
-			var consoleScreenBufferInfo = new CONSOLE_SCREEN_BUFFER_INFO ();
-			//consoleScreenBufferInfo.dwSize = Marshal.SizeOf (typeof (CONSOLE_SCREEN_BUFFER_INFO));
-			GetConsoleScreenBufferInfo (OutputHandle, out consoleScreenBufferInfo);
-			return new Size (consoleScreenBufferInfo.srWindow.Right - consoleScreenBufferInfo.srWindow.Left,
-				consoleScreenBufferInfo.srWindow.Bottom - consoleScreenBufferInfo.srWindow.Top);
+			placement = new WindowPlacement {
+				length = Marshal.SizeOf (typeof (WindowPlacement))
+			};
+			return GetWindowPlacement (handle, ref placement);
 		}
 		}
-#endif
-	}
-
-	internal class WindowsDriver : ConsoleDriver, IMainLoopDriver {
-		static bool sync = false;
-		ManualResetEventSlim eventReady = new ManualResetEventSlim (false);
-		ManualResetEventSlim waitForProbe = new ManualResetEventSlim (false);
-		MainLoop mainLoop;
-		WindowsConsole.CharInfo [] OutputBuffer;
-		int cols, rows;
-		WindowsConsole winConsole;
-		WindowsConsole.SmallRect damageRegion;
-
-		public override int Cols => cols;
-		public override int Rows => rows;
-		public override int Top => 0;
 
 
-		public WindowsDriver ()
+		internal bool SetWindow (IntPtr handle, ref WindowPlacement placement)
 		{
 		{
-			winConsole = new WindowsConsole ();
-
-			SetupColorsAndBorders ();
-
-			cols = Console.WindowWidth;
-			rows = Console.WindowHeight;
-#if false
-			winConsole.ShowWindow (WindowsConsole.RESTORE);
-#endif
-			WindowsConsole.SmallRect.MakeEmpty (ref damageRegion);
-
-			ResizeScreen ();
-			UpdateOffScreen ();
-
-			Task.Run ((Action)WindowsInputHandler);
+			return SetWindowPlacement (handle, ref placement);
 		}
 		}
 
 
-		private void SetupColorsAndBorders ()
-		{
-			Colors.TopLevel = new ColorScheme ();
-			Colors.Base = new ColorScheme ();
-			Colors.Dialog = new ColorScheme ();
-			Colors.Menu = new ColorScheme ();
-			Colors.Error = new ColorScheme ();
-
-			Colors.TopLevel.Normal = MakeColor (ConsoleColor.Green, ConsoleColor.Black);
-			Colors.TopLevel.Focus = MakeColor (ConsoleColor.White, ConsoleColor.DarkCyan);
-			Colors.TopLevel.HotNormal = MakeColor (ConsoleColor.DarkYellow, ConsoleColor.Black);
-			Colors.TopLevel.HotFocus = MakeColor (ConsoleColor.DarkBlue, ConsoleColor.DarkCyan);
-
-			Colors.Base.Normal = MakeColor (ConsoleColor.White, ConsoleColor.DarkBlue);
-			Colors.Base.Focus = MakeColor (ConsoleColor.Black, ConsoleColor.Gray);
-			Colors.Base.HotNormal = MakeColor (ConsoleColor.DarkCyan, ConsoleColor.DarkBlue);
-			Colors.Base.HotFocus = MakeColor (ConsoleColor.Blue, ConsoleColor.Gray);
+		[DllImport ("user32.dll", SetLastError = true)]
+		static extern bool GetWindowRect (IntPtr hwnd, out System.Drawing.Rectangle lpRect);
 
 
-			Colors.Menu.Normal = MakeColor (ConsoleColor.White, ConsoleColor.DarkGray);
-			Colors.Menu.Focus = MakeColor (ConsoleColor.White, ConsoleColor.Black);
-			Colors.Menu.HotNormal = MakeColor (ConsoleColor.Yellow, ConsoleColor.DarkGray);
-			Colors.Menu.HotFocus = MakeColor (ConsoleColor.Yellow, ConsoleColor.Black);
-			Colors.Menu.Disabled = MakeColor (ConsoleColor.Gray, ConsoleColor.DarkGray);
-
-			Colors.Dialog.Normal = MakeColor (ConsoleColor.Black, ConsoleColor.Gray);
-			Colors.Dialog.Focus = MakeColor (ConsoleColor.Black, ConsoleColor.DarkGray);
-			Colors.Dialog.HotNormal = MakeColor (ConsoleColor.DarkBlue, ConsoleColor.Gray);
-			Colors.Dialog.HotFocus = MakeColor (ConsoleColor.DarkBlue, ConsoleColor.DarkGray);
-
-			Colors.Error.Normal = MakeColor (ConsoleColor.DarkRed, ConsoleColor.White);
-			Colors.Error.Focus = MakeColor (ConsoleColor.White, ConsoleColor.DarkRed);
-			Colors.Error.HotNormal = MakeColor (ConsoleColor.Black, ConsoleColor.White);
-			Colors.Error.HotFocus = MakeColor (ConsoleColor.Black, ConsoleColor.DarkRed);
+		internal bool GetRect (IntPtr handle, out System.Drawing.Rectangle lpRect)
+		{
+			return GetWindowRect (handle, out lpRect);
 		}
 		}
 
 
-		[StructLayout (LayoutKind.Sequential)]
-		public struct ConsoleKeyInfoEx {
-			public ConsoleKeyInfo consoleKeyInfo;
-			public bool CapsLock;
-			public bool NumLock;
+#if false
+		// size of a device name string
+		private const int CCHDEVICENAME = 32;
 
 
-			public ConsoleKeyInfoEx (ConsoleKeyInfo consoleKeyInfo, bool capslock, bool numlock)
-			{
-				this.consoleKeyInfo = consoleKeyInfo;
-				CapsLock = capslock;
-				NumLock = numlock;
-			}
+		[StructLayout (LayoutKind.Sequential, CharSet = CharSet.Auto)]
+		internal struct MonitorInfoEx {
+			public uint cbSize;
+			public System.Drawing.Rectangle rcMonitor;
+			public System.Drawing.Rectangle rcWork;
+			public int dwFlags;
+			[MarshalAs (UnmanagedType.ByValTStr, SizeConst = CCHDEVICENAME)]
+			public string szDevice;
 		}
 		}
 
 
-		// The records that we keep fetching
-		WindowsConsole.InputRecord [] result, records = new WindowsConsole.InputRecord [1];
+		[DllImport ("user32.dll", CharSet = CharSet.Auto)]
+		static extern bool GetMonitorInfo (IntPtr hMonitor, ref MonitorInfoEx lpmi);
 
 
-		void WindowsInputHandler ()
+		internal bool GetMonitor(IntPtr hMonitor, ref MonitorInfoEx minfo)
 		{
 		{
-			while (true) {
-				waitForProbe.Wait ();
-				waitForProbe.Reset ();
+			minfo.cbSize = (uint)Marshal.SizeOf (minfo);
+			return GetMonitorInfo (hMonitor, ref minfo);
+		}
 
 
-				result = winConsole.ReadConsoleInput ();
+		[DllImport ("user32.dll")]
+		static extern IntPtr MonitorFromWindow (IntPtr hwnd, uint dwFlags);
 
 
-				eventReady.Set ();
-			}
-		}
+		public const int MONITOR_DEFAULTTONULL = 0;
+		public const int MONITOR_DEFAULTTOPRIMARY = 1;
+		public const int MONITOR_DEFAULTTONEAREST = 2;
 
 
-		void IMainLoopDriver.Setup (MainLoop mainLoop)
+		internal IntPtr GetMonitorWindow (IntPtr hwnd, uint dwFlag)
 		{
 		{
-			this.mainLoop = mainLoop;
+			return MonitorFromWindow (hwnd, dwFlag);
 		}
 		}
 
 
-		void IMainLoopDriver.Wakeup ()
-		{
-			//tokenSource.Cancel ();
-			eventReady.Reset ();
-			eventReady.Set ();
-		}
+		[DllImport ("kernel32.dll", SetLastError = true)]
+		static extern bool GetConsoleScreenBufferInfo (IntPtr hConsoleOutput, out ConsoleScreenBufferInfo ConsoleScreenBufferInfo);
 
 
-		bool IMainLoopDriver.EventsPending (bool wait)
+		// Theoretically GetConsoleScreenBuffer height should give the console Window size, but the Top is always 0.
+		// It does not work, however, and always returns the size the window was initially created at
+		internal Size GetWindowSize (IntPtr handle)
 		{
 		{
-			if (CheckTimers (wait, out var waitTimeout)) {
-				return true;
-			}
-
-			result = null;
-			waitForProbe.Set ();
-
-			try {
-				if (!tokenSource.IsCancellationRequested) {
-					eventReady.Wait (waitTimeout, tokenSource.Token);
-				}
-			} catch (OperationCanceledException) {
-				return true;
-			} finally {
-				eventReady.Reset ();
-			}
-
-			if (!tokenSource.IsCancellationRequested) {
-				return result != null || CheckTimers (wait, out waitTimeout);
-			}
-
-			tokenSource.Dispose ();
-			tokenSource = new CancellationTokenSource ();
-			return true;
+			GetConsoleScreenBufferInfo (handle, out ConsoleScreenBufferInfo consoleScreenBufferInfo);
+			return new Size (consoleScreenBufferInfo.srWindow.Right - consoleScreenBufferInfo.srWindow.Left + 1,
+				consoleScreenBufferInfo.srWindow.Bottom - consoleScreenBufferInfo.srWindow.Top + 1);
 		}
 		}
+#endif
+	}
 
 
-		bool CheckTimers (bool wait, out int waitTimeout)
-		{
-			long now = DateTime.UtcNow.Ticks;
-
-			if (mainLoop.timeouts.Count > 0) {
-				waitTimeout = (int)((mainLoop.timeouts.Keys [0] - now) / TimeSpan.TicksPerMillisecond);
-				if (waitTimeout < 0)
-					return true;
-			} else {
-				waitTimeout = -1;
-			}
-
-			if (!wait)
-				waitTimeout = 0;
+	internal class WindowsDriver : ConsoleDriver {
+		static bool sync = false;
+		WindowsConsole.CharInfo [] OutputBuffer;
+		int cols, rows, top;
+		WindowsConsole winConsole;
+		WindowsConsole.SmallRect damageRegion;
 
 
-			int ic;
-			lock (mainLoop.idleHandlers) {
-				ic = mainLoop.idleHandlers.Count;
-			}
+		public override int Cols => cols;
+		public override int Rows => rows;
+		public override int Top => top;
+		public override bool HeightAsBuffer { get; set; }
 
 
-			return ic > 0;
+		public WindowsConsole WinConsole {
+			get => winConsole;
+			private set => winConsole = value;
 		}
 		}
 
 
 		Action<KeyEvent> keyHandler;
 		Action<KeyEvent> keyHandler;
@@ -663,20 +604,58 @@ namespace Terminal.Gui {
 		Action<KeyEvent> keyUpHandler;
 		Action<KeyEvent> keyUpHandler;
 		Action<MouseEvent> mouseHandler;
 		Action<MouseEvent> mouseHandler;
 
 
+		public WindowsDriver ()
+		{
+			winConsole = new WindowsConsole () {
+				HeightAsBuffer = this.HeightAsBuffer
+			};
+		}
+
+		MainLoop mainLoop;
+
 		public override void PrepareToRun (MainLoop mainLoop, Action<KeyEvent> keyHandler, Action<KeyEvent> keyDownHandler, Action<KeyEvent> keyUpHandler, Action<MouseEvent> mouseHandler)
 		public override void PrepareToRun (MainLoop mainLoop, Action<KeyEvent> keyHandler, Action<KeyEvent> keyDownHandler, Action<KeyEvent> keyUpHandler, Action<MouseEvent> mouseHandler)
 		{
 		{
 			this.keyHandler = keyHandler;
 			this.keyHandler = keyHandler;
 			this.keyDownHandler = keyDownHandler;
 			this.keyDownHandler = keyDownHandler;
 			this.keyUpHandler = keyUpHandler;
 			this.keyUpHandler = keyUpHandler;
 			this.mouseHandler = mouseHandler;
 			this.mouseHandler = mouseHandler;
+			this.mainLoop = mainLoop;
+
+			var mLoop = mainLoop.Driver as WindowsMainLoop;
+
+			mLoop.ProcessInput = (e) => ProcessInput (e);
+
+			mLoop.WinChanged = (e) => ChangeWin (e);
 		}
 		}
 
 
-		void IMainLoopDriver.MainIteration ()
+		bool winChanging;
+		bool wasChangeWin;
+
+		void ChangeWin (Size size)
 		{
 		{
-			if (result == null)
-				return;
+			if (!HeightAsBuffer) {
+				winChanging = true;
+				top = 0;
+				cols = size.Width;
+				rows = size.Height;
+				ResizeScreen ();
+				UpdateOffScreen ();
+				var bufferCoords = new WindowsConsole.Coord () {
+					X = (short)cols,
+					Y = (short)rows
+				};
+				winConsole.ReadFromConsoleOutput (size, bufferCoords, damageRegion);
+				if (!winChanging) {
+					TerminalResized.Invoke ();
+				}
+				wasChangeWin = true;
+			}
+		}
+
+		bool isFromRestore;
 
 
-			var inputEvent = result [0];
+		void ProcessInput (WindowsConsole.InputRecord inputEvent)
+		{
 			switch (inputEvent.EventType) {
 			switch (inputEvent.EventType) {
 			case WindowsConsole.EventType.Key:
 			case WindowsConsole.EventType.Key:
 				var map = MapKey (ToConsoleKeyInfoEx (inputEvent.KeyEvent));
 				var map = MapKey (ToConsoleKeyInfoEx (inputEvent.KeyEvent));
@@ -766,20 +745,30 @@ namespace Terminal.Gui {
 				break;
 				break;
 
 
 			case WindowsConsole.EventType.WindowBufferSize:
 			case WindowsConsole.EventType.WindowBufferSize:
-				cols = inputEvent.WindowBufferSizeEvent.size.X;
-				rows = inputEvent.WindowBufferSizeEvent.size.Y;
-				ResizeScreen ();
-				UpdateOffScreen ();
-				TerminalResized?.Invoke ();
+				if (HeightAsBuffer) {
+					cols = inputEvent.WindowBufferSizeEvent.size.X;
+					rows = inputEvent.WindowBufferSizeEvent.size.Y;
+					ResizeScreen ();
+					UpdateOffScreen ();
+					TerminalResized?.Invoke ();
+				} else if (!HeightAsBuffer && !wasChangeWin && !(mainLoop.Driver as WindowsMainLoop).Maximized
+					&& !isFromRestore) {
+					ChangeWin (new Size (inputEvent.WindowBufferSizeEvent.size.X,
+						inputEvent.WindowBufferSizeEvent.size.Y));
+				} else if (!HeightAsBuffer && wasChangeWin && (mainLoop.Driver as WindowsMainLoop).Restored) {
+					(mainLoop.Driver as WindowsMainLoop).Restored = false;
+					isFromRestore = true;
+				} else if (!HeightAsBuffer && wasChangeWin && !(mainLoop.Driver as WindowsMainLoop).Maximized
+					&& !(mainLoop.Driver as WindowsMainLoop).Restored && !isFromRestore) {
+					wasChangeWin = false;
+				} else if (isFromRestore) {
+					isFromRestore = false;
+				}
 				break;
 				break;
 
 
 			case WindowsConsole.EventType.Focus:
 			case WindowsConsole.EventType.Focus:
 				break;
 				break;
-
-			default:
-				break;
 			}
 			}
-			result = null;
 		}
 		}
 
 
 		WindowsConsole.ButtonState? LastMouseButtonPressed = null;
 		WindowsConsole.ButtonState? LastMouseButtonPressed = null;
@@ -810,6 +799,11 @@ namespace Terminal.Gui {
 				IsButtonReleased = false;
 				IsButtonReleased = false;
 			}
 			}
 
 
+			var p = new Point () {
+				X = mouseEvent.MousePosition.X,
+				Y = mouseEvent.MousePosition.Y
+			};
+
 			if ((mouseEvent.ButtonState != 0 && mouseEvent.EventFlags == 0 && LastMouseButtonPressed == null && !IsButtonDoubleClicked) ||
 			if ((mouseEvent.ButtonState != 0 && mouseEvent.EventFlags == 0 && LastMouseButtonPressed == null && !IsButtonDoubleClicked) ||
 				(mouseEvent.EventFlags == WindowsConsole.EventFlags.MouseMoved &&
 				(mouseEvent.EventFlags == WindowsConsole.EventFlags.MouseMoved &&
 				mouseEvent.ButtonState != 0 && !IsButtonReleased && !IsButtonDoubleClicked)) {
 				mouseEvent.ButtonState != 0 && !IsButtonReleased && !IsButtonDoubleClicked)) {
@@ -865,32 +859,24 @@ namespace Terminal.Gui {
 				IsButtonPressed = false;
 				IsButtonPressed = false;
 				IsButtonReleased = true;
 				IsButtonReleased = true;
 			} else if ((mouseEvent.EventFlags == 0 || mouseEvent.EventFlags == WindowsConsole.EventFlags.MouseMoved) &&
 			} else if ((mouseEvent.EventFlags == 0 || mouseEvent.EventFlags == WindowsConsole.EventFlags.MouseMoved) &&
-				  IsButtonReleased) {
-				var p = new Point () {
+				  IsButtonReleased && p == point) {
+				switch (LastMouseButtonPressed) {
+				case WindowsConsole.ButtonState.Button1Pressed:
+					mouseFlag = MouseFlags.Button1Clicked;
+					break;
+
+				case WindowsConsole.ButtonState.Button2Pressed:
+					mouseFlag = MouseFlags.Button2Clicked;
+					break;
+
+				case WindowsConsole.ButtonState.RightmostButtonPressed:
+					mouseFlag = MouseFlags.Button3Clicked;
+					break;
+				}
+				point = new Point () {
 					X = mouseEvent.MousePosition.X,
 					X = mouseEvent.MousePosition.X,
 					Y = mouseEvent.MousePosition.Y
 					Y = mouseEvent.MousePosition.Y
 				};
 				};
-				//if (p == point) {
-					switch (LastMouseButtonPressed) {
-					case WindowsConsole.ButtonState.Button1Pressed:
-						mouseFlag = MouseFlags.Button1Clicked;
-						break;
-
-					case WindowsConsole.ButtonState.Button2Pressed:
-						mouseFlag = MouseFlags.Button2Clicked;
-						break;
-
-					case WindowsConsole.ButtonState.RightmostButtonPressed:
-						mouseFlag = MouseFlags.Button3Clicked;
-						break;
-					}
-					point = new Point () {
-						X = mouseEvent.MousePosition.X,
-						Y = mouseEvent.MousePosition.Y
-					};
-				//} else {
-				//	mouseFlag = 0;
-				//}
 				LastMouseButtonPressed = null;
 				LastMouseButtonPressed = null;
 				IsButtonReleased = false;
 				IsButtonReleased = false;
 			} else if (mouseEvent.EventFlags.HasFlag (WindowsConsole.EventFlags.DoubleClick)) {
 			} else if (mouseEvent.EventFlags.HasFlag (WindowsConsole.EventFlags.DoubleClick)) {
@@ -975,7 +961,7 @@ namespace Terminal.Gui {
 		async Task ProcessContinuousButtonPressedAsync (WindowsConsole.MouseEventRecord mouseEvent, MouseFlags mouseFlag)
 		async Task ProcessContinuousButtonPressedAsync (WindowsConsole.MouseEventRecord mouseEvent, MouseFlags mouseFlag)
 		{
 		{
 			while (IsButtonPressed) {
 			while (IsButtonPressed) {
-				await Task.Delay (200);
+				await Task.Delay (100);
 				var me = new MouseEvent () {
 				var me = new MouseEvent () {
 					X = mouseEvent.MousePosition.X,
 					X = mouseEvent.MousePosition.X,
 					Y = mouseEvent.MousePosition.Y,
 					Y = mouseEvent.MousePosition.Y,
@@ -1009,7 +995,7 @@ namespace Terminal.Gui {
 
 
 		KeyModifiers keyModifiers;
 		KeyModifiers keyModifiers;
 
 
-		public ConsoleKeyInfoEx ToConsoleKeyInfoEx (WindowsConsole.KeyEventRecord keyEvent)
+		public WindowsConsole.ConsoleKeyInfoEx ToConsoleKeyInfoEx (WindowsConsole.KeyEventRecord keyEvent)
 		{
 		{
 			var state = keyEvent.dwControlKeyState;
 			var state = keyEvent.dwControlKeyState;
 
 
@@ -1036,10 +1022,10 @@ namespace Terminal.Gui {
 				keyModifiers.Scrolllock = scrolllock;
 				keyModifiers.Scrolllock = scrolllock;
 
 
 			var ConsoleKeyInfo = new ConsoleKeyInfo (keyEvent.UnicodeChar, (ConsoleKey)keyEvent.wVirtualKeyCode, shift, alt, control);
 			var ConsoleKeyInfo = new ConsoleKeyInfo (keyEvent.UnicodeChar, (ConsoleKey)keyEvent.wVirtualKeyCode, shift, alt, control);
-			return new ConsoleKeyInfoEx (ConsoleKeyInfo, capslock, numlock);
+			return new WindowsConsole.ConsoleKeyInfoEx (ConsoleKeyInfo, capslock, numlock);
 		}
 		}
 
 
-		public Key MapKey (ConsoleKeyInfoEx keyInfoEx)
+		public Key MapKey (WindowsConsole.ConsoleKeyInfoEx keyInfoEx)
 		{
 		{
 			var keyInfo = keyInfoEx.consoleKeyInfo;
 			var keyInfo = keyInfoEx.consoleKeyInfo;
 			switch (keyInfo.Key) {
 			switch (keyInfo.Key) {
@@ -1163,7 +1149,7 @@ namespace Terminal.Gui {
 			return (Key)(0xffffffff);
 			return (Key)(0xffffffff);
 		}
 		}
 
 
-		private Key MapKeyModifiers (ConsoleKeyInfo keyInfo, Key key)
+		Key MapKeyModifiers (ConsoleKeyInfo keyInfo, Key key)
 		{
 		{
 			Key keyMod = new Key ();
 			Key keyMod = new Key ();
 			if ((keyInfo.Modifiers & ConsoleModifiers.Shift) != 0)
 			if ((keyInfo.Modifiers & ConsoleModifiers.Shift) != 0)
@@ -1179,7 +1165,48 @@ namespace Terminal.Gui {
 		public override void Init (Action terminalResized)
 		public override void Init (Action terminalResized)
 		{
 		{
 			TerminalResized = terminalResized;
 			TerminalResized = terminalResized;
-			SetupColorsAndBorders ();
+
+			cols = Console.WindowWidth;
+			rows = Console.WindowHeight;
+#if false
+			winConsole.ShowWindow (WindowsConsole.RESTORE);
+#endif
+			WindowsConsole.SmallRect.MakeEmpty (ref damageRegion);
+
+			ResizeScreen ();
+			UpdateOffScreen ();
+
+			Colors.TopLevel = new ColorScheme ();
+			Colors.Base = new ColorScheme ();
+			Colors.Dialog = new ColorScheme ();
+			Colors.Menu = new ColorScheme ();
+			Colors.Error = new ColorScheme ();
+
+			Colors.TopLevel.Normal = MakeColor (ConsoleColor.Green, ConsoleColor.Black);
+			Colors.TopLevel.Focus = MakeColor (ConsoleColor.White, ConsoleColor.DarkCyan);
+			Colors.TopLevel.HotNormal = MakeColor (ConsoleColor.DarkYellow, ConsoleColor.Black);
+			Colors.TopLevel.HotFocus = MakeColor (ConsoleColor.DarkBlue, ConsoleColor.DarkCyan);
+
+			Colors.Base.Normal = MakeColor (ConsoleColor.White, ConsoleColor.DarkBlue);
+			Colors.Base.Focus = MakeColor (ConsoleColor.Black, ConsoleColor.Gray);
+			Colors.Base.HotNormal = MakeColor (ConsoleColor.DarkCyan, ConsoleColor.DarkBlue);
+			Colors.Base.HotFocus = MakeColor (ConsoleColor.Blue, ConsoleColor.Gray);
+
+			Colors.Menu.Normal = MakeColor (ConsoleColor.White, ConsoleColor.DarkGray);
+			Colors.Menu.Focus = MakeColor (ConsoleColor.White, ConsoleColor.Black);
+			Colors.Menu.HotNormal = MakeColor (ConsoleColor.Yellow, ConsoleColor.DarkGray);
+			Colors.Menu.HotFocus = MakeColor (ConsoleColor.Yellow, ConsoleColor.Black);
+			Colors.Menu.Disabled = MakeColor (ConsoleColor.Gray, ConsoleColor.DarkGray);
+
+			Colors.Dialog.Normal = MakeColor (ConsoleColor.Black, ConsoleColor.Gray);
+			Colors.Dialog.Focus = MakeColor (ConsoleColor.Black, ConsoleColor.DarkGray);
+			Colors.Dialog.HotNormal = MakeColor (ConsoleColor.DarkBlue, ConsoleColor.Gray);
+			Colors.Dialog.HotFocus = MakeColor (ConsoleColor.DarkBlue, ConsoleColor.DarkGray);
+
+			Colors.Error.Normal = MakeColor (ConsoleColor.DarkRed, ConsoleColor.White);
+			Colors.Error.Focus = MakeColor (ConsoleColor.White, ConsoleColor.DarkRed);
+			Colors.Error.HotNormal = MakeColor (ConsoleColor.Black, ConsoleColor.White);
+			Colors.Error.HotFocus = MakeColor (ConsoleColor.Black, ConsoleColor.DarkRed);
 		}
 		}
 
 
 		void ResizeScreen ()
 		void ResizeScreen ()
@@ -1196,12 +1223,15 @@ namespace Terminal.Gui {
 
 
 		void UpdateOffScreen ()
 		void UpdateOffScreen ()
 		{
 		{
-			for (int row = 0; row < rows; row++)
+			for (int row = 0; row < rows; row++) {
 				for (int col = 0; col < cols; col++) {
 				for (int col = 0; col < cols; col++) {
 					int position = row * cols + col;
 					int position = row * cols + col;
 					OutputBuffer [position].Attributes = (ushort)Colors.TopLevel.Normal;
 					OutputBuffer [position].Attributes = (ushort)Colors.TopLevel.Normal;
 					OutputBuffer [position].Char.UnicodeChar = ' ';
 					OutputBuffer [position].Char.UnicodeChar = ' ';
 				}
 				}
+			}
+
+			winChanging = false;
 		}
 		}
 
 
 		int ccol, crow;
 		int ccol, crow;
@@ -1245,21 +1275,20 @@ namespace Terminal.Gui {
 		}
 		}
 
 
 		int currentAttribute;
 		int currentAttribute;
-		CancellationTokenSource tokenSource = new CancellationTokenSource ();
 
 
 		public override void SetAttribute (Attribute c)
 		public override void SetAttribute (Attribute c)
 		{
 		{
-			currentAttribute = c.value;
+			currentAttribute = c.Value;
 		}
 		}
 
 
 		Attribute MakeColor (ConsoleColor f, ConsoleColor b)
 		Attribute MakeColor (ConsoleColor f, ConsoleColor b)
 		{
 		{
 			// Encode the colors into the int value.
 			// Encode the colors into the int value.
-			return new Attribute () {
-				value = ((int)f | (int)b << 4),
-				foreground = (Color)f,
-				background = (Color)b
-			};
+			return new Attribute (
+				value: ((int)f | (int)b << 4),
+				foreground: (Color)f,
+				background: (Color)b
+			);
 		}
 		}
 
 
 		public override Attribute MakeAttribute (Color fore, Color back)
 		public override Attribute MakeAttribute (Color fore, Color back)
@@ -1298,16 +1327,16 @@ namespace Terminal.Gui {
 				Y = (short)Clip.Height
 				Y = (short)Clip.Height
 			};
 			};
 
 
-			var window = new WindowsConsole.SmallRect () {
-				Top = 0,
-				Left = 0,
-				Right = (short)Clip.Right,
-				Bottom = (short)Clip.Bottom
-			};
+			//var window = new WindowsConsole.SmallRect () {
+			//	Top = 0,
+			//	Left = 0,
+			//	Right = (short)Clip.Right,
+			//	Bottom = (short)Clip.Bottom
+			//};
 
 
 			UpdateCursor ();
 			UpdateCursor ();
 			winConsole.WriteToConsole (OutputBuffer, bufferCoords, damageRegion);
 			winConsole.WriteToConsole (OutputBuffer, bufferCoords, damageRegion);
-			//			System.Diagnostics.Debugger.Log(0, "debug", $"Region={damageRegion.Right - damageRegion.Left},{damageRegion.Bottom - damageRegion.Top}\n");
+			//System.Diagnostics.Debugger.Log(0, "debug", $"Region={damageRegion.Right - damageRegion.Left},{damageRegion.Bottom - damageRegion.Top}\n");
 			WindowsConsole.SmallRect.MakeEmpty (ref damageRegion);
 			WindowsConsole.SmallRect.MakeEmpty (ref damageRegion);
 		}
 		}
 
 
@@ -1325,7 +1354,12 @@ namespace Terminal.Gui {
 			winConsole.Cleanup ();
 			winConsole.Cleanup ();
 		}
 		}
 
 
-#region Unused
+		public override Attribute GetAttribute ()
+		{
+			return currentAttribute;
+		}
+
+		#region Unused
 		public override void SetColors (ConsoleColor foreground, ConsoleColor background)
 		public override void SetColors (ConsoleColor foreground, ConsoleColor background)
 		{
 		{
 		}
 		}
@@ -1353,8 +1387,242 @@ namespace Terminal.Gui {
 		public override void CookMouse ()
 		public override void CookMouse ()
 		{
 		{
 		}
 		}
-#endregion
-
+		#endregion
 	}
 	}
 
 
+	/// <summary>
+	/// Mainloop intended to be used with the <see cref="WindowsDriver"/>, and can
+	/// only be used on Windows.
+	/// </summary>
+	/// <remarks>
+	/// This implementation is used for WindowsDriver.
+	/// </remarks>
+	internal class WindowsMainLoop : IMainLoopDriver {
+		ManualResetEventSlim eventReady = new ManualResetEventSlim (false);
+		ManualResetEventSlim waitForProbe = new ManualResetEventSlim (false);
+		ManualResetEventSlim winChange = new ManualResetEventSlim (false);
+		MainLoop mainLoop;
+		ConsoleDriver consoleDriver;
+		WindowsConsole winConsole;
+		bool winChanged;
+		Size windowSize;
+		CancellationTokenSource tokenSource = new CancellationTokenSource ();
+
+		// The records that we keep fetching
+		WindowsConsole.InputRecord [] result = new WindowsConsole.InputRecord [1];
+
+		/// <summary>
+		/// Invoked when a Key is pressed or released.
+		/// </summary>
+		public Action<WindowsConsole.InputRecord> ProcessInput;
+
+		/// <summary>
+		/// Invoked when the window is changed.
+		/// </summary>
+		public Action<Size> WinChanged;
+
+		public bool Maximized;
+		public bool Restored;
+
+		public WindowsMainLoop (ConsoleDriver consoleDriver = null)
+		{
+			if (consoleDriver == null) {
+				throw new ArgumentNullException ("Console driver instance must be provided.");
+			}
+			this.consoleDriver = consoleDriver;
+			winConsole = ((WindowsDriver)consoleDriver).WinConsole;
+		}
+
+		void IMainLoopDriver.Setup (MainLoop mainLoop)
+		{
+			this.mainLoop = mainLoop;
+			Task.Run (WindowsInputHandler);
+			Task.Run (CheckWinChange);
+		}
+
+		void WindowsInputHandler ()
+		{
+			while (true) {
+				waitForProbe.Wait ();
+				waitForProbe.Reset ();
+
+				result = winConsole.ReadConsoleInput ();
+
+				eventReady.Set ();
+			}
+		}
+
+		void CheckWinChange ()
+		{
+			while (true) {
+				winChange.Wait ();
+				winChange.Reset ();
+				WaitWinChange ();
+				winChanged = true;
+				eventReady.Set ();
+			}
+		}
+
+		const int Width_Divider = 8;
+		const int Height_Divider = 18;
+		bool docked;
+
+		void WaitWinChange ()
+		{
+			var handle = winConsole.GetConsole ();
+
+			while (!consoleDriver.HeightAsBuffer) {
+				WindowsConsole.WindowPlacement windowPlacement = new WindowsConsole.WindowPlacement ();
+				winConsole.GetWindow (handle, ref windowPlacement);
+
+				if (windowPlacement.rcNormalPosition.Size.Height > -1) {
+					windowSize = SetWindowSize (windowPlacement.rcNormalPosition);
+
+					if (windowPlacement.showCmd != WindowsConsole.SHOW_MAXIMIZED && !Maximized && !Restored && !docked
+						&& (windowSize.Width != consoleDriver.Cols || windowSize.Height != consoleDriver.Rows)) {
+						docked = false;
+						return;
+					} else if (windowPlacement.showCmd == WindowsConsole.SHOW_MAXIMIZED && !Maximized
+						&& (Console.LargestWindowWidth != consoleDriver.Cols || Console.LargestWindowHeight != consoleDriver.Rows)) {
+						windowSize = new Size (Console.LargestWindowWidth, Console.LargestWindowHeight);
+						Maximized = true;
+						docked = false;
+						return;
+					} else if (windowPlacement.showCmd != WindowsConsole.SHOW_MAXIMIZED && Maximized) {
+						windowPlacement = new WindowsConsole.WindowPlacement () {
+							showCmd = WindowsConsole.RESTORE
+						};
+						winConsole.SetWindow (handle, ref windowPlacement);
+						Restored = true;
+						Maximized = false;
+						docked = false;
+						return;
+					} else if (!Maximized && IsDockedToMonitor (handle, windowPlacement)) {
+						return;
+					}
+				}
+			}
+
+			Size SetWindowSize (System.Drawing.Rectangle rect)
+			{
+				return new Size (Math.Max (((rect.Width - rect.X) / Width_Divider) - 2, 0),
+					Math.Max (((rect.Height - rect.Y) / Height_Divider) - 2, 0));
+			}
+
+			bool IsDockedToMonitor (IntPtr hWnd, WindowsConsole.WindowPlacement placement)
+			{
+				System.Drawing.Rectangle rc;
+				winConsole.GetRect (hWnd, out rc);
+
+				var changed = placement.showCmd == WindowsConsole.SHOW_NORMAL
+				    && (rc.Left != placement.rcNormalPosition.Left ||
+					rc.Top != placement.rcNormalPosition.Top ||
+					rc.Right != placement.rcNormalPosition.Right ||
+					rc.Bottom != placement.rcNormalPosition.Bottom);
+
+				if (changed) {
+					var pSize = new Size (placement.rcNormalPosition.Size.Width - placement.rcNormalPosition.X,
+						placement.rcNormalPosition.Size.Height - placement.rcNormalPosition.Y);
+					var rSize = new Size (rc.Width - rc.X,
+						rc.Height - rc.Y);
+					windowSize = SetWindowSize (rc);
+
+					if ((rc.X < 0) || (rc.Y == 0) || (rc.Y == 0 && rc.X < 0)
+						|| (rc.Y == 0 && rc.Right / Width_Divider >= Console.LargestWindowWidth)
+						|| (rc.X < 0 && rc.Bottom / Height_Divider >= Console.LargestWindowHeight)
+						|| (rc.X / Width_Divider >= Console.LargestWindowWidth / 2 - 1 && rc.Bottom / Height_Divider >= Console.LargestWindowHeight)) {
+						if (!docked || consoleDriver.Cols != windowSize.Width
+							|| consoleDriver.Rows != windowSize.Height) {
+							docked = true;
+						} else {
+							changed = false;
+						}
+					} else {
+						if (!docked && (pSize == rSize || rSize.Width / Width_Divider >= Console.LargestWindowWidth
+							|| rSize.Height / Height_Divider >= Console.LargestWindowHeight)) {
+							changed = false;
+						}
+						docked = false;
+					}
+				} else {
+					docked = false;
+				}
+
+				return changed;
+			}
+		}
+
+		void IMainLoopDriver.Wakeup ()
+		{
+			//tokenSource.Cancel ();
+			eventReady.Set ();
+		}
+
+		bool IMainLoopDriver.EventsPending (bool wait)
+		{
+			if (CheckTimers (wait, out var waitTimeout)) {
+				return true;
+			}
+
+			//result = null;
+			waitForProbe.Set ();
+			if (!consoleDriver.HeightAsBuffer) {
+				winChange.Set ();
+			}
+
+			try {
+				if (!tokenSource.IsCancellationRequested) {
+					eventReady.Wait (waitTimeout, tokenSource.Token);
+				}
+			} catch (OperationCanceledException) {
+				return true;
+			} finally {
+				eventReady.Reset ();
+			}
+
+			if (!tokenSource.IsCancellationRequested) {
+				return result != null || CheckTimers (wait, out _) || winChanged;
+			}
+
+			tokenSource.Dispose ();
+			tokenSource = new CancellationTokenSource ();
+			return true;
+		}
+
+		bool CheckTimers (bool wait, out int waitTimeout)
+		{
+			long now = DateTime.UtcNow.Ticks;
+
+			if (mainLoop.timeouts.Count > 0) {
+				waitTimeout = (int)((mainLoop.timeouts.Keys [0] - now) / TimeSpan.TicksPerMillisecond);
+				if (waitTimeout < 0)
+					return true;
+			} else {
+				waitTimeout = -1;
+			}
+
+			if (!wait)
+				waitTimeout = 0;
+
+			int ic;
+			lock (mainLoop.idleHandlers) {
+				ic = mainLoop.idleHandlers.Count;
+			}
+
+			return ic > 0;
+		}
+
+		void IMainLoopDriver.MainIteration ()
+		{
+			if (result != null) {
+				var inputEvent = result [0];
+				result = null;
+				ProcessInput?.Invoke (inputEvent);
+			}
+			if (winChanged) {
+				winChanged = false;
+				WinChanged?.Invoke (windowSize);
+			}
+		}
+	}
 }
 }

+ 86 - 34
Terminal.Gui/Core/Application.cs

@@ -59,7 +59,7 @@ namespace Terminal.Gui {
 		/// The current <see cref="ConsoleDriver"/> in use.
 		/// The current <see cref="ConsoleDriver"/> in use.
 		/// </summary>
 		/// </summary>
 		public static ConsoleDriver Driver;
 		public static ConsoleDriver Driver;
-
+		
 		/// <summary>
 		/// <summary>
 		/// The <see cref="Toplevel"/> object used for the application on startup (<seealso cref="Application.Top"/>)
 		/// The <see cref="Toplevel"/> object used for the application on startup (<seealso cref="Application.Top"/>)
 		/// </summary>
 		/// </summary>
@@ -67,16 +67,49 @@ namespace Terminal.Gui {
 		public static Toplevel Top { get; private set; }
 		public static Toplevel Top { get; private set; }
 
 
 		/// <summary>
 		/// <summary>
-		/// The current <see cref="Toplevel"/> object. This is updated when <see cref="Application.Run()"/> enters and leaves to point to the current <see cref="Toplevel"/> .
+		/// The current <see cref="Toplevel"/> object. This is updated when <see cref="Application.Run(Func{Exception, bool})"/> enters and leaves to point to the current <see cref="Toplevel"/> .
 		/// </summary>
 		/// </summary>
 		/// <value>The current.</value>
 		/// <value>The current.</value>
 		public static Toplevel Current { get; private set; }
 		public static Toplevel Current { get; private set; }
 
 
 		/// <summary>
 		/// <summary>
-		/// TThe current <see cref="View"/> object being redrawn.
+		/// The current <see cref="ConsoleDriver.HeightAsBuffer"/> used in the terminal.
 		/// </summary>
 		/// </summary>
-		/// /// <value>The current.</value>
-		public static View CurrentView { get; set; }
+		public static bool HeightAsBuffer {
+			get {
+				if (Driver == null) {
+					throw new ArgumentNullException ("The driver must be initialized first.");
+				}
+				return Driver.HeightAsBuffer;
+			}
+			set {
+				if (Driver == null) {
+					throw new ArgumentNullException ("The driver must be initialized first.");
+				}
+				if (Driver.HeightAsBuffer != value) {
+					Driver.HeightAsBuffer = value;
+					Driver.Refresh ();
+				}
+			}
+		}
+
+		/// <summary>
+		/// Used only by <see cref="NetDriver"/> to forcing always moving the cursor position when writing to the screen.
+		/// </summary>
+		public static bool AlwaysSetPosition {
+			get {
+				if (Driver is NetDriver) {
+					return (Driver as NetDriver).AlwaysSetPosition;
+				}
+				return false;
+			}
+			set {
+				if (Driver is NetDriver) {
+					(Driver as NetDriver).AlwaysSetPosition = value;
+					Driver.Refresh ();
+				}
+			}
+		}
 
 
 		/// <summary>
 		/// <summary>
 		/// The <see cref="MainLoop"/>  driver for the application
 		/// The <see cref="MainLoop"/>  driver for the application
@@ -154,7 +187,7 @@ namespace Terminal.Gui {
 		/// Loads the right <see cref="ConsoleDriver"/> for the platform.
 		/// Loads the right <see cref="ConsoleDriver"/> for the platform.
 		/// </para>
 		/// </para>
 		/// <para>
 		/// <para>
-		/// Creates a <see cref="Toplevel"/> and assigns it to <see cref="Top"/> and <see cref="CurrentView"/>
+		/// Creates a <see cref="Toplevel"/> and assigns it to <see cref="Top"/>
 		/// </para>
 		/// </para>
 		/// </remarks>
 		/// </remarks>
 		public static void Init (ConsoleDriver driver = null, IMainLoopDriver mainLoopDriver = null) => Init (() => Toplevel.Create (), driver, mainLoopDriver);
 		public static void Init (ConsoleDriver driver = null, IMainLoopDriver mainLoopDriver = null) => Init (() => Toplevel.Create (), driver, mainLoopDriver);
@@ -167,7 +200,7 @@ namespace Terminal.Gui {
 		static void Init (Func<Toplevel> topLevelFactory, ConsoleDriver driver = null, IMainLoopDriver mainLoopDriver = null)
 		static void Init (Func<Toplevel> topLevelFactory, ConsoleDriver driver = null, IMainLoopDriver mainLoopDriver = null)
 		{
 		{
 			if (_initialized && driver == null) return;
 			if (_initialized && driver == null) return;
-
+			
 			// Used only for start debugging on Unix.
 			// Used only for start debugging on Unix.
 //#if DEBUG
 //#if DEBUG
 //			while (!System.Diagnostics.Debugger.IsAttached) {
 //			while (!System.Diagnostics.Debugger.IsAttached) {
@@ -193,9 +226,8 @@ namespace Terminal.Gui {
 					Driver = new NetDriver ();
 					Driver = new NetDriver ();
 					mainLoopDriver = new NetMainLoop (Driver);
 					mainLoopDriver = new NetMainLoop (Driver);
 				} else if (p == PlatformID.Win32NT || p == PlatformID.Win32S || p == PlatformID.Win32Windows) {
 				} else if (p == PlatformID.Win32NT || p == PlatformID.Win32S || p == PlatformID.Win32Windows) {
-					var windowsDriver = new WindowsDriver ();
-					mainLoopDriver = windowsDriver;
-					Driver = windowsDriver;
+					Driver = new WindowsDriver ();
+					mainLoopDriver = new WindowsMainLoop (Driver);
 				} else {
 				} else {
 					mainLoopDriver = new UnixMainLoop ();
 					mainLoopDriver = new UnixMainLoop ();
 					Driver = new CursesDriver ();
 					Driver = new CursesDriver ();
@@ -206,7 +238,6 @@ namespace Terminal.Gui {
 			}
 			}
 			Top = topLevelFactory ();
 			Top = topLevelFactory ();
 			Current = Top;
 			Current = Top;
-			CurrentView = Top;
 			_initialized = true;
 			_initialized = true;
 		}
 		}
 
 
@@ -320,7 +351,7 @@ namespace Terminal.Gui {
 					var ry = y - startFrame.Y;
 					var ry = y - startFrame.Y;
 					for (int i = count - 1; i >= 0; i--) {
 					for (int i = count - 1; i >= 0; i--) {
 						View v = start.InternalSubviews [i];
 						View v = start.InternalSubviews [i];
-						if (v.Frame.Contains (rx, ry)) {
+						if (v.Visible && v.Frame.Contains (rx, ry)) {
 							var deep = FindDeepestView (v, rx, ry, out resx, out resy);
 							var deep = FindDeepestView (v, rx, ry, out resx, out resy);
 							if (deep == null)
 							if (deep == null)
 								return v;
 								return v;
@@ -400,8 +431,8 @@ namespace Terminal.Gui {
 					X = rx,
 					X = rx,
 					Y = ry,
 					Y = ry,
 					Flags = me.Flags,
 					Flags = me.Flags,
-					OfX = rx,
-					OfY = ry,
+					OfX = 0,
+					OfY = 0,
 					View = view
 					View = view
 				};
 				};
 
 
@@ -503,19 +534,22 @@ namespace Terminal.Gui {
 			}
 			}
 			toplevels.Clear ();
 			toplevels.Clear ();
 			Current = null;
 			Current = null;
-			CurrentView = null;
 			Top = null;
 			Top = null;
 
 
 			MainLoop = null;
 			MainLoop = null;
 			Driver?.End ();
 			Driver?.End ();
 			Driver = null;
 			Driver = null;
 			_initialized = false;
 			_initialized = false;
+
+			// Reset synchronization context to allow the user to run async/await,
+			// as the main loop has been ended, the synchronization context from 
+			// gui.cs does no longer process any callbacks. See #1084 for more details:
+			// (https://github.com/migueldeicaza/gui.cs/issues/1084).
+			SynchronizationContext.SetSynchronizationContext (syncContext: null);
 		}
 		}
 
 
 		static void Redraw (View view)
 		static void Redraw (View view)
 		{
 		{
-			Application.CurrentView = view;
-
 			view.Redraw (view.Bounds);
 			view.Redraw (view.Bounds);
 			Driver.Refresh ();
 			Driver.Refresh ();
 		}
 		}
@@ -550,10 +584,8 @@ namespace Terminal.Gui {
 
 
 			if (toplevels.Count == 0) {
 			if (toplevels.Count == 0) {
 				Current = null;
 				Current = null;
-				CurrentView = null;
 			} else {
 			} else {
 				Current = toplevels.Peek ();
 				Current = toplevels.Peek ();
-				CurrentView = Current;
 				Refresh ();
 				Refresh ();
 			}
 			}
 		}
 		}
@@ -588,11 +620,11 @@ namespace Terminal.Gui {
 				} else if (!wait) {
 				} else if (!wait) {
 					return;
 					return;
 				}
 				}
-				if (state.Toplevel != Top && (!Top.NeedDisplay.IsEmpty || Top.childNeedsDisplay)) {
+				if (state.Toplevel != Top && (!Top.NeedDisplay.IsEmpty || Top.ChildNeedsDisplay || Top.LayoutNeeded)) {
 					Top.Redraw (Top.Bounds);
 					Top.Redraw (Top.Bounds);
 					state.Toplevel.SetNeedsDisplay (state.Toplevel.Bounds);
 					state.Toplevel.SetNeedsDisplay (state.Toplevel.Bounds);
 				}
 				}
-				if (!state.Toplevel.NeedDisplay.IsEmpty || state.Toplevel.childNeedsDisplay) {
+				if (!state.Toplevel.NeedDisplay.IsEmpty || state.Toplevel.ChildNeedsDisplay || state.Toplevel.LayoutNeeded) {
 					state.Toplevel.Redraw (state.Toplevel.Bounds);
 					state.Toplevel.Redraw (state.Toplevel.Bounds);
 					if (DebugDrawBounds) {
 					if (DebugDrawBounds) {
 						DrawBounds (state.Toplevel);
 						DrawBounds (state.Toplevel);
@@ -617,20 +649,20 @@ namespace Terminal.Gui {
 		}
 		}
 
 
 		/// <summary>
 		/// <summary>
-		/// Runs the application by calling <see cref="Run(Toplevel)"/> 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>
 		/// </summary>
-		public static void Run ()
+		public static void Run (Func<Exception, bool> errorHandler = null)
 		{
 		{
-			Run (Top);
+			Run (Top, errorHandler);
 		}
 		}
 
 
 		/// <summary>
 		/// <summary>
-		/// Runs the application by calling <see cref="Run(Toplevel)"/> 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
 		/// </summary>
 		/// </summary>
-		public static void Run<T> () where T : Toplevel, new()
+		public static void Run<T> (Func<Exception, bool> errorHandler = null) where T : Toplevel, new()
 		{
 		{
 			Init (() => new T ());
 			Init (() => new T ());
-			Run (Top);
+			Run (Top, errorHandler);
 		}
 		}
 
 
 		/// <summary>
 		/// <summary>
@@ -643,10 +675,10 @@ namespace Terminal.Gui {
 		///     run other modal <see cref="View"/>s such as <see cref="Dialog"/> boxes.
 		///     run other modal <see cref="View"/>s such as <see cref="Dialog"/> boxes.
 		///   </para>
 		///   </para>
 		///   <para>
 		///   <para>
-		///     To make a <see cref="Run(Toplevel)"/> stop execution, call <see cref="Application.RequestStop"/>.
+		///     To make a <see cref="Run(Toplevel, Func{Exception, bool})"/> stop execution, call <see cref="Application.RequestStop"/>.
 		///   </para>
 		///   </para>
 		///   <para>
 		///   <para>
-		///     Calling <see cref="Run(Toplevel)"/> is equivalent to calling <see cref="Begin(Toplevel)"/>, followed by <see cref="RunLoop(RunState, bool)"/>,
+		///     Calling <see cref="Run(Toplevel, Func{Exception, bool})"/> is equivalent to calling <see cref="Begin(Toplevel)"/>, followed by <see cref="RunLoop(RunState, bool)"/>,
 		///     and then calling <see cref="End(RunState)"/>.
 		///     and then calling <see cref="End(RunState)"/>.
 		///   </para>
 		///   </para>
 		///   <para>
 		///   <para>
@@ -656,13 +688,33 @@ namespace Terminal.Gui {
 		///     the <see cref="RunLoop(RunState, bool)"/> method will only process any pending events, timers, idle handlers and
 		///     the <see cref="RunLoop(RunState, bool)"/> method will only process any pending events, timers, idle handlers and
 		///     then return control immediately.
 		///     then return control immediately.
 		///   </para>
 		///   </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.
+		///   </para>
 		/// </remarks>
 		/// </remarks>
 		/// <param name="view">The <see cref="Toplevel"/> tu run modally.</param>
 		/// <param name="view">The <see cref="Toplevel"/> tu run modally.</param>
-		public static void Run (Toplevel view)
+		/// <param name="errorHandler">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 runToken = Begin (view);
-			RunLoop (runToken);
-			End (runToken);
+			var resume = true;
+			while (resume)
+			{
+				try
+				{
+					resume = false;
+					var runToken = Begin (view);
+					RunLoop (runToken);
+					End (runToken);
+				}
+				catch (Exception error)
+				{
+					if (errorHandler == null)
+					{
+						throw;
+					}
+					resume = errorHandler(error);
+				}
+			}
 		}
 		}
 
 
 		/// <summary>
 		/// <summary>
@@ -670,7 +722,7 @@ namespace Terminal.Gui {
 		/// </summary>
 		/// </summary>
 		/// <remarks>
 		/// <remarks>
 		///   <para>
 		///   <para>
-		///   This will cause <see cref="Application.Run()"/> to return.
+		///   This will cause <see cref="Application.Run(Func{Exception, bool})"/> to return.
 		///   </para>
 		///   </para>
 		///   <para>
 		///   <para>
 		///     Calling <see cref="Application.RequestStop"/> is equivalent to setting the <see cref="Toplevel.Running"/> property on the curently running <see cref="Toplevel"/> to false.
 		///     Calling <see cref="Application.RequestStop"/> is equivalent to setting the <see cref="Toplevel.Running"/> property on the curently running <see cref="Toplevel"/> to false.

+ 96 - 68
Terminal.Gui/Core/ConsoleDriver.cs

@@ -61,7 +61,7 @@ namespace Terminal.Gui {
 		/// </summary>
 		/// </summary>
 		BrightGreen,
 		BrightGreen,
 		/// <summary>
 		/// <summary>
-		/// The brigh cyan color.
+		/// The bright cyan color.
 		/// </summary>
 		/// </summary>
 		BrighCyan,
 		BrighCyan,
 		/// <summary>
 		/// <summary>
@@ -91,9 +91,18 @@ namespace Terminal.Gui {
 	///   class to define color schemes that can be used in your application.
 	///   class to define color schemes that can be used in your application.
 	/// </remarks>
 	/// </remarks>
 	public struct Attribute {
 	public struct Attribute {
-		internal int value;
-		internal Color foreground;
-		internal Color background;
+		/// <summary>
+		/// The color attribute value.
+		/// </summary>
+		public int Value { get; }
+		/// <summary>
+		/// The foreground color.
+		/// </summary>
+		public Color Foreground { get; }
+		/// <summary>
+		/// The background color.
+		/// </summary>
+		public Color Background { get; }
 
 
 		/// <summary>
 		/// <summary>
 		/// Initializes a new instance of the <see cref="Attribute"/> struct.
 		/// Initializes a new instance of the <see cref="Attribute"/> struct.
@@ -103,9 +112,9 @@ namespace Terminal.Gui {
 		/// <param name="background">Background</param>
 		/// <param name="background">Background</param>
 		public Attribute (int value, Color foreground = new Color (), Color background = new Color ())
 		public Attribute (int value, Color foreground = new Color (), Color background = new Color ())
 		{
 		{
-			this.value = value;
-			this.foreground = foreground;
-			this.background = background;
+			Value = value;
+			Foreground = foreground;
+			Background = background;
 		}
 		}
 
 
 		/// <summary>
 		/// <summary>
@@ -115,9 +124,9 @@ namespace Terminal.Gui {
 		/// <param name="background">Background</param>
 		/// <param name="background">Background</param>
 		public Attribute (Color foreground = new Color (), Color background = new Color ())
 		public Attribute (Color foreground = new Color (), Color background = new Color ())
 		{
 		{
-			this.value = value = ((int)foreground | (int)background << 4);
-			this.foreground = foreground;
-			this.background = background;
+			Value = (int)foreground | ((int)background << 4);
+			Foreground = foreground;
+			Background = background;
 		}
 		}
 
 
 		/// <summary>
 		/// <summary>
@@ -125,7 +134,7 @@ namespace Terminal.Gui {
 		/// </summary>
 		/// </summary>
 		/// <returns>The integer value stored in the attribute.</returns>
 		/// <returns>The integer value stored in the attribute.</returns>
 		/// <param name="c">The attribute to convert</param>
 		/// <param name="c">The attribute to convert</param>
-		public static implicit operator int (Attribute c) => c.value;
+		public static implicit operator int (Attribute c) => c.Value;
 
 
 		/// <summary>
 		/// <summary>
 		/// Implicitly convert an integer value into an <see cref="Attribute"/>
 		/// Implicitly convert an integer value into an <see cref="Attribute"/>
@@ -146,6 +155,17 @@ namespace Terminal.Gui {
 				throw new InvalidOperationException ("The Application has not been initialized");
 				throw new InvalidOperationException ("The Application has not been initialized");
 			return Application.Driver.MakeAttribute (foreground, background);
 			return Application.Driver.MakeAttribute (foreground, background);
 		}
 		}
+
+		/// <summary>
+		/// Gets the current <see cref="Attribute"/> from the driver.
+		/// </summary>
+		/// <returns>The current attribute.</returns>
+		public static Attribute Get ()
+		{
+			if (Application.Driver == null)
+				throw new InvalidOperationException ("The Application has not been initialized");
+			return Application.Driver.GetAttribute ();
+		}
 	}
 	}
 
 
 	/// <summary>
 	/// <summary>
@@ -201,18 +221,18 @@ namespace Terminal.Gui {
 			case "TopLevel":
 			case "TopLevel":
 				switch (callerMemberName) {
 				switch (callerMemberName) {
 				case "Normal":
 				case "Normal":
-					HotNormal = Application.Driver.MakeAttribute (HotNormal.foreground, attribute.background);
+					HotNormal = Application.Driver.MakeAttribute (HotNormal.Foreground, attribute.Background);
 					break;
 					break;
 				case "Focus":
 				case "Focus":
-					HotFocus = Application.Driver.MakeAttribute (HotFocus.foreground, attribute.background);
+					HotFocus = Application.Driver.MakeAttribute (HotFocus.Foreground, attribute.Background);
 					break;
 					break;
 				case "HotNormal":
 				case "HotNormal":
-					HotFocus = Application.Driver.MakeAttribute (attribute.foreground, HotFocus.background);
+					HotFocus = Application.Driver.MakeAttribute (attribute.Foreground, HotFocus.Background);
 					break;
 					break;
 				case "HotFocus":
 				case "HotFocus":
-					HotNormal = Application.Driver.MakeAttribute (attribute.foreground, HotNormal.background);
-					if (Focus.foreground != attribute.background)
-						Focus = Application.Driver.MakeAttribute (Focus.foreground, attribute.background);
+					HotNormal = Application.Driver.MakeAttribute (attribute.Foreground, HotNormal.Background);
+					if (Focus.Foreground != attribute.Background)
+						Focus = Application.Driver.MakeAttribute (Focus.Foreground, attribute.Background);
 					break;
 					break;
 				}
 				}
 				break;
 				break;
@@ -220,19 +240,19 @@ namespace Terminal.Gui {
 			case "Base":
 			case "Base":
 				switch (callerMemberName) {
 				switch (callerMemberName) {
 				case "Normal":
 				case "Normal":
-					HotNormal = Application.Driver.MakeAttribute (HotNormal.foreground, attribute.background);
+					HotNormal = Application.Driver.MakeAttribute (HotNormal.Foreground, attribute.Background);
 					break;
 					break;
 				case "Focus":
 				case "Focus":
-					HotFocus = Application.Driver.MakeAttribute (HotFocus.foreground, attribute.background);
+					HotFocus = Application.Driver.MakeAttribute (HotFocus.Foreground, attribute.Background);
 					break;
 					break;
 				case "HotNormal":
 				case "HotNormal":
-					HotFocus = Application.Driver.MakeAttribute (attribute.foreground, HotFocus.background);
-					Normal = Application.Driver.MakeAttribute (Normal.foreground, attribute.background);
+					HotFocus = Application.Driver.MakeAttribute (attribute.Foreground, HotFocus.Background);
+					Normal = Application.Driver.MakeAttribute (Normal.Foreground, attribute.Background);
 					break;
 					break;
 				case "HotFocus":
 				case "HotFocus":
-					HotNormal = Application.Driver.MakeAttribute (attribute.foreground, HotNormal.background);
-					if (Focus.foreground != attribute.background)
-						Focus = Application.Driver.MakeAttribute (Focus.foreground, attribute.background);
+					HotNormal = Application.Driver.MakeAttribute (attribute.Foreground, HotNormal.Background);
+					if (Focus.Foreground != attribute.Background)
+						Focus = Application.Driver.MakeAttribute (Focus.Foreground, attribute.Background);
 					break;
 					break;
 				}
 				}
 				break;
 				break;
@@ -240,57 +260,56 @@ namespace Terminal.Gui {
 			case "Menu":
 			case "Menu":
 				switch (callerMemberName) {
 				switch (callerMemberName) {
 				case "Normal":
 				case "Normal":
-					if (Focus.background != attribute.background)
-						Focus = Application.Driver.MakeAttribute (attribute.foreground, Focus.background);
-					HotNormal = Application.Driver.MakeAttribute (HotNormal.foreground, attribute.background);
-					Disabled = Application.Driver.MakeAttribute (Disabled.foreground, attribute.background);
+					if (Focus.Background != attribute.Background)
+						Focus = Application.Driver.MakeAttribute (attribute.Foreground, Focus.Background);
+					HotNormal = Application.Driver.MakeAttribute (HotNormal.Foreground, attribute.Background);
+					Disabled = Application.Driver.MakeAttribute (Disabled.Foreground, attribute.Background);
 					break;
 					break;
 				case "Focus":
 				case "Focus":
-					Normal = Application.Driver.MakeAttribute (attribute.foreground, Normal.background);
-					HotFocus = Application.Driver.MakeAttribute (HotFocus.foreground, attribute.background);
+					Normal = Application.Driver.MakeAttribute (attribute.Foreground, Normal.Background);
+					HotFocus = Application.Driver.MakeAttribute (HotFocus.Foreground, attribute.Background);
 					break;
 					break;
 				case "HotNormal":
 				case "HotNormal":
-					if (Focus.background != attribute.background)
-						HotFocus = Application.Driver.MakeAttribute (attribute.foreground, HotFocus.background);
-					Normal = Application.Driver.MakeAttribute (Normal.foreground, attribute.background);
-					Disabled = Application.Driver.MakeAttribute (Disabled.foreground, attribute.background);
+					if (Focus.Background != attribute.Background)
+						HotFocus = Application.Driver.MakeAttribute (attribute.Foreground, HotFocus.Background);
+					Normal = Application.Driver.MakeAttribute (Normal.Foreground, attribute.Background);
+					Disabled = Application.Driver.MakeAttribute (Disabled.Foreground, attribute.Background);
 					break;
 					break;
 				case "HotFocus":
 				case "HotFocus":
-					HotNormal = Application.Driver.MakeAttribute (attribute.foreground, HotNormal.background);
-					if (Focus.foreground != attribute.background)
-						Focus = Application.Driver.MakeAttribute (Focus.foreground, attribute.background);
+					HotNormal = Application.Driver.MakeAttribute (attribute.Foreground, HotNormal.Background);
+					if (Focus.Foreground != attribute.Background)
+						Focus = Application.Driver.MakeAttribute (Focus.Foreground, attribute.Background);
 					break;
 					break;
 				case "Disabled":
 				case "Disabled":
-					if (Focus.background != attribute.background)
-						HotFocus = Application.Driver.MakeAttribute (attribute.foreground, HotFocus.background);
-					Normal = Application.Driver.MakeAttribute (Normal.foreground, attribute.background);
-					HotNormal = Application.Driver.MakeAttribute (HotNormal.foreground, attribute.background);
+					if (Focus.Background != attribute.Background)
+						HotFocus = Application.Driver.MakeAttribute (attribute.Foreground, HotFocus.Background);
+					Normal = Application.Driver.MakeAttribute (Normal.Foreground, attribute.Background);
+					HotNormal = Application.Driver.MakeAttribute (HotNormal.Foreground, attribute.Background);
 					break;
 					break;
-
 				}
 				}
 				break;
 				break;
 
 
 			case "Dialog":
 			case "Dialog":
 				switch (callerMemberName) {
 				switch (callerMemberName) {
 				case "Normal":
 				case "Normal":
-					if (Focus.background != attribute.background)
-						Focus = Application.Driver.MakeAttribute (attribute.foreground, Focus.background);
-					HotNormal = Application.Driver.MakeAttribute (HotNormal.foreground, attribute.background);
+					if (Focus.Background != attribute.Background)
+						Focus = Application.Driver.MakeAttribute (attribute.Foreground, Focus.Background);
+					HotNormal = Application.Driver.MakeAttribute (HotNormal.Foreground, attribute.Background);
 					break;
 					break;
 				case "Focus":
 				case "Focus":
-					Normal = Application.Driver.MakeAttribute (attribute.foreground, Normal.background);
-					HotFocus = Application.Driver.MakeAttribute (HotFocus.foreground, attribute.background);
+					Normal = Application.Driver.MakeAttribute (attribute.Foreground, Normal.Background);
+					HotFocus = Application.Driver.MakeAttribute (HotFocus.Foreground, attribute.Background);
 					break;
 					break;
 				case "HotNormal":
 				case "HotNormal":
-					if (Focus.background != attribute.background)
-						HotFocus = Application.Driver.MakeAttribute (attribute.foreground, HotFocus.background);
-					if (Normal.foreground != attribute.background)
-						Normal = Application.Driver.MakeAttribute (Normal.foreground, attribute.background);
+					if (Focus.Background != attribute.Background)
+						HotFocus = Application.Driver.MakeAttribute (attribute.Foreground, HotFocus.Background);
+					if (Normal.Foreground != attribute.Background)
+						Normal = Application.Driver.MakeAttribute (Normal.Foreground, attribute.Background);
 					break;
 					break;
 				case "HotFocus":
 				case "HotFocus":
-					HotNormal = Application.Driver.MakeAttribute (attribute.foreground, HotNormal.background);
-					if (Focus.foreground != attribute.background)
-						Focus = Application.Driver.MakeAttribute (Focus.foreground, attribute.background);
+					HotNormal = Application.Driver.MakeAttribute (attribute.Foreground, HotNormal.Background);
+					if (Focus.Foreground != attribute.Background)
+						Focus = Application.Driver.MakeAttribute (Focus.Foreground, attribute.Background);
 					break;
 					break;
 				}
 				}
 				break;
 				break;
@@ -298,17 +317,16 @@ namespace Terminal.Gui {
 			case "Error":
 			case "Error":
 				switch (callerMemberName) {
 				switch (callerMemberName) {
 				case "Normal":
 				case "Normal":
-					HotNormal = Application.Driver.MakeAttribute (HotNormal.foreground, attribute.background);
-					HotFocus = Application.Driver.MakeAttribute (HotFocus.foreground, attribute.background);
+					HotNormal = Application.Driver.MakeAttribute (HotNormal.Foreground, attribute.Background);
+					HotFocus = Application.Driver.MakeAttribute (HotFocus.Foreground, attribute.Background);
 					break;
 					break;
 				case "HotNormal":
 				case "HotNormal":
 				case "HotFocus":
 				case "HotFocus":
-					HotFocus = Application.Driver.MakeAttribute (attribute.foreground, attribute.background);
-					Normal = Application.Driver.MakeAttribute (Normal.foreground, attribute.background);
+					HotFocus = Application.Driver.MakeAttribute (attribute.Foreground, attribute.Background);
+					Normal = Application.Driver.MakeAttribute (Normal.Foreground, attribute.Background);
 					break;
 					break;
 				}
 				}
 				break;
 				break;
-
 			}
 			}
 			preparingScheme = false;
 			preparingScheme = false;
 			return attribute;
 			return attribute;
@@ -383,7 +401,7 @@ namespace Terminal.Gui {
 	public static class Colors {
 	public static class Colors {
 		static Colors ()
 		static Colors ()
 		{
 		{
-			// Use reflection to dynamically create the default set of ColorSchemes from the list defiined 
+			// Use reflection to dynamically create the default set of ColorSchemes from the list defined 
 			// by the class. 
 			// by the class. 
 			ColorSchemes = typeof (Colors).GetProperties ()
 			ColorSchemes = typeof (Colors).GetProperties ()
 				.Where (p => p.PropertyType == typeof (ColorScheme))
 				.Where (p => p.PropertyType == typeof (ColorScheme))
@@ -546,6 +564,12 @@ namespace Terminal.Gui {
 		/// </summary>
 		/// </summary>
 		public abstract int Top { get; }
 		public abstract int Top { get; }
 
 
+		/// <summary>
+		/// If false height is measured by the window height and thus no scrolling.
+		/// If true then height is measured by the buffer height, enabling scrolling.
+		/// </summary>
+		public abstract bool HeightAsBuffer { get; set; }
+
 		/// <summary>
 		/// <summary>
 		/// Initializes the driver
 		/// Initializes the driver
 		/// </summary>
 		/// </summary>
@@ -570,12 +594,10 @@ namespace Terminal.Gui {
 		/// <returns></returns>
 		/// <returns></returns>
 		public static Rune MakePrintable (Rune c)
 		public static Rune MakePrintable (Rune c)
 		{
 		{
-			if (c <= 0x1F) {
-				// ASCII (C0) control characters. 
-				return new Rune (c + 0x2400);
-			} else if (c >= 0x80 && c <= 0x9F) {
+			if (c <= 0x1F || (c >= 0x80 && c <= 0x9F)) {
+				// ASCII (C0) control characters.
 				// C1 control characters (https://www.aivosto.com/articles/control-characters.html#c1)
 				// C1 control characters (https://www.aivosto.com/articles/control-characters.html#c1)
-				return new Rune (0x25a1); // U+25A1, WHITE SQUARE, □: 
+				return new Rune (c + 0x2400);
 			} else {
 			} else {
 				return c;
 				return c;
 			}
 			}
@@ -657,7 +679,7 @@ namespace Terminal.Gui {
 		/// <param name="paddingTop">Number of rows to pad on the top (if 0 the border and title will not appear on the top).</param>
 		/// <param name="paddingTop">Number of rows to pad on the top (if 0 the border and title will not appear on the top).</param>
 		/// <param name="paddingRight">Number of columns to pad on the right (if 0 the border will not appear on the right).</param>
 		/// <param name="paddingRight">Number of columns to pad on the right (if 0 the border will not appear on the right).</param>
 		/// <param name="paddingBottom">Number of rows to pad on the bottom (if 0 the border will not appear on the bottom).</param>
 		/// <param name="paddingBottom">Number of rows to pad on the bottom (if 0 the border will not appear on the bottom).</param>
-		/// <param name="textAlignment">Not yet immplemented.</param>
+		/// <param name="textAlignment">Not yet implemented.</param>
 		/// <remarks></remarks>
 		/// <remarks></remarks>
 		public virtual void DrawWindowTitle (Rect region, ustring title, int paddingLeft, int paddingTop, int paddingRight, int paddingBottom, TextAlignment textAlignment = TextAlignment.Left)
 		public virtual void DrawWindowTitle (Rect region, ustring title, int paddingLeft, int paddingTop, int paddingRight, int paddingBottom, TextAlignment textAlignment = TextAlignment.Left)
 		{
 		{
@@ -672,7 +694,7 @@ namespace Terminal.Gui {
 		}
 		}
 
 
 		/// <summary>
 		/// <summary>
-		/// Enables diagnostic funcions
+		/// Enables diagnostic functions
 		/// </summary>
 		/// </summary>
 		[Flags]
 		[Flags]
 		public enum DiagnosticFlags : uint {
 		public enum DiagnosticFlags : uint {
@@ -863,7 +885,7 @@ namespace Terminal.Gui {
 		/// <param name="region">Screen relative region where the frame will be drawn.</param>
 		/// <param name="region">Screen relative region where the frame will be drawn.</param>
 		/// <param name="padding">Padding to add on the sides.</param>
 		/// <param name="padding">Padding to add on the sides.</param>
 		/// <param name="fill">If set to <c>true</c> it will clear the contents with the current color, otherwise the contents will be left untouched.</param>
 		/// <param name="fill">If set to <c>true</c> it will clear the contents with the current color, otherwise the contents will be left untouched.</param>
-		/// <remarks>This API has been superceded by <see cref="DrawWindowFrame(Rect, int, int, int, int, bool, bool)"/>.</remarks>
+		/// <remarks>This API has been superseded by <see cref="DrawWindowFrame(Rect, int, int, int, int, bool, bool)"/>.</remarks>
 		/// <remarks>This API is equivalent to calling <c>DrawWindowFrame(Rect, p - 1, p - 1, p - 1, p - 1)</c>. In other words,
 		/// <remarks>This API is equivalent to calling <c>DrawWindowFrame(Rect, p - 1, p - 1, p - 1, p - 1)</c>. In other words,
 		/// A padding value of 0 means there is actually a one cell border.
 		/// A padding value of 0 means there is actually a one cell border.
 		/// </remarks>
 		/// </remarks>
@@ -1049,5 +1071,11 @@ namespace Terminal.Gui {
 		/// <param name="back">Background.</param>
 		/// <param name="back">Background.</param>
 		/// <returns></returns>
 		/// <returns></returns>
 		public abstract Attribute MakeAttribute (Color fore, Color back);
 		public abstract Attribute MakeAttribute (Color fore, Color back);
+
+		/// <summary>
+		/// Gets the current <see cref="Attribute"/>.
+		/// </summary>
+		/// <returns>The current attribute.</returns>
+		public abstract Attribute GetAttribute ();
 	}
 	}
 }
 }

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

@@ -392,7 +392,7 @@ namespace Terminal.Gui {
 		/// Gets a value indicating whether the Shift key was pressed.
 		/// Gets a value indicating whether the Shift key was pressed.
 		/// </summary>
 		/// </summary>
 		/// <value><c>true</c> if is shift; otherwise, <c>false</c>.</value>
 		/// <value><c>true</c> if is shift; otherwise, <c>false</c>.</value>
-		public bool IsShift => keyModifiers.Shift;
+		public bool IsShift => keyModifiers.Shift || Key == Key.BackTab;
 
 
 		/// <summary>
 		/// <summary>
 		/// Gets a value indicating whether the Alt key was pressed (real or synthesized)
 		/// Gets a value indicating whether the Alt key was pressed (real or synthesized)

+ 51 - 34
Terminal.Gui/Core/Toplevel.cs

@@ -15,7 +15,7 @@ namespace Terminal.Gui {
 	/// </summary>
 	/// </summary>
 	/// <remarks>
 	/// <remarks>
 	///   <para>
 	///   <para>
-	///     Toplevels can be modally executing views, started by calling <see cref="Application.Run(Toplevel)"/>. 
+	///     Toplevels can be modally executing views, started by calling <see cref="Application.Run(Toplevel, Func{Exception, bool})"/>. 
 	///     They return control to the caller when <see cref="Application.RequestStop()"/> has 
 	///     They return control to the caller when <see cref="Application.RequestStop()"/> has 
 	///     been called (which sets the <see cref="Toplevel.Running"/> property to false). 
 	///     been called (which sets the <see cref="Toplevel.Running"/> property to false). 
 	///   </para>
 	///   </para>
@@ -23,7 +23,7 @@ namespace Terminal.Gui {
 	///     A Toplevel is created when an application initialzies Terminal.Gui by callling <see cref="Application.Init(ConsoleDriver, IMainLoopDriver)"/>.
 	///     A Toplevel is created when an application initialzies Terminal.Gui by callling <see cref="Application.Init(ConsoleDriver, IMainLoopDriver)"/>.
 	///     The application Toplevel can be accessed via <see cref="Application.Top"/>. Additional Toplevels can be created 
 	///     The application Toplevel can be accessed via <see cref="Application.Top"/>. Additional Toplevels can be created 
 	///     and run (e.g. <see cref="Dialog"/>s. To run a Toplevel, create the <see cref="Toplevel"/> and 
 	///     and run (e.g. <see cref="Dialog"/>s. To run a Toplevel, create the <see cref="Toplevel"/> and 
-	///     call <see cref="Application.Run(Toplevel)"/>.
+	///     call <see cref="Application.Run(Toplevel, Func{Exception, bool})"/>.
 	///   </para>
 	///   </para>
 	///   <para>
 	///   <para>
 	///     Toplevels can also opt-in to more sophisticated initialization
 	///     Toplevels can also opt-in to more sophisticated initialization
@@ -57,7 +57,7 @@ namespace Terminal.Gui {
 		/// <summary>
 		/// <summary>
 		/// Fired once the Toplevel's <see cref="MainLoop"/> has started it's first iteration.
 		/// Fired once the Toplevel's <see cref="MainLoop"/> has started it's first iteration.
 		/// Subscribe to this event to perform tasks when the <see cref="Toplevel"/> has been laid out and focus has been set.
 		/// Subscribe to this event to perform tasks when the <see cref="Toplevel"/> has been laid out and focus has been set.
-		/// changes. A Ready event handler is a good place to finalize initialization after calling `<see cref="Application.Run()"/>(topLevel)`.
+		/// changes. A Ready event handler is a good place to finalize initialization after calling `<see cref="Application.Run(Func{Exception, bool})"/>(topLevel)`.
 		/// </summary>
 		/// </summary>
 		public event Action Ready;
 		public event Action Ready;
 
 
@@ -158,6 +158,8 @@ namespace Terminal.Gui {
 
 
 			switch (keyEvent.Key) {
 			switch (keyEvent.Key) {
 			case Key.AltMask:
 			case Key.AltMask:
+			case Key.AltMask | Key.Space:
+			case Key.CtrlMask | Key.Space:
 				if (MenuBar != null && MenuBar.OnKeyDown (keyEvent)) {
 				if (MenuBar != null && MenuBar.OnKeyDown (keyEvent)) {
 					return true;
 					return true;
 				}
 				}
@@ -176,6 +178,8 @@ namespace Terminal.Gui {
 
 
 			switch (keyEvent.Key) {
 			switch (keyEvent.Key) {
 			case Key.AltMask:
 			case Key.AltMask:
+			case Key.AltMask | Key.Space:
+			case Key.CtrlMask | Key.Space:
 				if (MenuBar != null && MenuBar.OnKeyUp (keyEvent)) {
 				if (MenuBar != null && MenuBar.OnKeyUp (keyEvent)) {
 					return true;
 					return true;
 				}
 				}
@@ -191,7 +195,7 @@ namespace Terminal.Gui {
 			if (base.ProcessKey (keyEvent))
 			if (base.ProcessKey (keyEvent))
 				return true;
 				return true;
 
 
-			switch (keyEvent.Key) {
+			switch (ShortcutHelper.GetModifiersKey (keyEvent)) {
 			case Key.Q | Key.CtrlMask:
 			case Key.Q | Key.CtrlMask:
 				// FIXED: stop current execution of this container
 				// FIXED: stop current execution of this container
 				Application.RequestStop ();
 				Application.RequestStop ();
@@ -213,27 +217,32 @@ namespace Terminal.Gui {
 				var old = GetDeepestFocusedSubview (Focused);
 				var old = GetDeepestFocusedSubview (Focused);
 				if (!FocusNext ())
 				if (!FocusNext ())
 					FocusNext ();
 					FocusNext ();
-				if (old != Focused) {
+				if (old != Focused && old != Focused?.Focused) {
 					old?.SetNeedsDisplay ();
 					old?.SetNeedsDisplay ();
 					Focused?.SetNeedsDisplay ();
 					Focused?.SetNeedsDisplay ();
 				} else {
 				} else {
-					FocusNearestView (GetToplevelSubviews (true));
+					FocusNearestView (SuperView?.TabIndexes, Direction.Forward);
 				}
 				}
 				return true;
 				return true;
+			case Key.BackTab | Key.ShiftMask:
 			case Key.CursorLeft:
 			case Key.CursorLeft:
 			case Key.CursorUp:
 			case Key.CursorUp:
-			case Key.BackTab:
 				old = GetDeepestFocusedSubview (Focused);
 				old = GetDeepestFocusedSubview (Focused);
 				if (!FocusPrev ())
 				if (!FocusPrev ())
 					FocusPrev ();
 					FocusPrev ();
-				if (old != Focused) {
+				if (old != Focused && old != Focused?.Focused) {
 					old?.SetNeedsDisplay ();
 					old?.SetNeedsDisplay ();
 					Focused?.SetNeedsDisplay ();
 					Focused?.SetNeedsDisplay ();
 				} else {
 				} else {
-					FocusNearestView (GetToplevelSubviews (false));
+					FocusNearestView (SuperView?.TabIndexes?.Reverse(), Direction.Backward);
 				}
 				}
 				return true;
 				return true;
-
+			case Key.Tab | Key.CtrlMask:
+				Application.Top.FocusNext ();
+				return true;
+			case Key.Tab | Key.ShiftMask | Key.CtrlMask:
+				Application.Top.FocusPrev ();
+				return true;
 			case Key.L | Key.CtrlMask:
 			case Key.L | Key.CtrlMask:
 				Application.Refresh ();
 				Application.Refresh ();
 				return true;
 				return true;
@@ -268,39 +277,34 @@ namespace Terminal.Gui {
 			return view;
 			return view;
 		}
 		}
 
 
-		IEnumerable<View> GetToplevelSubviews (bool isForward)
-		{
-			if (SuperView == null) {
-				return null;
-			}
-
-			HashSet<View> views = new HashSet<View> ();
-
-			foreach (var v in SuperView.Subviews) {
-				views.Add (v);
-			}
-
-			return isForward ? views : views.Reverse ();
-		}
-
-		void FocusNearestView (IEnumerable<View> views)
+		void FocusNearestView (IEnumerable<View> views, Direction direction)
 		{
 		{
 			if (views == null) {
 			if (views == null) {
 				return;
 				return;
 			}
 			}
 
 
 			bool found = false;
 			bool found = false;
+			bool focusProcessed = false;
+			int idx = 0;
 
 
 			foreach (var v in views) {
 			foreach (var v in views) {
 				if (v == this) {
 				if (v == this) {
 					found = true;
 					found = true;
 				}
 				}
 				if (found && v != this) {
 				if (found && v != this) {
-					v.EnsureFocus ();
+					if (direction == Direction.Forward) {
+						SuperView?.FocusNext ();
+					} else {
+						SuperView?.FocusPrev ();
+					}
+					focusProcessed = true;
 					if (SuperView.Focused != null && SuperView.Focused != this) {
 					if (SuperView.Focused != null && SuperView.Focused != this) {
 						return;
 						return;
 					}
 					}
+				} else if (found && !focusProcessed && idx == views.Count () - 1) {
+					views.ToList () [0].SetFocus ();
 				}
 				}
+				idx++;
 			}
 			}
 		}
 		}
 
 
@@ -359,14 +363,24 @@ namespace Terminal.Gui {
 		internal void EnsureVisibleBounds (Toplevel top, int x, int y, out int nx, out int ny)
 		internal void EnsureVisibleBounds (Toplevel top, int x, int y, out int nx, out int ny)
 		{
 		{
 			nx = Math.Max (x, 0);
 			nx = Math.Max (x, 0);
-			nx = nx + top.Frame.Width > Driver.Cols ? Math.Max (Driver.Cols - top.Frame.Width, 0) : nx;
+			int l;
+			if (SuperView == null || SuperView is Toplevel) {
+				l = Driver.Cols;
+			} else {
+				l = SuperView.Frame.Width;
+			}
+			nx = nx + top.Frame.Width > l ? Math.Max (l - top.Frame.Width, 0) : nx;
+			SetWidth (top.Frame.Width, out int rWidth);
+			if (rWidth < 0 && nx >= top.Frame.X) {
+				nx = Math.Max (top.Frame.Right - 2, 0);
+			}
+			//System.Diagnostics.Debug.WriteLine ($"nx:{nx}, rWidth:{rWidth}");
 			bool m, s;
 			bool m, s;
 			if (SuperView == null || SuperView.GetType () != typeof (Toplevel)) {
 			if (SuperView == null || SuperView.GetType () != typeof (Toplevel)) {
 				m = Application.Top.MenuBar != null;
 				m = Application.Top.MenuBar != null;
 			} else {
 			} else {
 				m = ((Toplevel)SuperView).MenuBar != null;
 				m = ((Toplevel)SuperView).MenuBar != null;
 			}
 			}
-			int l;
 			if (SuperView == null || SuperView is Toplevel) {
 			if (SuperView == null || SuperView is Toplevel) {
 				l = m ? 1 : 0;
 				l = m ? 1 : 0;
 			} else {
 			} else {
@@ -385,6 +399,11 @@ namespace Terminal.Gui {
 			}
 			}
 			ny = Math.Min (ny, l);
 			ny = Math.Min (ny, l);
 			ny = ny + top.Frame.Height > l ? Math.Max (l - top.Frame.Height, m ? 1 : 0) : ny;
 			ny = ny + top.Frame.Height > l ? Math.Max (l - top.Frame.Height, m ? 1 : 0) : ny;
+			SetHeight (top.Frame.Height, out int rHeight);
+			if (rHeight < 0 && ny >= top.Frame.Y) {
+				ny = Math.Max (top.Frame.Bottom - 2, 0);
+			}
+			//System.Diagnostics.Debug.WriteLine ($"ny:{ny}, rHeight:{rHeight}");
 		}
 		}
 
 
 		internal void PositionToplevels ()
 		internal void PositionToplevels ()
@@ -401,10 +420,10 @@ namespace Terminal.Gui {
 		{
 		{
 			EnsureVisibleBounds (top, top.Frame.X, top.Frame.Y, out int nx, out int ny);
 			EnsureVisibleBounds (top, top.Frame.X, top.Frame.Y, out int nx, out int ny);
 			if ((nx != top.Frame.X || ny != top.Frame.Y) && top.LayoutStyle == LayoutStyle.Computed) {
 			if ((nx != top.Frame.X || ny != top.Frame.Y) && top.LayoutStyle == LayoutStyle.Computed) {
-				if (top.X is Pos.PosAbsolute && top.Bounds.X != nx) {
+				if ((top.X == null || top.X is Pos.PosAbsolute) && top.Bounds.X != nx) {
 					top.X = nx;
 					top.X = nx;
 				}
 				}
-				if (top.Y is Pos.PosAbsolute && top.Bounds.Y != ny) {
+				if ((top.Y == null || top.Y is Pos.PosAbsolute) && top.Bounds.Y != ny) {
 					top.Y = ny;
 					top.Y = ny;
 				}
 				}
 			}
 			}
@@ -424,8 +443,6 @@ namespace Terminal.Gui {
 		///<inheritdoc/>
 		///<inheritdoc/>
 		public override void Redraw (Rect bounds)
 		public override void Redraw (Rect bounds)
 		{
 		{
-			Application.CurrentView = this;
-
 			if (IsCurrentTop || this == Application.Top) {
 			if (IsCurrentTop || this == Application.Top) {
 				if (!NeedDisplay.IsEmpty || LayoutNeeded) {
 				if (!NeedDisplay.IsEmpty || LayoutNeeded) {
 					Driver.SetAttribute (Colors.TopLevel.Normal);
 					Driver.SetAttribute (Colors.TopLevel.Normal);
@@ -452,7 +469,7 @@ namespace Terminal.Gui {
 		}
 		}
 
 
 		/// <summary>
 		/// <summary>
-		/// Invoked by <see cref="Application.Begin"/> as part of the <see cref="Application.Run(Toplevel)"/> after
+		/// Invoked by <see cref="Application.Begin"/> as part of the <see cref="Application.Run(Toplevel, Func{Exception, bool})"/> after
 		/// the views have been laid out, and before the views are drawn for the first time.
 		/// the views have been laid out, and before the views are drawn for the first time.
 		/// </summary>
 		/// </summary>
 		public virtual void WillPresent ()
 		public virtual void WillPresent ()

+ 80 - 17
Terminal.Gui/Core/View.cs

@@ -721,7 +721,7 @@ namespace Terminal.Gui {
 				NeedDisplay = new Rect (x, y, w, h);
 				NeedDisplay = new Rect (x, y, w, h);
 			}
 			}
 			if (container != null)
 			if (container != null)
-				container.ChildNeedsDisplay ();
+				container.SetChildNeedsDisplay ();
 			if (subviews == null)
 			if (subviews == null)
 				return;
 				return;
 			foreach (var view in subviews)
 			foreach (var view in subviews)
@@ -733,16 +733,16 @@ namespace Terminal.Gui {
 				}
 				}
 		}
 		}
 
 
-		internal bool childNeedsDisplay;
+		internal bool ChildNeedsDisplay { get; private set; }
 
 
 		/// <summary>
 		/// <summary>
 		/// Indicates that any child views (in the <see cref="Subviews"/> list) need to be repainted.
 		/// Indicates that any child views (in the <see cref="Subviews"/> list) need to be repainted.
 		/// </summary>
 		/// </summary>
-		public void ChildNeedsDisplay ()
+		public void SetChildNeedsDisplay ()
 		{
 		{
-			childNeedsDisplay = true;
+			ChildNeedsDisplay = true;
 			if (container != null)
 			if (container != null)
-				container.ChildNeedsDisplay ();
+				container.SetChildNeedsDisplay ();
 		}
 		}
 
 
 		internal bool addingView = false;
 		internal bool addingView = false;
@@ -1125,10 +1125,10 @@ namespace Terminal.Gui {
 				return;
 				return;
 			}
 			}
 
 
-			if (focused != null) {
+			if (focused?.Frame.Width > 0 && focused.Frame.Height > 0) {
 				focused.PositionCursor ();
 				focused.PositionCursor ();
 			} else {
 			} else {
-				if (CanFocus && HasFocus && Visible) {
+				if (CanFocus && HasFocus && Visible && Frame.Width > 0 && Frame.Height > 0) {
 					Move (textFormatter.HotKeyPos == -1 ? 0 : textFormatter.CursorPosition, 0);
 					Move (textFormatter.HotKeyPos == -1 ? 0 : textFormatter.CursorPosition, 0);
 				} else {
 				} else {
 					Move (frame.X, frame.Y);
 					Move (frame.X, frame.Y);
@@ -1288,7 +1288,7 @@ namespace Terminal.Gui {
 		protected void ClearNeedsDisplay ()
 		protected void ClearNeedsDisplay ()
 		{
 		{
 			NeedDisplay = Rect.Empty;
 			NeedDisplay = Rect.Empty;
-			childNeedsDisplay = false;
+			ChildNeedsDisplay = false;
 		}
 		}
 
 
 		/// <summary>
 		/// <summary>
@@ -1316,8 +1316,9 @@ namespace Terminal.Gui {
 
 
 			var clipRect = new Rect (Point.Empty, frame.Size);
 			var clipRect = new Rect (Point.Empty, frame.Size);
 
 
-			if (ColorScheme != null)
+			if (ColorScheme != null) {
 				Driver.SetAttribute (HasFocus ? ColorScheme.Focus : ColorScheme.Normal);
 				Driver.SetAttribute (HasFocus ? ColorScheme.Focus : ColorScheme.Normal);
+			}
 
 
 			if (!ustring.IsNullOrEmpty (Text)) {
 			if (!ustring.IsNullOrEmpty (Text)) {
 				Clear ();
 				Clear ();
@@ -1333,23 +1334,24 @@ namespace Terminal.Gui {
 
 
 			if (subviews != null) {
 			if (subviews != null) {
 				foreach (var view in subviews) {
 				foreach (var view in subviews) {
-					if (!view.NeedDisplay.IsEmpty || view.childNeedsDisplay) {
+					if (!view.NeedDisplay.IsEmpty || view.ChildNeedsDisplay || view.LayoutNeeded) {
 						if (view.Frame.IntersectsWith (clipRect) && (view.Frame.IntersectsWith (bounds) || bounds.X < 0 || bounds.Y < 0)) {
 						if (view.Frame.IntersectsWith (clipRect) && (view.Frame.IntersectsWith (bounds) || bounds.X < 0 || bounds.Y < 0)) {
 							if (view.LayoutNeeded)
 							if (view.LayoutNeeded)
 								view.LayoutSubviews ();
 								view.LayoutSubviews ();
-							Application.CurrentView = view;
 
 
 							// Draw the subview
 							// Draw the subview
-							// Use the view's bounds (view-relative; Location will always be (0,0) because
-							if (view.Visible) {
+							// Use the view's bounds (view-relative; Location will always be (0,0)
+							if (view.Visible && view.Frame.Width > 0 && view.Frame.Height > 0) {
+								view.OnDrawContent (view.Bounds);
 								view.Redraw (view.Bounds);
 								view.Redraw (view.Bounds);
 							}
 							}
 						}
 						}
 						view.NeedDisplay = Rect.Empty;
 						view.NeedDisplay = Rect.Empty;
-						view.childNeedsDisplay = false;
+						view.ChildNeedsDisplay = false;
 					}
 					}
 				}
 				}
 			}
 			}
+			ClearLayoutNeeded ();
 			ClearNeedsDisplay ();
 			ClearNeedsDisplay ();
 		}
 		}
 
 
@@ -1965,6 +1967,7 @@ namespace Terminal.Gui {
 
 
 		bool ResizeView (bool autoSize)
 		bool ResizeView (bool autoSize)
 		{
 		{
+			var aSize = autoSize;
 			if (textFormatter.Size != Bounds.Size && (((width == null || width is Dim.DimAbsolute) && (Bounds.Width == 0
 			if (textFormatter.Size != Bounds.Size && (((width == null || width is Dim.DimAbsolute) && (Bounds.Width == 0
 				|| autoSize && Bounds.Width != textFormatter.Size.Width))
 				|| autoSize && Bounds.Width != textFormatter.Size.Width))
 				|| ((height == null || height is Dim.DimAbsolute) && (Bounds.Height == 0
 				|| ((height == null || height is Dim.DimAbsolute) && (Bounds.Height == 0
@@ -1975,17 +1978,17 @@ namespace Terminal.Gui {
 				} else if (width is Dim.DimAbsolute) {
 				} else if (width is Dim.DimAbsolute) {
 					width = Math.Max (Bounds.Width, height.Anchor (Bounds.Width));
 					width = Math.Max (Bounds.Width, height.Anchor (Bounds.Width));
 				} else {
 				} else {
-					return false;
+					aSize = false;
 				}
 				}
 				if (height == null) {
 				if (height == null) {
 					height = Bounds.Height;
 					height = Bounds.Height;
 				} else if (height is Dim.DimAbsolute) {
 				} else if (height is Dim.DimAbsolute) {
 					height = Math.Max (Bounds.Height, height.Anchor (Bounds.Height));
 					height = Math.Max (Bounds.Height, height.Anchor (Bounds.Height));
 				} else {
 				} else {
-					return false;
+					aSize = false;
 				}
 				}
 			}
 			}
-			return autoSize;
+			return aSize;
 		}
 		}
 
 
 		/// <summary>
 		/// <summary>
@@ -2140,5 +2143,65 @@ namespace Terminal.Gui {
 
 
 			return true;
 			return true;
 		}
 		}
+
+		/// <summary>
+		/// Calculate the width based on the <see cref="Width"/> settings.
+		/// </summary>
+		/// <param name="desiredWidth">The desired width.</param>
+		/// <param name="resultWidth">The real result width.</param>
+		/// <returns>True if the width can be directly assigned, false otherwise.</returns>
+		public bool SetWidth (int desiredWidth, out int resultWidth)
+		{
+			int w = desiredWidth;
+			bool canSetWidth;
+			if (Width is Dim.DimCombine || Width is Dim.DimView || Width is Dim.DimFill) {
+				// It's a Dim.DimCombine and so can't be assigned. Let it have it's width anchored.
+				w = Width.Anchor (w);
+				canSetWidth = false;
+			} else if (Width is Dim.DimFactor factor) {
+				// Tries to get the SuperView width otherwise the view width.
+				var sw = SuperView != null ? SuperView.Frame.Width : w;
+				if (factor.IsFromRemaining ()) {
+					sw -= Frame.X;
+				}
+				w = Width.Anchor (sw);
+				canSetWidth = false;
+			} else {
+				canSetWidth = true;
+			}
+			resultWidth = w;
+
+			return canSetWidth;
+		}
+
+		/// <summary>
+		/// Calculate the height based on the <see cref="Height"/> settings.
+		/// </summary>
+		/// <param name="desiredHeight">The desired height.</param>
+		/// <param name="resultHeight">The real result height.</param>
+		/// <returns>True if the height can be directly assigned, false otherwise.</returns>
+		public bool SetHeight (int desiredHeight, out int resultHeight)
+		{
+			int h = desiredHeight;
+			bool canSetHeight;
+			if (Height is Dim.DimCombine || Height is Dim.DimView || Height is Dim.DimFill) {
+				// It's a Dim.DimCombine and so can't be assigned. Let it have it's height anchored.
+				h = Height.Anchor (h);
+				canSetHeight = false;
+			} else if (Height is Dim.DimFactor factor) {
+				// Tries to get the SuperView height otherwise the view height.
+				var sh = SuperView != null ? SuperView.Frame.Height : h;
+				if (factor.IsFromRemaining ()) {
+					sh -= Frame.Y;
+				}
+				h = Height.Anchor (sh);
+				canSetHeight = false;
+			} else {
+				canSetHeight = true;
+			}
+			resultHeight = h;
+
+			return canSetHeight;
+		}
 	}
 	}
 }
 }

+ 17 - 6
Terminal.Gui/Core/Window.cs

@@ -169,7 +169,6 @@ namespace Terminal.Gui {
 		public override void Redraw (Rect bounds)
 		public override void Redraw (Rect bounds)
 		{
 		{
 			//var padding = 0;
 			//var padding = 0;
-			Application.CurrentView = this;
 			var scrRect = ViewToScreen (new Rect (0, 0, Frame.Width, Frame.Height));
 			var scrRect = ViewToScreen (new Rect (0, 0, Frame.Width, Frame.Height));
 
 
 			// BUGBUG: Why do we draw the frame twice? This call is here to clear the content area, I think. Why not just clear that area?
 			// BUGBUG: Why do we draw the frame twice? This call is here to clear the content area, I think. Why not just clear that area?
@@ -185,6 +184,7 @@ namespace Terminal.Gui {
 			contentView.Redraw (contentView.Bounds);
 			contentView.Redraw (contentView.Bounds);
 			Driver.Clip = savedClip;
 			Driver.Clip = savedClip;
 
 
+			ClearLayoutNeeded ();
 			ClearNeedsDisplay ();
 			ClearNeedsDisplay ();
 			Driver.SetAttribute (ColorScheme.Normal);
 			Driver.SetAttribute (ColorScheme.Normal);
 			Driver.DrawWindowFrame (scrRect, padding + 1, padding + 1, padding + 1, padding + 1, border: true, fill: false);
 			Driver.DrawWindowFrame (scrRect, padding + 1, padding + 1, padding + 1, padding + 1, border: true, fill: false);
@@ -193,6 +193,12 @@ namespace Terminal.Gui {
 				Driver.SetAttribute (ColorScheme.HotNormal);
 				Driver.SetAttribute (ColorScheme.HotNormal);
 			Driver.DrawWindowTitle (scrRect, Title, padding, padding, padding, padding);
 			Driver.DrawWindowTitle (scrRect, Title, padding, padding, padding, padding);
 			Driver.SetAttribute (ColorScheme.Normal);
 			Driver.SetAttribute (ColorScheme.Normal);
+
+			// Checks if there are any SuperView view which intersect with this window.
+			if (SuperView != null) {
+				SuperView.SetNeedsLayout ();
+				SuperView.SetNeedsDisplay ();
+			}
 		}
 		}
 
 
 		//
 		//
@@ -221,7 +227,7 @@ namespace Terminal.Gui {
 					Application.GrabMouse (this);
 					Application.GrabMouse (this);
 				}
 				}
 
 
-				//Demo.ml2.Text = $"Starting at {dragPosition}";
+				//System.Diagnostics.Debug.WriteLine ($"Starting at {dragPosition}");
 				return true;
 				return true;
 			} else if (mouseEvent.Flags == (MouseFlags.Button1Pressed | MouseFlags.ReportMousePosition) ||
 			} else if (mouseEvent.Flags == (MouseFlags.Button1Pressed | MouseFlags.ReportMousePosition) ||
 				mouseEvent.Flags == MouseFlags.Button3Pressed) {
 				mouseEvent.Flags == MouseFlags.Button3Pressed) {
@@ -239,10 +245,15 @@ namespace Terminal.Gui {
 						mouseEvent.Y + (SuperView == null ? mouseEvent.OfY : Frame.Y), out nx, out ny);
 						mouseEvent.Y + (SuperView == null ? mouseEvent.OfY : Frame.Y), out nx, out ny);
 
 
 					dragPosition = new Point (nx, ny);
 					dragPosition = new Point (nx, ny);
+					LayoutSubviews ();
 					Frame = new Rect (nx, ny, Frame.Width, Frame.Height);
 					Frame = new Rect (nx, ny, Frame.Width, Frame.Height);
-					X = nx;
-					Y = ny;
-					//Demo.ml2.Text = $"{dx},{dy}";
+					if (X == null || X is Pos.PosAbsolute) {
+						X = nx;
+					}
+					if (Y == null || Y is Pos.PosAbsolute) {
+						Y = ny;
+					}
+					//System.Diagnostics.Debug.WriteLine ($"nx:{nx},ny:{ny}");
 
 
 					// FIXED: optimize, only SetNeedsDisplay on the before/after regions.
 					// FIXED: optimize, only SetNeedsDisplay on the before/after regions.
 					SetNeedsDisplay ();
 					SetNeedsDisplay ();
@@ -256,7 +267,7 @@ namespace Terminal.Gui {
 				dragPosition = null;
 				dragPosition = null;
 			}
 			}
 
 
-			//Demo.ml.Text = me.ToString ();
+			//System.Diagnostics.Debug.WriteLine (mouseEvent.ToString ());
 			return false;
 			return false;
 		}
 		}
 
 

+ 1 - 1
Terminal.Gui/Terminal.Gui.csproj

@@ -167,7 +167,7 @@
   </PropertyGroup>-->
   </PropertyGroup>-->
   <ItemGroup>
   <ItemGroup>
     <PackageReference Include="Microsoft.NETFramework.ReferenceAssemblies" Version="1.0.0" PrivateAssets="true" />
     <PackageReference Include="Microsoft.NETFramework.ReferenceAssemblies" Version="1.0.0" PrivateAssets="true" />
-    <PackageReference Include="NStack.Core" Version="0.15.0" />
+    <PackageReference Include="NStack.Core" Version="0.16.0" />
   </ItemGroup>
   </ItemGroup>
   <!--<ItemGroup>
   <!--<ItemGroup>
     <Reference Include="NStack">
     <Reference Include="NStack">

+ 3 - 12
Terminal.Gui/Views/Button.cs

@@ -146,19 +146,10 @@ namespace Terminal.Gui {
 				base.Text = ustring.Make (_leftBracket) + " " + text + " " + ustring.Make (_rightBracket);
 				base.Text = ustring.Make (_leftBracket) + " " + text + " " + ustring.Make (_rightBracket);
 
 
 			int w = base.Text.RuneCount - (base.Text.Contains (HotKeySpecifier) ? 1 : 0);
 			int w = base.Text.RuneCount - (base.Text.Contains (HotKeySpecifier) ? 1 : 0);
-			if (Width is Dim.DimCombine || Width is Dim.DimView || Width is Dim.DimFill) {
-				// It's a Dim.DimCombine and so can't be assigned. Let it have it's width anchored.
-				w = Width.Anchor (w);
-			} else if (Width is Dim.DimFactor) {
-				// Tries to get the SuperView width otherwise the button width.
-				var sw = SuperView != null ? SuperView.Frame.Width : w;
-				if (((Dim.DimFactor)Width).IsFromRemaining ()) {
-					sw -= Frame.X;
-				}
-				w = Width.Anchor (sw);
-			} else {
-				Width = w;
+			if (SetWidth (w, out int rWidth)) {
+				Width = rWidth;
 			}
 			}
+			w = rWidth;
 			var layout = LayoutStyle;
 			var layout = LayoutStyle;
 			bool layoutChanged = false;
 			bool layoutChanged = false;
 			if (!(Height is Dim.DimAbsolute)) {
 			if (!(Height is Dim.DimAbsolute)) {

+ 0 - 1
Terminal.Gui/Views/FrameView.cs

@@ -147,7 +147,6 @@ namespace Terminal.Gui {
 		public override void Redraw (Rect bounds)
 		public override void Redraw (Rect bounds)
 		{
 		{
 			var padding = 0;
 			var padding = 0;
-			Application.CurrentView = this;
 			var scrRect = ViewToScreen (new Rect (0, 0, Frame.Width, Frame.Height));
 			var scrRect = ViewToScreen (new Rect (0, 0, Frame.Width, Frame.Height));
 
 
 			if (!NeedDisplay.IsEmpty) {
 			if (!NeedDisplay.IsEmpty) {

+ 155 - 30
Terminal.Gui/Views/ListView.cs

@@ -34,6 +34,11 @@ namespace Terminal.Gui {
 		/// </summary>
 		/// </summary>
 		int Count { get; }
 		int Count { get; }
 
 
+		/// <summary>
+		/// Returns the maximum length of elements to display
+		/// </summary>
+		int Length { get; }
+
 		/// <summary>
 		/// <summary>
 		/// This method is invoked to render a specified item, the method should cover the entire provided width.
 		/// This method is invoked to render a specified item, the method should cover the entire provided width.
 		/// </summary>
 		/// </summary>
@@ -45,10 +50,11 @@ namespace Terminal.Gui {
 		/// <param name="col">The column where the rendering will start</param>
 		/// <param name="col">The column where the rendering will start</param>
 		/// <param name="line">The line where the rendering will be done.</param>
 		/// <param name="line">The line where the rendering will be done.</param>
 		/// <param name="width">The width that must be filled out.</param>
 		/// <param name="width">The width that must be filled out.</param>
+		/// <param name="start">The index of the string to be displayed.</param>
 		/// <remarks>
 		/// <remarks>
 		///   The default color will be set before this method is invoked, and will be based on whether the item is selected or not.
 		///   The default color will be set before this method is invoked, and will be based on whether the item is selected or not.
 		/// </remarks>
 		/// </remarks>
-		void Render (ListView container, ConsoleDriver driver, bool selected, int item, int col, int line, int width);
+		void Render (ListView container, ConsoleDriver driver, bool selected, int item, int col, int line, int width, int start = 0);
 
 
 		/// <summary>
 		/// <summary>
 		/// Should return whether the specified item is currently marked.
 		/// Should return whether the specified item is currently marked.
@@ -103,7 +109,7 @@ namespace Terminal.Gui {
 	/// </para>
 	/// </para>
 	/// </remarks>
 	/// </remarks>
 	public class ListView : View {
 	public class ListView : View {
-		int top;
+		int top, left;
 		int selected;
 		int selected;
 
 
 		IListDataSource source;
 		IListDataSource source;
@@ -146,7 +152,7 @@ namespace Terminal.Gui {
 		/// </summary>
 		/// </summary>
 		/// <value>An item implementing the IList interface.</value>
 		/// <value>An item implementing the IList interface.</value>
 		/// <remarks>
 		/// <remarks>
-		///  Use the <see cref="Source"/> property to set a new <see cref="IListDataSource"/> source and use custome rendering.
+		///  Use the <see cref="Source"/> property to set a new <see cref="IListDataSource"/> source and use custom rendering.
 		/// </remarks>
 		/// </remarks>
 		public Task SetSourceAsync (IList source)
 		public Task SetSourceAsync (IList source)
 		{
 		{
@@ -204,13 +210,35 @@ namespace Terminal.Gui {
 				if (source == null)
 				if (source == null)
 					return;
 					return;
 
 
-				if (top < 0 || top >= source.Count)
+				if (value < 0 || (source.Count > 0 && value >= source.Count))
 					throw new ArgumentException ("value");
 					throw new ArgumentException ("value");
 				top = value;
 				top = value;
 				SetNeedsDisplay ();
 				SetNeedsDisplay ();
 			}
 			}
 		}
 		}
 
 
+		/// <summary>
+		/// Gets or sets the left column where the item start to be displayed at on the <see cref="ListView"/>.
+		/// </summary>
+		/// <value>The left position.</value>
+		public int LeftItem {
+			get => left;
+			set {
+				if (source == null)
+					return;
+
+				if (value < 0 || (Maxlength > 0 && value >= Maxlength))
+					throw new ArgumentException ("value");
+				left = value;
+				SetNeedsDisplay ();
+			}
+		}
+
+		/// <summary>
+		/// Gets the widest item.
+		/// </summary>
+		public int Maxlength => (source?.Length) ?? 0;
+
 		/// <summary>
 		/// <summary>
 		/// Gets or sets the index of the currently selected item.
 		/// Gets or sets the index of the currently selected item.
 		/// </summary>
 		/// </summary>
@@ -229,7 +257,6 @@ namespace Terminal.Gui {
 			}
 			}
 		}
 		}
 
 
-
 		static IListDataSource MakeWrapper (IList source)
 		static IListDataSource MakeWrapper (IList source)
 		{
 		{
 			return new ListWrapper (source);
 			return new ListWrapper (source);
@@ -298,14 +325,10 @@ namespace Terminal.Gui {
 			Driver.SetAttribute (current);
 			Driver.SetAttribute (current);
 			Move (0, 0);
 			Move (0, 0);
 			var f = Frame;
 			var f = Frame;
-			if (selected < top) {
-				top = selected;
-			} else if (selected >= top + f.Height) {
-				top = selected;
-			}
 			var item = top;
 			var item = top;
 			bool focused = HasFocus;
 			bool focused = HasFocus;
 			int col = allowsMarking ? 2 : 0;
 			int col = allowsMarking ? 2 : 0;
+			int start = left;
 
 
 			for (int row = 0; row < f.Height; row++, item++) {
 			for (int row = 0; row < f.Height; row++, item++) {
 				bool isSelected = item == selected;
 				bool isSelected = item == selected;
@@ -325,7 +348,7 @@ namespace Terminal.Gui {
 						Driver.AddRune (source.IsMarked (item) ? (AllowsMultipleSelection ? Driver.Checked : Driver.Selected) : (AllowsMultipleSelection ? Driver.UnChecked : Driver.UnSelected));
 						Driver.AddRune (source.IsMarked (item) ? (AllowsMultipleSelection ? Driver.Checked : Driver.Selected) : (AllowsMultipleSelection ? Driver.UnChecked : Driver.UnSelected));
 						Driver.AddRune (' ');
 						Driver.AddRune (' ');
 					}
 					}
-					Source.Render (this, Driver, isSelected, item, col, row, f.Width - col);
+					Source.Render (this, Driver, isSelected, item, col, row, f.Width - col, start);
 				}
 				}
 			}
 			}
 		}
 		}
@@ -477,8 +500,11 @@ namespace Terminal.Gui {
 			} else if (selected + 1 < source.Count) { //can move by down by one.
 			} else if (selected + 1 < source.Count) { //can move by down by one.
 				selected++;
 				selected++;
 
 
-				if (selected >= top + Frame.Height)
+				if (selected >= top + Frame.Height) {
 					top++;
 					top++;
+				} else if (selected < top) {
+					top = selected;
+				}
 				OnSelectedChanged ();
 				OnSelectedChanged ();
 				SetNeedsDisplay ();
 				SetNeedsDisplay ();
 			} else if (selected == 0) {
 			} else if (selected == 0) {
@@ -511,8 +537,11 @@ namespace Terminal.Gui {
 				if (selected > Source.Count) {
 				if (selected > Source.Count) {
 					selected = Source.Count - 1;
 					selected = Source.Count - 1;
 				}
 				}
-				if (selected < top)
+				if (selected < top) {
 					top = selected;
 					top = selected;
+				} else if (selected > top + Frame.Height) {
+					top = Math.Max (selected - Frame.Height + 1, 0);
+				}
 				OnSelectedChanged ();
 				OnSelectedChanged ();
 				SetNeedsDisplay ();
 				SetNeedsDisplay ();
 			}
 			}
@@ -551,6 +580,46 @@ namespace Terminal.Gui {
 			return true;
 			return true;
 		}
 		}
 
 
+		/// <summary>
+		/// Scrolls the view down.
+		/// </summary>
+		/// <param name="lines">Number of lines to scroll down.</param>
+		public virtual void ScrollDown (int lines)
+		{
+			top = Math.Max (Math.Min (top + lines, source.Count - 1), 0);
+			SetNeedsDisplay ();
+		}
+
+		/// <summary>
+		/// Scrolls the view up.
+		/// </summary>
+		/// <param name="lines">Number of lines to scroll up.</param>
+		public virtual void ScrollUp (int lines)
+		{
+			top = Math.Max (top - lines, 0);
+			SetNeedsDisplay ();
+		}
+
+		/// <summary>
+		/// Scrolls the view right.
+		/// </summary>
+		/// <param name="cols">Number of columns to scroll right.</param>
+		public virtual void ScrollRight (int cols)
+		{
+			left = Math.Max (Math.Min (left + cols, Maxlength - 1), 0);
+			SetNeedsDisplay ();
+		}
+
+		/// <summary>
+		/// Scrolls the view left.
+		/// </summary>
+		/// <param name="cols">Number of columns to scroll left.</param>
+		public virtual void ScrollLeft (int cols)
+		{
+			left = Math.Max (left - cols, 0);
+			SetNeedsDisplay ();
+		}
+
 		int lastSelectedItem = -1;
 		int lastSelectedItem = -1;
 		private bool allowsMultipleSelection = true;
 		private bool allowsMultipleSelection = true;
 
 
@@ -563,7 +632,9 @@ namespace Terminal.Gui {
 			if (selected != lastSelectedItem) {
 			if (selected != lastSelectedItem) {
 				var value = source?.Count > 0 ? source.ToList () [selected] : null;
 				var value = source?.Count > 0 ? source.ToList () [selected] : null;
 				SelectedItemChanged?.Invoke (new ListViewItemEventArgs (selected, value));
 				SelectedItemChanged?.Invoke (new ListViewItemEventArgs (selected, value));
-				lastSelectedItem = selected;
+				if (HasFocus) {
+					lastSelectedItem = selected;
+				}
 				return true;
 				return true;
 			}
 			}
 
 
@@ -587,6 +658,7 @@ namespace Terminal.Gui {
 		public override bool OnEnter (View view)
 		public override bool OnEnter (View view)
 		{
 		{
 			if (lastSelectedItem == -1) {
 			if (lastSelectedItem == -1) {
+				EnsuresVisibilitySelectedItem ();
 				OnSelectedChanged ();
 				OnSelectedChanged ();
 				return true;
 				return true;
 			}
 			}
@@ -605,6 +677,16 @@ namespace Terminal.Gui {
 			return false;
 			return false;
 		}
 		}
 
 
+		void EnsuresVisibilitySelectedItem ()
+		{
+			SuperView?.LayoutSubviews ();
+			if (selected < top) {
+				top = selected;
+			} else if (Frame.Height > 0 && selected >= top + Frame.Height) {
+				top = Math.Max (selected - Frame.Height + 2, 0);
+			}
+		}
+
 		///<inheritdoc/>
 		///<inheritdoc/>
 		public override void PositionCursor ()
 		public override void PositionCursor ()
 		{
 		{
@@ -618,7 +700,8 @@ namespace Terminal.Gui {
 		public override bool MouseEvent (MouseEvent me)
 		public override bool MouseEvent (MouseEvent me)
 		{
 		{
 			if (!me.Flags.HasFlag (MouseFlags.Button1Clicked) && !me.Flags.HasFlag (MouseFlags.Button1DoubleClicked) &&
 			if (!me.Flags.HasFlag (MouseFlags.Button1Clicked) && !me.Flags.HasFlag (MouseFlags.Button1DoubleClicked) &&
-				me.Flags != MouseFlags.WheeledDown && me.Flags != MouseFlags.WheeledUp)
+				me.Flags != MouseFlags.WheeledDown && me.Flags != MouseFlags.WheeledUp &&
+				me.Flags != MouseFlags.WheeledRight && me.Flags != MouseFlags.WheeledLeft)
 				return false;
 				return false;
 
 
 			if (!HasFocus && CanFocus) {
 			if (!HasFocus && CanFocus) {
@@ -630,10 +713,16 @@ namespace Terminal.Gui {
 			}
 			}
 
 
 			if (me.Flags == MouseFlags.WheeledDown) {
 			if (me.Flags == MouseFlags.WheeledDown) {
-				MoveDown ();
+				ScrollDown (1);
 				return true;
 				return true;
 			} else if (me.Flags == MouseFlags.WheeledUp) {
 			} else if (me.Flags == MouseFlags.WheeledUp) {
-				MoveUp ();
+				ScrollUp (1);
+				return true;
+			} else if (me.Flags == MouseFlags.WheeledRight) {
+				ScrollRight (1);
+				return true;
+			} else if (me.Flags == MouseFlags.WheeledLeft) {
+				ScrollLeft (1);
 				return true;
 				return true;
 			}
 			}
 
 
@@ -655,6 +744,8 @@ namespace Terminal.Gui {
 
 
 			return true;
 			return true;
 		}
 		}
+
+
 	}
 	}
 
 
 	/// <summary>
 	/// <summary>
@@ -664,7 +755,7 @@ namespace Terminal.Gui {
 	public class ListWrapper : IListDataSource {
 	public class ListWrapper : IListDataSource {
 		IList src;
 		IList src;
 		BitArray marks;
 		BitArray marks;
-		int count;
+		int count, len;
 
 
 		/// <summary>
 		/// <summary>
 		/// Initializes a new instance of <see cref="ListWrapper"/> given an <see cref="IList"/>
 		/// Initializes a new instance of <see cref="ListWrapper"/> given an <see cref="IList"/>
@@ -675,7 +766,8 @@ namespace Terminal.Gui {
 			if (source != null) {
 			if (source != null) {
 				count = source.Count;
 				count = source.Count;
 				marks = new BitArray (count);
 				marks = new BitArray (count);
-				this.src = source;
+				src = source;
+				len = GetMaxLengthItem ();
 			}
 			}
 		}
 		}
 
 
@@ -684,11 +776,42 @@ namespace Terminal.Gui {
 		/// </summary>
 		/// </summary>
 		public int Count => src != null ? src.Count : 0;
 		public int Count => src != null ? src.Count : 0;
 
 
-		void RenderUstr (ConsoleDriver driver, ustring ustr, int col, int line, int width)
+		/// <summary>
+		/// Gets the maximum item length in the <see cref="IList"/>.
+		/// </summary>
+		public int Length => len;
+
+		int GetMaxLengthItem ()
+		{
+			if (src?.Count == 0) {
+				return 0;
+			}
+
+			int maxLength = 0;
+			for (int i = 0; i < src.Count; i++) {
+				var t = src [i];
+				int l;
+				if (t is ustring u) {
+					l = u.RuneCount;
+				} else if (t is string s) {
+					l = s.Length;
+				} else {
+					l = t.ToString ().Length;
+				}
+
+				if (l > maxLength) {
+					maxLength = l;
+				}
+			}
+
+			return maxLength;
+		}
+
+		void RenderUstr (ConsoleDriver driver, ustring ustr, int col, int line, int width, int start = 0)
 		{
 		{
 			int byteLen = ustr.Length;
 			int byteLen = ustr.Length;
 			int used = 0;
 			int used = 0;
-			for (int i = 0; i < byteLen;) {
+			for (int i = start; i < byteLen;) {
 				(var rune, var size) = Utf8.DecodeRune (ustr, i, i - byteLen);
 				(var rune, var size) = Utf8.DecodeRune (ustr, i, i - byteLen);
 				var count = Rune.ColumnWidth (rune);
 				var count = Rune.ColumnWidth (rune);
 				if (used + count > width)
 				if (used + count > width)
@@ -712,19 +835,21 @@ namespace Terminal.Gui {
 		/// <param name="col">The col where to move.</param>
 		/// <param name="col">The col where to move.</param>
 		/// <param name="line">The line where to move.</param>
 		/// <param name="line">The line where to move.</param>
 		/// <param name="width">The item width.</param>
 		/// <param name="width">The item width.</param>
-		public void Render (ListView container, ConsoleDriver driver, bool marked, int item, int col, int line, int width)
+		/// <param name="start">The index of the string to be displayed.</param>
+		public void Render (ListView container, ConsoleDriver driver, bool marked, int item, int col, int line, int width, int start = 0)
 		{
 		{
 			container.Move (col, line);
 			container.Move (col, line);
 			var t = src [item];
 			var t = src [item];
 			if (t == null) {
 			if (t == null) {
 				RenderUstr (driver, ustring.Make (""), col, line, width);
 				RenderUstr (driver, ustring.Make (""), col, line, width);
 			} else {
 			} else {
-				if (t is ustring) {
-					RenderUstr (driver, (ustring)t, col, line, width);
-				} else if (t is string) {
-					RenderUstr (driver, (string)t, col, line, width);
-				} else
-					RenderUstr (driver, t.ToString (), col, line, width);
+				if (t is ustring u) {
+					RenderUstr (driver, u, col, line, width, start);
+				} else if (t is string s) {
+					RenderUstr (driver, s, col, line, width, start);
+				} else {
+					RenderUstr (driver, t.ToString (), col, line, width, start);
+				}
 			}
 			}
 		}
 		}
 
 
@@ -770,14 +895,14 @@ namespace Terminal.Gui {
 		/// </summary>
 		/// </summary>
 		public int Item { get; }
 		public int Item { get; }
 		/// <summary>
 		/// <summary>
-		/// The the <see cref="ListView"/> item.
+		/// The <see cref="ListView"/> item.
 		/// </summary>
 		/// </summary>
 		public object Value { get; }
 		public object Value { get; }
 
 
 		/// <summary>
 		/// <summary>
 		/// Initializes a new instance of <see cref="ListViewItemEventArgs"/>
 		/// Initializes a new instance of <see cref="ListViewItemEventArgs"/>
 		/// </summary>
 		/// </summary>
-		/// <param name="item">The index of the the <see cref="ListView"/> item.</param>
+		/// <param name="item">The index of the <see cref="ListView"/> item.</param>
 		/// <param name="value">The <see cref="ListView"/> item</param>
 		/// <param name="value">The <see cref="ListView"/> item</param>
 		public ListViewItemEventArgs (int item, object value)
 		public ListViewItemEventArgs (int item, object value)
 		{
 		{

+ 35 - 5
Terminal.Gui/Views/Menu.cs

@@ -239,6 +239,32 @@ namespace Terminal.Gui {
 			Children = children;
 			Children = children;
 		}
 		}
 
 
+		/// <summary>
+		/// Initializes a new <see cref="MenuBarItem"/> with separate list of items.
+		/// </summary>
+		/// <param name="title">Title for the menu item.</param>
+		/// <param name="children">The list of items in the current menu.</param>
+		/// <param name="parent">The parent <see cref="MenuItem"/> of this if exist, otherwise is null.</param>
+		public MenuBarItem (ustring title, List<MenuItem []> children, MenuItem parent = null)
+		{
+			if (children == null) {
+				throw new ArgumentNullException (nameof (children), "The parameter cannot be null. Use an empty array instead.");
+			}
+			SetTitle (title ?? "");
+			if (parent != null) {
+				Parent = parent;
+			}
+			MenuItem [] childrens = new MenuItem [] { };
+			foreach (var item in children) {
+				for (int i = 0; i < item.Length; i++) {
+					SetChildrensParent (item);
+					Array.Resize (ref childrens, childrens.Length + 1);
+					childrens [childrens.Length - 1] = item [i];
+				}
+			}
+			Children = childrens;
+		}
+
 		/// <summary>
 		/// <summary>
 		/// Initializes a new <see cref="MenuBarItem"/>.
 		/// Initializes a new <see cref="MenuBarItem"/>.
 		/// </summary>
 		/// </summary>
@@ -427,7 +453,7 @@ namespace Terminal.Gui {
 						Driver.AddRune (' ');
 						Driver.AddRune (' ');
 
 
 				if (item == null) {
 				if (item == null) {
-					Move (Frame.Right - 1, i + 1);
+					Move (Frame.Width - 1, i + 1);
 					Driver.AddRune (Driver.RightTee);
 					Driver.AddRune (Driver.RightTee);
 					continue;
 					continue;
 				}
 				}
@@ -808,7 +834,7 @@ namespace Terminal.Gui {
 		///<inheritdoc/>
 		///<inheritdoc/>
 		public override bool OnKeyDown (KeyEvent keyEvent)
 		public override bool OnKeyDown (KeyEvent keyEvent)
 		{
 		{
-			if (keyEvent.IsAlt) {
+			if (keyEvent.IsAlt || (keyEvent.IsCtrl && keyEvent.Key == (Key.CtrlMask | Key.Space))) {
 				openedByAltKey = true;
 				openedByAltKey = true;
 				SetNeedsDisplay ();
 				SetNeedsDisplay ();
 				openedByHotKey = false;
 				openedByHotKey = false;
@@ -819,9 +845,10 @@ namespace Terminal.Gui {
 		///<inheritdoc/>
 		///<inheritdoc/>
 		public override bool OnKeyUp (KeyEvent keyEvent)
 		public override bool OnKeyUp (KeyEvent keyEvent)
 		{
 		{
-			if (keyEvent.IsAlt) {
+			if (keyEvent.IsAlt || (keyEvent.IsCtrl && keyEvent.Key == (Key.CtrlMask | Key.Space))) {
 				// User pressed Alt - this may be a precursor to a menu accelerator (e.g. Alt-F)
 				// User pressed Alt - this may be a precursor to a menu accelerator (e.g. Alt-F)
-				if (!keyEvent.IsCtrl && openedByAltKey && !IsMenuOpen && openMenu == null && ((uint)keyEvent.Key & (uint)Key.CharMask) == 0) {
+				if (openedByAltKey && !IsMenuOpen && openMenu == null && (((uint)keyEvent.Key & (uint)Key.CharMask) == 0
+					|| ((uint)keyEvent.Key & (uint)Key.CharMask) == (uint)Key.Space)) {
 					// There's no open menu, the first menu item should be highlight.
 					// There's no open menu, the first menu item should be highlight.
 					// The right way to do this is to SetFocus(MenuBar), but for some reason
 					// The right way to do this is to SetFocus(MenuBar), but for some reason
 					// that faults.
 					// that faults.
@@ -900,6 +927,9 @@ namespace Terminal.Gui {
 		///<inheritdoc/>
 		///<inheritdoc/>
 		public override void PositionCursor ()
 		public override void PositionCursor ()
 		{
 		{
+			if (selected == -1 && HasFocus && Menus.Length > 0) {
+				selected = 0;
+			}
 			int pos = 0;
 			int pos = 0;
 			for (int i = 0; i < Menus.Length; i++) {
 			for (int i = 0; i < Menus.Length; i++) {
 				if (i == selected) {
 				if (i == selected) {
@@ -1415,7 +1445,7 @@ namespace Terminal.Gui {
 				CloseMenu ();
 				CloseMenu ();
 				if (openedByAltKey) {
 				if (openedByAltKey) {
 					openedByAltKey = false;
 					openedByAltKey = false;
-					LastFocused.SetFocus ();
+					LastFocused?.SetFocus ();
 				}
 				}
 				break;
 				break;
 
 

+ 662 - 0
Terminal.Gui/Views/ScrollBarView.cs

@@ -0,0 +1,662 @@
+//
+// ScrollBarView.cs: ScrollBarView view.
+//
+// Authors:
+//   Miguel de Icaza ([email protected])
+//
+
+using System;
+
+namespace Terminal.Gui {
+	/// <summary>
+	/// ScrollBarViews are views that display a 1-character scrollbar, either horizontal or vertical
+	/// </summary>
+	/// <remarks>
+	/// <para>
+	///   The scrollbar is drawn to be a representation of the Size, assuming that the 
+	///   scroll position is set at Position.
+	/// </para>
+	/// <para>
+	///   If the region to display the scrollbar is larger than three characters, 
+	///   arrow indicators are drawn.
+	/// </para>
+	/// </remarks>
+	public class ScrollBarView : View {
+		bool vertical;
+		int size, position;
+		bool showScrollIndicator;
+		bool keepContentAlwaysInViewport = true;
+		bool autoHideScrollBars = true;
+		bool hosted;
+		ScrollBarView otherScrollBarView;
+		View contentBottomRightCorner;
+
+		bool showBothScrollIndicator => OtherScrollBarView != null && OtherScrollBarView.showScrollIndicator && showScrollIndicator;
+
+		/// <summary>
+		/// Initializes a new instance of the <see cref="Gui.ScrollBarView"/> class using <see cref="LayoutStyle.Absolute"/> layout.
+		/// </summary>
+		/// <param name="rect">Frame for the scrollbar.</param>
+		public ScrollBarView (Rect rect) : this (rect, 0, 0, false) { }
+
+		/// <summary>
+		/// Initializes a new instance of the <see cref="Gui.ScrollBarView"/> class using <see cref="LayoutStyle.Absolute"/> layout.
+		/// </summary>
+		/// <param name="rect">Frame for the scrollbar.</param>
+		/// <param name="size">The size that this scrollbar represents. Sets the <see cref="Size"/> property.</param>
+		/// <param name="position">The position within this scrollbar. Sets the <see cref="Position"/> property.</param>
+		/// <param name="isVertical">If set to <c>true</c> this is a vertical scrollbar, otherwise, the scrollbar is horizontal. Sets the <see cref="IsVertical"/> property.</param>
+		public ScrollBarView (Rect rect, int size, int position, bool isVertical) : base (rect)
+		{
+			Init (size, position, isVertical);
+		}
+
+		/// <summary>
+		/// Initializes a new instance of the <see cref="Gui.ScrollBarView"/> class using <see cref="LayoutStyle.Computed"/> layout.
+		/// </summary>
+		public ScrollBarView () : this (0, 0, false) { }
+
+		/// <summary>
+		/// Initializes a new instance of the <see cref="Gui.ScrollBarView"/> class using <see cref="LayoutStyle.Computed"/> layout.
+		/// </summary>
+		/// <param name="size">The size that this scrollbar represents.</param>
+		/// <param name="position">The position within this scrollbar.</param>
+		/// <param name="isVertical">If set to <c>true</c> this is a vertical scrollbar, otherwise, the scrollbar is horizontal.</param>
+		public ScrollBarView (int size, int position, bool isVertical) : base ()
+		{
+			Init (size, position, isVertical);
+		}
+
+		/// <summary>
+		/// Initializes a new instance of the <see cref="Gui.ScrollBarView"/> class using <see cref="LayoutStyle.Computed"/> layout.
+		/// </summary>
+		/// <param name="host">The view that will host this scrollbar.</param>
+		/// <param name="isVertical">If set to <c>true</c> this is a vertical scrollbar, otherwise, the scrollbar is horizontal.</param>
+		/// <param name="showBothScrollIndicator">If set to <c>true (default)</c> will have the other scrollbar, otherwise will have only one.</param>
+		public ScrollBarView (View host, bool isVertical, bool showBothScrollIndicator = true) : this (0, 0, isVertical)
+		{
+			if (host == null) {
+				throw new ArgumentNullException ("The host parameter can't be null.");
+			} else if (host.SuperView == null) {
+				throw new ArgumentNullException ("The host SuperView parameter can't be null.");
+			}
+			hosted = true;
+			ColorScheme = host.ColorScheme;
+			X = isVertical ? Pos.Right (host) - 1 : Pos.Left (host);
+			Y = isVertical ? Pos.Top (host) : Pos.Bottom (host) - 1;
+			Host = host;
+			Host.SuperView.Add (this);
+			AutoHideScrollBars = true;
+			if (showBothScrollIndicator) {
+				OtherScrollBarView = new ScrollBarView (0, 0, !isVertical) {
+					ColorScheme = host.ColorScheme,
+					Host = host,
+					OtherScrollBarView = this,
+				};
+				OtherScrollBarView.hosted = true;
+				OtherScrollBarView.X = OtherScrollBarView.IsVertical ? Pos.Right (host) - 1 : Pos.Left (host);
+				OtherScrollBarView.Y = OtherScrollBarView.IsVertical ? Pos.Top (host) : Pos.Bottom (host) - 1;
+				OtherScrollBarView.Host.SuperView.Add (OtherScrollBarView);
+				OtherScrollBarView.showScrollIndicator = true;
+			}
+			ShowScrollIndicator = true;
+			contentBottomRightCorner = new View (" ");
+			Host.SuperView.Add (contentBottomRightCorner);
+			contentBottomRightCorner.X = Pos.Right (host) - 1;
+			contentBottomRightCorner.Y = Pos.Bottom (host) - 1;
+			contentBottomRightCorner.Width = 1;
+			contentBottomRightCorner.Height = 1;
+			contentBottomRightCorner.MouseClick += ContentBottomRightCorner_MouseClick;
+		}
+
+		void ContentBottomRightCorner_MouseClick (MouseEventArgs me)
+		{
+			if (me.MouseEvent.Flags == MouseFlags.WheeledDown || me.MouseEvent.Flags == MouseFlags.WheeledUp
+				|| me.MouseEvent.Flags == MouseFlags.WheeledRight || me.MouseEvent.Flags == MouseFlags.WheeledLeft) {
+				me.Handled = true;
+				MouseEvent (me.MouseEvent);
+			} else if (me.MouseEvent.Flags == MouseFlags.Button1Clicked) {
+				me.Handled = true;
+				Host.SetFocus ();
+			}
+		}
+
+		void Init (int size, int position, bool isVertical)
+		{
+			vertical = isVertical;
+			this.position = position;
+			this.size = size;
+			WantContinuousButtonPressed = true;
+		}
+
+		/// <summary>
+		/// If set to <c>true</c> this is a vertical scrollbar, otherwise, the scrollbar is horizontal.
+		/// </summary>
+		public bool IsVertical {
+			get => vertical;
+			set {
+				vertical = value;
+				SetNeedsDisplay ();
+			}
+		}
+
+		/// <summary>
+		/// The size of content the scrollbar represents.
+		/// </summary>
+		/// <value>The size.</value>
+		/// <remarks>The <see cref="Size"/> is typically the size of the virtual content. E.g. when a Scrollbar is
+		/// part of a <see cref="View"/> the Size is set to the appropriate dimension of <see cref="Host"/>.</remarks>
+		public int Size {
+			get => size;
+			set {
+				if (hosted || (otherScrollBarView != null && otherScrollBarView.hosted)) {
+					size = value + 1;
+				} else {
+					size = value;
+				}
+				SetNeedsDisplay ();
+			}
+		}
+
+		/// <summary>
+		/// This event is raised when the position on the scrollbar has changed.
+		/// </summary>
+		public event Action ChangedPosition;
+
+		/// <summary>
+		/// The position, relative to <see cref="Size"/>, to set the scrollbar at.
+		/// </summary>
+		/// <value>The position.</value>
+		public int Position {
+			get => position;
+			set {
+				if (position != value) {
+					if (CanScroll (value - position, out int max, vertical)) {
+						if (max == value - position) {
+							position = value;
+						} else {
+							position = Math.Max (position + max, 0);
+						}
+					} else if (max < 0) {
+						position = Math.Max (position + max, 0);
+					}
+					var s = GetBarsize (vertical);
+					if (position + s == size && (hosted || (otherScrollBarView != null && otherScrollBarView.hosted))) {
+						position++;
+					}
+					OnChangedPosition ();
+					SetNeedsDisplay ();
+				}
+			}
+		}
+
+		/// <summary>
+		/// Get or sets the view that host this <see cref="View"/>
+		/// </summary>
+		public View Host { get; internal set; }
+
+		/// <summary>
+		/// Represent a vertical or horizontal ScrollBarView other than this.
+		/// </summary>
+		public ScrollBarView OtherScrollBarView {
+			get => otherScrollBarView;
+			set {
+				if (value != null && (value.IsVertical && vertical || !value.IsVertical && !vertical)) {
+					throw new ArgumentException ($"There is already a {(vertical ? "vertical" : "horizontal")} ScrollBarView.");
+				}
+				otherScrollBarView = value;
+			}
+		}
+
+		/// <summary>
+		/// Gets or sets the visibility for the vertical or horizontal scroll indicator.
+		/// </summary>
+		/// <value><c>true</c> if show vertical or horizontal scroll indicator; otherwise, <c>false</c>.</value>
+		public bool ShowScrollIndicator {
+			get => showScrollIndicator;
+			set {
+				if (value == showScrollIndicator) {
+					return;
+				}
+
+				showScrollIndicator = value;
+				SetNeedsLayout ();
+				if (value) {
+					Visible = true;
+				} else {
+					Visible = false;
+					Position = 0;
+				}
+				SetWidthHeight ();
+			}
+		}
+
+		/// <summary>
+		/// Get or sets if the view-port is kept always visible in the area of this <see cref="ScrollBarView"/>
+		/// </summary>
+		public bool KeepContentAlwaysInViewport {
+			get { return keepContentAlwaysInViewport; }
+			set {
+				if (keepContentAlwaysInViewport != value) {
+					keepContentAlwaysInViewport = value;
+					int pos = 0;
+					if (value && !vertical && position + Host.Bounds.Width > size) {
+						pos = size - Host.Bounds.Width + (showBothScrollIndicator ? 1 : 0);
+					}
+					if (value && vertical && position + Host.Bounds.Height > size) {
+						pos = size - Host.Bounds.Height + (showBothScrollIndicator ? 1 : 0);
+					}
+					if (pos != 0) {
+						Position = pos;
+					}
+					if (OtherScrollBarView != null && OtherScrollBarView.keepContentAlwaysInViewport != value) {
+						OtherScrollBarView.KeepContentAlwaysInViewport = value;
+					}
+					if (pos == 0) {
+						Refresh ();
+					}
+				}
+			}
+		}
+
+		/// <summary>
+		/// If true the vertical/horizontal scroll bars won't be showed if it's not needed.
+		/// </summary>
+		public bool AutoHideScrollBars {
+			get => autoHideScrollBars;
+			set {
+				if (autoHideScrollBars != value) {
+					autoHideScrollBars = value;
+					SetNeedsDisplay ();
+				}
+			}
+		}
+
+		/// <summary>
+		/// Virtual method to invoke the <see cref="ChangedPosition"/> action event.
+		/// </summary>
+		public virtual void OnChangedPosition ()
+		{
+			ChangedPosition?.Invoke ();
+		}
+
+		/// <summary>
+		/// Only used for a hosted view that will update and redraw the scrollbars.
+		/// </summary>
+		public virtual void Refresh ()
+		{
+			ShowHideScrollBars ();
+		}
+
+		void ShowHideScrollBars ()
+		{
+			if (!hosted || (hosted && !autoHideScrollBars)) {
+				return;
+			}
+
+			var pending = CheckBothScrollBars (this);
+			CheckBothScrollBars (otherScrollBarView, pending);
+
+			SetWidthHeight ();
+			SetRelativeLayout (Bounds);
+			OtherScrollBarView.SetRelativeLayout (OtherScrollBarView.Bounds);
+
+			if (showBothScrollIndicator) {
+				if (contentBottomRightCorner != null) {
+					contentBottomRightCorner.Visible = true;
+				}
+			} else if (!showScrollIndicator) {
+				if (contentBottomRightCorner != null) {
+					contentBottomRightCorner.Visible = false;
+				}
+				if (Application.mouseGrabView != null && Application.mouseGrabView == this) {
+					Application.UngrabMouse ();
+				}
+			}
+			if (showScrollIndicator) {
+				Redraw (Bounds);
+			}
+			if (otherScrollBarView.showScrollIndicator) {
+				otherScrollBarView.Redraw (otherScrollBarView.Bounds);
+			}
+		}
+
+		bool CheckBothScrollBars (ScrollBarView scrollBarView, bool pending = false)
+		{
+			int barsize = scrollBarView.vertical ? scrollBarView.Bounds.Height : scrollBarView.Bounds.Width;
+
+			if (barsize == 0 || barsize > scrollBarView.size) {
+				if (scrollBarView.showScrollIndicator) {
+					scrollBarView.ShowScrollIndicator = false;
+				}
+			} else if (barsize > 0 && barsize == scrollBarView.size && scrollBarView.OtherScrollBarView != null && pending) {
+				if (scrollBarView.showScrollIndicator) {
+					scrollBarView.ShowScrollIndicator = false;
+				}
+				if (scrollBarView.OtherScrollBarView != null && scrollBarView.showBothScrollIndicator) {
+					scrollBarView.OtherScrollBarView.ShowScrollIndicator = false;
+				}
+			} else if (barsize > 0 && barsize == size && scrollBarView.OtherScrollBarView != null && !pending) {
+				pending = true;
+			} else {
+				if (scrollBarView.OtherScrollBarView != null && pending) {
+					if (!scrollBarView.showBothScrollIndicator) {
+						scrollBarView.OtherScrollBarView.ShowScrollIndicator = true;
+					}
+				}
+				if (!scrollBarView.showScrollIndicator) {
+					scrollBarView.ShowScrollIndicator = true;
+				}
+			}
+
+			return pending;
+		}
+
+		void SetWidthHeight ()
+		{
+			if (showBothScrollIndicator) {
+				Width = vertical ? 1 : Dim.Width (Host) - 1;
+				Height = vertical ? Dim.Height (Host) - 1 : 1;
+
+				otherScrollBarView.Width = otherScrollBarView.vertical ? 1 : Dim.Width (Host) - 1;
+				otherScrollBarView.Height = otherScrollBarView.vertical ? Dim.Height (Host) - 1 : 1;
+			} else if (showScrollIndicator) {
+				Width = vertical ? 1 : Dim.Width (Host) - 0;
+				Height = vertical ? Dim.Height (Host) - 0 : 1;
+			} else if (otherScrollBarView != null && otherScrollBarView.showScrollIndicator) {
+				otherScrollBarView.Width = otherScrollBarView.vertical ? 1 : Dim.Width (Host) - 0;
+				otherScrollBarView.Height = otherScrollBarView.vertical ? Dim.Height (Host) - 0 : 1;
+			}
+		}
+
+		int posTopTee;
+		int posLeftTee;
+		int posBottomTee;
+		int posRightTee;
+
+		///<inheritdoc/>
+		public override void Redraw (Rect region)
+		{
+			if (ColorScheme == null || Size == 0) {
+				return;
+			}
+
+			Driver.SetAttribute (ColorScheme.Normal);
+
+			if ((vertical && Bounds.Height == 0) || (!vertical && Bounds.Width == 0)) {
+				return;
+			}
+
+			if (vertical) {
+				if (region.Right < Bounds.Width - 1) {
+					return;
+				}
+
+				var col = Bounds.Width - 1;
+				var bh = Bounds.Height;
+				Rune special;
+
+				if (bh < 4) {
+					var by1 = position * bh / Size;
+					var by2 = (position + bh) * bh / Size;
+
+					Move (col, 0);
+					if (Bounds.Height == 1) {
+						Driver.AddRune (Driver.Diamond);
+					} else {
+						Driver.AddRune (Driver.UpArrow);
+					}
+					if (Bounds.Height == 3) {
+						Move (col, 1);
+						Driver.AddRune (Driver.Diamond);
+					}
+					if (Bounds.Height > 1) {
+						Move (col, Bounds.Height - 1);
+						Driver.AddRune (Driver.DownArrow);
+					}
+				} else {
+					bh -= 2;
+					var by1 = KeepContentAlwaysInViewport ? position * bh / Size : position * bh / (Size + bh);
+					var by2 = KeepContentAlwaysInViewport ? Math.Min (((position + bh) * bh / Size) + 1, bh - 1) : (position + bh) * bh / (Size + bh);
+					if (KeepContentAlwaysInViewport && by1 == by2) {
+						by1 = Math.Max (by1 - 1, 0);
+					}
+
+					Move (col, 0);
+					Driver.AddRune (Driver.UpArrow);
+					Move (col, Bounds.Height - 1);
+					Driver.AddRune (Driver.DownArrow);
+
+					bool hasTopTee = false;
+					bool hasDiamond = false;
+					bool hasBottomTee = false;
+					for (int y = 0; y < bh; y++) {
+						Move (col, y + 1);
+						if ((y < by1 || y > by2) && ((position > 0 && !hasTopTee) || (hasTopTee && hasBottomTee))) {
+							special = Driver.Stipple;
+						} else {
+							if (y != by2 && y > 1 && by2 - by1 == 0 && by1 < bh - 1 && hasTopTee && !hasDiamond) {
+								hasDiamond = true;
+								special = Driver.Diamond;
+							} else {
+								if (y == by1 && !hasTopTee) {
+									hasTopTee = true;
+									posTopTee = y;
+									special = Driver.TopTee;
+								} else if ((position == 0 && y == bh - 1 || y >= by2 || by2 == 0) && !hasBottomTee) {
+									hasBottomTee = true;
+									posBottomTee = y;
+									special = Driver.BottomTee;
+								} else {
+									special = Driver.VLine;
+								}
+							}
+						}
+						Driver.AddRune (special);
+					}
+					if (!hasTopTee) {
+						Move (col, Bounds.Height - 2);
+						Driver.AddRune (Driver.TopTee);
+					}
+				}
+			} else {
+				if (region.Bottom < Bounds.Height - 1) {
+					return;
+				}
+
+				var row = Bounds.Height - 1;
+				var bw = Bounds.Width;
+				Rune special;
+
+				if (bw < 4) {
+					var bx1 = position * bw / Size;
+					var bx2 = (position + bw) * bw / Size;
+
+					Move (0, row);
+					Driver.AddRune (Driver.LeftArrow);
+					Driver.AddRune (Driver.RightArrow);
+				} else {
+					bw -= 2;
+					var bx1 = KeepContentAlwaysInViewport ? position * bw / Size : position * bw / (Size + bw);
+					var bx2 = KeepContentAlwaysInViewport ? Math.Min (((position + bw) * bw / Size) + 1, bw - 1) : (position + bw) * bw / (Size + bw);
+					if (KeepContentAlwaysInViewport && bx1 == bx2) {
+						bx1 = Math.Max (bx1 - 1, 0);
+					}
+
+					Move (0, row);
+					Driver.AddRune (Driver.LeftArrow);
+
+					bool hasLeftTee = false;
+					bool hasDiamond = false;
+					bool hasRightTee = false;
+					for (int x = 0; x < bw; x++) {
+						if ((x < bx1 || x >= bx2 + 1) && ((position > 0 && !hasLeftTee) || (hasLeftTee && hasRightTee))) {
+							special = Driver.Stipple;
+						} else {
+							if (x != bx2 && x > 1 && bx2 - bx1 == 0 && bx1 < bw - 1 && hasLeftTee && !hasDiamond) {
+								hasDiamond = true;
+								special = Driver.Diamond;
+							} else {
+								if (x == bx1 && !hasLeftTee) {
+									hasLeftTee = true;
+									posLeftTee = x;
+									special = Driver.LeftTee;
+								} else if ((position == 0 && x == bw - 1 || x >= bx2 || bx2 == 0) && !hasRightTee) {
+									hasRightTee = true;
+									posRightTee = x;
+									special = Driver.RightTee;
+								} else {
+									special = Driver.HLine;
+								}
+							}
+						}
+						Driver.AddRune (special);
+					}
+					if (!hasLeftTee) {
+						Move (Bounds.Width - 2, row);
+						Driver.AddRune (Driver.LeftTee);
+					}
+
+					Driver.AddRune (Driver.RightArrow);
+				}
+			}
+
+			if (contentBottomRightCorner != null && hosted && showBothScrollIndicator) {
+				contentBottomRightCorner.Redraw (contentBottomRightCorner.Bounds);
+			}
+		}
+
+		int lastLocation = -1;
+		int posBarOffset;
+
+		///<inheritdoc/>
+		public override bool MouseEvent (MouseEvent me)
+		{
+			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) {
+				return false;
+			}
+
+			if (Host != null && !Host.HasFocus) {
+				Host.SetFocus ();
+			}
+
+			int location = vertical ? me.Y : me.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
+				&& (Application.mouseGrabView == null || Application.mouseGrabView != this)) {
+				Application.GrabMouse (this);
+			} else if (me.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 (location == 0) {
+				if (pos > 0) {
+					Position = pos - 1;
+				}
+			} else if (location == barsize + 1) {
+				if (CanScroll (1, out _, vertical)) {
+					Position = pos + 1;
+				}
+			} else if (location > 0 && location < barsize + 1) {
+				//var b1 = pos * (Size > 0 ? barsize / Size : 0);
+				//var b2 = Size > 0
+				//	? (KeepContentAlwaysInViewport ? Math.Min (((pos + barsize) * barsize / Size) + 1, barsize - 1) : (pos + barsize) * barsize / Size)
+				//	: 0;
+				//if (KeepContentAlwaysInViewport && b1 == b2) {
+				//	b1 = Math.Max (b1 - 1, 0);
+				//}
+
+				if (lastLocation > -1 || (location >= posTopLeftTee && location <= posBottomRightTee
+				&& me.Flags.HasFlag (MouseFlags.Button1Pressed | MouseFlags.ReportMousePosition))) {
+					if (lastLocation == -1) {
+						lastLocation = location;
+						posBarOffset = keepContentAlwaysInViewport ? Math.Max (location - posTopLeftTee, 1) : 0;
+						return true;
+					}
+
+					if (location > lastLocation) {
+						if (location - posBarOffset < barsize) {
+							var np = ((location - posBarOffset) * Size / barsize) + (Size / barsize);
+							if (CanScroll (np - pos, out int nv, vertical)) {
+								Position = pos + nv;
+							}
+						} else if (CanScroll (Size - pos, out int nv, vertical)) {
+							Position = Math.Min (pos + nv, Size);
+						}
+					} else if (location < lastLocation) {
+						if (location - posBarOffset > 0) {
+							var np = ((location - posBarOffset) * Size / barsize) - (Size / barsize);
+							if (CanScroll (np - pos, out int nv, vertical)) {
+								Position = pos + nv;
+							}
+						} else {
+							Position = 0;
+						}
+					} else if (location - posBarOffset >= barsize && posBottomRightTee - posTopLeftTee >= 3 && CanScroll (Size - pos, out int nv, vertical)) {
+						Position = Math.Min (pos + nv, Size);
+					} else if (location - posBarOffset >= barsize - 1 && posBottomRightTee - posTopLeftTee <= 3 && CanScroll (Size - pos, out nv, vertical)) {
+						Position = Math.Min (pos + nv, Size);
+					} else if (location - posBarOffset <= 0 && posBottomRightTee - posTopLeftTee <= 3) {
+						Position = 0;
+					}
+				} else if (location > posBottomRightTee) {
+					if (CanScroll (barsize, out int nv, vertical)) {
+						Position = pos + nv;
+					}
+				} else if (location < posTopLeftTee) {
+					if (CanScroll (-barsize, out int nv, vertical)) {
+						Position = pos + nv;
+					}
+				} else if (location == 1 && posTopLeftTee <= 3) {
+					Position = 0;
+				} else if (location == barsize) {
+					if (CanScroll (Size - pos, out int nv, vertical)) {
+						Position = Math.Min (pos + nv, Size);
+					}
+				}
+			}
+
+			return true;
+		}
+
+		internal bool CanScroll (int n, out int max, bool isVertical = false)
+		{
+			if (Host == null) {
+				max = 0;
+				return false;
+			}
+			int s = GetBarsize (isVertical);
+			var newSize = Math.Max (Math.Min (size - s, position + n), 0);
+			max = size > s + newSize ? (newSize == 0 ? -position : n) : size - (s + position) - 1;
+			if (size >= s + newSize && max != 0) {
+				return true;
+			}
+			return false;
+		}
+
+		int GetBarsize (bool isVertical)
+		{
+			if (Host == null) {
+				return 0;
+			}
+			return isVertical ?
+				(KeepContentAlwaysInViewport ? Host.Bounds.Height + (showBothScrollIndicator ? -2 : -1) : 0) :
+				(KeepContentAlwaysInViewport ? Host.Bounds.Width + (showBothScrollIndicator ? -2 : -1) : 0);
+		}
+	}
+}

+ 36 - 334
Terminal.Gui/Views/ScrollView.cs

@@ -1,5 +1,5 @@
 //
 //
-// ScrollView.cs: ScrollView and ScrollBarView views.
+// ScrollView.cs: ScrollView view.
 //
 //
 // Authors:
 // Authors:
 //   Miguel de Icaza ([email protected])
 //   Miguel de Icaza ([email protected])
@@ -15,312 +15,6 @@ using System;
 using System.Reflection;
 using System.Reflection;
 
 
 namespace Terminal.Gui {
 namespace Terminal.Gui {
-	/// <summary>
-	/// ScrollBarViews are views that display a 1-character scrollbar, either horizontal or vertical
-	/// </summary>
-	/// <remarks>
-	/// <para>
-	///   The scrollbar is drawn to be a representation of the Size, assuming that the 
-	///   scroll position is set at Position.
-	/// </para>
-	/// <para>
-	///   If the region to display the scrollbar is larger than three characters, 
-	///   arrow indicators are drawn.
-	/// </para>
-	/// </remarks>
-	public class ScrollBarView : View {
-		bool vertical = false;
-		int size = 0, position = 0;
-
-		/// <summary>
-		/// If set to <c>true</c> this is a vertical scrollbar, otherwise, the scrollbar is horizontal.
-		/// </summary>
-		public bool IsVertical {
-			get => vertical;
-			set {
-				vertical = value;
-				SetNeedsDisplay ();
-			}
-		}
-
-		/// <summary>
-		/// The size of content the scrollbar represents. 
-		/// </summary>
-		/// <value>The size.</value>
-		/// <remarks>The <see cref="Size"/> is typically the size of the virtual content. E.g. when a Scrollbar is
-		/// part of a <see cref="ScrollView"/> the Size is set to the appropriate dimension of <see cref="ScrollView.ContentSize"/>.</remarks>
-		public int Size {
-			get => size;
-			set {
-				size = value;
-				SetNeedsDisplay ();
-			}
-		}
-
-		/// <summary>
-		/// This event is raised when the position on the scrollbar has changed.
-		/// </summary>
-		public event Action ChangedPosition;
-
-		/// <summary>
-		/// The position, relative to <see cref="Size"/>, to set the scrollbar at.
-		/// </summary>
-		/// <value>The position.</value>
-		public int Position {
-			get => position;
-			set {
-				position = value;
-				SetNeedsDisplay ();
-			}
-		}
-
-		/// <summary>
-		/// Get or sets the view that host this <see cref="ScrollView"/>
-		/// </summary>
-		public ScrollView Host { get; internal set; }
-
-		void SetPosition (int newPos)
-		{
-			Position = newPos;
-			ChangedPosition?.Invoke ();
-		}
-
-		/// <summary>
-		/// Initializes a new instance of the <see cref="Gui.ScrollBarView"/> class using <see cref="LayoutStyle.Absolute"/> layout.
-		/// </summary>
-		/// <param name="rect">Frame for the scrollbar.</param>
-		public ScrollBarView (Rect rect) : this (rect, 0, 0, false) { }
-
-		/// <summary>
-		/// Initializes a new instance of the <see cref="Gui.ScrollBarView"/> class using <see cref="LayoutStyle.Absolute"/> layout.
-		/// </summary>
-		/// <param name="rect">Frame for the scrollbar.</param>
-		/// <param name="size">The size that this scrollbar represents. Sets the <see cref="Size"/> property.</param>
-		/// <param name="position">The position within this scrollbar. Sets the <see cref="Position"/> property.</param>
-		/// <param name="isVertical">If set to <c>true</c> this is a vertical scrollbar, otherwise, the scrollbar is horizontal. Sets the <see cref="IsVertical"/> property.</param>
-		public ScrollBarView (Rect rect, int size, int position, bool isVertical) : base (rect)
-		{
-			Init (size, position, isVertical);
-		}
-
-		/// <summary>
-		/// Initializes a new instance of the <see cref="Gui.ScrollBarView"/> class using <see cref="LayoutStyle.Computed"/> layout.
-		/// </summary>
-		public ScrollBarView () : this (0, 0, false) { }
-
-		/// <summary>
-		/// Initializes a new instance of the <see cref="Gui.ScrollBarView"/> class using <see cref="LayoutStyle.Computed"/> layout.
-		/// </summary>
-		/// <param name="size">The size that this scrollbar represents.</param>
-		/// <param name="position">The position within this scrollbar.</param>
-		/// <param name="isVertical">If set to <c>true</c> this is a vertical scrollbar, otherwise, the scrollbar is horizontal.</param>
-		public ScrollBarView (int size, int position, bool isVertical) : base ()
-		{
-			Init (size, position, isVertical);
-		}
-
-		void Init (int size, int position, bool isVertical)
-		{
-			vertical = isVertical;
-			this.position = position;
-			this.size = size;
-			WantContinuousButtonPressed = true;
-		}
-
-		int posTopTee;
-		int posLeftTee;
-		int posBottomTee;
-		int posRightTee;
-
-		///<inheritdoc/>
-		public override void Redraw (Rect region)
-		{
-			if (ColorScheme == null || Size == 0)
-				return;
-
-			Driver.SetAttribute (ColorScheme.Normal);
-
-			if (Bounds.Height == 0) {
-				return;
-			}
-
-			if (vertical) {
-				if (region.Right < Bounds.Width - 1)
-					return;
-
-				var col = Bounds.Width - 1;
-				var bh = Bounds.Height;
-				Rune special;
-
-				if (bh < 4) {
-					var by1 = position * bh / Size;
-					var by2 = (position + bh) * bh / Size;
-
-					Move (col, 0);
-					if (Bounds.Height == 1) {
-						Driver.AddRune (Driver.Diamond);
-					} else {
-						Driver.AddRune (Driver.UpArrow);
-					}
-					if (Bounds.Height == 3) {
-						Move (col, 1);
-						Driver.AddRune (Driver.Diamond);
-					}
-					if (Bounds.Height > 1) {
-						Move (col, Bounds.Height - 1);
-						Driver.AddRune (Driver.DownArrow);
-					}
-				} else {
-					bh -= 2;
-					var by1 = position * bh / Size;
-					var by2 = Host.KeepContentAlwaysInViewport ? Math.Min (((position + bh) * bh / Size) + 1, bh - 1) : (position + bh) * bh / Size;
-					if (Host.KeepContentAlwaysInViewport && by1 == by2) {
-						by1 = Math.Max (by1 - 1, 0);
-					}
-
-					Move (col, 0);
-					Driver.AddRune (Driver.UpArrow);
-					Move (col, Bounds.Height - 1);
-					Driver.AddRune (Driver.DownArrow);
-
-					bool hasTopTee = false;
-					bool hasDiamond = false;
-					bool hasBottomTee = false;
-					for (int y = 0; y < bh; y++) {
-						Move (col, y + 1);
-						if ((y < by1 || y > by2) && ((position > 0 && !hasTopTee) || (hasTopTee && hasBottomTee))) {
-							special = Driver.Stipple;
-						} else {
-							if (y != by2 && y > 1 && by2 - by1 == 0 && by1 < bh - 1 && hasTopTee && !hasDiamond) {
-								hasDiamond = true;
-								special = Driver.Diamond;
-							} else {
-								if (y == by1 && !hasTopTee) {
-									hasTopTee = true;
-									posTopTee = y;
-									special = Driver.TopTee;
-								} else if ((position == 0 && y == bh - 1 || y >= by2 || by2 == 0) && !hasBottomTee) {
-									hasBottomTee = true;
-									posBottomTee = y;
-									special = Driver.BottomTee;
-								} else {
-									special = Driver.VLine;
-								}
-							}
-						}
-						Driver.AddRune (special);
-					}
-					if (!hasTopTee) {
-						Move (col, Bounds.Height - 2);
-						Driver.AddRune (Driver.TopTee);
-					}
-				}
-			} else {
-				if (region.Bottom < Bounds.Height - 1)
-					return;
-
-				var row = Bounds.Height - 1;
-				var bw = Bounds.Width;
-				Rune special;
-
-				if (bw < 4) {
-					var bx1 = position * bw / Size;
-					var bx2 = (position + bw) * bw / Size;
-
-					Move (0, row);
-					Driver.AddRune (Driver.LeftArrow);
-					Driver.AddRune (Driver.RightArrow);
-				} else {
-					bw -= 2;
-					var bx1 = position * bw / Size;
-					var bx2 = Host.KeepContentAlwaysInViewport ? Math.Min (((position + bw) * bw / Size) + 1, bw - 1) : (position + bw) * bw / Size;
-					if (Host.KeepContentAlwaysInViewport && bx1 == bx2) {
-						bx1 = Math.Max (bx1 - 1, 0);
-					}
-
-					Move (0, row);
-					Driver.AddRune (Driver.LeftArrow);
-
-					bool hasLeftTee = false;
-					bool hasDiamond = false;
-					bool hasRightTee = false;
-					for (int x = 0; x < bw; x++) {
-						if ((x < bx1 || x >= bx2 + 1) && ((position > 0 && !hasLeftTee) || (hasLeftTee && hasRightTee))) {
-							special = Driver.Stipple;
-						} else {
-							if (x != bx2 && x > 1 && bx2 - bx1 == 0 && bx1 < bw - 1 && hasLeftTee && !hasDiamond) {
-								hasDiamond = true;
-								special = Driver.Diamond;
-							} else {
-								if (x == bx1 && !hasLeftTee) {
-									hasLeftTee = true;
-									posLeftTee = x;
-									special = Driver.LeftTee;
-								} else if ((position == 0 && x == bw - 1 || x >= bx2 || bx2 == 0) && !hasRightTee) {
-									hasRightTee = true;
-									posRightTee = x;
-									special = Driver.RightTee;
-								} else {
-									special = Driver.HLine;
-								}
-							}
-						}
-						Driver.AddRune (special);
-					}
-					if (!hasLeftTee) {
-						Move (Bounds.Width -2, row);
-						Driver.AddRune (Driver.LeftTee);
-					}
-
-					Driver.AddRune (Driver.RightArrow);
-				}
-			}
-		}
-
-		///<inheritdoc/>
-		public override bool MouseEvent (MouseEvent me)
-		{
-			if (me.Flags != MouseFlags.Button1Pressed && me.Flags != MouseFlags.Button1Clicked &&
-				!me.Flags.HasFlag (MouseFlags.Button1Pressed | MouseFlags.ReportMousePosition)) {
-				return false;
-			}
-
-			int location = vertical ? me.Y : me.X;
-			int barsize = vertical ? Bounds.Height : Bounds.Width;
-			int posTopLeftTee = vertical ? posTopTee : posLeftTee;
-			int posBottomRightTee = vertical ? posBottomTee : posRightTee;
-
-			barsize -= 2;
-			var pos = Position;
-			if (location == 0) {
-				if (pos > 0) {
-					SetPosition (pos - 1);
-				}
-			} else if (location == barsize + 1) {
-				if (Host.CanScroll (1, out _, vertical)) {
-					SetPosition (pos + 1);
-				}
-			} else if (location > 0 && location < barsize + 1) {
-				var b1 = pos * barsize / Size;
-				var b2 = Host.KeepContentAlwaysInViewport ? Math.Min (((pos + barsize) * barsize / Size) + 1, barsize - 1) : (pos + barsize) * barsize / Size;
-				if (Host.KeepContentAlwaysInViewport && b1 == b2) {
-					b1 = Math.Max (b1 - 1, 0);
-				}
-
-				if (location > b2 + 1 && location > posTopLeftTee && location > b1 && location > posBottomRightTee && posBottomRightTee > 0) {
-					Host.CanScroll (location, out int nv, vertical);
-					if (nv > 0) {
-						SetPosition (Math.Min (pos + nv, Size));
-					}
-				} else if (location <= b1) {
-					SetPosition (Math.Max (pos - barsize - location, 0));
-				}
-			}
-
-			return true;
-		}
-	}
-
 	/// <summary>
 	/// <summary>
 	/// Scrollviews are views that present a window into a virtual space where subviews are added.  Similar to the iOS UIScrollView.
 	/// Scrollviews are views that present a window into a virtual space where subviews are added.  Similar to the iOS UIScrollView.
 	/// </summary>
 	/// </summary>
@@ -384,6 +78,8 @@ namespace Terminal.Gui {
 
 
 			MouseEnter += View_MouseEnter;
 			MouseEnter += View_MouseEnter;
 			MouseLeave += View_MouseLeave;
 			MouseLeave += View_MouseLeave;
+			contentView.MouseEnter += View_MouseEnter;
+			contentView.MouseLeave += View_MouseLeave;
 		}
 		}
 
 
 		Size contentSize;
 		Size contentSize;
@@ -394,7 +90,7 @@ namespace Terminal.Gui {
 		bool autoHideScrollBars = true;
 		bool autoHideScrollBars = true;
 
 
 		/// <summary>
 		/// <summary>
-		/// Represents the contents of the data shown inside the scrolview
+		/// Represents the contents of the data shown inside the scrollview
 		/// </summary>
 		/// </summary>
 		/// <value>The size of the content.</value>
 		/// <value>The size of the content.</value>
 		public Size ContentSize {
 		public Size ContentSize {
@@ -421,11 +117,20 @@ namespace Terminal.Gui {
 				return contentOffset;
 				return contentOffset;
 			}
 			}
 			set {
 			set {
-				contentOffset = new Point (-Math.Abs (value.X), -Math.Abs (value.Y));
-				contentView.Frame = new Rect (contentOffset, contentSize);
-				vertical.Position = Math.Max (0, -contentOffset.Y);
-				horizontal.Position = Math.Max (0, -contentOffset.X);
-				SetNeedsDisplay ();
+				var co = new Point (-Math.Abs (value.X), -Math.Abs (value.Y));
+				if (contentOffset != co) {
+					contentOffset = co;
+					contentView.Frame = new Rect (contentOffset, contentSize);
+					var p = Math.Max (0, -contentOffset.Y);
+					if (vertical.Position != p) {
+						vertical.Position = Math.Max (0, -contentOffset.Y);
+					}
+					p = Math.Max (0, -contentOffset.X);
+					if (horizontal.Position != p) {
+						horizontal.Position = Math.Max (0, -contentOffset.X);
+					}
+					SetNeedsDisplay ();
+				}
 			}
 			}
 		}
 		}
 
 
@@ -450,6 +155,8 @@ namespace Terminal.Gui {
 			set {
 			set {
 				if (keepContentAlwaysInViewport != value) {
 				if (keepContentAlwaysInViewport != value) {
 					keepContentAlwaysInViewport = value;
 					keepContentAlwaysInViewport = value;
+					vertical.OtherScrollBarView.KeepContentAlwaysInViewport = value;
+					horizontal.OtherScrollBarView.KeepContentAlwaysInViewport = value;
 					Point p = default;
 					Point p = default;
 					if (value && -contentOffset.X + Bounds.Width > contentSize.Width) {
 					if (value && -contentOffset.X + Bounds.Width > contentSize.Width) {
 						p = new Point (contentSize.Width - Bounds.Width + (showVerticalScrollIndicator ? 1 : 0), -contentOffset.Y);
 						p = new Point (contentSize.Width - Bounds.Width + (showVerticalScrollIndicator ? 1 : 0), -contentOffset.Y);
@@ -484,7 +191,9 @@ namespace Terminal.Gui {
 
 
 		void View_MouseLeave (MouseEventArgs e)
 		void View_MouseLeave (MouseEventArgs e)
 		{
 		{
-			Application.UngrabMouse ();
+			if (Application.mouseGrabView != null && Application.mouseGrabView != vertical && Application.mouseGrabView != horizontal) {
+				Application.UngrabMouse ();
+			}
 		}
 		}
 
 
 		void View_MouseEnter (MouseEventArgs e)
 		void View_MouseEnter (MouseEventArgs e)
@@ -507,17 +216,21 @@ namespace Terminal.Gui {
 		public bool ShowHorizontalScrollIndicator {
 		public bool ShowHorizontalScrollIndicator {
 			get => showHorizontalScrollIndicator;
 			get => showHorizontalScrollIndicator;
 			set {
 			set {
-				if (value == showHorizontalScrollIndicator)
+				if (value == showHorizontalScrollIndicator) {
 					return;
 					return;
+				}
 
 
 				showHorizontalScrollIndicator = value;
 				showHorizontalScrollIndicator = value;
 				SetNeedsLayout ();
 				SetNeedsLayout ();
 				if (value) {
 				if (value) {
 					base.Add (horizontal);
 					base.Add (horizontal);
+					horizontal.OtherScrollBarView = vertical;
+					horizontal.OtherScrollBarView.ShowScrollIndicator = value;
 					horizontal.MouseEnter += View_MouseEnter;
 					horizontal.MouseEnter += View_MouseEnter;
 					horizontal.MouseLeave += View_MouseLeave;
 					horizontal.MouseLeave += View_MouseLeave;
 				} else {
 				} else {
-					Remove (horizontal);
+					base.Remove (horizontal);
+					horizontal.OtherScrollBarView = null;
 					horizontal.MouseEnter -= View_MouseEnter;
 					horizontal.MouseEnter -= View_MouseEnter;
 					horizontal.MouseLeave -= View_MouseLeave;
 					horizontal.MouseLeave -= View_MouseLeave;
 				}
 				}
@@ -542,17 +255,21 @@ namespace Terminal.Gui {
 		public bool ShowVerticalScrollIndicator {
 		public bool ShowVerticalScrollIndicator {
 			get => showVerticalScrollIndicator;
 			get => showVerticalScrollIndicator;
 			set {
 			set {
-				if (value == showVerticalScrollIndicator)
+				if (value == showVerticalScrollIndicator) {
 					return;
 					return;
+				}
 
 
 				showVerticalScrollIndicator = value;
 				showVerticalScrollIndicator = value;
 				SetNeedsLayout ();
 				SetNeedsLayout ();
 				if (value) {
 				if (value) {
 					base.Add (vertical);
 					base.Add (vertical);
+					vertical.OtherScrollBarView = horizontal;
+					vertical.OtherScrollBarView.ShowScrollIndicator = value;
 					vertical.MouseEnter += View_MouseEnter;
 					vertical.MouseEnter += View_MouseEnter;
 					vertical.MouseLeave += View_MouseLeave;
 					vertical.MouseLeave += View_MouseLeave;
 				} else {
 				} else {
 					Remove (vertical);
 					Remove (vertical);
+					vertical.OtherScrollBarView = null;
 					vertical.MouseEnter -= View_MouseEnter;
 					vertical.MouseEnter -= View_MouseEnter;
 					vertical.MouseLeave -= View_MouseLeave;
 					vertical.MouseLeave -= View_MouseLeave;
 				}
 				}
@@ -706,7 +423,7 @@ namespace Terminal.Gui {
 		/// <param name="lines">Number of lines to scroll.</param>
 		/// <param name="lines">Number of lines to scroll.</param>
 		public bool ScrollDown (int lines)
 		public bool ScrollDown (int lines)
 		{
 		{
-			if (CanScroll (lines, out _, true)) {
+			if (vertical.CanScroll (lines, out _, true)) {
 				ContentOffset = new Point (contentOffset.X, contentOffset.Y - lines);
 				ContentOffset = new Point (contentOffset.X, contentOffset.Y - lines);
 				return true;
 				return true;
 			}
 			}
@@ -720,28 +437,13 @@ namespace Terminal.Gui {
 		/// <param name="cols">Number of columns to scroll by.</param>
 		/// <param name="cols">Number of columns to scroll by.</param>
 		public bool ScrollRight (int cols)
 		public bool ScrollRight (int cols)
 		{
 		{
-			if (CanScroll (cols, out _)) {
+			if (horizontal.CanScroll (cols, out _)) {
 				ContentOffset = new Point (contentOffset.X - cols, contentOffset.Y);
 				ContentOffset = new Point (contentOffset.X - cols, contentOffset.Y);
 				return true;
 				return true;
 			}
 			}
 			return false;
 			return false;
 		}
 		}
 
 
-		internal bool CanScroll (int n, out int max, bool isVertical = false)
-		{
-			var size = isVertical ?
-				(KeepContentAlwaysInViewport ? Bounds.Height + (showHorizontalScrollIndicator ? -2 : -1) : 0) :
-				(KeepContentAlwaysInViewport ? Bounds.Width + (showVerticalScrollIndicator ? -2 : -1) : 0);
-			var cSize = isVertical ? -contentSize.Height : -contentSize.Width;
-			var cOffSet = isVertical ? contentOffset.Y : contentOffset.X;
-			var newSize = Math.Max (cSize, cOffSet - n);
-			max = cSize < newSize - size ? n : -cSize + (cOffSet - size) - 1;
-			if (cSize < newSize - size) {
-				return true;
-			}
-			return false;
-		}
-
 		///<inheritdoc/>
 		///<inheritdoc/>
 		public override bool ProcessKey (KeyEvent kb)
 		public override bool ProcessKey (KeyEvent kb)
 		{
 		{

+ 16 - 92
Terminal.Gui/Views/TextField.cs

@@ -115,8 +115,6 @@ namespace Terminal.Gui {
 			get => base.Frame;
 			get => base.Frame;
 			set {
 			set {
 				base.Frame = value;
 				base.Frame = value;
-				var w = base.Frame.Width;
-				first = point > w ? point - w : 0;
 				Adjust ();
 				Adjust ();
 			}
 			}
 		}
 		}
@@ -157,7 +155,7 @@ namespace Terminal.Gui {
 				TextChanged?.Invoke (oldText);
 				TextChanged?.Invoke (oldText);
 
 
 				if (point > text.Count) {
 				if (point > text.Count) {
-					point = Math.Max (DisplaySize (text, 0).size - 1, 0);
+					point = Math.Max (TextModel.DisplaySize (text, 0).size - 1, 0);
 				}
 				}
 
 
 				Adjust ();
 				Adjust ();
@@ -195,7 +193,7 @@ namespace Terminal.Gui {
 				if (idx == point)
 				if (idx == point)
 					break;
 					break;
 				var cols = Rune.ColumnWidth (text [idx]);
 				var cols = Rune.ColumnWidth (text [idx]);
-				col = SetCol (col, Frame.Width - 1, cols);
+				col = TextModel.SetCol (col, Frame.Width - 1, cols);
 			}
 			}
 			Move (col, 0);
 			Move (col, 0);
 		}
 		}
@@ -211,9 +209,9 @@ namespace Terminal.Gui {
 
 
 			int p = first;
 			int p = first;
 			int col = 0;
 			int col = 0;
-			int width = Frame.Width;
+			int width = Frame.Width + OffSetBackground ();
 			var tcount = text.Count;
 			var tcount = text.Count;
-			var roc = new Attribute (Color.DarkGray, Color.Gray);
+			var roc = Colors.Menu.Disabled;
 			for (int idx = p; idx < tcount; idx++) {
 			for (int idx = p; idx < tcount; idx++) {
 				var rune = text [idx];
 				var rune = text [idx];
 				var cols = Rune.ColumnWidth (rune);
 				var cols = Rune.ColumnWidth (rune);
@@ -227,7 +225,7 @@ namespace Terminal.Gui {
 				if (col + cols <= width) {
 				if (col + cols <= width) {
 					Driver.AddRune ((Rune)(Secret ? '*' : rune));
 					Driver.AddRune ((Rune)(Secret ? '*' : rune));
 				}
 				}
-				col = SetCol (col, width, cols);
+				col = TextModel.SetCol (col, width, cols);
 				if (idx + 1 < tcount && col + Rune.ColumnWidth (text [idx + 1]) > width) {
 				if (idx + 1 < tcount && col + Rune.ColumnWidth (text [idx + 1]) > width) {
 					break;
 					break;
 				}
 				}
@@ -241,45 +239,15 @@ namespace Terminal.Gui {
 			PositionCursor ();
 			PositionCursor ();
 		}
 		}
 
 
-		static int SetCol (int col, int width, int cols)
-		{
-			if (col + cols <= width) {
-				col += cols;
-			}
-
-			return col;
-		}
-
-		// Returns the size and length in a range of the string.
-		(int size, int length) DisplaySize (List<Rune> t, int start = -1, int end = -1, bool checkNextRune = true)
-		{
-			if (t == null || t.Count == 0) {
-				return (0, 0);
-			}
-			int size = 0;
-			int len = 0;
-			int tcount = end == -1 ? t.Count : end > t.Count ? t.Count : end;
-			int i = start == -1 ? 0 : start;
-			for (; i < tcount; i++) {
-				var rune = t [i];
-				size += Rune.ColumnWidth (rune);
-				len += Rune.RuneLen (rune);
-				if (checkNextRune && i == tcount - 1 && t.Count > tcount && Rune.ColumnWidth (t [i + 1]) > 1) {
-					size += Rune.ColumnWidth (t [i + 1]);
-					len += Rune.RuneLen (t [i + 1]);
-				}
-			}
-			return (size, len);
-		}
-
 		void Adjust ()
 		void Adjust ()
 		{
 		{
 			int offB = OffSetBackground ();
 			int offB = OffSetBackground ();
 			if (point < first) {
 			if (point < first) {
 				first = point;
 				first = point;
 			} else if (first + point - (Frame.Width + offB) == 0 ||
 			} else if (first + point - (Frame.Width + offB) == 0 ||
-				  DisplaySize (text, first, point).size >= Frame.Width + offB) {
-				first = Math.Max (CalculateFirst (text, first, point, Frame.Width - 1 + offB), 0);
+				  TextModel.DisplaySize (text, first, point).size >= Frame.Width + offB) {
+				first = Math.Max (TextModel.CalculateLeftColumn (text, first,
+					point, Frame.Width - 1 + offB, point), 0);
 			}
 			}
 			SetNeedsDisplay ();
 			SetNeedsDisplay ();
 		}
 		}
@@ -294,33 +262,6 @@ namespace Terminal.Gui {
 			return offB;
 			return offB;
 		}
 		}
 
 
-		int CalculateFirst (List<Rune> t, int start, int end, int width)
-		{
-			if (t == null) {
-				return 0;
-			}
-			(var dSize, _) = DisplaySize (t, start, end);
-			if (dSize < width) {
-				return start;
-			}
-			int size = 0;
-			int tcount = end > t.Count - 1 ? t.Count - 1 : end;
-			int col = 0;
-			for (int i = tcount; i > start; i--) {
-				var rune = t [i];
-				var s = Rune.ColumnWidth (rune);
-				size += s;
-				if (size >= dSize - width) {
-					col = tcount - i + start;
-					if (start == 0 || col == start || (point == t.Count && (point - col > width))) {
-						col++;
-					}
-					break;
-				}
-			}
-			return col;
-		}
-
 		void SetText (List<Rune> newText)
 		void SetText (List<Rune> newText)
 		{
 		{
 			Text = ustring.Make (newText);
 			Text = ustring.Make (newText);
@@ -779,7 +720,7 @@ namespace Terminal.Gui {
 		{
 		{
 			// We could also set the cursor position.
 			// We could also set the cursor position.
 			int x;
 			int x;
-			var pX = GetPointFromX (text, first, ev.X);
+			var pX = TextModel.GetColFromX (text, first, ev.X);
 			if (text.Count == 0) {
 			if (text.Count == 0) {
 				x = pX - ev.OfX;
 				x = pX - ev.OfX;
 			} else {
 			} else {
@@ -792,7 +733,7 @@ namespace Terminal.Gui {
 		{
 		{
 			int pX = x;
 			int pX = x;
 			if (getX) {
 			if (getX) {
-				pX = GetPointFromX (text, first, x);
+				pX = TextModel.GetColFromX (text, first, x);
 			}
 			}
 			if (first + pX > text.Count) {
 			if (first + pX > text.Count) {
 				point = text.Count;
 				point = text.Count;
@@ -805,23 +746,6 @@ namespace Terminal.Gui {
 			return point;
 			return point;
 		}
 		}
 
 
-		int GetPointFromX (List<Rune> t, int start, int x)
-		{
-			if (x < 0) {
-				return x;
-			}
-			int size = start;
-			var pX = x + start;
-			for (int i = start; i < t.Count; i++) {
-				var r = t [i];
-				size += Rune.ColumnWidth (r);
-				if (i == pX || (size > pX)) {
-					return i - start;
-				}
-			}
-			return t.Count - start;
-		}
-
 		void PrepareSelection (int x, int direction = 0)
 		void PrepareSelection (int x, int direction = 0)
 		{
 		{
 			x = x + first < 0 ? 0 : x;
 			x = x + first < 0 ? 0 : x;
@@ -897,9 +821,9 @@ namespace Terminal.Gui {
 			ustring actualText = Text;
 			ustring actualText = Text;
 			int selStart = SelectedLength < 0 ? SelectedLength + SelectedStart : SelectedStart;
 			int selStart = SelectedLength < 0 ? SelectedLength + SelectedStart : SelectedStart;
 			int selLength = Math.Abs (SelectedLength);
 			int selLength = Math.Abs (SelectedLength);
-			(var _, var len) = DisplaySize (text, 0, selStart, false);
-			(var _, var len2) = DisplaySize (text, selStart, selStart + selLength, false);
-			(var _, var len3) = DisplaySize (text, selStart + selLength, actualText.RuneCount, false);
+			(var _, var len) = TextModel.DisplaySize (text, 0, selStart, false);
+			(var _, var len2) = TextModel.DisplaySize (text, selStart, selStart + selLength, false);
+			(var _, var len3) = TextModel.DisplaySize (text, selStart + selLength, actualText.RuneCount, false);
 			Text = actualText[0, len] +
 			Text = actualText[0, len] +
 				actualText[len + len2, len + len2 + len3];
 				actualText[len + len2, len + len2 + len3];
 			ClearAllSelection ();
 			ClearAllSelection ();
@@ -919,9 +843,9 @@ namespace Terminal.Gui {
 			SetSelectedStartSelectedLength ();
 			SetSelectedStartSelectedLength ();
 			int selStart = start == -1 ? CursorPosition : start;
 			int selStart = start == -1 ? CursorPosition : start;
 			ustring actualText = Text;
 			ustring actualText = Text;
-			(int _, int len) = DisplaySize (text, 0, selStart, false);
-			(var _, var len2) = DisplaySize (text, selStart, selStart + length, false);
-			(var _, var len3) = DisplaySize (text, selStart + length, actualText.RuneCount, false);
+			(int _, int len) = TextModel.DisplaySize (text, 0, selStart, false);
+			(var _, var len2) = TextModel.DisplaySize (text, selStart, selStart + length, false);
+			(var _, var len3) = TextModel.DisplaySize (text, selStart + length, actualText.RuneCount, false);
 			ustring cbTxt = Clipboard.Contents ?? "";
 			ustring cbTxt = Clipboard.Contents ?? "";
 			Text = actualText [0, len] +
 			Text = actualText [0, len] +
 				cbTxt +
 				cbTxt +

+ 298 - 129
Terminal.Gui/Views/TextView.cs

@@ -171,6 +171,101 @@ namespace Terminal.Gui {
 		{
 		{
 			lines.RemoveAt (pos);
 			lines.RemoveAt (pos);
 		}
 		}
+
+		/// <summary>
+		/// Returns the maximum line length of the visible lines.
+		/// </summary>
+		/// <param name="first">The first line.</param>
+		/// <param name="last">The last line.</param>
+		public int GetMaxVisibleLine (int first, int last)
+		{
+			int maxLength = 0;
+			last = last < lines.Count ? last : lines.Count;
+			for (int i = first; i < last; i++) {
+				var l = GetLine (i).Count;
+				if (l > maxLength) {
+					maxLength = l;
+				}
+			}
+
+			return maxLength;
+		}
+
+		internal static int SetCol (int col, int width, int cols)
+		{
+			if (col + cols <= width) {
+				col += cols;
+			}
+
+			return col;
+		}
+
+		internal static int GetColFromX (List<Rune> t, int start, int x)
+		{
+			if (x < 0) {
+				return x;
+			}
+			int size = start;
+			var pX = x + start;
+			for (int i = start; i < t.Count; i++) {
+				var r = t [i];
+				size += Rune.ColumnWidth (r);
+				if (i == pX || (size > pX)) {
+					return i - start;
+				}
+			}
+			return t.Count - start;
+		}
+
+		// Returns the size and length in a range of the string.
+		internal static (int size, int length) DisplaySize (List<Rune> t, int start = -1, int end = -1, bool checkNextRune = true)
+		{
+			if (t == null || t.Count == 0) {
+				return (0, 0);
+			}
+			int size = 0;
+			int len = 0;
+			int tcount = end == -1 ? t.Count : end > t.Count ? t.Count : end;
+			int i = start == -1 ? 0 : start;
+			for (; i < tcount; i++) {
+				var rune = t [i];
+				size += Rune.ColumnWidth (rune);
+				len += Rune.RuneLen (rune);
+				if (checkNextRune && i == tcount - 1 && t.Count > tcount && Rune.ColumnWidth (t [i + 1]) > 1) {
+					size += Rune.ColumnWidth (t [i + 1]);
+					len += Rune.RuneLen (t [i + 1]);
+				}
+			}
+			return (size, len);
+		}
+
+		// Returns the left column in a range of the string.
+		internal static int CalculateLeftColumn (List<Rune> t, int start, int end, int width, int currentColumn)
+		{
+			if (t == null) {
+				return 0;
+			}
+			(var dSize, _) = TextModel.DisplaySize (t, start, end);
+			if (dSize < width) {
+				return start;
+			}
+			int size = 0;
+			int tcount = end > t.Count - 1 ? t.Count - 1 : end;
+			int col = 0;
+			for (int i = tcount; i > start; i--) {
+				var rune = t [i];
+				var s = Rune.ColumnWidth (rune);
+				size += s;
+				if (size >= dSize - width) {
+					col = tcount - i + start;
+					if (start == 0 || col == start || (currentColumn == t.Count && (currentColumn - col > width))) {
+						col++;
+					}
+					break;
+				}
+			}
+			return col;
+		}
 	}
 	}
 
 
 	/// <summary>
 	/// <summary>
@@ -356,6 +451,35 @@ namespace Terminal.Gui {
 			}
 			}
 		}
 		}
 
 
+		///<inheritdoc/>
+		public override Rect Frame {
+			get => base.Frame;
+			set {
+				base.Frame = value;
+				Adjust ();
+			}
+		}
+
+		/// <summary>
+		/// Gets or sets the top row.
+		/// </summary>
+		public int TopRow { get => topRow; set => topRow = Math.Max (Math.Min (value, Lines - 1), 0); }
+
+		/// <summary>
+		/// Gets or sets the left column.
+		/// </summary>
+		public int LeftColumn { get => leftColumn; set => leftColumn = Math.Max (Math.Min (value, Maxlength - 1), 0); }
+
+		/// <summary>
+		/// Gets the maximum visible length line.
+		/// </summary>
+		public int Maxlength => model.GetMaxVisibleLine (topRow, topRow + Frame.Height);
+
+		/// <summary>
+		/// Gets the  number of lines.
+		/// </summary>
+		public int Lines => model.Count;
+
 		/// <summary>
 		/// <summary>
 		/// Loads the contents of the file into the  <see cref="TextView"/>.
 		/// Loads the contents of the file into the  <see cref="TextView"/>.
 		/// </summary>
 		/// </summary>
@@ -419,7 +543,24 @@ namespace Terminal.Gui {
 
 
 				SetNeedsDisplay (new Rect (0, minRow, Frame.Width, maxRow));
 				SetNeedsDisplay (new Rect (0, minRow, Frame.Width, maxRow));
 			}
 			}
-			Move (CurrentColumn - leftColumn, CurrentRow - topRow);
+			var line = model.GetLine (currentRow);
+			var retreat = 0;
+			var col = 0;
+			if (line.Count > 0) {
+				retreat = Math.Max ((SpecialRune (line [Math.Max (CurrentColumn - leftColumn - 1, 0)])
+				? 1 : 0), 0);
+				for (int idx = leftColumn < 0 ? 0 : leftColumn; idx < line.Count; idx++) {
+					if (idx == CurrentColumn)
+						break;
+					var cols = Rune.ColumnWidth (line [idx]);
+					col += cols - 1;
+				}
+			}
+			var ccol = CurrentColumn - leftColumn - retreat + col;
+			if (leftColumn <= CurrentColumn && ccol < Frame.Width
+				&& topRow <= CurrentRow && CurrentRow - topRow < Frame.Height) {
+				Move (ccol, CurrentRow - topRow);
+			}
 		}
 		}
 
 
 		void ClearRegion (int left, int top, int right, int bottom)
 		void ClearRegion (int left, int top, int right, int bottom)
@@ -563,20 +704,36 @@ namespace Terminal.Gui {
 				}
 				}
 
 
 				Move (bounds.Left, row);
 				Move (bounds.Left, row);
-				for (int col = bounds.Left; col < right; col++) {
-					var lineCol = leftColumn + col;
+				var col = 0;
+				for (int idx = bounds.Left; idx < right; idx++) {
+					var lineCol = leftColumn + idx;
 					var rune = lineCol >= lineRuneCount ? ' ' : line [lineCol];
 					var rune = lineCol >= lineRuneCount ? ' ' : line [lineCol];
-					if (selecting && PointInSelection (col, row))
+					var cols = Rune.ColumnWidth (rune);
+					if (selecting && PointInSelection (idx, row)) {
 						ColorSelection ();
 						ColorSelection ();
-					else
+					} else {
 						ColorNormal ();
 						ColorNormal ();
+					}
 
 
-					AddRune (col, row, rune);
+					if (!SpecialRune (rune)) {
+						AddRune (col, row, rune);
+					}
+					col = TextModel.SetCol (col, bounds.Right, cols);
 				}
 				}
 			}
 			}
 			PositionCursor ();
 			PositionCursor ();
 		}
 		}
 
 
+		bool SpecialRune (Rune rune)
+		{
+			switch (rune) {
+			case (uint)Key.Enter:
+			case 0xd:
+				return true;
+			default:
+				return false;			}
+		}
+
 		///<inheritdoc/>
 		///<inheritdoc/>
 		public override bool CanFocus {
 		public override bool CanFocus {
 			get => base.CanFocus;
 			get => base.CanFocus;
@@ -622,19 +779,25 @@ namespace Terminal.Gui {
 
 
 		void InsertText (ustring text)
 		void InsertText (ustring text)
 		{
 		{
+			if (ustring.IsNullOrEmpty (text)) {
+				return;
+			}
+
 			var lines = TextModel.StringToRunes (text);
 			var lines = TextModel.StringToRunes (text);
 
 
-			if (lines.Count == 0)
+			if (lines.Count == 0) {
 				return;
 				return;
+			}
 
 
 			var line = GetCurrentLine ();
 			var line = GetCurrentLine ();
 
 
-			// Optmize single line
+			// Optimize single line
 			if (lines.Count == 1) {
 			if (lines.Count == 1) {
 				line.InsertRange (currentColumn, lines [0]);
 				line.InsertRange (currentColumn, lines [0]);
 				currentColumn += lines [0].Count;
 				currentColumn += lines [0].Count;
-				if (currentColumn - leftColumn > Frame.Width)
+				if (currentColumn - leftColumn > Frame.Width) {
 					leftColumn = currentColumn - Frame.Width + 1;
 					leftColumn = currentColumn - Frame.Width + 1;
+				}
 				SetNeedsDisplay (new Rect (0, currentRow - topRow, Frame.Width, currentRow - topRow + 1));
 				SetNeedsDisplay (new Rect (0, currentRow - topRow, Frame.Width, currentRow - topRow + 1));
 				return;
 				return;
 			}
 			}
@@ -647,26 +810,18 @@ namespace Terminal.Gui {
 			// First line is inserted at the current location, the rest is appended
 			// First line is inserted at the current location, the rest is appended
 			line.InsertRange (currentColumn, lines [0]);
 			line.InsertRange (currentColumn, lines [0]);
 
 
-			for (int i = 1; i < lines.Count; i++)
+			for (int i = 1; i < lines.Count; i++) {
 				model.AddLine (currentRow + i, lines [i]);
 				model.AddLine (currentRow + i, lines [i]);
+			}
 
 
 			var last = model.GetLine (currentRow + lines.Count - 1);
 			var last = model.GetLine (currentRow + lines.Count - 1);
 			var lastp = last.Count;
 			var lastp = last.Count;
 			last.InsertRange (last.Count, rest);
 			last.InsertRange (last.Count, rest);
 
 
-			// Now adjjust column and row positions
+			// Now adjust column and row positions
 			currentRow += lines.Count - 1;
 			currentRow += lines.Count - 1;
 			currentColumn = lastp;
 			currentColumn = lastp;
-			if (currentRow - topRow > Frame.Height) {
-				topRow = currentRow - Frame.Height + 1;
-				if (topRow < 0)
-					topRow = 0;
-			}
-			if (currentColumn < leftColumn)
-				leftColumn = currentColumn;
-			if (currentColumn - leftColumn >= Frame.Width)
-				leftColumn = currentColumn - Frame.Width + 1;
-			SetNeedsDisplay ();
+			Adjust ();
 		}
 		}
 
 
 		// The column we are tracking, or -1 if we are not tracking any column
 		// The column we are tracking, or -1 if we are not tracking any column
@@ -688,38 +843,63 @@ namespace Terminal.Gui {
 
 
 		void Adjust ()
 		void Adjust ()
 		{
 		{
+			var offB = OffSetBackground ();
+			var line = GetCurrentLine ();
 			bool need = false;
 			bool need = false;
 			if (currentColumn < leftColumn) {
 			if (currentColumn < leftColumn) {
-				currentColumn = leftColumn;
+				leftColumn = currentColumn;
 				need = true;
 				need = true;
-			}
-			if (currentColumn - leftColumn > Frame.Width) {
-				leftColumn = currentColumn - Frame.Width + 1;
+			} else if (currentColumn - leftColumn > Frame.Width + offB.width ||
+				TextModel.DisplaySize (line, leftColumn, currentColumn).size >= Frame.Width + offB.width) {
+				leftColumn = Math.Max (TextModel.CalculateLeftColumn (line, leftColumn,
+					currentColumn, Frame.Width - 1 + offB.width, currentColumn), 0);
 				need = true;
 				need = true;
 			}
 			}
 			if (currentRow < topRow) {
 			if (currentRow < topRow) {
 				topRow = currentRow;
 				topRow = currentRow;
 				need = true;
 				need = true;
-			}
-			if (currentRow - topRow > Frame.Height) {
-				topRow = currentRow - Frame.Height + 1;
+			} else if (currentRow - topRow >= Frame.Height + offB.height) {
+				topRow = Math.Min (Math.Max (currentRow - Frame.Height + 1, 0), currentRow);
 				need = true;
 				need = true;
 			}
 			}
-			if (need)
+			if (need) {
 				SetNeedsDisplay ();
 				SetNeedsDisplay ();
-			else
+			} else {
 				PositionCursor ();
 				PositionCursor ();
+			}
+		}
+
+		(int width, int height) OffSetBackground ()
+		{
+			int w = 0;
+			int h = 0;
+			if (SuperView?.Frame.Right - Frame.Right < 0) {
+				w = SuperView.Frame.Right - Frame.Right - 1;
+			}
+			if (SuperView?.Frame.Bottom - Frame.Bottom < 0) {
+				h = SuperView.Frame.Bottom - Frame.Bottom - 1;
+			}
+			return (w, h);
 		}
 		}
 
 
 		/// <summary>
 		/// <summary>
-		/// Will scroll the <see cref="TextView"/> to display the specified row at the top
+		/// Will scroll the <see cref="TextView"/> to display the specified row at the top if <paramref name="isRow"/> is true or
+		/// will scroll the <see cref="TextView"/> to display the specified column at the left if <paramref name="isRow"/> is false.
 		/// </summary>
 		/// </summary>
-		/// <param name="row">Row that should be displayed at the top, if the value is negative it will be reset to zero</param>
-		public void ScrollTo (int row)
+		/// <param name="idx">Row that should be displayed at the top or Column that should be displayed at the left,
+		///  if the value is negative it will be reset to zero</param>
+		/// <param name="isRow">If true (default) the <paramref name="idx"/> is a row, column otherwise.</param>
+		public void ScrollTo (int idx, bool isRow = true)
 		{
 		{
-			if (row < 0)
-				row = 0;
-			topRow = row > model.Count ? model.Count - 1 : row;
+			if (idx < 0) {
+				idx = 0;
+			}
+			if (isRow) {
+				topRow = Math.Max (idx > model.Count - 1 ? model.Count - 1 : idx, 0);
+			} else {
+				var maxlength = model.GetMaxVisibleLine (topRow, topRow + Frame.Height);
+				leftColumn = Math.Max (idx > maxlength - 1 ? maxlength - 1 : idx, 0);
+			}
 			SetNeedsDisplay ();
 			SetNeedsDisplay ();
 		}
 		}
 
 
@@ -767,7 +947,7 @@ namespace Terminal.Gui {
 				break;
 				break;
 
 
 			case Key.PageUp:
 			case Key.PageUp:
-			case ((int)'v' + Key.AltMask):
+			case ((int)'V' + Key.AltMask):
 				int nPageUpShift = Frame.Height - 1;
 				int nPageUpShift = Frame.Height - 1;
 				if (currentRow > 0) {
 				if (currentRow > 0) {
 					if (columnTrack == -1)
 					if (columnTrack == -1)
@@ -797,53 +977,33 @@ namespace Terminal.Gui {
 				var currentLine = GetCurrentLine ();
 				var currentLine = GetCurrentLine ();
 				if (currentColumn < currentLine.Count) {
 				if (currentColumn < currentLine.Count) {
 					currentColumn++;
 					currentColumn++;
-					if (currentColumn >= leftColumn + Frame.Width) {
-						leftColumn++;
-						SetNeedsDisplay ();
-					}
-					PositionCursor ();
 				} else {
 				} else {
 					if (currentRow + 1 < model.Count) {
 					if (currentRow + 1 < model.Count) {
 						currentRow++;
 						currentRow++;
 						currentColumn = 0;
 						currentColumn = 0;
-						leftColumn = 0;
 						if (currentRow >= topRow + Frame.Height) {
 						if (currentRow >= topRow + Frame.Height) {
 							topRow++;
 							topRow++;
 						}
 						}
-						SetNeedsDisplay ();
-						PositionCursor ();
 					}
 					}
-					break;
 				}
 				}
+				Adjust ();
 				break;
 				break;
 
 
 			case Key.B | Key.CtrlMask:
 			case Key.B | Key.CtrlMask:
 			case Key.CursorLeft:
 			case Key.CursorLeft:
 				if (currentColumn > 0) {
 				if (currentColumn > 0) {
 					currentColumn--;
 					currentColumn--;
-					if (currentColumn < leftColumn) {
-						leftColumn--;
-						SetNeedsDisplay ();
-					}
-					PositionCursor ();
 				} else {
 				} else {
 					if (currentRow > 0) {
 					if (currentRow > 0) {
 						currentRow--;
 						currentRow--;
 						if (currentRow < topRow) {
 						if (currentRow < topRow) {
 							topRow--;
 							topRow--;
-							SetNeedsDisplay ();
 						}
 						}
 						currentLine = GetCurrentLine ();
 						currentLine = GetCurrentLine ();
 						currentColumn = currentLine.Count;
 						currentColumn = currentLine.Count;
-						int prev = leftColumn;
-						leftColumn = currentColumn - Frame.Width + 1;
-						if (leftColumn < 0)
-							leftColumn = 0;
-						if (prev != leftColumn)
-							SetNeedsDisplay ();
-						PositionCursor ();
 					}
 					}
 				}
 				}
+				Adjust ();
 				break;
 				break;
 
 
 			case Key.Delete:
 			case Key.Delete:
@@ -871,10 +1031,7 @@ namespace Terminal.Gui {
 					model.RemoveLine (currentRow);
 					model.RemoveLine (currentRow);
 					currentRow--;
 					currentRow--;
 					currentColumn = prevCount;
 					currentColumn = prevCount;
-					leftColumn = currentColumn - Frame.Width + 1;
-					if (leftColumn < 0)
-						leftColumn = 0;
-					SetNeedsDisplay ();
+					Adjust ();
 				}
 				}
 				break;
 				break;
 
 
@@ -882,11 +1039,7 @@ namespace Terminal.Gui {
 			case Key.Home:
 			case Key.Home:
 			case Key.A | Key.CtrlMask:
 			case Key.A | Key.CtrlMask:
 				currentColumn = 0;
 				currentColumn = 0;
-				if (currentColumn < leftColumn) {
-					leftColumn = 0;
-					SetNeedsDisplay ();
-				} else
-					PositionCursor ();
+				Adjust ();
 				break;
 				break;
 			case Key.DeleteChar:
 			case Key.DeleteChar:
 			case Key.D | Key.CtrlMask: // Delete
 			case Key.D | Key.CtrlMask: // Delete
@@ -913,12 +1066,7 @@ namespace Terminal.Gui {
 				currentLine = GetCurrentLine ();
 				currentLine = GetCurrentLine ();
 				currentColumn = currentLine.Count;
 				currentColumn = currentLine.Count;
 				int pcol = leftColumn;
 				int pcol = leftColumn;
-				leftColumn = currentColumn - Frame.Width + 1;
-				if (leftColumn < 0)
-					leftColumn = 0;
-				if (pcol != leftColumn)
-					SetNeedsDisplay ();
-				PositionCursor ();
+				Adjust ();
 				break;
 				break;
 
 
 			case Key.K | Key.CtrlMask: // kill-to-end
 			case Key.K | Key.CtrlMask: // kill-to-end
@@ -959,11 +1107,7 @@ namespace Terminal.Gui {
 				selectionStartRow = currentRow;
 				selectionStartRow = currentRow;
 				break;
 				break;
 
 
-			case ((int)'w' + Key.AltMask):
-				SetClipboard (GetRegion ());
-				selecting = false;
-				break;
-
+			case ((int)'W' + Key.AltMask):
 			case Key.W | Key.CtrlMask:
 			case Key.W | Key.CtrlMask:
 				SetClipboard (GetRegion ());
 				SetClipboard (GetRegion ());
 				if (!isReadOnly)
 				if (!isReadOnly)
@@ -971,7 +1115,8 @@ namespace Terminal.Gui {
 				selecting = false;
 				selecting = false;
 				break;
 				break;
 
 
-			case (Key)((int)'b' + Key.AltMask):
+			case Key.CtrlMask | Key.CursorLeft:
+			case (Key)((int)'B' + Key.AltMask):
 				var newPos = WordBackward (currentColumn, currentRow);
 				var newPos = WordBackward (currentColumn, currentRow);
 				if (newPos.HasValue) {
 				if (newPos.HasValue) {
 					currentColumn = newPos.Value.col;
 					currentColumn = newPos.Value.col;
@@ -981,7 +1126,8 @@ namespace Terminal.Gui {
 
 
 				break;
 				break;
 
 
-			case (Key)((int)'f' + Key.AltMask):
+			case Key.CtrlMask | Key.CursorRight:
+			case (Key)((int)'F' + Key.AltMask):
 				newPos = WordForward (currentColumn, currentRow);
 				newPos = WordForward (currentColumn, currentRow);
 				if (newPos.HasValue) {
 				if (newPos.HasValue) {
 					currentColumn = newPos.Value.col;
 					currentColumn = newPos.Value.col;
@@ -993,7 +1139,6 @@ namespace Terminal.Gui {
 			case Key.Enter:
 			case Key.Enter:
 				if (isReadOnly)
 				if (isReadOnly)
 					break;
 					break;
-				var orow = currentRow;
 				currentLine = GetCurrentLine ();
 				currentLine = GetCurrentLine ();
 				restCount = currentLine.Count - currentColumn;
 				restCount = currentLine.Count - currentColumn;
 				rest = currentLine.GetRange (currentColumn, restCount);
 				rest = currentLine.GetRange (currentColumn, restCount);
@@ -1044,11 +1189,12 @@ namespace Terminal.Gui {
 			return true;
 			return true;
 		}
 		}
 
 
-		private void MoveUp ()
+		void MoveUp ()
 		{
 		{
 			if (currentRow > 0) {
 			if (currentRow > 0) {
-				if (columnTrack == -1)
+				if (columnTrack == -1) {
 					columnTrack = currentColumn;
 					columnTrack = currentColumn;
+				}
 				currentRow--;
 				currentRow--;
 				if (currentRow < topRow) {
 				if (currentRow < topRow) {
 					topRow--;
 					topRow--;
@@ -1059,11 +1205,12 @@ namespace Terminal.Gui {
 			}
 			}
 		}
 		}
 
 
-		private void MoveDown ()
+		void MoveDown ()
 		{
 		{
 			if (currentRow + 1 < model.Count) {
 			if (currentRow + 1 < model.Count) {
-				if (columnTrack == -1)
+				if (columnTrack == -1) {
 					columnTrack = currentColumn;
 					columnTrack = currentColumn;
+				}
 				currentRow++;
 				currentRow++;
 				if (currentRow >= topRow + Frame.Height) {
 				if (currentRow >= topRow + Frame.Height) {
 					topRow++;
 					topRow++;
@@ -1071,6 +1218,8 @@ namespace Terminal.Gui {
 				}
 				}
 				TrackColumn ();
 				TrackColumn ();
 				PositionCursor ();
 				PositionCursor ();
+			} else if (currentRow > Frame.Height) {
+				Adjust ();
 			}
 			}
 		}
 		}
 
 
@@ -1167,28 +1316,31 @@ namespace Terminal.Gui {
 		{
 		{
 			var col = fromCol;
 			var col = fromCol;
 			var row = fromRow;
 			var row = fromRow;
-			var line = GetCurrentLine ();
-			var rune = RuneAt (col, row);
+			try {
+				var rune = RuneAt (col, row);
 
 
-			var srow = row;
-			if (Rune.IsPunctuation (rune) || Rune.IsWhiteSpace (rune)) {
-				while (MoveNext (ref col, ref row, out rune)) {
-					if (Rune.IsLetterOrDigit (rune))
-						break;
-				}
-				while (MoveNext (ref col, ref row, out rune)) {
-					if (!Rune.IsLetterOrDigit (rune))
-						break;
-				}
-			} else {
-				while (MoveNext (ref col, ref row, out rune)) {
-					if (!Rune.IsLetterOrDigit (rune))
-						break;
+				var srow = row;
+				if (Rune.IsPunctuation (rune) || Rune.IsWhiteSpace (rune)) {
+					while (MoveNext (ref col, ref row, out rune)) {
+						if (Rune.IsLetterOrDigit (rune))
+							break;
+					}
+					while (MoveNext (ref col, ref row, out rune)) {
+						if (!Rune.IsLetterOrDigit (rune))
+							break;
+					}
+				} else {
+					while (MoveNext (ref col, ref row, out rune)) {
+						if (!Rune.IsLetterOrDigit (rune))
+							break;
+					}
 				}
 				}
+				if (fromCol != col || fromRow != row)
+					return (col, row);
+				return null;
+			} catch (Exception) {
+				return null;
 			}
 			}
-			if (fromCol != col || fromRow != row)
-				return (col, row);
-			return null;
 		}
 		}
 
 
 		(int col, int row)? WordBackward (int fromCol, int fromRow)
 		(int col, int row)? WordBackward (int fromCol, int fromRow)
@@ -1198,27 +1350,30 @@ namespace Terminal.Gui {
 
 
 			var col = fromCol;
 			var col = fromCol;
 			var row = fromRow;
 			var row = fromRow;
-			var line = GetCurrentLine ();
-			var rune = RuneAt (col, row);
+			try {
+				var rune = RuneAt (col, row);
 
 
-			if (Rune.IsPunctuation (rune) || Rune.IsSymbol (rune) || Rune.IsWhiteSpace (rune)) {
-				while (MovePrev (ref col, ref row, out rune)) {
-					if (Rune.IsLetterOrDigit (rune))
-						break;
-				}
-				while (MovePrev (ref col, ref row, out rune)) {
-					if (!Rune.IsLetterOrDigit (rune))
-						break;
-				}
-			} else {
-				while (MovePrev (ref col, ref row, out rune)) {
-					if (!Rune.IsLetterOrDigit (rune))
-						break;
+				if (Rune.IsPunctuation (rune) || Rune.IsSymbol (rune) || Rune.IsWhiteSpace (rune)) {
+					while (MovePrev (ref col, ref row, out rune)) {
+						if (Rune.IsLetterOrDigit (rune))
+							break;
+					}
+					while (MovePrev (ref col, ref row, out rune)) {
+						if (!Rune.IsLetterOrDigit (rune))
+							break;
+					}
+				} else {
+					while (MovePrev (ref col, ref row, out rune)) {
+						if (!Rune.IsLetterOrDigit (rune))
+							break;
+					}
 				}
 				}
+				if (fromCol != col || fromRow != row)
+					return (col, row);
+				return null;
+			} catch (Exception) {
+				return null;
 			}
 			}
-			if (fromCol != col || fromRow != row)
-				return (col, row);
-			return null;
 		}
 		}
 
 
 		///<inheritdoc/>
 		///<inheritdoc/>
@@ -1239,25 +1394,39 @@ namespace Terminal.Gui {
 
 
 			if (ev.Flags == MouseFlags.Button1Clicked) {
 			if (ev.Flags == MouseFlags.Button1Clicked) {
 				if (model.Count > 0) {
 				if (model.Count > 0) {
-					var maxCursorPositionableLine = (model.Count - 1) - topRow;
+					var maxCursorPositionableLine = Math.Max ((model.Count - 1) - topRow, 0);
 					if (ev.Y > maxCursorPositionableLine) {
 					if (ev.Y > maxCursorPositionableLine) {
 						currentRow = maxCursorPositionableLine;
 						currentRow = maxCursorPositionableLine;
 					} else {
 					} else {
 						currentRow = ev.Y + topRow;
 						currentRow = ev.Y + topRow;
 					}
 					}
 					var r = GetCurrentLine ();
 					var r = GetCurrentLine ();
-					if (ev.X - leftColumn >= r.Count)
+					var idx = TextModel.GetColFromX (r, leftColumn, ev.X);
+					if (idx - leftColumn >= r.Count) {
 						currentColumn = r.Count - leftColumn;
 						currentColumn = r.Count - leftColumn;
-					else
-						currentColumn = ev.X - leftColumn;
+					} else {
+						currentColumn = idx + leftColumn;
+					}
 				}
 				}
 				PositionCursor ();
 				PositionCursor ();
+				lastWasKill = false;
+				columnTrack = currentColumn;
 			} else if (ev.Flags == MouseFlags.WheeledDown) {
 			} else if (ev.Flags == MouseFlags.WheeledDown) {
 				lastWasKill = false;
 				lastWasKill = false;
-				MoveDown ();
+				columnTrack = currentColumn;
+				ScrollTo (topRow + 1);
 			} else if (ev.Flags == MouseFlags.WheeledUp) {
 			} else if (ev.Flags == MouseFlags.WheeledUp) {
 				lastWasKill = false;
 				lastWasKill = false;
-				MoveUp ();
+				columnTrack = currentColumn;
+				ScrollTo (topRow - 1);
+			} else if (ev.Flags == MouseFlags.WheeledRight) {
+				lastWasKill = false;
+				columnTrack = currentColumn;
+				ScrollTo (leftColumn + 1, false);
+			} else if (ev.Flags == MouseFlags.WheeledLeft) {
+				lastWasKill = false;
+				columnTrack = currentColumn;
+				ScrollTo (leftColumn - 1, false);
 			}
 			}
 
 
 			return true;
 			return true;

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

@@ -15,7 +15,7 @@ namespace Terminal.Gui {
 	/// or more <see cref="Button"/>s. It defaults to the <see cref="Colors.Dialog"/> color scheme and has a 1 cell padding around the edges.
 	/// or more <see cref="Button"/>s. It defaults to the <see cref="Colors.Dialog"/> color scheme and has a 1 cell padding around the edges.
 	/// </summary>
 	/// </summary>
 	/// <remarks>
 	/// <remarks>
-	///  To run the <see cref="Dialog"/> modally, create the <see cref="Dialog"/>, and pass it to <see cref="Application.Run()"/>. 
+	///  To run the <see cref="Dialog"/> modally, create the <see cref="Dialog"/>, and pass it to <see cref="Application.Run(Func{Exception, bool})"/>. 
 	///  This will execute the dialog until it terminates via the [ESC] or [CTRL-Q] key, or when one of the views
 	///  This will execute the dialog until it terminates via the [ESC] or [CTRL-Q] key, or when one of the views
 	///  or buttons added to the dialog calls <see cref="Application.RequestStop"/>.
 	///  or buttons added to the dialog calls <see cref="Application.RequestStop"/>.
 	/// </remarks>
 	/// </remarks>

+ 2 - 2
Terminal.Gui/Windows/FileDialog.cs

@@ -670,7 +670,7 @@ namespace Terminal.Gui {
 	/// <remarks>
 	/// <remarks>
 	/// <para>
 	/// <para>
 	///   To use, create an instance of <see cref="SaveDialog"/>, and pass it to
 	///   To use, create an instance of <see cref="SaveDialog"/>, and pass it to
-	///   <see cref="Application.Run()"/>. This will run the dialog modally,
+	///   <see cref="Application.Run(Func{Exception, bool})"/>. This will run the dialog modally,
 	///   and when this returns, the <see cref="FileName"/>property will contain the selected file name or 
 	///   and when this returns, the <see cref="FileName"/>property will contain the selected file name or 
 	///   null if the user canceled. 
 	///   null if the user canceled. 
 	/// </para>
 	/// </para>
@@ -713,7 +713,7 @@ namespace Terminal.Gui {
 	/// </para>
 	/// </para>
 	/// <para>
 	/// <para>
 	///   To use, create an instance of <see cref="OpenDialog"/>, and pass it to
 	///   To use, create an instance of <see cref="OpenDialog"/>, and pass it to
-	///   <see cref="Application.Run()"/>. This will run the dialog modally,
+	///   <see cref="Application.Run(Func{Exception, bool})"/>. This will run the dialog modally,
 	///   and when this returns, the list of filds will be available on the <see cref="FilePaths"/> property.
 	///   and when this returns, the list of filds will be available on the <see cref="FilePaths"/> property.
 	/// </para>
 	/// </para>
 	/// <para>
 	/// <para>

+ 29 - 14
UICatalog/Scenarios/CharacterMap.cs

@@ -1,4 +1,7 @@
-using NStack;
+#define DRAW_CONTENT
+//#define BASE_DRAW_CONTENT
+
+using NStack;
 using System.Collections.Generic;
 using System.Collections.Generic;
 using System.Linq;
 using System.Linq;
 using System.Text;
 using System.Text;
@@ -58,15 +61,20 @@ namespace UICatalog {
 				CreateRadio("End", CharMap.MaxCodePointVal - 16, CharMap.MaxCodePointVal),
 				CreateRadio("End", CharMap.MaxCodePointVal - 16, CharMap.MaxCodePointVal),
 			};
 			};
 
 
-			var jumpList = new RadioGroup (radioItems.Select (t => t.radioLabel).ToArray ());
-			jumpList.X = Pos.X (label);
-			jumpList.Y = Pos.Bottom (label);
-			jumpList.Width = Dim.Fill ();
+			var jumpList = new RadioGroup (radioItems.Select (t => t.radioLabel).ToArray ()) {
+				X = Pos.X (label),
+				Y = Pos.Bottom (label),
+				Width = Dim.Fill (),
+				SelectedItem = 8
+			};
 			jumpList.SelectedItemChanged += (args) => {
 			jumpList.SelectedItemChanged += (args) => {
-				_charMap.Start = radioItems[args.SelectedItem].start;
+				_charMap.Start = radioItems [args.SelectedItem].start;
 			};
 			};
 
 
 			Win.Add (jumpList);
 			Win.Add (jumpList);
+
+			jumpList.Refresh ();
+			jumpList.SetFocus ();
 		}
 		}
 
 
 		public override void Run ()
 		public override void Run ()
@@ -91,11 +99,14 @@ namespace UICatalog {
 		}
 		}
 		int _start = 0x2500;
 		int _start = 0x2500;
 
 
+		public const int H_SPACE = 2;
+		public const int V_SPACE = 2;
+
 		public static int MaxCodePointVal => 0xE0FFF;
 		public static int MaxCodePointVal => 0xE0FFF;
 
 
 		// Row Header + space + (space + char + space)
 		// Row Header + space + (space + char + space)
 		public static int RowHeaderWidth => $"U+{MaxCodePointVal:x5}".Length;
 		public static int RowHeaderWidth => $"U+{MaxCodePointVal:x5}".Length;
-		public static int RowWidth => RowHeaderWidth + (" c".Length * 16);
+		public static int RowWidth => RowHeaderWidth + (H_SPACE * 16);
 
 
 		public CharMap ()
 		public CharMap ()
 		{
 		{
@@ -109,10 +120,12 @@ namespace UICatalog {
 					ShowHorizontalScrollIndicator = false;
 					ShowHorizontalScrollIndicator = false;
 				}
 				}
 			};
 			};
+#if DRAW_CONTENT
 
 
 			DrawContent += CharMap_DrawContent;
 			DrawContent += CharMap_DrawContent;
+#endif
 		}
 		}
-#if true
+
 		private void CharMap_DrawContent (Rect viewport)
 		private void CharMap_DrawContent (Rect viewport)
 		{
 		{
 			//Rune ReplaceNonPrintables (Rune c)
 			//Rune ReplaceNonPrintables (Rune c)
@@ -124,11 +137,13 @@ namespace UICatalog {
 			//	}
 			//	}
 			//}
 			//}
 
 
+			ContentSize = new Size (CharMap.RowWidth, MaxCodePointVal / 16 + Frame.Height - 1);
+
 			for (int header = 0; header < 16; header++) {
 			for (int header = 0; header < 16; header++) {
-				Move (viewport.X + RowHeaderWidth + (header * 2), 0);
+				Move (viewport.X + RowHeaderWidth + (header * H_SPACE), 0);
 				Driver.AddStr ($" {header:x} ");
 				Driver.AddStr ($" {header:x} ");
 			}
 			}
-			for (int row = 0, y = 0; row < viewport.Height / 2 - 1; row++, y += 2) {
+			for (int row = 0, y = 0; row < viewport.Height / 2 - 1; row++, y+= V_SPACE) {
 				int val = (-viewport.Y + row) * 16;
 				int val = (-viewport.Y + row) * 16;
 				if (val < MaxCodePointVal) {
 				if (val < MaxCodePointVal) {
 					var rowLabel = $"U+{val / 16:x4}x";
 					var rowLabel = $"U+{val / 16:x4}x";
@@ -137,17 +152,17 @@ namespace UICatalog {
 					var prevColWasWide = false;
 					var prevColWasWide = false;
 					for (int col = 0; col < 16; col++) {
 					for (int col = 0; col < 16; col++) {
 						var rune = new Rune ((uint)((uint)(-viewport.Y + row) * 16 + col));
 						var rune = new Rune ((uint)((uint)(-viewport.Y + row) * 16 + col));
-						Move (viewport.X + RowHeaderWidth + (col * 2) + (prevColWasWide ? 0 : 1), 0 + y + 1);
+						Move (viewport.X + RowHeaderWidth + (col * H_SPACE) + (prevColWasWide ? 0 : 1), y + 1);
 						Driver.AddRune (rune);
 						Driver.AddRune (rune);
-						//prevColWasWide = Rune.ColumnWidth(rune) > 1;
+						//prevColWasWide = Rune.ColumnWidth (rune) > 1;
 					}
 					}
 				}
 				}
 			}
 			}
 		}
 		}
-#else
+#if BASE_DRAW_CONTENT
 		public override void OnDrawContent (Rect viewport)
 		public override void OnDrawContent (Rect viewport)
 		{
 		{
-			CharMap_DrawContent(this, viewport);
+			CharMap_DrawContent (viewport);
 			base.OnDrawContent (viewport);
 			base.OnDrawContent (viewport);
 		}
 		}
 #endif
 #endif

+ 5 - 5
UICatalog/Scenarios/Clipping.cs

@@ -32,7 +32,7 @@ namespace UICatalog {
 			//Win.Y = 2;
 			//Win.Y = 2;
 			//Win.Width = Dim.Fill () - 4;
 			//Win.Width = Dim.Fill () - 4;
 			//Win.Height = Dim.Fill () - 2;
 			//Win.Height = Dim.Fill () - 2;
-			var label = new Label ("ScrollView (new Rect (5, 5, 100, 60)) with a 200, 100 ContentSize...") {
+			var label = new Label ("ScrollView (new Rect (3, 3, 50, 20)) with a 200, 100 ContentSize...") {
 				X = 0, Y = 0,
 				X = 0, Y = 0,
 				//ColorScheme = Colors.Dialog
 				//ColorScheme = Colors.Dialog
 			};
 			};
@@ -40,10 +40,10 @@ namespace UICatalog {
 
 
 			var scrollView = new ScrollView (new Rect (3, 3, 50, 20));
 			var scrollView = new ScrollView (new Rect (3, 3, 50, 20));
 			scrollView.ColorScheme = Colors.Menu;
 			scrollView.ColorScheme = Colors.Menu;
-			scrollView.ContentSize = new Size (100, 60);
+			scrollView.ContentSize = new Size (200, 100);
 			//ContentOffset = new Point (0, 0),
 			//ContentOffset = new Point (0, 0),
-			scrollView.ShowVerticalScrollIndicator = true;
-			scrollView.ShowHorizontalScrollIndicator = true;
+			//scrollView.ShowVerticalScrollIndicator = true;
+			//scrollView.ShowHorizontalScrollIndicator = true;
 
 
 			var embedded1 = new Window ("1") {
 			var embedded1 = new Window ("1") {
 				X = 3,
 				X = 3,
@@ -78,7 +78,7 @@ namespace UICatalog {
 			embedded2.Add (embedded3);
 			embedded2.Add (embedded3);
 
 
 			scrollView.Add (embedded1);
 			scrollView.Add (embedded1);
-					
+
 			Top.Add (scrollView);
 			Top.Add (scrollView);
 		}
 		}
 	}
 	}

+ 43 - 0
UICatalog/Scenarios/Editor.cs

@@ -13,6 +13,7 @@ namespace UICatalog {
 		private string _fileName = "demo.txt";
 		private string _fileName = "demo.txt";
 		private TextView _textView;
 		private TextView _textView;
 		private bool _saved = true;
 		private bool _saved = true;
+		private ScrollBarView _scrollBar;
 
 
 		public override void Init (Toplevel top, ColorScheme colorScheme)
 		public override void Init (Toplevel top, ColorScheme colorScheme)
 		{
 		{
@@ -35,6 +36,7 @@ namespace UICatalog {
 					new MenuItem ("C_ut", "", () => Cut()),
 					new MenuItem ("C_ut", "", () => Cut()),
 					new MenuItem ("_Paste", "", () => Paste())
 					new MenuItem ("_Paste", "", () => Paste())
 				}),
 				}),
+				new MenuBarItem ("_ScrollBarView", CreateKeepChecked ())
 			});
 			});
 			Top.Add (menu);
 			Top.Add (menu);
 
 
@@ -67,6 +69,33 @@ namespace UICatalog {
 			LoadFile ();
 			LoadFile ();
 
 
 			Win.Add (_textView);
 			Win.Add (_textView);
+
+			_scrollBar = new ScrollBarView (_textView, true);
+
+			_scrollBar.ChangedPosition += () => {
+				_textView.TopRow = _scrollBar.Position;
+				if (_textView.TopRow != _scrollBar.Position) {
+					_scrollBar.Position = _textView.TopRow;
+				}
+				_textView.SetNeedsDisplay ();
+			};
+
+			_scrollBar.OtherScrollBarView.ChangedPosition += () => {
+				_textView.LeftColumn = _scrollBar.OtherScrollBarView.Position;
+				if (_textView.LeftColumn != _scrollBar.OtherScrollBarView.Position) {
+					_scrollBar.OtherScrollBarView.Position = _textView.LeftColumn;
+				}
+				_textView.SetNeedsDisplay ();
+			};
+
+			_textView.DrawContent += (e) => {
+				_scrollBar.Size = _textView.Lines - 1;
+				_scrollBar.Position = _textView.TopRow;
+				_scrollBar.OtherScrollBarView.Size = _textView.Maxlength;
+				_scrollBar.OtherScrollBarView.Position = _textView.LeftColumn;
+				_scrollBar.LayoutSubviews ();
+				_scrollBar.Refresh ();
+			};
 		}
 		}
 
 
 		public override void Setup ()
 		public override void Setup ()
@@ -145,11 +174,25 @@ namespace UICatalog {
 			sb.Append ("Hello world.\n");
 			sb.Append ("Hello world.\n");
 			sb.Append ("This is a test of the Emergency Broadcast System.\n");
 			sb.Append ("This is a test of the Emergency Broadcast System.\n");
 
 
+			for (int i = 0; i < 30; i++) {
+				sb.Append ($"{i} - This is a test with a very long line and many lines to test the ScrollViewBar against the TextView. - {i}\n");
+			}
 			var sw = System.IO.File.CreateText (fileName);
 			var sw = System.IO.File.CreateText (fileName);
 			sw.Write (sb.ToString ());
 			sw.Write (sb.ToString ());
 			sw.Close ();
 			sw.Close ();
 		}
 		}
 
 
+		private MenuItem [] CreateKeepChecked ()
+		{
+			var item = new MenuItem ();
+			item.Title = "Keep Content Always In Viewport";
+			item.CheckType |= MenuItemCheckStyle.Checked;
+			item.Checked = true;
+			item.Action += () => _scrollBar.KeepContentAlwaysInViewport = item.Checked = !item.Checked;
+
+			return new MenuItem [] { item };
+		}
+
 		public override void Run ()
 		public override void Run ()
 		{
 		{
 			base.Run ();
 			base.Run ();

+ 64 - 10
UICatalog/Scenarios/ListViewWithSelection.cs

@@ -6,7 +6,7 @@ using System.Linq;
 using Terminal.Gui;
 using Terminal.Gui;
 
 
 namespace UICatalog {
 namespace UICatalog {
-	[ScenarioMetadata (Name: "List View With Selection", Description: "ListView with colunns and selection")]
+	[ScenarioMetadata (Name: "List View With Selection", Description: "ListView with columns and selection")]
 	[ScenarioCategory ("Controls")]
 	[ScenarioCategory ("Controls")]
 	class ListViewWithSelection : Scenario {
 	class ListViewWithSelection : Scenario {
 
 
@@ -25,7 +25,7 @@ namespace UICatalog {
 				Height = 1,
 				Height = 1,
 			};
 			};
 			Win.Add (_customRenderCB);
 			Win.Add (_customRenderCB);
-			_customRenderCB.Toggled += _customRenderCB_Toggled; ;
+			_customRenderCB.Toggled += _customRenderCB_Toggled;
 
 
 			_allowMarkingCB = new CheckBox ("Allow Marking") {
 			_allowMarkingCB = new CheckBox ("Allow Marking") {
 				X = Pos.Right (_customRenderCB) + 1,
 				X = Pos.Right (_customRenderCB) + 1,
@@ -55,9 +55,41 @@ namespace UICatalog {
 			};
 			};
 			Win.Add (_listView);
 			Win.Add (_listView);
 
 
-			
+			var _scrollBar = new ScrollBarView (_listView, true);
+
+			_scrollBar.ChangedPosition += () => {
+				_listView.TopItem = _scrollBar.Position;
+				if (_listView.TopItem != _scrollBar.Position) {
+					_scrollBar.Position = _listView.TopItem;
+				}
+				_listView.SetNeedsDisplay ();
+			};
+
+			_scrollBar.OtherScrollBarView.ChangedPosition += () => {
+				_listView.LeftItem = _scrollBar.OtherScrollBarView.Position;
+				if (_listView.LeftItem != _scrollBar.OtherScrollBarView.Position) {
+					_scrollBar.OtherScrollBarView.Position = _listView.LeftItem;
+				}
+				_listView.SetNeedsDisplay ();
+			};
+
+			_listView.DrawContent += (e) => {
+				_scrollBar.Size = _listView.Source.Count - 1;
+				_scrollBar.Position = _listView.TopItem;
+				_scrollBar.OtherScrollBarView.Size = _listView.Maxlength - 1;
+				_scrollBar.OtherScrollBarView.Position = _listView.LeftItem;
+				_scrollBar.Refresh ();
+			};
+
 			_listView.SetSource (_scenarios);
 			_listView.SetSource (_scenarios);
 
 
+			var k = "Keep Content Always In Viewport";
+			var keepCheckBox = new CheckBox (k, _scrollBar.AutoHideScrollBars) {
+				X = Pos.AnchorEnd (k.Length + 3),
+				Y = 0,
+			};
+			keepCheckBox.Toggled += (_) => _scrollBar.KeepContentAlwaysInViewport = keepCheckBox.Checked;
+			Win.Add (keepCheckBox);
 		}
 		}
 
 
 		private void _customRenderCB_Toggled (bool prev)
 		private void _customRenderCB_Toggled (bool prev)
@@ -84,20 +116,21 @@ namespace UICatalog {
 			Win.SetNeedsDisplay ();
 			Win.SetNeedsDisplay ();
 		}
 		}
 
 
-		// This is basicaly the same implementation used by the UICatalog main window
+		// This is basically the same implementation used by the UICatalog main window
 		internal class ScenarioListDataSource : IListDataSource {
 		internal class ScenarioListDataSource : IListDataSource {
 			int _nameColumnWidth = 30;
 			int _nameColumnWidth = 30;
 			private List<Type> scenarios;
 			private List<Type> scenarios;
 			BitArray marks;
 			BitArray marks;
-			int count;
+			int count, len;
 
 
 			public List<Type> Scenarios {
 			public List<Type> Scenarios {
-				get => scenarios; 
+				get => scenarios;
 				set {
 				set {
 					if (value != null) {
 					if (value != null) {
 						count = value.Count;
 						count = value.Count;
 						marks = new BitArray (count);
 						marks = new BitArray (count);
 						scenarios = value;
 						scenarios = value;
+						len = GetMaxLengthItem ();
 					}
 					}
 				}
 				}
 			}
 			}
@@ -110,14 +143,16 @@ namespace UICatalog {
 
 
 			public int Count => Scenarios != null ? Scenarios.Count : 0;
 			public int Count => Scenarios != null ? Scenarios.Count : 0;
 
 
+			public int Length => len;
+
 			public ScenarioListDataSource (List<Type> itemList) => Scenarios = itemList;
 			public ScenarioListDataSource (List<Type> itemList) => Scenarios = itemList;
 
 
-			public void Render (ListView container, ConsoleDriver driver, bool selected, int item, int col, int line, int width)
+			public void Render (ListView container, ConsoleDriver driver, bool selected, int item, int col, int line, int width, int start = 0)
 			{
 			{
 				container.Move (col, line);
 				container.Move (col, line);
 				// Equivalent to an interpolated string like $"{Scenarios[item].Name, -widtestname}"; if such a thing were possible
 				// Equivalent to an interpolated string like $"{Scenarios[item].Name, -widtestname}"; if such a thing were possible
 				var s = String.Format (String.Format ("{{0,{0}}}", -_nameColumnWidth), Scenario.ScenarioMetadata.GetName (Scenarios [item]));
 				var s = String.Format (String.Format ("{{0,{0}}}", -_nameColumnWidth), Scenario.ScenarioMetadata.GetName (Scenarios [item]));
-				RenderUstr (driver, $"{s}  {Scenario.ScenarioMetadata.GetDescription (Scenarios [item])}", col, line, width);
+				RenderUstr (driver, $"{s}  {Scenario.ScenarioMetadata.GetDescription (Scenarios [item])}", col, line, width, start);
 			}
 			}
 
 
 			public void SetMark (int item, bool value)
 			public void SetMark (int item, bool value)
@@ -126,11 +161,30 @@ namespace UICatalog {
 					marks [item] = value;
 					marks [item] = value;
 			}
 			}
 
 
+			int GetMaxLengthItem ()
+			{
+				if (scenarios?.Count == 0) {
+					return 0;
+				}
+
+				int maxLength = 0;
+				for (int i = 0; i < scenarios.Count; i++) {
+					var s = String.Format (String.Format ("{{0,{0}}}", -_nameColumnWidth), Scenario.ScenarioMetadata.GetName (Scenarios [i]));
+					var sc = $"{s}  {Scenario.ScenarioMetadata.GetDescription (Scenarios [i])}";
+					var l = sc.Length;
+					if (l > maxLength) {
+						maxLength = l;
+					}
+				}
+
+				return maxLength;
+			}
+
 			// A slightly adapted method from: https://github.com/migueldeicaza/gui.cs/blob/fc1faba7452ccbdf49028ac49f0c9f0f42bbae91/Terminal.Gui/Views/ListView.cs#L433-L461
 			// A slightly adapted method from: https://github.com/migueldeicaza/gui.cs/blob/fc1faba7452ccbdf49028ac49f0c9f0f42bbae91/Terminal.Gui/Views/ListView.cs#L433-L461
-			private void RenderUstr (ConsoleDriver driver, ustring ustr, int col, int line, int width)
+			private void RenderUstr (ConsoleDriver driver, ustring ustr, int col, int line, int width, int start = 0)
 			{
 			{
 				int used = 0;
 				int used = 0;
-				int index = 0;
+				int index = start;
 				while (index < ustr.Length) {
 				while (index < ustr.Length) {
 					(var rune, var size) = Utf8.DecodeRune (ustr, index, index - ustr.Length);
 					(var rune, var size) = Utf8.DecodeRune (ustr, index, index - ustr.Length);
 					var count = Rune.ColumnWidth (rune);
 					var count = Rune.ColumnWidth (rune);

+ 53 - 0
UICatalog/Scenarios/ListsAndCombos.cs

@@ -39,6 +39,32 @@ namespace UICatalog.Scenarios {
 			listview.SelectedItemChanged += (ListViewItemEventArgs e) => lbListView.Text = items [listview.SelectedItem];
 			listview.SelectedItemChanged += (ListViewItemEventArgs e) => lbListView.Text = items [listview.SelectedItem];
 			Win.Add (lbListView, listview);
 			Win.Add (lbListView, listview);
 
 
+			var _scrollBar = new ScrollBarView (listview, true);
+
+			_scrollBar.ChangedPosition += () => {
+				listview.TopItem = _scrollBar.Position;
+				if (listview.TopItem != _scrollBar.Position) {
+					_scrollBar.Position = listview.TopItem;
+				}
+				listview.SetNeedsDisplay ();
+			};
+
+			_scrollBar.OtherScrollBarView.ChangedPosition += () => {
+				listview.LeftItem = _scrollBar.OtherScrollBarView.Position;
+				if (listview.LeftItem != _scrollBar.OtherScrollBarView.Position) {
+					_scrollBar.OtherScrollBarView.Position = listview.LeftItem;
+				}
+				listview.SetNeedsDisplay ();
+			};
+
+			listview.DrawContent += (e) => {
+				_scrollBar.Size = listview.Source.Count - 1;
+				_scrollBar.Position = listview.TopItem;
+				_scrollBar.OtherScrollBarView.Size = listview.Maxlength - 1;
+				_scrollBar.OtherScrollBarView.Position = listview.LeftItem;
+				_scrollBar.Refresh ();
+			};
+
 			// ComboBox
 			// ComboBox
 			var lbComboBox = new Label ("ComboBox") {
 			var lbComboBox = new Label ("ComboBox") {
 				ColorScheme = Colors.TopLevel,
 				ColorScheme = Colors.TopLevel,
@@ -57,6 +83,33 @@ namespace UICatalog.Scenarios {
 			comboBox.SelectedItemChanged += (ListViewItemEventArgs text) => lbComboBox.Text = items[comboBox.SelectedItem];
 			comboBox.SelectedItemChanged += (ListViewItemEventArgs text) => lbComboBox.Text = items[comboBox.SelectedItem];
 			Win.Add (lbComboBox, comboBox);
 			Win.Add (lbComboBox, comboBox);
 
 
+			var scrollBarCbx = new ScrollBarView (comboBox.Subviews [1], true);
+
+			scrollBarCbx.ChangedPosition += () => {
+				((ListView)comboBox.Subviews [1]).TopItem = scrollBarCbx.Position;
+				if (((ListView)comboBox.Subviews [1]).TopItem != scrollBarCbx.Position) {
+					scrollBarCbx.Position = ((ListView)comboBox.Subviews [1]).TopItem;
+				}
+				comboBox.SetNeedsDisplay ();
+			};
+
+			scrollBarCbx.OtherScrollBarView.ChangedPosition += () => {
+				((ListView)comboBox.Subviews [1]).LeftItem = scrollBarCbx.OtherScrollBarView.Position;
+				if (((ListView)comboBox.Subviews [1]).LeftItem != scrollBarCbx.OtherScrollBarView.Position) {
+					scrollBarCbx.OtherScrollBarView.Position = ((ListView)comboBox.Subviews [1]).LeftItem;
+				}
+				comboBox.SetNeedsDisplay ();
+			};
+
+			comboBox.DrawContent += (e) => {
+				scrollBarCbx.Size = comboBox.Source.Count;
+				scrollBarCbx.Position = ((ListView)comboBox.Subviews [1]).TopItem;
+				scrollBarCbx.OtherScrollBarView.Size = ((ListView)comboBox.Subviews [1]).Maxlength - 1;
+				scrollBarCbx.OtherScrollBarView.Position = ((ListView)comboBox.Subviews [1]).LeftItem;
+				scrollBarCbx.Refresh ();
+			};
+
+
 			var btnMoveUp = new Button ("Move _Up") {
 			var btnMoveUp = new Button ("Move _Up") {
 				X = 1,
 				X = 1,
 				Y = Pos.Bottom(lbListView),
 				Y = Pos.Bottom(lbListView),

+ 1 - 1
UICatalog/Scenarios/Scrolling.cs

@@ -128,7 +128,7 @@ namespace UICatalog {
 			var horizontalRuler = new Label () {
 			var horizontalRuler = new Label () {
 				X = 0,
 				X = 0,
 				Y = 0,
 				Y = 0,
-				Width = Dim.Fill (1),  // BUGBUG: I don't think this should be needed; DimFill() should respect container's frame. X does.
+				Width = Dim.Fill (),  // FIXED: I don't think this should be needed; DimFill() should respect container's frame. X does.
 				ColorScheme = Colors.Error
 				ColorScheme = Colors.Error
 			};
 			};
 			scrollView.Add (horizontalRuler);
 			scrollView.Add (horizontalRuler);

+ 7 - 1
UICatalog/Scenarios/Threading.cs

@@ -92,7 +92,13 @@ namespace UICatalog {
 			_btnQuit.Clicked += Application.RequestStop;
 			_btnQuit.Clicked += Application.RequestStop;
 
 
 			Win.Add (_itemsList, _btnActionCancel, _logJob, text, _btnAction, _btnLambda, _btnHandler, _btnSync, _btnMethod, _btnClearData, _btnQuit);
 			Win.Add (_itemsList, _btnActionCancel, _logJob, text, _btnAction, _btnLambda, _btnHandler, _btnSync, _btnMethod, _btnClearData, _btnQuit);
-			_btnActionCancel.SetFocus ();
+
+			void Top_Loaded ()
+			{
+				_btnActionCancel.SetFocus ();
+				Top.Loaded -= Top_Loaded;
+			}
+			Top.Loaded += Top_Loaded;
 		}
 		}
 
 
 		private async void LoadData ()
 		private async void LoadData ()

+ 1 - 1
UICatalog/Scenarios/WindowsAndFrameViews.cs

@@ -108,7 +108,7 @@ namespace UICatalog {
 				var frameView = new FrameView ("This is a Sub-FrameView") {
 				var frameView = new FrameView ("This is a Sub-FrameView") {
 					X = Pos.Percent (50),
 					X = Pos.Percent (50),
 					Y = 1,
 					Y = 1,
-					Width = Dim.Percent (100),
+					Width = Dim.Percent (100, true), // Or Dim.Percent (50)
 					Height = 5,
 					Height = 5,
 					ColorScheme = Colors.Base,
 					ColorScheme = Colors.Base,
 					Text = "The Text in the FrameView",
 					Text = "The Text in the FrameView",

+ 89 - 8
UICatalog/UICatalog.cs

@@ -63,6 +63,8 @@ namespace UICatalog {
 		private static Scenario _runningScenario = null;
 		private static Scenario _runningScenario = null;
 		private static bool _useSystemConsole = false;
 		private static bool _useSystemConsole = false;
 		private static ConsoleDriver.DiagnosticFlags _diagnosticFlags;
 		private static ConsoleDriver.DiagnosticFlags _diagnosticFlags;
+		private static bool _heightAsBuffer;
+		private static bool _alwaysSetPosition;
 
 
 		static void Main (string [] args)
 		static void Main (string [] args)
 		{
 		{
@@ -144,6 +146,8 @@ namespace UICatalog {
 		{
 		{
 			Application.UseSystemConsole = _useSystemConsole;
 			Application.UseSystemConsole = _useSystemConsole;
 			Application.Init ();
 			Application.Init ();
+			Application.HeightAsBuffer = _heightAsBuffer;
+			Application.AlwaysSetPosition = _alwaysSetPosition;
 
 
 			// Set this here because not initialized until driver is loaded
 			// Set this here because not initialized until driver is loaded
 			_baseColorScheme = Colors.Base;
 			_baseColorScheme = Colors.Base;
@@ -253,7 +257,7 @@ namespace UICatalog {
 					_leftPane.Height = Dim.Fill(_statusBar.Visible ? 1 : 0);
 					_leftPane.Height = Dim.Fill(_statusBar.Visible ? 1 : 0);
 					_rightPane.Height = Dim.Fill(_statusBar.Visible ? 1 : 0);
 					_rightPane.Height = Dim.Fill(_statusBar.Visible ? 1 : 0);
 					_top.LayoutSubviews();
 					_top.LayoutSubviews();
-					_top.ChildNeedsDisplay();
+					_top.SetChildNeedsDisplay();
 				}),
 				}),
 			};
 			};
 
 
@@ -274,7 +278,52 @@ namespace UICatalog {
 			return _runningScenario;
 			return _runningScenario;
 		}
 		}
 
 
-		static MenuItem [] CreateDiagnosticMenuItems ()
+		static List<MenuItem []> CreateDiagnosticMenuItems ()
+		{
+			List<MenuItem []> menuItems = new List<MenuItem []> ();
+			menuItems.Add (CreateDiagnosticFlagsMenuItems ());
+			menuItems.Add (new MenuItem [] { null });
+			menuItems.Add (CreateSizeStyle ());
+			menuItems.Add (CreateAlwaysSetPosition ());
+			return menuItems;
+		}
+
+		static MenuItem [] CreateAlwaysSetPosition ()
+		{
+			List<MenuItem> menuItems = new List<MenuItem> ();
+			var item = new MenuItem ();
+			item.Title = "_Always set position (NetDriver only)";
+			item.Shortcut = Key.CtrlMask | Key.AltMask | (Key)item.Title.ToString ().Substring (1, 1) [0];
+			item.CheckType |= MenuItemCheckStyle.Checked;
+			item.Checked = Application.AlwaysSetPosition;
+			item.Action += () => {
+				Application.AlwaysSetPosition = !item.Checked;
+				item.Checked = _alwaysSetPosition = Application.AlwaysSetPosition;
+			};
+			menuItems.Add (item);
+
+			return menuItems.ToArray ();
+		}
+
+		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;
+				Application.HeightAsBuffer = _heightAsBuffer;
+			};
+			menuItems.Add (item);
+
+			return menuItems.ToArray ();
+		}
+
+		static MenuItem [] CreateDiagnosticFlagsMenuItems ()
 		{
 		{
 			const string OFF = "Diagnostics: _Off";
 			const string OFF = "Diagnostics: _Off";
 			const string FRAME_RULER = "Diagnostics: Frame _Ruler";
 			const string FRAME_RULER = "Diagnostics: Frame _Ruler";
@@ -288,7 +337,12 @@ namespace UICatalog {
 				item.Shortcut = Key.AltMask + index.ToString () [0];
 				item.Shortcut = Key.AltMask + index.ToString () [0];
 				index++;
 				index++;
 				item.CheckType |= MenuItemCheckStyle.Checked;
 				item.CheckType |= MenuItemCheckStyle.Checked;
-				item.Checked = _diagnosticFlags.HasFlag (diag);
+				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 += () => {
 				item.Action += () => {
 					var t = GetDiagnosticsTitle (ConsoleDriver.DiagnosticFlags.Off);
 					var t = GetDiagnosticsTitle (ConsoleDriver.DiagnosticFlags.Off);
 					if (item.Title == t && !item.Checked) {
 					if (item.Title == t && !item.Checked) {
@@ -444,31 +498,58 @@ namespace UICatalog {
 		}
 		}
 
 
 		internal class ScenarioListDataSource : IListDataSource {
 		internal class ScenarioListDataSource : IListDataSource {
+			private readonly int len;
+
 			public List<Type> Scenarios { get; set; }
 			public List<Type> Scenarios { get; set; }
 
 
 			public bool IsMarked (int item) => false;
 			public bool IsMarked (int item) => false;
 
 
 			public int Count => Scenarios.Count;
 			public int Count => Scenarios.Count;
 
 
-			public ScenarioListDataSource (List<Type> itemList) => Scenarios = itemList;
+			public int Length => len;
 
 
-			public void Render (ListView container, ConsoleDriver driver, bool selected, int item, int col, int line, int width)
+			public ScenarioListDataSource (List<Type> itemList)
+			{
+				Scenarios = itemList;
+				len = GetMaxLengthItem ();
+			}
+
+			public void Render (ListView container, ConsoleDriver driver, bool selected, int item, int col, int line, int width, int start = 0)
 			{
 			{
 				container.Move (col, line);
 				container.Move (col, line);
 				// Equivalent to an interpolated string like $"{Scenarios[item].Name, -widtestname}"; if such a thing were possible
 				// Equivalent to an interpolated string like $"{Scenarios[item].Name, -widtestname}"; if such a thing were possible
 				var s = String.Format (String.Format ("{{0,{0}}}", -_nameColumnWidth), Scenario.ScenarioMetadata.GetName (Scenarios [item]));
 				var s = String.Format (String.Format ("{{0,{0}}}", -_nameColumnWidth), Scenario.ScenarioMetadata.GetName (Scenarios [item]));
-				RenderUstr (driver, $"{s}  {Scenario.ScenarioMetadata.GetDescription (Scenarios [item])}", col, line, width);
+				RenderUstr (driver, $"{s}  {Scenario.ScenarioMetadata.GetDescription (Scenarios [item])}", col, line, width, start);
 			}
 			}
 
 
 			public void SetMark (int item, bool value)
 			public void SetMark (int item, bool value)
 			{
 			{
 			}
 			}
 
 
+			int GetMaxLengthItem ()
+			{
+				if (Scenarios?.Count == 0) {
+					return 0;
+				}
+
+				int maxLength = 0;
+				for (int i = 0; i < Scenarios.Count; i++) {
+					var s = String.Format (String.Format ("{{0,{0}}}", -_nameColumnWidth), Scenario.ScenarioMetadata.GetName (Scenarios [i]));
+					var sc = $"{s}  {Scenario.ScenarioMetadata.GetDescription (Scenarios [i])}";
+					var l = sc.Length;
+					if (l > maxLength) {
+						maxLength = l;
+					}
+				}
+
+				return maxLength;
+			}
+
 			// A slightly adapted method from: https://github.com/migueldeicaza/gui.cs/blob/fc1faba7452ccbdf49028ac49f0c9f0f42bbae91/Terminal.Gui/Views/ListView.cs#L433-L461
 			// A slightly adapted method from: https://github.com/migueldeicaza/gui.cs/blob/fc1faba7452ccbdf49028ac49f0c9f0f42bbae91/Terminal.Gui/Views/ListView.cs#L433-L461
-			private void RenderUstr (ConsoleDriver driver, ustring ustr, int col, int line, int width)
+			private void RenderUstr (ConsoleDriver driver, ustring ustr, int col, int line, int width, int start = 0)
 			{
 			{
 				int used = 0;
 				int used = 0;
-				int index = 0;
+				int index = start;
 				while (index < ustr.Length) {
 				while (index < ustr.Length) {
 					(var rune, var size) = Utf8.DecodeRune (ustr, index, index - ustr.Length);
 					(var rune, var size) = Utf8.DecodeRune (ustr, index, index - ustr.Length);
 					var count = Rune.ColumnWidth (rune);
 					var count = Rune.ColumnWidth (rune);

+ 27 - 7
UnitTests/ApplicationTests.cs

@@ -1,6 +1,8 @@
 using System;
 using System;
 using System.Collections.Generic;
 using System.Collections.Generic;
 using System.Linq;
 using System.Linq;
+using System.Threading;
+using System.Threading.Tasks;
 using Terminal.Gui;
 using Terminal.Gui;
 using Xunit;
 using Xunit;
 
 
@@ -23,14 +25,12 @@ namespace Terminal.Gui {
 		public void Init_Shutdown_Cleans_Up ()
 		public void Init_Shutdown_Cleans_Up ()
 		{
 		{
 			Assert.Null (Application.Current);
 			Assert.Null (Application.Current);
-			Assert.Null (Application.CurrentView);
 			Assert.Null (Application.Top);
 			Assert.Null (Application.Top);
 			Assert.Null (Application.MainLoop);
 			Assert.Null (Application.MainLoop);
 			Assert.Null (Application.Driver);
 			Assert.Null (Application.Driver);
 
 
 			Application.Init (new FakeDriver (), new FakeMainLoop (() => FakeConsole.ReadKey (true)));
 			Application.Init (new FakeDriver (), new FakeMainLoop (() => FakeConsole.ReadKey (true)));
 			Assert.NotNull (Application.Current);
 			Assert.NotNull (Application.Current);
-			Assert.NotNull (Application.CurrentView);
 			Assert.NotNull (Application.Top);
 			Assert.NotNull (Application.Top);
 			Assert.NotNull (Application.MainLoop);
 			Assert.NotNull (Application.MainLoop);
 			Assert.NotNull (Application.Driver);
 			Assert.NotNull (Application.Driver);
@@ -41,7 +41,6 @@ namespace Terminal.Gui {
 
 
 			Application.Shutdown ();
 			Application.Shutdown ();
 			Assert.Null (Application.Current);
 			Assert.Null (Application.Current);
-			Assert.Null (Application.CurrentView);
 			Assert.Null (Application.Top);
 			Assert.Null (Application.Top);
 			Assert.Null (Application.MainLoop);
 			Assert.Null (Application.MainLoop);
 			Assert.Null (Application.Driver);
 			Assert.Null (Application.Driver);
@@ -92,7 +91,6 @@ namespace Terminal.Gui {
 			Application.End (rs);
 			Application.End (rs);
 
 
 			Assert.Null (Application.Current);
 			Assert.Null (Application.Current);
-			Assert.Null (Application.CurrentView);
 			Assert.NotNull (Application.Top);
 			Assert.NotNull (Application.Top);
 			Assert.NotNull (Application.MainLoop);
 			Assert.NotNull (Application.MainLoop);
 			Assert.NotNull (Application.Driver);
 			Assert.NotNull (Application.Driver);
@@ -123,7 +121,6 @@ namespace Terminal.Gui {
 
 
 			Application.Shutdown ();
 			Application.Shutdown ();
 			Assert.Null (Application.Current);
 			Assert.Null (Application.Current);
-			Assert.Null (Application.CurrentView);
 			Assert.Null (Application.Top);
 			Assert.Null (Application.Top);
 			Assert.Null (Application.MainLoop);
 			Assert.Null (Application.MainLoop);
 			Assert.Null (Application.Driver);
 			Assert.Null (Application.Driver);
@@ -148,7 +145,6 @@ namespace Terminal.Gui {
 
 
 			Application.Shutdown ();
 			Application.Shutdown ();
 			Assert.Null (Application.Current);
 			Assert.Null (Application.Current);
-			Assert.Null (Application.CurrentView);
 			Assert.Null (Application.Top);
 			Assert.Null (Application.Top);
 			Assert.Null (Application.MainLoop);
 			Assert.Null (Application.MainLoop);
 			Assert.Null (Application.Driver);
 			Assert.Null (Application.Driver);
@@ -207,7 +203,6 @@ namespace Terminal.Gui {
 
 
 			Application.Shutdown ();
 			Application.Shutdown ();
 			Assert.Null (Application.Current);
 			Assert.Null (Application.Current);
-			Assert.Null (Application.CurrentView);
 			Assert.Null (Application.Top);
 			Assert.Null (Application.Top);
 			Assert.Null (Application.MainLoop);
 			Assert.Null (Application.MainLoop);
 			Assert.Null (Application.Driver);
 			Assert.Null (Application.Driver);
@@ -227,5 +222,30 @@ namespace Terminal.Gui {
 			Application.Shutdown ();
 			Application.Shutdown ();
 			Assert.Equal (3, count);
 			Assert.Equal (3, count);
 		}
 		}
+
+		[Fact]
+		public void Shutdown_Allows_Async ()
+		{
+			static async Task TaskWithAsyncContinuation ()
+			{
+				await Task.Yield ();
+				await Task.Yield ();
+			}
+
+			Init ();
+			Application.Shutdown ();
+
+			var task = TaskWithAsyncContinuation ();
+			Thread.Sleep (20);
+			Assert.True (task.IsCompletedSuccessfully);
+		}
+
+		[Fact]
+		public void Shutdown_Resets_SyncContext ()
+		{
+			Init ();
+			Application.Shutdown ();
+			Assert.Null (SynchronizationContext.Current);
+		}
 	}
 	}
 }
 }

+ 408 - 0
UnitTests/ScrollBarViewTests.cs

@@ -0,0 +1,408 @@
+using System;
+using Xunit;
+
+namespace Terminal.Gui {
+	public class ScrollBarViewTests {
+		public class HostView : View {
+			public int Top { get; set; }
+			public int Lines { get; set; }
+			public int Left { get; set; }
+			public int Cols { get; set; }
+		}
+
+		private HostView _hostView;
+		private ScrollBarView _scrollBar;
+		private bool _added;
+
+		public ScrollBarViewTests ()
+		{
+			Application.Init (new FakeDriver (), new FakeMainLoop (() => FakeConsole.ReadKey (true)));
+
+			var top = Application.Top;
+
+			_hostView = new HostView () {
+				Width = Dim.Fill (),
+				Height = Dim.Fill (),
+				Top = 0,
+				Lines = 30,
+				Left = 0,
+				Cols = 100
+			};
+
+			top.Add (_hostView);
+		}
+
+		private void AddHandlers ()
+		{
+			if (!_added) {
+				_hostView.DrawContent += _hostView_DrawContent;
+				_scrollBar.ChangedPosition += _scrollBar_ChangedPosition;
+				_scrollBar.OtherScrollBarView.ChangedPosition += _scrollBar_OtherScrollBarView_ChangedPosition;
+			}
+			_added = true;
+		}
+
+		private void RemoveHandlers ()
+		{
+			if (_added) {
+				_hostView.DrawContent -= _hostView_DrawContent;
+				_scrollBar.ChangedPosition -= _scrollBar_ChangedPosition;
+				_scrollBar.OtherScrollBarView.ChangedPosition -= _scrollBar_OtherScrollBarView_ChangedPosition;
+			}
+			_added = false;
+		}
+
+		private void _hostView_DrawContent (Rect obj)
+		{
+			_scrollBar.Size = _hostView.Lines;
+			_scrollBar.Position = _hostView.Top;
+			_scrollBar.OtherScrollBarView.Size = _hostView.Cols;
+			_scrollBar.OtherScrollBarView.Position = _hostView.Left;
+			_scrollBar.Refresh ();
+		}
+
+		private void _scrollBar_ChangedPosition ()
+		{
+			_hostView.Top = _scrollBar.Position;
+			if (_hostView.Top != _scrollBar.Position) {
+				_scrollBar.Position = _hostView.Top;
+			}
+			_hostView.SetNeedsDisplay ();
+		}
+
+		private void _scrollBar_OtherScrollBarView_ChangedPosition ()
+		{
+			_hostView.Left = _scrollBar.OtherScrollBarView.Position;
+			if (_hostView.Left != _scrollBar.OtherScrollBarView.Position) {
+				_scrollBar.OtherScrollBarView.Position = _hostView.Left;
+			}
+			_hostView.SetNeedsDisplay ();
+		}
+
+		[Fact]
+		public void Hosting_A_Null_View_To_A_ScrollBarView_Throws_ArgumentNullException ()
+		{
+			Assert.Throws<ArgumentNullException> ("The host parameter can't be null.",
+				() => new ScrollBarView (null, true));
+			Assert.Throws<ArgumentNullException> ("The host parameter can't be null.",
+				() => new ScrollBarView (null, false));
+		}
+
+		[Fact]
+		public void Hosting_A_Null_SuperView_View_To_A_ScrollBarView_Throws_ArgumentNullException ()
+		{
+			Assert.Throws<ArgumentNullException> ("The host SuperView parameter can't be null.",
+				() => new ScrollBarView (new View (), true));
+			Assert.Throws<ArgumentNullException> ("The host SuperView parameter can't be null.",
+				() => new ScrollBarView (new View (), false));
+		}
+
+		[Fact]
+		public void Hosting_Two_Vertical_ScrollBarView_Throws_ArgumentException ()
+		{
+			var top = new Toplevel ();
+			var host = new View ();
+			top.Add (host);
+			var v = new ScrollBarView (host, true);
+			var h = new ScrollBarView (host, true);
+
+			Assert.Throws<ArgumentException> (null, () => v.OtherScrollBarView = h);
+			Assert.Throws<ArgumentException> (null, () => h.OtherScrollBarView = v);
+		}
+
+		[Fact]
+		public void Hosting_Two_Horizontal_ScrollBarView_Throws_ArgumentException ()
+		{
+			var top = new Toplevel ();
+			var host = new View ();
+			top.Add (host);
+			var v = new ScrollBarView (host, false);
+			var h = new ScrollBarView (host, false);
+
+			Assert.Throws<ArgumentException> (null, () => v.OtherScrollBarView = h);
+			Assert.Throws<ArgumentException> (null, () => h.OtherScrollBarView = v);
+		}
+
+		[Fact]
+		public void Scrolling_With_Default_Constructor_Do_Not_Scroll ()
+		{
+			var sbv = new ScrollBarView {
+				Position = 1
+			};
+			Assert.NotEqual (1, sbv.Position);
+			Assert.Equal (0, sbv.Position);
+		}
+
+		[Fact]
+		public void Hosting_A_View_To_A_ScrollBarView ()
+		{
+			RemoveHandlers ();
+
+			_scrollBar = new ScrollBarView (_hostView, true);
+
+			Assert.True (_scrollBar.IsVertical);
+			Assert.False (_scrollBar.OtherScrollBarView.IsVertical);
+
+			Assert.Equal (_scrollBar.Position, _hostView.Top);
+			Assert.NotEqual (_scrollBar.Size, _hostView.Lines);
+			Assert.Equal (_scrollBar.OtherScrollBarView.Position, _hostView.Left);
+			Assert.NotEqual (_scrollBar.OtherScrollBarView.Size, _hostView.Cols);
+
+			AddHandlers ();
+			_hostView.SuperView.LayoutSubviews ();
+			_hostView.Redraw (_hostView.Bounds);
+
+			Assert.Equal (_scrollBar.Position, _hostView.Top);
+			Assert.Equal (_scrollBar.Size, _hostView.Lines + 1);
+			Assert.Equal (_scrollBar.OtherScrollBarView.Position, _hostView.Left);
+			Assert.Equal (_scrollBar.OtherScrollBarView.Size, _hostView.Cols + 1);
+		}
+
+		[Fact]
+		public void ChangedPosition_Update_The_Hosted_View ()
+		{
+			Hosting_A_View_To_A_ScrollBarView ();
+
+			AddHandlers ();
+
+			_scrollBar.Position = 2;
+			Assert.Equal (_scrollBar.Position, _hostView.Top);
+
+			_scrollBar.OtherScrollBarView.Position = 5;
+			Assert.Equal (_scrollBar.OtherScrollBarView.Position, _hostView.Left);
+		}
+
+		[Fact]
+		public void ChangedPosition_Scrolling ()
+		{
+			Hosting_A_View_To_A_ScrollBarView ();
+
+			AddHandlers ();
+
+			for (int i = 0; i < _scrollBar.Size; i++) {
+				_scrollBar.Position += 1;
+				Assert.Equal (_scrollBar.Position, _hostView.Top);
+			}
+			for (int i = _scrollBar.Size - 1; i >= 0; i--) {
+				_scrollBar.Position -= 1;
+				Assert.Equal (_scrollBar.Position, _hostView.Top);
+			}
+
+			for (int i = 0; i < _scrollBar.OtherScrollBarView.Size; i++) {
+				_scrollBar.OtherScrollBarView.Position += i;
+				Assert.Equal (_scrollBar.OtherScrollBarView.Position, _hostView.Left);
+			}
+			for (int i = _scrollBar.OtherScrollBarView.Size - 1; i >= 0; i--) {
+				_scrollBar.OtherScrollBarView.Position -= 1;
+				Assert.Equal (_scrollBar.OtherScrollBarView.Position, _hostView.Left);
+			}
+		}
+
+		[Fact]
+		public void ChangedPosition_Negative_Value ()
+		{
+			Hosting_A_View_To_A_ScrollBarView ();
+
+			AddHandlers ();
+
+			_scrollBar.Position = -20;
+			Assert.Equal (0, _scrollBar.Position);
+			Assert.Equal (_scrollBar.Position, _hostView.Top);
+
+			_scrollBar.OtherScrollBarView.Position = -50;
+			Assert.Equal (0, _scrollBar.OtherScrollBarView.Position);
+			Assert.Equal (_scrollBar.OtherScrollBarView.Position, _hostView.Left);
+		}
+
+		[Fact]
+		public void DrawContent_Update_The_ScrollBarView_Position ()
+		{
+			Hosting_A_View_To_A_ScrollBarView ();
+
+			AddHandlers ();
+
+			_hostView.Top = 3;
+			_hostView.Redraw (_hostView.Bounds);
+			Assert.Equal (_scrollBar.Position, _hostView.Top);
+
+			_hostView.Left = 6;
+			_hostView.Redraw (_hostView.Bounds);
+			Assert.Equal (_scrollBar.OtherScrollBarView.Position, _hostView.Left);
+		}
+
+		[Fact]
+		public void OtherScrollBarView_Not_Null ()
+		{
+			Hosting_A_View_To_A_ScrollBarView ();
+
+			AddHandlers ();
+
+			Assert.NotNull (_scrollBar.OtherScrollBarView);
+			Assert.NotEqual (_scrollBar, _scrollBar.OtherScrollBarView);
+			Assert.Equal (_scrollBar.OtherScrollBarView.OtherScrollBarView, _scrollBar);
+		}
+
+		[Fact]
+		public void ShowScrollIndicator_Check ()
+		{
+			Hosting_A_View_To_A_ScrollBarView ();
+
+			AddHandlers ();
+
+			Assert.True (_scrollBar.ShowScrollIndicator);
+			Assert.True (_scrollBar.OtherScrollBarView.ShowScrollIndicator);
+		}
+
+		[Fact]
+		public void KeepContentAlwaysInViewport_True ()
+		{
+			Hosting_A_View_To_A_ScrollBarView ();
+
+			AddHandlers ();
+
+			Assert.Equal (80, _hostView.Bounds.Width);
+			Assert.Equal (25, _hostView.Bounds.Height);
+			Assert.Equal (79, _scrollBar.OtherScrollBarView.Bounds.Width);
+			Assert.Equal (24, _scrollBar.Bounds.Height);
+			Assert.Equal (31, _scrollBar.Size);
+			Assert.Equal (101, _scrollBar.OtherScrollBarView.Size);
+			Assert.True (_scrollBar.ShowScrollIndicator);
+			Assert.True (_scrollBar.OtherScrollBarView.ShowScrollIndicator);
+			Assert.True (_scrollBar.Visible);
+			Assert.True (_scrollBar.OtherScrollBarView.Visible);
+
+			_scrollBar.Position = 50;
+			Assert.Equal (_scrollBar.Position, _scrollBar.Size - _scrollBar.Bounds.Height);
+			Assert.Equal (_scrollBar.Position, _hostView.Top);
+			Assert.Equal (7, _scrollBar.Position);
+			Assert.Equal (7, _hostView.Top);
+			Assert.True (_scrollBar.ShowScrollIndicator);
+			Assert.True (_scrollBar.OtherScrollBarView.ShowScrollIndicator);
+			Assert.True (_scrollBar.Visible);
+			Assert.True (_scrollBar.OtherScrollBarView.Visible);
+
+			_scrollBar.OtherScrollBarView.Position = 150;
+			Assert.Equal (_scrollBar.OtherScrollBarView.Position, _scrollBar.OtherScrollBarView.Size - _scrollBar.OtherScrollBarView.Bounds.Width);
+			Assert.Equal (_scrollBar.OtherScrollBarView.Position, _hostView.Left);
+			Assert.Equal (22, _scrollBar.OtherScrollBarView.Position);
+			Assert.Equal (22, _hostView.Left);
+			Assert.True (_scrollBar.ShowScrollIndicator);
+			Assert.True (_scrollBar.OtherScrollBarView.ShowScrollIndicator);
+			Assert.True (_scrollBar.Visible);
+			Assert.True (_scrollBar.OtherScrollBarView.Visible);
+		}
+
+		[Fact]
+		public void KeepContentAlwaysInViewport_False ()
+		{
+			Hosting_A_View_To_A_ScrollBarView ();
+
+			AddHandlers ();
+
+			_scrollBar.KeepContentAlwaysInViewport = false;
+			_scrollBar.Position = 50;
+			Assert.Equal (_scrollBar.Position, _scrollBar.Size - 1);
+			Assert.Equal (_scrollBar.Position, _hostView.Top);
+			Assert.Equal (30, _scrollBar.Position);
+			Assert.Equal (30, _hostView.Top);
+
+			_scrollBar.OtherScrollBarView.Position = 150;
+			Assert.Equal (_scrollBar.OtherScrollBarView.Position, _scrollBar.OtherScrollBarView.Size - 1);
+			Assert.Equal (_scrollBar.OtherScrollBarView.Position, _hostView.Left);
+			Assert.Equal (100, _scrollBar.OtherScrollBarView.Position);
+			Assert.Equal (100, _hostView.Left);
+		}
+
+		[Fact]
+		public void AutoHideScrollBars_Check ()
+		{
+			Hosting_A_View_To_A_ScrollBarView ();
+
+			AddHandlers ();
+
+			_hostView.Redraw (_hostView.Bounds);
+			Assert.True (_scrollBar.ShowScrollIndicator);
+			Assert.True (_scrollBar.Visible);
+			Assert.Equal ("Dim.Absolute(1)", _scrollBar.Width.ToString ());
+			Assert.Equal (1, _scrollBar.Bounds.Width);
+			Assert.Equal ("Dim.Combine(DimView(side=Height, target=HostView()({X=0,Y=0,Width=80,Height=25}))-Dim.Absolute(1))",
+				_scrollBar.Height.ToString ());
+			Assert.Equal (24, _scrollBar.Bounds.Height);
+			Assert.True (_scrollBar.OtherScrollBarView.ShowScrollIndicator);
+			Assert.True (_scrollBar.OtherScrollBarView.Visible);
+			Assert.Equal ("Dim.Combine(DimView(side=Width, target=HostView()({X=0,Y=0,Width=80,Height=25}))-Dim.Absolute(1))",
+				_scrollBar.OtherScrollBarView.Width.ToString ());
+			Assert.Equal (79, _scrollBar.OtherScrollBarView.Bounds.Width);
+			Assert.Equal ("Dim.Absolute(1)", _scrollBar.OtherScrollBarView.Height.ToString ());
+			Assert.Equal (1, _scrollBar.OtherScrollBarView.Bounds.Height);
+
+			_hostView.Lines = 10;
+			_hostView.Redraw (_hostView.Bounds);
+			Assert.False (_scrollBar.ShowScrollIndicator);
+			Assert.False (_scrollBar.Visible);
+			Assert.Equal ("Dim.Absolute(1)", _scrollBar.Width.ToString ());
+			Assert.Equal (1, _scrollBar.Bounds.Width);
+			Assert.Equal ("Dim.Combine(DimView(side=Height, target=HostView()({X=0,Y=0,Width=80,Height=25}))-Dim.Absolute(1))",
+				_scrollBar.Height.ToString ());
+			Assert.Equal (24, _scrollBar.Bounds.Height);
+			Assert.True (_scrollBar.OtherScrollBarView.ShowScrollIndicator);
+			Assert.True (_scrollBar.OtherScrollBarView.Visible);
+			Assert.Equal ("Dim.Combine(DimView(side=Width, target=HostView()({X=0,Y=0,Width=80,Height=25}))-Dim.Absolute(0))",
+				_scrollBar.OtherScrollBarView.Width.ToString ());
+			Assert.Equal (80, _scrollBar.OtherScrollBarView.Bounds.Width);
+			Assert.Equal ("Dim.Absolute(1)", _scrollBar.OtherScrollBarView.Height.ToString ());
+			Assert.Equal (1, _scrollBar.OtherScrollBarView.Bounds.Height);
+
+			_hostView.Cols = 60;
+			_hostView.Redraw (_hostView.Bounds);
+			Assert.False (_scrollBar.ShowScrollIndicator);
+			Assert.False (_scrollBar.Visible);
+			Assert.Equal ("Dim.Absolute(1)", _scrollBar.Width.ToString ());
+			Assert.Equal (1, _scrollBar.Bounds.Width);
+			Assert.Equal ("Dim.Combine(DimView(side=Height, target=HostView()({X=0,Y=0,Width=80,Height=25}))-Dim.Absolute(1))",
+				_scrollBar.Height.ToString ());
+			Assert.Equal (24, _scrollBar.Bounds.Height);
+			Assert.False (_scrollBar.OtherScrollBarView.ShowScrollIndicator);
+			Assert.False (_scrollBar.OtherScrollBarView.Visible);
+			Assert.Equal ("Dim.Combine(DimView(side=Width, target=HostView()({X=0,Y=0,Width=80,Height=25}))-Dim.Absolute(0))",
+				_scrollBar.OtherScrollBarView.Width.ToString ());
+			Assert.Equal (80, _scrollBar.OtherScrollBarView.Bounds.Width);
+			Assert.Equal ("Dim.Absolute(1)", _scrollBar.OtherScrollBarView.Height.ToString ());
+			Assert.Equal (1, _scrollBar.OtherScrollBarView.Bounds.Height);
+
+			_hostView.Lines = 40;
+			_hostView.Redraw (_hostView.Bounds);
+			Assert.True (_scrollBar.ShowScrollIndicator);
+			Assert.True (_scrollBar.Visible);
+			Assert.Equal ("Dim.Absolute(1)", _scrollBar.Width.ToString ());
+			Assert.Equal (1, _scrollBar.Bounds.Width);
+			Assert.Equal ("Dim.Combine(DimView(side=Height, target=HostView()({X=0,Y=0,Width=80,Height=25}))-Dim.Absolute(0))",
+				_scrollBar.Height.ToString ());
+			Assert.Equal (25, _scrollBar.Bounds.Height);
+			Assert.False (_scrollBar.OtherScrollBarView.ShowScrollIndicator);
+			Assert.False (_scrollBar.OtherScrollBarView.Visible);
+			Assert.Equal ("Dim.Combine(DimView(side=Width, target=HostView()({X=0,Y=0,Width=80,Height=25}))-Dim.Absolute(0))",
+				_scrollBar.OtherScrollBarView.Width.ToString ());
+			Assert.Equal (80, _scrollBar.OtherScrollBarView.Bounds.Width);
+			Assert.Equal ("Dim.Absolute(1)", _scrollBar.OtherScrollBarView.Height.ToString ());
+			Assert.Equal (1, _scrollBar.OtherScrollBarView.Bounds.Height);
+
+			_hostView.Cols = 120;
+			_hostView.Redraw (_hostView.Bounds);
+			Assert.True (_scrollBar.ShowScrollIndicator);
+			Assert.True (_scrollBar.Visible);
+			Assert.Equal ("Dim.Absolute(1)", _scrollBar.Width.ToString ());
+			Assert.Equal (1, _scrollBar.Bounds.Width);
+			Assert.Equal ("Dim.Combine(DimView(side=Height, target=HostView()({X=0,Y=0,Width=80,Height=25}))-Dim.Absolute(1))",
+				_scrollBar.Height.ToString ());
+			Assert.Equal (24, _scrollBar.Bounds.Height);
+			Assert.True (_scrollBar.OtherScrollBarView.ShowScrollIndicator);
+			Assert.True (_scrollBar.OtherScrollBarView.Visible);
+			Assert.Equal ("Dim.Combine(DimView(side=Width, target=HostView()({X=0,Y=0,Width=80,Height=25}))-Dim.Absolute(1))",
+				_scrollBar.OtherScrollBarView.Width.ToString ());
+			Assert.Equal (79, _scrollBar.OtherScrollBarView.Bounds.Width);
+			Assert.Equal ("Dim.Absolute(1)", _scrollBar.OtherScrollBarView.Height.ToString ());
+			Assert.Equal (1, _scrollBar.OtherScrollBarView.Bounds.Height);
+		}
+	}
+}

+ 1 - 1
UnitTests/UnitTests.csproj

@@ -14,7 +14,7 @@
   </PropertyGroup>
   </PropertyGroup>
 
 
   <ItemGroup>
   <ItemGroup>
-    <PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.7.1" />
+    <PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.8.3" />
     <PackageReference Include="System.Collections" Version="4.3.0" />
     <PackageReference Include="System.Collections" Version="4.3.0" />
     <PackageReference Include="xunit" Version="2.4.1" />
     <PackageReference Include="xunit" Version="2.4.1" />
     <PackageReference Include="xunit.runner.visualstudio" Version="2.4.3">
     <PackageReference Include="xunit.runner.visualstudio" Version="2.4.3">

+ 36 - 4
UnitTests/ViewTests.cs

@@ -6,7 +6,7 @@ using System.Linq;
 using Terminal.Gui;
 using Terminal.Gui;
 using Xunit;
 using Xunit;
 
 
-// Alais Console to MockConsole so we don't accidentally use Console
+// Alias Console to MockConsole so we don't accidentally use Console
 using Console = Terminal.Gui.FakeConsole;
 using Console = Terminal.Gui.FakeConsole;
 
 
 namespace Terminal.Gui {
 namespace Terminal.Gui {
@@ -27,9 +27,9 @@ namespace Terminal.Gui {
 			Assert.Null (r.ColorScheme);
 			Assert.Null (r.ColorScheme);
 			Assert.Equal (Dim.Sized (0), r.Width);
 			Assert.Equal (Dim.Sized (0), r.Width);
 			Assert.Equal (Dim.Sized (0), r.Height);
 			Assert.Equal (Dim.Sized (0), r.Height);
-			// BUGBUG: Pos needs eqality implemented
-			//Assert.Equal (Pos.At (0), r.X);
-			//Assert.Equal (Pos.At (0), r.Y);
+			// FIXED: Pos needs equality implemented
+			Assert.Equal (Pos.At (0), r.X);
+			Assert.Equal (Pos.At (0), r.Y);
 			Assert.False (r.IsCurrentTop);
 			Assert.False (r.IsCurrentTop);
 			Assert.Empty (r.Id);
 			Assert.Empty (r.Id);
 			Assert.Empty (r.Subviews);
 			Assert.Empty (r.Subviews);
@@ -1077,5 +1077,37 @@ namespace Terminal.Gui {
 			Assert.True (view.Frame.IsEmpty);
 			Assert.True (view.Frame.IsEmpty);
 			Assert.True (view.Bounds.IsEmpty);
 			Assert.True (view.Bounds.IsEmpty);
 		}
 		}
+
+		[Fact]
+		public void FocusNearestView_Ensure_Focus_Ordered ()
+		{
+			var top = new Toplevel ();
+
+			var win = new Window ();
+			var winSubview = new View ("WindowSubview") {
+				CanFocus = true
+			};
+			win.Add (winSubview);
+			top.Add (win);
+
+			var frm = new FrameView ();
+			var frmSubview = new View ("FrameSubview") {
+				CanFocus = true
+			};
+			frm.Add (frmSubview);
+			top.Add (frm);
+
+			top.ProcessKey (new KeyEvent (Key.Tab, new KeyModifiers ()));
+			Assert.Equal ($"WindowSubview", top.MostFocused.Text);
+			top.ProcessKey (new KeyEvent (Key.Tab, new KeyModifiers ()));
+			Assert.Equal ("FrameSubview", top.MostFocused.Text);
+			top.ProcessKey (new KeyEvent (Key.Tab, new KeyModifiers ()));
+			Assert.Equal ($"WindowSubview", top.MostFocused.Text);
+
+			top.ProcessKey (new KeyEvent (Key.BackTab | Key.ShiftMask, new KeyModifiers ()));
+			Assert.Equal ("FrameSubview", top.MostFocused.Text);
+			top.ProcessKey (new KeyEvent (Key.BackTab | Key.ShiftMask, new KeyModifiers ()));
+			Assert.Equal ($"WindowSubview", top.MostFocused.Text);
+		}
 	}
 	}
 }
 }

Alguns arquivos não foram mostrados porque muitos arquivos mudaram nesse diff