فهرست منبع

Fixes drivers for wide runes handling which also works on Windows Terminal.

BDisp 3 سال پیش
والد
کامیت
311acc03df

+ 28 - 8
Terminal.Gui/ConsoleDrivers/CursesDriver/CursesDriver.cs

@@ -52,27 +52,47 @@ namespace Terminal.Gui {
 		static bool sync = false;
 		public override void AddRune (Rune rune)
 		{
-			if (Clip.Contains (ccol, crow)) {
+			rune = MakePrintable (rune);
+			var runeWidth = Rune.ColumnWidth (rune);
+			var validClip = IsValidContent (ccol, crow, Clip);
+
+			if (validClip) {
 				if (needMove) {
 					Curses.move (crow, ccol);
 					needMove = false;
 				}
-				rune = MakePrintable (rune);
+				if (runeWidth < 2 && ccol > 0
+					&& Rune.ColumnWidth ((char)contents [crow, ccol - 1, 0]) > 1) {
+
+					Curses.move (crow, ccol - 1);
+					Curses.addch ((int)(uint)' ');
+					contents [crow, ccol - 1, 0] = (int)(uint)' ';
+					Curses.move (crow, ccol);
+				} else if (runeWidth < 2 && ccol < Cols - 1 && Rune.ColumnWidth ((char)contents [crow, ccol, 0]) > 1) {
+
+					Curses.move (crow, ccol + 1);
+					Curses.addch ((int)(uint)' ');
+					contents [crow, ccol + 1, 0] = (int)(uint)' ';
+					Curses.move (crow, ccol);
+				}
 				Curses.addch ((int)(uint)rune);
 				contents [crow, ccol, 0] = (int)(uint)rune;
 				contents [crow, ccol, 1] = currentAttribute;
 				contents [crow, ccol, 2] = 1;
 			} else
 				needMove = true;
-			if (sync)
-				Application.Driver.Refresh ();
+
 			ccol++;
-			var runeWidth = Rune.ColumnWidth (rune);
 			if (runeWidth > 1) {
-				for (int i = 1; i < runeWidth; i++) {
-					ccol++;
+				if (validClip) {
+					contents [crow, ccol, 1] = currentAttribute;
+					contents [crow, ccol, 2] = 0;
 				}
+				ccol++;
 			}
+
+			if (sync)
+				UpdateScreen ();
 		}
 
 		public override void AddStr (ustring str)
@@ -933,7 +953,7 @@ namespace Terminal.Gui {
 			}
 		}
 
-		void UpdateOffScreen ()
+		public override void UpdateOffScreen ()
 		{
 			contents = new int [Rows, Cols, 3];
 			for (int row = 0; row < Rows; row++) {

+ 5 - 2
Terminal.Gui/ConsoleDrivers/FakeDriver/FakeConsole.cs

@@ -922,6 +922,7 @@ namespace Terminal.Gui {
 		{
 			BufferWidth = width;
 			BufferHeight = height;
+			_buffer = new char [BufferWidth, BufferHeight];
 		}
 
 		//
@@ -1060,7 +1061,8 @@ namespace Terminal.Gui {
 		/// <param name="top"></param>
 		public static void SetWindowPosition (int left, int top)
 		{
-			throw new NotImplementedException ();
+			WindowLeft = left;
+			WindowTop = top;
 		}
 
 		//
@@ -1094,7 +1096,8 @@ namespace Terminal.Gui {
 		/// <param name="height"></param>
 		public static void SetWindowSize (int width, int height)
 		{
-			throw new NotImplementedException ();
+			WindowWidth = width;
+			WindowHeight = height;
 		}
 
 		//

+ 106 - 79
Terminal.Gui/ConsoleDrivers/FakeDriver/FakeDriver.cs

@@ -37,23 +37,23 @@ namespace Terminal.Gui {
 		/// </summary>
 		internal override int [,,] Contents => contents;
 
-		void UpdateOffscreen ()
-		{
-			int cols = Cols;
-			int rows = Rows;
-
-			contents = new int [rows, cols, 3];
-			for (int r = 0; r < rows; r++) {
-				for (int c = 0; c < cols; c++) {
-					contents [r, c, 0] = ' ';
-					contents [r, c, 1] = MakeColor (ConsoleColor.Gray, ConsoleColor.Black);
-					contents [r, c, 2] = 0;
-				}
-			}
-			dirtyLine = new bool [rows];
-			for (int row = 0; row < rows; row++)
-				dirtyLine [row] = true;
-		}
+		//void UpdateOffscreen ()
+		//{
+		//	int cols = Cols;
+		//	int rows = Rows;
+
+		//	contents = new int [rows, cols, 3];
+		//	for (int r = 0; r < rows; r++) {
+		//		for (int c = 0; c < cols; c++) {
+		//			contents [r, c, 0] = ' ';
+		//			contents [r, c, 1] = MakeColor (ConsoleColor.Gray, ConsoleColor.Black);
+		//			contents [r, c, 2] = 0;
+		//		}
+		//	}
+		//	dirtyLine = new bool [rows];
+		//	for (int row = 0; row < rows; row++)
+		//		dirtyLine [row] = true;
+		//}
 
 		static bool sync = false;
 
@@ -89,13 +89,14 @@ namespace Terminal.Gui {
 				FakeConsole.CursorLeft = Clip.X;
 				needMove = true;
 			}
-
 		}
 
 		public override void AddRune (Rune rune)
 		{
 			rune = MakePrintable (rune);
-			if (Clip.Contains (ccol, crow)) {
+			var validClip = IsValidContent (ccol, crow, Clip);
+
+			if (validClip) {
 				if (needMove) {
 					//MockConsole.CursorLeft = ccol;
 					//MockConsole.CursorTop = crow;
@@ -107,7 +108,20 @@ namespace Terminal.Gui {
 				dirtyLine [crow] = true;
 			} else
 				needMove = true;
+
 			ccol++;
+			var runeWidth = Rune.ColumnWidth (rune);
+			if (runeWidth > 1) {
+				for (int i = 1; i < runeWidth; i++) {
+					if (validClip) {
+						contents [crow, ccol, 1] = currentAttribute;
+						contents [crow, ccol, 2] = 0;
+					} else {
+						break;
+					}
+					ccol++;
+				}
+			}
 			//if (ccol == Cols) {
 			//	ccol = 0;
 			//	if (crow + 1 < Rows)
@@ -218,26 +232,7 @@ namespace Terminal.Gui {
 		{
 			int top = Top;
 			int left = Left;
-			int rows = Math.Min (Console.WindowHeight + top, Rows);
-			int cols = Cols;
-
-			FakeConsole.CursorTop = 0;
-			FakeConsole.CursorLeft = 0;
-			for (int row = top; row < rows; row++) {
-				dirtyLine [row] = false;
-				for (int col = left; col < cols; col++) {
-					contents [row, col, 2] = 0;
-					var color = contents [row, col, 1];
-					if (color != redrawColor)
-						SetColor (color);
-					FakeConsole.Write ((char)contents [row, col, 0]);
-				}
-			}
-		}
-
-		public override void Refresh ()
-		{
-			int rows = Rows;
+			int rows = Math.Min (FakeConsole.WindowHeight + top, Rows);
 			int cols = Cols;
 
 			var savedRow = FakeConsole.CursorTop;
@@ -247,12 +242,21 @@ namespace Terminal.Gui {
 					continue;
 				dirtyLine [row] = false;
 				for (int col = 0; col < cols; col++) {
-					if (contents [row, col, 2] != 1)
-						continue;
-
 					FakeConsole.CursorTop = row;
 					FakeConsole.CursorLeft = col;
-					for (; col < cols && contents [row, col, 2] == 1; col++) {
+					for (; col < cols; col++) {
+						if (col > 0 && contents [row, col, 2] == 0
+							&& Rune.ColumnWidth ((char)contents [row, col - 1, 0]) > 1) {
+							FakeConsole.CursorLeft = col + 1;
+							continue;
+						}
+
+						if (col < cols - 1 && Rune.ColumnWidth ((char)contents [row, col, 0]) > 1
+							&& (contents [row, col + 1, 2] == 1 || col == cols - 1)) {
+
+							contents [row, col, 0] = ' ';
+						}
+
 						var color = contents [row, col, 1];
 						if (color != redrawColor)
 							SetColor (color);
@@ -266,6 +270,12 @@ namespace Terminal.Gui {
 			FakeConsole.CursorLeft = savedCol;
 		}
 
+		public override void Refresh ()
+		{
+			UpdateScreen ();
+			UpdateCursor ();
+		}
+
 		Attribute currentAttribute;
 		public override void SetAttribute (Attribute c)
 		{
@@ -388,6 +398,7 @@ namespace Terminal.Gui {
 
 		Action<KeyEvent> keyHandler;
 		Action<KeyEvent> keyUpHandler;
+		private CursorVisibility savedCursorVisibility;
 
 		public override void PrepareToRun (MainLoop mainLoop, Action<KeyEvent> keyHandler, Action<KeyEvent> keyDownHandler, Action<KeyEvent> keyUpHandler, Action<MouseEvent> mouseHandler)
 		{
@@ -427,31 +438,32 @@ namespace Terminal.Gui {
 		/// <inheritdoc/>
 		public override bool GetCursorVisibility (out CursorVisibility visibility)
 		{
-			if (FakeConsole.CursorVisible) {
-				visibility = CursorVisibility.Default;
-			} else {
-				visibility = CursorVisibility.Invisible;
-			}
+			visibility = FakeConsole.CursorVisible
+				? CursorVisibility.Default
+				: CursorVisibility.Invisible;
 
-			return false;
+			return FakeConsole.CursorVisible;
 		}
 
 		/// <inheritdoc/>
 		public override bool SetCursorVisibility (CursorVisibility visibility)
 		{
-			if (visibility == CursorVisibility.Invisible) {
-				FakeConsole.CursorVisible = false;
-			} else {
-				FakeConsole.CursorVisible = true;
-			}
-
-			return false;
+			savedCursorVisibility = visibility;
+			return FakeConsole.CursorVisible = visibility == CursorVisibility.Default;
 		}
 
 		/// <inheritdoc/>
 		public override bool EnsureCursorVisibility ()
 		{
-			return false;
+			if (!(ccol >= 0 && crow >= 0 && ccol < Cols && crow < Rows)) {
+				GetCursorVisibility (out CursorVisibility cursorVisibility);
+				savedCursorVisibility = cursorVisibility;
+				SetCursorVisibility (CursorVisibility.Invisible);
+				return false;
+			}
+
+			SetCursorVisibility (savedCursorVisibility);
+			return FakeConsole.CursorVisible;
 		}
 
 		public override void SendKeys (char keyChar, ConsoleKey key, bool shift, bool alt, bool control)
@@ -461,20 +473,24 @@ namespace Terminal.Gui {
 
 		public void SetBufferSize (int width, int height)
 		{
-			cols = FakeConsole.WindowWidth = FakeConsole.BufferWidth = width;
-			rows = FakeConsole.WindowHeight = FakeConsole.BufferHeight = height;
+			FakeConsole.SetBufferSize (width, height);
+			cols = width;
+			rows = height;
+			if (!HeightAsBuffer) {
+				SetWindowSize (width, height);
+			}
 			ProcessResize ();
 		}
 
 		public void SetWindowSize (int width, int height)
 		{
-			FakeConsole.WindowWidth = width;
-			FakeConsole.WindowHeight = height;
-			if (width > cols || !HeightAsBuffer) {
-				cols = FakeConsole.BufferWidth = width;
-			}
-			if (height > rows || !HeightAsBuffer) {
-				rows = FakeConsole.BufferHeight = height;
+			FakeConsole.SetWindowSize (width, height);
+			if (!HeightAsBuffer) {
+				if (width != cols || height != rows) {
+					SetBufferSize (width, height);
+					cols = width;
+					rows = height;
+				}
 			}
 			ProcessResize ();
 		}
@@ -482,12 +498,13 @@ namespace Terminal.Gui {
 		public void SetWindowPosition (int left, int top)
 		{
 			if (HeightAsBuffer) {
-				this.left = FakeConsole.WindowLeft = Math.Max (Math.Min (left, Cols - FakeConsole.WindowWidth), 0);
-				this.top = FakeConsole.WindowTop = Math.Max (Math.Min (top, Rows - Console.WindowHeight), 0);
+				this.left = Math.Max (Math.Min (left, Cols - FakeConsole.WindowWidth), 0);
+				this.top = Math.Max (Math.Min (top, Rows - FakeConsole.WindowHeight), 0);
 			} else if (this.left > 0 || this.top > 0) {
-				this.left = FakeConsole.WindowLeft = 0;
-				this.top = FakeConsole.WindowTop = 0;
+				this.left = 0;
+				this.top = 0;
 			}
+			FakeConsole.SetWindowPosition (this.left, this.top);
 		}
 
 		void ProcessResize ()
@@ -500,14 +517,14 @@ namespace Terminal.Gui {
 		void ResizeScreen ()
 		{
 			if (!HeightAsBuffer) {
-				if (Console.WindowHeight > 0) {
+				if (FakeConsole.WindowHeight > 0) {
 					// Can raise an exception while is still resizing.
 					try {
 #pragma warning disable CA1416
-						Console.CursorTop = 0;
-						Console.CursorLeft = 0;
-						Console.WindowTop = 0;
-						Console.WindowLeft = 0;
+						FakeConsole.CursorTop = 0;
+						FakeConsole.CursorLeft = 0;
+						FakeConsole.WindowTop = 0;
+						FakeConsole.WindowLeft = 0;
 #pragma warning restore CA1416
 					} catch (System.IO.IOException) {
 						return;
@@ -518,8 +535,8 @@ namespace Terminal.Gui {
 			} else {
 				try {
 #pragma warning disable CA1416
-					Console.WindowLeft = Math.Max (Math.Min (left, Cols - Console.WindowWidth), 0);
-					Console.WindowTop = Math.Max (Math.Min (top, Rows - Console.WindowHeight), 0);
+					FakeConsole.WindowLeft = Math.Max (Math.Min (left, Cols - FakeConsole.WindowWidth), 0);
+					FakeConsole.WindowTop = Math.Max (Math.Min (top, Rows - FakeConsole.WindowHeight), 0);
 #pragma warning restore CA1416
 				} catch (Exception) {
 					return;
@@ -532,7 +549,7 @@ namespace Terminal.Gui {
 			dirtyLine = new bool [Rows];
 		}
 
-		void UpdateOffScreen ()
+		public override void UpdateOffScreen ()
 		{
 			// Can raise an exception while is still resizing.
 			try {
@@ -569,7 +586,17 @@ namespace Terminal.Gui {
 		#region Unused
 		public override void UpdateCursor ()
 		{
-			//
+			if (!EnsureCursorVisibility ())
+				return;
+
+			// Prevents the exception of size changing during resizing.
+			try {
+				if (ccol >= 0 && ccol < FakeConsole.BufferWidth && crow >= 0 && crow < FakeConsole.BufferHeight) {
+					FakeConsole.SetCursorPosition (ccol, crow);
+				}
+			} catch (System.IO.IOException) {
+			} catch (ArgumentOutOfRangeException) {
+			}
 		}
 
 		public override void StartReportingMouseMoves ()

+ 72 - 40
Terminal.Gui/ConsoleDrivers/NetDriver.cs

@@ -1216,6 +1216,7 @@ namespace Terminal.Gui {
 
 		// Current row, and current col, tracked by Move/AddCh only
 		int ccol, crow;
+
 		public override void Move (int col, int row)
 		{
 			ccol = col;
@@ -1229,26 +1230,23 @@ namespace Terminal.Gui {
 			}
 			rune = MakePrintable (rune);
 			var runeWidth = Rune.ColumnWidth (rune);
-			if (Clip.Contains (ccol, crow) && ccol + Math.Max (runeWidth, 1) <= Cols) {
+			var validClip = IsValidContent (ccol, crow, Clip);
+
+			if (validClip) {
 				contents [crow, ccol, 0] = (int)(uint)rune;
 				contents [crow, ccol, 1] = currentAttribute;
 				contents [crow, ccol, 2] = 1;
+
 				dirtyLine [crow] = true;
+			}
 
-				ccol++;
-				if (runeWidth > 1) {
-					for (int i = 1; i < runeWidth; i++) {
-						if (ccol < cols) {
-							contents [crow, ccol, 2] = 0;
-						} else {
-							break;
-						}
-						ccol++;
-					}
+			ccol++;
+			if (runeWidth > 1) {
+				if (validClip) {
+					contents [crow, ccol, 1] = currentAttribute;
+					contents [crow, ccol, 2] = 0;
 				}
-			} else if (ccol > -1 && crow > -1 && ccol < cols && crow < rows) {
-				contents [crow, ccol, 2] = 1;
-				dirtyLine [crow] = true;
+				ccol++;
 			}
 
 			//if (ccol == Cols) {
@@ -1306,7 +1304,6 @@ namespace Terminal.Gui {
 			cols = Console.WindowWidth;
 			rows = Console.WindowHeight;
 
-			Clear ();
 			ResizeScreen ();
 			UpdateOffScreen ();
 
@@ -1352,6 +1349,8 @@ namespace Terminal.Gui {
 			Colors.Error.HotNormal = MakeColor (ConsoleColor.Black, ConsoleColor.White);
 			Colors.Error.HotFocus = MakeColor (ConsoleColor.Black, ConsoleColor.DarkRed);
 			Colors.Error.Disabled = MakeColor (ConsoleColor.DarkGray, ConsoleColor.White);
+
+			Clear ();
 		}
 
 		void ResizeScreen ()
@@ -1395,28 +1394,29 @@ namespace Terminal.Gui {
 						$";{Rows};{Cols}w");
 				}
 			}
-
 			Clip = new Rect (0, 0, Cols, Rows);
+		}
 
+		public override void UpdateOffScreen ()
+		{
 			contents = new int [Rows, Cols, 3];
 			dirtyLine = new bool [Rows];
-		}
 
-		void UpdateOffScreen ()
-		{
-			// Can raise an exception while is still resizing.
-			try {
-				for (int row = 0; row < rows; row++) {
-					for (int c = 0; c < cols; c++) {
-						contents [row, c, 0] = ' ';
-						contents [row, c, 1] = (ushort)Colors.TopLevel.Normal;
-						contents [row, c, 2] = 0;
-						dirtyLine [row] = true;
+			lock (contents) {
+				// Can raise an exception while is still resizing.
+				try {
+					for (int row = 0; row < rows; row++) {
+						for (int c = 0; c < cols; c++) {
+							contents [row, c, 0] = ' ';
+							contents [row, c, 1] = (ushort)Colors.TopLevel.Normal;
+							contents [row, c, 2] = 0;
+							dirtyLine [row] = true;
+						}
 					}
-				}
-			} catch (IndexOutOfRangeException) { }
+				} catch (IndexOutOfRangeException) { }
 
-			winChanging = false;
+				winChanging = false;
+			}
 		}
 
 		public override Attribute MakeAttribute (Color fore, Color back)
@@ -1427,6 +1427,7 @@ namespace Terminal.Gui {
 		public override void Refresh ()
 		{
 			UpdateScreen ();
+			UpdateCursor ();
 		}
 
 		int redrawAttr = -1;
@@ -1443,7 +1444,7 @@ namespace Terminal.Gui {
 			int rows = Math.Min (Console.WindowHeight + top, Rows);
 			int cols = Cols;
 
-			Console.CursorVisible = false;
+			var savedCursorVisible = Console.CursorVisible = false;
 			for (int row = top; row < rows; row++) {
 				if (!dirtyLine [row]) {
 					continue;
@@ -1454,13 +1455,29 @@ namespace Terminal.Gui {
 					if (Console.WindowHeight > 0 && !SetCursorPosition (col, row)) {
 						return;
 					}
+					var lastCol = -1;
 					for (; col < cols; col++) {
-						if (contents [row, col, 2] != 1) {
+						if (col > 0 && contents [row, col, 2] == 0
+							&& Rune.ColumnWidth ((char)contents [row, col - 1, 0]) > 1) {
+
+							if (col == cols - 1 && output.Length > 0) {
+								Console.CursorLeft = lastCol;
+								Console.Write (output);
+							}
 							continue;
 						}
+
+						if (col < cols - 1 && Rune.ColumnWidth ((char)contents [row, col, 0]) > 1
+							&& (contents [row, col + 1, 2] == 1 || col == cols - 1)) {
+
+							contents [row, col, 0] = ' ';
+						}
+
 						var attr = contents [row, col, 1];
 						if (attr != redrawAttr) {
 							output.Append (WriteAttributes (attr));
+							if (lastCol == -1)
+								lastCol = col;
 						}
 						if (AlwaysSetPosition && !SetCursorPosition (col, row)) {
 							return;
@@ -1469,6 +1486,8 @@ namespace Terminal.Gui {
 							Console.Write ($"{output}{(char)contents [row, col, 0]}");
 						} else {
 							output.Append ((char)contents [row, col, 0]);
+							if (lastCol == -1)
+								lastCol = col;
 						}
 						contents [row, col, 2] = 0;
 						if (!AlwaysSetPosition && col == cols - 1) {
@@ -1477,8 +1496,7 @@ namespace Terminal.Gui {
 					}
 				}
 			}
-			Console.CursorVisible = true;
-			UpdateCursor ();
+			Console.CursorVisible = savedCursorVisible;
 		}
 
 		System.Text.StringBuilder WriteAttributes (int attr)
@@ -1553,11 +1571,16 @@ namespace Terminal.Gui {
 			}
 		}
 
+		private CursorVisibility? savedCursorVisibility;
+
 		public override void UpdateCursor ()
 		{
+			if (!EnsureCursorVisibility ())
+				return;
+
 			// Prevents the exception of size changing during resizing.
 			try {
-				if (ccol >= 0 && ccol <= cols && crow >= 0 && crow <= rows) {
+				if (ccol >= 0 && ccol < Console.BufferWidth && crow >= 0 && crow < Console.BufferHeight) {
 					Console.SetCursorPosition (ccol, crow);
 				}
 			} catch (System.IO.IOException) {
@@ -1885,21 +1908,30 @@ namespace Terminal.Gui {
 		/// <inheritdoc/>
 		public override bool GetCursorVisibility (out CursorVisibility visibility)
 		{
-			visibility = CursorVisibility.Default;
-
-			return false;
+			visibility = savedCursorVisibility ?? CursorVisibility.Default;
+			return visibility == CursorVisibility.Default;
 		}
 
+
 		/// <inheritdoc/>
 		public override bool SetCursorVisibility (CursorVisibility visibility)
 		{
-			return false;
+			savedCursorVisibility = visibility;
+			return Console.CursorVisible = visibility == CursorVisibility.Default;
 		}
 
 		/// <inheritdoc/>
 		public override bool EnsureCursorVisibility ()
 		{
-			return false;
+			if (!(ccol >= 0 && crow >= 0 && ccol < Cols && crow < Rows)) {
+				GetCursorVisibility (out CursorVisibility cursorVisibility);
+				savedCursorVisibility = cursorVisibility;
+				SetCursorVisibility (CursorVisibility.Invisible);
+				return false;
+			}
+
+			SetCursorVisibility (savedCursorVisibility ?? CursorVisibility.Default);
+			return savedCursorVisibility == CursorVisibility.Default;
 		}
 
 		public override void SendKeys (char keyChar, ConsoleKey key, bool shift, bool alt, bool control)

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

@@ -1443,7 +1443,7 @@ namespace Terminal.Gui {
 			WinConsole.ForceRefreshCursorVisibility ();
 		}
 
-		void UpdateOffScreen ()
+		public override void UpdateOffScreen ()
 		{
 			contents = new int [rows, cols, 3];
 			for (int row = 0; row < rows; row++) {
@@ -1468,9 +1468,26 @@ namespace Terminal.Gui {
 		public override void AddRune (Rune rune)
 		{
 			rune = MakePrintable (rune);
+			var runeWidth = Rune.ColumnWidth (rune);
 			var position = crow * Cols + ccol;
+			var validClip = IsValidContent (ccol, crow, Clip);
+
+			if (validClip) {
+				if (runeWidth < 2 && ccol > 0
+					&& Rune.ColumnWidth ((char)contents [crow, ccol - 1, 0]) > 1) {
+
+					var prevPosition = crow * Cols + (ccol - 1);
+					OutputBuffer [prevPosition].Char.UnicodeChar = ' ';
+					contents [crow, ccol - 1, 0] = (int)(uint)' ';
+
+				} else if (runeWidth < 2 && ccol < Cols - 1 && Rune.ColumnWidth ((char)contents [crow, ccol, 0]) > 1) {
+
+					var prevPosition = crow * Cols + ccol + 1;
+					OutputBuffer [prevPosition].Char.UnicodeChar = (char)' ';
+					contents [crow, ccol + 1, 0] = (int)(uint)' ';
+				} else if (rune != ' ') {
 
-			if (Clip.Contains (ccol, crow)) {
+				}
 				OutputBuffer [position].Attributes = (ushort)currentAttribute;
 				OutputBuffer [position].Char.UnicodeChar = (char)rune;
 				contents [crow, ccol, 0] = (int)(uint)rune;
@@ -1480,17 +1497,18 @@ namespace Terminal.Gui {
 			}
 
 			ccol++;
-			var runeWidth = Rune.ColumnWidth (rune);
 			if (runeWidth > 1) {
-				for (int i = 1; i < runeWidth; i++) {
-					AddStr (" ");
+				if (validClip) {
+					position = crow * Cols + ccol;
+					OutputBuffer [position].Attributes = (ushort)currentAttribute;
+					OutputBuffer [position].Char.UnicodeChar = '\0';
+					contents [crow, ccol, 0] = (int)(uint)'\0';
+					contents [crow, ccol, 1] = currentAttribute;
+					contents [crow, ccol, 2] = 0;
 				}
+				ccol++;
 			}
-			//if (ccol == Cols) {
-			//	ccol = 0;
-			//	if (crow + 1 < Rows)
-			//		crow++;
-			//}
+
 			if (sync)
 				UpdateScreen ();
 		}

+ 17 - 1
Terminal.Gui/Core/ConsoleDriver.cs

@@ -701,6 +701,16 @@ namespace Terminal.Gui {
 			}
 		}
 
+		/// <summary>
+		/// Ensures that the column and line are in a valid range from the size of the driver.
+		/// </summary>
+		/// <param name="col">The column.</param>
+		/// <param name="row">The row.</param>
+		/// <param name="clip">The clip.</param>
+		/// <returns><c>true</c>if it's a valid range,<c>false</c>otherwise.</returns>
+		public bool IsValidContent (int col, int row, Rect clip) =>
+			col >= 0 && row >= 0 && col < Cols && row < Rows && clip.Contains (col, row);
+
 		/// <summary>
 		/// Adds the specified
 		/// </summary>
@@ -751,6 +761,11 @@ namespace Terminal.Gui {
 		/// </summary>
 		public abstract void End ();
 
+		/// <summary>
+		/// Reset and recreate the contents and the driver buffer.
+		/// </summary>
+		public abstract void UpdateOffScreen ();
+
 		/// <summary>
 		/// Redraws the physical screen with the contents that have been queued up via any of the printing commands.
 		/// </summary>
@@ -824,7 +839,8 @@ namespace Terminal.Gui {
 			if (!ustring.IsNullOrEmpty (title) && width > 4 && region.Y + paddingTop <= region.Y + paddingBottom) {
 				Move (region.X + 1 + paddingLeft, region.Y + paddingTop);
 				AddRune (' ');
-				var str = title.RuneCount >= width ? title [0, width - 2] : title;
+				var str = title.Sum (r => Math.Max (Rune.ColumnWidth (r), 1)) >= width
+					? TextFormatter.Format (title, width - 2, false, false) [0] : title;
 				AddStr (str);
 				AddRune (' ');
 			}

+ 9 - 5
UnitTests/ConsoleDriverTests.cs

@@ -339,10 +339,10 @@ namespace Terminal.Gui.ConsoleDrivers {
 			Assert.Equal (25, Application.Driver.Rows);
 			Assert.Equal (120, Console.BufferWidth);
 			Assert.Equal (25, Console.BufferHeight);
-			Assert.Equal (120, Console.WindowWidth);
+			Assert.Equal (80, Console.WindowWidth);
 			Assert.Equal (25, Console.WindowHeight);
 			driver.SetWindowPosition (121, 25);
-			Assert.Equal (0, Console.WindowLeft);
+			Assert.Equal (40, Console.WindowLeft);
 			Assert.Equal (0, Console.WindowTop);
 
 			driver.SetWindowSize (90, 25);
@@ -388,17 +388,19 @@ namespace Terminal.Gui.ConsoleDrivers {
 			Assert.Equal (0, Console.WindowLeft);
 			Assert.Equal (0, Console.WindowTop);
 
-			// MockDriver will now be sets to 120x25
+			// MockDriver will now be sets to 80x40
 			driver.SetBufferSize (80, 40);
 			Assert.Equal (80, Application.Driver.Cols);
 			Assert.Equal (40, Application.Driver.Rows);
 			Assert.Equal (80, Console.BufferWidth);
 			Assert.Equal (40, Console.BufferHeight);
 			Assert.Equal (80, Console.WindowWidth);
-			Assert.Equal (40, Console.WindowHeight);
-			driver.SetWindowPosition (80, 40);
+			Assert.Equal (25, Console.WindowHeight);
 			Assert.Equal (0, Console.WindowLeft);
 			Assert.Equal (0, Console.WindowTop);
+			driver.SetWindowPosition (80, 40);
+			Assert.Equal (0, Console.WindowLeft);
+			Assert.Equal (15, Console.WindowTop);
 
 			driver.SetWindowSize (80, 20);
 			Assert.Equal (80, Application.Driver.Cols);
@@ -407,6 +409,8 @@ namespace Terminal.Gui.ConsoleDrivers {
 			Assert.Equal (40, Console.BufferHeight);
 			Assert.Equal (80, Console.WindowWidth);
 			Assert.Equal (20, Console.WindowHeight);
+			Assert.Equal (0, Console.WindowLeft);
+			Assert.Equal (15, Console.WindowTop);
 			driver.SetWindowPosition (80, 41);
 			Assert.Equal (0, Console.WindowLeft);
 			Assert.Equal (20, Console.WindowTop);