Browse Source

Merge pull request #1685 from BDisp/wide-runes-console-fix

Wide runes console fix
Tig Kindel 3 years ago
parent
commit
391591e110

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

@@ -52,27 +52,51 @@ 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) {
+
+					var curAtttib = currentAttribute;
+					Curses.attrset (contents [crow, ccol - 1, 1]);
+					Curses.mvaddch (crow, ccol - 1, (int)(uint)' ');
+					contents [crow, ccol - 1, 0] = (int)(uint)' ';
+					Curses.move (crow, ccol);
+					Curses.attrset (curAtttib);
+				} else if (runeWidth < 2 && ccol < Cols - 1 && Rune.ColumnWidth ((char)contents [crow, ccol, 0]) > 1) {
+
+					var curAtttib = currentAttribute;
+					Curses.attrset (contents [crow, ccol + 1, 1]);
+					Curses.mvaddch (crow, ccol + 1, (int)(uint)' ');
+					contents [crow, ccol + 1, 0] = (int)(uint)' ';
+					Curses.move (crow, ccol);
+					Curses.attrset (curAtttib);
+				}
 				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 +957,7 @@ namespace Terminal.Gui {
 			}
 		}
 
-		void UpdateOffScreen ()
+		public override void UpdateOffScreen ()
 		{
 			contents = new int [Rows, Cols, 3];
 			for (int row = 0; row < Rows; row++) {

+ 15 - 0
Terminal.Gui/ConsoleDrivers/CursesDriver/binding.cs

@@ -177,6 +177,14 @@ namespace Unix.Terminal {
 			return addwstr (new String (c, 1));
 		}
 
+		public static int mvaddch (int y, int x, int ch)
+		{
+			if (ch < 127 || ch > 0xffff)
+				return methods.mvaddch (y, x, ch);
+			char c = (char)ch;
+			return mvaddwstr (y, x, new String (c, 1));
+		}
+
 		static IntPtr stdscr;
 
 		static IntPtr get_ptr (string key)
@@ -293,6 +301,7 @@ namespace Unix.Terminal {
 		static public int curs_set (int visibility) => methods.curs_set (visibility);
 		//static public int addch (int ch) => methods.addch (ch);
 		static public int addwstr (string s) => methods.addwstr (s);
+		static public int mvaddwstr (int y, int x, string s) => methods.mvaddwstr (y, x, s);
 		static public int wmove (IntPtr win, int line, int col) => methods.wmove (win, line, col);
 		static public int waddch (IntPtr win, int ch) => methods.waddch (win, ch);
 		static public int attron (int attrs) => methods.attron (attrs);
@@ -364,7 +373,9 @@ namespace Unix.Terminal {
 		public delegate int move (int line, int col);
 		public delegate int curs_set (int visibility);
 		public delegate int addch (int ch);
+		public delegate int mvaddch (int y, int x, int ch);
 		public delegate int addwstr ([MarshalAs (UnmanagedType.LPWStr)] string s);
+		public delegate int mvaddwstr (int y, int x, [MarshalAs (UnmanagedType.LPWStr)] string s);
 		public delegate int wmove (IntPtr win, int line, int col);
 		public delegate int waddch (IntPtr win, int ch);
 		public delegate int attron (int attrs);
@@ -435,7 +446,9 @@ namespace Unix.Terminal {
 		public readonly Delegates.move move;
 		public readonly Delegates.curs_set curs_set;
 		public readonly Delegates.addch addch;
+		public readonly Delegates.mvaddch mvaddch;
 		public readonly Delegates.addwstr addwstr;
+		public readonly Delegates.mvaddwstr mvaddwstr;
 		public readonly Delegates.wmove wmove;
 		public readonly Delegates.waddch waddch;
 		public readonly Delegates.attron attron;
@@ -508,7 +521,9 @@ namespace Unix.Terminal {
 			move = lib.GetNativeMethodDelegate<Delegates.move> ("move");
 			curs_set = lib.GetNativeMethodDelegate<Delegates.curs_set> ("curs_set");
 			addch = lib.GetNativeMethodDelegate<Delegates.addch> ("addch");
+			mvaddch = lib.GetNativeMethodDelegate<Delegates.mvaddch> ("mvaddch");
 			addwstr = lib.GetNativeMethodDelegate<Delegates.addwstr> ("addwstr");
+			mvaddwstr = lib.GetNativeMethodDelegate<Delegates.mvaddwstr> ("mvaddwstr");
 			wmove = lib.GetNativeMethodDelegate<Delegates.wmove> ("wmove");
 			waddch = lib.GetNativeMethodDelegate<Delegates.waddch> ("waddch");
 			attron = lib.GetNativeMethodDelegate<Delegates.attron> ("attron");

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

+ 111 - 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,25 +89,50 @@ namespace Terminal.Gui {
 				FakeConsole.CursorLeft = Clip.X;
 				needMove = true;
 			}
-
 		}
 
 		public override void AddRune (Rune rune)
 		{
 			rune = MakePrintable (rune);
-			if (Clip.Contains (ccol, crow)) {
+			var runeWidth = Rune.ColumnWidth (rune);
+			var validClip = IsValidContent (ccol, crow, Clip);
+
+			if (validClip) {
 				if (needMove) {
 					//MockConsole.CursorLeft = ccol;
 					//MockConsole.CursorTop = crow;
 					needMove = false;
 				}
+				if (runeWidth < 2 && ccol > 0
+					&& Rune.ColumnWidth ((char)contents [crow, ccol - 1, 0]) > 1) {
+
+					contents [crow, ccol - 1, 0] = (int)(uint)' ';
+
+				} else if (runeWidth < 2 && ccol < Cols - 1
+					&& Rune.ColumnWidth ((char)contents [crow, ccol, 0]) > 1) {
+
+					contents [crow, ccol + 1, 0] = (int)(uint)' ';
+				}
+
 				contents [crow, ccol, 0] = (int)(uint)rune;
 				contents [crow, ccol, 1] = currentAttribute;
 				contents [crow, ccol, 2] = 1;
 				dirtyLine [crow] = true;
 			} else
 				needMove = true;
+
 			ccol++;
+			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 +243,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 +253,15 @@ 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;
+						}
+
 						var color = contents [row, col, 1];
 						if (color != redrawColor)
 							SetColor (color);
@@ -266,6 +275,12 @@ namespace Terminal.Gui {
 			FakeConsole.CursorLeft = savedCol;
 		}
 
+		public override void Refresh ()
+		{
+			UpdateScreen ();
+			UpdateCursor ();
+		}
+
 		Attribute currentAttribute;
 		public override void SetAttribute (Attribute c)
 		{
@@ -388,6 +403,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 +443,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 +478,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 +503,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 +522,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 +540,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 +554,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 +591,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 ()

+ 82 - 42
Terminal.Gui/ConsoleDrivers/NetDriver.cs

@@ -471,10 +471,12 @@ namespace Terminal.Gui {
 				var c = kChar [i];
 				if (c == '[') {
 					foundPoint++;
-				} else if (foundPoint == 1 && c != ';') {
+				} else if (foundPoint == 1 && c != ';' && c != '?') {
 					value += c.ToString ();
+				} else if (c == '?') {
+					foundPoint++;
 				} else if (c == ';') {
-					if (foundPoint == 1) {
+					if (foundPoint >= 1) {
 						point.Y = int.Parse (value) - 1;
 					}
 					value = "";
@@ -500,6 +502,8 @@ namespace Terminal.Gui {
 							return;
 						}
 						break;
+					case 'c': // CSI?1;0c ("VT101 with No Options")
+						break;
 					default:
 						throw new NotImplementedException ();
 					}
@@ -1216,6 +1220,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 +1234,33 @@ 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) {
+				if (runeWidth < 2 && ccol > 0
+					&& Rune.ColumnWidth ((char)contents [crow, ccol - 1, 0]) > 1) {
+
+					contents [crow, ccol - 1, 0] = (int)(uint)' ';
+
+				} else if (runeWidth < 2 && ccol < Cols - 1
+					&& Rune.ColumnWidth ((char)contents [crow, ccol, 0]) > 1) {
+
+					contents [crow, ccol + 1, 0] = (int)(uint)' ';
+				}
 				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 +1318,6 @@ namespace Terminal.Gui {
 			cols = Console.WindowWidth;
 			rows = Console.WindowHeight;
 
-			Clear ();
 			ResizeScreen ();
 			UpdateOffScreen ();
 
@@ -1352,6 +1363,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 +1408,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 +1441,7 @@ namespace Terminal.Gui {
 		public override void Refresh ()
 		{
 			UpdateScreen ();
+			UpdateCursor ();
 		}
 
 		int redrawAttr = -1;
@@ -1443,7 +1458,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 +1469,23 @@ 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;
 						}
+
 						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 +1494,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 +1504,7 @@ namespace Terminal.Gui {
 					}
 				}
 			}
-			Console.CursorVisible = true;
-			UpdateCursor ();
+			Console.CursorVisible = savedCursorVisible;
 		}
 
 		System.Text.StringBuilder WriteAttributes (int attr)
@@ -1553,11 +1579,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 +1916,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)

+ 26 - 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,24 @@ 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) {
 
-			if (Clip.Contains (ccol, crow)) {
+					var prevPosition = crow * Cols + ccol + 1;
+					OutputBuffer [prevPosition].Char.UnicodeChar = (char)' ';
+					contents [crow, ccol + 1, 0] = (int)(uint)' ';
+				}
 				OutputBuffer [position].Attributes = (ushort)currentAttribute;
 				OutputBuffer [position].Char.UnicodeChar = (char)rune;
 				contents [crow, ccol, 0] = (int)(uint)rune;
@@ -1480,17 +1495,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 = (char)0x00;
+					contents [crow, ccol, 0] = (int)(uint)0x00;
+					contents [crow, ccol, 1] = currentAttribute;
+					contents [crow, ccol, 2] = 0;
 				}
+				ccol++;
 			}
-			//if (ccol == Cols) {
-			//	ccol = 0;
-			//	if (crow + 1 < Rows)
-			//		crow++;
-			//}
+
 			if (sync)
 				UpdateScreen ();
 		}

+ 15 - 7
Terminal.Gui/Core/Border.cs

@@ -460,15 +460,17 @@ namespace Terminal.Gui {
 		/// Drawn the <see cref="BorderThickness"/> more the <see cref="Padding"/>
 		///  more the <see cref="Border.BorderStyle"/> and the <see cref="Effect3D"/>.
 		/// </summary>
-		public void DrawContent (View view = null)
+		/// <param name="view">The view to draw.</param>
+		/// <param name="fill">If it will clear or not the content area.</param>
+		public void DrawContent (View view = null, bool fill = true)
 		{
 			if (Child == null) {
 				Child = view;
 			}
 			if (Parent?.Border != null) {
-				DrawParentBorder (Parent.ViewToScreen (Parent.Bounds));
+				DrawParentBorder (Parent.ViewToScreen (Parent.Bounds), fill);
 			} else {
-				DrawChildBorder (Child.ViewToScreen (Child.Bounds));
+				DrawChildBorder (Child.ViewToScreen (Child.Bounds), fill);
 			}
 		}
 
@@ -559,7 +561,7 @@ namespace Terminal.Gui {
 			}
 		}
 
-		private void DrawChildBorder (Rect frame)
+		private void DrawChildBorder (Rect frame, bool fill = true)
 		{
 			var drawMarginFrame = DrawMarginFrame ? 1 : 0;
 			var sumThickness = GetSumThickness ();
@@ -663,7 +665,7 @@ namespace Terminal.Gui {
 				Height = frame.Height + (2 * drawMarginFrame)
 			};
 			if (rect.Width > 0 && rect.Height > 0) {
-				driver.DrawWindowFrame (rect, 1, 1, 1, 1, BorderStyle != BorderStyle.None, fill: true, this);
+				driver.DrawWindowFrame (rect, 1, 1, 1, 1, BorderStyle != BorderStyle.None, fill, this);
 			}
 
 			if (Effect3D) {
@@ -712,7 +714,7 @@ namespace Terminal.Gui {
 			driver.SetAttribute (savedAttribute);
 		}
 
-		private void DrawParentBorder (Rect frame)
+		private void DrawParentBorder (Rect frame, bool fill = true)
 		{
 			var sumThickness = GetSumThickness ();
 			var borderThickness = BorderThickness;
@@ -815,7 +817,7 @@ namespace Terminal.Gui {
 				Height = Math.Max (frame.Height - sumThickness.Bottom - sumThickness.Top, 0)
 			};
 			if (rect.Width > 0 && rect.Height > 0) {
-				driver.DrawWindowFrame (rect, 1, 1, 1, 1, BorderStyle != BorderStyle.None, fill: true, this);
+				driver.DrawWindowFrame (rect, 1, 1, 1, 1, BorderStyle != BorderStyle.None, fill, this);
 			}
 
 			if (Effect3D) {
@@ -873,6 +875,12 @@ namespace Terminal.Gui {
 
 		private void AddRuneAt (ConsoleDriver driver, int col, int row, Rune ch)
 		{
+			if (col < driver.Cols && row < driver.Rows && col > 0 && driver.Contents [row, col, 2] == 0
+				&& Rune.ColumnWidth ((char)driver.Contents [row, col - 1, 0]) > 1) {
+
+				driver.Contents [row, col, 1] = driver.GetAttribute ();
+				return;
+			}
 			driver.Move (col, row);
 			driver.AddRune (ch);
 		}

+ 27 - 3
Terminal.Gui/Core/ConsoleDriver.cs

@@ -692,15 +692,33 @@ namespace Terminal.Gui {
 		/// <returns></returns>
 		public static Rune MakePrintable (Rune c)
 		{
-			if (c <= 0x1F || (c >= 0x80 && c <= 0x9F)) {
+			var controlChars = gethexaformat (c, 4);
+			if (controlChars <= 0x1F || (controlChars >= 0X7F && controlChars <= 0x9F)) {
 				// ASCII (C0) control characters.
 				// C1 control characters (https://www.aivosto.com/articles/control-characters.html#c1)
-				return new Rune (c + 0x2400);
+				return new Rune (controlChars + 0x2400);
 			} else {
 				return c;
 			}
 		}
 
+		static uint gethexaformat (uint rune, int length)
+		{
+			var hex = rune.ToString ($"x{length}");
+			var hexstr = hex.Substring (hex.Length - length, length);
+			return (uint)int.Parse (hexstr, System.Globalization.NumberStyles.HexNumber);
+		}
+
+		/// <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 +769,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 +847,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 (' ');
 			}

+ 210 - 68
Terminal.Gui/Core/TextFormatter.cs

@@ -303,7 +303,7 @@ namespace Terminal.Gui {
 		/// <remarks>
 		/// <para>
 		/// Upon a 'get' of this property, if the text needs to be formatted (if <see cref="NeedsFormat"/> is <c>true</c>)
-		/// <see cref="Format(ustring, int, bool, bool, bool, int)"/> will be called internally. 
+		/// <see cref="Format(ustring, int, bool, bool, bool, int, TextDirection)"/> will be called internally. 
 		/// </para>
 		/// </remarks>
 		public List<ustring> Lines {
@@ -328,12 +328,14 @@ namespace Terminal.Gui {
 					}
 
 					if (IsVerticalDirection (textDirection)) {
-						lines = Format (shown_text, Size.Height, textVerticalAlignment == VerticalTextAlignment.Justified, Size.Width > 1);
+						lines = Format (shown_text, Size.Height, textVerticalAlignment == VerticalTextAlignment.Justified, Size.Width > 1,
+							false, 0, textDirection);
 						if (!AutoSize && lines.Count > Size.Width) {
 							lines.RemoveRange (Size.Width, lines.Count - Size.Width);
 						}
 					} else {
-						lines = Format (shown_text, Size.Width, textAlignment == TextAlignment.Justified, Size.Height > 1);
+						lines = Format (shown_text, Size.Width, textAlignment == TextAlignment.Justified, Size.Height > 1,
+							false, 0, textDirection);
 						if (!AutoSize && lines.Count > Size.Height) {
 							lines.RemoveRange (Size.Height, lines.Count - Size.Height);
 						}
@@ -346,7 +348,7 @@ namespace Terminal.Gui {
 		}
 
 		/// <summary>
-		/// Gets or sets whether the <see cref="TextFormatter"/> needs to format the text when <see cref="Draw(Rect, Attribute, Attribute)"/> is called.
+		/// Gets or sets whether the <see cref="TextFormatter"/> needs to format the text when <see cref="Draw(Rect, Attribute, Attribute, Rect)"/> is called.
 		/// If it is <c>false</c> when Draw is called, the Draw call will be faster.
 		/// </summary>
 		/// <remarks>
@@ -436,6 +438,7 @@ namespace Terminal.Gui {
 		/// <param name="preserveTrailingSpaces">If <c>true</c>, the wrapped text will keep the trailing spaces.
 		///  If <c>false</c>, the trailing spaces will be trimmed.</param>
 		/// <param name="tabWidth">The tab width.</param>
+		/// <param name="textDirection">The text direction.</param>
 		/// <returns>Returns a list of word wrapped lines.</returns>
 		/// <remarks>
 		/// <para>
@@ -445,7 +448,8 @@ namespace Terminal.Gui {
 		/// This method strips Newline ('\n' and '\r\n') sequences before processing.
 		/// </para>
 		/// </remarks>
-		public static List<ustring> WordWrap (ustring text, int width, bool preserveTrailingSpaces = false, int tabWidth = 0)
+		public static List<ustring> WordWrap (ustring text, int width, bool preserveTrailingSpaces = false, int tabWidth = 0,
+			TextDirection textDirection = TextDirection.LeftRight_TopBottom)
 		{
 			if (width < 0) {
 				throw new ArgumentOutOfRangeException ("Width cannot be negative.");
@@ -460,15 +464,29 @@ namespace Terminal.Gui {
 
 			var runes = StripCRLF (text).ToRuneList ();
 			if (!preserveTrailingSpaces) {
-				while ((end = start + width) < runes.Count) {
-					while (runes [end] != ' ' && end > start)
-						end--;
-					if (end == start)
-						end = start + width;
-					lines.Add (ustring.Make (runes.GetRange (start, end - start)));
-					start = end;
-					if (runes [end] == ' ') {
-						start++;
+				if (IsHorizontalDirection (textDirection)) {
+					while ((end = start + GetMaxLengthForWidth (runes.GetRange (start, runes.Count - start), width)) < runes.Count) {
+						while (runes [end] != ' ' && end > start)
+							end--;
+						if (end == start)
+							end = start + GetMaxLengthForWidth (runes.GetRange (end, runes.Count - end), width);
+						lines.Add (ustring.Make (runes.GetRange (start, end - start)));
+						start = end;
+						if (runes [end] == ' ') {
+							start++;
+						}
+					}
+				} else {
+					while ((end = start + width) < runes.Count) {
+						while (runes [end] != ' ' && end > start)
+							end--;
+						if (end == start)
+							end = start + width;
+						lines.Add (ustring.Make (runes.GetRange (start, end - start)));
+						start = end;
+						if (runes [end] == ' ') {
+							start++;
+						}
 					}
 				}
 			} else {
@@ -486,7 +504,11 @@ namespace Terminal.Gui {
 
 				while (length < cWidth && to < runes.Count) {
 					var rune = runes [to];
-					length += Rune.ColumnWidth (rune);
+					if (IsHorizontalDirection (textDirection)) {
+						length += Rune.ColumnWidth (rune);
+					} else {
+						length++;
+					}
 					if (rune == ' ') {
 						if (length == cWidth) {
 							return to + 1;
@@ -527,10 +549,11 @@ namespace Terminal.Gui {
 		/// <param name="text">The text to justify.</param>
 		/// <param name="width">If the text length is greater that <c>width</c> it will be clipped.</param>
 		/// <param name="talign">Alignment.</param>
+		/// <param name="textDirection">The text direction.</param>
 		/// <returns>Justified and clipped text.</returns>
-		public static ustring ClipAndJustify (ustring text, int width, TextAlignment talign)
+		public static ustring ClipAndJustify (ustring text, int width, TextAlignment talign, TextDirection textDirection = TextDirection.LeftRight_TopBottom)
 		{
-			return ClipAndJustify (text, width, talign == TextAlignment.Justified);
+			return ClipAndJustify (text, width, talign == TextAlignment.Justified, textDirection);
 		}
 
 		/// <summary>
@@ -539,8 +562,9 @@ namespace Terminal.Gui {
 		/// <param name="text">The text to justify.</param>
 		/// <param name="width">If the text length is greater that <c>width</c> it will be clipped.</param>
 		/// <param name="justify">Justify.</param>
+		/// <param name="textDirection">The text direction.</param>
 		/// <returns>Justified and clipped text.</returns>
-		public static ustring ClipAndJustify (ustring text, int width, bool justify)
+		public static ustring ClipAndJustify (ustring text, int width, bool justify, TextDirection textDirection = TextDirection.LeftRight_TopBottom)
 		{
 			if (width < 0) {
 				throw new ArgumentOutOfRangeException ("Width cannot be negative.");
@@ -552,10 +576,12 @@ namespace Terminal.Gui {
 			var runes = text.ToRuneList ();
 			int slen = runes.Count;
 			if (slen > width) {
-				return ustring.Make (runes.GetRange (0, width));
+				return ustring.Make (runes.GetRange (0, GetMaxLengthForWidth (text, width)));
 			} else {
 				if (justify) {
-					return Justify (text, width);
+					return Justify (text, width, ' ', textDirection);
+				} else if (GetTextWidth (text) > width && IsHorizontalDirection (textDirection)) {
+					return ustring.Make (runes.GetRange (0, GetMaxLengthForWidth (text, width)));
 				}
 				return text;
 			}
@@ -568,8 +594,9 @@ namespace Terminal.Gui {
 		/// <param name="text"></param>
 		/// <param name="width"></param>
 		/// <param name="spaceChar">Character to replace whitespace and pad with. For debugging purposes.</param>
+		/// <param name="textDirection">The text direction.</param>
 		/// <returns>The justified text.</returns>
-		public static ustring Justify (ustring text, int width, char spaceChar = ' ')
+		public static ustring Justify (ustring text, int width, char spaceChar = ' ', TextDirection textDirection = TextDirection.LeftRight_TopBottom)
 		{
 			if (width < 0) {
 				throw new ArgumentOutOfRangeException ("Width cannot be negative.");
@@ -579,8 +606,12 @@ namespace Terminal.Gui {
 			}
 
 			var words = text.Split (ustring.Make (' '));
-			int textCount = words.Sum (arg => arg.RuneCount);
-
+			int textCount;
+			if (IsHorizontalDirection (textDirection)) {
+				textCount = words.Sum (arg => GetTextWidth (arg));
+			} else {
+				textCount = words.Sum (arg => arg.RuneCount);
+			}
 			var spaces = words.Length > 1 ? (width - textCount) / (words.Length - 1) : 0;
 			var extras = words.Length > 1 ? (width - textCount) % words.Length : 0;
 
@@ -610,6 +641,7 @@ namespace Terminal.Gui {
 		/// <param name="wordWrap">If <c>true</c>, the text will be wrapped to new lines as need. If <c>false</c>, forces text to fit a single line. Line breaks are converted to spaces. The text will be clipped to <c>width</c></param>
 		/// <param name="preserveTrailingSpaces">If <c>true</c> and 'wordWrap' also true, the wrapped text will keep the trailing spaces. If <c>false</c>, the trailing spaces will be trimmed.</param>
 		/// <param name="tabWidth">The tab width.</param>
+		/// <param name="textDirection">The text direction.</param>
 		/// <returns>A list of word wrapped lines.</returns>
 		/// <remarks>
 		/// <para>
@@ -622,9 +654,9 @@ namespace Terminal.Gui {
 		/// If <c>width</c> is int.MaxValue, the text will be formatted to the maximum width possible. 
 		/// </para>
 		/// </remarks>
-		public static List<ustring> Format (ustring text, int width, TextAlignment talign, bool wordWrap, bool preserveTrailingSpaces = false, int tabWidth = 0)
+		public static List<ustring> Format (ustring text, int width, TextAlignment talign, bool wordWrap, bool preserveTrailingSpaces = false, int tabWidth = 0, TextDirection textDirection = TextDirection.LeftRight_TopBottom)
 		{
-			return Format (text, width, talign == TextAlignment.Justified, wordWrap, preserveTrailingSpaces, tabWidth);
+			return Format (text, width, talign == TextAlignment.Justified, wordWrap, preserveTrailingSpaces, tabWidth, textDirection);
 		}
 
 		/// <summary>
@@ -636,6 +668,7 @@ namespace Terminal.Gui {
 		/// <param name="wordWrap">If <c>true</c>, the text will be wrapped to new lines as need. If <c>false</c>, forces text to fit a single line. Line breaks are converted to spaces. The text will be clipped to <c>width</c></param>
 		/// <param name="preserveTrailingSpaces">If <c>true</c> and 'wordWrap' also true, the wrapped text will keep the trailing spaces. If <c>false</c>, the trailing spaces will be trimmed.</param>
 		/// <param name="tabWidth">The tab width.</param>
+		/// <param name="textDirection">The text direction.</param>
 		/// <returns>A list of word wrapped lines.</returns>
 		/// <remarks>
 		/// <para>
@@ -649,7 +682,7 @@ namespace Terminal.Gui {
 		/// </para>
 		/// </remarks>
 		public static List<ustring> Format (ustring text, int width, bool justify, bool wordWrap,
-			bool preserveTrailingSpaces = false, int tabWidth = 0)
+			bool preserveTrailingSpaces = false, int tabWidth = 0, TextDirection textDirection = TextDirection.LeftRight_TopBottom)
 		{
 			if (width < 0) {
 				throw new ArgumentOutOfRangeException ("width cannot be negative");
@@ -666,7 +699,7 @@ namespace Terminal.Gui {
 
 			if (wordWrap == false) {
 				text = ReplaceCRLFWithSpace (text);
-				lineResult.Add (ClipAndJustify (text, width, justify));
+				lineResult.Add (ClipAndJustify (text, width, justify, textDirection));
 				return lineResult;
 			}
 
@@ -676,9 +709,9 @@ namespace Terminal.Gui {
 			for (int i = 0; i < runeCount; i++) {
 				Rune c = runes [i];
 				if (c == '\n') {
-					var wrappedLines = WordWrap (ustring.Make (runes.GetRange (lp, i - lp)), width, preserveTrailingSpaces, tabWidth);
+					var wrappedLines = WordWrap (ustring.Make (runes.GetRange (lp, i - lp)), width, preserveTrailingSpaces, tabWidth, textDirection);
 					foreach (var line in wrappedLines) {
-						lineResult.Add (ClipAndJustify (line, width, justify));
+						lineResult.Add (ClipAndJustify (line, width, justify, textDirection));
 					}
 					if (wrappedLines.Count == 0) {
 						lineResult.Add (ustring.Empty);
@@ -686,8 +719,8 @@ namespace Terminal.Gui {
 					lp = i + 1;
 				}
 			}
-			foreach (var line in WordWrap (ustring.Make (runes.GetRange (lp, runeCount - lp)), width, preserveTrailingSpaces, tabWidth)) {
-				lineResult.Add (ClipAndJustify (line, width, justify));
+			foreach (var line in WordWrap (ustring.Make (runes.GetRange (lp, runeCount - lp)), width, preserveTrailingSpaces, tabWidth, textDirection)) {
+				lineResult.Add (ClipAndJustify (line, width, justify, textDirection));
 			}
 
 			return lineResult;
@@ -717,7 +750,7 @@ namespace Terminal.Gui {
 			var max = 0;
 			result.ForEach (s => {
 				var m = 0;
-				s.ToRuneList ().ForEach (r => m += Rune.ColumnWidth (r));
+				s.ToRuneList ().ForEach (r => m += Math.Max (Rune.ColumnWidth (r), 1));
 				if (m > max) {
 					max = m;
 				}
@@ -725,6 +758,94 @@ namespace Terminal.Gui {
 			return max;
 		}
 
+		/// <summary>
+		/// Gets the total width of the passed text.
+		/// </summary>
+		/// <param name="text"></param>
+		/// <returns>The text width.</returns>
+		public static int GetTextWidth (ustring text)
+		{
+			return text.ToRuneList ().Sum (r => Math.Max (Rune.ColumnWidth (r), 1));
+		}
+
+		/// <summary>
+		/// Gets the maximum characters width from the list based on the <paramref name="startIndex"/>
+		/// and the <paramref name="length"/>.
+		/// </summary>
+		/// <param name="lines">The lines.</param>
+		/// <param name="startIndex">The start index.</param>
+		/// <param name="length">The length.</param>
+		/// <returns>The maximum characters width.</returns>
+		public static int GetSumMaxCharWidth (List<ustring> lines, int startIndex = -1, int length = -1)
+		{
+			var max = 0;
+			for (int i = (startIndex == -1 ? 0 : startIndex); i < (length == -1 ? lines.Count : startIndex + length); i++) {
+				var runes = lines [i];
+				if (runes.Length > 0)
+					max += runes.Max (r => Math.Max (Rune.ColumnWidth (r), 1));
+			}
+			return max;
+		}
+
+		/// <summary>
+		/// Gets the maximum characters width from the text based on the <paramref name="startIndex"/>
+		/// and the <paramref name="length"/>.
+		/// </summary>
+		/// <param name="text">The text.</param>
+		/// <param name="startIndex">The start index.</param>
+		/// <param name="length">The length.</param>
+		/// <returns>The maximum characters width.</returns>
+		public static int GetSumMaxCharWidth (ustring text, int startIndex = -1, int length = -1)
+		{
+			var max = 0;
+			var runes = text.ToRunes ();
+			for (int i = (startIndex == -1 ? 0 : startIndex); i < (length == -1 ? runes.Length : startIndex + length); i++) {
+				max += Math.Max (Rune.ColumnWidth (runes [i]), 1);
+			}
+			return max;
+		}
+
+		/// <summary>
+		/// Gets the index position from the text based on the <paramref name="width"/>.
+		/// </summary>
+		/// <param name="text">The text.</param>
+		/// <param name="width">The width.</param>
+		/// <returns>The index of the text that fit the width.</returns>
+		public static int GetMaxLengthForWidth (ustring text, int width)
+		{
+			var runes = text.ToRuneList ();
+			var runesLength = 0;
+			var runeIdx = 0;
+			for (; runeIdx < runes.Count; runeIdx++) {
+				var runeWidth = Math.Max (Rune.ColumnWidth (runes [runeIdx]), 1);
+				if (runesLength + runeWidth > width) {
+					break;
+				}
+				runesLength += runeWidth;
+			}
+			return runeIdx;
+		}
+
+		/// <summary>
+		/// Gets the index position from the list based on the <paramref name="width"/>.
+		/// </summary>
+		/// <param name="runes">The runes.</param>
+		/// <param name="width">The width.</param>
+		/// <returns>The index of the list that fit the width.</returns>
+		public static int GetMaxLengthForWidth (List<Rune> runes, int width)
+		{
+			var runesLength = 0;
+			var runeIdx = 0;
+			for (; runeIdx < runes.Count; runeIdx++) {
+				var runeWidth = Math.Max (Rune.ColumnWidth (runes [runeIdx]), 1);
+				if (runesLength + runeWidth > width) {
+					break;
+				}
+				runesLength += runeWidth;
+			}
+			return runeIdx;
+		}
+
 		/// <summary>
 		///  Calculates the rectangle required to hold text, assuming no word wrapping.
 		/// </summary>
@@ -753,15 +874,13 @@ namespace Terminal.Gui {
 							mw = cols;
 						}
 						cols = 0;
-					} else {
-						if (rune != '\r') {
-							cols++;
-							var rw = Rune.ColumnWidth (rune);
-							if (rw > 0) {
-								rw--;
-							}
-							cols += rw;
+					} else if (rune != '\r') {
+						cols++;
+						var rw = Rune.ColumnWidth (rune);
+						if (rw > 0) {
+							rw--;
 						}
+						cols += rw;
 					}
 				}
 				if (cols > mw) {
@@ -781,16 +900,14 @@ namespace Terminal.Gui {
 							vh = rows;
 						}
 						rows = 0;
-					} else {
-						if (rune != '\r') {
-							rows++;
-							var rw = Rune.ColumnWidth (rune);
-							if (rw < 0) {
-								rw++;
-							}
-							if (rw > vw) {
-								vw = rw;
-							}
+					} else if (rune != '\r') {
+						rows++;
+						var rw = Rune.ColumnWidth (rune);
+						if (rw < 0) {
+							rw++;
+						}
+						if (rw > vw) {
+							vw = rw;
 						}
 					}
 				}
@@ -925,7 +1042,8 @@ namespace Terminal.Gui {
 		/// <param name="bounds">Specifies the screen-relative location and maximum size for drawing the text.</param>
 		/// <param name="normalColor">The color to use for all text except the hotkey</param>
 		/// <param name="hotColor">The color to use to draw the hotkey</param>
-		public void Draw (Rect bounds, Attribute normalColor, Attribute hotColor)
+		/// <param name="containerBounds">Specifies the screen-relative location and maximum container size.</param>
+		public void Draw (Rect bounds, Attribute normalColor, Attribute hotColor, Rect containerBounds = default)
 		{
 			// With this check, we protect against subclasses with overrides of Text (like Button)
 			if (ustring.IsNullOrEmpty (text)) {
@@ -969,26 +1087,31 @@ namespace Terminal.Gui {
 				// Horizontal Alignment
 				if (textAlignment == TextAlignment.Right || (textAlignment == TextAlignment.Justified && !IsLeftToRight (textDirection))) {
 					if (isVertical) {
-						x = bounds.Right - Lines.Count + line;
-						CursorPosition = bounds.Width - Lines.Count + hotKeyPos;
+						var runesWidth = GetSumMaxCharWidth (Lines, line);
+						x = bounds.Right - runesWidth;
+						CursorPosition = bounds.Width - runesWidth + hotKeyPos;
 					} else {
-						x = bounds.Right - runes.Length;
-						CursorPosition = bounds.Width - runes.Length + hotKeyPos;
+						var runesWidth = GetTextWidth (ustring.Make (runes));
+						x = bounds.Right - runesWidth;
+						CursorPosition = bounds.Width - runesWidth + hotKeyPos;
 					}
 				} else if (textAlignment == TextAlignment.Left || textAlignment == TextAlignment.Justified) {
 					if (isVertical) {
-						x = bounds.Left + line;
+						var runesWidth = line > 0 ? GetSumMaxCharWidth (Lines, 0, line) : 0;
+						x = bounds.Left + runesWidth;
 					} else {
 						x = bounds.Left;
 					}
 					CursorPosition = hotKeyPos;
 				} else if (textAlignment == TextAlignment.Centered) {
 					if (isVertical) {
-						x = bounds.Left + line + ((bounds.Width - Lines.Count) / 2);
-						CursorPosition = (bounds.Width - Lines.Count) / 2 + hotKeyPos;
+						var runesWidth = GetSumMaxCharWidth (Lines, line);
+						x = bounds.Left + line + ((bounds.Width - runesWidth) / 2);
+						CursorPosition = (bounds.Width - runesWidth) / 2 + hotKeyPos;
 					} else {
-						x = bounds.Left + (bounds.Width - runes.Length) / 2;
-						CursorPosition = (bounds.Width - runes.Length) / 2 + hotKeyPos;
+						var runesWidth = GetTextWidth (ustring.Make (runes));
+						x = bounds.Left + (bounds.Width - runesWidth) / 2;
+						CursorPosition = (bounds.Width - runesWidth) / 2 + hotKeyPos;
 					}
 				} else {
 					throw new ArgumentOutOfRangeException ();
@@ -1021,9 +1144,21 @@ namespace Terminal.Gui {
 
 				var start = isVertical ? bounds.Top : bounds.Left;
 				var size = isVertical ? bounds.Height : bounds.Width;
-
 				var current = start;
-				for (var idx = start; idx < start + size; idx++) {
+				var startX = start < 0
+					? start
+					: isVertical ? start - y : start - x;
+				var savedClip = Application.Driver?.Clip;
+				if (Application.Driver != null && containerBounds != default) {
+					Application.Driver.Clip = containerBounds == default
+						? bounds
+						: new Rect (Math.Max (containerBounds.X, bounds.X),
+						Math.Max (containerBounds.Y, bounds.Y),
+						Math.Min (containerBounds.Width, containerBounds.Right - bounds.Left),
+						Math.Min (containerBounds.Height, containerBounds.Bottom - bounds.Top));
+				}
+
+				for (var idx = startX; current < start + size; idx++) {
 					if (idx < 0) {
 						current++;
 						continue;
@@ -1031,13 +1166,13 @@ namespace Terminal.Gui {
 					var rune = (Rune)' ';
 					if (isVertical) {
 						Application.Driver?.Move (x, current);
-						if (idx >= y && idx < (y + runes.Length)) {
-							rune = runes [idx - y];
+						if (idx >= 0 && idx < runes.Length) {
+							rune = runes [idx];
 						}
 					} else {
 						Application.Driver?.Move (current, y);
-						if (idx >= x && idx < (x + runes.Length)) {
-							rune = runes [idx - x];
+						if (idx >= 0 && idx < runes.Length) {
+							rune = runes [idx];
 						}
 					}
 					if ((rune & HotKeyTagMask) == HotKeyTagMask) {
@@ -1051,11 +1186,18 @@ namespace Terminal.Gui {
 					} else {
 						Application.Driver?.AddRune (rune);
 					}
-					current += Rune.ColumnWidth (rune);
-					if (idx + 1 < runes.Length && current + Rune.ColumnWidth (runes [idx + 1]) > size) {
+					var runeWidth = Math.Max (Rune.ColumnWidth (rune), 1);
+					if (isVertical) {
+						current++;
+					} else {
+						current += runeWidth;
+					}
+					if (!isVertical && idx + 1 < runes.Length && current + Rune.ColumnWidth (runes [idx + 1]) > start + size) {
 						break;
 					}
 				}
+				if (Application.Driver != null)
+					Application.Driver.Clip = (Rect)savedClip;
 			}
 		}
 	}

+ 9 - 4
Terminal.Gui/Core/Toplevel.cs

@@ -582,9 +582,11 @@ namespace Terminal.Gui {
 			}
 			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) {
+			var canChange = SetWidth (top.Frame.Width, out int rWidth);
+			if (canChange && rWidth < 0 && nx >= top.Frame.X) {
 				nx = Math.Max (top.Frame.Right - 2, 0);
+			} else if (rWidth < 0 && nx >= top.Frame.X) {
+				nx = Math.Min (nx + 1, top.Frame.Right - 2);
 			}
 			//System.Diagnostics.Debug.WriteLine ($"nx:{nx}, rWidth:{rWidth}");
 			bool m, s;
@@ -623,9 +625,11 @@ namespace Terminal.Gui {
 			}
 			ny = Math.Min (ny, l);
 			ny = ny + top.Frame.Height >= l ? Math.Max (l - top.Frame.Height, m ? 1 : 0) : ny;
-			SetHeight (top.Frame.Height, out int rHeight);
-			if (rHeight < 0 && ny >= top.Frame.Y) {
+			canChange = SetHeight (top.Frame.Height, out int rHeight);
+			if (canChange && rHeight < 0 && ny >= top.Frame.Y) {
 				ny = Math.Max (top.Frame.Bottom - 2, 0);
+			} else if (rHeight < 0 && ny >= top.Frame.Y) {
+				ny = Math.Min (ny + 1, top.Frame.Bottom - 2);
 			}
 			//System.Diagnostics.Debug.WriteLine ($"ny:{ny}, rHeight:{rHeight}");
 
@@ -687,6 +691,7 @@ namespace Terminal.Gui {
 			if (!NeedDisplay.IsEmpty || ChildNeedsDisplay || LayoutNeeded) {
 				Driver.SetAttribute (GetNormalColor ());
 
+				Driver.UpdateOffScreen ();
 				// This is the Application.Top. Clear just the region we're being asked to redraw 
 				// (the bounds passed to us).
 				Clear ();

+ 51 - 41
Terminal.Gui/Core/View.cs

@@ -124,8 +124,6 @@ namespace Terminal.Gui {
 		Direction focusDirection;
 		bool autoSize;
 
-		TextFormatter textFormatter;
-
 		ShortcutHelper shortcutHelper;
 
 		/// <summary>
@@ -186,12 +184,12 @@ namespace Terminal.Gui {
 		/// <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>
-		public virtual Key HotKey { get => textFormatter.HotKey; set => textFormatter.HotKey = value; }
+		public virtual Key HotKey { get => TextFormatter.HotKey; set => TextFormatter.HotKey = value; }
 
 		/// <summary>
 		/// Gets or sets the specifier character for the hotkey (e.g. '_'). Set to '\xffff' to disable hotkey support for this View instance. The default is '\xffff'. 
 		/// </summary>
-		public virtual Rune HotKeySpecifier { get => textFormatter.HotKeySpecifier; set => textFormatter.HotKeySpecifier = value; }
+		public virtual Rune HotKeySpecifier { get => TextFormatter.HotKeySpecifier; set => TextFormatter.HotKeySpecifier = value; }
 
 		/// <summary>
 		/// This is the global setting that can be used as a global shortcut to invoke an action if provided.
@@ -524,7 +522,7 @@ namespace Terminal.Gui {
 				if (x is Pos.PosAbsolute) {
 					frame = new Rect (x.Anchor (0), frame.Y, frame.Width, frame.Height);
 				}
-				textFormatter.Size = frame.Size;
+				TextFormatter.Size = frame.Size;
 				SetNeedsDisplay (frame);
 			}
 		}
@@ -548,7 +546,7 @@ namespace Terminal.Gui {
 				if (y is Pos.PosAbsolute) {
 					frame = new Rect (frame.X, y.Anchor (0), frame.Width, frame.Height);
 				}
-				textFormatter.Size = frame.Size;
+				TextFormatter.Size = frame.Size;
 				SetNeedsDisplay (frame);
 			}
 		}
@@ -570,11 +568,14 @@ namespace Terminal.Gui {
 				}
 
 				width = value;
+				if (autoSize && value.Anchor (0) != TextFormatter.Size.Width) {
+					autoSize = false;
+				}
 				SetNeedsLayout ();
 				if (width is Dim.DimAbsolute) {
 					frame = new Rect (frame.X, frame.Y, width.Anchor (0), frame.Height);
 				}
-				textFormatter.Size = frame.Size;
+				TextFormatter.Size = frame.Size;
 				SetNeedsDisplay (frame);
 			}
 		}
@@ -592,11 +593,14 @@ namespace Terminal.Gui {
 				}
 
 				height = value;
+				if (autoSize && value.Anchor (0) != TextFormatter.Size.Height) {
+					autoSize = false;
+				}
 				SetNeedsLayout ();
 				if (height is Dim.DimAbsolute) {
 					frame = new Rect (frame.X, frame.Y, frame.Width, height.Anchor (0));
 				}
-				textFormatter.Size = frame.Size;
+				TextFormatter.Size = frame.Size;
 				SetNeedsDisplay (frame);
 			}
 		}
@@ -614,6 +618,11 @@ namespace Terminal.Gui {
 			return false;
 		}
 
+		/// <summary>
+		/// Gets or sets the <see cref="Terminal.Gui.TextFormatter"/> which can be handled differently by any derived class.
+		/// </summary>
+		public TextFormatter TextFormatter { get; set; }
+
 		/// <summary>
 		/// Returns the container for this view, or null if this view has not been added to a container.
 		/// </summary>
@@ -717,8 +726,8 @@ namespace Terminal.Gui {
 		void Initialize (ustring text, Rect rect, LayoutStyle layoutStyle = LayoutStyle.Computed,
 			TextDirection direction = TextDirection.LeftRight_TopBottom, Border border = null)
 		{
-			textFormatter = new TextFormatter ();
-			textFormatter.HotKeyChanged += TextFormatter_HotKeyChanged;
+			TextFormatter = new TextFormatter ();
+			TextFormatter.HotKeyChanged += TextFormatter_HotKeyChanged;
 			TextDirection = direction;
 			Border = border;
 			if (Border != null) {
@@ -772,7 +781,7 @@ namespace Terminal.Gui {
 			foreach (var view in Subviews) {
 				view.SetNeedsLayout ();
 			}
-			textFormatter.NeedsFormat = true;
+			TextFormatter.NeedsFormat = true;
 		}
 
 		/// <summary>
@@ -1013,7 +1022,7 @@ namespace Terminal.Gui {
 			for (int line = 0; line < h; line++) {
 				Move (0, line);
 				for (int col = 0; col < w; col++)
-					Driver.AddRune (' ');
+					Driver.AddStr (" ");
 			}
 		}
 
@@ -1209,7 +1218,7 @@ namespace Terminal.Gui {
 				focused.PositionCursor ();
 			} else {
 				if (CanFocus && HasFocus && Visible && Frame.Width > 0 && Frame.Height > 0) {
-					Move (textFormatter.HotKeyPos == -1 ? 0 : textFormatter.CursorPosition, 0);
+					Move (TextFormatter.HotKeyPos == -1 ? 0 : TextFormatter.CursorPosition, 0);
 				} else {
 					Move (frame.X, frame.Y);
 				}
@@ -1411,11 +1420,12 @@ namespace Terminal.Gui {
 			if (!ustring.IsNullOrEmpty (Text) || (this is Label && !AutoSize)) {
 				Clear ();
 				// Draw any Text
-				if (textFormatter != null) {
-					textFormatter.NeedsFormat = true;
+				if (TextFormatter != null) {
+					TextFormatter.NeedsFormat = true;
 				}
-				textFormatter?.Draw (ViewToScreen (Bounds), HasFocus ? ColorScheme.Focus : GetNormalColor (),
-					HasFocus ? ColorScheme.HotFocus : Enabled ? ColorScheme.HotNormal : ColorScheme.Disabled);
+				TextFormatter?.Draw (ViewToScreen (Bounds), HasFocus ? ColorScheme.Focus : GetNormalColor (),
+					HasFocus ? ColorScheme.HotFocus : Enabled ? ColorScheme.HotNormal : ColorScheme.Disabled,
+					SuperView == null ? default : SuperView.ViewToScreen (SuperView.Bounds));
 			}
 
 			// Invoke DrawContentEvent
@@ -1728,7 +1738,7 @@ namespace Terminal.Gui {
 		/// </summary>
 		/// <param name="command">The command to search.</param>
 		/// <returns>The <see cref="Key"/> used by a <see cref="Command"/></returns>
-		public Key GetKeyFromCommand(Command command)
+		public Key GetKeyFromCommand (Command command)
 		{
 			return KeyBindings.First (x => x.Value == command).Key;
 		}
@@ -2162,7 +2172,7 @@ namespace Terminal.Gui {
 			Rect oldBounds = Bounds;
 			OnLayoutStarted (new LayoutEventArgs () { OldBounds = oldBounds });
 
-			textFormatter.Size = Bounds.Size;
+			TextFormatter.Size = Bounds.Size;
 
 
 			// Sort out the dependencies of the X, Y, Width, Height properties
@@ -2263,15 +2273,15 @@ namespace Terminal.Gui {
 		/// </para>
 		/// </remarks>
 		public virtual ustring Text {
-			get => textFormatter.Text;
+			get => TextFormatter.Text;
 			set {
-				textFormatter.Text = value;
+				TextFormatter.Text = value;
 				var prevSize = frame.Size;
 				var canResize = ResizeView (autoSize);
-				if (canResize && textFormatter.Size != Bounds.Size) {
-					Bounds = new Rect (new Point (Bounds.X, Bounds.Y), textFormatter.Size);
-				} else if (!canResize && textFormatter.Size != Bounds.Size) {
-					textFormatter.Size = Bounds.Size;
+				if (canResize && TextFormatter.Size != Bounds.Size) {
+					Bounds = new Rect (new Point (Bounds.X, Bounds.Y), TextFormatter.Size);
+				} else if (!canResize && TextFormatter.Size != Bounds.Size) {
+					TextFormatter.Size = Bounds.Size;
 				}
 				SetNeedsLayout ();
 				SetNeedsDisplay (new Rect (new Point (0, 0),
@@ -2289,10 +2299,10 @@ namespace Terminal.Gui {
 			get => autoSize;
 			set {
 				var v = ResizeView (value);
-				textFormatter.AutoSize = v;
+				TextFormatter.AutoSize = v;
 				if (autoSize != v) {
 					autoSize = v;
-					textFormatter.NeedsFormat = true;
+					TextFormatter.NeedsFormat = true;
 					SetNeedsLayout ();
 					SetNeedsDisplay ();
 				}
@@ -2304,9 +2314,9 @@ namespace Terminal.Gui {
 		/// </summary>
 		/// <value>The text alignment.</value>
 		public virtual TextAlignment TextAlignment {
-			get => textFormatter.Alignment;
+			get => TextFormatter.Alignment;
 			set {
-				textFormatter.Alignment = value;
+				TextFormatter.Alignment = value;
 				SetNeedsDisplay ();
 			}
 		}
@@ -2316,9 +2326,9 @@ namespace Terminal.Gui {
 		/// </summary>
 		/// <value>The text alignment.</value>
 		public virtual VerticalTextAlignment VerticalTextAlignment {
-			get => textFormatter.VerticalAlignment;
+			get => TextFormatter.VerticalAlignment;
 			set {
-				textFormatter.VerticalAlignment = value;
+				TextFormatter.VerticalAlignment = value;
 				SetNeedsDisplay ();
 			}
 		}
@@ -2328,17 +2338,17 @@ namespace Terminal.Gui {
 		/// </summary>
 		/// <value>The text alignment.</value>
 		public virtual TextDirection TextDirection {
-			get => textFormatter.Direction;
+			get => TextFormatter.Direction;
 			set {
-				if (textFormatter.Direction != value) {
-					textFormatter.Direction = value;
-					if (IsInitialized && AutoSize) {
+				if (TextFormatter.Direction != value) {
+					TextFormatter.Direction = value;
+					if (AutoSize) {
 						ResizeView (true);
 					} else if (IsInitialized) {
 						var b = new Rect (Bounds.X, Bounds.Y, Bounds.Height, Bounds.Width);
 						SetWidthHeight (b);
 					}
-					textFormatter.Size = Bounds.Size;
+					TextFormatter.Size = Bounds.Size;
 					SetNeedsDisplay ();
 				}
 			}
@@ -2423,11 +2433,11 @@ namespace Terminal.Gui {
 			}
 
 			var aSize = autoSize;
-			Rect nBounds = TextFormatter.CalcRect (Bounds.X, Bounds.Y, Text, textFormatter.Direction);
-			if (textFormatter.Size != nBounds.Size) {
-				textFormatter.Size = nBounds.Size;
+			Rect nBounds = TextFormatter.CalcRect (Bounds.X, Bounds.Y, Text, TextFormatter.Direction);
+			if (TextFormatter.Size != nBounds.Size) {
+				TextFormatter.Size = nBounds.Size;
 			}
-			if ((textFormatter.Size != Bounds.Size || textFormatter.Size != nBounds.Size)
+			if ((TextFormatter.Size != Bounds.Size || TextFormatter.Size != nBounds.Size)
 				&& (((width == null || width is Dim.DimAbsolute) && (Bounds.Width == 0
 				|| autoSize && Bounds.Width != nBounds.Width))
 				|| ((height == null || height is Dim.DimAbsolute) && (Bounds.Height == 0
@@ -2452,7 +2462,7 @@ namespace Terminal.Gui {
 			}
 			if (aSize) {
 				Bounds = new Rect (Bounds.X, Bounds.Y, canSizeW ? rW : Bounds.Width, canSizeH ? rH : Bounds.Height);
-				textFormatter.Size = Bounds.Size;
+				TextFormatter.Size = Bounds.Size;
 			}
 
 			return aSize;

+ 5 - 6
Terminal.Gui/Core/Window.cs

@@ -269,18 +269,16 @@ namespace Terminal.Gui {
 			var scrRect = ViewToScreen (new Rect (0, 0, Frame.Width, Frame.Height));
 			//var borderLength = Border.DrawMarginFrame ? 1 : 0;
 
-			// 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?
+			// FIXED: 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 (GetNormalColor ());
-				//Driver.DrawWindowFrame (scrRect, padding.Left + borderLength, padding.Top + borderLength, padding.Right + borderLength, padding.Bottom + borderLength,
-				//	Border.BorderStyle != BorderStyle.None, fill: true, Border);
-				Border.DrawContent ();
+				Clear ();
 			}
 			var savedClip = contentView.ClipToBounds ();
 
 			// Redraw our contentView
-			// TODO: smartly constrict contentView.Bounds to just be what intersects with the 'bounds' we were passed
-			contentView.Redraw (contentView.Bounds);
+			// DONE: smartly constrict contentView.Bounds to just be what intersects with the 'bounds' we were passed
+			contentView.Redraw (!NeedDisplay.IsEmpty ? contentView.Bounds : bounds);
 			Driver.Clip = savedClip;
 
 			ClearLayoutNeeded ();
@@ -289,6 +287,7 @@ namespace Terminal.Gui {
 				Driver.SetAttribute (GetNormalColor ());
 				//Driver.DrawWindowFrame (scrRect, padding.Left + borderLength, padding.Top + borderLength, padding.Right + borderLength, padding.Bottom + borderLength,
 				//	Border.BorderStyle != BorderStyle.None, fill: true, Border.BorderStyle);
+				Border.DrawContent (this, false);
 				if (HasFocus)
 					Driver.SetAttribute (ColorScheme.HotNormal);
 				Driver.DrawWindowTitle (scrRect, Title, padding.Left, padding.Top, padding.Right, padding.Bottom);

+ 12 - 11
Terminal.Gui/Views/Button.cs

@@ -33,7 +33,6 @@ namespace Terminal.Gui {
 	public class Button : View {
 		ustring text;
 		bool is_default;
-		TextFormatter textFormatter = new TextFormatter ();
 
 		/// <summary>
 		///   Initializes a new instance of <see cref="Button"/> using <see cref="LayoutStyle.Computed"/> layout.
@@ -178,7 +177,7 @@ namespace Terminal.Gui {
 		public override Rune HotKeySpecifier {
 			get => hotKeySpecifier;
 			set {
-				hotKeySpecifier = textFormatter.HotKeySpecifier = value;
+				hotKeySpecifier = TextFormatter.HotKeySpecifier = value;
 			}
 		}
 
@@ -194,11 +193,11 @@ namespace Terminal.Gui {
 		internal void Update ()
 		{
 			if (IsDefault)
-				textFormatter.Text = ustring.Make (_leftBracket) + ustring.Make (_leftDefault) + " " + text + " " + ustring.Make (_rightDefault) + ustring.Make (_rightBracket);
+				TextFormatter.Text = ustring.Make (_leftBracket) + ustring.Make (_leftDefault) + " " + text + " " + ustring.Make (_rightDefault) + ustring.Make (_rightBracket);
 			else
-				textFormatter.Text = ustring.Make (_leftBracket) + " " + text + " " + ustring.Make (_rightBracket);
+				TextFormatter.Text = ustring.Make (_leftBracket) + " " + text + " " + ustring.Make (_rightBracket);
 
-			int w = textFormatter.Text.RuneCount - (textFormatter.Text.Contains (HotKeySpecifier) ? 1 : 0);
+			int w = TextFormatter.Size.Width - (TextFormatter.Text.Contains (HotKeySpecifier) ? 1 : 0);
 			GetCurrentWidth (out int cWidth);
 			var canSetWidth = SetWidth (w, out int rWidth);
 			if (canSetWidth && (cWidth < rWidth || AutoSize)) {
@@ -233,11 +232,12 @@ namespace Terminal.Gui {
 				Border.DrawContent (this);
 			}
 
-			if (!ustring.IsNullOrEmpty (textFormatter.Text)) {
+			if (!ustring.IsNullOrEmpty (TextFormatter.Text)) {
 				Clear ();
-				textFormatter.NeedsFormat = true;
-				textFormatter?.Draw (ViewToScreen (Bounds), HasFocus ? ColorScheme.Focus : GetNormalColor (),
-					HasFocus ? ColorScheme.HotFocus : Enabled ? ColorScheme.HotNormal : ColorScheme.Disabled);
+				TextFormatter.NeedsFormat = true;
+				TextFormatter?.Draw (ViewToScreen (Bounds), HasFocus ? ColorScheme.Focus : GetNormalColor (),
+					HasFocus ? ColorScheme.HotFocus : Enabled ? ColorScheme.HotNormal : ColorScheme.Disabled,
+					SuperView == null ? default : SuperView.ViewToScreen (SuperView.Bounds));
 			}
 		}
 
@@ -328,6 +328,7 @@ namespace Terminal.Gui {
 					if (!HasFocus) {
 						SetFocus ();
 						SetNeedsDisplay ();
+						Redraw (Bounds);
 					}
 					OnClicked ();
 				}
@@ -341,8 +342,8 @@ namespace Terminal.Gui {
 		public override void PositionCursor ()
 		{
 			if (HotKey == Key.Unknown && text != "") {
-				for (int i = 0; i < textFormatter.Text.RuneCount; i++) {
-					if (textFormatter.Text [i] == text [0]) {
+				for (int i = 0; i < TextFormatter.Text.RuneCount; i++) {
+					if (TextFormatter.Text [i] == text [0]) {
 						Move (i, 0);
 						return;
 					}

+ 3 - 2
Terminal.Gui/Views/FrameView.cs

@@ -216,17 +216,18 @@ namespace Terminal.Gui {
 			if (!NeedDisplay.IsEmpty) {
 				Driver.SetAttribute (GetNormalColor ());
 				//Driver.DrawWindowFrame (scrRect, padding + 1, padding + 1, padding + 1, padding + 1, border: true, fill: true);
-				Border.DrawContent ();
+				Clear ();
 			}
 
 			var savedClip = contentView.ClipToBounds ();
-			contentView.Redraw (contentView.Bounds);
+			contentView.Redraw (!NeedDisplay.IsEmpty ? contentView.Bounds : bounds);
 			Driver.Clip = savedClip;
 
 			ClearNeedsDisplay ();
 			if (Border.BorderStyle != BorderStyle.None) {
 				Driver.SetAttribute (GetNormalColor ());
 				//Driver.DrawWindowFrame (scrRect, padding + 1, padding + 1, padding + 1, padding + 1, border: true, fill: false);
+				Border.DrawContent (this, false);
 				if (HasFocus)
 					Driver.SetAttribute (ColorScheme.HotNormal);
 				//Driver.DrawWindowTitle (scrRect, Title, padding, padding, padding, padding);

+ 12 - 10
Terminal.Gui/Views/Label.cs

@@ -27,37 +27,39 @@ namespace Terminal.Gui {
 		}
 
 		/// <inheritdoc/>
-		public Label (Rect frame) : base (frame)
+		public Label (Rect frame, bool autosize = false) : base (frame)
 		{
+			Initialize (autosize);
 		}
 
 		/// <inheritdoc/>
-		public Label (ustring text) : base (text)
+		public Label (ustring text, bool autosize = true) : base (text)
 		{
-			Initialize ();
+			Initialize (autosize);
 		}
 
 		/// <inheritdoc/>
-		public Label (Rect rect, ustring text) : base (rect, text)
+		public Label (Rect rect, ustring text, bool autosize = false) : base (rect, text)
 		{
+			Initialize (autosize);
 		}
 
 		/// <inheritdoc/>
-		public Label (int x, int y, ustring text) : base (x, y, text)
+		public Label (int x, int y, ustring text, bool autosize = true) : base (x, y, text)
 		{
-			Initialize ();
+			Initialize (autosize);
 		}
 
 		/// <inheritdoc/>
-		public Label (ustring text, TextDirection direction)
+		public Label (ustring text, TextDirection direction, bool autosize = true)
 			: base (text, direction)
 		{
-			Initialize ();
+			Initialize (autosize);
 		}
 
-		void Initialize ()
+		void Initialize (bool autosize = true)
 		{
-			AutoSize = true;
+			AutoSize = autosize;
 		}
 
 		/// <summary>

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

@@ -514,7 +514,8 @@ namespace Terminal.Gui {
 						};
 						tf.Draw (ViewToScreen (new Rect (2, i + 1, Frame.Width - 3, 1)),
 							i == current ? ColorScheme.Focus : GetNormalColor (),
-							i == current ? ColorScheme.HotFocus : ColorScheme.HotNormal);
+							i == current ? ColorScheme.HotFocus : ColorScheme.HotNormal,
+							SuperView == null ? default : SuperView.ViewToScreen (SuperView.Bounds));
 					} else {
 						DrawHotString (textToDraw,
 							i == current ? ColorScheme.HotFocus : ColorScheme.HotNormal,

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

@@ -335,7 +335,8 @@ namespace Terminal.Gui {
 					? 2 : 1);
 				Move (padding, row);
 				var rect = new Rect (padding, row, fWidth, Frame.Height);
-				tf?.Draw (ViewToScreen (rect), ColorScheme.HotNormal, ColorScheme.HotNormal);
+				tf?.Draw (ViewToScreen (rect), ColorScheme.HotNormal, ColorScheme.HotNormal,
+					SuperView == null ? default : SuperView.ViewToScreen (SuperView.Bounds));
 				break;
 			}
 		}

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

@@ -374,7 +374,10 @@ namespace Terminal.Gui {
 			}
 			var pos = point - first + Math.Min (Frame.X, 0);
 			var offB = OffSetBackground ();
-			if (pos > -1 && col >= pos && pos < Frame.Width + offB) {
+			var containerFrame = SuperView?.ViewToScreen (SuperView.Bounds) ?? default;
+			var thisFrame = ViewToScreen (Bounds);
+			if (pos > -1 && col >= pos && pos < Frame.Width + offB
+				&& containerFrame.IntersectsWith (thisFrame)) {
 				RestoreCursorVisibility ();
 				Move (col, 0);
 			} else {

+ 2 - 2
UICatalog/Scenarios/Borders.cs

@@ -36,8 +36,8 @@ namespace UICatalog.Scenarios {
 			smartPanel.Add (new Label () { // Or smartPanel.Child = 
 				X = 0,
 				Y = 0,
-				Width = 24,
-				Height = 13,
+				//Width = 24, commenting because now setting the size disable auto-size
+				//Height = 13,
 				ColorScheme = Colors.TopLevel,
 				Text = "This is a test\nwith a \nPanelView",
 				TextAlignment = TextAlignment.Centered

+ 7 - 4
UICatalog/Scenarios/CharacterMap.cs

@@ -144,11 +144,8 @@ namespace UICatalog.Scenarios {
 				Move (viewport.X + RowHeaderWidth + (header * H_SPACE), 0);
 				Driver.AddStr ($" {header:x} ");
 			}
-			for (int row = 0, y = 0; row < viewport.Height / 2 - 1; row++, y+= V_SPACE) {
+			for (int row = 0, y = 0; row < viewport.Height / 2 - 1; row++, y += V_SPACE) {
 				int val = (-viewport.Y + row) * 16;
-				if (val >= 0x00D800 && val <= 0x00DFFF) {
-					continue;
-				}
 				if (val < MaxCodePointVal) {
 					var rowLabel = $"U+{val / 16:x4}x";
 					Move (0, y + 1);
@@ -157,6 +154,12 @@ namespace UICatalog.Scenarios {
 					for (int col = 0; col < 16; col++) {
 						var rune = new Rune ((uint)((uint)(-viewport.Y + row) * 16 + col));
 						Move (viewport.X + RowHeaderWidth + (col * H_SPACE) + (prevColWasWide ? 0 : 1), y + 1);
+						if (rune >= 0x00D800 && rune <= 0x00DFFF) {
+							if (col == 0) {
+								Driver.AddStr ("Reserved to surrogate pairs.");
+							}
+							continue;
+						}
 						Driver.AddRune (rune);
 						//prevColWasWide = Rune.ColumnWidth (rune) > 1;
 					}

+ 3 - 3
UICatalog/Scenarios/ComputedLayout.cs

@@ -39,7 +39,7 @@ namespace UICatalog.Scenarios {
 			var horizontalRuler = new Label ("") {
 				X = 0,
 				Y = 0,
-				Width = Dim.Fill (1),  // BUGBUG: I don't think this should be needed; DimFill() should respect container's frame. X does.
+				Width = Dim.Fill (),  // FIXED: I don't think this should be needed; DimFill() should respect container's frame. X does.
 				ColorScheme = Colors.Error
 			};
 
@@ -143,14 +143,14 @@ namespace UICatalog.Scenarios {
 				ColorScheme = Colors.Menu,
 				Width = Dim.Fill (),
 				X = Pos.Center (),
-				Y = Pos.Bottom (Win) - 4  // BUGBUG: -2 should be two lines above border; but it has to be -4
+				Y = Pos.AnchorEnd () - 2 // FIXED: -2 should be two lines above border; but it has to be -4
 			};
 			Win.Add (bottomLabel);
 
 			// Show positioning vertically using Pos.Bottom 
 			// BUGBUG: -1 should be just above border; but it has to be -3
 			var leftButton = new Button ("Left") {
-				Y = Pos.Bottom (Win) - 3
+				Y = Pos.AnchorEnd () - 1
 			};
 			leftButton.Clicked += () => {
 				// Ths demonstrates how to have a dynamically sized button

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

+ 14 - 5
UnitTests/GraphViewTests.cs

@@ -9,6 +9,7 @@ using Attribute = Terminal.Gui.Attribute;
 using System.Text;
 using System.Text.RegularExpressions;
 using Xunit.Abstractions;
+using Rune = System.Rune;
 
 namespace Terminal.Gui.Views {
 
@@ -140,10 +141,13 @@ namespace Terminal.Gui.Views {
 								runes.InsertRange (i, new List<char> () { ' ' });
 							}
 						}
-						if (c > w) {
-							w = c;
+						if (Rune.ColumnWidth (rune) > 1) {
+							c++;
 						}
-						h = r - y;
+						if (c + 1 > w) {
+							w = c + 1;
+						}
+						h = r - y + 1;
 					}
 					if (x > -1) {
 						runes.Add (rune);
@@ -155,7 +159,7 @@ namespace Terminal.Gui.Views {
 			}
 
 			// Remove unnecessary empty lines
-			for (int r = lines.Count - 1; r > h; r--) {
+			for (int r = lines.Count - 1; r > h - 1; r--) {
 				lines.RemoveAt (r);
 			}
 
@@ -201,7 +205,7 @@ namespace Terminal.Gui.Views {
 
 				Assert.Equal (expectedLook, actualLook);
 			}
-			return new Rect (x, y, w > -1 ? w + 1 : 0, h > -1 ? h + 1 : 0);
+			return new Rect (x, y, w > -1 ? w : 0, h > -1 ? h : 0);
 		}
 
 #pragma warning disable xUnit1013 // Public method should be marked as test
@@ -1687,6 +1691,11 @@ namespace Terminal.Gui.Views {
 				//put label into view
 				mount.Add (lbl1);
 
+				//putting mount into toplevel since changing size
+				//also change AutoSize to false
+				Application.Top.Add (mount);
+				Application.Begin (Application.Top);
+
 				// render view
 				lbl1.ColorScheme = new ColorScheme ();
 				Assert.Equal (1, lbl1.Height);

+ 141 - 1
UnitTests/PanelViewTests.cs

@@ -4,9 +4,17 @@ using System.Linq;
 using System.Text;
 using System.Threading.Tasks;
 using Xunit;
+using Xunit.Abstractions;
 
 namespace Terminal.Gui.Views {
 	public class PanelViewTests {
+		readonly ITestOutputHelper output;
+
+		public PanelViewTests (ITestOutputHelper output)
+		{
+			this.output = output;
+		}
+
 		[Fact]
 		public void Constructor_Defaults ()
 		{
@@ -32,7 +40,7 @@ namespace Terminal.Gui.Views {
 		{
 			var pv = new PanelView (new Label ("This is a test."));
 			Assert.NotNull (pv.Child);
-			Assert.Equal (1, pv.Subviews[0].Subviews.Count);
+			Assert.Equal (1, pv.Subviews [0].Subviews.Count);
 
 			pv.Child = null;
 			Assert.Null (pv.Child);
@@ -331,5 +339,137 @@ namespace Terminal.Gui.Views {
 			Assert.Equal (new Rect (3, 5, 20, 10), pv1.Frame);
 			Assert.Equal (new Rect (5, 6, 15, 4), pv1.Child.Frame);
 		}
+
+		[Fact, AutoInitShutdown]
+		public void Setting_Child_Size_Disable_AutoSize ()
+		{
+			var top = Application.Top;
+			var win = new Window ();
+			var label = new Label ("Hello World") {
+				Width = 24,
+				Height = 13,
+				ColorScheme = Colors.TopLevel,
+				Text = "This is a test\nwith a \nPanelView",
+				TextAlignment = TextAlignment.Centered
+			};
+			var pv = new PanelView (label) {
+				Width = 24,
+				Height = 13,
+				Border = new Border () {
+					BorderStyle = BorderStyle.Single,
+					DrawMarginFrame = true,
+					BorderThickness = new Thickness (2),
+					BorderBrush = Color.Red,
+					Padding = new Thickness (2),
+					Background = Color.BrightGreen,
+					Effect3D = true
+				},
+			};
+			win.Add (pv);
+			top.Add (win);
+
+			Application.Begin (top);
+
+			Assert.Equal (new Rect (0, 0, 24, 13), label.Frame);
+			Assert.Equal (new Rect (0, 0, 34, 23), pv.Frame);
+			Assert.Equal (new Rect (0, 0, 80, 25), win.Frame);
+			Assert.Equal (new Rect (0, 0, 80, 25), Application.Top.Frame);
+
+			var expected = @"
+┌──────────────────────────────────────────────────────────────────────────────┐
+│                                                                              │
+│                                                                              │
+│                                                                              │
+│                                                                              │
+│    ┌────────────────────────┐                                                │
+│    │     This is a test     │                                                │
+│    │        with a          │                                                │
+│    │       PanelView        │                                                │
+│    │                        │                                                │
+│    │                        │                                                │
+│    │                        │                                                │
+│    │                        │                                                │
+│    │                        │                                                │
+│    │                        │                                                │
+│    │                        │                                                │
+│    │                        │                                                │
+│    │                        │                                                │
+│    │                        │                                                │
+│    └────────────────────────┘                                                │
+│                                                                              │
+│                                                                              │
+│                                                                              │
+│                                                                              │
+└──────────────────────────────────────────────────────────────────────────────┘
+";
+
+			var pos = GraphViewTests.AssertDriverContentsWithFrameAre (expected, output);
+			Assert.Equal (new Rect (0, 0, 80, 25), pos);
+		}
+
+		[Fact, AutoInitShutdown]
+		public void Not_Setting_Child_Size_Default_AutoSize_True ()
+		{
+			var top = Application.Top;
+			var win = new Window ();
+			var label = new Label ("Hello World") {
+				ColorScheme = Colors.TopLevel,
+				Text = "This is a test\nwith a \nPanelView",
+				TextAlignment = TextAlignment.Centered
+			};
+			var pv = new PanelView (label) {
+				Width = 24,
+				Height = 13,
+				Border = new Border () {
+					BorderStyle = BorderStyle.Single,
+					DrawMarginFrame = true,
+					BorderThickness = new Thickness (2),
+					BorderBrush = Color.Red,
+					Padding = new Thickness (2),
+					Background = Color.BrightGreen,
+					Effect3D = true
+				},
+			};
+			win.Add (pv);
+			top.Add (win);
+
+			Application.Begin (top);
+
+			Assert.Equal (new Rect (0, 0, 14, 3), label.Frame);
+			Assert.Equal (new Rect (0, 0, 24, 13), pv.Frame);
+			Assert.Equal (new Rect (0, 0, 80, 25), win.Frame);
+			Assert.Equal (new Rect (0, 0, 80, 25), Application.Top.Frame);
+
+			var expected = @"
+┌──────────────────────────────────────────────────────────────────────────────┐
+│                                                                              │
+│                                                                              │
+│                                                                              │
+│                                                                              │
+│    ┌──────────────┐                                                          │
+│    │This is a test│                                                          │
+│    │   with a     │                                                          │
+│    │  PanelView   │                                                          │
+│    └──────────────┘                                                          │
+│                                                                              │
+│                                                                              │
+│                                                                              │
+│                                                                              │
+│                                                                              │
+│                                                                              │
+│                                                                              │
+│                                                                              │
+│                                                                              │
+│                                                                              │
+│                                                                              │
+│                                                                              │
+│                                                                              │
+│                                                                              │
+└──────────────────────────────────────────────────────────────────────────────┘
+";
+
+			var pos = GraphViewTests.AssertDriverContentsWithFrameAre (expected, output);
+			Assert.Equal (new Rect (0, 0, 80, 25), pos);
+		}
 	}
 }

+ 603 - 4
UnitTests/TextFormatterTests.cs

@@ -1,17 +1,22 @@
 using NStack;
 using System;
 using System.Collections.Generic;
-using System.ComponentModel;
-using System.IO;
 using System.Linq;
-using Terminal.Gui;
+using Terminal.Gui.Views;
 using Xunit;
+using Xunit.Abstractions;
 
 // Alias Console to MockConsole so we don't accidentally use Console
 using Console = Terminal.Gui.FakeConsole;
 
 namespace Terminal.Gui.Core {
 	public class TextFormatterTests {
+		readonly ITestOutputHelper output;
+
+		public TextFormatterTests (ITestOutputHelper output)
+		{
+			this.output = output;
+		}
 
 		[Fact]
 		public void Basic_Usage ()
@@ -1931,6 +1936,240 @@ namespace Terminal.Gui.Core {
 			Assert.True (wrappedLines.Count == text.Length);
 		}
 
+		[Fact]
+		public void WordWrap_preserveTrailingSpaces_Wide_Runes ()
+		{
+			var text = ustring.Empty;
+			int maxWidth = 1;
+			int expectedClippedWidth = 1;
+
+			List<ustring> wrappedLines;
+
+			text = "文に は言葉 があり ます。";
+			maxWidth = 14;
+			expectedClippedWidth = 14;
+			wrappedLines = TextFormatter.WordWrap (text, maxWidth, true);
+			Assert.True (expectedClippedWidth >= wrappedLines.Max (l => l.RuneCount));
+			Assert.Equal ("文に は言葉 ", wrappedLines [0].ToString ());
+			Assert.Equal ("があり ます。", wrappedLines [1].ToString ());
+			Assert.True (wrappedLines.Count == 2);
+
+			maxWidth = 3;
+			expectedClippedWidth = 3;
+			wrappedLines = TextFormatter.WordWrap (text, maxWidth, true);
+			Assert.True (expectedClippedWidth >= wrappedLines.Max (l => l.RuneCount));
+			Assert.Equal ("文に", wrappedLines [0].ToString ());
+			Assert.Equal (" ", wrappedLines [1].ToString ());
+			Assert.Equal ("は言", wrappedLines [2].ToString ());
+			Assert.Equal ("葉 ", wrappedLines [3].ToString ());
+			Assert.Equal ("があ", wrappedLines [4].ToString ());
+			Assert.Equal ("り ", wrappedLines [5].ToString ());
+			Assert.Equal ("ます", wrappedLines [6].ToString ());
+			Assert.Equal ("。", wrappedLines [^1].ToString ());
+			Assert.True (wrappedLines.Count == 8);
+
+			maxWidth = 2;
+			expectedClippedWidth = 2;
+			wrappedLines = TextFormatter.WordWrap (text, maxWidth, true);
+			Assert.True (expectedClippedWidth >= wrappedLines.Max (l => l.RuneCount));
+			Assert.Equal ("文", wrappedLines [0].ToString ());
+			Assert.Equal ("に", wrappedLines [1].ToString ());
+			Assert.Equal (" ", wrappedLines [2].ToString ());
+			Assert.Equal ("は", wrappedLines [3].ToString ());
+			Assert.Equal ("言", wrappedLines [4].ToString ());
+			Assert.Equal ("葉", wrappedLines [5].ToString ());
+			Assert.Equal (" ", wrappedLines [6].ToString ());
+			Assert.Equal ("が", wrappedLines [7].ToString ());
+			Assert.Equal ("あ", wrappedLines [8].ToString ());
+			Assert.Equal ("り", wrappedLines [9].ToString ());
+			Assert.Equal (" ", wrappedLines [10].ToString ());
+			Assert.Equal ("ま", wrappedLines [11].ToString ());
+			Assert.Equal ("す", wrappedLines [12].ToString ());
+			Assert.Equal ("。", wrappedLines [^1].ToString ());
+			Assert.True (wrappedLines.Count == 14);
+
+			maxWidth = 1;
+			expectedClippedWidth = 1;
+			wrappedLines = TextFormatter.WordWrap (text, maxWidth, true);
+			Assert.True (expectedClippedWidth >= wrappedLines.Max (l => l.RuneCount));
+			Assert.Equal ("文", wrappedLines [0].ToString ());
+			Assert.Equal ("に", wrappedLines [1].ToString ());
+			Assert.Equal (" ", wrappedLines [2].ToString ());
+			Assert.Equal ("は", wrappedLines [3].ToString ());
+			Assert.Equal ("言", wrappedLines [4].ToString ());
+			Assert.Equal ("葉", wrappedLines [5].ToString ());
+			Assert.Equal (" ", wrappedLines [6].ToString ());
+			Assert.Equal ("が", wrappedLines [7].ToString ());
+			Assert.Equal ("あ", wrappedLines [8].ToString ());
+			Assert.Equal ("り", wrappedLines [9].ToString ());
+			Assert.Equal (" ", wrappedLines [10].ToString ());
+			Assert.Equal ("ま", wrappedLines [11].ToString ());
+			Assert.Equal ("す", wrappedLines [12].ToString ());
+			Assert.Equal ("。", wrappedLines [^1].ToString ());
+			Assert.False (wrappedLines.Count == text.Length);
+			Assert.True (wrappedLines.Count == text.RuneCount);
+			Assert.Equal (25, text.ConsoleWidth);
+			Assert.Equal (25, TextFormatter.GetTextWidth (text));
+		}
+
+		[Fact, AutoInitShutdown]
+		public void WordWrap_preserveTrailingSpaces_Horizontal_With_Simple_Runes ()
+		{
+			var text = "A sentence has words.";
+			var width = 3;
+			var height = 8;
+			var wrappedLines = TextFormatter.WordWrap (text, width, true);
+			var breakLines = "";
+			foreach (var line in wrappedLines) {
+				breakLines += $"{line}{Environment.NewLine}";
+			}
+			var label = new Label (breakLines) { Width = Dim.Fill (), Height = Dim.Fill () };
+			var frame = new FrameView () { Width = Dim.Fill (), Height = Dim.Fill () };
+
+			frame.Add (label);
+			Application.Top.Add (frame);
+			Application.Begin (Application.Top);
+			((FakeDriver)Application.Driver).SetBufferSize (width + 2, height + 2);
+
+			Assert.False (label.AutoSize);
+			Assert.Equal (new Rect (0, 0, width, height), label.Frame);
+			Assert.Equal (new Rect (0, 0, width + 2, height + 2), frame.Frame);
+
+			var expected = @"
+┌───┐
+│A  │
+│sen│
+│ten│
+│ce │
+│has│
+│   │
+│wor│
+│ds.│
+└───┘
+";
+
+			var pos = GraphViewTests.AssertDriverContentsWithFrameAre (expected, output);
+			Assert.Equal (new Rect (0, 0, width + 2, height + 2), pos);
+		}
+
+		[Fact, AutoInitShutdown]
+		public void WordWrap_preserveTrailingSpaces_Vertical_With_Simple_Runes ()
+		{
+			var text = "A sentence has words.";
+			var width = 8;
+			var height = 3;
+			var wrappedLines = TextFormatter.WordWrap (text, width, true);
+			var breakLines = "";
+			foreach (var line in wrappedLines) {
+				breakLines += $"{line}{Environment.NewLine}";
+			}
+			var label = new Label (breakLines) {
+				TextDirection = TextDirection.TopBottom_LeftRight,
+				Width = Dim.Fill (), Height = Dim.Fill () 
+			};
+			var frame = new FrameView () { Width = Dim.Fill (), Height = Dim.Fill () };
+
+			frame.Add (label);
+			Application.Top.Add (frame);
+			Application.Begin (Application.Top);
+			((FakeDriver)Application.Driver).SetBufferSize (width + 2, height + 2);
+
+			Assert.False (label.AutoSize);
+			Assert.Equal (new Rect (0, 0, width, height), label.Frame);
+			Assert.Equal (new Rect (0, 0, width + 2, height + 2), frame.Frame);
+
+			var expected = @"
+┌────────┐
+│Astc swd│
+│ eeeh os│
+│ nn a r.│
+└────────┘
+";
+
+			var pos = GraphViewTests.AssertDriverContentsWithFrameAre (expected, output);
+			Assert.Equal (new Rect (0, 0, width + 2, height + 2), pos);
+		}
+
+		[Fact, AutoInitShutdown]
+		public void WordWrap_preserveTrailingSpaces_Horizontal_With_Wide_Runes ()
+		{
+			var text = "文に は言葉 があり ます。";
+			var width = 6;
+			var height = 8;
+			var wrappedLines = TextFormatter.WordWrap (text, width, true);
+			var breakLines = "";
+			foreach (var line in wrappedLines) {
+				breakLines += $"{line}{Environment.NewLine}";
+			}
+			var label = new Label (breakLines) { Width = Dim.Fill (), Height = Dim.Fill () };
+			var frame = new FrameView () { Width = Dim.Fill (), Height = Dim.Fill () };
+
+			frame.Add (label);
+			Application.Top.Add (frame);
+			Application.Begin (Application.Top);
+			((FakeDriver)Application.Driver).SetBufferSize (width + 2, height + 2);
+
+			Assert.False (label.AutoSize);
+			Assert.Equal (new Rect (0, 0, width, height), label.Frame);
+			Assert.Equal (new Rect (0, 0, width + 2, height + 2), frame.Frame);
+
+			var expected = @"
+┌──────┐
+│文に  │
+│は言葉│
+│ があ │
+│り    │
+│ ます │
+│。    │
+│      │
+│      │
+└──────┘
+";
+
+			var pos = GraphViewTests.AssertDriverContentsWithFrameAre (expected, output);
+			Assert.Equal (new Rect (0, 0, width + 2, height + 2), pos);
+		}
+
+		[Fact, AutoInitShutdown]
+		public void WordWrap_preserveTrailingSpaces_Vertical_With_Wide_Runes ()
+		{
+			var text = "文に は言葉 があり ます。";
+			var width = 8;
+			var height = 4;
+			var wrappedLines = TextFormatter.WordWrap (text, width, true);
+			var breakLines = "";
+			foreach (var line in wrappedLines) {
+				breakLines += $"{line}{Environment.NewLine}";
+			}
+			var label = new Label (breakLines) {
+				TextDirection = TextDirection.TopBottom_LeftRight,
+				Width = Dim.Fill (),
+				Height = Dim.Fill ()
+			};
+			var frame = new FrameView () { Width = Dim.Fill (), Height = Dim.Fill () };
+
+			frame.Add (label);
+			Application.Top.Add (frame);
+			Application.Begin (Application.Top);
+			((FakeDriver)Application.Driver).SetBufferSize (width + 2, height + 2);
+
+			Assert.False (label.AutoSize);
+			Assert.Equal (new Rect (0, 0, width, height), label.Frame);
+			Assert.Equal (new Rect (0, 0, width + 2, height + 2), frame.Frame);
+
+			var expected = @"
+┌────────┐
+│文はがま│
+│に言あす│
+│  葉り。│
+│        │
+└────────┘
+";
+
+			var pos = GraphViewTests.AssertDriverContentsWithFrameAre (expected, output);
+			Assert.Equal (new Rect (0, 0, width + 2, height + 2), pos);
+		}
+
 		[Fact]
 		public void WordWrap_preserveTrailingSpaces_With_Tab ()
 		{
@@ -2011,6 +2250,18 @@ namespace Terminal.Gui.Core {
 			Assert.True (wrappedLines.Count == text.Length);
 		}
 
+		[Fact]
+		public void WordWrap_Unicode_Wide_Runes ()
+		{
+			ustring text = "これが最初の行です。 こんにちは世界。 これが2行目です。";
+			var width = text.RuneCount;
+			var wrappedLines = TextFormatter.WordWrap (text, width);
+			Assert.Equal (3, wrappedLines.Count);
+			Assert.Equal ("これが最初の行です。", wrappedLines [0].ToString ());
+			Assert.Equal ("こんにちは世界。", wrappedLines [1].ToString ());
+			Assert.Equal ("これが2行目です。", wrappedLines [^1].ToString ());
+		}
+
 		[Fact]
 		public void ReplaceHotKeyWithTag ()
 		{
@@ -2539,7 +2790,7 @@ namespace Terminal.Gui.Core {
 		}
 
 		[Fact]
-		public void TestClipOrPad_ShortWord()
+		public void TestClipOrPad_ShortWord ()
 		{
 			// word is short but we want it to fill 6 so it should be padded
 			Assert.Equal ("fff   ", TextFormatter.ClipOrPad ("fff", 6));
@@ -2589,7 +2840,355 @@ namespace Terminal.Gui.Core {
 			Assert.Equal (Key.Null, tf.HotKey);
 			tf.HotKey = Key.CtrlMask | Key.Q;
 			Assert.Equal (Key.CtrlMask | Key.Q, tf.HotKey);
+		}
+
+		[Fact, AutoInitShutdown]
+		public void Draw_Horizontal_Simple_Runes ()
+		{
+			var label = new Label ("Demo Simple Rune");
+			Application.Top.Add (label);
+			Application.Begin (Application.Top);
+
+			Assert.True (label.AutoSize);
+			Assert.Equal (new Rect (0, 0, 16, 1), label.Frame);
+
+			var expected = @"
+Demo Simple Rune
+";
+
+			var pos = GraphViewTests.AssertDriverContentsWithFrameAre (expected, output);
+			Assert.Equal (new Rect (0, 0, 16, 1), pos);
+		}
+
+		[Fact, AutoInitShutdown]
+		public void Draw_Vertical_Simple_Runes ()
+		{
+			var label = new Label ("Demo Simple Rune") {
+				TextDirection = TextDirection.TopBottom_LeftRight
+			};
+			Application.Top.Add (label);
+			Application.Begin (Application.Top);
+
+			Assert.True (label.AutoSize);
+			Assert.Equal (new Rect (0, 0, 1, 16), label.Frame);
+
+			var expected = @"
+D
+e
+m
+o
+
+S
+i
+m
+p
+l
+e
+
+R
+u
+n
+e
+";
+
+			var pos = GraphViewTests.AssertDriverContentsWithFrameAre (expected, output);
+			Assert.Equal (new Rect (0, 0, 1, 16), pos);
+		}
+
+		[Fact, AutoInitShutdown]
+		public void Draw_Horizontal_Wide_Runes ()
+		{
+			var label = new Label ("デモエムポンズ");
+			Application.Top.Add (label);
+			Application.Begin (Application.Top);
+
+			Assert.True (label.AutoSize);
+			Assert.Equal (new Rect (0, 0, 14, 1), label.Frame);
+
+			var expected = @"
+デモエムポンズ
+";
+
+			var pos = GraphViewTests.AssertDriverContentsWithFrameAre (expected, output);
+			Assert.Equal (new Rect (0, 0, 14, 1), pos);
+		}
+
+		[Fact, AutoInitShutdown]
+		public void Draw_Vertical_Wide_Runes ()
+		{
+			var label = new Label ("デモエムポンズ") {
+				TextDirection = TextDirection.TopBottom_LeftRight
+			};
+			Application.Top.Add (label);
+			Application.Begin (Application.Top);
+
+			Assert.True (label.AutoSize);
+			Assert.Equal (new Rect (0, 0, 2, 7), label.Frame);
+
+			var expected = @"
+デ
+モ
+エ
+ム
+ポ
+ン
+ズ
+";
+
+			var pos = GraphViewTests.AssertDriverContentsWithFrameAre (expected, output);
+			Assert.Equal (new Rect (0, 0, 2, 7), pos);
+		}
+
+		[Fact, AutoInitShutdown]
+		public void Draw_Horizontal_Simple_TextAlignments ()
+		{
+			var text = "Hello World";
+			var width = 20;
+			var lblLeft = new Label (text) { Width = width };
+			var lblCenter = new Label (text) { Y = 1, Width = width, TextAlignment = TextAlignment.Centered };
+			var lblRight = new Label (text) { Y = 2, Width = width, TextAlignment = TextAlignment.Right };
+			var lblJust = new Label (text) { Y = 3, Width = width, TextAlignment = TextAlignment.Justified };
+			var frame = new FrameView () { Width = Dim.Fill (), Height = Dim.Fill () };
+
+			frame.Add (lblLeft, lblCenter, lblRight, lblJust);
+			Application.Top.Add (frame);
+			Application.Begin (Application.Top);
+			((FakeDriver)Application.Driver).SetBufferSize (width + 2, 6);
+
+			Assert.False (lblLeft.AutoSize);
+			Assert.False (lblCenter.AutoSize);
+			Assert.False (lblRight.AutoSize);
+			Assert.False (lblJust.AutoSize);
+			Assert.Equal (new Rect (0, 0, width, 1), lblLeft.Frame);
+			Assert.Equal (new Rect (0, 1, width, 1), lblCenter.Frame);
+			Assert.Equal (new Rect (0, 2, width, 1), lblRight.Frame);
+			Assert.Equal (new Rect (0, 3, width, 1), lblJust.Frame);
+			Assert.Equal (new Rect (0, 0, width + 2, 6), frame.Frame);
+
+			var expected = @"
+┌────────────────────┐
+│Hello World         │
+│    Hello World     │
+│         Hello World│
+│Hello          World│
+└────────────────────┘
+";
+
+			var pos = GraphViewTests.AssertDriverContentsWithFrameAre (expected, output);
+			Assert.Equal (new Rect (0, 0, width + 2, 6), pos);
+		}
+
+		[Fact, AutoInitShutdown]
+		public void Draw_Vertical_Simple_TextAlignments ()
+		{
+			var text = "Hello World";
+			var height = 20;
+			var lblLeft = new Label (text, direction: TextDirection.TopBottom_LeftRight) { Height = height };
+			var lblCenter = new Label (text, direction: TextDirection.TopBottom_LeftRight) { X = 2, Height = height, VerticalTextAlignment = VerticalTextAlignment.Middle };
+			var lblRight = new Label (text, direction: TextDirection.TopBottom_LeftRight) { X = 4, Height = height, VerticalTextAlignment = VerticalTextAlignment.Bottom };
+			var lblJust = new Label (text, direction: TextDirection.TopBottom_LeftRight) { X = 6, Height = height, VerticalTextAlignment = VerticalTextAlignment.Justified };
+			var frame = new FrameView () { Width = Dim.Fill (), Height = Dim.Fill () };
+
+			frame.Add (lblLeft, lblCenter, lblRight, lblJust);
+			Application.Top.Add (frame);
+			Application.Begin (Application.Top);
+			((FakeDriver)Application.Driver).SetBufferSize (9, height + 2);
+
+			Assert.False (lblLeft.AutoSize);
+			Assert.False (lblCenter.AutoSize);
+			Assert.False (lblRight.AutoSize);
+			Assert.False (lblJust.AutoSize);
+			Assert.Equal (new Rect (0, 0, 1, height), lblLeft.Frame);
+			Assert.Equal (new Rect (2, 0, 1, height), lblCenter.Frame);
+			Assert.Equal (new Rect (4, 0, 1, height), lblRight.Frame);
+			Assert.Equal (new Rect (6, 0, 1, height), lblJust.Frame);
+			Assert.Equal (new Rect (0, 0, 9, height + 2), frame.Frame);
+
+			var expected = @"
+┌───────┐
+│H     H│
+│e     e│
+│l     l│
+│l     l│
+│o H   o│
+│  e    │
+│W l    │
+│o l    │
+│r o    │
+│l   H  │
+│d W e  │
+│  o l  │
+│  r l  │
+│  l o  │
+│  d    │
+│    W W│
+│    o o│
+│    r r│
+│    l l│
+│    d d│
+└───────┘
+";
+
+			var pos = GraphViewTests.AssertDriverContentsWithFrameAre (expected, output);
+			Assert.Equal (new Rect (0, 0, 9, height + 2), pos);
+		}
+
+		[Fact, AutoInitShutdown]
+		public void Draw_Horizontal_Wide_TextAlignments ()
+		{
+			var text = "こんにちは 世界";
+			var width = 25;
+			var lblLeft = new Label (text) { Width = width };
+			var lblCenter = new Label (text) { Y = 1, Width = width, TextAlignment = TextAlignment.Centered };
+			var lblRight = new Label (text) { Y = 2, Width = width, TextAlignment = TextAlignment.Right };
+			var lblJust = new Label (text) { Y = 3, Width = width, TextAlignment = TextAlignment.Justified };
+			var frame = new FrameView () { Width = Dim.Fill (), Height = Dim.Fill () };
+
+			frame.Add (lblLeft, lblCenter, lblRight, lblJust);
+			Application.Top.Add (frame);
+			Application.Begin (Application.Top);
+			((FakeDriver)Application.Driver).SetBufferSize (width + 2, 6);
+
+			Assert.False (lblLeft.AutoSize);
+			Assert.False (lblCenter.AutoSize);
+			Assert.False (lblRight.AutoSize);
+			Assert.False (lblJust.AutoSize);
+			Assert.Equal (new Rect (0, 0, width, 1), lblLeft.Frame);
+			Assert.Equal (new Rect (0, 1, width, 1), lblCenter.Frame);
+			Assert.Equal (new Rect (0, 2, width, 1), lblRight.Frame);
+			Assert.Equal (new Rect (0, 3, width, 1), lblJust.Frame);
+			Assert.Equal (new Rect (0, 0, width + 2, 6), frame.Frame);
+
+			var expected = @"
+┌─────────────────────────┐
+│こんにちは 世界          │
+│     こんにちは 世界     │
+│          こんにちは 世界│
+│こんにちは           世界│
+└─────────────────────────┘
+";
+
+			var pos = GraphViewTests.AssertDriverContentsWithFrameAre (expected, output);
+			Assert.Equal (new Rect (0, 0, width + 2, 6), pos);
+		}
+
+		[Fact, AutoInitShutdown]
+		public void Draw_Vertical_Wide_TextAlignments ()
+		{
+			var text = "こんにちは 世界";
+			var height = 23;
+			var lblLeft = new Label (text) { Width = 2, Height = height, TextDirection = TextDirection.TopBottom_LeftRight };
+			var lblCenter = new Label (text) { X = 3, Width = 2, Height = height, TextDirection = TextDirection.TopBottom_LeftRight, VerticalTextAlignment = VerticalTextAlignment.Middle };
+			var lblRight = new Label (text) { X = 6, Width = 2, Height = height, TextDirection = TextDirection.TopBottom_LeftRight, VerticalTextAlignment = VerticalTextAlignment.Bottom };
+			var lblJust = new Label (text) { X = 9, Width = 2, Height = height, TextDirection = TextDirection.TopBottom_LeftRight, VerticalTextAlignment = VerticalTextAlignment.Justified };
+			var frame = new FrameView () { Width = Dim.Fill (), Height = Dim.Fill () };
+
+			frame.Add (lblLeft, lblCenter, lblRight, lblJust);
+			Application.Top.Add (frame);
+			Application.Begin (Application.Top);
+			((FakeDriver)Application.Driver).SetBufferSize (13, height + 2);
+
+			Assert.False (lblLeft.AutoSize);
+			Assert.False (lblCenter.AutoSize);
+			Assert.False (lblRight.AutoSize);
+			Assert.False (lblJust.AutoSize);
+			Assert.Equal (new Rect (0, 0, 2, height), lblLeft.Frame);
+			Assert.Equal (new Rect (3, 0, 2, height), lblCenter.Frame);
+			Assert.Equal (new Rect (6, 0, 2, height), lblRight.Frame);
+			Assert.Equal (new Rect (9, 0, 2, height), lblJust.Frame);
+			Assert.Equal (new Rect (0, 0, 13, height + 2), frame.Frame);
+
+			var expected = @"
+┌───────────┐
+│こ       こ│
+│ん       ん│
+│に       に│
+│ち       ち│
+│は       は│
+│           │
+│世         │
+│界 こ      │
+│   ん      │
+│   に      │
+│   ち      │
+│   は      │
+│           │
+│   世      │
+│   界      │
+│      こ   │
+│      ん   │
+│      に   │
+│      ち   │
+│      は   │
+│           │
+│      世 世│
+│      界 界│
+└───────────┘
+";
+
+			var pos = GraphViewTests.AssertDriverContentsWithFrameAre (expected, output);
+			Assert.Equal (new Rect (0, 0, 13, height + 2), pos);
+		}
+
+		[Fact]
+		public void GetTextWidth_Simple_And_Wide_Runes ()
+		{
+			ustring text = "Hello World";
+			Assert.Equal (11, TextFormatter.GetTextWidth (text));
+			text = "こんにちは世界";
+			Assert.Equal (14, TextFormatter.GetTextWidth (text));
+		}
+
+		[Fact]
+		public void GetSumMaxCharWidth_Simple_And_Wide_Runes ()
+		{
+			ustring text = "Hello World";
+			Assert.Equal (11, TextFormatter.GetSumMaxCharWidth (text));
+			Assert.Equal (1, TextFormatter.GetSumMaxCharWidth (text, 6, 1));
+			text = "こんにちは 世界";
+			Assert.Equal (15, TextFormatter.GetSumMaxCharWidth (text));
+			Assert.Equal (2, TextFormatter.GetSumMaxCharWidth (text, 6, 1));
+		}
+
+		[Fact]
+		public void GetSumMaxCharWidth_List_Simple_And_Wide_Runes ()
+		{
+			List<ustring> text =new List<ustring>() { "Hello", "World" };
+			Assert.Equal (2, TextFormatter.GetSumMaxCharWidth (text));
+			Assert.Equal (1, TextFormatter.GetSumMaxCharWidth (text, 1, 1));
+			text = new List<ustring> () { "こんにちは", "世界" };
+			Assert.Equal (4, TextFormatter.GetSumMaxCharWidth (text));
+			Assert.Equal (2, TextFormatter.GetSumMaxCharWidth (text, 1, 1));
+		}
+
+		[Fact]
+		public void GetMaxLengthForWidth_Simple_And_Wide_Runes ()
+		{
+			ustring text = "Hello World";
+			Assert.Equal (6, TextFormatter.GetMaxLengthForWidth (text, 6));
+			text = "こんにちは 世界";
+			Assert.Equal (3, TextFormatter.GetMaxLengthForWidth (text, 6));
+		}
+
+		[Fact]
+		public void GetMaxLengthForWidth_List_Simple_And_Wide_Runes ()
+		{
+			var runes = ustring.Make ("Hello World").ToRuneList ();
+			Assert.Equal (6, TextFormatter.GetMaxLengthForWidth (runes, 6));
+			runes = ustring.Make ("こんにちは 世界").ToRuneList ();
+			Assert.Equal (3, TextFormatter.GetMaxLengthForWidth (runes, 6));
+		}
+
+		[Fact]
+		public void Format_Truncate_Simple_And_Wide_Runes ()
+		{
+			var text = "Truncate";
+			var list = TextFormatter.Format (text, 3, false, false);
+			Assert.Equal ("Tru", list [^1].ToString ());
 
+			text = "デモエムポンズ";
+			list = TextFormatter.Format (text, 3, false, false);
+			Assert.Equal ("デ", list [^1].ToString ());
 		}
 	}
 }

+ 54 - 0
UnitTests/ViewTests.cs

@@ -1338,6 +1338,60 @@ namespace Terminal.Gui.Core {
 			Assert.Equal ("{X=0,Y=0,Width=28,Height=2}", label.Bounds.ToString ());
 		}
 
+		[Fact, AutoInitShutdown]
+		public void AutoSize_True_Setting_With_Height_Sets_AutoSize_False_Horizontal ()
+		{
+			var label = new Label ("Hello") { Width = 10, Height = 2 };
+			var viewX = new View ("X") { X = Pos.Right (label) };
+			var viewY = new View ("Y") { Y = Pos.Bottom (label) };
+
+			Application.Top.Add (label, viewX, viewY);
+			Application.Begin (Application.Top);
+
+			Assert.False (label.AutoSize);
+			Assert.Equal (new Rect (0, 0, 10, 2), label.Frame);
+
+			var expected = @"
+Hello     X
+
+Y
+";
+
+			var pos = GraphViewTests.AssertDriverContentsWithFrameAre (expected, output);
+			Assert.Equal (new Rect (0, 0, 11, 3), pos);
+		}
+
+		[Fact, AutoInitShutdown]
+		public void AutoSize_True_Setting_With_Height_Sets_AutoSize_False_Vertical ()
+		{
+			var label = new Label ("Hello") { Width = 2, Height = 10, TextDirection = TextDirection.TopBottom_LeftRight };
+			var viewX = new View ("X") { X = Pos.Right (label) };
+			var viewY = new View ("Y") { Y = Pos.Bottom (label) };
+
+			Application.Top.Add (label, viewX, viewY);
+			Application.Begin (Application.Top);
+
+			Assert.False (label.AutoSize);
+			Assert.Equal (new Rect (0, 0, 2, 10), label.Frame);
+
+			var expected = @"
+H X
+e
+l
+l
+o
+
+
+
+
+
+Y
+";
+
+			var pos = GraphViewTests.AssertDriverContentsWithFrameAre (expected, output);
+			Assert.Equal (new Rect (0, 0, 3, 11), pos);
+		}
+
 		[Theory]
 		[InlineData (1)]
 		[InlineData (2)]