瀏覽代碼

Merge pull request #1 from migueldeicaza/master

update to latest master for scrollbar
Thomas Nind 4 年之前
父節點
當前提交
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
 
 * **[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.
 * **[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#.
@@ -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
 
+### 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
 
 * 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) {
-					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;
 			var mf = GetButtonState (cev, true);
@@ -358,7 +341,7 @@ namespace Terminal.Gui {
 			return mf;
 		}
 
-		private MouseFlags ProcessButtonReleasedEvent (Curses.MouseEvent cev)
+		MouseFlags ProcessButtonReleasedEvent (Curses.MouseEvent cev)
 		{
 			var mf = MapCursesButton (cev.ButtonState);
 			if (!cancelButtonClicked && LastMouseButtonPressed == null && !isReportMousePosition) {
@@ -371,6 +354,26 @@ namespace Terminal.Gui {
 			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 mf = default;

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

@@ -391,6 +391,7 @@ namespace Terminal.Gui {
 
 				keyHandler (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.
 //
 // Authors:
@@ -107,24 +108,40 @@ namespace Terminal.Gui {
 	internal class NetEvents {
 		ManualResetEventSlim inputReady = new ManualResetEventSlim (false);
 		ManualResetEventSlim waitForStart = new ManualResetEventSlim (false);
+		ManualResetEventSlim winChange = new ManualResetEventSlim (false);
 		Queue<InputResult?> inputResultQueue = new Queue<InputResult?> ();
-
+		ConsoleDriver consoleDriver;
+		int lastWindowHeight;
+		int largestWindowHeight;
+#if PROCESS_REQUEST
+		bool neededProcessRequest;
+#endif
 		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;
 			Task.Run (ProcessInputResultQueue);
+			Task.Run (CheckWinChange);
 		}
 
 		public InputResult? ReadConsoleInput ()
 		{
 			while (true) {
 				waitForStart.Set ();
+				winChange.Set ();
+
 				if (inputResultQueue.Count == 0) {
 					inputReady.Wait ();
 					inputReady.Reset ();
 				}
+#if PROCESS_REQUEST
+				neededProcessRequest = false;
+#endif
 				if (inputResultQueue.Count > 0) {
 					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)
 		{
 			InputResult inputResult = new InputResult {
@@ -174,6 +252,7 @@ namespace Terminal.Gui {
 				}
 				break;
 			case 27:
+			case 91:
 				ConsoleKeyInfo [] cki = new ConsoleKeyInfo [] { consoleKeyInfo };
 				ConsoleModifiers mod = consoleKeyInfo.Modifiers;
 				while (Console.KeyAvailable) {
@@ -207,30 +286,71 @@ namespace Terminal.Gui {
 			ConsoleKeyInfo [] splitedCki = new ConsoleKeyInfo [] { };
 			int length = 0;
 			var kChar = GetKeyCharArray (cki);
-			var nCSI = kChar.Where (val => val == '\x1b').ToArray ().Length;
+			var nCSI = GetNumberOfCSI (kChar);
 			int curCSI = 0;
+			char previousKChar = '\0';
 			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 (ck.KeyChar == '\x1b') {
+						if (cki [i + 1].KeyChar == '\x1b' && previousKChar != '\0') {
 							curCSI++;
+							previousKChar = '\0';
+						} else {
+							previousKChar = ck.KeyChar;
 						}
 						continue;
 					}
 					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);
 						}
 						splitedCki = new ConsoleKeyInfo [] { };
 						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 {
 				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)
@@ -306,7 +426,8 @@ namespace Terminal.Gui {
 					(mod & ConsoleModifiers.Control) != 0);
 				break;
 			case 7:
-				throw new NotImplementedException ("Condition not yet detected!");
+				GetRequestEvent (GetKeyCharArray (cki));
+				return;
 			case int n when n >= 8:
 				GetMouseEvent (cki);
 				return;
@@ -320,12 +441,63 @@ namespace Terminal.Gui {
 			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;
 		bool isButtonPressed;
 		bool isButtonClicked;
 		bool isButtonDoubleClicked;
 		bool isButtonTripleClicked;
 		bool isProcContBtnPressedRuning;
+		bool isButtonReleased;
 
 		void GetMouseEvent (ConsoleKeyInfo [] cki)
 		{
@@ -358,36 +530,64 @@ namespace Terminal.Gui {
 				} else if (c == 'm' || c == 'M') {
 					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) {
 					case 0:
 					case 8:
 					case 16:
+					case 24:
 					case 32:
+					case 36:
+					case 40:
+					case 48:
+					case 56:
 						buttonState = c == 'M' ? MouseButtonState.Button1Pressed
 							: MouseButtonState.Button1Released;
 						break;
 					case 1:
 					case 9:
 					case 17:
+					case 25:
 					case 33:
+					case 37:
+					case 41:
+					case 45:
+					case 49:
+					case 53:
+					case 57:
+					case 61:
 						buttonState = c == 'M' ? MouseButtonState.Button2Pressed
 							: MouseButtonState.Button2Released;
 						break;
 					case 2:
 					case 10:
+					case 14:
 					case 18:
+					case 22:
+					case 26:
+					case 30:
 					case 34:
+					case 42:
+					case 46:
+					case 50:
+					case 54:
+					case 58:
+					case 62:
 						buttonState = c == 'M' ? MouseButtonState.Button3Pressed
 							: MouseButtonState.Button3Released;
 						break;
 					case 35:
+					case 39:
 					case 43:
+					case 47:
+					case 55:
+					case 59:
+					case 63:
 						buttonState = MouseButtonState.ReportMousePosition;
 						break;
 					case 64:
@@ -396,11 +596,15 @@ namespace Terminal.Gui {
 					case 65:
 						buttonState = MouseButtonState.ButtonWheeledDown;
 						break;
+					case 68:
 					case 72:
-						buttonState = MouseButtonState.ButtonWheeledLeft;       // Ctrl+ButtonWheeledUp
+					case 80:
+						buttonState = MouseButtonState.ButtonWheeledLeft;       // Shift/Ctrl+ButtonWheeledUp
 						break;
+					case 69:
 					case 73:
-						buttonState = MouseButtonState.ButtonWheeledRight;      // Ctrl+ButtonWheeledDown
+					case 81:
+						buttonState = MouseButtonState.ButtonWheeledRight;      // Shift/Ctrl+ButtonWheeledDown
 						break;
 					}
 					// Modifiers.
@@ -408,17 +612,74 @@ namespace Terminal.Gui {
 					case 8:
 					case 9:
 					case 10:
+					case 43:
+						buttonState |= MouseButtonState.ButtonAlt;
+						break;
+					case 14:
+					case 47:
+						buttonState |= MouseButtonState.ButtonAlt | MouseButtonState.ButtonShift;
+						break;
 					case 16:
 					case 17:
 					case 18:
-					case 43:
+					case 51:
 						buttonState |= MouseButtonState.ButtonCtrl;
 						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 33:
 					case 34:
 						buttonState |= MouseButtonState.ReportMousePosition;
 						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.ButtonState = buttonState;
 
+			if ((buttonState & MouseButtonState.Button1Pressed) != 0
+				|| (buttonState & MouseButtonState.Button2Pressed) != 0
+				|| (buttonState & MouseButtonState.Button3Pressed) != 0) {
+				isButtonPressed = true;
+			} else {
+				isButtonPressed = false;
+			}
+
 			if ((isButtonClicked || isButtonDoubleClicked || isButtonTripleClicked)
 				&& ((buttonState & MouseButtonState.Button1Released) != 0
 				|| (buttonState & MouseButtonState.Button2Released) != 0
@@ -460,6 +729,16 @@ namespace Terminal.Gui {
 				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 () {
 				EventType = EventType.Mouse,
 				MouseEvent = mouseEvent
@@ -484,6 +763,7 @@ namespace Terminal.Gui {
 
 			lastMouseEvent = mouseEvent;
 			if (isButtonPressed && !isButtonClicked && !isButtonDoubleClicked && !isButtonTripleClicked && !isProcContBtnPressedRuning) {
+				isButtonReleased = false;
 				Application.MainLoop.AddIdle (() => {
 					ProcessContinuousButtonPressedAsync ().ConfigureAwait (false);
 					return false;
@@ -499,22 +779,18 @@ namespace Terminal.Gui {
 				Position = mouseEvent.Position,
 				ButtonState = mouseEvent.ButtonState
 			};
-			switch (mouseEvent.ButtonState) {
-			case MouseButtonState.Button1Released:
+			if ((mouseEvent.ButtonState & MouseButtonState.Button1Released) != 0) {
 				me.ButtonState &= ~MouseButtonState.Button1Released;
 				me.ButtonState |= MouseButtonState.Button1Clicked;
-				break;
-			case MouseButtonState.Button2Released:
+			} else if ((mouseEvent.ButtonState & MouseButtonState.Button2Released) != 0) {
 				me.ButtonState &= ~MouseButtonState.Button2Released;
 				me.ButtonState |= MouseButtonState.Button2Clicked;
-				break;
-			case MouseButtonState.Button3Released:
+			} else if ((mouseEvent.ButtonState & MouseButtonState.Button3Released) != 0) {
 				me.ButtonState &= ~MouseButtonState.Button3Released;
 				me.ButtonState |= MouseButtonState.Button3Clicked;
-				break;
-			default:
-				return;
 			}
+			isButtonReleased = true;
+
 			inputResultQueue.Enqueue (new InputResult () {
 				EventType = EventType.Mouse,
 				MouseEvent = me
@@ -527,22 +803,18 @@ namespace Terminal.Gui {
 				Position = mouseEvent.Position,
 				ButtonState = mouseEvent.ButtonState
 			};
-			switch (mouseEvent.ButtonState) {
-			case MouseButtonState.Button1Pressed:
+			if ((mouseEvent.ButtonState & MouseButtonState.Button1Pressed) != 0) {
 				me.ButtonState &= ~MouseButtonState.Button1Pressed;
 				me.ButtonState |= MouseButtonState.Button1DoubleClicked;
-				break;
-			case MouseButtonState.Button2Pressed:
+			} else if ((mouseEvent.ButtonState & MouseButtonState.Button2Pressed) != 0) {
 				me.ButtonState &= ~MouseButtonState.Button2Pressed;
 				me.ButtonState |= MouseButtonState.Button2DoubleClicked;
-				break;
-			case MouseButtonState.Button3Pressed:
+			} else if ((mouseEvent.ButtonState & MouseButtonState.Button3Pressed) != 0) {
 				me.ButtonState &= ~MouseButtonState.Button3Pressed;
 				me.ButtonState |= MouseButtonState.Button3DoubleClicked;
-				break;
-			default:
-				return;
 			}
+			isButtonReleased = true;
+
 			inputResultQueue.Enqueue (new InputResult () {
 				EventType = EventType.Mouse,
 				MouseEvent = me
@@ -555,22 +827,18 @@ namespace Terminal.Gui {
 				Position = mouseEvent.Position,
 				ButtonState = mouseEvent.ButtonState
 			};
-			switch (mouseEvent.ButtonState) {
-			case MouseButtonState.Button1Pressed:
+			if ((mouseEvent.ButtonState & MouseButtonState.Button1Pressed) != 0) {
 				me.ButtonState &= ~MouseButtonState.Button1Pressed;
 				me.ButtonState |= MouseButtonState.Button1TripleClicked;
-				break;
-			case MouseButtonState.Button2Pressed:
+			} else if ((mouseEvent.ButtonState & MouseButtonState.Button2Pressed) != 0) {
 				me.ButtonState &= ~MouseButtonState.Button2Pressed;
 				me.ButtonState |= MouseButtonState.Button2TrippleClicked;
-				break;
-			case MouseButtonState.Button3Pressed:
+			} else if ((mouseEvent.ButtonState & MouseButtonState.Button3Pressed) != 0) {
 				me.ButtonState &= ~MouseButtonState.Button3Pressed;
 				me.ButtonState |= MouseButtonState.Button3TripleClicked;
-				break;
-			default:
-				return;
 			}
+			isButtonReleased = true;
+
 			inputResultQueue.Enqueue (new InputResult () {
 				EventType = EventType.Mouse,
 				MouseEvent = me
@@ -585,7 +853,7 @@ namespace Terminal.Gui {
 				await Task.Delay (200);
 				var view = Application.wantContinuousButtonPressedView;
 				if (isButtonPressed && !Console.KeyAvailable
-					&&  !isButtonClicked && !isButtonDoubleClicked && !isButtonTripleClicked
+					&& !isButtonClicked && !isButtonDoubleClicked && !isButtonTripleClicked
 					&& (view != null || view == null && lastMouseEvent.Position != point)) {
 					point = lastMouseEvent.Position;
 					inputResultQueue.Enqueue (new InputResult () {
@@ -598,20 +866,32 @@ namespace Terminal.Gui {
 				}
 			}
 			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)
@@ -719,7 +999,9 @@ namespace Terminal.Gui {
 
 		public enum EventType {
 			key = 1,
-			Mouse = 2
+			Mouse = 2,
+			WindowSize = 3,
+			WindowPosition = 4
 		}
 
 		[Flags]
@@ -760,10 +1042,21 @@ namespace Terminal.Gui {
 			public MouseButtonState ButtonState;
 		}
 
+		public struct WindowSizeEvent {
+			public Size Size;
+		}
+
+		public struct WindowPositionEvent {
+			public int Top;
+			public Point CursorPosition;
+		}
+
 		public struct InputResult {
 			public EventType EventType;
 			public ConsoleKeyInfo ConsoleKeyInfo;
 			public MouseEvent MouseEvent;
+			public WindowSizeEvent WindowSizeEvent;
+			public WindowPositionEvent WindowPositionEvent;
 		}
 	}
 
@@ -775,16 +1068,19 @@ namespace Terminal.Gui {
 		public override bool HeightAsBuffer { get; set; }
 
 		public NetWinVTConsole NetWinConsole { get; }
+		public bool IsWinPlatform { get; }
+		public bool AlwaysSetPosition { get; set; }
 
-		bool isWinPlatform;
+		int largestWindowHeight;
 
 		public NetDriver ()
 		{
 			var p = Environment.OSVersion.Platform;
 			if (p == PlatformID.Win32NT || p == PlatformID.Win32S || p == PlatformID.Win32Windows) {
-				isWinPlatform = true;
+				IsWinPlatform = true;
 				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
@@ -848,7 +1144,7 @@ namespace Terminal.Gui {
 
 		public override void End ()
 		{
-			if (isWinPlatform) {
+			if (IsWinPlatform) {
 				NetWinConsole.Cleanup ();
 			}
 
@@ -861,6 +1157,8 @@ namespace Terminal.Gui {
 		{
 			if (Rows > 0) {
 				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.
 					try {
 						// Not supported on Unix.
-						if (isWinPlatform) {
+						if (IsWinPlatform) {
 #pragma warning disable CA1416
 							Console.CursorTop = 0;
 							Console.CursorLeft = 0;
@@ -954,7 +1252,7 @@ namespace Terminal.Gui {
 					}
 				}
 			} else {
-				if (isWinPlatform && Console.WindowHeight > 0) {
+				if (IsWinPlatform && Console.WindowHeight > 0) {
 					// Can raise an exception while is still resizing.
 					try {
 #pragma warning disable CA1416
@@ -1012,11 +1310,16 @@ namespace Terminal.Gui {
 			}
 		}
 
+		public override void Refresh ()
+		{
+			UpdateScreen ();
+		}
+
 		public override void UpdateScreen ()
 		{
 			if (winChanging || Console.WindowHeight == 0 || contents.Length != Rows * Cols * 3
 				|| (!HeightAsBuffer && Rows != Console.WindowHeight)
-				|| (HeightAsBuffer && Rows != Console.BufferHeight)) {
+				|| (HeightAsBuffer && Rows != largestWindowHeight)) {
 				return;
 			}
 
@@ -1024,6 +1327,7 @@ namespace Terminal.Gui {
 			int rows = Math.Min (Console.WindowHeight + top, Rows);
 			int cols = Cols;
 
+			Console.CursorVisible = false;
 			for (int row = top; row < rows; row++) {
 				if (!dirtyLine [row]) {
 					continue;
@@ -1033,31 +1337,35 @@ namespace Terminal.Gui {
 					if (contents [row, col, 2] != 1) {
 						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++) {
 						var color = contents [row, col, 1];
 						if (color != redrawColor) {
 							SetColor (color);
 						}
+						if (AlwaysSetPosition && !SetCursorPosition (col, row)) {
+							return;
+						}
 						Console.Write ((char)contents [row, col, 0]);
 						contents [row, col, 2] = 0;
 					}
 				}
 			}
-
+			Console.CursorVisible = true;
 			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 ()
@@ -1230,15 +1538,42 @@ namespace Terminal.Gui {
 
 			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;
 
-		void ChangeWin (int newTop)
+		void ChangeWin ()
 		{
 			winChanging = true;
 			const int Min_WindowWidth = 14;
@@ -1248,8 +1583,8 @@ namespace Terminal.Gui {
 					Console.WindowHeight);
 				top = 0;
 			} 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;
 			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)
 		{
 			MouseFlags mouseFlag = 0;
@@ -1416,24 +1732,15 @@ namespace Terminal.Gui {
 	internal class NetMainLoop : IMainLoopDriver {
 		ManualResetEventSlim keyReady = new ManualResetEventSlim (false);
 		ManualResetEventSlim waitForProbe = new ManualResetEventSlim (false);
-		ManualResetEventSlim winChange = new ManualResetEventSlim (false);
 		Queue<NetEvents.InputResult?> inputResult = new Queue<NetEvents.InputResult?> ();
 		MainLoop mainLoop;
-		ConsoleDriver consoleDriver;
-		bool winChanged;
-		int newTop;
 		CancellationTokenSource tokenSource = new CancellationTokenSource ();
 		NetEvents netEvents;
 
 		/// <summary>
 		/// Invoked when a Key is pressed.
 		/// </summary>
-		public Action<NetEvents.InputResult> KeyPressed;
-
-		/// <summary>
-		/// Invoked when the window is changed.
-		/// </summary>
-		public Action<int> WinChanged;
+		public Action<NetEvents.InputResult> ProcessInput;
 
 		/// <summary>
 		/// Initializes the class with the console driver.
@@ -1447,11 +1754,10 @@ namespace Terminal.Gui {
 			if (consoleDriver == null) {
 				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) {
 				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)
 		{
 			this.mainLoop = mainLoop;
-			Task.Run (KeyReader);
-			Task.Run (CheckWinChange);
+			Task.Run (NetInputHandler);
 		}
 
 		void IMainLoopDriver.Wakeup ()
@@ -1517,7 +1790,6 @@ namespace Terminal.Gui {
 		bool IMainLoopDriver.EventsPending (bool wait)
 		{
 			waitForProbe.Set ();
-			winChange.Set ();
 
 			if (CheckTimers (wait, out var waitTimeout)) {
 				return true;
@@ -1534,7 +1806,7 @@ namespace Terminal.Gui {
 			}
 
 			if (!tokenSource.IsCancellationRequested) {
-				return inputResult.Count > 0 || CheckTimers (wait, out _) || winChanged;
+				return inputResult.Count > 0 || CheckTimers (wait, out _);
 			}
 
 			tokenSource.Dispose ();
@@ -1568,11 +1840,7 @@ namespace Terminal.Gui {
 		void IMainLoopDriver.MainIteration ()
 		{
 			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;
 			}
 
+			var p = new Point () {
+				X = mouseEvent.MousePosition.X,
+				Y = mouseEvent.MousePosition.Y
+			};
+
 			if ((mouseEvent.ButtonState != 0 && mouseEvent.EventFlags == 0 && LastMouseButtonPressed == null && !IsButtonDoubleClicked) ||
 				(mouseEvent.EventFlags == WindowsConsole.EventFlags.MouseMoved &&
 				mouseEvent.ButtonState != 0 && !IsButtonReleased && !IsButtonDoubleClicked)) {
@@ -854,12 +859,7 @@ namespace Terminal.Gui {
 				IsButtonPressed = false;
 				IsButtonReleased = true;
 			} 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) {
 				case WindowsConsole.ButtonState.Button1Pressed:
 					mouseFlag = MouseFlags.Button1Clicked;
@@ -877,9 +877,6 @@ namespace Terminal.Gui {
 					X = mouseEvent.MousePosition.X,
 					Y = mouseEvent.MousePosition.Y
 				};
-				//} else {
-				//	mouseFlag = 0;
-				//}
 				LastMouseButtonPressed = null;
 				IsButtonReleased = false;
 			} else if (mouseEvent.EventFlags.HasFlag (WindowsConsole.EventFlags.DoubleClick)) {
@@ -964,7 +961,7 @@ namespace Terminal.Gui {
 		async Task ProcessContinuousButtonPressedAsync (WindowsConsole.MouseEventRecord mouseEvent, MouseFlags mouseFlag)
 		{
 			while (IsButtonPressed) {
-				await Task.Delay (200);
+				await Task.Delay (100);
 				var me = new MouseEvent () {
 					X = mouseEvent.MousePosition.X,
 					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; }
 
 		/// <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>
 		/// <value>The current.</value>
 		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>
 		/// The current <see cref="ConsoleDriver.HeightAsBuffer"/> used in the terminal.
 		/// </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>
 		/// The <see cref="MainLoop"/>  driver for the application
 		/// </summary>
@@ -175,7 +187,7 @@ namespace Terminal.Gui {
 		/// Loads the right <see cref="ConsoleDriver"/> for the platform.
 		/// </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>
 		/// </remarks>
 		public static void Init (ConsoleDriver driver = null, IMainLoopDriver mainLoopDriver = null) => Init (() => Toplevel.Create (), driver, mainLoopDriver);
@@ -226,7 +238,6 @@ namespace Terminal.Gui {
 			}
 			Top = topLevelFactory ();
 			Current = Top;
-			CurrentView = Top;
 			_initialized = true;
 		}
 
@@ -340,7 +351,7 @@ namespace Terminal.Gui {
 					var ry = y - startFrame.Y;
 					for (int i = count - 1; i >= 0; 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);
 							if (deep == null)
 								return v;
@@ -420,8 +431,8 @@ namespace Terminal.Gui {
 					X = rx,
 					Y = ry,
 					Flags = me.Flags,
-					OfX = rx,
-					OfY = ry,
+					OfX = 0,
+					OfY = 0,
 					View = view
 				};
 
@@ -523,19 +534,22 @@ namespace Terminal.Gui {
 			}
 			toplevels.Clear ();
 			Current = null;
-			CurrentView = null;
 			Top = null;
 
 			MainLoop = null;
 			Driver?.End ();
 			Driver = null;
 			_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)
 		{
-			Application.CurrentView = view;
-
 			view.Redraw (view.Bounds);
 			Driver.Refresh ();
 		}
@@ -570,10 +584,8 @@ namespace Terminal.Gui {
 
 			if (toplevels.Count == 0) {
 				Current = null;
-				CurrentView = null;
 			} else {
 				Current = toplevels.Peek ();
-				CurrentView = Current;
 				Refresh ();
 			}
 		}
@@ -637,20 +649,20 @@ namespace Terminal.Gui {
 		}
 
 		/// <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>
-		public static void Run ()
+		public static void Run (Func<Exception, bool> errorHandler = null)
 		{
-			Run (Top);
+			Run (Top, errorHandler);
 		}
 
 		/// <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>
-		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 ());
-			Run (Top);
+			Run (Top, errorHandler);
 		}
 
 		/// <summary>
@@ -663,10 +675,10 @@ namespace Terminal.Gui {
 		///     run other modal <see cref="View"/>s such as <see cref="Dialog"/> boxes.
 		///   </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>
-		///     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)"/>.
 		///   </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
 		///     then return control immediately.
 		///   </para>
+		///   <para>
+		///     When <paramref name="errorHandler"/> is null the exception is rethrown, when it returns true the application is resumed and when false method exits gracefully.
+		///   </para>
 		/// </remarks>
 		/// <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>
@@ -690,7 +722,7 @@ namespace Terminal.Gui {
 		/// </summary>
 		/// <remarks>
 		///   <para>
-		///   This will cause <see cref="Application.Run()"/> to return.
+		///   This will cause <see cref="Application.Run(Func{Exception, bool})"/> to return.
 		///   </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.

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

@@ -392,7 +392,7 @@ namespace Terminal.Gui {
 		/// Gets a value indicating whether the Shift key was pressed.
 		/// </summary>
 		/// <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>
 		/// 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>
 	/// <remarks>
 	///   <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 
 	///     been called (which sets the <see cref="Toplevel.Running"/> property to false). 
 	///   </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)"/>.
 	///     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 
-	///     call <see cref="Application.Run(Toplevel)"/>.
+	///     call <see cref="Application.Run(Toplevel, Func{Exception, bool})"/>.
 	///   </para>
 	///   <para>
 	///     Toplevels can also opt-in to more sophisticated initialization
@@ -57,7 +57,7 @@ namespace Terminal.Gui {
 		/// <summary>
 		/// 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.
-		/// 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>
 		public event Action Ready;
 
@@ -195,7 +195,7 @@ namespace Terminal.Gui {
 			if (base.ProcessKey (keyEvent))
 				return true;
 
-			switch (keyEvent.Key) {
+			switch (ShortcutHelper.GetModifiersKey (keyEvent)) {
 			case Key.Q | Key.CtrlMask:
 				// FIXED: stop current execution of this container
 				Application.RequestStop ();
@@ -217,27 +217,32 @@ namespace Terminal.Gui {
 				var old = GetDeepestFocusedSubview (Focused);
 				if (!FocusNext ())
 					FocusNext ();
-				if (old != Focused) {
+				if (old != Focused && old != Focused?.Focused) {
 					old?.SetNeedsDisplay ();
 					Focused?.SetNeedsDisplay ();
 				} else {
-					FocusNearestView (GetToplevelSubviews (true));
+					FocusNearestView (SuperView?.TabIndexes, Direction.Forward);
 				}
 				return true;
+			case Key.BackTab | Key.ShiftMask:
 			case Key.CursorLeft:
 			case Key.CursorUp:
-			case Key.BackTab:
 				old = GetDeepestFocusedSubview (Focused);
 				if (!FocusPrev ())
 					FocusPrev ();
-				if (old != Focused) {
+				if (old != Focused && old != Focused?.Focused) {
 					old?.SetNeedsDisplay ();
 					Focused?.SetNeedsDisplay ();
 				} else {
-					FocusNearestView (GetToplevelSubviews (false));
+					FocusNearestView (SuperView?.TabIndexes?.Reverse(), Direction.Backward);
 				}
 				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:
 				Application.Refresh ();
 				return true;
@@ -272,39 +277,34 @@ namespace Terminal.Gui {
 			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) {
 				return;
 			}
 
 			bool found = false;
+			bool focusProcessed = false;
+			int idx = 0;
 
 			foreach (var v in views) {
 				if (v == this) {
 					found = true;
 				}
 				if (found && v != this) {
-					v.EnsureFocus ();
+					if (direction == Direction.Forward) {
+						SuperView?.FocusNext ();
+					} else {
+						SuperView?.FocusPrev ();
+					}
+					focusProcessed = true;
 					if (SuperView.Focused != null && SuperView.Focused != this) {
 						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)
 		{
 			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;
 			if (SuperView == null || SuperView.GetType () != typeof (Toplevel)) {
 				m = Application.Top.MenuBar != null;
 			} else {
 				m = ((Toplevel)SuperView).MenuBar != null;
 			}
-			int l;
 			if (SuperView == null || SuperView is Toplevel) {
 				l = m ? 1 : 0;
 			} else {
@@ -389,6 +399,11 @@ namespace Terminal.Gui {
 			}
 			ny = Math.Min (ny, l);
 			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 ()
@@ -428,8 +443,6 @@ namespace Terminal.Gui {
 		///<inheritdoc/>
 		public override void Redraw (Rect bounds)
 		{
-			Application.CurrentView = this;
-
 			if (IsCurrentTop || this == Application.Top) {
 				if (!NeedDisplay.IsEmpty || LayoutNeeded) {
 					Driver.SetAttribute (Colors.TopLevel.Normal);
@@ -456,7 +469,7 @@ namespace Terminal.Gui {
 		}
 
 		/// <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.
 		/// </summary>
 		public virtual void WillPresent ()

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

@@ -1125,10 +1125,10 @@ namespace Terminal.Gui {
 				return;
 			}
 
-			if (focused != null) {
+			if (focused?.Frame.Width > 0 && focused.Frame.Height > 0) {
 				focused.PositionCursor ();
 			} else {
-				if (CanFocus && HasFocus && Visible) {
+				if (CanFocus && HasFocus && Visible && Frame.Width > 0 && Frame.Height > 0) {
 					Move (textFormatter.HotKeyPos == -1 ? 0 : textFormatter.CursorPosition, 0);
 				} else {
 					Move (frame.X, frame.Y);
@@ -1314,8 +1314,6 @@ namespace Terminal.Gui {
 				return;
 			}
 
-			Application.CurrentView = this;
-
 			var clipRect = new Rect (Point.Empty, frame.Size);
 
 			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.LayoutNeeded)
 								view.LayoutSubviews ();
-							Application.CurrentView = view;
 
 							// Draw the subview
 							// 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);
 							}
 						}
@@ -1969,6 +1967,7 @@ namespace Terminal.Gui {
 
 		bool ResizeView (bool autoSize)
 		{
+			var aSize = autoSize;
 			if (textFormatter.Size != Bounds.Size && (((width == null || width is Dim.DimAbsolute) && (Bounds.Width == 0
 				|| autoSize && Bounds.Width != textFormatter.Size.Width))
 				|| ((height == null || height is Dim.DimAbsolute) && (Bounds.Height == 0
@@ -1979,17 +1978,17 @@ namespace Terminal.Gui {
 				} else if (width is Dim.DimAbsolute) {
 					width = Math.Max (Bounds.Width, height.Anchor (Bounds.Width));
 				} else {
-					return false;
+					aSize = false;
 				}
 				if (height == null) {
 					height = Bounds.Height;
 				} else if (height is Dim.DimAbsolute) {
 					height = Math.Max (Bounds.Height, height.Anchor (Bounds.Height));
 				} else {
-					return false;
+					aSize = false;
 				}
 			}
-			return autoSize;
+			return aSize;
 		}
 
 		/// <summary>
@@ -2144,5 +2143,65 @@ namespace Terminal.Gui {
 
 			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)
 		{
 			//var padding = 0;
-			Application.CurrentView = this;
 			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?
@@ -185,6 +184,7 @@ namespace Terminal.Gui {
 			contentView.Redraw (contentView.Bounds);
 			Driver.Clip = savedClip;
 
+			ClearLayoutNeeded ();
 			ClearNeedsDisplay ();
 			Driver.SetAttribute (ColorScheme.Normal);
 			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.
 			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);
 				}
 
-				//Demo.ml2.Text = $"Starting at {dragPosition}";
+				//System.Diagnostics.Debug.WriteLine ($"Starting at {dragPosition}");
 				return true;
 			} else if (mouseEvent.Flags == (MouseFlags.Button1Pressed | MouseFlags.ReportMousePosition) ||
 				mouseEvent.Flags == MouseFlags.Button3Pressed) {
@@ -249,10 +245,15 @@ namespace Terminal.Gui {
 						mouseEvent.Y + (SuperView == null ? mouseEvent.OfY : Frame.Y), out nx, out ny);
 
 					dragPosition = new Point (nx, ny);
+					LayoutSubviews ();
 					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.
 					SetNeedsDisplay ();
@@ -266,7 +267,7 @@ namespace Terminal.Gui {
 				dragPosition = null;
 			}
 
-			//Demo.ml.Text = me.ToString ();
+			//System.Diagnostics.Debug.WriteLine (mouseEvent.ToString ());
 			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);
 
 			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;
 			bool layoutChanged = false;
 			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)
 		{
 			var padding = 0;
-			Application.CurrentView = this;
 			var scrRect = ViewToScreen (new Rect (0, 0, Frame.Width, Frame.Height));
 
 			if (!NeedDisplay.IsEmpty) {

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

@@ -34,6 +34,11 @@ namespace Terminal.Gui {
 		/// </summary>
 		int Count { get; }
 
+		/// <summary>
+		/// Returns the maximum length of elements to display
+		/// </summary>
+		int Length { get; }
+
 		/// <summary>
 		/// This method is invoked to render a specified item, the method should cover the entire provided width.
 		/// </summary>
@@ -45,10 +50,11 @@ namespace Terminal.Gui {
 		/// <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="width">The width that must be filled out.</param>
+		/// <param name="start">The index of the string to be displayed.</param>
 		/// <remarks>
 		///   The default color will be set before this method is invoked, and will be based on whether the item is selected or not.
 		/// </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>
 		/// Should return whether the specified item is currently marked.
@@ -103,7 +109,7 @@ namespace Terminal.Gui {
 	/// </para>
 	/// </remarks>
 	public class ListView : View {
-		int top;
+		int top, left;
 		int selected;
 
 		IListDataSource source;
@@ -146,7 +152,7 @@ namespace Terminal.Gui {
 		/// </summary>
 		/// <value>An item implementing the IList interface.</value>
 		/// <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>
 		public Task SetSourceAsync (IList source)
 		{
@@ -204,13 +210,35 @@ namespace Terminal.Gui {
 				if (source == null)
 					return;
 
-				if (top < 0 || top >= source.Count)
+				if (value < 0 || (source.Count > 0 && value >= source.Count))
 					throw new ArgumentException ("value");
 				top = value;
 				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>
 		/// Gets or sets the index of the currently selected item.
 		/// </summary>
@@ -229,7 +257,6 @@ namespace Terminal.Gui {
 			}
 		}
 
-
 		static IListDataSource MakeWrapper (IList source)
 		{
 			return new ListWrapper (source);
@@ -298,14 +325,10 @@ namespace Terminal.Gui {
 			Driver.SetAttribute (current);
 			Move (0, 0);
 			var f = Frame;
-			if (selected < top) {
-				top = selected;
-			} else if (selected >= top + f.Height) {
-				top = selected;
-			}
 			var item = top;
 			bool focused = HasFocus;
 			int col = allowsMarking ? 2 : 0;
+			int start = left;
 
 			for (int row = 0; row < f.Height; row++, item++) {
 				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.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.
 				selected++;
 
-				if (selected >= top + Frame.Height)
+				if (selected >= top + Frame.Height) {
 					top++;
+				} else if (selected < top) {
+					top = selected;
+				}
 				OnSelectedChanged ();
 				SetNeedsDisplay ();
 			} else if (selected == 0) {
@@ -511,8 +537,11 @@ namespace Terminal.Gui {
 				if (selected > Source.Count) {
 					selected = Source.Count - 1;
 				}
-				if (selected < top)
+				if (selected < top) {
 					top = selected;
+				} else if (selected > top + Frame.Height) {
+					top = Math.Max (selected - Frame.Height + 1, 0);
+				}
 				OnSelectedChanged ();
 				SetNeedsDisplay ();
 			}
@@ -551,6 +580,46 @@ namespace Terminal.Gui {
 			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;
 		private bool allowsMultipleSelection = true;
 
@@ -563,7 +632,9 @@ namespace Terminal.Gui {
 			if (selected != lastSelectedItem) {
 				var value = source?.Count > 0 ? source.ToList () [selected] : null;
 				SelectedItemChanged?.Invoke (new ListViewItemEventArgs (selected, value));
-				lastSelectedItem = selected;
+				if (HasFocus) {
+					lastSelectedItem = selected;
+				}
 				return true;
 			}
 
@@ -587,6 +658,7 @@ namespace Terminal.Gui {
 		public override bool OnEnter (View view)
 		{
 			if (lastSelectedItem == -1) {
+				EnsuresVisibilitySelectedItem ();
 				OnSelectedChanged ();
 				return true;
 			}
@@ -605,6 +677,16 @@ namespace Terminal.Gui {
 			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/>
 		public override void PositionCursor ()
 		{
@@ -618,7 +700,8 @@ namespace Terminal.Gui {
 		public override bool MouseEvent (MouseEvent me)
 		{
 			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;
 
 			if (!HasFocus && CanFocus) {
@@ -630,10 +713,16 @@ namespace Terminal.Gui {
 			}
 
 			if (me.Flags == MouseFlags.WheeledDown) {
-				MoveDown ();
+				ScrollDown (1);
 				return true;
 			} 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;
 			}
 
@@ -655,6 +744,8 @@ namespace Terminal.Gui {
 
 			return true;
 		}
+
+
 	}
 
 	/// <summary>
@@ -664,7 +755,7 @@ namespace Terminal.Gui {
 	public class ListWrapper : IListDataSource {
 		IList src;
 		BitArray marks;
-		int count;
+		int count, len;
 
 		/// <summary>
 		/// Initializes a new instance of <see cref="ListWrapper"/> given an <see cref="IList"/>
@@ -675,7 +766,8 @@ namespace Terminal.Gui {
 			if (source != null) {
 				count = source.Count;
 				marks = new BitArray (count);
-				this.src = source;
+				src = source;
+				len = GetMaxLengthItem ();
 			}
 		}
 
@@ -684,11 +776,42 @@ namespace Terminal.Gui {
 		/// </summary>
 		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 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 count = Rune.ColumnWidth (rune);
 				if (used + count > width)
@@ -712,19 +835,21 @@ namespace Terminal.Gui {
 		/// <param name="col">The col where to move.</param>
 		/// <param name="line">The line where to move.</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);
 			var t = src [item];
 			if (t == null) {
 				RenderUstr (driver, ustring.Make (""), col, line, width);
 			} 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>
 		public int Item { get; }
 		/// <summary>
-		/// The the <see cref="ListView"/> item.
+		/// The <see cref="ListView"/> item.
 		/// </summary>
 		public object Value { get; }
 
 		/// <summary>
 		/// Initializes a new instance of <see cref="ListViewItemEventArgs"/>
 		/// </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>
 		public ListViewItemEventArgs (int item, object value)
 		{

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

@@ -453,7 +453,7 @@ namespace Terminal.Gui {
 						Driver.AddRune (' ');
 
 				if (item == null) {
-					Move (Frame.Right - 1, i + 1);
+					Move (Frame.Width - 1, i + 1);
 					Driver.AddRune (Driver.RightTee);
 					continue;
 				}
@@ -927,6 +927,9 @@ namespace Terminal.Gui {
 		///<inheritdoc/>
 		public override void PositionCursor ()
 		{
+			if (selected == -1 && HasFocus && Menus.Length > 0) {
+				selected = 0;
+			}
 			int pos = 0;
 			for (int i = 0; i < Menus.Length; i++) {
 				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:
 //   Miguel de Icaza ([email protected])
@@ -15,312 +15,6 @@ using System;
 using System.Reflection;
 
 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>
 	/// Scrollviews are views that present a window into a virtual space where subviews are added.  Similar to the iOS UIScrollView.
 	/// </summary>
@@ -384,6 +78,8 @@ namespace Terminal.Gui {
 
 			MouseEnter += View_MouseEnter;
 			MouseLeave += View_MouseLeave;
+			contentView.MouseEnter += View_MouseEnter;
+			contentView.MouseLeave += View_MouseLeave;
 		}
 
 		Size contentSize;
@@ -394,7 +90,7 @@ namespace Terminal.Gui {
 		bool autoHideScrollBars = true;
 
 		/// <summary>
-		/// Represents the contents of the data shown inside the scrolview
+		/// Represents the contents of the data shown inside the scrollview
 		/// </summary>
 		/// <value>The size of the content.</value>
 		public Size ContentSize {
@@ -421,11 +117,20 @@ namespace Terminal.Gui {
 				return contentOffset;
 			}
 			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 {
 				if (keepContentAlwaysInViewport != value) {
 					keepContentAlwaysInViewport = value;
+					vertical.OtherScrollBarView.KeepContentAlwaysInViewport = value;
+					horizontal.OtherScrollBarView.KeepContentAlwaysInViewport = value;
 					Point p = default;
 					if (value && -contentOffset.X + Bounds.Width > contentSize.Width) {
 						p = new Point (contentSize.Width - Bounds.Width + (showVerticalScrollIndicator ? 1 : 0), -contentOffset.Y);
@@ -484,7 +191,9 @@ namespace Terminal.Gui {
 
 		void View_MouseLeave (MouseEventArgs e)
 		{
-			Application.UngrabMouse ();
+			if (Application.mouseGrabView != null && Application.mouseGrabView != vertical && Application.mouseGrabView != horizontal) {
+				Application.UngrabMouse ();
+			}
 		}
 
 		void View_MouseEnter (MouseEventArgs e)
@@ -507,17 +216,21 @@ namespace Terminal.Gui {
 		public bool ShowHorizontalScrollIndicator {
 			get => showHorizontalScrollIndicator;
 			set {
-				if (value == showHorizontalScrollIndicator)
+				if (value == showHorizontalScrollIndicator) {
 					return;
+				}
 
 				showHorizontalScrollIndicator = value;
 				SetNeedsLayout ();
 				if (value) {
 					base.Add (horizontal);
+					horizontal.OtherScrollBarView = vertical;
+					horizontal.OtherScrollBarView.ShowScrollIndicator = value;
 					horizontal.MouseEnter += View_MouseEnter;
 					horizontal.MouseLeave += View_MouseLeave;
 				} else {
-					Remove (horizontal);
+					base.Remove (horizontal);
+					horizontal.OtherScrollBarView = null;
 					horizontal.MouseEnter -= View_MouseEnter;
 					horizontal.MouseLeave -= View_MouseLeave;
 				}
@@ -542,17 +255,21 @@ namespace Terminal.Gui {
 		public bool ShowVerticalScrollIndicator {
 			get => showVerticalScrollIndicator;
 			set {
-				if (value == showVerticalScrollIndicator)
+				if (value == showVerticalScrollIndicator) {
 					return;
+				}
 
 				showVerticalScrollIndicator = value;
 				SetNeedsLayout ();
 				if (value) {
 					base.Add (vertical);
+					vertical.OtherScrollBarView = horizontal;
+					vertical.OtherScrollBarView.ShowScrollIndicator = value;
 					vertical.MouseEnter += View_MouseEnter;
 					vertical.MouseLeave += View_MouseLeave;
 				} else {
 					Remove (vertical);
+					vertical.OtherScrollBarView = null;
 					vertical.MouseEnter -= View_MouseEnter;
 					vertical.MouseLeave -= View_MouseLeave;
 				}
@@ -706,7 +423,7 @@ namespace Terminal.Gui {
 		/// <param name="lines">Number of lines to scroll.</param>
 		public bool ScrollDown (int lines)
 		{
-			if (CanScroll (lines, out _, true)) {
+			if (vertical.CanScroll (lines, out _, true)) {
 				ContentOffset = new Point (contentOffset.X, contentOffset.Y - lines);
 				return true;
 			}
@@ -720,28 +437,13 @@ namespace Terminal.Gui {
 		/// <param name="cols">Number of columns to scroll by.</param>
 		public bool ScrollRight (int cols)
 		{
-			if (CanScroll (cols, out _)) {
+			if (horizontal.CanScroll (cols, out _)) {
 				ContentOffset = new Point (contentOffset.X - cols, contentOffset.Y);
 				return true;
 			}
 			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/>
 		public override bool ProcessKey (KeyEvent kb)
 		{

+ 14 - 90
Terminal.Gui/Views/TextField.cs

@@ -115,8 +115,6 @@ namespace Terminal.Gui {
 			get => base.Frame;
 			set {
 				base.Frame = value;
-				var w = base.Frame.Width;
-				first = point > w ? point - w : 0;
 				Adjust ();
 			}
 		}
@@ -157,7 +155,7 @@ namespace Terminal.Gui {
 				TextChanged?.Invoke (oldText);
 
 				if (point > text.Count) {
-					point = Math.Max (DisplaySize (text, 0).size - 1, 0);
+					point = Math.Max (TextModel.DisplaySize (text, 0).size - 1, 0);
 				}
 
 				Adjust ();
@@ -195,7 +193,7 @@ namespace Terminal.Gui {
 				if (idx == point)
 					break;
 				var cols = Rune.ColumnWidth (text [idx]);
-				col = SetCol (col, Frame.Width - 1, cols);
+				col = TextModel.SetCol (col, Frame.Width - 1, cols);
 			}
 			Move (col, 0);
 		}
@@ -227,7 +225,7 @@ namespace Terminal.Gui {
 				if (col + cols <= width) {
 					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) {
 					break;
 				}
@@ -241,45 +239,15 @@ namespace Terminal.Gui {
 			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 ()
 		{
 			int offB = OffSetBackground ();
 			if (point < first) {
 				first = point;
 			} 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 ();
 		}
@@ -294,33 +262,6 @@ namespace Terminal.Gui {
 			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)
 		{
 			Text = ustring.Make (newText);
@@ -779,7 +720,7 @@ namespace Terminal.Gui {
 		{
 			// We could also set the cursor position.
 			int x;
-			var pX = GetPointFromX (text, first, ev.X);
+			var pX = TextModel.GetColFromX (text, first, ev.X);
 			if (text.Count == 0) {
 				x = pX - ev.OfX;
 			} else {
@@ -792,7 +733,7 @@ namespace Terminal.Gui {
 		{
 			int pX = x;
 			if (getX) {
-				pX = GetPointFromX (text, first, x);
+				pX = TextModel.GetColFromX (text, first, x);
 			}
 			if (first + pX > text.Count) {
 				point = text.Count;
@@ -805,23 +746,6 @@ namespace Terminal.Gui {
 			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)
 		{
 			x = x + first < 0 ? 0 : x;
@@ -897,9 +821,9 @@ namespace Terminal.Gui {
 			ustring actualText = Text;
 			int selStart = SelectedLength < 0 ? SelectedLength + SelectedStart : SelectedStart;
 			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] +
 				actualText[len + len2, len + len2 + len3];
 			ClearAllSelection ();
@@ -919,9 +843,9 @@ namespace Terminal.Gui {
 			SetSelectedStartSelectedLength ();
 			int selStart = start == -1 ? CursorPosition : start;
 			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 ?? "";
 			Text = actualText [0, len] +
 				cbTxt +

+ 277 - 127
Terminal.Gui/Views/TextView.cs

@@ -171,6 +171,101 @@ namespace Terminal.Gui {
 		{
 			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>
@@ -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>
 		/// Loads the contents of the file into the  <see cref="TextView"/>.
 		/// </summary>
@@ -421,11 +545,22 @@ namespace Terminal.Gui {
 			}
 			var line = model.GetLine (currentRow);
 			var retreat = 0;
+			var col = 0;
 			if (line.Count > 0) {
 				retreat = Math.Max ((SpecialRune (line [Math.Max (CurrentColumn - leftColumn - 1, 0)])
 				? 1 : 0), 0);
+				for (int idx = leftColumn < 0 ? 0 : leftColumn; idx < line.Count; idx++) {
+					if (idx == CurrentColumn)
+						break;
+					var cols = Rune.ColumnWidth (line [idx]);
+					col += cols - 1;
+				}
+			}
+			var ccol = CurrentColumn - leftColumn - retreat + col;
+			if (leftColumn <= CurrentColumn && ccol < Frame.Width
+				&& topRow <= CurrentRow && CurrentRow - topRow < Frame.Height) {
+				Move (ccol, CurrentRow - topRow);
 			}
-			Move (CurrentColumn - leftColumn - retreat, CurrentRow - topRow);
 		}
 
 		void ClearRegion (int left, int top, int right, int bottom)
@@ -569,10 +704,12 @@ namespace Terminal.Gui {
 				}
 
 				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];
-					if (selecting && PointInSelection (col, row)) {
+					var cols = Rune.ColumnWidth (rune);
+					if (selecting && PointInSelection (idx, row)) {
 						ColorSelection ();
 					} else {
 						ColorNormal ();
@@ -581,6 +718,7 @@ namespace Terminal.Gui {
 					if (!SpecialRune (rune)) {
 						AddRune (col, row, rune);
 					}
+					col = TextModel.SetCol (col, bounds.Right, cols);
 				}
 			}
 			PositionCursor ();
@@ -641,19 +779,25 @@ namespace Terminal.Gui {
 
 		void InsertText (ustring text)
 		{
+			if (ustring.IsNullOrEmpty (text)) {
+				return;
+			}
+
 			var lines = TextModel.StringToRunes (text);
 
-			if (lines.Count == 0)
+			if (lines.Count == 0) {
 				return;
+			}
 
 			var line = GetCurrentLine ();
 
-			// Optmize single line
+			// Optimize single line
 			if (lines.Count == 1) {
 				line.InsertRange (currentColumn, lines [0]);
 				currentColumn += lines [0].Count;
-				if (currentColumn - leftColumn > Frame.Width)
+				if (currentColumn - leftColumn > Frame.Width) {
 					leftColumn = currentColumn - Frame.Width + 1;
+				}
 				SetNeedsDisplay (new Rect (0, currentRow - topRow, Frame.Width, currentRow - topRow + 1));
 				return;
 			}
@@ -666,26 +810,18 @@ namespace Terminal.Gui {
 			// First line is inserted at the current location, the rest is appended
 			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]);
+			}
 
 			var last = model.GetLine (currentRow + lines.Count - 1);
 			var lastp = last.Count;
 			last.InsertRange (last.Count, rest);
 
-			// Now adjjust column and row positions
+			// Now adjust column and row positions
 			currentRow += lines.Count - 1;
 			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
@@ -707,38 +843,63 @@ namespace Terminal.Gui {
 
 		void Adjust ()
 		{
+			var offB = OffSetBackground ();
+			var line = GetCurrentLine ();
 			bool need = false;
 			if (currentColumn < leftColumn) {
-				currentColumn = leftColumn;
+				leftColumn = currentColumn;
 				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;
 			}
 			if (currentRow < topRow) {
 				topRow = currentRow;
 				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;
 			}
-			if (need)
+			if (need) {
 				SetNeedsDisplay ();
-			else
+			} else {
 				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>
-		/// 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>
-		/// <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 ();
 		}
 
@@ -786,7 +947,7 @@ namespace Terminal.Gui {
 				break;
 
 			case Key.PageUp:
-			case ((int)'v' + Key.AltMask):
+			case ((int)'V' + Key.AltMask):
 				int nPageUpShift = Frame.Height - 1;
 				if (currentRow > 0) {
 					if (columnTrack == -1)
@@ -816,53 +977,33 @@ namespace Terminal.Gui {
 				var currentLine = GetCurrentLine ();
 				if (currentColumn < currentLine.Count) {
 					currentColumn++;
-					if (currentColumn >= leftColumn + Frame.Width) {
-						leftColumn++;
-						SetNeedsDisplay ();
-					}
-					PositionCursor ();
 				} else {
 					if (currentRow + 1 < model.Count) {
 						currentRow++;
 						currentColumn = 0;
-						leftColumn = 0;
 						if (currentRow >= topRow + Frame.Height) {
 							topRow++;
 						}
-						SetNeedsDisplay ();
-						PositionCursor ();
 					}
-					break;
 				}
+				Adjust ();
 				break;
 
 			case Key.B | Key.CtrlMask:
 			case Key.CursorLeft:
 				if (currentColumn > 0) {
 					currentColumn--;
-					if (currentColumn < leftColumn) {
-						leftColumn--;
-						SetNeedsDisplay ();
-					}
-					PositionCursor ();
 				} else {
 					if (currentRow > 0) {
 						currentRow--;
 						if (currentRow < topRow) {
 							topRow--;
-							SetNeedsDisplay ();
 						}
 						currentLine = GetCurrentLine ();
 						currentColumn = currentLine.Count;
-						int prev = leftColumn;
-						leftColumn = currentColumn - Frame.Width + 1;
-						if (leftColumn < 0)
-							leftColumn = 0;
-						if (prev != leftColumn)
-							SetNeedsDisplay ();
-						PositionCursor ();
 					}
 				}
+				Adjust ();
 				break;
 
 			case Key.Delete:
@@ -890,10 +1031,7 @@ namespace Terminal.Gui {
 					model.RemoveLine (currentRow);
 					currentRow--;
 					currentColumn = prevCount;
-					leftColumn = currentColumn - Frame.Width + 1;
-					if (leftColumn < 0)
-						leftColumn = 0;
-					SetNeedsDisplay ();
+					Adjust ();
 				}
 				break;
 
@@ -901,11 +1039,7 @@ namespace Terminal.Gui {
 			case Key.Home:
 			case Key.A | Key.CtrlMask:
 				currentColumn = 0;
-				if (currentColumn < leftColumn) {
-					leftColumn = 0;
-					SetNeedsDisplay ();
-				} else
-					PositionCursor ();
+				Adjust ();
 				break;
 			case Key.DeleteChar:
 			case Key.D | Key.CtrlMask: // Delete
@@ -932,12 +1066,7 @@ namespace Terminal.Gui {
 				currentLine = GetCurrentLine ();
 				currentColumn = currentLine.Count;
 				int pcol = leftColumn;
-				leftColumn = currentColumn - Frame.Width + 1;
-				if (leftColumn < 0)
-					leftColumn = 0;
-				if (pcol != leftColumn)
-					SetNeedsDisplay ();
-				PositionCursor ();
+				Adjust ();
 				break;
 
 			case Key.K | Key.CtrlMask: // kill-to-end
@@ -978,11 +1107,7 @@ namespace Terminal.Gui {
 				selectionStartRow = currentRow;
 				break;
 
-			case ((int)'w' + Key.AltMask):
-				SetClipboard (GetRegion ());
-				selecting = false;
-				break;
-
+			case ((int)'W' + Key.AltMask):
 			case Key.W | Key.CtrlMask:
 				SetClipboard (GetRegion ());
 				if (!isReadOnly)
@@ -990,7 +1115,8 @@ namespace Terminal.Gui {
 				selecting = false;
 				break;
 
-			case (Key)((int)'b' + Key.AltMask):
+			case Key.CtrlMask | Key.CursorLeft:
+			case (Key)((int)'B' + Key.AltMask):
 				var newPos = WordBackward (currentColumn, currentRow);
 				if (newPos.HasValue) {
 					currentColumn = newPos.Value.col;
@@ -1000,7 +1126,8 @@ namespace Terminal.Gui {
 
 				break;
 
-			case (Key)((int)'f' + Key.AltMask):
+			case Key.CtrlMask | Key.CursorRight:
+			case (Key)((int)'F' + Key.AltMask):
 				newPos = WordForward (currentColumn, currentRow);
 				if (newPos.HasValue) {
 					currentColumn = newPos.Value.col;
@@ -1012,7 +1139,6 @@ namespace Terminal.Gui {
 			case Key.Enter:
 				if (isReadOnly)
 					break;
-				var orow = currentRow;
 				currentLine = GetCurrentLine ();
 				restCount = currentLine.Count - currentColumn;
 				rest = currentLine.GetRange (currentColumn, restCount);
@@ -1063,11 +1189,12 @@ namespace Terminal.Gui {
 			return true;
 		}
 
-		private void MoveUp ()
+		void MoveUp ()
 		{
 			if (currentRow > 0) {
-				if (columnTrack == -1)
+				if (columnTrack == -1) {
 					columnTrack = currentColumn;
+				}
 				currentRow--;
 				if (currentRow < topRow) {
 					topRow--;
@@ -1078,11 +1205,12 @@ namespace Terminal.Gui {
 			}
 		}
 
-		private void MoveDown ()
+		void MoveDown ()
 		{
 			if (currentRow + 1 < model.Count) {
-				if (columnTrack == -1)
+				if (columnTrack == -1) {
 					columnTrack = currentColumn;
+				}
 				currentRow++;
 				if (currentRow >= topRow + Frame.Height) {
 					topRow++;
@@ -1090,6 +1218,8 @@ namespace Terminal.Gui {
 				}
 				TrackColumn ();
 				PositionCursor ();
+			} else if (currentRow > Frame.Height) {
+				Adjust ();
 			}
 		}
 
@@ -1186,28 +1316,31 @@ namespace Terminal.Gui {
 		{
 			var col = fromCol;
 			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)
@@ -1217,27 +1350,30 @@ namespace Terminal.Gui {
 
 			var col = fromCol;
 			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/>
@@ -1258,25 +1394,39 @@ namespace Terminal.Gui {
 
 			if (ev.Flags == MouseFlags.Button1Clicked) {
 				if (model.Count > 0) {
-					var maxCursorPositionableLine = (model.Count - 1) - topRow;
+					var maxCursorPositionableLine = Math.Max ((model.Count - 1) - topRow, 0);
 					if (ev.Y > maxCursorPositionableLine) {
 						currentRow = maxCursorPositionableLine;
 					} else {
 						currentRow = ev.Y + topRow;
 					}
 					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;
-					else
-						currentColumn = ev.X - leftColumn;
+					} else {
+						currentColumn = idx + leftColumn;
+					}
 				}
 				PositionCursor ();
+				lastWasKill = false;
+				columnTrack = currentColumn;
 			} else if (ev.Flags == MouseFlags.WheeledDown) {
 				lastWasKill = false;
-				MoveDown ();
+				columnTrack = currentColumn;
+				ScrollTo (topRow + 1);
 			} else if (ev.Flags == MouseFlags.WheeledUp) {
 				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;

+ 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.
 	/// </summary>
 	/// <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
 	///  or buttons added to the dialog calls <see cref="Application.RequestStop"/>.
 	/// </remarks>

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

@@ -670,7 +670,7 @@ namespace Terminal.Gui {
 	/// <remarks>
 	/// <para>
 	///   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 
 	///   null if the user canceled. 
 	/// </para>
@@ -713,7 +713,7 @@ namespace Terminal.Gui {
 	/// </para>
 	/// <para>
 	///   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.
 	/// </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++) {
 				Move (viewport.X + RowHeaderWidth + (header * H_SPACE), 0);
 				Driver.AddStr ($" {header:x} ");

+ 5 - 5
UICatalog/Scenarios/Clipping.cs

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

+ 43 - 0
UICatalog/Scenarios/Editor.cs

@@ -13,6 +13,7 @@ namespace UICatalog {
 		private string _fileName = "demo.txt";
 		private TextView _textView;
 		private bool _saved = true;
+		private ScrollBarView _scrollBar;
 
 		public override void Init (Toplevel top, ColorScheme colorScheme)
 		{
@@ -35,6 +36,7 @@ namespace UICatalog {
 					new MenuItem ("C_ut", "", () => Cut()),
 					new MenuItem ("_Paste", "", () => Paste())
 				}),
+				new MenuBarItem ("_ScrollBarView", CreateKeepChecked ())
 			});
 			Top.Add (menu);
 
@@ -67,6 +69,33 @@ namespace UICatalog {
 			LoadFile ();
 
 			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 ()
@@ -145,11 +174,25 @@ namespace UICatalog {
 			sb.Append ("Hello world.\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);
 			sw.Write (sb.ToString ());
 			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 ()
 		{
 			base.Run ();

+ 64 - 10
UICatalog/Scenarios/ListViewWithSelection.cs

@@ -6,7 +6,7 @@ using System.Linq;
 using Terminal.Gui;
 
 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")]
 	class ListViewWithSelection : Scenario {
 
@@ -25,7 +25,7 @@ namespace UICatalog {
 				Height = 1,
 			};
 			Win.Add (_customRenderCB);
-			_customRenderCB.Toggled += _customRenderCB_Toggled; ;
+			_customRenderCB.Toggled += _customRenderCB_Toggled;
 
 			_allowMarkingCB = new CheckBox ("Allow Marking") {
 				X = Pos.Right (_customRenderCB) + 1,
@@ -55,9 +55,41 @@ namespace UICatalog {
 			};
 			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);
 
+			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)
@@ -84,20 +116,21 @@ namespace UICatalog {
 			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 {
 			int _nameColumnWidth = 30;
 			private List<Type> scenarios;
 			BitArray marks;
-			int count;
+			int count, len;
 
 			public List<Type> Scenarios {
-				get => scenarios; 
+				get => scenarios;
 				set {
 					if (value != null) {
 						count = value.Count;
 						marks = new BitArray (count);
 						scenarios = value;
+						len = GetMaxLengthItem ();
 					}
 				}
 			}
@@ -110,14 +143,16 @@ namespace UICatalog {
 
 			public int Count => Scenarios != null ? Scenarios.Count : 0;
 
+			public int Length => len;
+
 			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);
 				// 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]));
-				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)
@@ -126,11 +161,30 @@ namespace UICatalog {
 					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
-			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 index = 0;
+				int index = start;
 				while (index < ustr.Length) {
 					(var rune, var size) = Utf8.DecodeRune (ustr, index, index - ustr.Length);
 					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];
 			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
 			var lbComboBox = new Label ("ComboBox") {
 				ColorScheme = Colors.TopLevel,
@@ -57,6 +83,33 @@ namespace UICatalog.Scenarios {
 			comboBox.SelectedItemChanged += (ListViewItemEventArgs text) => lbComboBox.Text = items[comboBox.SelectedItem];
 			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") {
 				X = 1,
 				Y = Pos.Bottom(lbListView),

+ 1 - 1
UICatalog/Scenarios/Scrolling.cs

@@ -128,7 +128,7 @@ namespace UICatalog {
 			var horizontalRuler = new Label () {
 				X = 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
 			};
 			scrollView.Add (horizontalRuler);

+ 7 - 1
UICatalog/Scenarios/Threading.cs

@@ -92,7 +92,13 @@ namespace UICatalog {
 			_btnQuit.Clicked += Application.RequestStop;
 
 			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 ()

+ 1 - 1
UICatalog/Scenarios/WindowsAndFrameViews.cs

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

+ 56 - 6
UICatalog/UICatalog.cs

@@ -63,6 +63,8 @@ namespace UICatalog {
 		private static Scenario _runningScenario = null;
 		private static bool _useSystemConsole = false;
 		private static ConsoleDriver.DiagnosticFlags _diagnosticFlags;
+		private static bool _heightAsBuffer;
+		private static bool _alwaysSetPosition;
 
 		static void Main (string [] args)
 		{
@@ -144,6 +146,8 @@ namespace UICatalog {
 		{
 			Application.UseSystemConsole = _useSystemConsole;
 			Application.Init ();
+			Application.HeightAsBuffer = _heightAsBuffer;
+			Application.AlwaysSetPosition = _alwaysSetPosition;
 
 			// Set this here because not initialized until driver is loaded
 			_baseColorScheme = Colors.Base;
@@ -280,9 +284,27 @@ namespace UICatalog {
 			menuItems.Add (CreateDiagnosticFlagsMenuItems ());
 			menuItems.Add (new MenuItem [] { null });
 			menuItems.Add (CreateSizeStyle ());
+			menuItems.Add (CreateAlwaysSetPosition ());
 			return menuItems;
 		}
 
+		static MenuItem [] CreateAlwaysSetPosition ()
+		{
+			List<MenuItem> menuItems = new List<MenuItem> ();
+			var item = new MenuItem ();
+			item.Title = "_Always set position (NetDriver only)";
+			item.Shortcut = Key.CtrlMask | Key.AltMask | (Key)item.Title.ToString ().Substring (1, 1) [0];
+			item.CheckType |= MenuItemCheckStyle.Checked;
+			item.Checked = Application.AlwaysSetPosition;
+			item.Action += () => {
+				Application.AlwaysSetPosition = !item.Checked;
+				item.Checked = _alwaysSetPosition = Application.AlwaysSetPosition;
+			};
+			menuItems.Add (item);
+
+			return menuItems.ToArray ();
+		}
+
 		static MenuItem [] CreateSizeStyle ()
 		{
 			List<MenuItem> menuItems = new List<MenuItem> ();
@@ -293,7 +315,8 @@ namespace UICatalog {
 			item.Checked = Application.HeightAsBuffer;
 			item.Action += () => {
 				item.Checked = !item.Checked;
-				Application.HeightAsBuffer = item.Checked;
+				_heightAsBuffer = item.Checked;
+				Application.HeightAsBuffer = _heightAsBuffer;
 			};
 			menuItems.Add (item);
 
@@ -475,31 +498,58 @@ namespace UICatalog {
 		}
 
 		internal class ScenarioListDataSource : IListDataSource {
+			private readonly int len;
+
 			public List<Type> Scenarios { get; set; }
 
 			public bool IsMarked (int item) => false;
 
 			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);
 				// 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]));
-				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)
 			{
 			}
 
+			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
-			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 index = 0;
+				int index = start;
 				while (index < ustr.Length) {
 					(var rune, var size) = Utf8.DecodeRune (ustr, index, index - ustr.Length);
 					var count = Rune.ColumnWidth (rune);

+ 27 - 7
UnitTests/ApplicationTests.cs

@@ -1,6 +1,8 @@
 using System;
 using System.Collections.Generic;
 using System.Linq;
+using System.Threading;
+using System.Threading.Tasks;
 using Terminal.Gui;
 using Xunit;
 
@@ -23,14 +25,12 @@ namespace Terminal.Gui {
 		public void Init_Shutdown_Cleans_Up ()
 		{
 			Assert.Null (Application.Current);
-			Assert.Null (Application.CurrentView);
 			Assert.Null (Application.Top);
 			Assert.Null (Application.MainLoop);
 			Assert.Null (Application.Driver);
 
 			Application.Init (new FakeDriver (), new FakeMainLoop (() => FakeConsole.ReadKey (true)));
 			Assert.NotNull (Application.Current);
-			Assert.NotNull (Application.CurrentView);
 			Assert.NotNull (Application.Top);
 			Assert.NotNull (Application.MainLoop);
 			Assert.NotNull (Application.Driver);
@@ -41,7 +41,6 @@ namespace Terminal.Gui {
 
 			Application.Shutdown ();
 			Assert.Null (Application.Current);
-			Assert.Null (Application.CurrentView);
 			Assert.Null (Application.Top);
 			Assert.Null (Application.MainLoop);
 			Assert.Null (Application.Driver);
@@ -92,7 +91,6 @@ namespace Terminal.Gui {
 			Application.End (rs);
 
 			Assert.Null (Application.Current);
-			Assert.Null (Application.CurrentView);
 			Assert.NotNull (Application.Top);
 			Assert.NotNull (Application.MainLoop);
 			Assert.NotNull (Application.Driver);
@@ -123,7 +121,6 @@ namespace Terminal.Gui {
 
 			Application.Shutdown ();
 			Assert.Null (Application.Current);
-			Assert.Null (Application.CurrentView);
 			Assert.Null (Application.Top);
 			Assert.Null (Application.MainLoop);
 			Assert.Null (Application.Driver);
@@ -148,7 +145,6 @@ namespace Terminal.Gui {
 
 			Application.Shutdown ();
 			Assert.Null (Application.Current);
-			Assert.Null (Application.CurrentView);
 			Assert.Null (Application.Top);
 			Assert.Null (Application.MainLoop);
 			Assert.Null (Application.Driver);
@@ -207,7 +203,6 @@ namespace Terminal.Gui {
 
 			Application.Shutdown ();
 			Assert.Null (Application.Current);
-			Assert.Null (Application.CurrentView);
 			Assert.Null (Application.Top);
 			Assert.Null (Application.MainLoop);
 			Assert.Null (Application.Driver);
@@ -227,5 +222,30 @@ namespace Terminal.Gui {
 			Application.Shutdown ();
 			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 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;
 
 namespace Terminal.Gui {
@@ -27,9 +27,9 @@ namespace Terminal.Gui {
 			Assert.Null (r.ColorScheme);
 			Assert.Equal (Dim.Sized (0), r.Width);
 			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.Empty (r.Id);
 			Assert.Empty (r.Subviews);
@@ -1077,5 +1077,37 @@ namespace Terminal.Gui {
 			Assert.True (view.Frame.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);
+		}
 	}
 }