Browse Source

Fixes #1041. NetDriver events should stay all together.

BDisp 4 years ago
parent
commit
72a4104ee5
3 changed files with 304 additions and 122 deletions
  1. 262 121
      Terminal.Gui/ConsoleDrivers/NetDriver.cs
  2. 18 0
      Terminal.Gui/Core/Application.cs
  3. 24 1
      UICatalog/UICatalog.cs

+ 262 - 121
Terminal.Gui/ConsoleDrivers/NetDriver.cs

@@ -1,4 +1,5 @@
-//
+//#define PROCESS_REQUEST
+//
 // NetDriver.cs: The System.Console-based .NET driver, works on Windows and Unix, but is not particularly efficient.
 //
 // Authors:
@@ -107,24 +108,40 @@ namespace Terminal.Gui {
 	internal class NetEvents {
 		ManualResetEventSlim inputReady = new ManualResetEventSlim (false);
 		ManualResetEventSlim waitForStart = new ManualResetEventSlim (false);
+		ManualResetEventSlim winChange = new ManualResetEventSlim (false);
 		Queue<InputResult?> inputResultQueue = new Queue<InputResult?> ();
-
+		ConsoleDriver consoleDriver;
+		int lastWindowHeight;
+		int largestWindowHeight;
+#if PROCESS_REQUEST
+		bool neededProcessRequest;
+#endif
 		public int NumberOfCSI { get; }
 
-		public NetEvents (int numberOfCSI = 1)
+		public NetEvents (ConsoleDriver consoleDriver, int numberOfCSI = 1)
 		{
+			if (consoleDriver == null) {
+				throw new ArgumentNullException ("Console driver instance must be provided.");
+			}
+			this.consoleDriver = consoleDriver;
 			NumberOfCSI = numberOfCSI;
 			Task.Run (ProcessInputResultQueue);
+			Task.Run (CheckWinChange);
 		}
 
 		public InputResult? ReadConsoleInput ()
 		{
 			while (true) {
 				waitForStart.Set ();
+				winChange.Set ();
+
 				if (inputResultQueue.Count == 0) {
 					inputReady.Wait ();
 					inputReady.Reset ();
 				}
+#if PROCESS_REQUEST
+				neededProcessRequest = false;
+#endif
 				if (inputResultQueue.Count > 0) {
 					return inputResultQueue.Dequeue ();
 				}
@@ -145,6 +162,65 @@ namespace Terminal.Gui {
 			}
 		}
 
+		void CheckWinChange ()
+		{
+			while (true) {
+				winChange.Wait ();
+				winChange.Reset ();
+				WaitWinChange ();
+				inputReady.Set ();
+			}
+		}
+
+		void WaitWinChange ()
+		{
+			while (true) {
+				if (!consoleDriver.HeightAsBuffer) {
+					if (Console.WindowWidth != consoleDriver.Cols || Console.WindowHeight != consoleDriver.Rows) {
+						GetWindowSizeEvent (new Size (Console.WindowWidth, Console.WindowHeight));
+						return;
+					}
+				} else {
+					largestWindowHeight = Math.Max (Console.BufferHeight, largestWindowHeight);
+					if (Console.BufferWidth != consoleDriver.Cols || largestWindowHeight != consoleDriver.Rows
+						|| Console.WindowHeight != lastWindowHeight) {
+						lastWindowHeight = Console.WindowHeight;
+						GetWindowSizeEvent (new Size (Console.BufferWidth, lastWindowHeight));
+						return;
+					}
+					if (Console.WindowTop != consoleDriver.Top) {
+						// Top only working on Windows.
+						var winPositionEv = new WindowPositionEvent () {
+							Top = Console.WindowTop
+						};
+						inputResultQueue.Enqueue (new InputResult () {
+							EventType = EventType.WindowPosition,
+							WindowPositionEvent = winPositionEv
+						});
+						return;
+					}
+#if PROCESS_REQUEST
+					if (!neededProcessRequest) {
+						Console.Out.Write ("\x1b[6n");
+						neededProcessRequest = true;
+					}
+#endif
+				}
+			}
+		}
+
+		void GetWindowSizeEvent (Size size)
+		{
+			WindowSizeEvent windowSizeEvent = new WindowSizeEvent () {
+				Size = size
+			};
+
+			inputResultQueue.Enqueue (new InputResult () {
+				EventType = EventType.WindowSize,
+				WindowSizeEvent = windowSizeEvent
+			});
+		}
+
 		void GetConsoleInputType (ConsoleKeyInfo consoleKeyInfo)
 		{
 			InputResult inputResult = new InputResult {
@@ -174,6 +250,7 @@ namespace Terminal.Gui {
 				}
 				break;
 			case 27:
+			case 91:
 				ConsoleKeyInfo [] cki = new ConsoleKeyInfo [] { consoleKeyInfo };
 				ConsoleModifiers mod = consoleKeyInfo.Modifiers;
 				while (Console.KeyAvailable) {
@@ -207,30 +284,67 @@ namespace Terminal.Gui {
 			ConsoleKeyInfo [] splitedCki = new ConsoleKeyInfo [] { };
 			int length = 0;
 			var kChar = GetKeyCharArray (cki);
-			var nCSI = kChar.Where (val => val == '\x1b').ToArray ().Length;
+			var nCSI = GetNumberOfCSI (kChar);
 			int curCSI = 0;
+			char previousKChar = '\0';
 			if (nCSI > 1) {
 				foreach (var ck in cki) {
 					if (NumberOfCSI > 0 && nCSI - curCSI > NumberOfCSI) {
-						if (ck.KeyChar == '\x1b') {
+						if (ck.KeyChar == '\x1b'
+							|| (ck.KeyChar == '[' && previousKChar != '\x1b')) {
 							curCSI++;
 						}
+						previousKChar = ck.KeyChar;
 						continue;
 					}
-					if (ck.KeyChar == '\x1b') {
-						if (splitedCki.Length > 0) {
+					if (ck.KeyChar == '\x1b' || (ck.KeyChar == '[' && previousKChar != '\x1b')) {
+						if (ck.KeyChar == 'R') {
+							ResizeArray (ck);
+						}
+						if (splitedCki.Length > 1) {
 							DecodeCSI (ref inputResult, ref newConsoleKeyInfo, ref key, ref mouseEvent, splitedCki, ref mod);
 						}
 						splitedCki = new ConsoleKeyInfo [] { };
 						length = 0;
 					}
-					length++;
-					Array.Resize (ref splitedCki, length);
-					splitedCki [length - 1] = ck;
+					ResizeArray (ck);
+					previousKChar = ck.KeyChar;
 				}
 			} else {
 				DecodeCSI (ref inputResult, ref newConsoleKeyInfo, ref key, ref mouseEvent, cki, ref mod);
 			}
+
+			void ResizeArray (ConsoleKeyInfo ck)
+			{
+				length++;
+				Array.Resize (ref splitedCki, length);
+				splitedCki [length - 1] = ck;
+			}
+		}
+
+		char [] GetKeyCharArray (ConsoleKeyInfo [] cki)
+		{
+			char [] kChar = new char [] { };
+			var length = 0;
+			foreach (var kc in cki) {
+				length++;
+				Array.Resize (ref kChar, length);
+				kChar [length - 1] = kc.KeyChar;
+			}
+
+			return kChar;
+		}
+
+		int GetNumberOfCSI (char [] csi)
+		{
+			int nCSI = 0;
+			for (int i = 0; i < csi.Length; i++) {
+				if (csi [i] == '\x1b' || (csi [i] == '[' && (i == 0 || (i > 0 && csi [i - 1] != '\x1b')))) {
+					nCSI++;
+				}
+			}
+
+			return nCSI;
 		}
 
 		void DecodeCSI (ref InputResult inputResult, ref ConsoleKeyInfo newConsoleKeyInfo, ref ConsoleKey key, ref MouseEvent mouseEvent, ConsoleKeyInfo [] cki, ref ConsoleModifiers mod)
@@ -306,7 +420,8 @@ namespace Terminal.Gui {
 					(mod & ConsoleModifiers.Control) != 0);
 				break;
 			case 7:
-				throw new NotImplementedException ("Condition not yet detected!");
+				GetRequestEvent (GetKeyCharArray (cki));
+				return;
 			case int n when n >= 8:
 				GetMouseEvent (cki);
 				return;
@@ -320,6 +435,56 @@ namespace Terminal.Gui {
 			inputResultQueue.Enqueue (inputResult);
 		}
 
+		Point lastCursorPosition;
+
+		void GetRequestEvent (char [] kChar)
+		{
+			EventType eventType = new EventType ();
+			Point point = new Point ();
+			int foundPoint = 0;
+			string value = "";
+			for (int i = 0; i < kChar.Length; i++) {
+				var c = kChar [i];
+				if (c == '[') {
+					foundPoint++;
+				} else if (foundPoint == 1 && c != ';') {
+					value += c.ToString ();
+				} else if (c == ';') {
+					if (foundPoint == 1) {
+						point.Y = int.Parse (value) - 1;
+					}
+					value = "";
+					foundPoint++;
+				} else if (foundPoint > 0 && i < kChar.Length - 1) {
+					value += c.ToString ();
+				} else if (i == kChar.Length - 1) {
+					point.X = int.Parse (value) + Console.WindowTop - 1;
+
+					switch (c) {
+					case 'R':
+						if (lastCursorPosition.Y != point.Y) {
+							lastCursorPosition = point;
+							eventType = EventType.WindowPosition;
+							var winPositionEv = new WindowPositionEvent () {
+								CursorPosition = point
+							};
+							inputResultQueue.Enqueue (new InputResult () {
+								EventType = eventType,
+								WindowPositionEvent = winPositionEv
+							});
+						} else {
+							return;
+						}
+						break;
+					default:
+						throw new NotImplementedException ();
+					}
+				}
+			}
+
+			inputReady.Set ();
+		}
+
 		MouseEvent lastMouseEvent;
 		bool isButtonPressed;
 		bool isButtonClicked;
@@ -369,6 +534,7 @@ namespace Terminal.Gui {
 					case 8:
 					case 16:
 					case 32:
+					case 40:
 						buttonState = c == 'M' ? MouseButtonState.Button1Pressed
 							: MouseButtonState.Button1Released;
 						break;
@@ -376,6 +542,7 @@ namespace Terminal.Gui {
 					case 9:
 					case 17:
 					case 33:
+					case 41:
 						buttonState = c == 'M' ? MouseButtonState.Button2Pressed
 							: MouseButtonState.Button2Released;
 						break;
@@ -383,6 +550,7 @@ namespace Terminal.Gui {
 					case 10:
 					case 18:
 					case 34:
+					case 42:
 						buttonState = c == 'M' ? MouseButtonState.Button3Pressed
 							: MouseButtonState.Button3Released;
 						break;
@@ -585,7 +753,7 @@ namespace Terminal.Gui {
 				await Task.Delay (200);
 				var view = Application.wantContinuousButtonPressedView;
 				if (isButtonPressed && !Console.KeyAvailable
-					&&  !isButtonClicked && !isButtonDoubleClicked && !isButtonTripleClicked
+					&& !isButtonClicked && !isButtonDoubleClicked && !isButtonTripleClicked
 					&& (view != null || view == null && lastMouseEvent.Position != point)) {
 					point = lastMouseEvent.Position;
 					inputResultQueue.Enqueue (new InputResult () {
@@ -601,19 +769,6 @@ namespace Terminal.Gui {
 			isButtonPressed = false;
 		}
 
-		char [] GetKeyCharArray (ConsoleKeyInfo [] cki)
-		{
-			char [] kChar = new char [] { };
-			var length = 0;
-			foreach (var kc in cki) {
-				length++;
-				Array.Resize (ref kChar, length);
-				kChar [length - 1] = kc.KeyChar;
-			}
-
-			return kChar;
-		}
-
 		ConsoleModifiers GetConsoleModifiers (uint keyChar)
 		{
 			switch (keyChar) {
@@ -719,7 +874,9 @@ namespace Terminal.Gui {
 
 		public enum EventType {
 			key = 1,
-			Mouse = 2
+			Mouse = 2,
+			WindowSize = 3,
+			WindowPosition = 4
 		}
 
 		[Flags]
@@ -760,10 +917,21 @@ namespace Terminal.Gui {
 			public MouseButtonState ButtonState;
 		}
 
+		public struct WindowSizeEvent {
+			public Size Size;
+		}
+
+		public struct WindowPositionEvent {
+			public int Top;
+			public Point CursorPosition;
+		}
+
 		public struct InputResult {
 			public EventType EventType;
 			public ConsoleKeyInfo ConsoleKeyInfo;
 			public MouseEvent MouseEvent;
+			public WindowSizeEvent WindowSizeEvent;
+			public WindowPositionEvent WindowPositionEvent;
 		}
 	}
 
@@ -775,16 +943,19 @@ namespace Terminal.Gui {
 		public override bool HeightAsBuffer { get; set; }
 
 		public NetWinVTConsole NetWinConsole { get; }
+		public bool IsWinPlatform { get; }
+		public bool AlwaysSetPosition { get; set; }
 
-		bool isWinPlatform;
+		int largestWindowHeight;
 
 		public NetDriver ()
 		{
 			var p = Environment.OSVersion.Platform;
 			if (p == PlatformID.Win32NT || p == PlatformID.Win32S || p == PlatformID.Win32Windows) {
-				isWinPlatform = true;
+				IsWinPlatform = true;
 				NetWinConsole = new NetWinVTConsole ();
 			}
+			largestWindowHeight = Math.Max (Console.BufferHeight, largestWindowHeight);
 		}
 
 		// The format is rows, columns and 3 values on the last column: Rune, Attribute and Dirty Flag
@@ -848,7 +1019,7 @@ namespace Terminal.Gui {
 
 		public override void End ()
 		{
-			if (isWinPlatform) {
+			if (IsWinPlatform) {
 				NetWinConsole.Cleanup ();
 			}
 
@@ -934,7 +1105,7 @@ namespace Terminal.Gui {
 					// Can raise an exception while is still resizing.
 					try {
 						// Not supported on Unix.
-						if (isWinPlatform) {
+						if (IsWinPlatform) {
 #pragma warning disable CA1416
 							Console.CursorTop = 0;
 							Console.CursorLeft = 0;
@@ -954,7 +1125,7 @@ namespace Terminal.Gui {
 					}
 				}
 			} else {
-				if (isWinPlatform && Console.WindowHeight > 0) {
+				if (IsWinPlatform && Console.WindowHeight > 0) {
 					// Can raise an exception while is still resizing.
 					try {
 #pragma warning disable CA1416
@@ -1012,11 +1183,16 @@ namespace Terminal.Gui {
 			}
 		}
 
+		public override void Refresh ()
+		{
+			UpdateScreen ();
+		}
+
 		public override void UpdateScreen ()
 		{
 			if (winChanging || Console.WindowHeight == 0 || contents.Length != Rows * Cols * 3
 				|| (!HeightAsBuffer && Rows != Console.WindowHeight)
-				|| (HeightAsBuffer && Rows != Console.BufferHeight)) {
+				|| (HeightAsBuffer && Rows != largestWindowHeight)) {
 				return;
 			}
 
@@ -1024,6 +1200,7 @@ namespace Terminal.Gui {
 			int rows = Math.Min (Console.WindowHeight + top, Rows);
 			int cols = Cols;
 
+			Console.CursorVisible = false;
 			for (int row = top; row < rows; row++) {
 				if (!dirtyLine [row]) {
 					continue;
@@ -1033,31 +1210,35 @@ namespace Terminal.Gui {
 					if (contents [row, col, 2] != 1) {
 						continue;
 					}
-					if (Console.WindowHeight > 0) {
-						// Could happens that the windows is still resizing and the col is bigger than Console.WindowWidth.
-						try {
-							Console.SetCursorPosition (col, row);
-						} catch (Exception) {
-							return;
-						}
+					if (Console.WindowHeight > 0 && !SetCursorPosition (col, row)) {
+						return;
 					}
 					for (; col < cols && contents [row, col, 2] == 1; col++) {
 						var color = contents [row, col, 1];
 						if (color != redrawColor) {
 							SetColor (color);
 						}
+						if (AlwaysSetPosition && !SetCursorPosition (col, row)) {
+							return;
+						}
 						Console.Write ((char)contents [row, col, 0]);
 						contents [row, col, 2] = 0;
 					}
 				}
 			}
-
+			Console.CursorVisible = true;
 			UpdateCursor ();
 		}
 
-		public override void Refresh ()
+		bool SetCursorPosition (int col, int row)
 		{
-			UpdateScreen ();
+			// Could happens that the windows is still resizing and the col is bigger than Console.WindowWidth.
+			try {
+				Console.SetCursorPosition (col, row);
+				return true;
+			} catch (Exception) {
+				return false;
+			}
 		}
 
 		public override void UpdateCursor ()
@@ -1230,15 +1411,42 @@ namespace Terminal.Gui {
 
 			var mLoop = mainLoop.Driver as NetMainLoop;
 
-			// Note: Net doesn't support keydown/up events and thus any passed keyDown/UpHandlers will never be called
-			mLoop.KeyPressed = (e) => ProcessInput (e);
+			// Note: Net doesn't support keydown/up events and thus any passed keyDown/UpHandlers will be simulated to be called.
+			mLoop.ProcessInput = (e) => ProcessInput (e);
+		}
 
-			mLoop.WinChanged = (e) => ChangeWin (e);
+		void ProcessInput (NetEvents.InputResult inputEvent)
+		{
+			switch (inputEvent.EventType) {
+			case NetEvents.EventType.key:
+				var map = MapKey (inputEvent.ConsoleKeyInfo);
+				if (map == (Key)0xffffffff) {
+					return;
+				}
+				keyDownHandler (new KeyEvent (map, keyModifiers));
+				keyHandler (new KeyEvent (map, keyModifiers));
+				keyUpHandler (new KeyEvent (map, keyModifiers));
+				keyModifiers = new KeyModifiers ();
+				break;
+			case NetEvents.EventType.Mouse:
+				mouseHandler (ToDriverMouse (inputEvent.MouseEvent));
+				break;
+			case NetEvents.EventType.WindowSize:
+				ChangeWin ();
+				break;
+			case NetEvents.EventType.WindowPosition:
+				var newTop = inputEvent.WindowPositionEvent.Top;
+				if (HeightAsBuffer && top != newTop) {
+					top = newTop;
+					Refresh ();
+				}
+				break;
+			}
 		}
 
 		bool winChanging;
 
-		void ChangeWin (int newTop)
+		void ChangeWin ()
 		{
 			winChanging = true;
 			const int Min_WindowWidth = 14;
@@ -1248,8 +1456,8 @@ namespace Terminal.Gui {
 					Console.WindowHeight);
 				top = 0;
 			} else {
-				size = new Size (Console.BufferWidth, Console.BufferHeight);
-				top = newTop;
+				largestWindowHeight = Math.Max (Console.BufferHeight, largestWindowHeight);
+				size = new Size (Console.BufferWidth, largestWindowHeight);
 			}
 			cols = size.Width;
 			rows = size.Height;
@@ -1260,25 +1468,6 @@ namespace Terminal.Gui {
 			}
 		}
 
-		void ProcessInput (NetEvents.InputResult inputEvent)
-		{
-			switch (inputEvent.EventType) {
-			case NetEvents.EventType.key:
-				var map = MapKey (inputEvent.ConsoleKeyInfo);
-				if (map == (Key)0xffffffff) {
-					return;
-				}
-				keyDownHandler (new KeyEvent (map, keyModifiers));
-				keyHandler (new KeyEvent (map, keyModifiers));
-				keyUpHandler (new KeyEvent (map, keyModifiers));
-				keyModifiers = new KeyModifiers ();
-				break;
-			case NetEvents.EventType.Mouse:
-				mouseHandler (ToDriverMouse (inputEvent.MouseEvent));
-				break;
-			}
-		}
-
 		MouseEvent ToDriverMouse (NetEvents.MouseEvent me)
 		{
 			MouseFlags mouseFlag = 0;
@@ -1416,24 +1605,15 @@ namespace Terminal.Gui {
 	internal class NetMainLoop : IMainLoopDriver {
 		ManualResetEventSlim keyReady = new ManualResetEventSlim (false);
 		ManualResetEventSlim waitForProbe = new ManualResetEventSlim (false);
-		ManualResetEventSlim winChange = new ManualResetEventSlim (false);
 		Queue<NetEvents.InputResult?> inputResult = new Queue<NetEvents.InputResult?> ();
 		MainLoop mainLoop;
-		ConsoleDriver consoleDriver;
-		bool winChanged;
-		int newTop;
 		CancellationTokenSource tokenSource = new CancellationTokenSource ();
 		NetEvents netEvents;
 
 		/// <summary>
 		/// Invoked when a Key is pressed.
 		/// </summary>
-		public Action<NetEvents.InputResult> KeyPressed;
-
-		/// <summary>
-		/// Invoked when the window is changed.
-		/// </summary>
-		public Action<int> WinChanged;
+		public Action<NetEvents.InputResult> ProcessInput;
 
 		/// <summary>
 		/// Initializes the class with the console driver.
@@ -1447,11 +1627,10 @@ namespace Terminal.Gui {
 			if (consoleDriver == null) {
 				throw new ArgumentNullException ("Console driver instance must be provided.");
 			}
-			this.consoleDriver = consoleDriver;
-			netEvents = new NetEvents ();
+			netEvents = new NetEvents (consoleDriver);
 		}
 
-		void KeyReader ()
+		void NetInputHandler ()
 		{
 			while (true) {
 				waitForProbe.Wait ();
@@ -1470,43 +1649,10 @@ namespace Terminal.Gui {
 			}
 		}
 
-		void CheckWinChange ()
-		{
-			while (true) {
-				winChange.Wait ();
-				winChange.Reset ();
-				WaitWinChange ();
-				winChanged = true;
-				keyReady.Set ();
-			}
-		}
-
-		int lastWindowHeight;
-		void WaitWinChange ()
-		{
-			while (true) {
-				if (!consoleDriver.HeightAsBuffer) {
-					if (Console.WindowWidth != consoleDriver.Cols || Console.WindowHeight != consoleDriver.Rows) {
-						return;
-					}
-				} else {
-					if (Console.BufferWidth != consoleDriver.Cols || Console.BufferHeight != consoleDriver.Rows
-						|| Console.WindowTop != consoleDriver.Top
-						|| Console.WindowHeight != lastWindowHeight) {
-						// Top only working on Windows.
-						newTop = Console.WindowTop;
-						lastWindowHeight = Console.WindowHeight;
-						return;
-					}
-				}
-			}
-		}
-
 		void IMainLoopDriver.Setup (MainLoop mainLoop)
 		{
 			this.mainLoop = mainLoop;
-			Task.Run (KeyReader);
-			Task.Run (CheckWinChange);
+			Task.Run (NetInputHandler);
 		}
 
 		void IMainLoopDriver.Wakeup ()
@@ -1517,7 +1663,6 @@ namespace Terminal.Gui {
 		bool IMainLoopDriver.EventsPending (bool wait)
 		{
 			waitForProbe.Set ();
-			winChange.Set ();
 
 			if (CheckTimers (wait, out var waitTimeout)) {
 				return true;
@@ -1534,7 +1679,7 @@ namespace Terminal.Gui {
 			}
 
 			if (!tokenSource.IsCancellationRequested) {
-				return inputResult.Count > 0 || CheckTimers (wait, out _) || winChanged;
+				return inputResult.Count > 0 || CheckTimers (wait, out _);
 			}
 
 			tokenSource.Dispose ();
@@ -1568,11 +1713,7 @@ namespace Terminal.Gui {
 		void IMainLoopDriver.MainIteration ()
 		{
 			if (inputResult.Count > 0) {
-				KeyPressed?.Invoke (inputResult.Dequeue ().Value);
-			}
-			if (winChanged) {
-				winChanged = false;
-				WinChanged?.Invoke (newTop);
+				ProcessInput?.Invoke (inputResult.Dequeue ().Value);
 			}
 		}
 	}

+ 18 - 0
Terminal.Gui/Core/Application.cs

@@ -99,6 +99,24 @@ namespace Terminal.Gui {
 			}
 		}
 
+		/// <summary>
+		/// Used only by <see cref="NetDriver"/> to forcing always setting the cursor position when writing to the screen.
+		/// </summary>
+		public static bool AlwaysSetPosition {
+			get {
+				if (Driver is NetDriver) {
+					return (Driver as NetDriver).AlwaysSetPosition;
+				}
+				return false;
+			}
+			set {
+				if (Driver is NetDriver) {
+					(Driver as NetDriver).AlwaysSetPosition = value;
+					Driver.Refresh ();
+				}
+			}
+		}
+
 		/// <summary>
 		/// The <see cref="MainLoop"/>  driver for the application
 		/// </summary>

+ 24 - 1
UICatalog/UICatalog.cs

@@ -63,6 +63,8 @@ namespace UICatalog {
 		private static Scenario _runningScenario = null;
 		private static bool _useSystemConsole = false;
 		private static ConsoleDriver.DiagnosticFlags _diagnosticFlags;
+		private static bool _heightAsBuffer;
+		private static bool _alwaysSetPosition;
 
 		static void Main (string [] args)
 		{
@@ -144,6 +146,8 @@ namespace UICatalog {
 		{
 			Application.UseSystemConsole = _useSystemConsole;
 			Application.Init ();
+			Application.HeightAsBuffer = _heightAsBuffer;
+			Application.AlwaysSetPosition = _alwaysSetPosition;
 
 			// Set this here because not initialized until driver is loaded
 			_baseColorScheme = Colors.Base;
@@ -280,9 +284,27 @@ namespace UICatalog {
 			menuItems.Add (CreateDiagnosticFlagsMenuItems ());
 			menuItems.Add (new MenuItem [] { null });
 			menuItems.Add (CreateSizeStyle ());
+			menuItems.Add (CreateAlwaysSetPosition ());
 			return menuItems;
 		}
 
+		static MenuItem [] CreateAlwaysSetPosition ()
+		{
+			List<MenuItem> menuItems = new List<MenuItem> ();
+			var item = new MenuItem ();
+			item.Title = "_Always set position (NetDriver only)";
+			item.Shortcut = Key.CtrlMask | Key.AltMask | (Key)item.Title.ToString ().Substring (1, 1) [0];
+			item.CheckType |= MenuItemCheckStyle.Checked;
+			item.Checked = Application.AlwaysSetPosition;
+			item.Action += () => {
+				Application.AlwaysSetPosition = !item.Checked;
+				item.Checked = _alwaysSetPosition = Application.AlwaysSetPosition;
+			};
+			menuItems.Add (item);
+
+			return menuItems.ToArray ();
+		}
+
 		static MenuItem [] CreateSizeStyle ()
 		{
 			List<MenuItem> menuItems = new List<MenuItem> ();
@@ -293,7 +315,8 @@ namespace UICatalog {
 			item.Checked = Application.HeightAsBuffer;
 			item.Action += () => {
 				item.Checked = !item.Checked;
-				Application.HeightAsBuffer = item.Checked;
+				_heightAsBuffer = item.Checked;
+				Application.HeightAsBuffer = _heightAsBuffer;
 			};
 			menuItems.Add (item);