Selaa lähdekoodia

Fixes #1384. Added a VisibleChanged event on the View class. (#1385)

* Fixes #1384. Added a VisibleChanged event on the View class.

* Getting the last char.

* Fixes #871. Added Enable property to Responder and added Disabled color for all ColorSchemes.

* Added GetNormalColor method to the View being more readable.

* Fixes the contentBottomRightCorner Enable and Visible.

* Fixes #643. Added AddItemAt and RemoveItem to StatusBar and fixed more bugs.

* Typo fixes.

* Fixes #1387. Allowing the UnitTests project to test internal keywords.

* Fixes #1389. Added a unidirectional feature to the Marquee styles to the ProgressBar.

* Fixes #1394. Added ReflectedType to check for overridden.

* Fixes #1396. Using the Loaded event instead the Ready event.

* Fixes #1402. Only WindowsDriver supports horizontal scroll. (#1403)

* Fixes #1402. Only WindowsDriver supports horizontal scroll.

* Fixes ProcessContinuousButtonPressedAsync on all drivers.

* Fixed internal unit test.

* Fixing warning.

* Fixing Editor scenario error.

* Fixes double and triple click on a touchpad.

* Ensuring reset the counting.

* Allowing touchpad double and triple click with one finger on CursesDriver.

* Allowing touchpad double and triple click with one finger on WindowsDriver.

* Fixes #1414. Fixed multi toplevels and mdi container issues.

* Improving EnsureVisibleBounds and PositionToplevel.

* Added mouseGrabView to the ResetState method.

* Changing namespace.

* Allowing file type on the SaveDialog.

* Fixes SaveDialogs writing the extension twice.
BDisp 4 vuotta sitten
vanhempi
commit
52f48b2044
42 muutettua tiedostoa jossa 2409 lisäystä ja 364 poistoa
  1. 90 26
      Terminal.Gui/ConsoleDrivers/CursesDriver/CursesDriver.cs
  2. 18 2
      Terminal.Gui/ConsoleDrivers/FakeDriver/FakeDriver.cs
  3. 50 10
      Terminal.Gui/ConsoleDrivers/NetDriver.cs
  4. 75 7
      Terminal.Gui/ConsoleDrivers/WindowsDriver.cs
  5. 6 2
      Terminal.Gui/Core/Application.cs
  6. 25 0
      Terminal.Gui/Core/Responder.cs
  7. 10 4
      Terminal.Gui/Core/ShortcutHelper.cs
  8. 53 50
      Terminal.Gui/Core/Toplevel.cs
  9. 165 39
      Terminal.Gui/Core/View.cs
  10. 3 3
      Terminal.Gui/Core/Window.cs
  11. 13 1
      Terminal.Gui/Views/Button.cs
  12. 2 2
      Terminal.Gui/Views/Checkbox.cs
  13. 30 13
      Terminal.Gui/Views/ComboBox.cs
  14. 4 4
      Terminal.Gui/Views/FrameView.cs
  15. 2 2
      Terminal.Gui/Views/GraphView.cs
  16. 7 8
      Terminal.Gui/Views/HexView.cs
  17. 1 2
      Terminal.Gui/Views/Label.cs
  18. 1 1
      Terminal.Gui/Views/LineView.cs
  19. 3 1
      Terminal.Gui/Views/ListView.cs
  20. 13 11
      Terminal.Gui/Views/Menu.cs
  21. 1 1
      Terminal.Gui/Views/ProgressBar.cs
  22. 9 8
      Terminal.Gui/Views/RadioGroup.cs
  23. 53 4
      Terminal.Gui/Views/ScrollBarView.cs
  24. 3 3
      Terminal.Gui/Views/ScrollView.cs
  25. 61 9
      Terminal.Gui/Views/StatusBar.cs
  26. 4 4
      Terminal.Gui/Views/TabView.cs
  27. 8 6
      Terminal.Gui/Views/TableView.cs
  28. 13 8
      Terminal.Gui/Views/TextField.cs
  29. 48 14
      Terminal.Gui/Views/TextView.cs
  30. 1 1
      Terminal.Gui/Views/TreeView.cs
  31. 195 30
      Terminal.Gui/Windows/FileDialog.cs
  32. 25 25
      UICatalog/Scenarios/DynamicMenuBar.cs
  33. 594 0
      UICatalog/Scenarios/DynamicStatusBar.cs
  34. 116 41
      UICatalog/Scenarios/Editor.cs
  35. 2 2
      UICatalog/Scenarios/ProgressBarStyles.cs
  36. 4 8
      UICatalog/Scenarios/SingleBackgroundWorker.cs
  37. 2 4
      UnitTests/LineViewTests.cs
  38. 3 1
      UnitTests/ResponderTests.cs
  39. 160 0
      UnitTests/StatusBarTests.cs
  40. 65 7
      UnitTests/TextViewTests.cs
  41. 315 0
      UnitTests/ToplevelTests.cs
  42. 156 0
      UnitTests/ViewTests.cs

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

@@ -143,6 +143,11 @@ namespace Terminal.Gui {
 				background: MapCursesColor (background));
 		}
 
+		static Attribute MakeColor (Color fore, Color back)
+		{
+			return MakeColor ((short)MapColor (fore), (short)MapColor (back));
+		}
+
 		int [,] colorPairs = new int [16, 16];
 
 		public override void SetColors (ConsoleColor foreground, ConsoleColor background)
@@ -253,6 +258,7 @@ namespace Terminal.Gui {
 		bool cancelButtonClicked;
 		bool isReportMousePosition;
 		Point point;
+		int buttonPressedCount;
 
 		MouseEvent ToDriverMouse (Curses.MouseEvent cev)
 		{
@@ -263,10 +269,59 @@ namespace Terminal.Gui {
 				isButtonPressed = false;
 			}
 
+			if (cev.ButtonState == Curses.Event.Button1Pressed
+				|| cev.ButtonState == Curses.Event.Button2Pressed
+				|| cev.ButtonState == Curses.Event.Button3Pressed) {
+
+				isButtonPressed = true;
+				buttonPressedCount++;
+			} else {
+				buttonPressedCount = 0;
+			}
+			//System.Diagnostics.Debug.WriteLine ($"buttonPressedCount: {buttonPressedCount}");
+
+			if (buttonPressedCount == 2
+				&& (cev.ButtonState == Curses.Event.Button1Pressed
+				|| cev.ButtonState == Curses.Event.Button2Pressed
+				|| cev.ButtonState == Curses.Event.Button3Pressed)) {
+
+				switch (cev.ButtonState) {
+				case Curses.Event.Button1Pressed:
+					mouseFlag = MouseFlags.Button1DoubleClicked;
+					break;
+
+				case Curses.Event.Button2Pressed:
+					mouseFlag = MouseFlags.Button2DoubleClicked;
+					break;
+
+				case Curses.Event.Button3Pressed:
+					mouseFlag = MouseFlags.Button3DoubleClicked;
+					break;
+				}
+
+			} else if (buttonPressedCount == 3
+			       && (cev.ButtonState == Curses.Event.Button1Pressed
+			       || cev.ButtonState == Curses.Event.Button2Pressed
+			       || cev.ButtonState == Curses.Event.Button3Pressed)) {
+
+				switch (cev.ButtonState) {
+				case Curses.Event.Button1Pressed:
+					mouseFlag = MouseFlags.Button1TripleClicked;
+					break;
+
+				case Curses.Event.Button2Pressed:
+					mouseFlag = MouseFlags.Button2TripleClicked;
+					break;
+
+				case Curses.Event.Button3Pressed:
+					mouseFlag = MouseFlags.Button3TripleClicked;
+					break;
+				}
+				buttonPressedCount = 0;
 
-			if ((cev.ButtonState == Curses.Event.Button1Clicked || cev.ButtonState == Curses.Event.Button2Clicked ||
-				cev.ButtonState == Curses.Event.Button3Clicked) &&
-				lastMouseButtonPressed == null) {
+			} else if ((cev.ButtonState == Curses.Event.Button1Clicked || cev.ButtonState == Curses.Event.Button2Clicked ||
+			       cev.ButtonState == Curses.Event.Button3Clicked) &&
+			       lastMouseButtonPressed == null) {
 
 				isButtonPressed = false;
 				mouseFlag = ProcessButtonClickedEvent (cev);
@@ -797,57 +852,66 @@ namespace Terminal.Gui {
 				Curses.StartColor ();
 				Curses.UseDefaultColors ();
 
-				Colors.TopLevel.Normal = MakeColor (Curses.COLOR_GREEN, Curses.COLOR_BLACK);
-				Colors.TopLevel.Focus = MakeColor (Curses.COLOR_WHITE, Curses.COLOR_CYAN);
-				Colors.TopLevel.HotNormal = MakeColor (Curses.COLOR_YELLOW, Curses.COLOR_BLACK);
-				Colors.TopLevel.HotFocus = MakeColor (Curses.COLOR_BLUE, Curses.COLOR_CYAN);
+				Colors.TopLevel.Normal = MakeColor (Color.Green, Color.Black);
+				Colors.TopLevel.Focus = MakeColor (Color.White, Color.Cyan);
+				Colors.TopLevel.HotNormal = MakeColor (Color.Brown, Color.Black);
+				Colors.TopLevel.HotFocus = MakeColor (Color.Blue, Color.Cyan);
+				Colors.TopLevel.Disabled = MakeColor (Color.DarkGray, Color.Black);
 
-				Colors.Base.Normal = MakeColor (Curses.COLOR_WHITE, Curses.COLOR_BLUE);
-				Colors.Base.Focus = MakeColor (Curses.COLOR_BLACK, Curses.COLOR_WHITE);
-				Colors.Base.HotNormal = MakeColor (Curses.COLOR_CYAN, Curses.COLOR_BLUE);
-				Colors.Base.HotFocus = Curses.A_BOLD | MakeColor (Curses.COLOR_BLUE, Curses.COLOR_GRAY);
+				Colors.Base.Normal = MakeColor (Color.White, Color.Blue);
+				Colors.Base.Focus = MakeColor (Color.Black, Color.Gray);
+				Colors.Base.HotNormal = MakeColor (Color.Cyan, Color.Blue);
+				Colors.Base.HotFocus = MakeColor (Color.Blue, Color.Gray);
+				Colors.Base.Disabled = MakeColor (Color.DarkGray, Color.Blue);
 
 				// Focused,
 				//    Selected, Hot: Yellow on Black
 				//    Selected, text: white on black
 				//    Unselected, hot: yellow on cyan
 				//    unselected, text: same as unfocused
-				Colors.Menu.Normal = Curses.A_BOLD | MakeColor (Curses.COLOR_WHITE, Curses.COLOR_GRAY);
-				Colors.Menu.Focus = Curses.A_BOLD | MakeColor (Curses.COLOR_WHITE, Curses.COLOR_BLACK);
-				Colors.Menu.HotNormal = Curses.A_BOLD | MakeColor (Curses.COLOR_YELLOW, Curses.COLOR_GRAY);
-				Colors.Menu.HotFocus = Curses.A_BOLD | MakeColor (Curses.COLOR_YELLOW, Curses.COLOR_BLACK);
-				Colors.Menu.Disabled = MakeColor (Curses.COLOR_WHITE, Curses.COLOR_GRAY);
-
-				Colors.Dialog.Normal = MakeColor (Curses.COLOR_BLACK, Curses.COLOR_WHITE);
-				Colors.Dialog.Focus = MakeColor (Curses.COLOR_WHITE, Curses.COLOR_GRAY);
-				Colors.Dialog.HotNormal = MakeColor (Curses.COLOR_BLUE, Curses.COLOR_WHITE);
-				Colors.Dialog.HotFocus = MakeColor (Curses.COLOR_BLUE, Curses.COLOR_GRAY);
-
-				Colors.Error.Normal = MakeColor (Curses.COLOR_RED, Curses.COLOR_WHITE);
-				Colors.Error.Focus = Curses.A_BOLD | MakeColor (Curses.COLOR_WHITE, Curses.COLOR_RED);
-				Colors.Error.HotNormal = MakeColor (Curses.COLOR_BLACK, Curses.COLOR_WHITE);
-				Colors.Error.HotFocus = Curses.A_BOLD | MakeColor (Curses.COLOR_BLACK, Curses.COLOR_RED);
+				Colors.Menu.Normal = MakeColor (Color.White, Color.DarkGray);
+				Colors.Menu.Focus = MakeColor (Color.White, Color.Black);
+				Colors.Menu.HotNormal = MakeColor (Color.BrightYellow, Color.DarkGray);
+				Colors.Menu.HotFocus = MakeColor (Color.BrightYellow, Color.Black);
+				Colors.Menu.Disabled = MakeColor (Color.Gray, Color.DarkGray);
+
+				Colors.Dialog.Normal = MakeColor (Color.Black, Color.Gray);
+				Colors.Dialog.Focus = MakeColor (Color.White, Color.DarkGray);
+				Colors.Dialog.HotNormal = MakeColor (Color.Blue, Color.Gray);
+				Colors.Dialog.HotFocus = MakeColor (Color.Blue, Color.DarkGray);
+				Colors.Dialog.Disabled = MakeColor (Color.DarkGray, Color.Gray);
+
+				Colors.Error.Normal = MakeColor (Color.Red, Color.White);
+				Colors.Error.Focus = MakeColor (Color.White, Color.Red);
+				Colors.Error.HotNormal = MakeColor (Color.Black, Color.White);
+				Colors.Error.HotFocus = MakeColor (Color.Black, Color.Red);
+				Colors.Error.Disabled = MakeColor (Color.DarkGray, Color.White);
 			} else {
 				Colors.TopLevel.Normal = Curses.COLOR_GREEN;
 				Colors.TopLevel.Focus = Curses.COLOR_WHITE;
 				Colors.TopLevel.HotNormal = Curses.COLOR_YELLOW;
 				Colors.TopLevel.HotFocus = Curses.COLOR_YELLOW;
+				Colors.TopLevel.Disabled = Curses.A_BOLD | Curses.COLOR_GRAY;
 				Colors.Base.Normal = Curses.A_NORMAL;
 				Colors.Base.Focus = Curses.A_REVERSE;
 				Colors.Base.HotNormal = Curses.A_BOLD;
 				Colors.Base.HotFocus = Curses.A_BOLD | Curses.A_REVERSE;
+				Colors.Base.Disabled = Curses.A_BOLD | Curses.COLOR_GRAY;
 				Colors.Menu.Normal = Curses.A_REVERSE;
 				Colors.Menu.Focus = Curses.A_NORMAL;
 				Colors.Menu.HotNormal = Curses.A_BOLD;
 				Colors.Menu.HotFocus = Curses.A_NORMAL;
+				Colors.Menu.Disabled = Curses.A_BOLD | Curses.COLOR_GRAY;
 				Colors.Dialog.Normal = Curses.A_REVERSE;
 				Colors.Dialog.Focus = Curses.A_NORMAL;
 				Colors.Dialog.HotNormal = Curses.A_BOLD;
 				Colors.Dialog.HotFocus = Curses.A_NORMAL;
+				Colors.Dialog.Disabled = Curses.A_BOLD | Curses.COLOR_GRAY;
 				Colors.Error.Normal = Curses.A_BOLD;
 				Colors.Error.Focus = Curses.A_BOLD | Curses.A_REVERSE;
 				Colors.Error.HotNormal = Curses.A_BOLD | Curses.A_REVERSE;
 				Colors.Error.HotFocus = Curses.A_REVERSE;
+				Colors.Error.Disabled = Curses.A_BOLD | Curses.COLOR_GRAY;
 			}
 		}
 

+ 18 - 2
Terminal.Gui/ConsoleDrivers/FakeDriver/FakeDriver.cs

@@ -145,7 +145,9 @@ namespace Terminal.Gui {
 
 			cols = FakeConsole.WindowWidth = FakeConsole.BufferWidth = FakeConsole.WIDTH;
 			rows = FakeConsole.WindowHeight = FakeConsole.BufferHeight = FakeConsole.HEIGHT;
-			UpdateOffscreen ();
+			FakeConsole.Clear ();
+			ResizeScreen ();
+			UpdateOffScreen ();
 
 			Colors.TopLevel = new ColorScheme ();
 			Colors.Base = new ColorScheme ();
@@ -158,11 +160,13 @@ namespace Terminal.Gui {
 			Colors.TopLevel.Focus = MakeColor (ConsoleColor.White, ConsoleColor.DarkCyan);
 			Colors.TopLevel.HotNormal = MakeColor (ConsoleColor.DarkYellow, ConsoleColor.Black);
 			Colors.TopLevel.HotFocus = MakeColor (ConsoleColor.DarkBlue, ConsoleColor.DarkCyan);
+			Colors.TopLevel.Disabled = MakeColor (ConsoleColor.DarkGray, ConsoleColor.Black);
 
 			Colors.Base.Normal = MakeColor (ConsoleColor.White, ConsoleColor.Blue);
 			Colors.Base.Focus = MakeColor (ConsoleColor.Black, ConsoleColor.Cyan);
 			Colors.Base.HotNormal = MakeColor (ConsoleColor.Yellow, ConsoleColor.Blue);
 			Colors.Base.HotFocus = MakeColor (ConsoleColor.Yellow, ConsoleColor.Cyan);
+			Colors.Base.Disabled = MakeColor (ConsoleColor.DarkGray, ConsoleColor.DarkBlue);
 
 			// Focused,
 			//    Selected, Hot: Yellow on Black
@@ -179,11 +183,13 @@ namespace Terminal.Gui {
 			Colors.Dialog.Focus = MakeColor (ConsoleColor.Black, ConsoleColor.Cyan);
 			Colors.Dialog.HotNormal = MakeColor (ConsoleColor.Blue, ConsoleColor.Gray);
 			Colors.Dialog.HotFocus = MakeColor (ConsoleColor.Blue, ConsoleColor.Cyan);
+			Colors.Dialog.Disabled = MakeColor (ConsoleColor.DarkGray, ConsoleColor.Gray);
 
 			Colors.Error.Normal = MakeColor (ConsoleColor.White, ConsoleColor.Red);
 			Colors.Error.Focus = MakeColor (ConsoleColor.Black, ConsoleColor.Gray);
 			Colors.Error.HotNormal = MakeColor (ConsoleColor.Yellow, ConsoleColor.Red);
 			Colors.Error.HotFocus = Colors.Error.HotNormal;
+			Colors.Error.Disabled = MakeColor (ConsoleColor.DarkGray, ConsoleColor.White);
 
 			//MockConsole.Clear ();
 		}
@@ -442,7 +448,11 @@ namespace Terminal.Gui {
 		/// <inheritdoc/>
 		public override bool GetCursorVisibility (out CursorVisibility visibility)
 		{
-			visibility = CursorVisibility.Default;
+			if (FakeConsole.CursorVisible) {
+				visibility = CursorVisibility.Default;
+			} else {
+				visibility = CursorVisibility.Invisible;
+			}
 
 			return false;
 		}
@@ -450,6 +460,12 @@ namespace Terminal.Gui {
 		/// <inheritdoc/>
 		public override bool SetCursorVisibility (CursorVisibility visibility)
 		{
+			if (visibility == CursorVisibility.Invisible) {
+				FakeConsole.CursorVisible = false;
+			} else {
+				FakeConsole.CursorVisible = true;
+			}
+
 			return false;
 		}
 

+ 50 - 10
Terminal.Gui/ConsoleDrivers/NetDriver.cs

@@ -506,7 +506,7 @@ namespace Terminal.Gui {
 		bool isButtonDoubleClicked;
 		bool isButtonTripleClicked;
 		bool isProcContBtnPressedRuning;
-		Point point = new Point ();
+		int buttonPressedCount;
 		//bool isButtonReleased;
 
 		void GetMouseEvent (ConsoleKeyInfo [] cki)
@@ -701,16 +701,52 @@ namespace Terminal.Gui {
 			if ((buttonState & MouseButtonState.Button1Pressed) != 0
 				|| (buttonState & MouseButtonState.Button2Pressed) != 0
 				|| (buttonState & MouseButtonState.Button3Pressed) != 0) {
+
+				if ((buttonState & MouseButtonState.ReportMousePosition) == 0) {
+					buttonPressedCount++;
+				} else {
+					buttonPressedCount = 0;
+				}
+				//System.Diagnostics.Debug.WriteLine ($"buttonPressedCount: {buttonPressedCount}");
 				isButtonPressed = true;
 			} else {
 				isButtonPressed = false;
+				buttonPressedCount = 0;
+			}
+
+			if (buttonPressedCount == 2 && !isButtonDoubleClicked
+				&& (lastMouseEvent.ButtonState == MouseButtonState.Button1Pressed
+				|| lastMouseEvent.ButtonState == MouseButtonState.Button2Pressed
+				|| lastMouseEvent.ButtonState == MouseButtonState.Button3Pressed)) {
+
+				isButtonDoubleClicked = true;
+				ProcessButtonDoubleClicked (mouseEvent);
+				inputReady.Set ();
+				return;
+			} else if (buttonPressedCount == 3 && isButtonDoubleClicked
+			       && (lastMouseEvent.ButtonState == MouseButtonState.Button1Pressed
+			       || lastMouseEvent.ButtonState == MouseButtonState.Button2Pressed
+			       || lastMouseEvent.ButtonState == MouseButtonState.Button3Pressed)) {
+
+				isButtonDoubleClicked = false;
+				isButtonTripleClicked = true;
+				buttonPressedCount = 0;
+				ProcessButtonTripleClicked (mouseEvent);
+				lastMouseEvent = mouseEvent;
+				inputReady.Set ();
+				return;
 			}
 
+			//System.Diagnostics.Debug.WriteLine ($"isButtonClicked: {isButtonClicked} isButtonDoubleClicked: {isButtonDoubleClicked} isButtonTripleClicked: {isButtonTripleClicked}");
 			if ((isButtonClicked || isButtonDoubleClicked || isButtonTripleClicked)
 				&& ((buttonState & MouseButtonState.Button1Released) != 0
 				|| (buttonState & MouseButtonState.Button2Released) != 0
 				|| (buttonState & MouseButtonState.Button3Released) != 0)) {
+
+				//isButtonClicked = false;
+				//isButtonDoubleClicked = false;
 				isButtonTripleClicked = false;
+				buttonPressedCount = 0;
 				return;
 			}
 
@@ -721,11 +757,13 @@ namespace Terminal.Gui {
 				|| (buttonState & MouseButtonState.Button1Released) != 0
 				|| (buttonState & MouseButtonState.Button2Released) != 0
 				|| (buttonState & MouseButtonState.Button3Released) != 0)) {
+
+				isButtonClicked = false;
 				isButtonDoubleClicked = true;
 				ProcessButtonDoubleClicked (mouseEvent);
 				Application.MainLoop.AddIdle (() => {
 					Task.Run (async () => {
-						await Task.Delay (300);
+						await Task.Delay (600);
 						isButtonDoubleClicked = false;
 					});
 					return false;
@@ -740,6 +778,8 @@ namespace Terminal.Gui {
 				|| (buttonState & MouseButtonState.Button1Released) != 0
 				|| (buttonState & MouseButtonState.Button2Released) != 0
 				|| (buttonState & MouseButtonState.Button3Released) != 0)) {
+
+				isButtonDoubleClicked = false;
 				isButtonTripleClicked = true;
 				ProcessButtonTripleClicked (mouseEvent);
 				inputReady.Set ();
@@ -792,7 +832,7 @@ namespace Terminal.Gui {
 				}
 				if ((buttonState & MouseButtonState.ReportMousePosition) == 0) {
 					Application.MainLoop.AddIdle (() => {
-						Task.Run (async () => await ProcessContinuousButtonPressedAsync (mouseEvent));
+						Task.Run (async () => await ProcessContinuousButtonPressedAsync ());
 						return false;
 					});
 				}
@@ -873,24 +913,20 @@ namespace Terminal.Gui {
 			});
 		}
 
-		async Task ProcessContinuousButtonPressedAsync (MouseEvent mouseEvent)
+		async Task ProcessContinuousButtonPressedAsync ()
 		{
 			isProcContBtnPressedRuning = true;
 			await Task.Delay (200);
 			while (isButtonPressed) {
 				await Task.Delay (100);
-				var me = new MouseEvent () {
-					Position = new Point (mouseEvent.Position.X, mouseEvent.Position.Y),
-					ButtonState=mouseEvent.ButtonState
-				};
 				var view = Application.wantContinuousButtonPressedView;
 				if (view == null) {
 					break;
 				}
-				if (isButtonPressed && (mouseEvent.ButtonState & MouseButtonState.ReportMousePosition) == 0) {
+				if (isButtonPressed && (lastMouseEvent.ButtonState & MouseButtonState.ReportMousePosition) == 0) {
 					inputResultQueue.Enqueue (new InputResult () {
 						EventType = EventType.Mouse,
-						MouseEvent = me
+						MouseEvent = lastMouseEvent
 					});
 					inputReady.Set ();
 				}
@@ -1244,11 +1280,13 @@ namespace Terminal.Gui {
 			Colors.TopLevel.Focus = MakeColor (ConsoleColor.White, ConsoleColor.DarkCyan);
 			Colors.TopLevel.HotNormal = MakeColor (ConsoleColor.DarkYellow, ConsoleColor.Black);
 			Colors.TopLevel.HotFocus = MakeColor (ConsoleColor.DarkBlue, ConsoleColor.DarkCyan);
+			Colors.TopLevel.Disabled = MakeColor (ConsoleColor.DarkGray, ConsoleColor.Black);
 
 			Colors.Base.Normal = MakeColor (ConsoleColor.White, ConsoleColor.DarkBlue);
 			Colors.Base.Focus = MakeColor (ConsoleColor.Black, ConsoleColor.Gray);
 			Colors.Base.HotNormal = MakeColor (ConsoleColor.DarkCyan, ConsoleColor.DarkBlue);
 			Colors.Base.HotFocus = MakeColor (ConsoleColor.Blue, ConsoleColor.Gray);
+			Colors.Base.Disabled = MakeColor (ConsoleColor.DarkGray, ConsoleColor.DarkBlue);
 
 			// Focused,
 			//    Selected, Hot: Yellow on Black
@@ -1265,11 +1303,13 @@ namespace Terminal.Gui {
 			Colors.Dialog.Focus = MakeColor (ConsoleColor.White, ConsoleColor.DarkGray);
 			Colors.Dialog.HotNormal = MakeColor (ConsoleColor.DarkBlue, ConsoleColor.Gray);
 			Colors.Dialog.HotFocus = MakeColor (ConsoleColor.DarkBlue, ConsoleColor.DarkGray);
+			Colors.Dialog.Disabled = MakeColor (ConsoleColor.DarkGray, ConsoleColor.Gray);
 
 			Colors.Error.Normal = MakeColor (ConsoleColor.DarkRed, ConsoleColor.White);
 			Colors.Error.Focus = MakeColor (ConsoleColor.White, ConsoleColor.DarkRed);
 			Colors.Error.HotNormal = MakeColor (ConsoleColor.Black, ConsoleColor.White);
 			Colors.Error.HotFocus = MakeColor (ConsoleColor.Black, ConsoleColor.DarkRed);
+			Colors.Error.Disabled = MakeColor (ConsoleColor.DarkGray, ConsoleColor.White);
 		}
 
 		void ResizeScreen ()

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

@@ -791,11 +791,15 @@ namespace Terminal.Gui {
 		bool isButtonReleased = false;
 		bool isButtonDoubleClicked = false;
 		Point point;
+		int buttonPressedCount;
+		bool isOneFingerDoubleClicked = false;
 
 		MouseEvent ToDriverMouse (WindowsConsole.MouseEventRecord mouseEvent)
 		{
 			MouseFlags mouseFlag = MouseFlags.AllEvents;
 
+			//System.Diagnostics.Debug.WriteLine ($"ButtonState: {mouseEvent.ButtonState};EventFlags: {mouseEvent.EventFlags}");
+
 			if (isButtonDoubleClicked) {
 				Application.MainLoop.AddIdle (() => {
 					Task.Run (async () => await ProcessButtonDoubleClickedAsync ());
@@ -810,7 +814,7 @@ namespace Terminal.Gui {
 			// map to the correct clicked event.
 			if ((lastMouseButtonPressed != null || isButtonReleased) && mouseEvent.ButtonState != 0) {
 				lastMouseButtonPressed = null;
-				isButtonPressed = false;
+				//isButtonPressed = false;
 				isButtonReleased = false;
 			}
 
@@ -819,9 +823,70 @@ namespace Terminal.Gui {
 				Y = mouseEvent.MousePosition.Y
 			};
 
-			if ((mouseEvent.ButtonState != 0 && mouseEvent.EventFlags == 0 && lastMouseButtonPressed == null && !isButtonDoubleClicked) ||
-				(mouseEvent.EventFlags == WindowsConsole.EventFlags.MouseMoved &&
-				mouseEvent.ButtonState != 0 && !isButtonReleased && !isButtonDoubleClicked)) {
+			if (!isButtonPressed && buttonPressedCount < 2
+				&& mouseEvent.EventFlags == WindowsConsole.EventFlags.MouseMoved
+				&& (mouseEvent.ButtonState == WindowsConsole.ButtonState.Button1Pressed
+				|| mouseEvent.ButtonState == WindowsConsole.ButtonState.Button2Pressed
+				|| mouseEvent.ButtonState == WindowsConsole.ButtonState.Button3Pressed)) {
+
+				lastMouseButtonPressed = mouseEvent.ButtonState;
+				buttonPressedCount++;
+			} else if (!isButtonPressed && buttonPressedCount > 0 && mouseEvent.ButtonState == 0
+				&& mouseEvent.EventFlags == 0) {
+
+				buttonPressedCount++;
+			} else {
+				buttonPressedCount = 0;
+				isOneFingerDoubleClicked = false;
+			}
+			//System.Diagnostics.Debug.WriteLine ($"isButtonPressed: {isButtonPressed};buttonPressedCount: {buttonPressedCount};lastMouseButtonPressed: {lastMouseButtonPressed}");
+
+			if (buttonPressedCount == 3 && lastMouseButtonPressed != null
+				&& lastMouseButtonPressed == WindowsConsole.ButtonState.Button1Pressed
+				|| lastMouseButtonPressed == WindowsConsole.ButtonState.Button2Pressed
+				|| lastMouseButtonPressed == WindowsConsole.ButtonState.Button3Pressed) {
+
+				switch (lastMouseButtonPressed) {
+				case WindowsConsole.ButtonState.Button1Pressed:
+					mouseFlag = MouseFlags.Button1DoubleClicked;
+					break;
+
+				case WindowsConsole.ButtonState.Button2Pressed:
+					mouseFlag = MouseFlags.Button2DoubleClicked;
+					break;
+
+				case WindowsConsole.ButtonState.Button3Pressed:
+					mouseFlag = MouseFlags.Button3DoubleClicked;
+					break;
+				}
+				isOneFingerDoubleClicked = true;
+
+			} else if (buttonPressedCount == 5 && lastMouseButtonPressed != null && isOneFingerDoubleClicked
+				&& lastMouseButtonPressed == WindowsConsole.ButtonState.Button1Pressed
+				|| lastMouseButtonPressed == WindowsConsole.ButtonState.Button2Pressed
+				|| lastMouseButtonPressed == WindowsConsole.ButtonState.Button3Pressed) {
+
+				switch (lastMouseButtonPressed) {
+				case WindowsConsole.ButtonState.Button1Pressed:
+					mouseFlag = MouseFlags.Button1TripleClicked;
+					break;
+
+				case WindowsConsole.ButtonState.Button2Pressed:
+					mouseFlag = MouseFlags.Button2TripleClicked;
+					break;
+
+				case WindowsConsole.ButtonState.Button3Pressed:
+					mouseFlag = MouseFlags.Button3TripleClicked;
+					break;
+				}
+				buttonPressedCount = 0;
+				lastMouseButtonPressed = null;
+				isOneFingerDoubleClicked = false;
+				isButtonReleased = false;
+
+			} else if ((mouseEvent.ButtonState != 0 && mouseEvent.EventFlags == 0 && lastMouseButtonPressed == null && !isButtonDoubleClicked) ||
+				 (lastMouseButtonPressed == null && mouseEvent.EventFlags == WindowsConsole.EventFlags.MouseMoved &&
+				 mouseEvent.ButtonState != 0 && !isButtonReleased && !isButtonDoubleClicked)) {
 				switch (mouseEvent.ButtonState) {
 				case WindowsConsole.ButtonState.Button1Pressed:
 					mouseFlag = MouseFlags.Button1Pressed;
@@ -856,7 +921,6 @@ namespace Terminal.Gui {
 					});
 				}
 
-
 			} else if ((mouseEvent.EventFlags == 0 || mouseEvent.EventFlags == WindowsConsole.EventFlags.MouseMoved) &&
 				lastMouseButtonPressed != null && !isButtonReleased && !isButtonDoubleClicked) {
 				switch (lastMouseButtonPressed) {
@@ -874,8 +938,8 @@ namespace Terminal.Gui {
 				}
 				isButtonPressed = false;
 				isButtonReleased = true;
-			} else if ((mouseEvent.EventFlags == 0 || mouseEvent.EventFlags == WindowsConsole.EventFlags.MouseMoved) &&
-				  isButtonReleased && p == point) {
+			} else if ((mouseEvent.EventFlags == 0 || mouseEvent.EventFlags == WindowsConsole.EventFlags.MouseMoved)
+				&& !isOneFingerDoubleClicked && isButtonReleased && p == point) {
 				switch (lastMouseButtonPressed) {
 				case WindowsConsole.ButtonState.Button1Pressed:
 					mouseFlag = MouseFlags.Button1Clicked;
@@ -1214,11 +1278,13 @@ namespace Terminal.Gui {
 			Colors.TopLevel.Focus = MakeColor (ConsoleColor.White, ConsoleColor.DarkCyan);
 			Colors.TopLevel.HotNormal = MakeColor (ConsoleColor.DarkYellow, ConsoleColor.Black);
 			Colors.TopLevel.HotFocus = MakeColor (ConsoleColor.DarkBlue, ConsoleColor.DarkCyan);
+			Colors.TopLevel.Disabled = MakeColor (ConsoleColor.DarkGray, ConsoleColor.Black);
 
 			Colors.Base.Normal = MakeColor (ConsoleColor.White, ConsoleColor.DarkBlue);
 			Colors.Base.Focus = MakeColor (ConsoleColor.Black, ConsoleColor.Gray);
 			Colors.Base.HotNormal = MakeColor (ConsoleColor.DarkCyan, ConsoleColor.DarkBlue);
 			Colors.Base.HotFocus = MakeColor (ConsoleColor.Blue, ConsoleColor.Gray);
+			Colors.Base.Disabled = MakeColor (ConsoleColor.DarkGray, ConsoleColor.DarkBlue);
 
 			Colors.Menu.Normal = MakeColor (ConsoleColor.White, ConsoleColor.DarkGray);
 			Colors.Menu.Focus = MakeColor (ConsoleColor.White, ConsoleColor.Black);
@@ -1230,11 +1296,13 @@ namespace Terminal.Gui {
 			Colors.Dialog.Focus = MakeColor (ConsoleColor.White, ConsoleColor.DarkGray);
 			Colors.Dialog.HotNormal = MakeColor (ConsoleColor.DarkBlue, ConsoleColor.Gray);
 			Colors.Dialog.HotFocus = MakeColor (ConsoleColor.DarkBlue, ConsoleColor.DarkGray);
+			Colors.Dialog.Disabled = MakeColor (ConsoleColor.DarkGray, ConsoleColor.Gray);
 
 			Colors.Error.Normal = MakeColor (ConsoleColor.DarkRed, ConsoleColor.White);
 			Colors.Error.Focus = MakeColor (ConsoleColor.White, ConsoleColor.DarkRed);
 			Colors.Error.HotNormal = MakeColor (ConsoleColor.Black, ConsoleColor.White);
 			Colors.Error.HotFocus = MakeColor (ConsoleColor.Black, ConsoleColor.DarkRed);
+			Colors.Error.Disabled = MakeColor (ConsoleColor.DarkGray, ConsoleColor.White);
 		}
 
 		void ResizeScreen ()

+ 6 - 2
Terminal.Gui/Core/Application.cs

@@ -728,8 +728,8 @@ namespace Terminal.Gui {
 			Driver.PrepareToRun (MainLoop, ProcessKeyEvent, ProcessKeyDownEvent, ProcessKeyUpEvent, ProcessMouseEvent);
 			if (toplevel.LayoutStyle == LayoutStyle.Computed)
 				toplevel.SetRelativeLayout (new Rect (0, 0, Driver.Cols, Driver.Rows));
-			toplevel.PositionToplevels ();
 			toplevel.LayoutSubviews ();
+			toplevel.PositionToplevels ();
 			toplevel.WillPresent ();
 			if (refreshDriver) {
 				if (MdiTop != null) {
@@ -794,6 +794,7 @@ namespace Terminal.Gui {
 			RootMouseEvent = null;
 			Resized = null;
 			_initialized = false;
+			mouseGrabView = null;
 
 			// Reset synchronization context to allow the user to run async/await,
 			// as the main loop has been ended, the synchronization context from 
@@ -1141,8 +1142,8 @@ namespace Terminal.Gui {
 			Driver.Clip = full;
 			foreach (var t in toplevels) {
 				t.SetRelativeLayout (full);
-				t.PositionToplevels ();
 				t.LayoutSubviews ();
+				t.PositionToplevels ();
 			}
 			Refresh ();
 		}
@@ -1167,6 +1168,9 @@ namespace Terminal.Gui {
 		static bool SetCurrentAsTop ()
 		{
 			if (MdiTop == null && Current != Top && Current?.SuperView == null && Current?.Modal == false) {
+				if (Current.Frame != new Rect (0, 0, Driver.Cols, Driver.Rows)) {
+					Current.Frame = new Rect (0, 0, Driver.Cols, Driver.Rows);
+				}
 				Top = Current;
 				return true;
 			}

+ 25 - 0
Terminal.Gui/Core/Responder.cs

@@ -58,6 +58,16 @@ namespace Terminal.Gui {
 		/// <value><c>true</c> if has focus; otherwise, <c>false</c>.</value>
 		public virtual bool HasFocus { get; }
 
+		/// <summary>
+		/// Gets or sets a value indicating whether this <see cref="Responder"/> can respond to user interaction.
+		/// </summary>
+		public virtual bool Enabled { get; set; } = true;
+
+		/// <summary>
+		/// Gets or sets a value indicating whether this <see cref="Responder"/> and all its child controls are displayed.
+		/// </summary>
+		public virtual bool Visible { get; set; } = true;
+
 		// Key handling
 		/// <summary>
 		///   This method can be overwritten by view that
@@ -211,6 +221,21 @@ namespace Terminal.Gui {
 			return false;
 		}
 
+		/// <summary>
+		/// Method invoked when the <see cref="CanFocus"/> property from a view is changed.
+		/// </summary>
+		public virtual void OnCanFocusChanged () { }
+
+		/// <summary>
+		/// Method invoked when the <see cref="Enabled"/> property from a view is changed.
+		/// </summary>
+		public virtual void OnEnabledChanged () { }
+
+		/// <summary>
+		/// Method invoked when the <see cref="Visible"/> property from a view is changed.
+		/// </summary>
+		public virtual void OnVisibleChanged () { }
+
 		/// <summary>
 		/// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources.
 		/// </summary>

+ 10 - 4
Terminal.Gui/Core/ShortcutHelper.cs

@@ -59,15 +59,18 @@ namespace Terminal.Gui {
 		/// Get the <see cref="Shortcut"/> key as string.
 		/// </summary>
 		/// <param name="shortcut">The shortcut key.</param>
+		/// <param name="delimiter">The delimiter string.</param>
 		/// <returns></returns>
-		public static ustring GetShortcutTag (Key shortcut)
+		public static ustring GetShortcutTag (Key shortcut, ustring delimiter = null)
 		{
 			if (shortcut == Key.Null) {
 				return "";
 			}
 
 			var k = shortcut;
-			var delimiter = MenuBar.ShortcutDelimiter;
+			if (delimiter == null) {
+				delimiter = MenuBar.ShortcutDelimiter;
+			}
 			ustring tag = ustring.Empty;
 			var sCut = GetKeyToString (k, out Key knm).ToString ();
 			if (knm == Key.Unknown) {
@@ -146,7 +149,8 @@ namespace Terminal.Gui {
 		/// Allows to retrieve a <see cref="Key"/> from a <see cref="ShortcutTag"/>
 		/// </summary>
 		/// <param name="tag">The key as string.</param>
-		public static Key GetShortcutFromTag (ustring tag)
+		/// <param name="delimiter">The delimiter string.</param>
+		public static Key GetShortcutFromTag (ustring tag, ustring delimiter = null)
 		{
 			var sCut = tag;
 			if (sCut.IsEmpty) {
@@ -155,7 +159,9 @@ namespace Terminal.Gui {
 
 			Key key = Key.Null;
 			//var hasCtrl = false;
-			var delimiter = MenuBar.ShortcutDelimiter;
+			if (delimiter == null) {
+				delimiter = MenuBar.ShortcutDelimiter;
+			}
 
 			ustring [] keys = sCut.Split (delimiter);
 			for (int i = 0; i < keys.Length; i++) {

+ 53 - 50
Terminal.Gui/Core/Toplevel.cs

@@ -478,15 +478,19 @@ namespace Terminal.Gui {
 			}
 		}
 
-		internal void EnsureVisibleBounds (Toplevel top, int x, int y, out int nx, out int ny)
+		internal View EnsureVisibleBounds (Toplevel top, int x, int y,
+			out int nx, out int ny, out View mb, out View sb)
 		{
-			nx = Math.Max (x, 0);
 			int l;
-			if (SuperView == null || SuperView is Toplevel) {
+			View superView;
+			if (top?.SuperView == null || top == Application.Top || top?.SuperView == Application.Top) {
 				l = Driver.Cols;
+				superView = Application.Top;
 			} else {
-				l = SuperView.Frame.Width;
+				l = top.SuperView.Frame.Width;
+				superView = top.SuperView;
 			}
+			nx = Math.Max (x, 0);
 			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) {
@@ -494,34 +498,48 @@ namespace Terminal.Gui {
 			}
 			//System.Diagnostics.Debug.WriteLine ($"nx:{nx}, rWidth:{rWidth}");
 			bool m, s;
-			if (SuperView == null || SuperView.GetType () != typeof (Toplevel)) {
-				m = Application.Top.MenuBar != null;
+			if (top?.SuperView == null || top == Application.Top || top?.SuperView == Application.Top) {
+				m = Application.Top.MenuBar?.Visible == true;
+				mb = Application.Top.MenuBar;
 			} else {
-				m = ((Toplevel)SuperView).MenuBar != null;
+				var t = top.SuperView;
+				while (!(t is Toplevel)) {
+					t = t.SuperView;
+				}
+				m = ((Toplevel)t).MenuBar?.Visible == true;
+				mb = ((Toplevel)t).MenuBar;
 			}
-			if (SuperView == null || SuperView is Toplevel) {
+			if (top?.SuperView == null || top == Application.Top || top?.SuperView == Application.Top) {
 				l = m ? 1 : 0;
 			} else {
 				l = 0;
 			}
 			ny = Math.Max (y, l);
-			if (SuperView == null || SuperView.GetType () != typeof (Toplevel)) {
-				s = Application.Top.StatusBar != null && Application.Top.StatusBar.Visible;
+			if (top?.SuperView == null || top == Application.Top || top?.SuperView == Application.Top) {
+				s = Application.Top.StatusBar?.Visible == true;
+				sb = Application.Top.StatusBar;
 			} else {
-				s = ((Toplevel)SuperView).StatusBar != null && ((Toplevel)SuperView).StatusBar.Visible;
+				var t = top.SuperView;
+				while (!(t is Toplevel)) {
+					t = t.SuperView;
+				}
+				s = ((Toplevel)t).StatusBar?.Visible == true;
+				sb = ((Toplevel)t).StatusBar;
 			}
-			if (SuperView == null || SuperView is Toplevel) {
+			if (top?.SuperView == null || top == Application.Top || top?.SuperView == Application.Top) {
 				l = s ? Driver.Rows - 1 : Driver.Rows;
 			} else {
-				l = s ? SuperView.Frame.Height - 1 : SuperView.Frame.Height;
+				l = s ? top.SuperView.Frame.Height - 1 : top.SuperView.Frame.Height;
 			}
 			ny = Math.Min (ny, l);
-			ny = ny + top.Frame.Height > l ? Math.Max (l - top.Frame.Height, m ? 1 : 0) : ny;
+			ny = ny + top.Frame.Height >= l ? Math.Max (l - top.Frame.Height, m ? 1 : 0) : ny;
 			SetHeight (top.Frame.Height, out int rHeight);
 			if (rHeight < 0 && ny >= top.Frame.Y) {
 				ny = Math.Max (top.Frame.Bottom - 2, 0);
 			}
 			//System.Diagnostics.Debug.WriteLine ($"ny:{ny}, rHeight:{rHeight}");
+
+			return superView;
 		}
 
 		internal void PositionToplevels ()
@@ -540,45 +558,32 @@ namespace Terminal.Gui {
 		/// <param name="top">The toplevel.</param>
 		public virtual void PositionToplevel (Toplevel top)
 		{
-			EnsureVisibleBounds (top, top.Frame.X, top.Frame.Y, out int nx, out int ny);
-			if ((top?.SuperView != null || top != Application.Top)
+			var superView = EnsureVisibleBounds (top, top.Frame.X, top.Frame.Y,
+				out int nx, out int ny, out _, out View sb);
+			bool layoutSubviews = false;
+			if ((top?.SuperView != null || (top != Application.Top && top.Modal)
+				|| (top?.SuperView == null && top.IsMdiChild))
 				&& (nx > top.Frame.X || ny > top.Frame.Y) && top.LayoutStyle == LayoutStyle.Computed) {
+
 				if ((top.X == null || top.X is Pos.PosAbsolute) && top.Bounds.X != nx) {
 					top.X = nx;
+					layoutSubviews = true;
 				}
 				if ((top.Y == null || top.Y is Pos.PosAbsolute) && top.Bounds.Y != ny) {
 					top.Y = ny;
+					layoutSubviews = true;
 				}
 			}
 
-			View superView = null;
-			StatusBar statusBar = null;
+			if (sb != null && ny + top.Frame.Height != superView.Frame.Height - (sb.Visible ? 1 : 0)
+					&& top.Height is Dim.DimFill) {
 
-			if (top != Application.Top && Application.Top.StatusBar != null) {
-				superView = Application.Top;
-				statusBar = Application.Top.StatusBar;
-			} else if (top?.SuperView != null && top.SuperView is Toplevel toplevel) {
-				superView = top.SuperView;
-				statusBar = toplevel.StatusBar;
+				top.Height = Dim.Fill (sb.Visible ? 1 : 0);
+				layoutSubviews = true;
 			}
-			if (statusBar != null) {
-				if (ny + top.Frame.Height >= superView.Frame.Height - (statusBar.Visible ? 1 : 0)) {
-					if (top.Height is Dim.DimFill) {
-						top.Height = Dim.Fill (statusBar.Visible ? 1 : 0);
-					}
-				}
-				if (superView == Application.Top) {
-					top.SetRelativeLayout (superView.Frame);
-				} else {
-					superView.LayoutSubviews ();
-				}
-			}
-			if (top.StatusBar != null) {
-				if (top.StatusBar.Frame.Y != top.Frame.Height - (top.StatusBar.Visible ? 1 : 0)) {
-					top.StatusBar.Y = top.Frame.Height - (top.StatusBar.Visible ? 1 : 0);
-					top.LayoutSubviews ();
-				}
-				top.BringSubviewToFront (top.StatusBar);
+
+			if (layoutSubviews) {
+				superView.LayoutSubviews ();
 			}
 		}
 
@@ -590,18 +595,15 @@ namespace Terminal.Gui {
 			}
 
 			if (!NeedDisplay.IsEmpty || ChildNeedsDisplay || LayoutNeeded) {
-				Driver.SetAttribute (ColorScheme.Normal);
+				Driver.SetAttribute (GetNormalColor ());
 
 				// This is the Application.Top. Clear just the region we're being asked to redraw 
 				// (the bounds passed to us).
-				// Must be the screen-relative region to clear, not the bounds.
-				Clear (Frame);
-				Driver.SetAttribute (Colors.Base.Normal);
+				Clear ();
+				Driver.SetAttribute (Enabled ? Colors.Base.Normal : Colors.Base.Disabled);
 
-				if (LayoutStyle == LayoutStyle.Computed)
-					SetRelativeLayout (Bounds);
-				PositionToplevels ();
 				LayoutSubviews ();
+				PositionToplevels ();
 
 				if (this == Application.MdiTop) {
 					foreach (var top in Application.MdiChildes.AsEnumerable ().Reverse ()) {
@@ -680,7 +682,8 @@ namespace Terminal.Gui {
 						SuperView.SetNeedsDisplay ();
 					}
 					EnsureVisibleBounds (this, mouseEvent.X + (SuperView == null ? mouseEvent.OfX - start.X : Frame.X - start.X),
-						mouseEvent.Y + (SuperView == null ? mouseEvent.OfY : Frame.Y), out nx, out ny);
+						mouseEvent.Y + (SuperView == null ? mouseEvent.OfY : Frame.Y),
+						out nx, out ny, out _, out _);
 
 					dragPosition = new Point (nx, ny);
 					LayoutSubviews ();

+ 165 - 39
Terminal.Gui/Core/View.cs

@@ -163,6 +163,21 @@ namespace Terminal.Gui {
 		/// </summary>
 		public event Action<MouseEventArgs> MouseClick;
 
+		/// <summary>
+		/// Event fired when the <see cref="CanFocus"/> value is being changed.
+		/// </summary>
+		public event Action CanFocusChanged;
+
+		/// <summary>
+		/// Event fired when the <see cref="Enabled"/> value is being changed.
+		/// </summary>
+		public event Action EnabledChanged;
+
+		/// <summary>
+		/// Event fired when the <see cref="Visible"/> value is being changed.
+		/// </summary>
+		public event Action VisibleChanged;
+
 		/// <summary>
 		/// Gets or sets the HotKey defined for this view. A user pressing HotKey on the keyboard while this view has focus will cause the Clicked event to fire.
 		/// </summary>
@@ -329,6 +344,11 @@ namespace Terminal.Gui {
 						TabIndex = SuperView != null ? SuperView.tabIndexes.IndexOf (this) : -1;
 					}
 					TabStop = value;
+					if (!value && HasFocus) {
+						SetHasFocus (false, this);
+					}
+					OnCanFocusChanged ();
+					SetNeedsDisplay ();
 				}
 				if (subviews != null && IsInitialized) {
 					foreach (var view in subviews) {
@@ -1114,7 +1134,7 @@ namespace Terminal.Gui {
 			if (focused)
 				DrawHotString (text, scheme.HotFocus, scheme.Focus);
 			else
-				DrawHotString (text, scheme.HotNormal, scheme.Normal);
+				DrawHotString (text, Enabled ? scheme.HotNormal : scheme.Disabled, Enabled ? scheme.Normal : scheme.Disabled);
 		}
 
 		/// <summary>
@@ -1143,11 +1163,11 @@ namespace Terminal.Gui {
 		///    in a visually sensible place.
 		public virtual void PositionCursor ()
 		{
-			if (!CanBeVisible (this)) {
+			if (!CanBeVisible (this) || !Enabled) {
 				return;
 			}
 
-			if (focused?.Frame.Width > 0 && focused.Frame.Height > 0) {
+			if (focused?.Visible == true && focused?.Enabled == true && focused?.Frame.Width > 0 && focused.Frame.Height > 0) {
 				focused.PositionCursor ();
 			} else {
 				if (CanFocus && HasFocus && Visible && Frame.Width > 0 && Frame.Height > 0) {
@@ -1271,6 +1291,8 @@ namespace Terminal.Gui {
 			}
 		}
 
+		ColorScheme colorScheme;
+
 		/// <summary>
 		/// The color scheme for this view, if it is not defined, it returns the <see cref="SuperView"/>'s
 		/// color scheme.
@@ -1289,8 +1311,6 @@ namespace Terminal.Gui {
 			}
 		}
 
-		ColorScheme colorScheme;
-
 		/// <summary>
 		/// Displays the specified character in the specified column and row of the View.
 		/// </summary>
@@ -1351,7 +1371,8 @@ namespace Terminal.Gui {
 				if (textFormatter != null) {
 					textFormatter.NeedsFormat = true;
 				}
-				textFormatter?.Draw (ViewToScreen (Bounds), HasFocus ? ColorScheme.Focus : ColorScheme.Normal, HasFocus ? ColorScheme.HotFocus : ColorScheme.HotNormal);
+				textFormatter?.Draw (ViewToScreen (Bounds), HasFocus ? ColorScheme.Focus : GetNormalColor (),
+					HasFocus ? ColorScheme.HotFocus : Enabled ? ColorScheme.HotNormal : ColorScheme.Disabled);
 			}
 
 			// Invoke DrawContentEvent
@@ -1414,7 +1435,7 @@ namespace Terminal.Gui {
 			if (view == null)
 				return;
 			//Console.WriteLine ($"Request to focus {view}");
-			if (!view.CanFocus || !view.Visible)
+			if (!view.CanFocus || !view.Visible || !view.Enabled)
 				return;
 			if (focused?.hasFocus == true && focused == view)
 				return;
@@ -1444,7 +1465,10 @@ namespace Terminal.Gui {
 		/// </summary>
 		public void SetFocus ()
 		{
-			if (!CanBeVisible (this)) {
+			if (!CanBeVisible (this) || !Enabled) {
+				if (HasFocus) {
+					SetHasFocus (false, this);
+				}
 				return;
 			}
 
@@ -1479,14 +1503,20 @@ namespace Terminal.Gui {
 		/// <inheritdoc/>
 		public override bool ProcessKey (KeyEvent keyEvent)
 		{
+			if (!Enabled) {
+				return false;
+			}
+
 			KeyEventEventArgs args = new KeyEventEventArgs (keyEvent);
 			KeyPress?.Invoke (args);
 			if (args.Handled)
 				return true;
-			Focused?.KeyPress?.Invoke (args);
-			if (args.Handled)
-				return true;
-			if (Focused?.ProcessKey (keyEvent) == true)
+			if (Focused?.Enabled == true) {
+				Focused?.KeyPress?.Invoke (args);
+				if (args.Handled)
+					return true;
+			}
+			if (Focused?.Enabled == true && Focused?.ProcessKey (keyEvent) == true)
 				return true;
 
 			return false;
@@ -1495,19 +1525,25 @@ namespace Terminal.Gui {
 		/// <inheritdoc/>
 		public override bool ProcessHotKey (KeyEvent keyEvent)
 		{
+			if (!Enabled) {
+				return false;
+			}
+
 			KeyEventEventArgs args = new KeyEventEventArgs (keyEvent);
 			KeyPress?.Invoke (args);
 			if (args.Handled)
 				return true;
-			Focused?.KeyPress?.Invoke (args);
-			if (args.Handled)
-				return true;
-			if (Focused?.ProcessKey (keyEvent) == true)
+			if (Focused?.Enabled == true) {
+				Focused?.KeyPress?.Invoke (args);
+				if (args.Handled)
+					return true;
+			}
+			if (Focused?.Enabled == true && Focused?.ProcessKey (keyEvent) == true)
 				return true;
 			if (subviews == null || subviews.Count == 0)
 				return false;
 			foreach (var view in subviews)
-				if (view.ProcessHotKey (keyEvent))
+				if (view.Enabled && view.ProcessHotKey (keyEvent))
 					return true;
 			return false;
 		}
@@ -1515,19 +1551,25 @@ namespace Terminal.Gui {
 		/// <inheritdoc/>
 		public override bool ProcessColdKey (KeyEvent keyEvent)
 		{
+			if (!Enabled) {
+				return false;
+			}
+
 			KeyEventEventArgs args = new KeyEventEventArgs (keyEvent);
 			KeyPress?.Invoke (args);
 			if (args.Handled)
 				return true;
-			Focused?.KeyPress?.Invoke (args);
-			if (args.Handled)
-				return true;
-			if (Focused?.ProcessKey (keyEvent) == true)
+			if (Focused?.Enabled == true) {
+				Focused?.KeyPress?.Invoke (args);
+				if (args.Handled)
+					return true;
+			}
+			if (Focused?.Enabled == true && Focused?.ProcessKey (keyEvent) == true)
 				return true;
 			if (subviews == null || subviews.Count == 0)
 				return false;
 			foreach (var view in subviews)
-				if (view.ProcessColdKey (keyEvent))
+				if (view.Enabled && view.ProcessColdKey (keyEvent))
 					return true;
 			return false;
 		}
@@ -1540,12 +1582,16 @@ namespace Terminal.Gui {
 		/// <param name="keyEvent">Contains the details about the key that produced the event.</param>
 		public override bool OnKeyDown (KeyEvent keyEvent)
 		{
+			if (!Enabled) {
+				return false;
+			}
+
 			KeyEventEventArgs args = new KeyEventEventArgs (keyEvent);
 			KeyDown?.Invoke (args);
 			if (args.Handled) {
 				return true;
 			}
-			if (Focused?.OnKeyDown (keyEvent) == true) {
+			if (Focused?.Enabled == true && Focused?.OnKeyDown (keyEvent) == true) {
 				return true;
 			}
 
@@ -1560,12 +1606,16 @@ namespace Terminal.Gui {
 		/// <param name="keyEvent">Contains the details about the key that produced the event.</param>
 		public override bool OnKeyUp (KeyEvent keyEvent)
 		{
+			if (!Enabled) {
+				return false;
+			}
+
 			KeyEventEventArgs args = new KeyEventEventArgs (keyEvent);
 			KeyUp?.Invoke (args);
 			if (args.Handled) {
 				return true;
 			}
-			if (Focused?.OnKeyUp (keyEvent) == true) {
+			if (Focused?.Enabled == true && Focused?.OnKeyUp (keyEvent) == true) {
 				return true;
 			}
 
@@ -1601,7 +1651,7 @@ namespace Terminal.Gui {
 			}
 
 			foreach (var view in tabIndexes) {
-				if (view.CanFocus && view.tabStop && view.Visible) {
+				if (view.CanFocus && view.tabStop && view.Visible && view.Enabled) {
 					SetFocus (view);
 					return;
 				}
@@ -1626,7 +1676,7 @@ namespace Terminal.Gui {
 				i--;
 
 				View v = tabIndexes [i];
-				if (v.CanFocus && v.tabStop && v.Visible) {
+				if (v.CanFocus && v.tabStop && v.Visible && v.Enabled) {
 					SetFocus (v);
 					return;
 				}
@@ -1662,10 +1712,10 @@ namespace Terminal.Gui {
 					focused_idx = i;
 					continue;
 				}
-				if (w.CanFocus && focused_idx != -1 && w.tabStop && w.Visible) {
+				if (w.CanFocus && focused_idx != -1 && w.tabStop && w.Visible && w.Enabled) {
 					focused.SetHasFocus (false, w);
 
-					if (w != null && w.CanFocus && w.tabStop && w.Visible)
+					if (w != null && w.CanFocus && w.tabStop && w.Visible && w.Enabled)
 						w.FocusLast ();
 
 					SetFocus (w);
@@ -1708,10 +1758,10 @@ namespace Terminal.Gui {
 					focused_idx = i;
 					continue;
 				}
-				if (w.CanFocus && focused_idx != -1 && w.tabStop && w.Visible) {
+				if (w.CanFocus && focused_idx != -1 && w.tabStop && w.Visible && w.Enabled) {
 					focused.SetHasFocus (false, w);
 
-					if (w != null && w.CanFocus && w.tabStop && w.Visible)
+					if (w != null && w.CanFocus && w.tabStop && w.Visible && w.Enabled)
 						w.FocusFirst ();
 
 					SetFocus (w);
@@ -2072,7 +2122,50 @@ namespace Terminal.Gui {
 		/// Get or sets if  the <see cref="View"/> was already initialized.
 		/// This derived from <see cref="ISupportInitializeNotification"/> to allow notify all the views that are being initialized.
 		/// </summary>
-		public bool IsInitialized { get; set; }
+		public virtual bool IsInitialized { get; set; }
+
+		bool oldEnabled;
+
+		/// <inheritdoc/>
+		public override bool Enabled {
+			get => base.Enabled;
+			set {
+				if (base.Enabled != value) {
+					base.Enabled = value;
+					if (!value && HasFocus) {
+						SetHasFocus (false, this);
+					}
+					OnEnabledChanged ();
+					SetNeedsDisplay ();
+				}
+				if (subviews != null) {
+					foreach (var view in subviews) {
+						if (!value) {
+							view.oldEnabled = view.Enabled;
+							view.Enabled = value;
+						} else {
+							view.Enabled = view.oldEnabled;
+							view.addingView = false;
+						}
+					}
+				}
+			}
+		}
+
+		/// <inheritdoc/>>
+		public override bool Visible {
+			get => base.Visible;
+			set {
+				if (base.Visible != value) {
+					base.Visible = value;
+					if (!value && HasFocus) {
+						SetHasFocus (false, this);
+					}
+					OnVisibleChanged ();
+					SetNeedsDisplay ();
+				}
+			}
+		}
 
 		/// <summary>
 		/// Pretty prints the View
@@ -2148,6 +2241,10 @@ namespace Terminal.Gui {
 		/// <inheritdoc/>
 		public override bool OnMouseEnter (MouseEvent mouseEvent)
 		{
+			if (!Enabled) {
+				return true;
+			}
+
 			if (!CanBeVisible (this)) {
 				return false;
 			}
@@ -2165,6 +2262,10 @@ namespace Terminal.Gui {
 		/// <inheritdoc/>
 		public override bool OnMouseLeave (MouseEvent mouseEvent)
 		{
+			if (!Enabled) {
+				return true;
+			}
+
 			if (!CanBeVisible (this)) {
 				return false;
 			}
@@ -2186,13 +2287,16 @@ namespace Terminal.Gui {
 		/// <returns><c>true</c>, if the event was handled, <c>false</c> otherwise.</returns>
 		public virtual bool OnMouseEvent (MouseEvent mouseEvent)
 		{
+			if (!Enabled) {
+				return true;
+			}
+
 			if (!CanBeVisible (this)) {
 				return false;
 			}
 
 			MouseEventArgs args = new MouseEventArgs (mouseEvent);
-			OnMouseClick (args);
-			if (args.Handled)
+			if (OnMouseClick (args))
 				return true;
 			if (MouseEvent (mouseEvent))
 				return true;
@@ -2211,7 +2315,24 @@ namespace Terminal.Gui {
 		/// <summary>
 		/// Invokes the MouseClick event.
 		/// </summary>
-		protected void OnMouseClick (MouseEventArgs args) => MouseClick?.Invoke (args);
+		protected bool OnMouseClick (MouseEventArgs args)
+		{
+			if (!Enabled) {
+				return true;
+			}
+
+			MouseClick?.Invoke (args);
+			return args.Handled;
+		}
+
+		/// <inheritdoc/>
+		public override void OnCanFocusChanged () => CanFocusChanged?.Invoke ();
+
+		/// <inheritdoc/>
+		public override void OnEnabledChanged () => EnabledChanged?.Invoke ();
+
+		/// <inheritdoc/>
+		public override void OnVisibleChanged () => VisibleChanged?.Invoke ();
 
 		/// <inheritdoc/>
 		protected override void Dispose (bool disposing)
@@ -2258,11 +2379,6 @@ namespace Terminal.Gui {
 			Initialized?.Invoke (this, EventArgs.Empty);
 		}
 
-		/// <summary>
-		/// Gets or sets the view visibility.
-		/// </summary>
-		public bool Visible { get; set; } = true;
-
 		bool CanBeVisible (View view)
 		{
 			if (!view.Visible) {
@@ -2372,5 +2488,15 @@ namespace Terminal.Gui {
 
 			return CanSetHeight (0, out _);
 		}
+
+		/// <summary>
+		/// Determines the current <see cref="ColorScheme"/> based on the <see cref="Enabled"/> value.
+		/// </summary>
+		/// <returns><see cref="ColorScheme.Normal"/> if <see cref="Enabled"/> is <see langword="true"/>
+		/// or <see cref="ColorScheme.Disabled"/> if <see cref="Enabled"/> is <see langword="false"/></returns>
+		protected Attribute GetNormalColor ()
+		{
+			return Enabled ? ColorScheme.Normal : ColorScheme.Disabled;
+		}
 	}
 }

+ 3 - 3
Terminal.Gui/Core/Window.cs

@@ -181,7 +181,7 @@ namespace Terminal.Gui {
 
 			// 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?
 			if (!NeedDisplay.IsEmpty) {
-				Driver.SetAttribute (ColorScheme.Normal);
+				Driver.SetAttribute (GetNormalColor ());
 				Driver.DrawWindowFrame (scrRect, padding + 1, padding + 1, padding + 1, padding + 1, border: true, fill: true);
 			}
 
@@ -194,13 +194,13 @@ namespace Terminal.Gui {
 
 			ClearLayoutNeeded ();
 			ClearNeedsDisplay ();
-			Driver.SetAttribute (ColorScheme.Normal);
+			Driver.SetAttribute (GetNormalColor ());
 			Driver.DrawWindowFrame (scrRect, padding + 1, padding + 1, padding + 1, padding + 1, border: true, fill: false);
 
 			if (HasFocus)
 				Driver.SetAttribute (ColorScheme.HotNormal);
 			Driver.DrawWindowTitle (scrRect, Title, padding, padding, padding, padding);
-			Driver.SetAttribute (ColorScheme.Normal);
+			Driver.SetAttribute (GetNormalColor ());
 
 			// Checks if there are any SuperView view which intersect with this window.
 			if (SuperView != null) {

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

@@ -184,6 +184,10 @@ namespace Terminal.Gui {
 		///<inheritdoc/>
 		public override bool ProcessHotKey (KeyEvent kb)
 		{
+			if (!Enabled) {
+				return false;
+			}
+
 			if (kb.IsAlt)
 				return CheckKey (kb);
 
@@ -193,6 +197,10 @@ namespace Terminal.Gui {
 		///<inheritdoc/>
 		public override bool ProcessColdKey (KeyEvent kb)
 		{
+			if (!Enabled) {
+				return false;
+			}
+
 			if (IsDefault && kb.KeyValue == '\n') {
 				Clicked?.Invoke ();
 				return true;
@@ -203,6 +211,10 @@ namespace Terminal.Gui {
 		///<inheritdoc/>
 		public override bool ProcessKey (KeyEvent kb)
 		{
+			if (!Enabled) {
+				return false;
+			}
+
 			var c = kb.KeyValue;
 			if (c == '\n' || c == ' ' || kb.Key == HotKey) {
 				Clicked?.Invoke ();
@@ -228,7 +240,7 @@ namespace Terminal.Gui {
 		{
 			if (me.Flags == MouseFlags.Button1Clicked || me.Flags == MouseFlags.Button1DoubleClicked ||
 				me.Flags == MouseFlags.Button1TripleClicked) {
-				if (CanFocus) {
+				if (CanFocus && Enabled) {
 					if (!HasFocus) {
 						SetFocus ();
 						SetNeedsDisplay ();

+ 2 - 2
Terminal.Gui/Views/Checkbox.cs

@@ -116,7 +116,7 @@ namespace Terminal.Gui {
 		///<inheritdoc/>
 		public override void Redraw (Rect bounds)
 		{
-			Driver.SetAttribute (HasFocus ? ColorScheme.Focus : ColorScheme.Normal);
+			Driver.SetAttribute (HasFocus ? ColorScheme.Focus : GetNormalColor ());
 			Move (0, 0);
 			Driver.AddRune (Checked ? Driver.Checked : Driver.UnChecked);
 			Driver.AddRune (' ');
@@ -124,7 +124,7 @@ namespace Terminal.Gui {
 			Driver.AddStr (Text);
 			if (hot_pos != -1) {
 				Move (2 + hot_pos, 0);
-				Driver.SetAttribute (HasFocus ? ColorScheme.HotFocus : ColorScheme.HotNormal);
+				Driver.SetAttribute (HasFocus ? ColorScheme.HotFocus : Enabled ? ColorScheme.HotNormal : ColorScheme.Disabled);
 				Driver.AddRune (hot_key);
 			}
 		}

+ 30 - 13
Terminal.Gui/Views/ComboBox.cs

@@ -30,7 +30,7 @@ namespace Terminal.Gui {
 				source = value;
 
 				// Only need to refresh list if its been added to a container view
-				if(SuperView != null && SuperView.Subviews.Contains(this)) { 
+				if (SuperView != null && SuperView.Subviews.Contains (this)) {
 					Search_Changed ("");
 					SetNeedsDisplay ();
 				}
@@ -90,7 +90,7 @@ namespace Terminal.Gui {
 		{
 			search = new TextField ("");
 			listview = new ListView () { LayoutStyle = LayoutStyle.Computed, CanFocus = true, TabStop = false };
-						
+
 			Initialize ();
 			Text = text;
 		}
@@ -124,8 +124,8 @@ namespace Terminal.Gui {
 
 			// On resize
 			LayoutComplete += (LayoutEventArgs a) => {
-				if (!autoHide && search.Frame.Width != Bounds.Width ||
-					autoHide && search.Frame.Width != Bounds.Width - 1) {
+				if ((!autoHide && Bounds.Width > 0 && search.Frame.Width != Bounds.Width) ||
+					(autoHide && Bounds.Width > 0 && search.Frame.Width != Bounds.Width - 1)) {
 					search.Width = listview.Width = autoHide ? Bounds.Width - 1 : Bounds.Width;
 					listview.Height = CalculatetHeight ();
 					search.SetRelativeLayout (Bounds);
@@ -144,7 +144,7 @@ namespace Terminal.Gui {
 
 				// Determine if this view is hosted inside a dialog and is the only control
 				for (View view = this.SuperView; view != null; view = view.SuperView) {
-					if (view is Dialog && SuperView != null && SuperView.Subviews.Count == 1 && SuperView.Subviews[0] == this) {
+					if (view is Dialog && SuperView != null && SuperView.Subviews.Count == 1 && SuperView.Subviews [0] == this) {
 						autoHide = false;
 						break;
 					}
@@ -176,6 +176,21 @@ namespace Terminal.Gui {
 			}
 		}
 
+		/// <summary>
+		///If set to true its not allow any changes in the text.
+		/// </summary>
+		public bool ReadOnly {
+			get => search.ReadOnly;
+			set {
+				search.ReadOnly = value;
+				if (search.ReadOnly) {
+					if (search.ColorScheme != null) {
+						search.ColorScheme.Normal = search.ColorScheme.Focus;
+					}
+				}
+			}
+		}
+
 		///<inheritdoc/>
 		public override bool MouseEvent (MouseEvent me)
 		{
@@ -247,7 +262,7 @@ namespace Terminal.Gui {
 		{
 			// Note: Cannot rely on "listview.SelectedItem != lastSelectedItem" because the list is dynamic. 
 			// So we cannot optimize. Ie: Don't call if not changed
-			SelectedItemChanged?.Invoke (new ListViewItemEventArgs(SelectedItem, search.Text));
+			SelectedItemChanged?.Invoke (new ListViewItemEventArgs (SelectedItem, search.Text));
 
 			return true;
 		}
@@ -321,7 +336,7 @@ namespace Terminal.Gui {
 				return true;
 			}
 
-			if(e.Key == Key.PageDown) {
+			if (e.Key == Key.PageDown) {
 				if (listview.SelectedItem != -1) {
 					listview.MovePageDown ();
 				}
@@ -342,8 +357,8 @@ namespace Terminal.Gui {
 				return true;
 			}
 
-			if(e.Key == Key.End) {
-				if(listview.SelectedItem != -1) {
+			if (e.Key == Key.End) {
+				if (listview.SelectedItem != -1) {
 					listview.MoveEnd ();
 				}
 				return true;
@@ -380,7 +395,7 @@ namespace Terminal.Gui {
 		private void SetValue (object text)
 		{
 			search.TextChanged -= Search_Changed;
-			this.text = search.Text = text.ToString();
+			this.text = search.Text = text.ToString ();
 			search.CursorPosition = 0;
 			search.TextChanged += Search_Changed;
 			SelectedItem = GetSelectedItemFromSource (this.text);
@@ -472,7 +487,7 @@ namespace Terminal.Gui {
 				ResetSearchSet (noCopy: true);
 
 				foreach (var item in source.ToList ()) { // Iterate to preserver object type and force deep copy
-					if (item.ToString().StartsWith (search.Text.ToString(), StringComparison.CurrentCultureIgnoreCase)) { 
+					if (item.ToString ().StartsWith (search.Text.ToString (), StringComparison.CurrentCultureIgnoreCase)) {
 						searchset.Add (item);
 					}
 				}
@@ -501,9 +516,11 @@ namespace Terminal.Gui {
 		/// Consider making public
 		private void HideList ()
 		{
+			var rect = listview.ViewToScreen (listview.Bounds);
 			Reset (SelectedItem > -1);
-			listview.Clear ();
+			listview.Clear (rect);
 			listview.TabStop = false;
+			SuperView?.SetNeedsDisplay (rect);
 		}
 
 		/// <summary>
@@ -515,7 +532,7 @@ namespace Terminal.Gui {
 			if (Bounds.Height == 0)
 				return 0;
 
-			return Math.Min (Math.Max(Bounds.Height - 1, minimumHeight - 1), searchset?.Count > 0 ? searchset.Count : isShow ? Math.Max (Bounds.Height - 1, minimumHeight - 1) : 0);
+			return Math.Min (Math.Max (Bounds.Height - 1, minimumHeight - 1), searchset?.Count > 0 ? searchset.Count : isShow ? Math.Max (Bounds.Height - 1, minimumHeight - 1) : 0);
 		}
 	}
 }

+ 4 - 4
Terminal.Gui/Views/FrameView.cs

@@ -60,7 +60,7 @@ namespace Terminal.Gui {
 		/// </summary>
 		/// <param name="frame">Frame.</param>
 		/// <param name="title">Title.</param>
-		/// /// <param name="views">Views.</param>
+		/// <param name="views">Views.</param>
 		public FrameView (Rect frame, ustring title, View [] views) : this (frame, title)
 		{
 			Initialize (title, frame, views);
@@ -157,7 +157,7 @@ namespace Terminal.Gui {
 			var scrRect = ViewToScreen (new Rect (0, 0, Frame.Width, Frame.Height));
 
 			if (!NeedDisplay.IsEmpty) {
-				Driver.SetAttribute (ColorScheme.Normal);
+				Driver.SetAttribute (GetNormalColor ());
 				Driver.DrawWindowFrame (scrRect, padding + 1, padding + 1, padding + 1, padding + 1, border: true, fill: true);
 			}
 
@@ -166,13 +166,13 @@ namespace Terminal.Gui {
 			Driver.Clip = savedClip;
 
 			ClearNeedsDisplay ();
-			Driver.SetAttribute (ColorScheme.Normal);
+			Driver.SetAttribute (GetNormalColor ());
 			Driver.DrawWindowFrame (scrRect, padding + 1, padding + 1, padding + 1, padding + 1, border: true, fill: false);
 
 			if (HasFocus)
 				Driver.SetAttribute (ColorScheme.HotNormal);
 			Driver.DrawWindowTitle (scrRect, Title, padding, padding, padding, padding);
-			Driver.SetAttribute (ColorScheme.Normal);
+			Driver.SetAttribute (GetNormalColor ());
 		}
 
 		/// <summary>

+ 2 - 2
Terminal.Gui/Views/GraphView.cs

@@ -99,7 +99,7 @@ namespace Terminal.Gui {
 				throw new Exception ($"{nameof(CellSize)} cannot be 0");
 			}
 
-			SetDriverColorToGraphColor (); 
+			SetDriverColorToGraphColor ();
 
 			Move (0, 0);
 
@@ -164,7 +164,7 @@ namespace Terminal.Gui {
 		/// </summary>
 		public void SetDriverColorToGraphColor ()
 		{
-			Driver.SetAttribute (GraphColor ?? ColorScheme.Normal);
+			Driver.SetAttribute (GraphColor ?? (GetNormalColor ()));
 		}
 
 		/// <summary>

+ 7 - 8
Terminal.Gui/Views/HexView.cs

@@ -162,7 +162,7 @@ namespace Terminal.Gui {
 				Driver.AddStr (string.Format ("{0:x8} ", displayStart + line * nblocks * 4));
 
 				currentAttribute = ColorScheme.HotNormal;
-				SetAttribute (ColorScheme.Normal);
+				SetAttribute (GetNormalColor ());
 
 				for (int block = 0; block < nblocks; block++) {
 					for (int b = 0; b < 4; b++) {
@@ -172,10 +172,10 @@ namespace Terminal.Gui {
 						if (offset + displayStart == position || edited)
 							SetAttribute (leftSide ? activeColor : trackingColor);
 						else
-							SetAttribute (ColorScheme.Normal);
+							SetAttribute (GetNormalColor ());
 
 						Driver.AddStr (offset >= n ? "  " : string.Format ("{0:x2}", value));
-						SetAttribute (ColorScheme.Normal);
+						SetAttribute (GetNormalColor ());
 						Driver.AddRune (' ');
 					}
 					Driver.AddStr (block + 1 == nblocks ? " " : "| ");
@@ -201,7 +201,7 @@ namespace Terminal.Gui {
 					if (offset + displayStart == position || edited)
 						SetAttribute (leftSide ? trackingColor : activeColor);
 					else
-						SetAttribute (ColorScheme.Normal);
+						SetAttribute (GetNormalColor ());
 
 					Driver.AddRune (c);
 				}
@@ -401,12 +401,11 @@ namespace Terminal.Gui {
 		/// <summary>
 		/// Get / Set the wished cursor when the field is focused
 		/// </summary>
-		public CursorVisibility DesiredCursorVisibility 
-		{ 
-			get => desiredCursorVisibility; 
+		public CursorVisibility DesiredCursorVisibility {
+			get => desiredCursorVisibility;
 			set {
 				if (desiredCursorVisibility != value && HasFocus) {
-					Application.Driver.SetCursorVisibility (value);		
+					Application.Driver.SetCursorVisibility (value);
 				}
 
 				desiredCursorVisibility = value;

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

@@ -94,8 +94,7 @@ namespace Terminal.Gui {
 		public override bool OnMouseEvent (MouseEvent mouseEvent)
 		{
 			MouseEventArgs args = new MouseEventArgs (mouseEvent);
-			OnMouseClick (args);
-			if (args.Handled)
+			if (OnMouseClick (args))
 				return true;
 			if (MouseEvent (mouseEvent))
 				return true;

+ 1 - 1
Terminal.Gui/Views/LineView.cs

@@ -73,7 +73,7 @@ namespace Terminal.Gui.Views {
 			base.Redraw (bounds);
 
 			Move (0, 0);
-			Driver.SetAttribute (ColorScheme.Normal);
+			Driver.SetAttribute (GetNormalColor ());
 
 			var hLineWidth = Math.Max (1, Rune.ColumnWidth (Driver.HLine));
 

+ 3 - 1
Terminal.Gui/Views/ListView.cs

@@ -334,7 +334,9 @@ namespace Terminal.Gui {
 			for (int row = 0; row < f.Height; row++, item++) {
 				bool isSelected = item == selected;
 
-				var newcolor = focused ? (isSelected ? ColorScheme.Focus : ColorScheme.Normal) : (isSelected ? ColorScheme.HotNormal : ColorScheme.Normal);
+				var newcolor = focused ? (isSelected ? ColorScheme.Focus : GetNormalColor ())
+						       : (isSelected ? ColorScheme.HotNormal : GetNormalColor ());
+
 				if (newcolor != current) {
 					Driver.SetAttribute (newcolor);
 					current = newcolor;

+ 13 - 11
Terminal.Gui/Views/Menu.cs

@@ -426,17 +426,18 @@ namespace Terminal.Gui {
 				if (index == current) return ColorScheme.Focus;
 				if (!item.IsEnabled ()) return ColorScheme.Disabled;
 			}
-			return ColorScheme.Normal;
+			return GetNormalColor ();
 		}
 
 		public override void Redraw (Rect bounds)
 		{
-			Driver.SetAttribute (ColorScheme.Normal);
+			Driver.SetAttribute (GetNormalColor ());
 			DrawFrame (bounds, padding: 0, fill: true);
 
 			for (int i = 0; i < barItems.Children.Length; i++) {
 				var item = barItems.Children [i];
-				Driver.SetAttribute (item == null ? ColorScheme.Normal : i == current ? ColorScheme.Focus : ColorScheme.Normal);
+				Driver.SetAttribute (item == null ? GetNormalColor ()
+					: i == current ? ColorScheme.Focus : GetNormalColor ());
 				if (item == null) {
 					Move (0, i + 1);
 					Driver.AddRune (Driver.LeftTee);
@@ -482,7 +483,7 @@ namespace Terminal.Gui {
 				else
 					DrawHotString (textToDraw,
 					       i == current ? ColorScheme.HotFocus : ColorScheme.HotNormal,
-					       i == current ? ColorScheme.Focus : ColorScheme.Normal);
+					       i == current ? ColorScheme.Focus : GetNormalColor ());
 
 				// The help string
 				var l = item.ShortcutTag.RuneCount == 0 ? item.Help.RuneCount : item.Help.RuneCount + item.ShortcutTag.RuneCount + 2;
@@ -905,7 +906,7 @@ namespace Terminal.Gui {
 		public override void Redraw (Rect bounds)
 		{
 			Move (0, 0);
-			Driver.SetAttribute (ColorScheme.Normal);
+			Driver.SetAttribute (GetNormalColor ());
 			for (int i = 0; i < Frame.Width; i++)
 				Driver.AddRune (' ');
 
@@ -918,13 +919,14 @@ namespace Terminal.Gui {
 				Attribute hotColor, normalColor;
 				if (i == selected && IsMenuOpen) {
 					hotColor = i == selected ? ColorScheme.HotFocus : ColorScheme.HotNormal;
-					normalColor = i == selected ? ColorScheme.Focus : ColorScheme.Normal;
+					normalColor = i == selected ? ColorScheme.Focus :
+						GetNormalColor ();
 				} else if (openedByAltKey) {
 					hotColor = ColorScheme.HotNormal;
-					normalColor = ColorScheme.Normal;
+					normalColor = GetNormalColor ();
 				} else {
-					hotColor = ColorScheme.Normal;
-					normalColor = ColorScheme.Normal;
+					hotColor = GetNormalColor ();
+					normalColor = GetNormalColor ();
 				}
 				DrawHotString (menu.Help.IsEmpty ? $" {menu.Title}  " : $" {menu.Title}  {menu.Help}  ", hotColor, normalColor);
 				pos += 1 + menu.TitleLength + (menu.Help.Length > 0 ? menu.Help.Length + 2 : 0) + 2;
@@ -989,7 +991,7 @@ namespace Terminal.Gui {
 		/// Virtual method that will invoke the <see cref="MenuOpening"/> event if it's defined.
 		/// </summary>
 		/// <param name="currentMenu">The current menu to be replaced.</param>
-		/// /// <returns>Returns the <see cref="MenuOpeningEventArgs"/></returns>
+		/// <returns>Returns the <see cref="MenuOpeningEventArgs"/></returns>
 		public virtual MenuOpeningEventArgs OnMenuOpening (MenuBarItem currentMenu)
 		{
 			var ev = new MenuOpeningEventArgs (currentMenu);
@@ -1175,7 +1177,7 @@ namespace Terminal.Gui {
 				}
 				LastFocused = lastFocused;
 				lastFocused = null;
-				if (LastFocused != null) {
+				if (LastFocused != null && LastFocused.CanFocus) {
 					if (!reopen) {
 						selected = -1;
 					}

+ 1 - 1
Terminal.Gui/Views/ProgressBar.cs

@@ -279,7 +279,7 @@ namespace Terminal.Gui {
 		{
 			DrawFrame ();
 
-			Driver.SetAttribute (ColorScheme.Normal);
+			Driver.SetAttribute (GetNormalColor ());
 
 			int fWidth = GetFrameWidth ();
 			if (isActivity) {

+ 9 - 8
Terminal.Gui/Views/RadioGroup.cs

@@ -17,7 +17,7 @@ namespace Terminal.Gui {
 		void Init (Rect rect, ustring [] radioLabels, int selected)
 		{
 			if (radioLabels == null) {
-				this.radioLabels = new List<ustring>();
+				this.radioLabels = new List<ustring> ();
 			} else {
 				this.radioLabels = radioLabels.ToList ();
 			}
@@ -63,7 +63,8 @@ namespace Terminal.Gui {
 		/// <param name="radioLabels">The radio labels; an array of strings that can contain hotkeys using an underscore before the letter.</param>
 		/// <param name="selected">The item to be selected, the value is clamped to the number of items.</param>		
 		public RadioGroup (int x, int y, ustring [] radioLabels, int selected = 0) :
-			this (MakeRect (x, y, radioLabels != null ? radioLabels.ToList() : null), radioLabels, selected) { }
+			this (MakeRect (x, y, radioLabels != null ? radioLabels.ToList () : null), radioLabels, selected)
+		{ }
 
 		/// <summary>
 		/// Gets or sets the <see cref="DisplayModeLayout"/> for this <see cref="RadioGroup"/>.
@@ -141,7 +142,7 @@ namespace Terminal.Gui {
 		/// </summary>
 		/// <value>The radio labels.</value>
 		public ustring [] RadioLabels {
-			get => radioLabels.ToArray();
+			get => radioLabels.ToArray ();
 			set {
 				var prevCount = radioLabels.Count;
 				radioLabels = value.ToList ();
@@ -184,7 +185,7 @@ namespace Terminal.Gui {
 		///<inheritdoc/>
 		public override void Redraw (Rect bounds)
 		{
-			Driver.SetAttribute (ColorScheme.Normal);
+			Driver.SetAttribute (GetNormalColor ());
 			Clear ();
 			for (int i = 0; i < radioLabels.Count; i++) {
 				switch (DisplayMode) {
@@ -195,8 +196,8 @@ namespace Terminal.Gui {
 					Move (horizontal [i].pos, 0);
 					break;
 				}
-				Driver.SetAttribute (ColorScheme.Normal);
-				Driver.AddStr (ustring.Make(new Rune[] { (i == selected ? Driver.Selected : Driver.UnSelected), ' '}));
+				Driver.SetAttribute (GetNormalColor ());
+				Driver.AddStr (ustring.Make (new Rune [] { (i == selected ? Driver.Selected : Driver.UnSelected), ' ' }));
 				DrawHotString (radioLabels [i], HasFocus && i == cursor, ColorScheme);
 			}
 		}
@@ -222,7 +223,7 @@ namespace Terminal.Gui {
 			/// <summary>
 			/// Gets the index of the item that was previously selected. -1 if there was no previous selection.
 			/// </summary>
-			public int PreviousSelectedItem { get;  }
+			public int PreviousSelectedItem { get; }
 
 			/// <summary>
 			/// Gets the index of the item that is now selected. -1 if there is no selection.
@@ -234,7 +235,7 @@ namespace Terminal.Gui {
 			/// </summary>
 			/// <param name="selectedItem"></param>
 			/// <param name="previousSelectedItem"></param>
-			public SelectedItemChangedArgs(int selectedItem, int previousSelectedItem)
+			public SelectedItemChangedArgs (int selectedItem, int previousSelectedItem)
 			{
 				PreviousSelectedItem = previousSelectedItem;
 				SelectedItem = selectedItem;

+ 53 - 4
Terminal.Gui/Views/ScrollBarView.cs

@@ -85,13 +85,22 @@ namespace Terminal.Gui {
 			X = isVertical ? Pos.Right (host) - 1 : Pos.Left (host);
 			Y = isVertical ? Pos.Top (host) : Pos.Bottom (host) - 1;
 			Host = host;
+			CanFocus = host.CanFocus;
+			Enabled = host.Enabled;
+			Visible = host.Visible;
+			Host.CanFocusChanged += Host_CanFocusChanged;
+			Host.EnabledChanged += Host_EnabledChanged;
+			Host.VisibleChanged += Host_VisibleChanged;
 			Host.SuperView.Add (this);
 			AutoHideScrollBars = true;
 			if (showBothScrollIndicator) {
 				OtherScrollBarView = new ScrollBarView (0, 0, !isVertical) {
 					ColorScheme = host.ColorScheme,
 					Host = host,
-					OtherScrollBarView = this,
+					CanFocus = host.CanFocus,
+					Enabled = host.Enabled,
+					Visible = host.Visible,
+					OtherScrollBarView = this
 				};
 				OtherScrollBarView.hosted = true;
 				OtherScrollBarView.X = OtherScrollBarView.IsVertical ? Pos.Right (host) - 1 : Pos.Left (host);
@@ -100,7 +109,7 @@ namespace Terminal.Gui {
 				OtherScrollBarView.showScrollIndicator = true;
 			}
 			ShowScrollIndicator = true;
-			contentBottomRightCorner = new View (" ");
+			contentBottomRightCorner = new View (" ") { Visible = host.Visible };
 			Host.SuperView.Add (contentBottomRightCorner);
 			contentBottomRightCorner.X = Pos.Right (host) - 1;
 			contentBottomRightCorner.Y = Pos.Bottom (host) - 1;
@@ -109,6 +118,36 @@ namespace Terminal.Gui {
 			contentBottomRightCorner.MouseClick += ContentBottomRightCorner_MouseClick;
 		}
 
+		private void Host_VisibleChanged ()
+		{
+			if (!Host.Visible) {
+				Visible = Host.Visible;
+				if (otherScrollBarView != null) {
+					otherScrollBarView.Visible = Visible;
+				}
+				contentBottomRightCorner.Visible = Visible;
+			} else {
+				ShowHideScrollBars ();
+			}
+		}
+
+		private void Host_EnabledChanged ()
+		{
+			Enabled = Host.Enabled;
+			if (otherScrollBarView != null) {
+				otherScrollBarView.Enabled = Enabled;
+			}
+			contentBottomRightCorner.Enabled = Enabled;
+		}
+
+		private void Host_CanFocusChanged ()
+		{
+			CanFocus = Host.CanFocus;
+			if (otherScrollBarView != null) {
+				otherScrollBarView.CanFocus = CanFocus;
+			}
+		}
+
 		void ContentBottomRightCorner_MouseClick (MouseEventArgs me)
 		{
 			if (me.MouseEvent.Flags == MouseFlags.WheeledDown || me.MouseEvent.Flags == MouseFlags.WheeledUp
@@ -322,6 +361,13 @@ namespace Terminal.Gui {
 			} else {
 				contentBottomRightCorner.Visible = false;
 			}
+			if (Host?.Visible == true && showScrollIndicator && !Visible) {
+				Visible = true;
+			}
+			if (Host?.Visible == true && otherScrollBarView != null && otherScrollBarView.showScrollIndicator
+				&& !otherScrollBarView.Visible) {
+				otherScrollBarView.Visible = true;
+			}
 			if (showScrollIndicator) {
 				Redraw (Bounds);
 			}
@@ -390,7 +436,7 @@ namespace Terminal.Gui {
 				return;
 			}
 
-			Driver.SetAttribute (ColorScheme.Normal);
+			Driver.SetAttribute (GetNormalColor ());
 
 			if ((vertical && Bounds.Height == 0) || (!vertical && Bounds.Width == 0)) {
 				return;
@@ -549,7 +595,10 @@ namespace Terminal.Gui {
 				return false;
 			}
 
-			if (Host != null && !Host.HasFocus) {
+			if (!Host.CanFocus) {
+				return true;
+			}
+			if (Host?.HasFocus == false) {
 				Host.SetFocus ();
 			}
 

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

@@ -250,7 +250,7 @@ namespace Terminal.Gui {
 		}
 
 		/// <summary>
-		/// /// Gets or sets the visibility for the vertical scroll indicator.
+		/// Gets or sets the visibility for the vertical scroll indicator.
 		/// </summary>
 		/// <value><c>true</c> if show vertical scroll indicator; otherwise, <c>false</c>.</value>
 		public bool ShowVerticalScrollIndicator {
@@ -281,7 +281,7 @@ namespace Terminal.Gui {
 		/// <inheritdoc/>
 		public override void Redraw (Rect region)
 		{
-			Driver.SetAttribute (ColorScheme.Normal);
+			Driver.SetAttribute (GetNormalColor ());
 			SetViewsNeedsDisplay ();
 			Clear ();
 
@@ -308,7 +308,7 @@ namespace Terminal.Gui {
 			if (ShowVerticalScrollIndicator && ShowHorizontalScrollIndicator) {
 				AddRune (Bounds.Width - 1, Bounds.Height - 1, ' ');
 			}
-			Driver.SetAttribute (ColorScheme.Normal);
+			Driver.SetAttribute (GetNormalColor ());
 		}
 
 		void ShowHideScrollBars ()

+ 61 - 9
Terminal.Gui/Views/StatusBar.cs

@@ -7,6 +7,7 @@
 // TODO:
 //   Add mouse support
 using System;
+using System.Collections.Generic;
 using NStack;
 
 namespace Terminal.Gui {
@@ -82,8 +83,6 @@ namespace Terminal.Gui {
 		/// <param name="items">A list of statusbar items.</param>
 		public StatusBar (StatusItem [] items) : base ()
 		{
-			Width = Dim.Fill ();
-			Height = 1;
 			Items = items;
 			CanFocus = false;
 			ColorScheme = Colors.Menu;
@@ -97,7 +96,17 @@ namespace Terminal.Gui {
 
 		private void StatusBar_Initialized (object sender, EventArgs e)
 		{
-			Y = SuperView.Frame.Height - 1;
+			if (SuperView.Frame == Rect.Empty) {
+				((Toplevel)SuperView).Loaded += StatusBar_Loaded;
+			} else {
+				Y = Math.Max (SuperView.Frame.Height - (Visible ? 1 : 0), 0);
+			}
+		}
+
+		private void StatusBar_Loaded ()
+		{
+			Y = Math.Max (SuperView.Frame.Height - (Visible ? 1 : 0), 0);
+			((Toplevel)SuperView).Loaded -= StatusBar_Loaded;
 		}
 
 		private Action<Application.ResizedEventArgs> Application_Resized ()
@@ -106,13 +115,26 @@ namespace Terminal.Gui {
 				X = 0;
 				Height = 1;
 				if (SuperView != null || SuperView is Toplevel) {
-					Y = SuperView.Frame.Height - (Visible ? 1 : 0);
-				} else {
-					//Y = Pos.Bottom (SuperView);
+					if (Frame.Y != SuperView.Frame.Height - (Visible ? 1 : 0)) {
+						Y = SuperView.Frame.Height - (Visible ? 1 : 0);
+					}
 				}
 			};
 		}
 
+		static ustring shortcutDelimiter = "-";
+		/// <summary>
+		/// Used for change the shortcut delimiter separator.
+		/// </summary>
+		public static ustring ShortcutDelimiter {
+			get => shortcutDelimiter;
+			set {
+				if (shortcutDelimiter != value) {
+					shortcutDelimiter = value == ustring.Empty ? " " : value;
+				}
+			}
+		}
+
 		Attribute ToggleScheme (Attribute scheme)
 		{
 			var result = scheme == ColorScheme.Normal ? ColorScheme.HotNormal : ColorScheme.Normal;
@@ -130,12 +152,12 @@ namespace Terminal.Gui {
 			//}
 
 			Move (0, 0);
-			Driver.SetAttribute (ColorScheme.Normal);
+			Driver.SetAttribute (GetNormalColor ());
 			for (int i = 0; i < Frame.Width; i++)
 				Driver.AddRune (' ');
 
 			Move (1, 0);
-			var scheme = ColorScheme.Normal;
+			var scheme = GetNormalColor ();
 			Driver.SetAttribute (scheme);
 			for (int i = 0; i < Items.Length; i++) {
 				var title = Items [i].Title.ToString ();
@@ -159,7 +181,7 @@ namespace Terminal.Gui {
 		{
 			foreach (var item in Items) {
 				if (kb.Key == item.Shortcut) {
-					item.Action?.Invoke ();
+					Run (item.Action);
 					return true;
 				}
 			}
@@ -176,6 +198,7 @@ namespace Terminal.Gui {
 			for (int i = 0; i < Items.Length; i++) {
 				if (me.X >= pos && me.X < pos + GetItemTitleLength (Items [i].Title)) {
 					Run (Items [i].Action);
+					break;
 				}
 				pos += GetItemTitleLength (Items [i].Title) + 3;
 			}
@@ -223,5 +246,34 @@ namespace Terminal.Gui {
 
 			return base.OnEnter (view);
 		}
+
+		/// <summary>
+		/// Inserts a <see cref="StatusItem"/> in the specified index of <see cref="Items"/>.
+		/// </summary>
+		/// <param name="index">The zero-based index at which item should be inserted.</param>
+		/// <param name="item">The item to insert.</param>
+		public void AddItemAt (int index, StatusItem item)
+		{
+			var itemsList = new List<StatusItem> (Items);
+			itemsList.Insert (index, item);
+			Items = itemsList.ToArray ();
+			SetNeedsDisplay ();
+		}
+
+		/// <summary>
+		/// Removes a <see cref="StatusItem"/> at specified index of <see cref="Items"/>.
+		/// </summary>
+		/// <param name="index">The zero-based index of the item to remove.</param>
+		/// <returns>The <see cref="StatusItem"/> removed.</returns>
+		public StatusItem RemoveItem (int index)
+		{
+			var itemsList = new List<StatusItem> (Items);
+			var item = itemsList [index];
+			itemsList.RemoveAt (index);
+			Items = itemsList.ToArray ();
+			SetNeedsDisplay ();
+
+			return item;
+		}
 	}
 }

+ 4 - 4
Terminal.Gui/Views/TabView.cs

@@ -165,7 +165,7 @@ namespace Terminal.Gui {
 		public override void Redraw (Rect bounds)
 		{
 			Move (0, 0);
-			Driver.SetAttribute (ColorScheme.Normal);
+			Driver.SetAttribute (GetNormalColor ());
 
 			if (Style.ShowBorder) {
 
@@ -486,7 +486,7 @@ namespace Terminal.Gui {
 
 				var tabLocations = host.CalculateViewport (bounds).ToArray ();
 				var width = bounds.Width;
-				Driver.SetAttribute (ColorScheme.Normal);
+				Driver.SetAttribute (GetNormalColor ());
 
 				if (host.Style.ShowTopLine) {
 					RenderOverline (tabLocations, width);
@@ -495,7 +495,7 @@ namespace Terminal.Gui {
 				RenderTabLine (tabLocations, width);
 
 				RenderUnderline (tabLocations, width);
-				Driver.SetAttribute (ColorScheme.Normal);
+				Driver.SetAttribute (GetNormalColor ());
 
 
 			}
@@ -589,7 +589,7 @@ namespace Terminal.Gui {
 
 
 					Driver.AddStr (toRender.TextToRender);
-					Driver.SetAttribute (ColorScheme.Normal);
+					Driver.SetAttribute (GetNormalColor ());
 
 					if (toRender.IsSelected) {
 						Driver.AddRune (Driver.VLine);

+ 8 - 6
Terminal.Gui/Views/TableView.cs

@@ -199,7 +199,7 @@ namespace Terminal.Gui {
 			// What columns to render at what X offset in viewport
 			var columnsToRender = CalculateViewport (bounds).ToArray ();
 
-			Driver.SetAttribute (ColorScheme.Normal);
+			Driver.SetAttribute (GetNormalColor ());
 
 			//invalidate current row (prevents scrolling around leaving old characters in the frame
 			Driver.AddStr (new string (' ', bounds.Width));
@@ -253,7 +253,7 @@ namespace Terminal.Gui {
 		private void ClearLine (int row, int width)
 		{
 			Move (0, row);
-			Driver.SetAttribute (ColorScheme.Normal);
+			Driver.SetAttribute (GetNormalColor ());
 			Driver.AddStr (new string (' ', width));
 		}
 
@@ -391,7 +391,8 @@ namespace Terminal.Gui {
 
 			//start by clearing the entire line
 			Move (0, row);
-			Driver.SetAttribute (FullRowSelect && IsSelected (0, rowToRender) ? rowScheme.HotFocus : rowScheme.Normal);
+			Driver.SetAttribute (FullRowSelect && IsSelected (0, rowToRender) ? rowScheme.HotFocus
+				: Enabled ? rowScheme.Normal : rowScheme.Disabled);
 			Driver.AddStr (new string (' ', Bounds.Width));
 
 			// Render cells for each visible header for the current row
@@ -431,7 +432,7 @@ namespace Terminal.Gui {
 					scheme = rowScheme;
 				}
 
-				var cellColor = isSelectedCell ? scheme.HotFocus : scheme.Normal;
+				var cellColor = isSelectedCell ? scheme.HotFocus : Enabled ? scheme.Normal : scheme.Disabled;
 
 				var render = TruncateOrPad (val, representation, current.Width, colStyle);
 
@@ -442,12 +443,13 @@ namespace Terminal.Gui {
 								
 				// Reset color scheme to normal for drawing separators if we drew text with custom scheme
 				if (scheme != rowScheme) {
-					Driver.SetAttribute (isSelectedCell ? rowScheme.HotFocus : rowScheme.Normal);
+					Driver.SetAttribute (isSelectedCell ? rowScheme.HotFocus
+						: Enabled ? rowScheme.Normal : rowScheme.Disabled);
 				}
 
 				// If not in full row select mode always, reset color scheme to normal and render the vertical line (or space) at the end of the cell
 				if (!FullRowSelect)
-					Driver.SetAttribute (rowScheme.Normal);
+					Driver.SetAttribute (Enabled ? rowScheme.Normal : rowScheme.Disabled);
 
 				RenderSeparator (current.X - 1, row, false);
 

+ 13 - 8
Terminal.Gui/Views/TextField.cs

@@ -83,7 +83,7 @@ namespace Terminal.Gui {
 
 		void Initialize (ustring text, int w)
 		{
-			Initialize ();
+			Height = 1;
 
 			if (text == null)
 				text = "";
@@ -96,11 +96,6 @@ namespace Terminal.Gui {
 			WantMousePositionReports = true;
 		}
 
-		void Initialize ()
-		{
-			Height = 1;
-		}
-
 		///<inheritdoc/>
 		public override bool OnLeave (View view)
 		{
@@ -221,7 +216,7 @@ namespace Terminal.Gui {
 			int col = 0;
 			int width = Frame.Width + OffSetBackground ();
 			var tcount = text.Count;
-			var roc = Colors.Menu.Disabled;
+			var roc = GetReadOnlyColor ();
 			for (int idx = p; idx < tcount; idx++) {
 				var rune = text [idx];
 				var cols = Rune.ColumnWidth (rune);
@@ -229,8 +224,10 @@ namespace Terminal.Gui {
 					Driver.SetAttribute (selColor);
 				} else if (ReadOnly) {
 					Driver.SetAttribute (idx >= start && length > 0 && idx < start + length ? selColor : roc);
-				} else if (!HasFocus) {
+				} else if (!HasFocus && Enabled) {
 					Driver.SetAttribute (ColorScheme.Focus);
+				} else if (!Enabled) {
+					Driver.SetAttribute (roc);
 				} else {
 					Driver.SetAttribute (idx >= start && length > 0 && idx < start + length ? selColor : ColorScheme.Focus);
 				}
@@ -253,6 +250,14 @@ namespace Terminal.Gui {
 			PositionCursor ();
 		}
 
+		Attribute GetReadOnlyColor ()
+		{
+			if (ColorScheme.Disabled.Foreground == ColorScheme.Focus.Background) {
+				return new Attribute (ColorScheme.Focus.Foreground, ColorScheme.Focus.Background);
+			}
+			return new Attribute (ColorScheme.Disabled.Foreground, ColorScheme.Focus.Background);
+		}
+
 		void Adjust ()
 		{
 			int offB = OffSetBackground ();

+ 48 - 14
Terminal.Gui/Views/TextView.cs

@@ -303,10 +303,7 @@ namespace Terminal.Gui {
 				if (rune == '\t') {
 					size += tabWidth + 1;
 				}
-				if (size > width) {
-					if (end == t.Count) {
-						col++;
-					}
+				if (size >= width) {
 					break;
 				} else if (end < t.Count && col > 0 && start < end && col == start) {
 					break;
@@ -898,6 +895,7 @@ namespace Terminal.Gui {
 		bool wordWrap;
 		WordWrapManager wrapManager;
 		bool continuousFind;
+		int bottomOffset, rightOffset;
 		int tabWidth = 4;
 		bool allowsTab = true;
 		bool allowsReturn = true;
@@ -1128,13 +1126,31 @@ namespace Terminal.Gui {
 		/// The bottom offset needed to use a horizontal scrollbar or for another reason.
 		/// This is only needed with the keyboard navigation.
 		/// </summary>
-		public int BottomOffset { get; set; }
+		public int BottomOffset {
+			get => bottomOffset;
+			set {
+				if (currentRow == Lines - 1 && bottomOffset > 0 && value == 0) {
+					topRow = Math.Max (topRow - bottomOffset, 0);
+				}
+				bottomOffset = value;
+				Adjust ();
+			}
+		}
 
 		/// <summary>
 		/// The right offset needed to use a vertical scrollbar or for another reason.
 		/// This is only needed with the keyboard navigation.
 		/// </summary>
-		public int RightOffset { get; set; }
+		public int RightOffset {
+			get => rightOffset;
+			set {
+				if (currentColumn == GetCurrentLine ().Count && rightOffset > 0 && value == 0) {
+					leftColumn = Math.Max (leftColumn - rightOffset, 0);
+				}
+				rightOffset = value;
+				Adjust ();
+			}
+		}
 
 		/// <summary>
 		/// Gets or sets a value indicating whether pressing ENTER in a <see cref="TextView"/>
@@ -1236,22 +1252,27 @@ namespace Terminal.Gui {
 			return SelectedText.Length;
 		}
 
-		CursorVisibility savedCursorVisibility = CursorVisibility.Default;
+		CursorVisibility savedCursorVisibility;
 
 		void SaveCursorVisibility ()
 		{
 			if (desiredCursorVisibility != CursorVisibility.Invisible) {
-				savedCursorVisibility = desiredCursorVisibility;
+				if (savedCursorVisibility == 0) {
+					savedCursorVisibility = desiredCursorVisibility;
+				}
 				DesiredCursorVisibility = CursorVisibility.Invisible;
 			}
 		}
 
 		void ResetCursorVisibility ()
 		{
-			if (savedCursorVisibility != desiredCursorVisibility) {
+			if (savedCursorVisibility == 0) {
+				savedCursorVisibility = desiredCursorVisibility;
+			}
+			if (savedCursorVisibility != desiredCursorVisibility && !HasFocus) {
 				DesiredCursorVisibility = savedCursorVisibility;
 				savedCursorVisibility = CursorVisibility.Default;
-			} else {
+			} else if (desiredCursorVisibility != CursorVisibility.Underline) {
 				DesiredCursorVisibility = CursorVisibility.Underline;
 			}
 		}
@@ -1309,6 +1330,10 @@ namespace Terminal.Gui {
 		/// </summary>
 		public override void PositionCursor ()
 		{
+			if (!CanFocus || !Enabled) {
+				return;
+			}
+
 			if (selecting) {
 				var minRow = Math.Min (Math.Max (Math.Min (selectionStartRow, currentRow) - topRow, 0), Frame.Height);
 				var maxRow = Math.Min (Math.Max (Math.Max (selectionStartRow, currentRow) - topRow, 0), Frame.Height);
@@ -1325,10 +1350,13 @@ namespace Terminal.Gui {
 					if (line [idx] == '\t') {
 						cols += TabWidth + 1;
 					}
-					TextModel.SetCol (ref col, Frame.Width, cols);
+					if (!TextModel.SetCol (ref col, Frame.Width, cols)) {
+						col = currentColumn;
+						break;
+					}
 				}
 			}
-			if ((col >= leftColumn || col < Frame.Width)
+			if (col >= leftColumn && currentColumn - leftColumn + RightOffset < Frame.Width
 				&& topRow <= currentRow && currentRow - topRow + BottomOffset < Frame.Height) {
 				ResetCursorVisibility ();
 				Move (col, currentRow - topRow);
@@ -1351,7 +1379,7 @@ namespace Terminal.Gui {
 		/// </summary>
 		protected virtual void ColorNormal ()
 		{
-			Driver.SetAttribute (ColorScheme.Normal);
+			Driver.SetAttribute (GetNormalColor ());
 		}
 
 		/// <summary>
@@ -1363,7 +1391,7 @@ namespace Terminal.Gui {
 		/// <param name="idx"></param>
 		protected virtual void ColorNormal (List<Rune> line, int idx)
 		{
-			Driver.SetAttribute (ColorScheme.Normal);
+			Driver.SetAttribute (GetNormalColor ());
 		}
 
 		/// <summary>
@@ -1400,6 +1428,7 @@ namespace Terminal.Gui {
 			get => isReadOnly;
 			set {
 				isReadOnly = value;
+				SetNeedsDisplay ();
 			}
 		}
 
@@ -1416,6 +1445,7 @@ namespace Terminal.Gui {
 				}
 
 				desiredCursorVisibility = value;
+				SetNeedsDisplay ();
 			}
 		}
 
@@ -1970,6 +2000,10 @@ namespace Terminal.Gui {
 		///<inheritdoc/>
 		public override bool ProcessKey (KeyEvent kb)
 		{
+			if (!CanFocus) {
+				return true;
+			}
+
 			int restCount;
 			List<Rune> rest;
 

+ 1 - 1
Terminal.Gui/Views/TreeView.cs

@@ -377,7 +377,7 @@ namespace Terminal.Gui {
 
 					// Else clear the line to prevent stale symbols due to scrolling etc
 					Move (0, line);
-					Driver.SetAttribute (ColorScheme.Normal);
+					Driver.SetAttribute (GetNormalColor ());
 					Driver.AddStr (new string (' ', bounds.Width));
 				}
 

+ 195 - 30
Terminal.Gui/Windows/FileDialog.cs

@@ -19,6 +19,7 @@ namespace Terminal.Gui {
 	internal class DirListView : View {
 		int top, selected;
 		DirectoryInfo dirInfo;
+		FileSystemWatcher watcher;
 		List<(string, bool, bool)> infos;
 		internal bool canChooseFiles = true;
 		internal bool canChooseDirectories = false;
@@ -39,7 +40,7 @@ namespace Terminal.Gui {
 			if (allowedFileTypes == null)
 				return true;
 			foreach (var ft in allowedFileTypes)
-				if (fsi.Name.EndsWith (ft))
+				if (fsi.Name.EndsWith (ft) || ft == ".*")
 					return true;
 			return false;
 		}
@@ -49,6 +50,21 @@ namespace Terminal.Gui {
 			bool valid = false;
 			try {
 				dirInfo = new DirectoryInfo (value == null ? directory.ToString () : value.ToString ());
+				watcher = new FileSystemWatcher (dirInfo.FullName);
+				watcher.NotifyFilter = NotifyFilters.Attributes
+				 | NotifyFilters.CreationTime
+				 | NotifyFilters.DirectoryName
+				 | NotifyFilters.FileName
+				 | NotifyFilters.LastAccess
+				 | NotifyFilters.LastWrite
+				 | NotifyFilters.Security
+				 | NotifyFilters.Size;
+				watcher.Changed += Watcher_Changed;
+				watcher.Created += Watcher_Changed;
+				watcher.Deleted += Watcher_Changed;
+				watcher.Renamed += Watcher_Changed;
+				watcher.Error += Watcher_Error;
+				watcher.EnableRaisingEvents = true;
 				infos = (from x in dirInfo.GetFileSystemInfos ()
 					 where IsAllowed (x) && (!canChooseFiles ? x.Attributes.HasFlag (FileAttributes.Directory) : true)
 					 orderby (!x.Attributes.HasFlag (FileAttributes.Directory)) + x.Name
@@ -62,6 +78,7 @@ namespace Terminal.Gui {
 				case DirectoryNotFoundException _:
 				case ArgumentException _:
 					dirInfo = null;
+					watcher = null;
 					infos.Clear ();
 					valid = true;
 					break;
@@ -77,6 +94,16 @@ namespace Terminal.Gui {
 			return valid;
 		}
 
+		void Watcher_Error (object sender, ErrorEventArgs e)
+		{
+			Application.MainLoop.Invoke (() => Reload ());
+		}
+
+		void Watcher_Changed (object sender, FileSystemEventArgs e)
+		{
+			Application.MainLoop.Invoke (() => Reload ());
+		}
+
 		ustring directory;
 		public ustring Directory {
 			get => directory;
@@ -168,13 +195,11 @@ namespace Terminal.Gui {
 			return true;
 		}
 
-		private void UnMarkAll ()
+		void UnMarkAll ()
 		{
-			if (allowsMultipleSelection && infos.Count > 0) {
-				for (int i = 0; i < infos.Count; i++) {
-					if (infos [i].Item3) {
-						infos [i] = (infos [i].Item1, infos [i].Item2, false);
-					}
+			for (int i = 0; i < infos.Count; i++) {
+				if (infos [i].Item3) {
+					infos [i] = (infos [i].Item1, infos [i].Item2, false);
 				}
 			}
 		}
@@ -221,7 +246,8 @@ namespace Terminal.Gui {
 			for (int row = 0; row < f.Height; row++, item++) {
 				bool isSelected = item == selected;
 				Move (0, row);
-				var newcolor = focused ? (isSelected ? ColorScheme.HotNormal : ColorScheme.Focus) : ColorScheme.Focus;
+				var newcolor = focused ? (isSelected ? ColorScheme.HotNormal : ColorScheme.Focus)
+					: Enabled ? ColorScheme.Focus : ColorScheme.Disabled;
 				if (newcolor != current) {
 					Driver.SetAttribute (newcolor);
 					current = newcolor;
@@ -250,11 +276,13 @@ namespace Terminal.Gui {
 		public Action<ustring> DirectoryChanged { get; set; }
 		public Action<ustring> FileChanged { get; set; }
 
+		string splitString = ",";
+
 		void OnSelectionChanged ()
 		{
 			if (allowsMultipleSelection) {
 				if (FilePaths.Count > 0) {
-					FileChanged?.Invoke (string.Join (", ", GetFilesName (FilePaths)));
+					FileChanged?.Invoke (string.Join (splitString, GetFilesName (FilePaths)));
 				} else {
 					FileChanged?.Invoke (infos [selected].Item2 && !canChooseDirectories ? "" : Path.GetFileName (infos [selected].Item1));
 				}
@@ -275,6 +303,46 @@ namespace Terminal.Gui {
 			return filesName;
 		}
 
+		public bool GetValidFilesName (string files, out string result)
+		{
+			result = string.Empty;
+			if (infos?.Count == 0) {
+				return false;
+			}
+
+			var valid = true;
+			IReadOnlyList<string> filesList = new List<string> (files.Split (splitString.ToArray (), StringSplitOptions.None));
+			var filesName = new List<string> ();
+			UnMarkAll ();
+
+			foreach (var file in filesList) {
+				if (!allowsMultipleSelection && filesName.Count > 0) {
+					break;
+				}
+				var idx = infos.IndexOf (x => x.Item1.IndexOf (file, StringComparison.OrdinalIgnoreCase) >= 0);
+				if (idx > -1 && string.Equals (infos [idx].Item1, file, StringComparison.OrdinalIgnoreCase)) {
+					if (canChooseDirectories && !canChooseFiles && !infos [idx].Item2) {
+						valid = false;
+					}
+					if (allowsMultipleSelection && !infos [idx].Item3) {
+						infos [idx] = (infos [idx].Item1, infos [idx].Item2, true);
+					}
+					if (!allowsMultipleSelection) {
+						selected = idx;
+					}
+					filesName.Add (Path.GetFileName (infos [idx].Item1));
+				} else if (idx > -1) {
+					valid = false;
+					filesName.Add (Path.GetFileName (file));
+				}
+			}
+			result = string.Join (splitString, filesName);
+			if (string.IsNullOrEmpty (result)) {
+				valid = false;
+			}
+			return valid;
+		}
+
 		public override bool ProcessKey (KeyEvent keyEvent)
 		{
 			switch (keyEvent.Key) {
@@ -378,7 +446,7 @@ namespace Terminal.Gui {
 			}
 		}
 
-		internal bool ExecuteSelection ()
+		internal bool ExecuteSelection (bool navigateFolder = true)
 		{
 			if (infos.Count == 0) {
 				return false;
@@ -388,8 +456,11 @@ namespace Terminal.Gui {
 			if (isDir) {
 				Directory = Path.GetFullPath (Path.Combine (Path.GetFullPath (Directory.ToString ()), infos [selected].Item1));
 				DirectoryChanged?.Invoke (Directory);
+				if (canChooseDirectories && !navigateFolder) {
+					return true;
+				}
 			} else {
-				FileChanged?.Invoke (infos [selected].Item1);
+				OnSelectionChanged ();
 				if (canChooseFiles) {
 					// Ensures that at least one file is selected.
 					if (FilePaths.Count == 0)
@@ -483,11 +554,14 @@ namespace Terminal.Gui {
 		Label nameFieldLabel, message, nameDirLabel;
 		TextField dirEntry, nameEntry;
 		internal DirListView dirListView;
+		ComboBox cmbAllowedTypes;
 
 		/// <summary>
 		/// Initializes a new <see cref="FileDialog"/>.
 		/// </summary>
-		public FileDialog () : this (title: string.Empty, prompt: string.Empty, nameFieldLabel: string.Empty, message: string.Empty) { }
+		public FileDialog () : this (title: string.Empty, prompt: string.Empty,
+			nameFieldLabel: string.Empty, message: string.Empty)
+		{ }
 
 		/// <summary>
 		/// Initializes a new instance of <see cref="FileDialog"/>
@@ -496,8 +570,9 @@ namespace Terminal.Gui {
 		/// <param name="prompt">The prompt.</param>
 		/// <param name="nameFieldLabel">The name of the file field label..</param>
 		/// <param name="message">The message.</param>
-		public FileDialog (ustring title, ustring prompt, ustring nameFieldLabel, ustring message)
-			: this (title, prompt, ustring.Empty, nameFieldLabel, message) { }
+		/// <param name="allowedTypes">The allowed types.</param>
+		public FileDialog (ustring title, ustring prompt, ustring nameFieldLabel, ustring message, List<string> allowedTypes = null)
+			: this (title, prompt, ustring.Empty, nameFieldLabel, message, allowedTypes) { }
 
 		/// <summary>
 		/// Initializes a new instance of <see cref="FileDialog"/>
@@ -505,8 +580,9 @@ namespace Terminal.Gui {
 		/// <param name="title">The title.</param>
 		/// <param name="prompt">The prompt.</param>
 		/// <param name="message">The message.</param>
-
-		public FileDialog (ustring title, ustring prompt, ustring message) : this (title, prompt, ustring.Empty, message) { }
+		/// <param name="allowedTypes">The allowed types.</param>
+		public FileDialog (ustring title, ustring prompt, ustring message, List<string> allowedTypes)
+			: this (title, prompt, ustring.Empty, message, allowedTypes) { }
 
 		/// <summary>
 		/// Initializes a new instance of <see cref="FileDialog"/>
@@ -516,7 +592,9 @@ namespace Terminal.Gui {
 		/// <param name="nameDirLabel">The name of the directory field label.</param>
 		/// <param name="nameFieldLabel">The name of the file field label..</param>
 		/// <param name="message">The message.</param>
-		public FileDialog (ustring title, ustring prompt, ustring nameDirLabel, ustring nameFieldLabel, ustring message) : base (title)//, Driver.Cols - 20, Driver.Rows - 5, null)
+		/// <param name="allowedTypes">The allowed types.</param>
+		public FileDialog (ustring title, ustring prompt, ustring nameDirLabel, ustring nameFieldLabel, ustring message,
+			List<string> allowedTypes = null) : base (title)//, Driver.Cols - 20, Driver.Rows - 5, null)
 		{
 			this.message = new Label (message) {
 				X = 1,
@@ -550,10 +628,22 @@ namespace Terminal.Gui {
 			nameEntry = new TextField ("") {
 				X = Pos.Left (dirEntry),
 				Y = 3 + msgLines,
-				Width = Dim.Fill () - 1
+				Width = Dim.Percent (70, true)
 			};
 			Add (this.nameFieldLabel, nameEntry);
 
+			cmbAllowedTypes = new ComboBox () {
+				X = Pos.Right (nameEntry) + 2,
+				Y = Pos.Top (nameEntry),
+				Width = Dim.Fill (1),
+				Height = allowedTypes != null ? allowedTypes.Count + 1 : 1,
+				Text = allowedTypes?.Count > 0 ? allowedTypes [0] : string.Empty,
+				ReadOnly = true
+			};
+			cmbAllowedTypes.SetSource (allowedTypes ?? new List<string> ());
+			cmbAllowedTypes.OpenSelectedItem += (e) => AllowedFileTypes = cmbAllowedTypes.Text.ToString ().Split (';');
+			Add (cmbAllowedTypes);
+
 			dirListView = new DirListView (this) {
 				X = 1,
 				Y = 3 + msgLines + 2,
@@ -562,21 +652,40 @@ namespace Terminal.Gui {
 			};
 			DirectoryPath = Path.GetFullPath (Environment.CurrentDirectory);
 			Add (dirListView);
+
+			AllowedFileTypes = cmbAllowedTypes.Text.ToString ().Split (';');
 			dirListView.DirectoryChanged = (dir) => { nameEntry.Text = ustring.Empty; dirEntry.Text = dir; };
 			dirListView.FileChanged = (file) => nameEntry.Text = file == ".." ? "" : file;
 			dirListView.SelectedChanged = (file) => nameEntry.Text = file.Item1 == ".." ? "" : file.Item1;
 			this.cancel = new Button ("Cancel");
 			this.cancel.Clicked += () => {
-				canceled = true;
-				Application.RequestStop ();
+				Cancel ();
 			};
 			AddButton (cancel);
 
 			this.prompt = new Button (prompt.IsEmpty ? "Ok" : prompt) {
 				IsDefault = true,
-				CanFocus = nameEntry.Text.IsEmpty ? false : true
+				Enabled = nameEntry.Text.IsEmpty ? false : true
 			};
 			this.prompt.Clicked += () => {
+				if (this is OpenDialog) {
+					if (!dirListView.GetValidFilesName (nameEntry.Text.ToString (), out string res)) {
+						nameEntry.Text = res;
+						dirListView.SetNeedsDisplay ();
+						return;
+					}
+					if (!dirListView.canChooseDirectories && !dirListView.ExecuteSelection (false)) {
+						return;
+					}
+				} else if (this is SaveDialog) {
+					var name = nameEntry.Text.ToString ();
+					if (FilePath.IsEmpty || name.Split (',').Length > 1) {
+						return;
+					}
+					var ext = name.EndsWith (cmbAllowedTypes.Text.ToString ())
+						? "" : cmbAllowedTypes.Text.ToString ();
+					FilePath = Path.Combine (FilePath.ToString (), $"{name}{ext}");
+				}
 				canceled = false;
 				Application.RequestStop ();
 			};
@@ -584,9 +693,9 @@ namespace Terminal.Gui {
 
 			nameEntry.TextChanged += (e) => {
 				if (nameEntry.Text.IsEmpty) {
-					this.prompt.CanFocus = false;
+					this.prompt.Enabled = false;
 				} else {
-					this.prompt.CanFocus = true;
+					this.prompt.Enabled = true;
 				}
 			};
 
@@ -595,6 +704,18 @@ namespace Terminal.Gui {
 
 			// On success, we will set this to false.
 			canceled = true;
+
+			KeyPress += (e) => {
+				if (e.KeyEvent.Key == Key.Esc) {
+					Cancel ();
+					e.Handled = true;
+				}
+			};
+			void Cancel ()
+			{
+				canceled = true;
+				Application.RequestStop ();
+			}
 		}
 
 		internal bool canceled;
@@ -603,7 +724,7 @@ namespace Terminal.Gui {
 		public override void WillPresent ()
 		{
 			base.WillPresent ();
-			//SetFocus (nameEntry);
+			dirListView.SetFocus ();
 		}
 
 		//protected override void Dispose (bool disposing)
@@ -689,7 +810,6 @@ namespace Terminal.Gui {
 			set => dirListView.AllowedFileTypes = value;
 		}
 
-
 		/// <summary>
 		/// Gets or sets a value indicating whether this <see cref="FileDialog"/> allows the file to be saved with a different extension
 		/// </summary>
@@ -736,7 +856,9 @@ namespace Terminal.Gui {
 		/// </summary>
 		/// <param name="title">The title.</param>
 		/// <param name="message">The message.</param>
-		public SaveDialog (ustring title, ustring message) : base (title, prompt: "Save", nameFieldLabel: "Save as:", message: message) { }
+		/// <param name="allowedTypes">The allowed types.</param>
+		public SaveDialog (ustring title, ustring message, List<string> allowedTypes = null)
+			: base (title, prompt: "Save", nameFieldLabel: "Save as:", message: message, allowedTypes) { }
 
 		/// <summary>
 		/// Gets the name of the file the user selected for saving, or null
@@ -764,13 +886,33 @@ namespace Terminal.Gui {
 	/// <para>
 	///   To use, create an instance of <see cref="OpenDialog"/>, and pass it to
 	///   <see cref="Application.Run(Func{Exception, bool})"/>. This will run the dialog modally,
-	///   and when this returns, the list of filds will be available on the <see cref="FilePaths"/> property.
+	///   and when this returns, the list of files will be available on the <see cref="FilePaths"/> property.
 	/// </para>
 	/// <para>
 	/// To select more than one file, users can use the spacebar, or control-t.
 	/// </para>
 	/// </remarks>
 	public class OpenDialog : FileDialog {
+		OpenMode openMode;
+
+		/// <summary>
+		/// Determine which <see cref="System.IO"/> type to open.
+		/// </summary>
+		public enum OpenMode {
+			/// <summary>
+			/// Opens only file or files.
+			/// </summary>
+			File,
+			/// <summary>
+			/// Opens only directory or directories.
+			/// </summary>
+			Directory,
+			/// <summary>
+			/// Opens files and directories.
+			/// </summary>
+			Mixed
+		}
+
 		/// <summary>
 		/// Initializes a new <see cref="OpenDialog"/>.
 		/// </summary>
@@ -779,10 +921,30 @@ namespace Terminal.Gui {
 		/// <summary>
 		/// Initializes a new <see cref="OpenDialog"/>.
 		/// </summary>
-		/// <param name="title"></param>
-		/// <param name="message"></param>
-		public OpenDialog (ustring title, ustring message) : base (title, prompt: "Open", nameFieldLabel: "Open", message: message)
+		/// <param name="title">The title.</param>
+		/// <param name="message">The message.</param>
+		/// <param name="allowedTypes">The allowed types.</param>
+		/// <param name="openMode">The open mode.</param>
+		public OpenDialog (ustring title, ustring message, List<string> allowedTypes = null, OpenMode openMode = OpenMode.File) : base (title,
+			prompt: openMode == OpenMode.File ? "Open" : openMode == OpenMode.Directory ? "Select folder" : "Select Mixed",
+			nameFieldLabel: "Open", message: message, allowedTypes)
 		{
+			this.openMode = openMode;
+			switch (openMode) {
+			case OpenMode.File:
+				CanChooseFiles = true;
+				CanChooseDirectories = false;
+				break;
+			case OpenMode.Directory:
+				CanChooseFiles = false;
+				CanChooseDirectories = true;
+				break;
+			case OpenMode.Mixed:
+				CanChooseFiles = true;
+				CanChooseDirectories = true;
+				AllowsMultipleSelection = true;
+				break;
+			}
 		}
 
 		/// <summary>
@@ -816,6 +978,9 @@ namespace Terminal.Gui {
 		public bool AllowsMultipleSelection {
 			get => dirListView.allowsMultipleSelection;
 			set {
+				if (!value && openMode == OpenMode.Mixed) {
+					return;
+				}
 				dirListView.allowsMultipleSelection = value;
 				dirListView.Reload ();
 			}

+ 25 - 25
UICatalog/Scenarios/DynamicMenuBar.cs

@@ -515,10 +515,10 @@ namespace UICatalog {
 
 				_currentEditMenuBarItem = menuItem;
 				_frmMenuDetails.EditMenuBarItem (menuItem);
-				var f = _btnOk.CanFocus == _frmMenuDetails.CanFocus;
+				var f = _btnOk.Enabled == _frmMenuDetails.Enabled;
 				if (!f) {
-					_btnOk.CanFocus = _frmMenuDetails.CanFocus;
-					_btnCancel.CanFocus = _frmMenuDetails.CanFocus;
+					_btnOk.Enabled = _frmMenuDetails.Enabled;
+					_btnCancel.Enabled = _frmMenuDetails.Enabled;
 				}
 			}
 
@@ -624,7 +624,7 @@ namespace UICatalog {
 			}
 
 
-			_frmMenuDetails.Initialized += (s, e) => _frmMenuDetails.CanFocus = false;
+			//_frmMenuDetails.Initialized += (s, e) => _frmMenuDetails.Enabled = false;
 		}
 	}
 
@@ -790,19 +790,19 @@ namespace UICatalog {
 				if (_ckbIsTopLevel.Checked) {
 					_ckbSubMenu.Checked = false;
 					_ckbSubMenu.SetNeedsDisplay ();
-					_txtHelp.CanFocus = true;
-					_txtAction.CanFocus = true;
-					_txtShortcut.CanFocus = !_ckbIsTopLevel.Checked && !_ckbSubMenu.Checked;
+					_txtHelp.Enabled = true;
+					_txtAction.Enabled = true;
+					_txtShortcut.Enabled = !_ckbIsTopLevel.Checked && !_ckbSubMenu.Checked;
 				} else {
 					if (_menuItem == null && !hasParent || _menuItem.Parent == null) {
 						_ckbSubMenu.Checked = true;
 						_ckbSubMenu.SetNeedsDisplay ();
-						_txtShortcut.CanFocus = false;
+						_txtShortcut.Enabled = false;
 					}
 					_txtHelp.Text = "";
-					_txtHelp.CanFocus = false;
+					_txtHelp.Enabled = false;
 					_txtAction.Text = "";
-					_txtAction.CanFocus = false;
+					_txtAction.Enabled = false;
 				}
 			};
 			_ckbSubMenu.Toggled += (e) => {
@@ -810,20 +810,20 @@ namespace UICatalog {
 					_ckbIsTopLevel.Checked = false;
 					_ckbIsTopLevel.SetNeedsDisplay ();
 					_txtHelp.Text = "";
-					_txtHelp.CanFocus = false;
+					_txtHelp.Enabled = false;
 					_txtAction.Text = "";
-					_txtAction.CanFocus = false;
+					_txtAction.Enabled = false;
 					_txtShortcut.Text = "";
-					_txtShortcut.CanFocus = false;
+					_txtShortcut.Enabled = false;
 				} else {
 					if (!hasParent) {
 						_ckbIsTopLevel.Checked = true;
 						_ckbIsTopLevel.SetNeedsDisplay ();
-						_txtShortcut.CanFocus = false;
+						_txtShortcut.Enabled = false;
 					}
-					_txtHelp.CanFocus = true;
-					_txtAction.CanFocus = true;
-					_txtShortcut.CanFocus = !_ckbIsTopLevel.Checked && !_ckbSubMenu.Checked;
+					_txtHelp.Enabled = true;
+					_txtAction.Enabled = true;
+					_txtShortcut.Enabled = !_ckbIsTopLevel.Checked && !_ckbSubMenu.Checked;
 				}
 			};
 
@@ -843,9 +843,9 @@ namespace UICatalog {
 				_txtAction.Text = m.action;
 				_ckbIsTopLevel.Checked = false;
 				_ckbSubMenu.Checked = !hasParent;
-				_txtHelp.CanFocus = hasParent;
-				_txtAction.CanFocus = hasParent;
-				_txtShortcut.CanFocus = hasParent;
+				_txtHelp.Enabled = hasParent;
+				_txtAction.Enabled = hasParent;
+				_txtShortcut.Enabled = hasParent;
 			} else {
 				EditMenuBarItem (_menuItem);
 			}
@@ -891,12 +891,12 @@ namespace UICatalog {
 		{
 			if (menuItem == null) {
 				hasParent = false;
-				CanFocus = false;
+				Enabled = false;
 				CleanEditMenuBarItem ();
 				return;
 			} else {
 				hasParent = menuItem.Parent != null;
-				CanFocus = true;
+				Enabled = true;
 			}
 			_menuItem = menuItem;
 			_txtTitle.Text = menuItem?.Title ?? "";
@@ -904,11 +904,11 @@ namespace UICatalog {
 			_txtAction.Text = menuItem != null && menuItem.Action != null ? GetTargetAction (menuItem.Action) : ustring.Empty;
 			_ckbIsTopLevel.Checked = IsTopLevel (menuItem);
 			_ckbSubMenu.Checked = HasSubMenus (menuItem);
-			_txtHelp.CanFocus = !_ckbSubMenu.Checked;
-			_txtAction.CanFocus = !_ckbSubMenu.Checked;
+			_txtHelp.Enabled = !_ckbSubMenu.Checked;
+			_txtAction.Enabled = !_ckbSubMenu.Checked;
 			_rbChkStyle.SelectedItem = (int)(menuItem?.CheckType ?? MenuItemCheckStyle.NoCheck);
 			_txtShortcut.Text = menuItem?.ShortcutTag ?? "";
-			_txtShortcut.CanFocus = !_ckbIsTopLevel.Checked && !_ckbSubMenu.Checked;
+			_txtShortcut.Enabled = !_ckbIsTopLevel.Checked && !_ckbSubMenu.Checked;
 		}
 
 		void CleanEditMenuBarItem ()

+ 594 - 0
UICatalog/Scenarios/DynamicStatusBar.cs

@@ -0,0 +1,594 @@
+using NStack;
+using System;
+using System.Collections.Generic;
+using System.ComponentModel;
+using System.Runtime.CompilerServices;
+using Terminal.Gui;
+
+namespace UICatalog {
+	[ScenarioMetadata (Name: "Dynamic StatusBar", Description: "Demonstrates how to add and remove a StatusBar and change items dynamically.")]
+	[ScenarioCategory ("Dynamic")]
+	class DynamicStatusBar : Scenario {
+		public override void Run ()
+		{
+			Top.Add (new DynamicStatusBarSample (Win.Title));
+			base.Run ();
+		}
+	}
+
+	public class DynamicStatusItemList {
+		public ustring Title { get; set; }
+		public StatusItem StatusItem { get; set; }
+
+		public DynamicStatusItemList () { }
+
+		public DynamicStatusItemList (ustring title, StatusItem statusItem)
+		{
+			Title = title;
+			StatusItem = statusItem;
+		}
+
+		public override string ToString () => $"{Title}, {StatusItem}";
+	}
+
+	public class DynamicStatusItem {
+		public ustring title = "New";
+		public ustring action = "";
+		public ustring shortcut;
+
+		public DynamicStatusItem () { }
+
+		public DynamicStatusItem (ustring title)
+		{
+			this.title = title;
+		}
+
+		public DynamicStatusItem (ustring title, ustring action, ustring shortcut = null)
+		{
+			this.title = title;
+			this.action = action;
+			this.shortcut = shortcut;
+		}
+	}
+
+	public class DynamicStatusBarSample : Window {
+		StatusBar _statusBar;
+		StatusItem _currentStatusItem;
+		int _currentSelectedStatusBar = -1;
+		StatusItem _currentEditStatusItem;
+		ListView _lstItems;
+
+		public DynamicStatusItemModel DataContext { get; set; }
+
+		public DynamicStatusBarSample (ustring title) : base (title)
+		{
+			DataContext = new DynamicStatusItemModel ();
+
+			var _frmDelimiter = new FrameView ("Shortcut Delimiter:") {
+				X = Pos.Center (),
+				Y = 0,
+				Width = 25,
+				Height = 4
+			};
+
+			var _txtDelimiter = new TextField (StatusBar.ShortcutDelimiter.ToString ()) {
+				X = Pos.Center (),
+				Width = 2,
+			};
+			_txtDelimiter.TextChanged += (_) => StatusBar.ShortcutDelimiter = _txtDelimiter.Text;
+			_frmDelimiter.Add (_txtDelimiter);
+
+			Add (_frmDelimiter);
+
+			var _frmStatusBar = new FrameView ("Items:") {
+				Y = 5,
+				Width = Dim.Percent (50),
+				Height = Dim.Fill (2)
+			};
+
+			var _btnAddStatusBar = new Button ("Add a StatusBar") {
+				Y = 1,
+			};
+			_frmStatusBar.Add (_btnAddStatusBar);
+
+			var _btnRemoveStatusBar = new Button ("Remove a StatusBar") {
+				Y = 1
+			};
+			_btnRemoveStatusBar.X = Pos.AnchorEnd () - (Pos.Right (_btnRemoveStatusBar) - Pos.Left (_btnRemoveStatusBar));
+			_frmStatusBar.Add (_btnRemoveStatusBar);
+
+			var _btnAdd = new Button (" Add  ") {
+				Y = Pos.Top (_btnRemoveStatusBar) + 2,
+			};
+			_btnAdd.X = Pos.AnchorEnd () - (Pos.Right (_btnAdd) - Pos.Left (_btnAdd));
+			_frmStatusBar.Add (_btnAdd);
+
+			_lstItems = new ListView (new List<DynamicStatusItemList> ()) {
+				ColorScheme = Colors.Dialog,
+				Y = Pos.Top (_btnAddStatusBar) + 2,
+				Width = Dim.Fill () - Dim.Width (_btnAdd) - 1,
+				Height = Dim.Fill (),
+			};
+			_frmStatusBar.Add (_lstItems);
+
+			var _btnRemove = new Button ("Remove") {
+				X = Pos.Left (_btnAdd),
+				Y = Pos.Top (_btnAdd) + 1
+			};
+			_frmStatusBar.Add (_btnRemove);
+
+			var _btnUp = new Button ("^") {
+				X = Pos.Right (_lstItems) + 2,
+				Y = Pos.Top (_btnRemove) + 2
+			};
+			_frmStatusBar.Add (_btnUp);
+
+			var _btnDown = new Button ("v") {
+				X = Pos.Right (_lstItems) + 2,
+				Y = Pos.Top (_btnUp) + 1
+			};
+			_frmStatusBar.Add (_btnDown);
+
+			Add (_frmStatusBar);
+
+
+			var _frmStatusBarDetails = new DynamicStatusBarDetails ("StatusBar Item Details:") {
+				X = Pos.Right (_frmStatusBar),
+				Y = Pos.Top (_frmStatusBar),
+				Width = Dim.Fill (),
+				Height = Dim.Fill (4)
+			};
+			Add (_frmStatusBarDetails);
+
+			_btnUp.Clicked += () => {
+				var i = _lstItems.SelectedItem;
+				var statusItem = DataContext.Items.Count > 0 ? DataContext.Items [i].StatusItem : null;
+				if (statusItem != null) {
+					var items = _statusBar.Items;
+					if (i > 0) {
+						items [i] = items [i - 1];
+						items [i - 1] = statusItem;
+						DataContext.Items [i] = DataContext.Items [i - 1];
+						DataContext.Items [i - 1] = new DynamicStatusItemList (statusItem.Title, statusItem);
+						_lstItems.SelectedItem = i - 1;
+						_statusBar.SetNeedsDisplay ();
+					}
+				}
+			};
+
+			_btnDown.Clicked += () => {
+				var i = _lstItems.SelectedItem;
+				var statusItem = DataContext.Items.Count > 0 ? DataContext.Items [i].StatusItem : null;
+				if (statusItem != null) {
+					var items = _statusBar.Items;
+					if (i < items.Length - 1) {
+						items [i] = items [i + 1];
+						items [i + 1] = statusItem;
+						DataContext.Items [i] = DataContext.Items [i + 1];
+						DataContext.Items [i + 1] = new DynamicStatusItemList (statusItem.Title, statusItem);
+						_lstItems.SelectedItem = i + 1;
+						_statusBar.SetNeedsDisplay ();
+					}
+				}
+			};
+
+			var _btnOk = new Button ("Ok") {
+				X = Pos.Right (_frmStatusBar) + 20,
+				Y = Pos.Bottom (_frmStatusBarDetails),
+			};
+			Add (_btnOk);
+
+			var _btnCancel = new Button ("Cancel") {
+				X = Pos.Right (_btnOk) + 3,
+				Y = Pos.Top (_btnOk),
+			};
+			_btnCancel.Clicked += () => {
+				SetFrameDetails (_currentEditStatusItem);
+			};
+			Add (_btnCancel);
+
+			_lstItems.SelectedItemChanged += (e) => {
+				SetFrameDetails ();
+			};
+
+			_btnOk.Clicked += () => {
+				if (ustring.IsNullOrEmpty (_frmStatusBarDetails._txtTitle.Text) && _currentEditStatusItem != null) {
+					MessageBox.ErrorQuery ("Invalid title", "Must enter a valid title!.", "Ok");
+				} else if (_currentEditStatusItem != null) {
+					_frmStatusBarDetails._txtTitle.Text = SetTitleText (
+						_frmStatusBarDetails._txtTitle.Text, _frmStatusBarDetails._txtShortcut.Text);
+					var statusItem = new DynamicStatusItem (_frmStatusBarDetails._txtTitle.Text,
+						_frmStatusBarDetails._txtAction.Text,
+						_frmStatusBarDetails._txtShortcut.Text);
+					UpdateStatusItem (_currentEditStatusItem, statusItem, _lstItems.SelectedItem);
+				}
+			};
+
+			_btnAdd.Clicked += () => {
+				if (StatusBar == null) {
+					MessageBox.ErrorQuery ("StatusBar Bar Error", "Must add a StatusBar first!", "Ok");
+					_btnAddStatusBar.SetFocus ();
+					return;
+				}
+
+				var frameDetails = new DynamicStatusBarDetails ();
+				var item = frameDetails.EnterStatusItem ();
+				if (item == null) {
+					return;
+				}
+
+				StatusItem newStatusItem = CreateNewStatusBar (item);
+				_currentSelectedStatusBar++;
+				_statusBar.AddItemAt (_currentSelectedStatusBar, newStatusItem);
+				DataContext.Items.Add (new DynamicStatusItemList (newStatusItem.Title, newStatusItem));
+				_lstItems.MoveDown ();
+			};
+
+			_btnRemove.Clicked += () => {
+				var statusItem = DataContext.Items.Count > 0 ? DataContext.Items [_lstItems.SelectedItem].StatusItem : null;
+				if (statusItem != null) {
+					_statusBar.RemoveItem (_currentSelectedStatusBar);
+					DataContext.Items.RemoveAt (_lstItems.SelectedItem);
+					if (_lstItems.Source.Count > 0 && _lstItems.SelectedItem > _lstItems.Source.Count - 1) {
+						_lstItems.SelectedItem = _lstItems.Source.Count - 1;
+					}
+					_lstItems.SetNeedsDisplay ();
+					SetFrameDetails ();
+				}
+			};
+
+			_lstItems.Enter += (_) => {
+				var statusItem = DataContext.Items.Count > 0 ? DataContext.Items [_lstItems.SelectedItem].StatusItem : null;
+				SetFrameDetails (statusItem);
+			};
+
+			_btnAddStatusBar.Clicked += () => {
+				if (_statusBar != null) {
+					return;
+				}
+
+				_statusBar = new StatusBar ();
+				Add (_statusBar);
+			};
+
+			_btnRemoveStatusBar.Clicked += () => {
+				if (_statusBar == null) {
+					return;
+				}
+
+				Remove (_statusBar);
+				_statusBar = null;
+				DataContext.Items = new List<DynamicStatusItemList> ();
+				_currentStatusItem = null;
+				_currentSelectedStatusBar = -1;
+				SetListViewSource (_currentStatusItem, true);
+				SetFrameDetails (null);
+			};
+
+
+			SetFrameDetails ();
+
+
+			var ustringConverter = new UStringValueConverter ();
+			var listWrapperConverter = new ListWrapperConverter ();
+
+			var lstItems = new Binding (this, "Items", _lstItems, "Source", listWrapperConverter);
+
+
+			void SetFrameDetails (StatusItem statusItem = null)
+			{
+				StatusItem newStatusItem;
+
+				if (statusItem == null) {
+					newStatusItem = DataContext.Items.Count > 0 ? DataContext.Items [_lstItems.SelectedItem].StatusItem : null;
+				} else {
+					newStatusItem = statusItem;
+				}
+
+				_currentEditStatusItem = newStatusItem;
+				_frmStatusBarDetails.EditStatusItem (newStatusItem);
+				var f = _btnOk.Enabled == _frmStatusBarDetails.Enabled;
+				if (!f) {
+					_btnOk.Enabled = _frmStatusBarDetails.Enabled;
+					_btnCancel.Enabled = _frmStatusBarDetails.Enabled;
+				}
+			}
+
+			void SetListViewSource (StatusItem _currentStatusItem, bool fill = false)
+			{
+				DataContext.Items = new List<DynamicStatusItemList> ();
+				var statusItem = _currentStatusItem;
+				if (!fill) {
+					return;
+				}
+				if (statusItem != null) {
+					foreach (var si in _statusBar.Items) {
+						DataContext.Items.Add (new DynamicStatusItemList (si.Title, si));
+					}
+				}
+			}
+
+			StatusItem CreateNewStatusBar (DynamicStatusItem item)
+			{
+				StatusItem newStatusItem;
+				newStatusItem = new StatusItem (ShortcutHelper.GetShortcutFromTag (
+					item.shortcut, StatusBar.ShortcutDelimiter),
+					item.title, _frmStatusBarDetails.CreateAction (item));
+
+				return newStatusItem;
+			}
+
+			void UpdateStatusItem (StatusItem _currentEditStatusItem, DynamicStatusItem statusItem, int index)
+			{
+				_currentEditStatusItem = CreateNewStatusBar (statusItem);
+				_statusBar.Items [index] = _currentEditStatusItem;
+				if (DataContext.Items.Count == 0) {
+					DataContext.Items.Add (new DynamicStatusItemList (_currentEditStatusItem.Title, _currentEditStatusItem));
+				}
+				DataContext.Items [index] = new DynamicStatusItemList (_currentEditStatusItem.Title, _currentEditStatusItem);
+				SetFrameDetails (_currentEditStatusItem);
+			}
+
+
+			//_frmStatusBarDetails.Initialized += (s, e) => _frmStatusBarDetails.Enabled = false;
+		}
+
+		public static ustring SetTitleText (ustring title, ustring shortcut)
+		{
+			var txt = title;
+			var split = title.ToString ().Split ('~');
+			if (split.Length > 1) {
+				txt = split [2].Trim (); ;
+			}
+			if (string.IsNullOrEmpty (shortcut.ToString ())) {
+				return txt;
+			}
+
+			return $"~{shortcut}~ {txt}";
+		}
+	}
+
+	public class DynamicStatusBarDetails : FrameView {
+		public StatusItem _statusItem;
+		public TextField _txtTitle;
+		public TextView _txtAction;
+		public TextField _txtShortcut;
+
+		public DynamicStatusBarDetails (StatusItem statusItem = null) : this (statusItem == null ? "Adding New StatusBar Item." : "Editing StatusBar Item.")
+		{
+			_statusItem = statusItem;
+		}
+
+		public DynamicStatusBarDetails (ustring title) : base (title)
+		{
+			var _lblTitle = new Label ("Title:") {
+				Y = 1
+			};
+			Add (_lblTitle);
+
+			_txtTitle = new TextField () {
+				X = Pos.Right (_lblTitle) + 4,
+				Y = Pos.Top (_lblTitle),
+				Width = Dim.Fill ()
+			};
+			Add (_txtTitle);
+
+			var _lblAction = new Label ("Action:") {
+				X = Pos.Left (_lblTitle),
+				Y = Pos.Bottom (_lblTitle) + 1
+			};
+			Add (_lblAction);
+
+			_txtAction = new TextView () {
+				ColorScheme = Colors.Dialog,
+				X = Pos.Left (_txtTitle),
+				Y = Pos.Top (_lblAction),
+				Width = Dim.Fill (),
+				Height = 5
+			};
+			Add (_txtAction);
+
+			var _lblShortcut = new Label ("Shortcut:") {
+				X = Pos.Left (_lblTitle),
+				Y = Pos.Bottom (_txtAction) + 1
+			};
+			Add (_lblShortcut);
+
+			_txtShortcut = new TextField () {
+				X = Pos.X (_txtAction),
+				Y = Pos.Y (_lblShortcut),
+				Width = Dim.Fill (),
+				ReadOnly = true
+			};
+			_txtShortcut.KeyDown += (e) => {
+				if (!ProcessKey (e.KeyEvent)) {
+					return;
+				}
+
+				var k = ShortcutHelper.GetModifiersKey (e.KeyEvent);
+				if (CheckShortcut (k, true)) {
+					e.Handled = true;
+				}
+			};
+
+			bool ProcessKey (KeyEvent ev)
+			{
+				switch (ev.Key) {
+				case Key.CursorUp:
+				case Key.CursorDown:
+				case Key.Tab:
+				case Key.BackTab:
+					return false;
+				}
+
+				return true;
+			}
+
+			bool CheckShortcut (Key k, bool pre)
+			{
+				var m = _statusItem != null ? _statusItem : new StatusItem (k, "", null);
+				if (pre && !ShortcutHelper.PreShortcutValidation (k)) {
+					_txtShortcut.Text = "";
+					return false;
+				}
+				if (!pre) {
+					if (!ShortcutHelper.PostShortcutValidation (ShortcutHelper.GetShortcutFromTag (
+						_txtShortcut.Text, StatusBar.ShortcutDelimiter))) {
+						_txtShortcut.Text = "";
+						return false;
+					}
+					return true;
+				}
+				_txtShortcut.Text = ShortcutHelper.GetShortcutTag (k, StatusBar.ShortcutDelimiter);
+
+				return true;
+			}
+
+			_txtShortcut.KeyUp += (e) => {
+				var k = ShortcutHelper.GetModifiersKey (e.KeyEvent);
+				if (CheckShortcut (k, false)) {
+					e.Handled = true;
+				}
+			};
+			Add (_txtShortcut);
+
+			var _btnShortcut = new Button ("Clear Shortcut") {
+				X = Pos.X (_lblShortcut),
+				Y = Pos.Bottom (_txtShortcut) + 1
+			};
+			_btnShortcut.Clicked += () => {
+				_txtShortcut.Text = "";
+			};
+			Add (_btnShortcut);
+		}
+
+
+		public DynamicStatusItem EnterStatusItem ()
+		{
+			var valid = false;
+
+			if (_statusItem == null) {
+				var m = new DynamicStatusItem ();
+				_txtTitle.Text = m.title;
+				_txtAction.Text = m.action;
+			} else {
+				EditStatusItem (_statusItem);
+			}
+
+			var _btnOk = new Button ("Ok") {
+				IsDefault = true,
+			};
+			_btnOk.Clicked += () => {
+				if (ustring.IsNullOrEmpty (_txtTitle.Text)) {
+					MessageBox.ErrorQuery ("Invalid title", "Must enter a valid title!.", "Ok");
+				} else {
+					if (!ustring.IsNullOrEmpty (_txtShortcut.Text)) {
+						_txtTitle.Text = DynamicStatusBarSample.SetTitleText (
+							_txtTitle.Text, _txtShortcut.Text);
+					}
+					valid = true;
+					Application.RequestStop ();
+				}
+			};
+			var _btnCancel = new Button ("Cancel");
+			_btnCancel.Clicked += () => {
+				_txtTitle.Text = ustring.Empty;
+				Application.RequestStop ();
+			};
+			var _dialog = new Dialog ("Please enter the item details.", _btnOk, _btnCancel);
+
+			Width = Dim.Fill ();
+			Height = Dim.Fill () - 1;
+			_dialog.Add (this);
+			_txtTitle.SetFocus ();
+			_txtTitle.CursorPosition = _txtTitle.Text.Length;
+			Application.Run (_dialog);
+
+			if (valid) {
+				return new DynamicStatusItem (_txtTitle.Text, _txtAction.Text, _txtShortcut.Text);
+			} else {
+				return null;
+			}
+		}
+
+		public void EditStatusItem (StatusItem statusItem)
+		{
+			if (statusItem == null) {
+				Enabled = false;
+				CleanEditStatusItem ();
+				return;
+			} else {
+				Enabled = true;
+			}
+			_statusItem = statusItem;
+			_txtTitle.Text = statusItem?.Title ?? "";
+			_txtAction.Text = statusItem != null && statusItem.Action != null ? GetTargetAction (statusItem.Action) : ustring.Empty;
+			_txtShortcut.Text = ShortcutHelper.GetShortcutTag (statusItem.Shortcut, StatusBar.ShortcutDelimiter) ?? "";
+		}
+
+		void CleanEditStatusItem ()
+		{
+			_txtTitle.Text = "";
+			_txtAction.Text = "";
+			_txtShortcut.Text = "";
+		}
+
+		ustring GetTargetAction (Action action)
+		{
+			var me = action.Target;
+
+			if (me == null) {
+				throw new ArgumentException ();
+			}
+			object v = new object ();
+			foreach (var field in me.GetType ().GetFields ()) {
+				if (field.Name == "item") {
+					v = field.GetValue (me);
+				}
+			}
+			return v == null || !(v is DynamicStatusItem item) ? ustring.Empty : item.action;
+		}
+
+		public Action CreateAction (DynamicStatusItem item)
+		{
+			return new Action (() => MessageBox.ErrorQuery (item.title, item.action, "Ok"));
+		}
+	}
+
+	public class DynamicStatusItemModel : INotifyPropertyChanged {
+		public event PropertyChangedEventHandler PropertyChanged;
+
+		private ustring statusBar;
+		private List<DynamicStatusItemList> items;
+
+		public ustring StatusBar {
+			get => statusBar;
+			set {
+				if (value != statusBar) {
+					statusBar = value;
+					PropertyChanged?.Invoke (this, new PropertyChangedEventArgs (GetPropertyName ()));
+				}
+			}
+		}
+
+		public List<DynamicStatusItemList> Items {
+			get => items;
+			set {
+				if (value != items) {
+					items = value;
+					PropertyChanged?.Invoke (this, new PropertyChangedEventArgs (GetPropertyName ()));
+				}
+			}
+		}
+
+		public DynamicStatusItemModel ()
+		{
+			Items = new List<DynamicStatusItemList> ();
+		}
+
+		public string GetPropertyName ([CallerMemberName] string propertyName = null)
+		{
+			return propertyName;
+		}
+	}
+}

+ 116 - 41
UICatalog/Scenarios/Editor.cs

@@ -1,4 +1,5 @@
 using System;
+using System.Collections.Generic;
 using System.Text;
 using Terminal.Gui;
 
@@ -19,7 +20,7 @@ namespace UICatalog {
 		private string _textToReplace;
 		private bool _matchCase;
 		private bool _matchWholeWord;
-		Window winDialog;
+		private Window winDialog;
 
 		public override void Init (Toplevel top, ColorScheme colorScheme)
 		{
@@ -29,6 +30,30 @@ namespace UICatalog {
 				Top = Application.Top;
 			}
 
+			Win = new Window (_fileName ?? "Untitled") {
+				X = 0,
+				Y = 1,
+				Width = Dim.Fill (),
+				Height = Dim.Fill (),
+				ColorScheme = colorScheme,
+			};
+			Top.Add (Win);
+
+			_textView = new TextView () {
+				X = 0,
+				Y = 0,
+				Width = Dim.Fill (),
+				Height = Dim.Fill (),
+				BottomOffset = 1,
+				RightOffset = 1
+			};
+
+			CreateDemoFile (_fileName);
+
+			LoadFile ();
+
+			Win.Add (_textView);
+
 			var menu = new MenuBar (new MenuBarItem [] {
 				new MenuBarItem ("_File", new MenuItem [] {
 					new MenuItem ("_New", "", () => New()),
@@ -71,42 +96,24 @@ namespace UICatalog {
 				new MenuBarItem ("Forma_t", new MenuItem [] {
 					CreateWrapChecked (),
 					CreateAllowsTabChecked ()
-				})
+				}),
+				new MenuBarItem ("_Responder", new MenuItem [] {
+					CreateCanFocusChecked (),
+					CreateEnabledChecked (),
+					CreateVisibleChecked ()
+				}),
 			});
 			Top.Add (menu);
 
 			var statusBar = new StatusBar (new StatusItem [] {
 				new StatusItem(Key.F2, "~F2~ Open", () => Open()),
 				new StatusItem(Key.F3, "~F3~ Save", () => Save()),
+				new StatusItem(Key.F4, "~F4~ Save As", () => SaveAs()),
 				new StatusItem(Key.CtrlMask | Key.Q, "~^Q~ Quit", () => Quit()),
 				new StatusItem(Key.Null, $"OS Clipboard IsSupported : {Clipboard.IsSupported}", null)
 			});
 			Top.Add (statusBar);
 
-			CreateDemoFile (_fileName);
-
-			Win = new Window (_fileName ?? "Untitled") {
-				X = 0,
-				Y = 1,
-				Width = Dim.Fill (),
-				Height = Dim.Fill (),
-				ColorScheme = colorScheme,
-			};
-			Top.Add (Win);
-
-			_textView = new TextView () {
-				X = 0,
-				Y = 0,
-				Width = Dim.Fill (),
-				Height = Dim.Fill (),
-				BottomOffset = 1,
-				RightOffset = 1
-			};
-
-			LoadFile ();
-
-			Win.Add (_textView);
-
 			_scrollBar = new ScrollBarView (_textView, true);
 
 			_scrollBar.ChangedPosition += () => {
@@ -125,6 +132,22 @@ namespace UICatalog {
 				_textView.SetNeedsDisplay ();
 			};
 
+			_scrollBar.VisibleChanged += () => {
+				if (_scrollBar.Visible && _textView.RightOffset == 0) {
+					_textView.RightOffset = 1;
+				} else if (!_scrollBar.Visible && _textView.RightOffset == 1) {
+					_textView.RightOffset = 0;
+				}
+			};
+
+			_scrollBar.OtherScrollBarView.VisibleChanged += () => {
+				if (_scrollBar.OtherScrollBarView.Visible && _textView.BottomOffset == 0) {
+					_textView.BottomOffset = 1;
+				} else if (!_scrollBar.OtherScrollBarView.Visible && _textView.BottomOffset == 1) {
+					_textView.BottomOffset = 0;
+				}
+			};
+
 			_textView.DrawContent += (e) => {
 				_scrollBar.Size = _textView.Lines;
 				_scrollBar.Position = _textView.TopRow;
@@ -315,8 +338,8 @@ namespace UICatalog {
 			if (!CanCloseFile ()) {
 				return;
 			}
-
-			var d = new OpenDialog ("Open", "Open a file") { AllowsMultipleSelection = false };
+			var aTypes = new List<string> () { ".txt;.bin;.xml;.json", ".txt", ".bin", ".xml", ".*" };
+			var d = new OpenDialog ("Open", "Choose the path where to open the file.", aTypes) { AllowsMultipleSelection = false };
 			Application.Run (d);
 
 			if (!d.Canceled && d.FilePaths.Count > 0) {
@@ -338,7 +361,8 @@ namespace UICatalog {
 
 		private bool SaveAs ()
 		{
-			var sd = new SaveDialog ("Save file", "Choose the path where to save the file.");
+		var aTypes = new List<string> () { ".txt", ".bin", ".xml", ".*" };
+			var sd = new SaveDialog ("Save file", "Choose the path where to save the file.", aTypes);
 			sd.FilePath = System.IO.Path.Combine (sd.FilePath.ToString (), Win.Title.ToString ());
 			Application.Run (sd);
 
@@ -433,7 +457,7 @@ namespace UICatalog {
 				Title = "Word Wrap"
 			};
 			item.CheckType |= MenuItemCheckStyle.Checked;
-			item.Checked = false;
+			item.Checked = _textView.WordWrap;
 			item.Action += () => {
 				_textView.WordWrap = item.Checked = !item.Checked;
 				if (_textView.WordWrap) {
@@ -455,7 +479,7 @@ namespace UICatalog {
 				Title = "Allows Tab"
 			};
 			item.CheckType |= MenuItemCheckStyle.Checked;
-			item.Checked = true;
+			item.Checked = _textView.AllowsTab;
 			item.Action += () => {
 				_textView.AllowsTab = item.Checked = !item.Checked;
 			};
@@ -463,6 +487,57 @@ namespace UICatalog {
 			return item;
 		}
 
+		private MenuItem CreateCanFocusChecked ()
+		{
+			var item = new MenuItem {
+				Title = "CanFocus"
+			};
+			item.CheckType |= MenuItemCheckStyle.Checked;
+			item.Checked = _textView.CanFocus;
+			item.Action += () => {
+				_textView.CanFocus = item.Checked = !item.Checked;
+				if (_textView.CanFocus) {
+					_textView.SetFocus ();
+				}
+			};
+
+			return item;
+		}
+
+		private MenuItem CreateEnabledChecked ()
+		{
+			var item = new MenuItem {
+				Title = "Enabled"
+			};
+			item.CheckType |= MenuItemCheckStyle.Checked;
+			item.Checked = _textView.Enabled;
+			item.Action += () => {
+				_textView.Enabled = item.Checked = !item.Checked;
+				if (_textView.Enabled) {
+					_textView.SetFocus ();
+				}
+			};
+
+			return item;
+		}
+
+		private MenuItem CreateVisibleChecked ()
+		{
+			var item = new MenuItem {
+				Title = "Visible"
+			};
+			item.CheckType |= MenuItemCheckStyle.Checked;
+			item.Checked = _textView.Visible;
+			item.Action += () => {
+				_textView.Visible = item.Checked = !item.Checked;
+				if (_textView.Visible) {
+					_textView.SetFocus ();
+				}
+			};
+
+			return item;
+		}
+
 		private void CreateFindReplace (bool isFind = true)
 		{
 			winDialog = new Window (isFind ? "Find" : "Replace") {
@@ -538,7 +613,7 @@ namespace UICatalog {
 				X = Pos.Right (txtToFind) + 1,
 				Y = Pos.Top (label),
 				Width = 20,
-				CanFocus = !txtToFind.Text.IsEmpty,
+				Enabled = !txtToFind.Text.IsEmpty,
 				TextAlignment = TextAlignment.Centered,
 				IsDefault = true
 			};
@@ -549,7 +624,7 @@ namespace UICatalog {
 				X = Pos.Right (txtToFind) + 1,
 				Y = Pos.Top (btnFindNext) + 1,
 				Width = 20,
-				CanFocus = !txtToFind.Text.IsEmpty,
+				Enabled = !txtToFind.Text.IsEmpty,
 				TextAlignment = TextAlignment.Centered
 			};
 			btnFindPrevious.Clicked += () => FindPrevious ();
@@ -558,8 +633,8 @@ namespace UICatalog {
 			txtToFind.TextChanged += (e) => {
 				_textToFind = txtToFind.Text.ToString ();
 				_textView.FindTextChanged ();
-				btnFindNext.CanFocus = !txtToFind.Text.IsEmpty;
-				btnFindPrevious.CanFocus = !txtToFind.Text.IsEmpty;
+				btnFindNext.Enabled = !txtToFind.Text.IsEmpty;
+				btnFindPrevious.Enabled = !txtToFind.Text.IsEmpty;
 			};
 
 			var btnCancel = new Button ("Cancel") {
@@ -631,7 +706,7 @@ namespace UICatalog {
 				X = Pos.Right (txtToFind) + 1,
 				Y = Pos.Top (label),
 				Width = 20,
-				CanFocus = !txtToFind.Text.IsEmpty,
+				Enabled = !txtToFind.Text.IsEmpty,
 				TextAlignment = TextAlignment.Centered,
 				IsDefault = true
 			};
@@ -659,7 +734,7 @@ namespace UICatalog {
 				X = Pos.Right (txtToFind) + 1,
 				Y = Pos.Top (btnFindNext) + 1,
 				Width = 20,
-				CanFocus = !txtToFind.Text.IsEmpty,
+				Enabled = !txtToFind.Text.IsEmpty,
 				TextAlignment = TextAlignment.Centered
 			};
 			btnFindPrevious.Clicked += () => ReplacePrevious ();
@@ -669,7 +744,7 @@ namespace UICatalog {
 				X = Pos.Right (txtToFind) + 1,
 				Y = Pos.Top (btnFindPrevious) + 1,
 				Width = 20,
-				CanFocus = !txtToFind.Text.IsEmpty,
+				Enabled = !txtToFind.Text.IsEmpty,
 				TextAlignment = TextAlignment.Centered
 			};
 			btnReplaceAll.Clicked += () => ReplaceAll ();
@@ -678,9 +753,9 @@ namespace UICatalog {
 			txtToFind.TextChanged += (e) => {
 				_textToFind = txtToFind.Text.ToString ();
 				_textView.FindTextChanged ();
-				btnFindNext.CanFocus = !txtToFind.Text.IsEmpty;
-				btnFindPrevious.CanFocus = !txtToFind.Text.IsEmpty;
-				btnReplaceAll.CanFocus = !txtToFind.Text.IsEmpty;
+				btnFindNext.Enabled = !txtToFind.Text.IsEmpty;
+				btnFindPrevious.Enabled = !txtToFind.Text.IsEmpty;
+				btnReplaceAll.Enabled = !txtToFind.Text.IsEmpty;
 			};
 
 			var btnCancel = new Button ("Cancel") {

+ 2 - 2
UICatalog/Scenarios/ProgressBarStyles.cs

@@ -64,7 +64,7 @@ namespace UICatalog {
 			};
 			button.Clicked += () => {
 				if (_fractionTimer == null) {
-					button.CanFocus = false;
+					button.Enabled = false;
 					blocksPB.Fraction = 0;
 					continuousPB.Fraction = 0;
 					float fractionSum = 0;
@@ -75,7 +75,7 @@ namespace UICatalog {
 						if (fractionSum > 1) {
 							_fractionTimer.Dispose ();
 							_fractionTimer = null;
-							button.CanFocus = true;
+							button.Enabled = true;
 						}
 						Application.MainLoop.Driver.Wakeup ();
 					}, null, 0, _timerTick);

+ 4 - 8
UICatalog/Scenarios/SingleBackgroundWorker.cs

@@ -60,11 +60,6 @@ namespace UICatalog {
 				Add (top);
 			}
 
-			public void Load ()
-			{
-				Application.Run (this);
-			}
-
 			private void RunWorker ()
 			{
 				worker = new BackgroundWorker () { WorkerSupportsCancellation = true };
@@ -87,6 +82,10 @@ namespace UICatalog {
 				listLog.SetNeedsDisplay ();
 
 				var md = new Dialog ($"Running Worker started at {startStaging}.{startStaging:fff}", cancel);
+				md.Add (new Label ("Wait for worker to finish...") {
+					X = Pos.Center (),
+					Y = Pos.Center ()
+				});
 
 				worker.DoWork += (s, e) => {
 					var stageResult = new List<string> ();
@@ -163,9 +162,6 @@ namespace UICatalog {
 				top.Add (statusBar);
 
 				Title = $"Worker started at {start}.{start:fff}";
-				Y = 1;
-				Height = Dim.Fill (1);
-
 				ColorScheme = Colors.Base;
 
 				Add (new ListView (list) {

+ 2 - 4
UnitTests/LineViewTests.cs

@@ -1,9 +1,7 @@
-using Terminal.Gui;
-using Terminal.Gui.Graphs;
-using Terminal.Gui.Views;
+using Terminal.Gui.Graphs;
 using Xunit;
 
-namespace UnitTests {
+namespace Terminal.Gui.Views {
 	public class LineViewTests {
 
 		[Fact]

+ 3 - 1
UnitTests/ResponderTests.cs

@@ -4,7 +4,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.Core {
@@ -17,6 +17,8 @@ namespace Terminal.Gui.Core {
 			Assert.Equal ("Terminal.Gui.Responder", r.ToString ());
 			Assert.False (r.CanFocus);
 			Assert.False (r.HasFocus);
+			Assert.True (r.Enabled);
+			Assert.True (r.Visible);
 		}
 
 		[Fact]

+ 160 - 0
UnitTests/StatusBarTests.cs

@@ -0,0 +1,160 @@
+using System;
+using Xunit;
+using Xunit.Abstractions;
+
+namespace Terminal.Gui.Views {
+	public class StatusBarTests {
+		readonly ITestOutputHelper output;
+
+		public StatusBarTests (ITestOutputHelper output)
+		{
+			this.output = output;
+		}
+
+		[Fact]
+		public void StatusItem_Constructor ()
+		{
+			var si = new StatusItem (Key.CtrlMask | Key.Q, "~^Q~ Quit", null);
+			Assert.Equal (Key.CtrlMask | Key.Q, si.Shortcut);
+			Assert.Equal ("~^Q~ Quit", si.Title);
+			Assert.Null (si.Action);
+			si = new StatusItem (Key.CtrlMask | Key.Q, "~^Q~ Quit", () => { });
+			Assert.NotNull (si.Action);
+		}
+
+		[Fact]
+		public void StatusBar_Contructor_Default ()
+		{
+			var sb = new StatusBar ();
+
+			Assert.Empty (sb.Items);
+			Assert.False (sb.CanFocus);
+			Assert.Equal (Colors.Menu, sb.ColorScheme);
+			Assert.Equal (0, sb.X);
+			Assert.Equal (Dim.Fill (), sb.Width);
+			Assert.Equal (1, sb.Height);
+
+			Assert.Equal (0, sb.Y);
+
+			var driver = new FakeDriver ();
+			Application.Init (driver, new FakeMainLoop (() => FakeConsole.ReadKey (true)));
+
+			sb = new StatusBar ();
+
+			driver.SetCursorVisibility (CursorVisibility.Default);
+			driver.GetCursorVisibility (out CursorVisibility cv);
+			Assert.Equal (CursorVisibility.Default, cv);
+			Assert.True (FakeConsole.CursorVisible);
+
+			Application.Iteration += () => {
+				Assert.Equal (24, sb.Y);
+
+				driver.SetWindowSize (driver.Cols, 15);
+
+				Assert.Equal (14, sb.Y);
+
+				sb.OnEnter (null);
+				driver.GetCursorVisibility (out cv);
+				Assert.Equal (CursorVisibility.Invisible, cv);
+				Assert.False (FakeConsole.CursorVisible);
+
+				Application.RequestStop ();
+			};
+
+			Application.Top.Add (sb);
+
+			Application.Run ();
+
+			Application.Shutdown ();
+		}
+
+		[Fact]
+		[AutoInitShutdown]
+		public void Run_Action_With_Key_And_Mouse ()
+		{
+			var msg = "";
+			var sb = new StatusBar (new StatusItem [] { new StatusItem (Key.CtrlMask | Key.Q, "~^Q~ Quit", () => msg = "Quiting...") });
+			Application.Top.Add (sb);
+
+			var iteration = 0;
+
+			Application.Iteration += () => {
+				if (iteration == 0) {
+					Assert.Equal ("", msg);
+					sb.ProcessHotKey (new KeyEvent (Key.CtrlMask | Key.Q, null));
+				} else if (iteration == 1) {
+					Assert.Equal ("Quiting...", msg);
+					msg = "";
+					sb.MouseEvent (new MouseEvent () { X = 1, Y = 24, Flags = MouseFlags.Button1Clicked });
+				} else {
+					Assert.Equal ("Quiting...", msg);
+
+					Application.RequestStop ();
+				}
+				iteration++;
+			};
+
+			Application.Run ();
+		}
+
+		[Fact]
+		[AutoInitShutdown]
+		public void Redraw_Output ()
+		{
+			var sb = new StatusBar (new StatusItem [] {
+				new StatusItem (Key.CtrlMask | Key.Q, "~^O~ Open", null),
+				new StatusItem (Key.CtrlMask | Key.Q, "~^Q~ Quit", null)
+			});
+			Application.Top.Add (sb);
+
+			sb.Redraw (sb.Bounds);
+
+			string expected = @$"
+^O Open {Application.Driver.VLine} ^Q Quit
+";
+
+			GraphViewTests.AssertDriverContentsAre (expected, output);
+
+			sb = new StatusBar (new StatusItem [] {
+				new StatusItem (Key.CtrlMask | Key.Q, "~CTRL-O~ Open", null),
+				new StatusItem (Key.CtrlMask | Key.Q, "~CTRL-Q~ Quit", null)
+			});
+			sb.Redraw (sb.Bounds);
+
+			expected = @$"
+CTRL-O Open {Application.Driver.VLine} CTRL-Q Quit
+";
+
+			GraphViewTests.AssertDriverContentsAre (expected, output);
+		}
+
+		[Fact]
+		public void AddItemAt_RemoveItem_Replacing ()
+		{
+			var sb = new StatusBar (new StatusItem [] {
+				new StatusItem (Key.CtrlMask | Key.Q, "~^O~ Open", null),
+				new StatusItem (Key.CtrlMask | Key.Q, "~^S~ Save", null),
+				new StatusItem (Key.CtrlMask | Key.Q, "~^Q~ Quit", null)
+			});
+
+			sb.AddItemAt (2, new StatusItem (Key.CtrlMask | Key.Q, "~^C~ Close", null));
+
+			Assert.Equal ("~^O~ Open", sb.Items [0].Title);
+			Assert.Equal ("~^S~ Save", sb.Items [1].Title);
+			Assert.Equal ("~^C~ Close", sb.Items [2].Title);
+			Assert.Equal ("~^Q~ Quit", sb.Items [^1].Title);
+
+			Assert.Equal ("~^S~ Save", sb.RemoveItem (1).Title);
+
+			Assert.Equal ("~^O~ Open", sb.Items [0].Title);
+			Assert.Equal ("~^C~ Close", sb.Items [1].Title);
+			Assert.Equal ("~^Q~ Quit", sb.Items [^1].Title);
+
+			sb.Items [1] = new StatusItem (Key.CtrlMask | Key.A, "~^A~ Save As", null);
+
+			Assert.Equal ("~^O~ Open", sb.Items [0].Title);
+			Assert.Equal ("~^A~ Save As", sb.Items [1].Title);
+			Assert.Equal ("~^Q~ Quit", sb.Items [^1].Title);
+		}
+	}
+}

+ 65 - 7
UnitTests/TextViewTests.cs

@@ -1801,10 +1801,7 @@ namespace Terminal.Gui.Views {
 				if (r == '\t') {
 					sumLength += tabWidth + 1;
 				}
-				if (sumLength > width) {
-					if (cCol == line.Length) {
-						col++;
-					}
+				if (sumLength >= width) {
 					break;
 				} else if (cCol < line.Length && col > 0 && start < cCol && col == start) {
 					break;
@@ -1968,6 +1965,7 @@ line.
 			int col = 0;
 			Assert.True (TextModel.SetCol (ref col, 80, 79));
 			Assert.False (TextModel.SetCol (ref col, 80, 80));
+			Assert.Equal (79, col);
 
 			var start = 0;
 			var x = 8;
@@ -1981,9 +1979,9 @@ line.
 			Assert.Equal ((15, 15), TextModel.DisplaySize (txtRunes));
 			Assert.Equal ((6, 6), TextModel.DisplaySize (txtRunes, 1, 7));
 
-			Assert.Equal (0, TextModel.CalculateLeftColumn (txtRunes, 0, 7, 8));
-			Assert.Equal (1, TextModel.CalculateLeftColumn (txtRunes, 0, 8, 8));
-			Assert.Equal (2, TextModel.CalculateLeftColumn (txtRunes, 0, 9, 8));
+			Assert.Equal (1, TextModel.CalculateLeftColumn (txtRunes, 0, 7, 8));
+			Assert.Equal (2, TextModel.CalculateLeftColumn (txtRunes, 0, 8, 8));
+			Assert.Equal (3, TextModel.CalculateLeftColumn (txtRunes, 0, 9, 8));
 
 			var tm = new TextModel ();
 			tm.AddLine (0, TextModel.ToRunes ("This is first line."));
@@ -2020,5 +2018,65 @@ line.
 			Assert.Equal (TextModel.ToRunes ("This really first line."), tm.GetLine (0));
 			Assert.Equal (TextModel.ToRunes ("This really last line."), tm.GetLine (1));
 		}
+
+		[Fact]
+		[InitShutdown]
+		public void BottomOffset_Sets_To_Zero_Adjust_TopRow ()
+		{
+			string text = "";
+
+			for (int i = 0; i < 12; i++) {
+				text += $"This is the line {i}\n";
+			}
+			var tv = new TextView () { Width = 10, Height = 10, BottomOffset = 1 };
+			tv.Text = text;
+
+			tv.ProcessKey (new KeyEvent (Key.CtrlMask | Key.End, null));
+
+			Assert.Equal (4, tv.TopRow);
+			Assert.Equal (1, tv.BottomOffset);
+
+			tv.BottomOffset = 0;
+			Assert.Equal (3, tv.TopRow);
+			Assert.Equal (0, tv.BottomOffset);
+
+			tv.BottomOffset = 2;
+			Assert.Equal (5, tv.TopRow);
+			Assert.Equal (2, tv.BottomOffset);
+
+			tv.BottomOffset = 0;
+			Assert.Equal (3, tv.TopRow);
+			Assert.Equal (0, tv.BottomOffset);
+		}
+
+		[Fact]
+		[InitShutdown]
+		public void RightOffset_Sets_To_Zero_Adjust_leftColumn ()
+		{
+			string text = "";
+
+			for (int i = 0; i < 12; i++) {
+				text += $"{i.ToString () [^1]}";
+			}
+			var tv = new TextView () { Width = 10, Height = 10, RightOffset = 1 };
+			tv.Text = text;
+
+			tv.ProcessKey (new KeyEvent (Key.End, null));
+
+			Assert.Equal (4, tv.LeftColumn);
+			Assert.Equal (1, tv.RightOffset);
+
+			tv.RightOffset = 0;
+			Assert.Equal (3, tv.LeftColumn);
+			Assert.Equal (0, tv.RightOffset);
+
+			tv.RightOffset = 2;
+			Assert.Equal (5, tv.LeftColumn);
+			Assert.Equal (2, tv.RightOffset);
+
+			tv.RightOffset = 0;
+			Assert.Equal (3, tv.LeftColumn);
+			Assert.Equal (0, tv.RightOffset);
+		}
 	}
 }

+ 315 - 0
UnitTests/ToplevelTests.cs

@@ -0,0 +1,315 @@
+using System;
+using Xunit;
+
+namespace Terminal.Gui.Core {
+	public class ToplevelTests {
+		[Fact]
+		[AutoInitShutdown]
+		public void Constructor_Default ()
+		{
+			var top = new Toplevel ();
+
+			Assert.Equal (Colors.TopLevel, top.ColorScheme);
+			Assert.Equal ("Dim.Fill(margin=0)", top.Width.ToString ());
+			Assert.Equal ("Dim.Fill(margin=0)", top.Height.ToString ());
+			Assert.False (top.Running);
+			Assert.False (top.Modal);
+			Assert.Null (top.MenuBar);
+			Assert.Null (top.StatusBar);
+			Assert.False (top.IsMdiContainer);
+			Assert.False (top.IsMdiChild);
+		}
+
+		[Fact]
+		[AutoInitShutdown]
+		public void Create_Toplevel ()
+		{
+			var top = Toplevel.Create ();
+			Assert.Equal (new Rect (0, 0, Application.Driver.Cols, Application.Driver.Rows), top.Bounds);
+		}
+
+
+		[Fact]
+		[AutoInitShutdown]
+		public void Application_Top_EnsureVisibleBounds_To_Driver_Rows_And_Cols ()
+		{
+			var iterations = 0;
+
+			Application.Iteration += () => {
+				if (iterations == 0) {
+					Assert.Equal ("Top1", Application.Top.Text);
+					Assert.Equal (0, Application.Top.Frame.X);
+					Assert.Equal (0, Application.Top.Frame.Y);
+					Assert.Equal (Application.Driver.Cols, Application.Top.Frame.Width);
+					Assert.Equal (Application.Driver.Rows, Application.Top.Frame.Height);
+
+					Application.Top.ProcessHotKey (new KeyEvent (Key.CtrlMask | Key.R, new KeyModifiers ()));
+				} else if (iterations == 1) {
+					Assert.Equal ("Top2", Application.Top.Text);
+					Assert.Equal (0, Application.Top.Frame.X);
+					Assert.Equal (0, Application.Top.Frame.Y);
+					Assert.Equal (Application.Driver.Cols, Application.Top.Frame.Width);
+					Assert.Equal (Application.Driver.Rows, Application.Top.Frame.Height);
+
+					Application.Top.ProcessHotKey (new KeyEvent (Key.CtrlMask | Key.C, new KeyModifiers ()));
+				} else if (iterations == 3) {
+					Assert.Equal ("Top1", Application.Top.Text);
+					Assert.Equal (0, Application.Top.Frame.X);
+					Assert.Equal (0, Application.Top.Frame.Y);
+					Assert.Equal (Application.Driver.Cols, Application.Top.Frame.Width);
+					Assert.Equal (Application.Driver.Rows, Application.Top.Frame.Height);
+
+					Application.Top.ProcessHotKey (new KeyEvent (Key.CtrlMask | Key.R, new KeyModifiers ()));
+				} else if (iterations == 4) {
+					Assert.Equal ("Top2", Application.Top.Text);
+					Assert.Equal (0, Application.Top.Frame.X);
+					Assert.Equal (0, Application.Top.Frame.Y);
+					Assert.Equal (Application.Driver.Cols, Application.Top.Frame.Width);
+					Assert.Equal (Application.Driver.Rows, Application.Top.Frame.Height);
+
+					Application.Top.ProcessHotKey (new KeyEvent (Key.CtrlMask | Key.C, new KeyModifiers ()));
+				} else if (iterations == 6) {
+					Assert.Equal ("Top1", Application.Top.Text);
+					Assert.Equal (0, Application.Top.Frame.X);
+					Assert.Equal (0, Application.Top.Frame.Y);
+					Assert.Equal (Application.Driver.Cols, Application.Top.Frame.Width);
+					Assert.Equal (Application.Driver.Rows, Application.Top.Frame.Height);
+
+					Application.Top.ProcessHotKey (new KeyEvent (Key.CtrlMask | Key.Q, new KeyModifiers ()));
+				}
+				iterations++;
+			};
+
+			Application.Run (Top1 ());
+
+			Toplevel Top1 ()
+			{
+				var top = Application.Top;
+				top.Text = "Top1";
+				var menu = new MenuBar (new MenuBarItem [] {
+					new MenuBarItem ("_Options", new MenuItem [] {
+						new MenuItem ("_Run Top2", "", () => Application.Run (Top2 ()), null, null, Key.CtrlMask | Key.R),
+						new MenuItem ("_Quit", "", () => Application.RequestStop(), null, null, Key.CtrlMask | Key.Q)
+					})
+				});
+				top.Add (menu);
+
+				var statusBar = new StatusBar (new [] {
+					new StatusItem(Key.CtrlMask | Key.R, "~^R~ Run Top2", () => Application.Run (Top2 ())),
+					new StatusItem(Key.CtrlMask | Key.Q, "~^Q~ Quit", () => Application.RequestStop())
+				});
+				top.Add (statusBar);
+
+				var t1 = new Toplevel ();
+				top.Add (t1);
+
+				return top;
+			}
+
+			Toplevel Top2 ()
+			{
+				var top = new Toplevel (Application.Top.Frame);
+				top.Text = "Top2";
+				var win = new Window () { Width = Dim.Fill (), Height = Dim.Fill () };
+				var menu = new MenuBar (new MenuBarItem [] {
+					new MenuBarItem ("_Stage", new MenuItem [] {
+						new MenuItem ("_Close", "", () => Application.RequestStop(), null, null, Key.CtrlMask | Key.C)
+					})
+				});
+				top.Add (menu);
+
+				var statusBar = new StatusBar (new [] {
+					new StatusItem(Key.CtrlMask | Key.C, "~^C~ Close", () => Application.RequestStop()),
+				});
+				top.Add (statusBar);
+
+				win.Add (new ListView () {
+					X = 0,
+					Y = 0,
+					Width = Dim.Fill (),
+					Height = Dim.Fill ()
+				});
+				top.Add (win);
+
+				return top;
+			}
+		}
+
+		[Fact]
+		[AutoInitShutdown]
+		public void Internal_Tests ()
+		{
+			var top = new Toplevel ();
+			var eventInvoked = "";
+
+			top.ChildUnloaded += (e) => eventInvoked = "ChildUnloaded";
+			top.OnChildUnloaded (top);
+			Assert.Equal ("ChildUnloaded", eventInvoked);
+			top.ChildLoaded += (e) => eventInvoked = "ChildLoaded";
+			top.OnChildLoaded (top);
+			Assert.Equal ("ChildLoaded", eventInvoked);
+			top.Closed += (e) => eventInvoked = "Closed";
+			top.OnClosed (top);
+			Assert.Equal ("Closed", eventInvoked);
+			top.Closing += (e) => eventInvoked = "Closing";
+			top.OnClosing (new ToplevelClosingEventArgs (top));
+			Assert.Equal ("Closing", eventInvoked);
+			top.AllChildClosed += () => eventInvoked = "AllChildClosed";
+			top.OnAllChildClosed ();
+			Assert.Equal ("AllChildClosed", eventInvoked);
+			top.ChildClosed += (e) => eventInvoked = "ChildClosed";
+			top.OnChildClosed (top);
+			Assert.Equal ("ChildClosed", eventInvoked);
+			top.Deactivate += (e) => eventInvoked = "Deactivate";
+			top.OnDeactivate (top);
+			Assert.Equal ("Deactivate", eventInvoked);
+			top.Activate += (e) => eventInvoked = "Activate";
+			top.OnActivate (top);
+			Assert.Equal ("Activate", eventInvoked);
+			top.Loaded += () => eventInvoked = "Loaded";
+			top.OnLoaded ();
+			Assert.Equal ("Loaded", eventInvoked);
+			top.Ready += () => eventInvoked = "Ready";
+			top.OnReady ();
+			Assert.Equal ("Ready", eventInvoked);
+			top.Unloaded += () => eventInvoked = "Unloaded";
+			top.OnUnloaded ();
+			Assert.Equal ("Unloaded", eventInvoked);
+
+			top.AddMenuStatusBar (new MenuBar ());
+			Assert.NotNull (top.MenuBar);
+			top.AddMenuStatusBar (new StatusBar ());
+			Assert.NotNull (top.StatusBar);
+			top.RemoveMenuStatusBar (top.MenuBar);
+			Assert.Null (top.MenuBar);
+			top.RemoveMenuStatusBar (top.StatusBar);
+			Assert.Null (top.StatusBar);
+
+			Application.Begin (top);
+			Assert.Equal (top, Application.Top);
+
+			// top is Application.Top without menu and status bar.
+			var supView = top.EnsureVisibleBounds (top, 2, 2, out int nx, out int ny, out View mb, out View sb);
+			Assert.Equal (Application.Top, supView);
+			Assert.Equal (0, nx);
+			Assert.Equal (0, ny);
+			Assert.Null (mb);
+			Assert.Null (sb);
+
+			top.AddMenuStatusBar (new MenuBar ());
+			Assert.NotNull (top.MenuBar);
+
+			// top is Application.Top with a menu and without status bar.
+			top.EnsureVisibleBounds (top, 2, 2, out nx, out ny, out mb, out sb);
+			Assert.Equal (0, nx);
+			Assert.Equal (1, ny);
+			Assert.NotNull (mb);
+			Assert.Null (sb);
+
+			top.AddMenuStatusBar (new StatusBar ());
+			Assert.NotNull (top.StatusBar);
+
+			// top is Application.Top with a menu and status bar.
+			top.EnsureVisibleBounds (top, 2, 2, out nx, out ny, out mb, out sb);
+			Assert.Equal (0, nx);
+			Assert.Equal (1, ny);
+			Assert.NotNull (mb);
+			Assert.NotNull (sb);
+
+			top.RemoveMenuStatusBar (top.MenuBar);
+			Assert.Null (top.MenuBar);
+
+			// top is Application.Top without a menu and with a status bar.
+			top.EnsureVisibleBounds (top, 2, 2, out nx, out ny, out mb, out sb);
+			Assert.Equal (0, nx);
+			Assert.Equal (0, ny);
+			Assert.Null (mb);
+			Assert.NotNull (sb);
+
+			top.RemoveMenuStatusBar (top.StatusBar);
+			Assert.Null (top.StatusBar);
+			Assert.Null (top.MenuBar);
+
+			var win = new Window () { Width = Dim.Fill (), Height = Dim.Fill () };
+			top.Add (win);
+			top.LayoutSubviews ();
+
+			// The SuperView is always the same regardless of the caller.
+			supView = top.EnsureVisibleBounds (win, 0, 0, out nx, out ny, out mb, out sb);
+			Assert.Equal (Application.Top, supView);
+			supView = win.EnsureVisibleBounds (win, 0, 0, out nx, out ny, out mb, out sb);
+			Assert.Equal (Application.Top, supView);
+
+			// top is Application.Top without menu and status bar.
+			top.EnsureVisibleBounds (win, 0, 0, out nx, out ny, out mb, out sb);
+			Assert.Equal (0, nx);
+			Assert.Equal (0, ny);
+			Assert.Null (mb);
+			Assert.Null (sb);
+
+			top.AddMenuStatusBar (new MenuBar ());
+			Assert.NotNull (top.MenuBar);
+
+			// top is Application.Top with a menu and without status bar.
+			top.EnsureVisibleBounds (win, 2, 2, out nx, out ny, out mb, out sb);
+			Assert.Equal (0, nx);
+			Assert.Equal (1, ny);
+			Assert.NotNull (mb);
+			Assert.Null (sb);
+
+			top.AddMenuStatusBar (new StatusBar ());
+			Assert.NotNull (top.StatusBar);
+
+			// top is Application.Top with a menu and status bar.
+			top.EnsureVisibleBounds (win, 30, 20, out nx, out ny, out mb, out sb);
+			Assert.Equal (0, nx);
+			Assert.Equal (1, ny);
+			Assert.NotNull (mb);
+			Assert.NotNull (sb);
+
+			top.RemoveMenuStatusBar (top.MenuBar);
+			top.RemoveMenuStatusBar (top.StatusBar);
+			Assert.Null (top.StatusBar);
+			Assert.Null (top.MenuBar);
+
+			top.Remove (win);
+
+			win = new Window () { Width = 60, Height = 15 };
+			top.Add (win);
+
+			// top is Application.Top without menu and status bar.
+			top.EnsureVisibleBounds (win, 0, 0, out nx, out ny, out mb, out sb);
+			Assert.Equal (0, nx);
+			Assert.Equal (0, ny);
+			Assert.Null (mb);
+			Assert.Null (sb);
+
+			top.AddMenuStatusBar (new MenuBar ());
+			Assert.NotNull (top.MenuBar);
+
+			// top is Application.Top with a menu and without status bar.
+			top.EnsureVisibleBounds (win, 2, 2, out nx, out ny, out mb, out sb);
+			Assert.Equal (2, nx);
+			Assert.Equal (2, ny);
+			Assert.NotNull (mb);
+			Assert.Null (sb);
+
+			top.AddMenuStatusBar (new StatusBar ());
+			Assert.NotNull (top.StatusBar);
+
+			// top is Application.Top with a menu and status bar.
+			top.EnsureVisibleBounds (win, 30, 20, out nx, out ny, out mb, out sb);
+			Assert.Equal (20, nx); // 20+60=80
+			Assert.Equal (9, ny); // 9+15+1(mb)=25
+			Assert.NotNull (mb);
+			Assert.NotNull (sb);
+
+			top.PositionToplevels ();
+			Assert.Equal (new Rect (0, 1, 60, 15), win.Frame);
+
+			Assert.Null (Toplevel.dragPosition);
+			win.MouseEvent (new MouseEvent () { X = 6, Y = 0, Flags = MouseFlags.Button1Pressed });
+			Assert.Equal (new Point (6, 0), Toplevel.dragPosition);
+		}
+	}
+}

+ 156 - 0
UnitTests/ViewTests.cs

@@ -1412,5 +1412,161 @@ namespace Terminal.Gui.Views {
 			view.OnLayoutComplete (null);
 			Assert.False (layoutStarted);
 		}
+
+		[Fact]
+		[AutoInitShutdown]
+		public void Enabled_False_Sets_HasFocus_To_False ()
+		{
+			var wasClicked = false;
+			var view = new Button ("Click Me");
+			view.Clicked += () => wasClicked = !wasClicked;
+			Application.Top.Add (view);
+
+			view.ProcessKey (new KeyEvent (Key.Enter, null));
+			Assert.True (wasClicked);
+			view.MouseEvent (new MouseEvent () { Flags = MouseFlags.Button1Clicked });
+			Assert.False (wasClicked);
+			Assert.True (view.Enabled);
+			Assert.True (view.CanFocus);
+			Assert.True (view.HasFocus);
+
+			view.Enabled = false;
+			view.ProcessKey (new KeyEvent (Key.Enter, null));
+			Assert.False (wasClicked);
+			view.MouseEvent (new MouseEvent () { Flags = MouseFlags.Button1Clicked });
+			Assert.False (wasClicked);
+			Assert.False (view.Enabled);
+			Assert.True (view.CanFocus);
+			Assert.False (view.HasFocus);
+			view.SetFocus ();
+			Assert.False (view.HasFocus);
+		}
+
+		[Fact]
+		[AutoInitShutdown]
+		public void Enabled_Sets_Also_Sets_Subviews ()
+		{
+			var wasClicked = false;
+			var button = new Button ("Click Me");
+			button.Clicked += () => wasClicked = !wasClicked;
+			var win = new Window () { Width = Dim.Fill (), Height = Dim.Fill () };
+			win.Add (button);
+			Application.Top.Add (win);
+
+			var iterations = 0;
+
+			Application.Iteration += () => {
+				iterations++;
+
+				button.ProcessKey (new KeyEvent (Key.Enter, null));
+				Assert.True (wasClicked);
+				button.MouseEvent (new MouseEvent () { Flags = MouseFlags.Button1Clicked });
+				Assert.False (wasClicked);
+				Assert.True (button.Enabled);
+				Assert.True (button.CanFocus);
+				Assert.True (button.HasFocus);
+				Assert.True (win.Enabled);
+				Assert.True (win.CanFocus);
+				Assert.True (win.HasFocus);
+
+				win.Enabled = false;
+				button.ProcessKey (new KeyEvent (Key.Enter, null));
+				Assert.False (wasClicked);
+				button.MouseEvent (new MouseEvent () { Flags = MouseFlags.Button1Clicked });
+				Assert.False (wasClicked);
+				Assert.False (button.Enabled);
+				Assert.True (button.CanFocus);
+				Assert.False (button.HasFocus);
+				Assert.False (win.Enabled);
+				Assert.True (win.CanFocus);
+				Assert.False (win.HasFocus);
+				button.SetFocus ();
+				Assert.False (button.HasFocus);
+				Assert.False (win.HasFocus);
+				win.SetFocus ();
+				Assert.False (button.HasFocus);
+				Assert.False (win.HasFocus);
+
+				win.Enabled = true;
+				win.FocusFirst ();
+				Assert.True (button.HasFocus);
+				Assert.True (win.HasFocus);
+
+				Application.RequestStop ();
+			};
+
+			Application.Run ();
+
+			Assert.Equal (1, iterations);
+		}
+
+		[Fact]
+		[AutoInitShutdown]
+		public void Visible_Sets_Also_Sets_Subviews ()
+		{
+			var button = new Button ("Click Me");
+			var win = new Window () { Width = Dim.Fill (), Height = Dim.Fill () };
+			win.Add (button);
+			var top = Application.Top;
+			top.Add (win);
+
+			var iterations = 0;
+
+			Application.Iteration += () => {
+				iterations++;
+
+				Assert.True (button.Visible);
+				Assert.True (button.CanFocus);
+				Assert.True (button.HasFocus);
+				Assert.True (win.Visible);
+				Assert.True (win.CanFocus);
+				Assert.True (win.HasFocus);
+				Assert.True (RunesCount () > 0);
+
+				win.Visible = false;
+				Assert.True (button.Visible);
+				Assert.True (button.CanFocus);
+				Assert.False (button.HasFocus);
+				Assert.False (win.Visible);
+				Assert.True (win.CanFocus);
+				Assert.False (win.HasFocus);
+				button.SetFocus ();
+				Assert.False (button.HasFocus);
+				Assert.False (win.HasFocus);
+				win.SetFocus ();
+				Assert.False (button.HasFocus);
+				Assert.False (win.HasFocus);
+				top.Redraw (top.Bounds);
+				Assert.True (RunesCount () == 0);
+
+				win.Visible = true;
+				win.FocusFirst ();
+				Assert.True (button.HasFocus);
+				Assert.True (win.HasFocus);
+				top.Redraw (top.Bounds);
+				Assert.True (RunesCount () > 0);
+
+				Application.RequestStop ();
+			};
+
+			Application.Run ();
+
+			Assert.Equal (1, iterations);
+
+			int RunesCount ()
+			{
+				var contents = ((FakeDriver)Application.Driver).Contents;
+				var runesCount = 0;
+
+				for (int i = 0; i < Application.Driver.Rows; i++) {
+					for (int j = 0; j < Application.Driver.Cols; j++) {
+						if (contents [i, j, 0] != ' ') {
+							runesCount++;
+						}
+					}
+				}
+				return runesCount;
+			}
+		}
 	}
 }