Browse Source

Merge pull request #1 from migueldeicaza/master

update to latest master for scrollbar
Thomas Nind 4 years ago
parent
commit
52360cfdde

+ 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`.

+ 23 - 20
Terminal.Gui/ConsoleDrivers/CursesDriver/CursesDriver.cs

@@ -269,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);
 				}
 				}
 
 
 
 
@@ -342,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);
@@ -358,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) {
@@ -371,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;

+ 1 - 0
Terminal.Gui/ConsoleDrivers/FakeDriver/FakeDriver.cs

@@ -391,6 +391,7 @@ 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 ();
 			};
 			};
 		}
 		}
 
 

+ 421 - 153
Terminal.Gui/ConsoleDrivers/NetDriver.cs

@@ -1,4 +1,5 @@
-//
+//#define PROCESS_REQUEST
+//
 // NetDriver.cs: The System.Console-based .NET driver, works on Windows and Unix, but is not particularly efficient.
 // NetDriver.cs: The System.Console-based .NET driver, works on Windows and Unix, but is not particularly efficient.
 //
 //
 // Authors:
 // Authors:
@@ -107,24 +108,40 @@ namespace Terminal.Gui {
 	internal class NetEvents {
 	internal class NetEvents {
 		ManualResetEventSlim inputReady = new ManualResetEventSlim (false);
 		ManualResetEventSlim inputReady = new ManualResetEventSlim (false);
 		ManualResetEventSlim waitForStart = new ManualResetEventSlim (false);
 		ManualResetEventSlim waitForStart = new ManualResetEventSlim (false);
+		ManualResetEventSlim winChange = new ManualResetEventSlim (false);
 		Queue<InputResult?> inputResultQueue = new Queue<InputResult?> ();
 		Queue<InputResult?> inputResultQueue = new Queue<InputResult?> ();
-
+		ConsoleDriver consoleDriver;
+		int lastWindowHeight;
+		int largestWindowHeight;
+#if PROCESS_REQUEST
+		bool neededProcessRequest;
+#endif
 		public int NumberOfCSI { get; }
 		public int NumberOfCSI { get; }
 
 
-		public NetEvents (int numberOfCSI = 1)
+		public NetEvents (ConsoleDriver consoleDriver, int numberOfCSI = 1)
 		{
 		{
+			if (consoleDriver == null) {
+				throw new ArgumentNullException ("Console driver instance must be provided.");
+			}
+			this.consoleDriver = consoleDriver;
 			NumberOfCSI = numberOfCSI;
 			NumberOfCSI = numberOfCSI;
 			Task.Run (ProcessInputResultQueue);
 			Task.Run (ProcessInputResultQueue);
+			Task.Run (CheckWinChange);
 		}
 		}
 
 
 		public InputResult? ReadConsoleInput ()
 		public InputResult? ReadConsoleInput ()
 		{
 		{
 			while (true) {
 			while (true) {
 				waitForStart.Set ();
 				waitForStart.Set ();
+				winChange.Set ();
+
 				if (inputResultQueue.Count == 0) {
 				if (inputResultQueue.Count == 0) {
 					inputReady.Wait ();
 					inputReady.Wait ();
 					inputReady.Reset ();
 					inputReady.Reset ();
 				}
 				}
+#if PROCESS_REQUEST
+				neededProcessRequest = false;
+#endif
 				if (inputResultQueue.Count > 0) {
 				if (inputResultQueue.Count > 0) {
 					return inputResultQueue.Dequeue ();
 					return inputResultQueue.Dequeue ();
 				}
 				}
@@ -145,6 +162,67 @@ namespace Terminal.Gui {
 			}
 			}
 		}
 		}
 
 
+		void CheckWinChange ()
+		{
+			while (true) {
+				winChange.Wait ();
+				winChange.Reset ();
+				WaitWinChange ();
+				inputReady.Set ();
+			}
+		}
+
+		void WaitWinChange ()
+		{
+			while (true) {
+				if (!consoleDriver.HeightAsBuffer) {
+					if (Console.WindowWidth != consoleDriver.Cols || Console.WindowHeight != consoleDriver.Rows) {
+						var w = Math.Max (Console.WindowWidth, 0);
+						var h = Math.Max (Console.WindowHeight, 0);
+						GetWindowSizeEvent (new Size (w, h));
+						return;
+					}
+				} else {
+					largestWindowHeight = Math.Max (Console.BufferHeight, largestWindowHeight);
+					if (Console.BufferWidth != consoleDriver.Cols || largestWindowHeight != consoleDriver.Rows
+						|| Console.WindowHeight != lastWindowHeight) {
+						lastWindowHeight = Console.WindowHeight;
+						GetWindowSizeEvent (new Size (Console.BufferWidth, lastWindowHeight));
+						return;
+					}
+					if (Console.WindowTop != consoleDriver.Top) {
+						// Top only working on Windows.
+						var winPositionEv = new WindowPositionEvent () {
+							Top = Console.WindowTop
+						};
+						inputResultQueue.Enqueue (new InputResult () {
+							EventType = EventType.WindowPosition,
+							WindowPositionEvent = winPositionEv
+						});
+						return;
+					}
+#if PROCESS_REQUEST
+					if (!neededProcessRequest) {
+						Console.Out.Write ("\x1b[6n");
+						neededProcessRequest = true;
+					}
+#endif
+				}
+			}
+		}
+
+		void GetWindowSizeEvent (Size size)
+		{
+			WindowSizeEvent windowSizeEvent = new WindowSizeEvent () {
+				Size = size
+			};
+
+			inputResultQueue.Enqueue (new InputResult () {
+				EventType = EventType.WindowSize,
+				WindowSizeEvent = windowSizeEvent
+			});
+		}
+
 		void GetConsoleInputType (ConsoleKeyInfo consoleKeyInfo)
 		void GetConsoleInputType (ConsoleKeyInfo consoleKeyInfo)
 		{
 		{
 			InputResult inputResult = new InputResult {
 			InputResult inputResult = new InputResult {
@@ -174,6 +252,7 @@ namespace Terminal.Gui {
 				}
 				}
 				break;
 				break;
 			case 27:
 			case 27:
+			case 91:
 				ConsoleKeyInfo [] cki = new ConsoleKeyInfo [] { consoleKeyInfo };
 				ConsoleKeyInfo [] cki = new ConsoleKeyInfo [] { consoleKeyInfo };
 				ConsoleModifiers mod = consoleKeyInfo.Modifiers;
 				ConsoleModifiers mod = consoleKeyInfo.Modifiers;
 				while (Console.KeyAvailable) {
 				while (Console.KeyAvailable) {
@@ -207,30 +286,71 @@ namespace Terminal.Gui {
 			ConsoleKeyInfo [] splitedCki = new ConsoleKeyInfo [] { };
 			ConsoleKeyInfo [] splitedCki = new ConsoleKeyInfo [] { };
 			int length = 0;
 			int length = 0;
 			var kChar = GetKeyCharArray (cki);
 			var kChar = GetKeyCharArray (cki);
-			var nCSI = kChar.Where (val => val == '\x1b').ToArray ().Length;
+			var nCSI = GetNumberOfCSI (kChar);
 			int curCSI = 0;
 			int curCSI = 0;
+			char previousKChar = '\0';
 			if (nCSI > 1) {
 			if (nCSI > 1) {
-				foreach (var ck in cki) {
+				for (int i = 0; i < cki.Length; i++) {
+					var ck = cki [i];
 					if (NumberOfCSI > 0 && nCSI - curCSI > NumberOfCSI) {
 					if (NumberOfCSI > 0 && nCSI - curCSI > NumberOfCSI) {
-						if (ck.KeyChar == '\x1b') {
+						if (cki [i + 1].KeyChar == '\x1b' && previousKChar != '\0') {
 							curCSI++;
 							curCSI++;
+							previousKChar = '\0';
+						} else {
+							previousKChar = ck.KeyChar;
 						}
 						}
 						continue;
 						continue;
 					}
 					}
 					if (ck.KeyChar == '\x1b') {
 					if (ck.KeyChar == '\x1b') {
-						if (splitedCki.Length > 0) {
+						if (ck.KeyChar == 'R') {
+							ResizeArray (ck);
+						}
+						if (splitedCki.Length > 1) {
 							DecodeCSI (ref inputResult, ref newConsoleKeyInfo, ref key, ref mouseEvent, splitedCki, ref mod);
 							DecodeCSI (ref inputResult, ref newConsoleKeyInfo, ref key, ref mouseEvent, splitedCki, ref mod);
 						}
 						}
 						splitedCki = new ConsoleKeyInfo [] { };
 						splitedCki = new ConsoleKeyInfo [] { };
 						length = 0;
 						length = 0;
 					}
 					}
-					length++;
-					Array.Resize (ref splitedCki, length);
-					splitedCki [length - 1] = ck;
+					ResizeArray (ck);
+					if (i == cki.Length - 1 && splitedCki.Length > 0) {
+						DecodeCSI (ref inputResult, ref newConsoleKeyInfo, ref key, ref mouseEvent, splitedCki, ref mod);
+					}
 				}
 				}
 			} else {
 			} else {
 				DecodeCSI (ref inputResult, ref newConsoleKeyInfo, ref key, ref mouseEvent, cki, ref mod);
 				DecodeCSI (ref inputResult, ref newConsoleKeyInfo, ref key, ref mouseEvent, cki, ref mod);
 			}
 			}
+
+			void ResizeArray (ConsoleKeyInfo ck)
+			{
+				length++;
+				Array.Resize (ref splitedCki, length);
+				splitedCki [length - 1] = ck;
+			}
+		}
+
+		char [] GetKeyCharArray (ConsoleKeyInfo [] cki)
+		{
+			char [] kChar = new char [] { };
+			var length = 0;
+			foreach (var kc in cki) {
+				length++;
+				Array.Resize (ref kChar, length);
+				kChar [length - 1] = kc.KeyChar;
+			}
+
+			return kChar;
+		}
+
+		int GetNumberOfCSI (char [] csi)
+		{
+			int nCSI = 0;
+			for (int i = 0; i < csi.Length; i++) {
+				if (csi [i] == '\x1b' || (csi [i] == '[' && (i == 0 || (i > 0 && csi [i - 1] != '\x1b')))) {
+					nCSI++;
+				}
+			}
+
+			return nCSI;
 		}
 		}
 
 
 		void DecodeCSI (ref InputResult inputResult, ref ConsoleKeyInfo newConsoleKeyInfo, ref ConsoleKey key, ref MouseEvent mouseEvent, ConsoleKeyInfo [] cki, ref ConsoleModifiers mod)
 		void DecodeCSI (ref InputResult inputResult, ref ConsoleKeyInfo newConsoleKeyInfo, ref ConsoleKey key, ref MouseEvent mouseEvent, ConsoleKeyInfo [] cki, ref ConsoleModifiers mod)
@@ -306,7 +426,8 @@ namespace Terminal.Gui {
 					(mod & ConsoleModifiers.Control) != 0);
 					(mod & ConsoleModifiers.Control) != 0);
 				break;
 				break;
 			case 7:
 			case 7:
-				throw new NotImplementedException ("Condition not yet detected!");
+				GetRequestEvent (GetKeyCharArray (cki));
+				return;
 			case int n when n >= 8:
 			case int n when n >= 8:
 				GetMouseEvent (cki);
 				GetMouseEvent (cki);
 				return;
 				return;
@@ -320,12 +441,63 @@ namespace Terminal.Gui {
 			inputResultQueue.Enqueue (inputResult);
 			inputResultQueue.Enqueue (inputResult);
 		}
 		}
 
 
+		Point lastCursorPosition;
+
+		void GetRequestEvent (char [] kChar)
+		{
+			EventType eventType = new EventType ();
+			Point point = new Point ();
+			int foundPoint = 0;
+			string value = "";
+			for (int i = 0; i < kChar.Length; i++) {
+				var c = kChar [i];
+				if (c == '[') {
+					foundPoint++;
+				} else if (foundPoint == 1 && c != ';') {
+					value += c.ToString ();
+				} else if (c == ';') {
+					if (foundPoint == 1) {
+						point.Y = int.Parse (value) - 1;
+					}
+					value = "";
+					foundPoint++;
+				} else if (foundPoint > 0 && i < kChar.Length - 1) {
+					value += c.ToString ();
+				} else if (i == kChar.Length - 1) {
+					point.X = int.Parse (value) + Console.WindowTop - 1;
+
+					switch (c) {
+					case 'R':
+						if (lastCursorPosition.Y != point.Y) {
+							lastCursorPosition = point;
+							eventType = EventType.WindowPosition;
+							var winPositionEv = new WindowPositionEvent () {
+								CursorPosition = point
+							};
+							inputResultQueue.Enqueue (new InputResult () {
+								EventType = eventType,
+								WindowPositionEvent = winPositionEv
+							});
+						} else {
+							return;
+						}
+						break;
+					default:
+						throw new NotImplementedException ();
+					}
+				}
+			}
+
+			inputReady.Set ();
+		}
+
 		MouseEvent lastMouseEvent;
 		MouseEvent lastMouseEvent;
 		bool isButtonPressed;
 		bool isButtonPressed;
 		bool isButtonClicked;
 		bool isButtonClicked;
 		bool isButtonDoubleClicked;
 		bool isButtonDoubleClicked;
 		bool isButtonTripleClicked;
 		bool isButtonTripleClicked;
 		bool isProcContBtnPressedRuning;
 		bool isProcContBtnPressedRuning;
+		bool isButtonReleased;
 
 
 		void GetMouseEvent (ConsoleKeyInfo [] cki)
 		void GetMouseEvent (ConsoleKeyInfo [] cki)
 		{
 		{
@@ -358,36 +530,64 @@ namespace Terminal.Gui {
 				} else if (c == 'm' || c == 'M') {
 				} else if (c == 'm' || c == 'M') {
 					point.Y = int.Parse (value) + Console.WindowTop - 1;
 					point.Y = int.Parse (value) + Console.WindowTop - 1;
 
 
-					if (c == 'M') {
-						isButtonPressed = true;
-					} else if (c == 'm') {
-						isButtonPressed = false;
-					}
+					//if (c == 'M') {
+					//	isButtonPressed = true;
+					//} else if (c == 'm') {
+					//	isButtonPressed = false;
+					//}
 
 
 					switch (buttonCode) {
 					switch (buttonCode) {
 					case 0:
 					case 0:
 					case 8:
 					case 8:
 					case 16:
 					case 16:
+					case 24:
 					case 32:
 					case 32:
+					case 36:
+					case 40:
+					case 48:
+					case 56:
 						buttonState = c == 'M' ? MouseButtonState.Button1Pressed
 						buttonState = c == 'M' ? MouseButtonState.Button1Pressed
 							: MouseButtonState.Button1Released;
 							: MouseButtonState.Button1Released;
 						break;
 						break;
 					case 1:
 					case 1:
 					case 9:
 					case 9:
 					case 17:
 					case 17:
+					case 25:
 					case 33:
 					case 33:
+					case 37:
+					case 41:
+					case 45:
+					case 49:
+					case 53:
+					case 57:
+					case 61:
 						buttonState = c == 'M' ? MouseButtonState.Button2Pressed
 						buttonState = c == 'M' ? MouseButtonState.Button2Pressed
 							: MouseButtonState.Button2Released;
 							: MouseButtonState.Button2Released;
 						break;
 						break;
 					case 2:
 					case 2:
 					case 10:
 					case 10:
+					case 14:
 					case 18:
 					case 18:
+					case 22:
+					case 26:
+					case 30:
 					case 34:
 					case 34:
+					case 42:
+					case 46:
+					case 50:
+					case 54:
+					case 58:
+					case 62:
 						buttonState = c == 'M' ? MouseButtonState.Button3Pressed
 						buttonState = c == 'M' ? MouseButtonState.Button3Pressed
 							: MouseButtonState.Button3Released;
 							: MouseButtonState.Button3Released;
 						break;
 						break;
 					case 35:
 					case 35:
+					case 39:
 					case 43:
 					case 43:
+					case 47:
+					case 55:
+					case 59:
+					case 63:
 						buttonState = MouseButtonState.ReportMousePosition;
 						buttonState = MouseButtonState.ReportMousePosition;
 						break;
 						break;
 					case 64:
 					case 64:
@@ -396,11 +596,15 @@ namespace Terminal.Gui {
 					case 65:
 					case 65:
 						buttonState = MouseButtonState.ButtonWheeledDown;
 						buttonState = MouseButtonState.ButtonWheeledDown;
 						break;
 						break;
+					case 68:
 					case 72:
 					case 72:
-						buttonState = MouseButtonState.ButtonWheeledLeft;       // Ctrl+ButtonWheeledUp
+					case 80:
+						buttonState = MouseButtonState.ButtonWheeledLeft;       // Shift/Ctrl+ButtonWheeledUp
 						break;
 						break;
+					case 69:
 					case 73:
 					case 73:
-						buttonState = MouseButtonState.ButtonWheeledRight;      // Ctrl+ButtonWheeledDown
+					case 81:
+						buttonState = MouseButtonState.ButtonWheeledRight;      // Shift/Ctrl+ButtonWheeledDown
 						break;
 						break;
 					}
 					}
 					// Modifiers.
 					// Modifiers.
@@ -408,17 +612,74 @@ namespace Terminal.Gui {
 					case 8:
 					case 8:
 					case 9:
 					case 9:
 					case 10:
 					case 10:
+					case 43:
+						buttonState |= MouseButtonState.ButtonAlt;
+						break;
+					case 14:
+					case 47:
+						buttonState |= MouseButtonState.ButtonAlt | MouseButtonState.ButtonShift;
+						break;
 					case 16:
 					case 16:
 					case 17:
 					case 17:
 					case 18:
 					case 18:
-					case 43:
+					case 51:
 						buttonState |= MouseButtonState.ButtonCtrl;
 						buttonState |= MouseButtonState.ButtonCtrl;
 						break;
 						break;
+					case 22:
+					case 55:
+						buttonState |= MouseButtonState.ButtonCtrl | MouseButtonState.ButtonShift;
+						break;
+					case 24:
+					case 25:
+					case 26:
+					case 59:
+						buttonState |= MouseButtonState.ButtonAlt | MouseButtonState.ButtonCtrl;
+						break;
+					case 30:
+					case 63:
+						buttonState |= MouseButtonState.ButtonCtrl | MouseButtonState.ButtonShift | MouseButtonState.ButtonAlt;
+						break;
 					case 32:
 					case 32:
 					case 33:
 					case 33:
 					case 34:
 					case 34:
 						buttonState |= MouseButtonState.ReportMousePosition;
 						buttonState |= MouseButtonState.ReportMousePosition;
 						break;
 						break;
+					case 36:
+					case 37:
+						buttonState |= MouseButtonState.ReportMousePosition | MouseButtonState.ButtonShift;
+						break;
+					case 39:
+					case 68:
+					case 69:
+						buttonState |= MouseButtonState.ButtonShift;
+						break;
+					case 40:
+					case 41:
+					case 42:
+						buttonState |= MouseButtonState.ReportMousePosition | MouseButtonState.ButtonAlt;
+						break;
+					case 45:
+					case 46:
+						buttonState |= MouseButtonState.ReportMousePosition | MouseButtonState.ButtonAlt | MouseButtonState.ButtonShift;
+						break;
+					case 48:
+					case 49:
+					case 50:
+						buttonState |= MouseButtonState.ReportMousePosition | MouseButtonState.ButtonCtrl;
+						break;
+					case 53:
+					case 54:
+						buttonState |= MouseButtonState.ReportMousePosition | MouseButtonState.ButtonCtrl | MouseButtonState.ButtonShift;
+						break;
+					case 56:
+					case 57:
+					case 58:
+						buttonState |= MouseButtonState.ReportMousePosition | MouseButtonState.ButtonCtrl | MouseButtonState.ButtonAlt;
+						break;
+					case 61:
+					case 62:
+						buttonState |= MouseButtonState.ReportMousePosition | MouseButtonState.ButtonCtrl | MouseButtonState.ButtonShift | MouseButtonState.ButtonAlt;
+						break;
 					}
 					}
 				}
 				}
 			}
 			}
@@ -426,6 +687,14 @@ namespace Terminal.Gui {
 			mouseEvent.Position.Y = point.Y;
 			mouseEvent.Position.Y = point.Y;
 			mouseEvent.ButtonState = buttonState;
 			mouseEvent.ButtonState = buttonState;
 
 
+			if ((buttonState & MouseButtonState.Button1Pressed) != 0
+				|| (buttonState & MouseButtonState.Button2Pressed) != 0
+				|| (buttonState & MouseButtonState.Button3Pressed) != 0) {
+				isButtonPressed = true;
+			} else {
+				isButtonPressed = false;
+			}
+
 			if ((isButtonClicked || isButtonDoubleClicked || isButtonTripleClicked)
 			if ((isButtonClicked || isButtonDoubleClicked || isButtonTripleClicked)
 				&& ((buttonState & MouseButtonState.Button1Released) != 0
 				&& ((buttonState & MouseButtonState.Button1Released) != 0
 				|| (buttonState & MouseButtonState.Button2Released) != 0
 				|| (buttonState & MouseButtonState.Button2Released) != 0
@@ -460,6 +729,16 @@ namespace Terminal.Gui {
 				return;
 				return;
 			}
 			}
 
 
+			if (!isButtonPressed && !isButtonClicked && !isButtonDoubleClicked && !isButtonTripleClicked
+				&& !isButtonReleased
+				&& ((buttonState & MouseButtonState.Button1Released) == 0
+				&& (buttonState & MouseButtonState.Button2Released) == 0
+				&& (buttonState & MouseButtonState.Button3Released) == 0)) {
+				ProcessButtonReleased (lastMouseEvent);
+				inputReady.Set ();
+				return;
+			}
+
 			inputResultQueue.Enqueue (new InputResult () {
 			inputResultQueue.Enqueue (new InputResult () {
 				EventType = EventType.Mouse,
 				EventType = EventType.Mouse,
 				MouseEvent = mouseEvent
 				MouseEvent = mouseEvent
@@ -484,6 +763,7 @@ namespace Terminal.Gui {
 
 
 			lastMouseEvent = mouseEvent;
 			lastMouseEvent = mouseEvent;
 			if (isButtonPressed && !isButtonClicked && !isButtonDoubleClicked && !isButtonTripleClicked && !isProcContBtnPressedRuning) {
 			if (isButtonPressed && !isButtonClicked && !isButtonDoubleClicked && !isButtonTripleClicked && !isProcContBtnPressedRuning) {
+				isButtonReleased = false;
 				Application.MainLoop.AddIdle (() => {
 				Application.MainLoop.AddIdle (() => {
 					ProcessContinuousButtonPressedAsync ().ConfigureAwait (false);
 					ProcessContinuousButtonPressedAsync ().ConfigureAwait (false);
 					return false;
 					return false;
@@ -499,22 +779,18 @@ namespace Terminal.Gui {
 				Position = mouseEvent.Position,
 				Position = mouseEvent.Position,
 				ButtonState = mouseEvent.ButtonState
 				ButtonState = mouseEvent.ButtonState
 			};
 			};
-			switch (mouseEvent.ButtonState) {
-			case MouseButtonState.Button1Released:
+			if ((mouseEvent.ButtonState & MouseButtonState.Button1Released) != 0) {
 				me.ButtonState &= ~MouseButtonState.Button1Released;
 				me.ButtonState &= ~MouseButtonState.Button1Released;
 				me.ButtonState |= MouseButtonState.Button1Clicked;
 				me.ButtonState |= MouseButtonState.Button1Clicked;
-				break;
-			case MouseButtonState.Button2Released:
+			} else if ((mouseEvent.ButtonState & MouseButtonState.Button2Released) != 0) {
 				me.ButtonState &= ~MouseButtonState.Button2Released;
 				me.ButtonState &= ~MouseButtonState.Button2Released;
 				me.ButtonState |= MouseButtonState.Button2Clicked;
 				me.ButtonState |= MouseButtonState.Button2Clicked;
-				break;
-			case MouseButtonState.Button3Released:
+			} else if ((mouseEvent.ButtonState & MouseButtonState.Button3Released) != 0) {
 				me.ButtonState &= ~MouseButtonState.Button3Released;
 				me.ButtonState &= ~MouseButtonState.Button3Released;
 				me.ButtonState |= MouseButtonState.Button3Clicked;
 				me.ButtonState |= MouseButtonState.Button3Clicked;
-				break;
-			default:
-				return;
 			}
 			}
+			isButtonReleased = true;
+
 			inputResultQueue.Enqueue (new InputResult () {
 			inputResultQueue.Enqueue (new InputResult () {
 				EventType = EventType.Mouse,
 				EventType = EventType.Mouse,
 				MouseEvent = me
 				MouseEvent = me
@@ -527,22 +803,18 @@ namespace Terminal.Gui {
 				Position = mouseEvent.Position,
 				Position = mouseEvent.Position,
 				ButtonState = mouseEvent.ButtonState
 				ButtonState = mouseEvent.ButtonState
 			};
 			};
-			switch (mouseEvent.ButtonState) {
-			case MouseButtonState.Button1Pressed:
+			if ((mouseEvent.ButtonState & MouseButtonState.Button1Pressed) != 0) {
 				me.ButtonState &= ~MouseButtonState.Button1Pressed;
 				me.ButtonState &= ~MouseButtonState.Button1Pressed;
 				me.ButtonState |= MouseButtonState.Button1DoubleClicked;
 				me.ButtonState |= MouseButtonState.Button1DoubleClicked;
-				break;
-			case MouseButtonState.Button2Pressed:
+			} else if ((mouseEvent.ButtonState & MouseButtonState.Button2Pressed) != 0) {
 				me.ButtonState &= ~MouseButtonState.Button2Pressed;
 				me.ButtonState &= ~MouseButtonState.Button2Pressed;
 				me.ButtonState |= MouseButtonState.Button2DoubleClicked;
 				me.ButtonState |= MouseButtonState.Button2DoubleClicked;
-				break;
-			case MouseButtonState.Button3Pressed:
+			} else if ((mouseEvent.ButtonState & MouseButtonState.Button3Pressed) != 0) {
 				me.ButtonState &= ~MouseButtonState.Button3Pressed;
 				me.ButtonState &= ~MouseButtonState.Button3Pressed;
 				me.ButtonState |= MouseButtonState.Button3DoubleClicked;
 				me.ButtonState |= MouseButtonState.Button3DoubleClicked;
-				break;
-			default:
-				return;
 			}
 			}
+			isButtonReleased = true;
+
 			inputResultQueue.Enqueue (new InputResult () {
 			inputResultQueue.Enqueue (new InputResult () {
 				EventType = EventType.Mouse,
 				EventType = EventType.Mouse,
 				MouseEvent = me
 				MouseEvent = me
@@ -555,22 +827,18 @@ namespace Terminal.Gui {
 				Position = mouseEvent.Position,
 				Position = mouseEvent.Position,
 				ButtonState = mouseEvent.ButtonState
 				ButtonState = mouseEvent.ButtonState
 			};
 			};
-			switch (mouseEvent.ButtonState) {
-			case MouseButtonState.Button1Pressed:
+			if ((mouseEvent.ButtonState & MouseButtonState.Button1Pressed) != 0) {
 				me.ButtonState &= ~MouseButtonState.Button1Pressed;
 				me.ButtonState &= ~MouseButtonState.Button1Pressed;
 				me.ButtonState |= MouseButtonState.Button1TripleClicked;
 				me.ButtonState |= MouseButtonState.Button1TripleClicked;
-				break;
-			case MouseButtonState.Button2Pressed:
+			} else if ((mouseEvent.ButtonState & MouseButtonState.Button2Pressed) != 0) {
 				me.ButtonState &= ~MouseButtonState.Button2Pressed;
 				me.ButtonState &= ~MouseButtonState.Button2Pressed;
 				me.ButtonState |= MouseButtonState.Button2TrippleClicked;
 				me.ButtonState |= MouseButtonState.Button2TrippleClicked;
-				break;
-			case MouseButtonState.Button3Pressed:
+			} else if ((mouseEvent.ButtonState & MouseButtonState.Button3Pressed) != 0) {
 				me.ButtonState &= ~MouseButtonState.Button3Pressed;
 				me.ButtonState &= ~MouseButtonState.Button3Pressed;
 				me.ButtonState |= MouseButtonState.Button3TripleClicked;
 				me.ButtonState |= MouseButtonState.Button3TripleClicked;
-				break;
-			default:
-				return;
 			}
 			}
+			isButtonReleased = true;
+
 			inputResultQueue.Enqueue (new InputResult () {
 			inputResultQueue.Enqueue (new InputResult () {
 				EventType = EventType.Mouse,
 				EventType = EventType.Mouse,
 				MouseEvent = me
 				MouseEvent = me
@@ -585,7 +853,7 @@ namespace Terminal.Gui {
 				await Task.Delay (200);
 				await Task.Delay (200);
 				var view = Application.wantContinuousButtonPressedView;
 				var view = Application.wantContinuousButtonPressedView;
 				if (isButtonPressed && !Console.KeyAvailable
 				if (isButtonPressed && !Console.KeyAvailable
-					&&  !isButtonClicked && !isButtonDoubleClicked && !isButtonTripleClicked
+					&& !isButtonClicked && !isButtonDoubleClicked && !isButtonTripleClicked
 					&& (view != null || view == null && lastMouseEvent.Position != point)) {
 					&& (view != null || view == null && lastMouseEvent.Position != point)) {
 					point = lastMouseEvent.Position;
 					point = lastMouseEvent.Position;
 					inputResultQueue.Enqueue (new InputResult () {
 					inputResultQueue.Enqueue (new InputResult () {
@@ -598,20 +866,32 @@ namespace Terminal.Gui {
 				}
 				}
 			}
 			}
 			isProcContBtnPressedRuning = false;
 			isProcContBtnPressedRuning = false;
-			isButtonPressed = false;
+			//isButtonPressed = false;
 		}
 		}
 
 
-		char [] GetKeyCharArray (ConsoleKeyInfo [] cki)
+		void ProcessButtonReleased (MouseEvent mouseEvent)
 		{
 		{
-			char [] kChar = new char [] { };
-			var length = 0;
-			foreach (var kc in cki) {
-				length++;
-				Array.Resize (ref kChar, length);
-				kChar [length - 1] = kc.KeyChar;
-			}
+			var me = new MouseEvent () {
+				Position = mouseEvent.Position,
+				ButtonState = mouseEvent.ButtonState
+			};
+			if ((mouseEvent.ButtonState & MouseButtonState.Button1Pressed) != 0) {
+				me.ButtonState &= ~(MouseButtonState.Button1Pressed | MouseButtonState.ReportMousePosition);
+				me.ButtonState |= MouseButtonState.Button1Released;
+			} else if ((mouseEvent.ButtonState & MouseButtonState.Button2Pressed) != 0) {
+				me.ButtonState &= ~(MouseButtonState.Button2Pressed | MouseButtonState.ReportMousePosition);
+				me.ButtonState |= MouseButtonState.Button2Released;
+			} else if ((mouseEvent.ButtonState & MouseButtonState.Button3Pressed) != 0) {
+				me.ButtonState &= ~(MouseButtonState.Button3Pressed | MouseButtonState.ReportMousePosition);
+				me.ButtonState |= MouseButtonState.Button3Released;
+			}
+			isButtonReleased = true;
+			lastMouseEvent = me;
 
 
-			return kChar;
+			inputResultQueue.Enqueue (new InputResult () {
+				EventType = EventType.Mouse,
+				MouseEvent = me
+			});
 		}
 		}
 
 
 		ConsoleModifiers GetConsoleModifiers (uint keyChar)
 		ConsoleModifiers GetConsoleModifiers (uint keyChar)
@@ -719,7 +999,9 @@ namespace Terminal.Gui {
 
 
 		public enum EventType {
 		public enum EventType {
 			key = 1,
 			key = 1,
-			Mouse = 2
+			Mouse = 2,
+			WindowSize = 3,
+			WindowPosition = 4
 		}
 		}
 
 
 		[Flags]
 		[Flags]
@@ -760,10 +1042,21 @@ namespace Terminal.Gui {
 			public MouseButtonState ButtonState;
 			public MouseButtonState ButtonState;
 		}
 		}
 
 
+		public struct WindowSizeEvent {
+			public Size Size;
+		}
+
+		public struct WindowPositionEvent {
+			public int Top;
+			public Point CursorPosition;
+		}
+
 		public struct InputResult {
 		public struct InputResult {
 			public EventType EventType;
 			public EventType EventType;
 			public ConsoleKeyInfo ConsoleKeyInfo;
 			public ConsoleKeyInfo ConsoleKeyInfo;
 			public MouseEvent MouseEvent;
 			public MouseEvent MouseEvent;
+			public WindowSizeEvent WindowSizeEvent;
+			public WindowPositionEvent WindowPositionEvent;
 		}
 		}
 	}
 	}
 
 
@@ -775,16 +1068,19 @@ namespace Terminal.Gui {
 		public override bool HeightAsBuffer { get; set; }
 		public override bool HeightAsBuffer { get; set; }
 
 
 		public NetWinVTConsole NetWinConsole { get; }
 		public NetWinVTConsole NetWinConsole { get; }
+		public bool IsWinPlatform { get; }
+		public bool AlwaysSetPosition { get; set; }
 
 
-		bool isWinPlatform;
+		int largestWindowHeight;
 
 
 		public NetDriver ()
 		public NetDriver ()
 		{
 		{
 			var p = Environment.OSVersion.Platform;
 			var p = Environment.OSVersion.Platform;
 			if (p == PlatformID.Win32NT || p == PlatformID.Win32S || p == PlatformID.Win32Windows) {
 			if (p == PlatformID.Win32NT || p == PlatformID.Win32S || p == PlatformID.Win32Windows) {
-				isWinPlatform = true;
+				IsWinPlatform = true;
 				NetWinConsole = new NetWinVTConsole ();
 				NetWinConsole = new NetWinVTConsole ();
 			}
 			}
+			largestWindowHeight = Math.Max (Console.BufferHeight, largestWindowHeight);
 		}
 		}
 
 
 		// 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
@@ -848,7 +1144,7 @@ namespace Terminal.Gui {
 
 
 		public override void End ()
 		public override void End ()
 		{
 		{
-			if (isWinPlatform) {
+			if (IsWinPlatform) {
 				NetWinConsole.Cleanup ();
 				NetWinConsole.Cleanup ();
 			}
 			}
 
 
@@ -861,6 +1157,8 @@ namespace Terminal.Gui {
 		{
 		{
 			if (Rows > 0) {
 			if (Rows > 0) {
 				Console.Clear ();
 				Console.Clear ();
+				Console.Out.Write ("\x1b[3J");
+				//Console.Out.Write ("\x1b[?25l");
 			}
 			}
 		}
 		}
 
 
@@ -934,7 +1232,7 @@ namespace Terminal.Gui {
 					// Can raise an exception while is still resizing.
 					// Can raise an exception while is still resizing.
 					try {
 					try {
 						// Not supported on Unix.
 						// Not supported on Unix.
-						if (isWinPlatform) {
+						if (IsWinPlatform) {
 #pragma warning disable CA1416
 #pragma warning disable CA1416
 							Console.CursorTop = 0;
 							Console.CursorTop = 0;
 							Console.CursorLeft = 0;
 							Console.CursorLeft = 0;
@@ -954,7 +1252,7 @@ namespace Terminal.Gui {
 					}
 					}
 				}
 				}
 			} else {
 			} else {
-				if (isWinPlatform && Console.WindowHeight > 0) {
+				if (IsWinPlatform && Console.WindowHeight > 0) {
 					// Can raise an exception while is still resizing.
 					// Can raise an exception while is still resizing.
 					try {
 					try {
 #pragma warning disable CA1416
 #pragma warning disable CA1416
@@ -1012,11 +1310,16 @@ namespace Terminal.Gui {
 			}
 			}
 		}
 		}
 
 
+		public override void Refresh ()
+		{
+			UpdateScreen ();
+		}
+
 		public override void UpdateScreen ()
 		public override void UpdateScreen ()
 		{
 		{
 			if (winChanging || Console.WindowHeight == 0 || contents.Length != Rows * Cols * 3
 			if (winChanging || Console.WindowHeight == 0 || contents.Length != Rows * Cols * 3
 				|| (!HeightAsBuffer && Rows != Console.WindowHeight)
 				|| (!HeightAsBuffer && Rows != Console.WindowHeight)
-				|| (HeightAsBuffer && Rows != Console.BufferHeight)) {
+				|| (HeightAsBuffer && Rows != largestWindowHeight)) {
 				return;
 				return;
 			}
 			}
 
 
@@ -1024,6 +1327,7 @@ namespace Terminal.Gui {
 			int rows = Math.Min (Console.WindowHeight + top, Rows);
 			int rows = Math.Min (Console.WindowHeight + top, Rows);
 			int cols = Cols;
 			int cols = Cols;
 
 
+			Console.CursorVisible = false;
 			for (int row = top; row < rows; row++) {
 			for (int row = top; row < rows; row++) {
 				if (!dirtyLine [row]) {
 				if (!dirtyLine [row]) {
 					continue;
 					continue;
@@ -1033,31 +1337,35 @@ namespace Terminal.Gui {
 					if (contents [row, col, 2] != 1) {
 					if (contents [row, col, 2] != 1) {
 						continue;
 						continue;
 					}
 					}
-					if (Console.WindowHeight > 0) {
-						// Could happens that the windows is still resizing and the col is bigger than Console.WindowWidth.
-						try {
-							Console.SetCursorPosition (col, row);
-						} catch (Exception) {
-							return;
-						}
+					if (Console.WindowHeight > 0 && !SetCursorPosition (col, row)) {
+						return;
 					}
 					}
 					for (; col < cols && contents [row, col, 2] == 1; col++) {
 					for (; col < cols && contents [row, col, 2] == 1; col++) {
 						var color = contents [row, col, 1];
 						var color = contents [row, col, 1];
 						if (color != redrawColor) {
 						if (color != redrawColor) {
 							SetColor (color);
 							SetColor (color);
 						}
 						}
+						if (AlwaysSetPosition && !SetCursorPosition (col, row)) {
+							return;
+						}
 						Console.Write ((char)contents [row, col, 0]);
 						Console.Write ((char)contents [row, col, 0]);
 						contents [row, col, 2] = 0;
 						contents [row, col, 2] = 0;
 					}
 					}
 				}
 				}
 			}
 			}
-
+			Console.CursorVisible = true;
 			UpdateCursor ();
 			UpdateCursor ();
 		}
 		}
 
 
-		public override void Refresh ()
+		bool SetCursorPosition (int col, int row)
 		{
 		{
-			UpdateScreen ();
+			// Could happens that the windows is still resizing and the col is bigger than Console.WindowWidth.
+			try {
+				Console.SetCursorPosition (col, row);
+				return true;
+			} catch (Exception) {
+				return false;
+			}
 		}
 		}
 
 
 		public override void UpdateCursor ()
 		public override void UpdateCursor ()
@@ -1230,15 +1538,42 @@ namespace Terminal.Gui {
 
 
 			var mLoop = mainLoop.Driver as NetMainLoop;
 			var mLoop = mainLoop.Driver as NetMainLoop;
 
 
-			// Note: Net doesn't support keydown/up events and thus any passed keyDown/UpHandlers will never be called
-			mLoop.KeyPressed = (e) => ProcessInput (e);
+			// Note: Net doesn't support keydown/up events and thus any passed keyDown/UpHandlers will be simulated to be called.
+			mLoop.ProcessInput = (e) => ProcessInput (e);
+		}
 
 
-			mLoop.WinChanged = (e) => ChangeWin (e);
+		void ProcessInput (NetEvents.InputResult inputEvent)
+		{
+			switch (inputEvent.EventType) {
+			case NetEvents.EventType.key:
+				var map = MapKey (inputEvent.ConsoleKeyInfo);
+				if (map == (Key)0xffffffff) {
+					return;
+				}
+				keyDownHandler (new KeyEvent (map, keyModifiers));
+				keyHandler (new KeyEvent (map, keyModifiers));
+				keyUpHandler (new KeyEvent (map, keyModifiers));
+				keyModifiers = new KeyModifiers ();
+				break;
+			case NetEvents.EventType.Mouse:
+				mouseHandler (ToDriverMouse (inputEvent.MouseEvent));
+				break;
+			case NetEvents.EventType.WindowSize:
+				ChangeWin ();
+				break;
+			case NetEvents.EventType.WindowPosition:
+				var newTop = inputEvent.WindowPositionEvent.Top;
+				if (HeightAsBuffer && top != newTop) {
+					top = newTop;
+					Refresh ();
+				}
+				break;
+			}
 		}
 		}
 
 
 		bool winChanging;
 		bool winChanging;
 
 
-		void ChangeWin (int newTop)
+		void ChangeWin ()
 		{
 		{
 			winChanging = true;
 			winChanging = true;
 			const int Min_WindowWidth = 14;
 			const int Min_WindowWidth = 14;
@@ -1248,8 +1583,8 @@ namespace Terminal.Gui {
 					Console.WindowHeight);
 					Console.WindowHeight);
 				top = 0;
 				top = 0;
 			} else {
 			} else {
-				size = new Size (Console.BufferWidth, Console.BufferHeight);
-				top = newTop;
+				largestWindowHeight = Math.Max (Console.BufferHeight, largestWindowHeight);
+				size = new Size (Console.BufferWidth, largestWindowHeight);
 			}
 			}
 			cols = size.Width;
 			cols = size.Width;
 			rows = size.Height;
 			rows = size.Height;
@@ -1260,25 +1595,6 @@ namespace Terminal.Gui {
 			}
 			}
 		}
 		}
 
 
-		void ProcessInput (NetEvents.InputResult inputEvent)
-		{
-			switch (inputEvent.EventType) {
-			case NetEvents.EventType.key:
-				var map = MapKey (inputEvent.ConsoleKeyInfo);
-				if (map == (Key)0xffffffff) {
-					return;
-				}
-				keyDownHandler (new KeyEvent (map, keyModifiers));
-				keyHandler (new KeyEvent (map, keyModifiers));
-				keyUpHandler (new KeyEvent (map, keyModifiers));
-				keyModifiers = new KeyModifiers ();
-				break;
-			case NetEvents.EventType.Mouse:
-				mouseHandler (ToDriverMouse (inputEvent.MouseEvent));
-				break;
-			}
-		}
-
 		MouseEvent ToDriverMouse (NetEvents.MouseEvent me)
 		MouseEvent ToDriverMouse (NetEvents.MouseEvent me)
 		{
 		{
 			MouseFlags mouseFlag = 0;
 			MouseFlags mouseFlag = 0;
@@ -1416,24 +1732,15 @@ namespace Terminal.Gui {
 	internal class NetMainLoop : IMainLoopDriver {
 	internal class NetMainLoop : IMainLoopDriver {
 		ManualResetEventSlim keyReady = new ManualResetEventSlim (false);
 		ManualResetEventSlim keyReady = new ManualResetEventSlim (false);
 		ManualResetEventSlim waitForProbe = new ManualResetEventSlim (false);
 		ManualResetEventSlim waitForProbe = new ManualResetEventSlim (false);
-		ManualResetEventSlim winChange = new ManualResetEventSlim (false);
 		Queue<NetEvents.InputResult?> inputResult = new Queue<NetEvents.InputResult?> ();
 		Queue<NetEvents.InputResult?> inputResult = new Queue<NetEvents.InputResult?> ();
 		MainLoop mainLoop;
 		MainLoop mainLoop;
-		ConsoleDriver consoleDriver;
-		bool winChanged;
-		int newTop;
 		CancellationTokenSource tokenSource = new CancellationTokenSource ();
 		CancellationTokenSource tokenSource = new CancellationTokenSource ();
 		NetEvents netEvents;
 		NetEvents netEvents;
 
 
 		/// <summary>
 		/// <summary>
 		/// Invoked when a Key is pressed.
 		/// Invoked when a Key is pressed.
 		/// </summary>
 		/// </summary>
-		public Action<NetEvents.InputResult> KeyPressed;
-
-		/// <summary>
-		/// Invoked when the window is changed.
-		/// </summary>
-		public Action<int> WinChanged;
+		public Action<NetEvents.InputResult> ProcessInput;
 
 
 		/// <summary>
 		/// <summary>
 		/// Initializes the class with the console driver.
 		/// Initializes the class with the console driver.
@@ -1447,11 +1754,10 @@ namespace Terminal.Gui {
 			if (consoleDriver == null) {
 			if (consoleDriver == null) {
 				throw new ArgumentNullException ("Console driver instance must be provided.");
 				throw new ArgumentNullException ("Console driver instance must be provided.");
 			}
 			}
-			this.consoleDriver = consoleDriver;
-			netEvents = new NetEvents ();
+			netEvents = new NetEvents (consoleDriver);
 		}
 		}
 
 
-		void KeyReader ()
+		void NetInputHandler ()
 		{
 		{
 			while (true) {
 			while (true) {
 				waitForProbe.Wait ();
 				waitForProbe.Wait ();
@@ -1470,43 +1776,10 @@ namespace Terminal.Gui {
 			}
 			}
 		}
 		}
 
 
-		void CheckWinChange ()
-		{
-			while (true) {
-				winChange.Wait ();
-				winChange.Reset ();
-				WaitWinChange ();
-				winChanged = true;
-				keyReady.Set ();
-			}
-		}
-
-		int lastWindowHeight;
-		void WaitWinChange ()
-		{
-			while (true) {
-				if (!consoleDriver.HeightAsBuffer) {
-					if (Console.WindowWidth != consoleDriver.Cols || Console.WindowHeight != consoleDriver.Rows) {
-						return;
-					}
-				} else {
-					if (Console.BufferWidth != consoleDriver.Cols || Console.BufferHeight != consoleDriver.Rows
-						|| Console.WindowTop != consoleDriver.Top
-						|| Console.WindowHeight != lastWindowHeight) {
-						// Top only working on Windows.
-						newTop = Console.WindowTop;
-						lastWindowHeight = Console.WindowHeight;
-						return;
-					}
-				}
-			}
-		}
-
 		void IMainLoopDriver.Setup (MainLoop mainLoop)
 		void IMainLoopDriver.Setup (MainLoop mainLoop)
 		{
 		{
 			this.mainLoop = mainLoop;
 			this.mainLoop = mainLoop;
-			Task.Run (KeyReader);
-			Task.Run (CheckWinChange);
+			Task.Run (NetInputHandler);
 		}
 		}
 
 
 		void IMainLoopDriver.Wakeup ()
 		void IMainLoopDriver.Wakeup ()
@@ -1517,7 +1790,6 @@ namespace Terminal.Gui {
 		bool IMainLoopDriver.EventsPending (bool wait)
 		bool IMainLoopDriver.EventsPending (bool wait)
 		{
 		{
 			waitForProbe.Set ();
 			waitForProbe.Set ();
-			winChange.Set ();
 
 
 			if (CheckTimers (wait, out var waitTimeout)) {
 			if (CheckTimers (wait, out var waitTimeout)) {
 				return true;
 				return true;
@@ -1534,7 +1806,7 @@ namespace Terminal.Gui {
 			}
 			}
 
 
 			if (!tokenSource.IsCancellationRequested) {
 			if (!tokenSource.IsCancellationRequested) {
-				return inputResult.Count > 0 || CheckTimers (wait, out _) || winChanged;
+				return inputResult.Count > 0 || CheckTimers (wait, out _);
 			}
 			}
 
 
 			tokenSource.Dispose ();
 			tokenSource.Dispose ();
@@ -1568,11 +1840,7 @@ namespace Terminal.Gui {
 		void IMainLoopDriver.MainIteration ()
 		void IMainLoopDriver.MainIteration ()
 		{
 		{
 			if (inputResult.Count > 0) {
 			if (inputResult.Count > 0) {
-				KeyPressed?.Invoke (inputResult.Dequeue ().Value);
-			}
-			if (winChanged) {
-				winChanged = false;
-				WinChanged?.Invoke (newTop);
+				ProcessInput?.Invoke (inputResult.Dequeue ().Value);
 			}
 			}
 		}
 		}
 	}
 	}

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

@@ -799,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)) {
@@ -854,12 +859,7 @@ 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 () {
-					X = mouseEvent.MousePosition.X,
-					Y = mouseEvent.MousePosition.Y
-				};
-				//if (p == point) {
+				  IsButtonReleased && p == point) {
 				switch (LastMouseButtonPressed) {
 				switch (LastMouseButtonPressed) {
 				case WindowsConsole.ButtonState.Button1Pressed:
 				case WindowsConsole.ButtonState.Button1Pressed:
 					mouseFlag = MouseFlags.Button1Clicked;
 					mouseFlag = MouseFlags.Button1Clicked;
@@ -877,9 +877,6 @@ namespace Terminal.Gui {
 					X = mouseEvent.MousePosition.X,
 					X = mouseEvent.MousePosition.X,
 					Y = mouseEvent.MousePosition.Y
 					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)) {
@@ -964,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,

+ 62 - 30
Terminal.Gui/Core/Application.cs

@@ -67,17 +67,11 @@ 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>
-		/// The current <see cref="View"/> object being redrawn.
-		/// </summary>
-		/// /// <value>The current.</value>
-		public static View CurrentView { get; set; }
-
 		/// <summary>
 		/// <summary>
 		/// The current <see cref="ConsoleDriver.HeightAsBuffer"/> used in the terminal.
 		/// The current <see cref="ConsoleDriver.HeightAsBuffer"/> used in the terminal.
 		/// </summary>
 		/// </summary>
@@ -99,6 +93,24 @@ namespace Terminal.Gui {
 			}
 			}
 		}
 		}
 
 
+		/// <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
 		/// </summary>
 		/// </summary>
@@ -175,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);
@@ -226,7 +238,6 @@ namespace Terminal.Gui {
 			}
 			}
 			Top = topLevelFactory ();
 			Top = topLevelFactory ();
 			Current = Top;
 			Current = Top;
-			CurrentView = Top;
 			_initialized = true;
 			_initialized = true;
 		}
 		}
 
 
@@ -340,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;
@@ -420,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
 				};
 				};
 
 
@@ -523,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 ();
 		}
 		}
@@ -570,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 ();
 			}
 			}
 		}
 		}
@@ -637,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>
@@ -663,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>
@@ -676,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>
@@ -690,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.

+ 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)

+ 45 - 32
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;
 
 
@@ -195,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 ();
@@ -217,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;
@@ -272,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++;
 			}
 			}
 		}
 		}
 
 
@@ -363,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 {
@@ -389,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 ()
@@ -428,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);
@@ -456,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 ()

+ 68 - 9
Terminal.Gui/Core/View.cs

@@ -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);
@@ -1314,8 +1314,6 @@ namespace Terminal.Gui {
 				return;
 				return;
 			}
 			}
 
 
-			Application.CurrentView = this;
-
 			var clipRect = new Rect (Point.Empty, frame.Size);
 			var clipRect = new Rect (Point.Empty, frame.Size);
 
 
 			if (ColorScheme != null) {
 			if (ColorScheme != null) {
@@ -1340,11 +1338,11 @@ namespace Terminal.Gui {
 						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)
 							// Use the view's bounds (view-relative; Location will always be (0,0)
-							if (view.Visible) {
+							if (view.Visible && view.Frame.Width > 0 && view.Frame.Height > 0) {
+								view.OnDrawContent (view.Bounds);
 								view.Redraw (view.Bounds);
 								view.Redraw (view.Bounds);
 							}
 							}
 						}
 						}
@@ -1969,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
@@ -1979,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>
@@ -2144,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;
+		}
 	}
 	}
 }
 }

+ 13 - 12
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);
@@ -196,12 +196,8 @@ namespace Terminal.Gui {
 
 
 			// Checks if there are any SuperView view which intersect with this window.
 			// Checks if there are any SuperView view which intersect with this window.
 			if (SuperView != null) {
 			if (SuperView != null) {
-				foreach (var view in SuperView.Subviews) {
-					if (view != this && view.Frame.IntersectsWith (Bounds)) {
-						view.SetNeedsLayout ();
-						view.SetNeedsDisplay (view.Bounds);
-					}
-				}
+				SuperView.SetNeedsLayout ();
+				SuperView.SetNeedsDisplay ();
 			}
 			}
 		}
 		}
 
 
@@ -231,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) {
@@ -249,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 ();
@@ -266,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;
 		}
 		}
 
 

+ 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)
 		{
 		{

+ 4 - 1
Terminal.Gui/Views/Menu.cs

@@ -453,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;
 				}
 				}
@@ -927,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) {

+ 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)
 		{
 		{

+ 14 - 90
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);
 		}
 		}
@@ -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 +

+ 277 - 127
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>
@@ -421,11 +545,22 @@ namespace Terminal.Gui {
 			}
 			}
 			var line = model.GetLine (currentRow);
 			var line = model.GetLine (currentRow);
 			var retreat = 0;
 			var retreat = 0;
+			var col = 0;
 			if (line.Count > 0) {
 			if (line.Count > 0) {
 				retreat = Math.Max ((SpecialRune (line [Math.Max (CurrentColumn - leftColumn - 1, 0)])
 				retreat = Math.Max ((SpecialRune (line [Math.Max (CurrentColumn - leftColumn - 1, 0)])
 				? 1 : 0), 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);
 			}
 			}
-			Move (CurrentColumn - leftColumn - retreat, CurrentRow - topRow);
 		}
 		}
 
 
 		void ClearRegion (int left, int top, int right, int bottom)
 		void ClearRegion (int left, int top, int right, int bottom)
@@ -569,10 +704,12 @@ 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 ();
@@ -581,6 +718,7 @@ namespace Terminal.Gui {
 					if (!SpecialRune (rune)) {
 					if (!SpecialRune (rune)) {
 						AddRune (col, row, rune);
 						AddRune (col, row, rune);
 					}
 					}
+					col = TextModel.SetCol (col, bounds.Right, cols);
 				}
 				}
 			}
 			}
 			PositionCursor ();
 			PositionCursor ();
@@ -641,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;
 			}
 			}
@@ -666,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
@@ -707,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 ();
 		}
 		}
 
 
@@ -786,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)
@@ -816,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:
@@ -890,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;
 
 
@@ -901,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
@@ -932,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
@@ -978,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)
@@ -990,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;
@@ -1000,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;
@@ -1012,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);
@@ -1063,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--;
@@ -1078,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++;
@@ -1090,6 +1218,8 @@ namespace Terminal.Gui {
 				}
 				}
 				TrackColumn ();
 				TrackColumn ();
 				PositionCursor ();
 				PositionCursor ();
+			} else if (currentRow > Frame.Height) {
+				Adjust ();
 			}
 			}
 		}
 		}
 
 
@@ -1186,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)
@@ -1217,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/>
@@ -1258,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>

+ 2 - 0
UICatalog/Scenarios/CharacterMap.cs

@@ -137,6 +137,8 @@ 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 * H_SPACE), 0);
 				Move (viewport.X + RowHeaderWidth + (header * H_SPACE), 0);
 				Driver.AddStr ($" {header:x} ");
 				Driver.AddStr ($" {header:x} ");

+ 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",

+ 56 - 6
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;
@@ -280,9 +284,27 @@ namespace UICatalog {
 			menuItems.Add (CreateDiagnosticFlagsMenuItems ());
 			menuItems.Add (CreateDiagnosticFlagsMenuItems ());
 			menuItems.Add (new MenuItem [] { null });
 			menuItems.Add (new MenuItem [] { null });
 			menuItems.Add (CreateSizeStyle ());
 			menuItems.Add (CreateSizeStyle ());
+			menuItems.Add (CreateAlwaysSetPosition ());
 			return menuItems;
 			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 ()
 		static MenuItem [] CreateSizeStyle ()
 		{
 		{
 			List<MenuItem> menuItems = new List<MenuItem> ();
 			List<MenuItem> menuItems = new List<MenuItem> ();
@@ -293,7 +315,8 @@ namespace UICatalog {
 			item.Checked = Application.HeightAsBuffer;
 			item.Checked = Application.HeightAsBuffer;
 			item.Action += () => {
 			item.Action += () => {
 				item.Checked = !item.Checked;
 				item.Checked = !item.Checked;
-				Application.HeightAsBuffer = item.Checked;
+				_heightAsBuffer = item.Checked;
+				Application.HeightAsBuffer = _heightAsBuffer;
 			};
 			};
 			menuItems.Add (item);
 			menuItems.Add (item);
 
 
@@ -475,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 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)
+			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);
+		}
+	}
+}

+ 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);
+		}
 	}
 	}
 }
 }