2
0
Эх сурвалжийг харах

Code cleanup on just touched files

Tig Kindel 1 жил өмнө
parent
commit
2c040fff27

Файлын зөрүү хэтэрхий том тул дарагдсан байна
+ 1733 - 24
.editorconfig


+ 2054 - 1663
Terminal.Gui/Text/TextFormatter.cs

@@ -1,1664 +1,2055 @@
-using System;
-using System.Collections.Generic;
-using System.Linq;
-using System.Text;
-
-namespace Terminal.Gui {
-	/// <summary>
-	/// Text alignment enumeration, controls how text is displayed.
-	/// </summary>
-	public enum TextAlignment {
-		/// <summary>
-		/// The text will be left-aligned.
-		/// </summary>
-		Left,
-		/// <summary>
-		/// The text will be right-aligned.
-		/// </summary>
-		Right,
-		/// <summary>
-		/// The text will be centered horizontally.
-		/// </summary>
-		Centered,
-		/// <summary>
-		/// The text will be justified (spaces will be added to existing spaces such that
-		/// the text fills the container horizontally).
-		/// </summary>
-		Justified
-	}
-
-	/// <summary>
-	/// Vertical text alignment enumeration, controls how text is displayed.
-	/// </summary>
-	public enum VerticalTextAlignment {
-		/// <summary>
-		/// The text will be top-aligned.
-		/// </summary>
-		Top,
-		/// <summary>
-		/// The text will be bottom-aligned.
-		/// </summary>
-		Bottom,
-		/// <summary>
-		/// The text will centered vertically.
-		/// </summary>
-		Middle,
-		/// <summary>
-		/// The text will be justified (spaces will be added to existing spaces such that
-		/// the text fills the container vertically).
-		/// </summary>
-		Justified
-	}
-
-	/// <summary>
-	/// Text direction enumeration, controls how text is displayed.
-	/// </summary>
-	/// <remarks>
-	/// <para>TextDirection  [H] = Horizontal  [V] = Vertical</para>
-	/// <table>
-	///   <tr>
-	///     <th>TextDirection</th>
-	///     <th>Description</th>
-	///   </tr>
-	///   <tr>
-	///     <td>LeftRight_TopBottom [H]</td>
-	///     <td>Normal</td>
-	///   </tr>
-	///   <tr>
-	///     <td>TopBottom_LeftRight [V]</td>
-	///     <td>Normal</td>
-	///   </tr>
-	///   <tr>
-	///     <td>RightLeft_TopBottom [H]</td>
-	///     <td>Invert Text</td>
-	///   </tr>
-	///   <tr>
-	///     <td>TopBottom_RightLeft [V]</td>
-	///     <td>Invert Lines</td>
-	///   </tr>
-	///   <tr>
-	///     <td>LeftRight_BottomTop [H]</td>
-	///     <td>Invert Lines</td>
-	///   </tr>
-	///   <tr>
-	///     <td>BottomTop_LeftRight [V]</td>
-	///     <td>Invert Text</td>
-	///   </tr>
-	///   <tr>
-	///     <td>RightLeft_BottomTop [H]</td>
-	///     <td>Invert Text + Invert Lines</td>
-	///   </tr>
-	///   <tr>
-	///     <td>BottomTop_RightLeft [V]</td>
-	///     <td>Invert Text + Invert Lines</td>
-	///   </tr>
-	/// </table>
-	/// </remarks>
-	public enum TextDirection {
-		/// <summary>
-		/// Normal horizontal direction.
-		/// <code>HELLO<br/>WORLD</code>
-		/// </summary>
-		LeftRight_TopBottom,
-		/// <summary>
-		/// Normal vertical direction.
-		/// <code>H W<br/>E O<br/>L R<br/>L L<br/>O D</code>
-		/// </summary>
-		TopBottom_LeftRight,
-		/// <summary>
-		/// This is a horizontal direction. <br/> RTL
-		/// <code>OLLEH<br/>DLROW</code>
-		/// </summary>
-		RightLeft_TopBottom,
-		/// <summary>
-		/// This is a vertical direction.
-		/// <code>W H<br/>O E<br/>R L<br/>L L<br/>D O</code>
-		/// </summary>
-		TopBottom_RightLeft,
-		/// <summary>
-		/// This is a horizontal direction.
-		/// <code>WORLD<br/>HELLO</code>
-		/// </summary>
-		LeftRight_BottomTop,
-		/// <summary>
-		/// This is a vertical direction.
-		/// <code>O D<br/>L L<br/>L R<br/>E O<br/>H W</code>
-		/// </summary>
-		BottomTop_LeftRight,
-		/// <summary>
-		/// This is a horizontal direction.
-		/// <code>DLROW<br/>OLLEH</code>
-		/// </summary>
-		RightLeft_BottomTop,
-		/// <summary>
-		/// This is a vertical direction.
-		/// <code>D O<br/>L L<br/>R L<br/>O E<br/>W H</code>
-		/// </summary>
-		BottomTop_RightLeft
-	}
-
-	/// <summary>
-	/// Provides text formatting. Supports <see cref="View.HotKey"/>s, horizontal alignment, vertical alignment, multiple lines, and word-based line wrap.
-	/// </summary>
-	public class TextFormatter {
-
-		#region Static Members
-
-		static string StripCRLF (string str, bool keepNewLine = false)
-		{
-			var runes = str.ToRuneList ();
-			for (int i = 0; i < runes.Count; i++) {
-				switch ((char)runes [i].Value) {
-				case '\n':
-					if (!keepNewLine) {
-						runes.RemoveAt (i);
-					}
-					break;
-
-				case '\r':
-					if ((i + 1) < runes.Count && runes [i + 1].Value == '\n') {
-						runes.RemoveAt (i);
-						if (!keepNewLine) {
-							runes.RemoveAt (i);
-						}
-						i++;
-					} else {
-						if (!keepNewLine) {
-							runes.RemoveAt (i);
-						}
-					}
-					break;
-				}
-			}
-			return StringExtensions.ToString (runes);
-		}
-		static string ReplaceCRLFWithSpace (string str)
-		{
-			var runes = str.ToRuneList ();
-			for (int i = 0; i < runes.Count; i++) {
-				switch (runes [i].Value) {
-				case '\n':
-					runes [i] = (Rune)' ';
-					break;
-
-				case '\r':
-					if ((i + 1) < runes.Count && runes [i + 1].Value == '\n') {
-						runes [i] = (Rune)' ';
-						runes.RemoveAt (i + 1);
-						i++;
-					} else {
-						runes [i] = (Rune)' ';
-					}
-					break;
-				}
-			}
-			return StringExtensions.ToString (runes);
-		}
-
-		static string ReplaceTABWithSpaces (string str, int tabWidth)
-		{
-			if (tabWidth == 0) {
-				return str.Replace ("\t", "");
-			}
-
-			return str.Replace ("\t", new string (' ', tabWidth));
-		}
-
-		/// <summary>
-		/// Splits all newlines in the <paramref name="text"/> into a list
-		/// and supports both CRLF and LF, preserving the ending newline.
-		/// </summary>
-		/// <param name="text">The text.</param>
-		/// <returns>A list of text without the newline characters.</returns>
-		public static List<string> SplitNewLine (string text)
-		{
-			var runes = text.ToRuneList ();
-			var lines = new List<string> ();
-			var start = 0;
-			var end = 0;
-
-			for (int i = 0; i < runes.Count; i++) {
-				end = i;
-				switch (runes [i].Value) {
-				case '\n':
-					lines.Add (StringExtensions.ToString (runes.GetRange (start, end - start)));
-					i++;
-					start = i;
-					break;
-
-				case '\r':
-					if ((i + 1) < runes.Count && runes [i + 1].Value == '\n') {
-						lines.Add (StringExtensions.ToString (runes.GetRange (start, end - start)));
-						i += 2;
-						start = i;
-					} else {
-						lines.Add (StringExtensions.ToString (runes.GetRange (start, end - start)));
-						i++;
-						start = i;
-					}
-					break;
-				}
-			}
-			if (runes.Count > 0 && lines.Count == 0) {
-				lines.Add (StringExtensions.ToString (runes));
-			} else if (runes.Count > 0 && start < runes.Count) {
-				lines.Add (StringExtensions.ToString (runes.GetRange (start, runes.Count - start)));
-			} else {
-				lines.Add ("");
-			}
-			return lines;
-		}
-
-		/// <summary>
-		/// Adds trailing whitespace or truncates <paramref name="text"/>
-		/// so that it fits exactly <paramref name="width"/> console units.
-		/// Note that some unicode characters take 2+ columns
-		/// </summary>
-		/// <param name="text"></param>
-		/// <param name="width"></param>
-		/// <returns></returns>
-		public static string ClipOrPad (string text, int width)
-		{
-			if (string.IsNullOrEmpty (text))
-				return text;
-
-			// if value is not wide enough
-			if (text.EnumerateRunes ().Sum (c => c.GetColumns ()) < width) {
-
-				// pad it out with spaces to the given alignment
-				int toPad = width - (text.EnumerateRunes ().Sum (c => c.GetColumns ()));
-
-				return text + new string (' ', toPad);
-			}
-
-			// value is too wide
-			return new string (text.TakeWhile (c => (width -= ((Rune)c).GetColumns ()) >= 0).ToArray ());
-		}
-
-		/// <summary>
-		/// Formats the provided text to fit within the width provided using word wrapping.
-		/// </summary>
-		/// <param name="text">The text to word wrap</param>
-		/// <param name="width">The number of columns to constrain the text to</param>
-		/// <param name="preserveTrailingSpaces">If <see langword="true"/> trailing spaces at the end of wrapped lines will be preserved.
-		///  If <see langword="false"/>, trailing spaces at the end of wrapped lines will be trimmed.</param>
-		/// <param name="tabWidth">The number of columns used for a tab.</param>
-		/// <param name="textDirection">The text direction.</param>
-		/// <returns>A list of word wrapped lines.</returns>
-		/// <remarks>
-		/// <para>
-		/// This method does not do any justification.
-		/// </para>
-		/// <para>
-		/// This method strips Newline ('\n' and '\r\n') sequences before processing.
-		/// </para>
-		/// <para>
-		/// If <paramref name="preserveTrailingSpaces"/> is <see langword="false"/> at most one space will be preserved at the end of the last line.
-		/// </para>
-		/// </remarks>
-		public static List<string> WordWrapText (string text, int width, bool preserveTrailingSpaces = false, int tabWidth = 0,
-			TextDirection textDirection = TextDirection.LeftRight_TopBottom)
-		{
-			if (width < 0) {
-				throw new ArgumentOutOfRangeException ("Width cannot be negative.");
-			}
-
-			int start = 0, end;
-			var lines = new List<string> ();
-
-			if (string.IsNullOrEmpty (text)) {
-				return lines;
-			}
-
-			var runes = StripCRLF (text).ToRuneList ();
-			if (preserveTrailingSpaces) {
-				while ((end = start) < runes.Count) {
-					end = GetNextWhiteSpace (start, width, out bool incomplete);
-					if (end == 0 && incomplete) {
-						start = text.GetRuneCount ();
-						break;
-					}
-					lines.Add (StringExtensions.ToString (runes.GetRange (start, end - start)));
-					start = end;
-					if (incomplete) {
-						start = text.GetRuneCount ();
-						break;
-					}
-				}
-			} else {
-				if (IsHorizontalDirection (textDirection)) {
-					//if (GetLengthThatFits (runes.GetRange (start, runes.Count - start), width) > 0) {
-					//	// while there's still runes left and end is not past end...
-					//	while (start < runes.Count &&
-					//		(end = start + Math.Max (GetLengthThatFits (runes.GetRange (start, runes.Count - start), width) - 1, 0)) < runes.Count) {
-					//		// end now points to start + LengthThatFits
-					//		// Walk back over trailing spaces
-					//		while (runes [end] == ' ' && end > start) {
-					//			end--;
-					//		}
-					//		// end now points to start + LengthThatFits - any trailing spaces; start saving new line
-					//		var line = runes.GetRange (start, end - start + 1);
-
-					//		if (end == start && width > 1) {
-					//			// it was all trailing spaces; now walk forward to next non-space
-					//			do {
-					//				start++;
-					//			} while (start < runes.Count && runes [start] == ' ');
-
-					//			// start now points to first non-space we haven't seen yet or we're done
-					//			if (start < runes.Count) {
-					//				// we're not done. we have remaining = width - line.Count columns left; 
-					//				var remaining = width - line.Count;
-					//				if (remaining > 1) {
-					//					// add a space for all the spaces we walked over 
-					//					line.Add (' ');
-					//				}
-					//				var count = GetLengthThatFits (runes.GetRange (start, runes.Count - start), width - line.Count);
-
-					//				// [start..count] now has rest of line
-					//				line.AddRange (runes.GetRange (start, count));
-					//				start += count;
-					//			}
-					//		} else {
-					//			start += line.Count;
-					//		}
-
-					//		//// if the previous line was just a ' ' and the new line is just a ' '
-					//		//// don't add new line
-					//		//if (line [0] == ' ' && (lines.Count > 0 && lines [lines.Count - 1] [0] == ' ')) {
-					//		//} else {
-					//		//}
-					//		lines.Add (string.Make (line));
-
-					//		// move forward to next non-space
-					//		while (width > 1 && start < runes.Count && runes [start] == ' ') {
-					//			start++;
-					//		}
-					//	}
-					//}
-
-					while ((end = start + GetLengthThatFits (runes.GetRange (start, runes.Count - start), width, tabWidth)) < runes.Count) {
-						while (runes [end].Value != ' ' && end > start)
-							end--;
-						if (end == start)
-							end = start + GetLengthThatFits (runes.GetRange (end, runes.Count - end), width, tabWidth);
-						var str = StringExtensions.ToString (runes.GetRange (start, end - start));
-						if (end > start && GetRuneWidth (str, tabWidth) <= width) {
-							lines.Add (str);
-							start = end;
-							if (runes [end].Value == ' ') {
-								start++;
-							}
-						} else {
-							end++;
-							start = end;
-						}
-					}
-
-				} else {
-					while ((end = start + width) < runes.Count) {
-						while (runes [end].Value != ' ' && end > start) {
-							end--;
-						}
-						if (end == start) {
-							end = start + width;
-						}
-						var zeroLength = 0;
-						for (int i = end; i < runes.Count - start; i++) {
-							var r = runes [i];
-							if (r.GetColumns () == 0) {
-								zeroLength++;
-							} else {
-								break;
-							}
-						}
-						lines.Add (StringExtensions.ToString (runes.GetRange (start, end - start + zeroLength)));
-						end += zeroLength;
-						start = end;
-						if (runes [end].Value == ' ') {
-							start++;
-						}
-					}
-				}
-			}
-
-			int GetNextWhiteSpace (int from, int cWidth, out bool incomplete, int cLength = 0)
-			{
-				var lastFrom = from;
-				var to = from;
-				var length = cLength;
-				incomplete = false;
-
-				while (length < cWidth && to < runes.Count) {
-					var rune = runes [to];
-					if (IsHorizontalDirection (textDirection)) {
-						length += rune.GetColumns ();
-					} else {
-						length++;
-					}
-					if (length > cWidth) {
-						if (to >= runes.Count || (length > 1 && cWidth <= 1)) {
-							incomplete = true;
-						}
-						return to;
-					}
-					if (rune.Value == ' ') {
-						if (length == cWidth) {
-							return to + 1;
-						} else if (length > cWidth) {
-							return to;
-						} else {
-							return GetNextWhiteSpace (to + 1, cWidth, out incomplete, length);
-						}
-					} else if (rune.Value == '\t') {
-						length += tabWidth + 1;
-						if (length == tabWidth && tabWidth > cWidth) {
-							return to + 1;
-						} else if (length > cWidth && tabWidth > cWidth) {
-							return to;
-						} else {
-							return GetNextWhiteSpace (to + 1, cWidth, out incomplete, length);
-						}
-					}
-					to++;
-				}
-				if (cLength > 0 && to < runes.Count && runes [to].Value != ' ' && runes [to].Value != '\t') {
-					return from;
-				} else if (cLength > 0 && to < runes.Count && (runes [to].Value == ' ' || runes [to].Value == '\t')) {
-					return lastFrom;
-				} else {
-					return to;
-				}
-			}
-
-			if (start < text.GetRuneCount ()) {
-				var str = ReplaceTABWithSpaces (StringExtensions.ToString (runes.GetRange (start, runes.Count - start)), tabWidth);
-				if (IsVerticalDirection (textDirection) || preserveTrailingSpaces || (!preserveTrailingSpaces && str.GetColumns () <= width)) {
-					lines.Add (str);
-				}
-			}
-
-			return lines;
-		}
-
-		/// <summary>
-		/// Justifies text within a specified width. 
-		/// </summary>
-		/// <param name="text">The text to justify.</param>
-		/// <param name="width">The number of columns to clip the text to. Text longer than <paramref name="width"/> will be clipped.</param>
-		/// <param name="talign">Alignment.</param>
-		/// <param name="textDirection">The text direction.</param>
-		/// <param name="tabWidth">The number of columns used for a tab.</param>
-		/// <returns>Justified and clipped text.</returns>
-		public static string ClipAndJustify (string text, int width, TextAlignment talign, TextDirection textDirection = TextDirection.LeftRight_TopBottom, int tabWidth = 0)
-		{
-			return ClipAndJustify (text, width, talign == TextAlignment.Justified, textDirection, tabWidth);
-		}
-
-		/// <summary>
-		/// Justifies text within a specified width. 
-		/// </summary>
-		/// <param name="text">The text to justify.</param>
-		/// <param name="width">The number of columns to clip the text to. Text longer than <paramref name="width"/> will be clipped.</param>
-		/// <param name="justify">Justify.</param>
-		/// <param name="textDirection">The text direction.</param>
-		/// <param name="tabWidth">The number of columns used for a tab.</param>
-		/// <returns>Justified and clipped text.</returns>
-		public static string ClipAndJustify (string text, int width, bool justify, TextDirection textDirection = TextDirection.LeftRight_TopBottom, int tabWidth = 0)
-		{
-			if (width < 0) {
-				throw new ArgumentOutOfRangeException ("Width cannot be negative.");
-			}
-			if (string.IsNullOrEmpty (text)) {
-				return text;
-			}
-
-			text = ReplaceTABWithSpaces (text, tabWidth);
-			var runes = text.ToRuneList ();
-			int slen = runes.Count;
-			if (slen > width) {
-				if (IsHorizontalDirection (textDirection)) {
-					return StringExtensions.ToString (runes.GetRange (0, GetLengthThatFits (text, width, tabWidth)));
-				} else {
-					var zeroLength = runes.Sum (r => r.GetColumns () == 0 ? 1 : 0);
-					return StringExtensions.ToString (runes.GetRange (0, width + zeroLength));
-				}
-			} else {
-				if (justify) {
-					return Justify (text, width, ' ', textDirection, tabWidth);
-				} else if (IsHorizontalDirection (textDirection) && GetRuneWidth (text, tabWidth) > width) {
-					return StringExtensions.ToString (runes.GetRange (0, GetLengthThatFits (text, width, tabWidth)));
-				}
-				return text;
-			}
-		}
-
-		/// <summary>
-		/// Justifies the text to fill the width provided. Space will be added between words (demarked by spaces and tabs) to
-		/// make the text just fit <c>width</c>. Spaces will not be added to the ends.
-		/// </summary>
-		/// <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>
-		/// <param name="tabWidth">The number of columns used for a tab.</param>
-		/// <returns>The justified text.</returns>
-		public static string Justify (string text, int width, char spaceChar = ' ', TextDirection textDirection = TextDirection.LeftRight_TopBottom, int tabWidth = 0)
-		{
-			if (width < 0) {
-				throw new ArgumentOutOfRangeException ("Width cannot be negative.");
-			}
-			if (string.IsNullOrEmpty (text)) {
-				return text;
-			}
-
-			text = ReplaceTABWithSpaces (text, tabWidth);
-			var words = text.Split (' ');
-			int textCount;
-			if (IsHorizontalDirection (textDirection)) {
-				textCount = words.Sum (arg => GetRuneWidth (arg, tabWidth));
-			} else {
-				textCount = words.Sum (arg => arg.GetRuneCount ());
-			}
-			var spaces = words.Length > 1 ? (width - textCount) / (words.Length - 1) : 0;
-			var extras = words.Length > 1 ? (width - textCount) % (words.Length - 1) : 0;
-
-			var s = new System.Text.StringBuilder ();
-			for (int w = 0; w < words.Length; w++) {
-				var x = words [w];
-				s.Append (x);
-				if (w + 1 < words.Length)
-					for (int i = 0; i < spaces; i++)
-						s.Append (spaceChar);
-				if (extras > 0) {
-					for (int i = 0; i < 1; i++)
-						s.Append (spaceChar);
-					extras--;
-				}
-				if (w + 1 == words.Length - 1) {
-					for (int i = 0; i < extras; i++)
-						s.Append (spaceChar);
-				}
-			}
-			return s.ToString ();
-		}
-
-		//static char [] whitespace = new char [] { ' ', '\t' };
-
-		/// <summary>
-		/// Reformats text into lines, applying text alignment and optionally wrapping text to new lines on word boundaries.
-		/// </summary>
-		/// <param name="text"></param>
-		/// <param name="width">The number of columns to constrain the text to for word wrapping and clipping.</param>
-		/// <param name="talign">Specifies how the text will be aligned horizontally.</param>
-		/// <param name="wordWrap">If <see langword="true"/>, the text will be wrapped to new lines no longer than <paramref name="width"/>.	
-		/// If <see langword="false"/>, forces text to fit a single line. Line breaks are converted to spaces. The text will be clipped to <paramref name="width"/>.</param>
-		/// <param name="preserveTrailingSpaces">If <see langword="true"/> trailing spaces at the end of wrapped lines will be preserved.
-		///  If <see langword="false"/>, trailing spaces at the end of wrapped lines will be trimmed.</param>
-		/// <param name="tabWidth">The number of columns used for a tab.</param>
-		/// <param name="textDirection">The text direction.</param>
-		/// <param name="multiLine">If <see langword="true"/> new lines are allowed.</param>
-		/// <returns>A list of word wrapped lines.</returns>
-		/// <remarks>
-		/// <para>
-		/// An empty <paramref name="text"/> string will result in one empty line.
-		/// </para>
-		/// <para>
-		/// If <paramref name="width"/> is 0, a single, empty line will be returned.
-		/// </para>
-		/// <para>
-		/// If <paramref name="width"/> is int.MaxValue, the text will be formatted to the maximum width possible. 
-		/// </para>
-		/// </remarks>
-		public static List<string> Format (string text, int width, TextAlignment talign, bool wordWrap, bool preserveTrailingSpaces = false, int tabWidth = 0, TextDirection textDirection = TextDirection.LeftRight_TopBottom, bool multiLine = false)
-		{
-			return Format (text, width, talign == TextAlignment.Justified, wordWrap, preserveTrailingSpaces, tabWidth, textDirection, multiLine);
-		}
-
-		/// <summary>
-		/// Reformats text into lines, applying text alignment and optionally wrapping text to new lines on word boundaries.
-		/// </summary>
-		/// <param name="text"></param>
-		/// <param name="width">The number of columns to constrain the text to for word wrapping and clipping.</param>
-		/// <param name="justify">Specifies whether the text should be justified.</param>
-		/// <param name="wordWrap">If <see langword="true"/>, the text will be wrapped to new lines no longer than <paramref name="width"/>.	
-		/// If <see langword="false"/>, forces text to fit a single line. Line breaks are converted to spaces. The text will be clipped to <paramref name="width"/>.</param>
-		/// <param name="preserveTrailingSpaces">If <see langword="true"/> trailing spaces at the end of wrapped lines will be preserved.
-		///  If <see langword="false"/>, trailing spaces at the end of wrapped lines will be trimmed.</param>
-		/// <param name="tabWidth">The number of columns used for a tab.</param>
-		/// <param name="textDirection">The text direction.</param>
-		/// <param name="multiLine">If <see langword="true"/> new lines are allowed.</param>
-		/// <returns>A list of word wrapped lines.</returns>
-		/// <remarks>
-		/// <para>
-		/// An empty <paramref name="text"/> string will result in one empty line.
-		/// </para>
-		/// <para>
-		/// If <paramref name="width"/> is 0, a single, empty line will be returned.
-		/// </para>
-		/// <para>
-		/// If <paramref name="width"/> is int.MaxValue, the text will be formatted to the maximum width possible. 
-		/// </para>
-		/// </remarks>
-		public static List<string> Format (string text, int width, bool justify, bool wordWrap,
-			bool preserveTrailingSpaces = false, int tabWidth = 0, TextDirection textDirection = TextDirection.LeftRight_TopBottom, bool multiLine = false)
-		{
-			if (width < 0) {
-				throw new ArgumentOutOfRangeException ("width cannot be negative");
-			}
-			List<string> lineResult = new List<string> ();
-
-			if (string.IsNullOrEmpty (text) || width == 0) {
-				lineResult.Add (string.Empty);
-				return lineResult;
-			}
-
-			if (!wordWrap) {
-				text = ReplaceTABWithSpaces (text, tabWidth);
-				if (multiLine) {
-					string [] lines = null;
-					if (text.Contains ("\r\n")) {
-						lines = text.Split ("\r\n");
-					} else if (text.Contains ('\n')) {
-						lines = text.Split ('\n');
-					}
-					if (lines == null) {
-						lines = new [] { text };
-					}
-					foreach (var line in lines) {
-						lineResult.Add (ClipAndJustify (line, width, justify, textDirection, tabWidth));
-					}
-					return lineResult;
-				} else {
-					text = ReplaceCRLFWithSpace (text);
-					lineResult.Add (ClipAndJustify (text, width, justify, textDirection, tabWidth));
-					return lineResult;
-				}
-			}
-
-			var runes = StripCRLF (text, true).ToRuneList ();
-			int runeCount = runes.Count;
-			int lp = 0;
-			for (int i = 0; i < runeCount; i++) {
-				Rune c = runes [i];
-				if (c.Value == '\n') {
-					var wrappedLines = WordWrapText (StringExtensions.ToString (runes.GetRange (lp, i - lp)), width, preserveTrailingSpaces, tabWidth, textDirection);
-					foreach (var line in wrappedLines) {
-						lineResult.Add (ClipAndJustify (line, width, justify, textDirection, tabWidth));
-					}
-					if (wrappedLines.Count == 0) {
-						lineResult.Add (string.Empty);
-					}
-					lp = i + 1;
-				}
-			}
-			foreach (var line in WordWrapText (StringExtensions.ToString (runes.GetRange (lp, runeCount - lp)), width, preserveTrailingSpaces, tabWidth, textDirection)) {
-				lineResult.Add (ClipAndJustify (line, width, justify, textDirection, tabWidth));
-			}
-
-			return lineResult;
-		}
-
-		/// <summary>
-		/// Computes the number of lines needed to render the specified text given the width.
-		/// </summary>
-		/// <returns>Number of lines.</returns>
-		/// <param name="text">Text, may contain newlines.</param>
-		/// <param name="width">The minimum width for the text.</param>
-		public static int MaxLines (string text, int width)
-		{
-			var result = TextFormatter.Format (text, width, false, true);
-			return result.Count;
-		}
-
-		/// <summary>
-		/// Computes the maximum width needed to render the text (single line or multiple lines, word wrapped) given 
-		/// a number of columns to constrain the text to.
-		/// </summary>
-		/// <returns>Width of the longest line after formatting the text constrained by <paramref name="maxColumns"/>.</returns>
-		/// <param name="text">Text, may contain newlines.</param>
-		/// <param name="maxColumns">The number of columns to constrain the text to for formatting.</param>
-		/// <param name="tabWidth">The number of columns used for a tab.</param>
-		public static int MaxWidth (string text, int maxColumns, int tabWidth = 0)
-		{
-			var result = TextFormatter.Format (text: text, width: maxColumns, justify: false, wordWrap: true);
-			var max = 0;
-			result.ForEach (s => {
-				var m = 0;
-				s.ToRuneList ().ForEach (r => m += GetRuneWidth (r, tabWidth));
-				if (m > max) {
-					max = m;
-				}
-			});
-			return max;
-		}
-
-		/// <summary>
-		/// Returns the width of the widest line in the text, accounting for wide-glyphs (uses <see cref="StringExtensions.GetColumns"/>).
-		/// <paramref name="text"/> if it contains newlines.
-		/// </summary>
-		/// <param name="text">Text, may contain newlines.</param>
-		/// <param name="tabWidth">The number of columns used for a tab.</param>
-		/// <returns>The length of the longest line.</returns>
-		public static int MaxWidthLine (string text, int tabWidth = 0)
-		{
-			var result = TextFormatter.SplitNewLine (text);
-			return result.Max (x => GetRuneWidth (x, tabWidth));
-		}
-
-		/// <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>
-		/// <param name="tabWidth">The number of columns used for a tab.</param>
-		/// <returns>The maximum characters width.</returns>
-		public static int GetSumMaxCharWidth (List<string> lines, int startIndex = -1, int length = -1, int tabWidth = 0)
-		{
-			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.EnumerateRunes ().Max (r => GetRuneWidth (r, tabWidth));
-			}
-			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>
-		/// <param name="tabWidth">The number of columns used for a tab.</param>
-		/// <returns>The maximum characters width.</returns>
-		public static int GetSumMaxCharWidth (string text, int startIndex = -1, int length = -1, int tabWidth = 0)
-		{
-			var max = 0;
-			var runes = text.ToRunes ();
-			for (int i = (startIndex == -1 ? 0 : startIndex); i < (length == -1 ? runes.Length : startIndex + length); i++) {
-				max += GetRuneWidth (runes [i], tabWidth);
-			}
-			return max;
-		}
-
-		/// <summary>
-		/// Gets the number of the Runes in a <see cref="string"/> that will fit in <paramref name="columns"/>.
-		/// </summary>
-		/// <param name="text">The text.</param>
-		/// <param name="columns">The width.</param>
-		/// <param name="tabWidth">The number of columns used for a tab.</param>
-		/// <returns>The index of the text that fit the width.</returns>
-		public static int GetLengthThatFits (string text, int columns, int tabWidth = 0) => GetLengthThatFits (text?.ToRuneList (), columns, tabWidth);
-
-		/// <summary>
-		/// Gets the number of the Runes in a list of Runes that will fit in <paramref name="columns"/>.
-		/// </summary>
-		/// <param name="runes">The list of runes.</param>
-		/// <param name="columns">The width.</param>
-		/// <param name="tabWidth">The number of columns used for a tab.</param>
-		/// <returns>The index of the last Rune in <paramref name="runes"/> that fit in <paramref name="columns"/>.</returns>
-		public static int GetLengthThatFits (List<Rune> runes, int columns, int tabWidth = 0)
-		{
-			if (runes == null || runes.Count == 0) {
-				return 0;
-			}
-
-			var runesLength = 0;
-			var runeIdx = 0;
-			for (; runeIdx < runes.Count; runeIdx++) {
-				var runeWidth = GetRuneWidth (runes [runeIdx], tabWidth);
-				if (runesLength + runeWidth > columns) {
-					break;
-				}
-				runesLength += runeWidth;
-			}
-			return runeIdx;
-		}
-
-		private static int GetRuneWidth (string str, int tabWidth)
-		{
-			return GetRuneWidth (str.EnumerateRunes ().ToList (), tabWidth);
-		}
-
-		private static int GetRuneWidth (List<Rune> runes, int tabWidth)
-		{
-			return runes.Sum (r => GetRuneWidth (r, tabWidth));
-		}
-
-		private static int GetRuneWidth (Rune rune, int tabWidth)
-		{
-			var runeWidth = rune.GetColumns ();
-			if (rune.Value == '\t') {
-				return tabWidth;
-			}
-			if (runeWidth < 0 || runeWidth > 0) {
-				return Math.Max (runeWidth, 1);
-			}
-
-			return runeWidth;
-		}
-
-		/// <summary>
-		/// Gets the index position from the list based on the <paramref name="width"/>.
-		/// </summary>
-		/// <param name="lines">The lines.</param>
-		/// <param name="width">The width.</param>
-		/// <param name="tabWidth">The number of columns used for a tab.</param>
-		/// <returns>The index of the list that fit the width.</returns>
-		public static int GetMaxColsForWidth (List<string> lines, int width, int tabWidth = 0)
-		{
-			var runesLength = 0;
-			var lineIdx = 0;
-			for (; lineIdx < lines.Count; lineIdx++) {
-				var runes = lines [lineIdx].ToRuneList ();
-				var maxRruneWidth = runes.Count > 0
-					? runes.Max (r => GetRuneWidth (r, tabWidth)) : 1;
-				if (runesLength + maxRruneWidth > width) {
-					break;
-				}
-				runesLength += maxRruneWidth;
-			}
-			return lineIdx;
-		}
-
-		/// <summary>
-		/// Calculates the rectangle required to hold a formatted string of text.
-		/// </summary>
-		/// <param name="x">The x location of the rectangle</param>
-		/// <param name="y">The y location of the rectangle</param>
-		/// <param name="text">The text to measure</param>
-		/// <param name="direction">The text direction.</param>
-		/// <param name="tabWidth">The number of columns used for a tab.</param>
-		/// <returns></returns>
-		public static Rect CalcRect (int x, int y, string text, TextDirection direction = TextDirection.LeftRight_TopBottom, int tabWidth = 0)
-		{
-			if (string.IsNullOrEmpty (text)) {
-				return new Rect (new Point (x, y), Size.Empty);
-			}
-
-			int w, h;
-
-			if (IsHorizontalDirection (direction)) {
-				int mw = 0;
-				int ml = 1;
-
-				int cols = 0;
-				foreach (var rune in text.EnumerateRunes ()) {
-					if (rune.Value == '\n') {
-						ml++;
-						if (cols > mw) {
-							mw = cols;
-						}
-						cols = 0;
-					} else if (rune.Value != '\r') {
-						cols++;
-						var rw = 0;
-						if (rune.Value == '\t') {
-							rw += tabWidth - 1;
-						} else {
-							rw = ((Rune)rune).GetColumns ();
-							if (rw > 0) {
-								rw--;
-							} else if (rw == 0) {
-								cols--;
-							}
-						}
-						cols += rw;
-					}
-				}
-				if (cols > mw) {
-					mw = cols;
-				}
-				w = mw;
-				h = ml;
-			} else {
-				int vw = 1, cw = 1;
-				int vh = 0;
-
-				int rows = 0;
-				foreach (var rune in text.EnumerateRunes ()) {
-					if (rune.Value == '\n') {
-						vw++;
-						if (rows > vh) {
-							vh = rows;
-						}
-						rows = 0;
-						cw = 1;
-					} else if (rune.Value != '\r') {
-						rows++;
-						var rw = 0;
-						if (rune.Value == '\t') {
-							rw += tabWidth - 1;
-							rows += rw;
-						} else {
-							rw = ((Rune)rune).GetColumns ();
-							if (rw == 0) {
-								rows--;
-							} else if (cw < rw) {
-								cw = rw;
-								vw++;
-							}
-						}
-					}
-				}
-				if (rows > vh) {
-					vh = rows;
-				}
-				w = vw;
-				h = vh;
-			}
-
-			return new Rect (x, y, w, h);
-		}
-
-		/// <summary>
-		/// Finds the HotKey and its location in text. 
-		/// </summary>
-		/// <param name="text">The text to look in.</param>
-		/// <param name="hotKeySpecifier">The HotKey specifier (e.g. '_') to look for.</param>
-		/// <param name="hotPos">Outputs the Rune index into <c>text</c>.</param>
-		/// <param name="hotKey">Outputs the hotKey. <see cref="Key.Empty"/> if not found.</param>
-		/// <param name="firstUpperCase">If <c>true</c> the legacy behavior of identifying the
-		/// first upper case character as the HotKey will be enabled.
-		/// Regardless of the value of this parameter, <c>hotKeySpecifier</c> takes precedence.
-		/// Defaults to <see langword="false"/>.</param>
-		/// <returns><c>true</c> if a HotKey was found; <c>false</c> otherwise.</returns>
-		public static bool FindHotKey (string text, Rune hotKeySpecifier, out int hotPos, out Key hotKey, bool firstUpperCase = false)
-		{
-			if (string.IsNullOrEmpty (text) || hotKeySpecifier == (Rune)0xFFFF) {
-				hotPos = -1;
-				hotKey = KeyCode.Null;
-				return false;
-			}
-
-			Rune hot_key = (Rune)0;
-			int hot_pos = -1;
-
-			// Use first hot_key char passed into 'hotKey'.
-			// TODO: Ignore hot_key of two are provided
-			// TODO: Do not support non-alphanumeric chars that can't be typed
-			int i = 0;
-			foreach (Rune c in text.EnumerateRunes ()) {
-				if ((char)c.Value != 0xFFFD) {
-					if (c == hotKeySpecifier) {
-						hot_pos = i;
-					} else if (hot_pos > -1) {
-						hot_key = c;
-						break;
-					}
-				}
-				i++;
-			}
-
-			// Legacy support - use first upper case char if the specifier was not found
-			if (hot_pos == -1 && firstUpperCase) {
-				i = 0;
-				foreach (Rune c in text.EnumerateRunes ()) {
-					if ((char)c.Value != 0xFFFD) {
-						if (Rune.IsUpper (c)) {
-							hot_key = c;
-							hot_pos = i;
-							break;
-						}
-					}
-					i++;
-				}
-			}
-
-			if (hot_key != (Rune)0 && hot_pos != -1) {
-				hotPos = hot_pos;
-
-				var newHotKey = (KeyCode)hot_key.Value;
-				if (newHotKey != KeyCode.Null && !(newHotKey == KeyCode.Space || Rune.IsControl (hot_key))) {
-					if ((newHotKey & ~KeyCode.Space) is >= KeyCode.A and <= KeyCode.Z) {
-						newHotKey &= ~KeyCode.Space;
-					}
-					hotKey = newHotKey;
-					return true;
-				}
-			}
-
-			hotPos = -1;
-			hotKey = KeyCode.Null;
-			return false;
-		}
-
-		/// <summary>
-		/// Replaces the Rune at the index specified by the <c>hotPos</c> parameter with a tag identifying 
-		/// it as the hotkey.
-		/// </summary>
-		/// <param name="text">The text to tag the hotkey in.</param>
-		/// <param name="hotPos">The Rune index of the hotkey in <c>text</c>.</param>
-		/// <returns>The text with the hotkey tagged.</returns>
-		/// <remarks>
-		/// The returned string will not render correctly without first un-doing the tag. To undo the tag, search for 
-		/// </remarks>
-		public string ReplaceHotKeyWithTag (string text, int hotPos)
-		{
-			// Set the high bit
-			var runes = text.ToRuneList ();
-			if (Rune.IsLetterOrDigit (runes [hotPos])) {
-				runes [hotPos] = new Rune ((uint)runes [hotPos].Value);
-			}
-			return StringExtensions.ToString (runes);
-		}
-
-		/// <summary>
-		/// Removes the hotkey specifier from text.
-		/// </summary>
-		/// <param name="text">The text to manipulate.</param>
-		/// <param name="hotKeySpecifier">The hot-key specifier (e.g. '_') to look for.</param>
-		/// <param name="hotPos">Returns the position of the hot-key in the text. -1 if not found.</param>
-		/// <returns>The input text with the hotkey specifier ('_') removed.</returns>
-		public static string RemoveHotKeySpecifier (string text, int hotPos, Rune hotKeySpecifier)
-		{
-			if (string.IsNullOrEmpty (text)) {
-				return text;
-			}
-
-			// Scan 
-			string start = string.Empty;
-			int i = 0;
-			foreach (Rune c in text) {
-				if (c == hotKeySpecifier && i == hotPos) {
-					i++;
-					continue;
-				}
-				start += c;
-				i++;
-			}
-			return start;
-		}
-		#endregion // Static Members
-
-		List<string> _lines = new List<string> ();
-		string _text = null;
-		TextAlignment _textAlignment;
-		VerticalTextAlignment _textVerticalAlignment;
-		TextDirection _textDirection;
-		Key _hotKey = new Key ();
-		int _hotKeyPos = -1;
-		Size _size;
-		private bool _autoSize;
-		private bool _preserveTrailingSpaces;
-		private int _tabWidth = 4;
-		private bool _wordWrap = true;
-		private bool _multiLine;
-
-		/// <summary>
-		/// Event invoked when the <see cref="HotKey"/> is changed.
-		/// </summary>
-		public event EventHandler<KeyChangedEventArgs> HotKeyChanged;
-
-		/// <summary>
-		///   The text to be displayed. This string is never modified.
-		/// </summary>
-		public virtual string Text {
-			get => _text;
-			set {
-				var textWasNull = _text == null && value != null;
-				_text = EnableNeedsFormat (value);
-
-				if ((AutoSize && Alignment != TextAlignment.Justified && VerticalAlignment != VerticalTextAlignment.Justified) || (textWasNull && Size.IsEmpty)) {
-					Size = CalcRect (0, 0, _text, Direction, TabWidth).Size;
-				}
-
-				//if (_text != null && _text.GetRuneCount () > 0 && (Size.Width == 0 || Size.Height == 0 || Size.Width != _text.GetColumns ())) {
-				//	// Provide a default size (width = length of longest line, height = 1)
-				//	// TODO: It might makes more sense for the default to be width = length of first line?
-				//	Size = new Size (TextFormatter.MaxWidth (Text, int.MaxValue), 1);
-				//}
-			}
-		}
-
-		/// <summary>
-		/// Gets or sets whether the <see cref="Size"/> should be automatically changed to fit the <see cref="Text"/>.
-		/// </summary>
-		/// <remarks>
-		/// <para>
-		/// Used by <see cref="View.AutoSize"/> to resize the view's <see cref="View.Bounds"/> to fit <see cref="Size"/>.
-		/// </para>
-		/// <para>
-		/// AutoSize is ignored if <see cref="TextAlignment.Justified"/> and <see cref="VerticalTextAlignment.Justified"/> are used.
-		/// </para>
-		/// </remarks>
-		public bool AutoSize {
-			get => _autoSize;
-			set {
-				_autoSize = EnableNeedsFormat (value);
-				if (_autoSize && Alignment != TextAlignment.Justified && VerticalAlignment != VerticalTextAlignment.Justified) {
-					Size = CalcRect (0, 0, _text, Direction, TabWidth).Size;
-				}
-			}
-		}
-
-		/// <summary>
-		/// Gets or sets whether trailing spaces at the end of word-wrapped lines are preserved
-		/// or not when <see cref="TextFormatter.WordWrap"/> is enabled. 
-		/// If <see langword="true"/> trailing spaces at the end of wrapped lines will be removed when 
-		/// <see cref="Text"/> is formatted for display. The default is <see langword="false"/>.
-		/// </summary>
-		public bool PreserveTrailingSpaces {
-			get => _preserveTrailingSpaces;
-			set => _preserveTrailingSpaces = EnableNeedsFormat (value);
-		}
-
-		/// <summary>
-		/// Controls the horizontal text-alignment property.
-		/// </summary>
-		/// <value>The text alignment.</value>
-		public TextAlignment Alignment {
-			get => _textAlignment;
-			set => _textAlignment = EnableNeedsFormat (value);
-		}
-
-		/// <summary>
-		/// Controls the vertical text-alignment property. 
-		/// </summary>
-		/// <value>The text vertical alignment.</value>
-		public VerticalTextAlignment VerticalAlignment {
-			get => _textVerticalAlignment;
-			set => _textVerticalAlignment = EnableNeedsFormat (value);
-		}
-
-		/// <summary>
-		/// Controls the text-direction property. 
-		/// </summary>
-		/// <value>The text vertical alignment.</value>
-		public TextDirection Direction {
-			get => _textDirection;
-			set {
-				_textDirection = EnableNeedsFormat (value);
-				if (AutoSize && Alignment != TextAlignment.Justified && VerticalAlignment != VerticalTextAlignment.Justified) {
-					Size = CalcRect (0, 0, Text, Direction, TabWidth).Size;
-				}
-			}
-		}
-
-		/// <summary>
-		/// Check if it is a horizontal direction
-		/// </summary>
-		public static bool IsHorizontalDirection (TextDirection textDirection)
-		{
-			switch (textDirection) {
-			case TextDirection.LeftRight_TopBottom:
-			case TextDirection.LeftRight_BottomTop:
-			case TextDirection.RightLeft_TopBottom:
-			case TextDirection.RightLeft_BottomTop:
-				return true;
-			default:
-				return false;
-			}
-		}
-
-		/// <summary>
-		/// Check if it is a vertical direction
-		/// </summary>
-		public static bool IsVerticalDirection (TextDirection textDirection)
-		{
-			switch (textDirection) {
-			case TextDirection.TopBottom_LeftRight:
-			case TextDirection.TopBottom_RightLeft:
-			case TextDirection.BottomTop_LeftRight:
-			case TextDirection.BottomTop_RightLeft:
-				return true;
-			default:
-				return false;
-			}
-		}
-
-		/// <summary>
-		/// Check if it is Left to Right direction
-		/// </summary>
-		public static bool IsLeftToRight (TextDirection textDirection)
-		{
-			switch (textDirection) {
-			case TextDirection.LeftRight_TopBottom:
-			case TextDirection.LeftRight_BottomTop:
-				return true;
-			default:
-				return false;
-			}
-		}
-
-		/// <summary>
-		/// Check if it is Top to Bottom direction
-		/// </summary>
-		public static bool IsTopToBottom (TextDirection textDirection)
-		{
-			switch (textDirection) {
-			case TextDirection.TopBottom_LeftRight:
-			case TextDirection.TopBottom_RightLeft:
-				return true;
-			default:
-				return false;
-			}
-		}
-
-		/// <summary>
-		/// Gets or sets whether word wrap will be used to fit <see cref="Text"/> to <see cref="Size"/>.
-		/// </summary>
-		public bool WordWrap {
-			get => _wordWrap;
-			set => _wordWrap = EnableNeedsFormat (value);
-		}
-
-		/// <summary>
-		/// Gets or sets the size <see cref="Text"/> will be constrained to when formatted.
-		/// </summary>
-		/// <remarks>
-		/// Does not return the size of the formatted text but the size that will be used to constrain the text when formatted.
-		/// </remarks>
-		public Size Size {
-			get => _size;
-			set {
-				if (AutoSize && Alignment != TextAlignment.Justified && VerticalAlignment != VerticalTextAlignment.Justified) {
-					_size = EnableNeedsFormat (CalcRect (0, 0, Text, Direction, TabWidth).Size);
-				} else {
-					_size = EnableNeedsFormat (value);
-				}
-			}
-		}
-
-		/// <summary>
-		/// The specifier character for the hot key (e.g. '_'). Set to '\xffff' to disable hot key support for this View instance. The default is '\xffff'.
-		/// </summary>
-		public Rune HotKeySpecifier { get; set; } = (Rune)0xFFFF;
-
-		/// <summary>
-		/// The position in the text of the hot key. The hot key will be rendered using the hot color.
-		/// </summary>
-		public int HotKeyPos { get => _hotKeyPos; internal set => _hotKeyPos = value; }
-
-		/// <summary>
-		/// Gets or sets the hot key. Must be be an upper case letter or digit. Fires the <see cref="HotKeyChanged"/> event.
-		/// </summary>
-		public Key HotKey {
-			get => _hotKey;
-			internal set {
-				if (_hotKey != value) {
-					var oldKey = _hotKey;
-					_hotKey = value;
-					HotKeyChanged?.Invoke (this, new KeyChangedEventArgs (oldKey, value));
-				}
-			}
-		}
-
-		/// <summary>
-		/// Gets the cursor position from <see cref="HotKey"/>. If the <see cref="HotKey"/> is defined, the cursor will be positioned over it.
-		/// </summary>
-		public int CursorPosition { get; internal set; }
-
-		/// <summary>
-		/// Gets the size required to hold the formatted text, given the constraints placed by <see cref="Size"/>.
-		/// </summary>
-		/// <remarks>
-		/// Causes a format, resetting <see cref="NeedsFormat"/>.
-		/// </remarks>
-		/// <returns></returns>
-		public Size GetFormattedSize ()
-		{
-			var lines = Lines;
-			if (Lines.Count > 0) {
-				var width = Lines.Max (line => line.GetColumns ());
-				var height = Lines.Count;
-				return new Size (width, height);
-			}
-			return Size.Empty;
-		}
-
-		/// <summary>
-		/// Gets the formatted lines.
-		/// </summary>
-		/// <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(string, int, bool, bool, bool, int, TextDirection, bool)"/> will be called internally. 
-		/// </para>
-		/// </remarks>
-		public List<string> Lines {
-			get {
-				// With this check, we protect against subclasses with overrides of Text
-				if (string.IsNullOrEmpty (Text) || Size.IsEmpty) {
-					_lines = new List<string> {
-						string.Empty
-					};
-					NeedsFormat = false;
-					return _lines;
-				}
-
-				if (NeedsFormat) {
-					var shown_text = _text;
-					if (FindHotKey (_text, HotKeySpecifier, out _hotKeyPos, out var newHotKey)) {
-						HotKey = newHotKey;
-						shown_text = RemoveHotKeySpecifier (Text, _hotKeyPos, HotKeySpecifier);
-						shown_text = ReplaceHotKeyWithTag (shown_text, _hotKeyPos);
-					}
-
-					if (IsVerticalDirection (Direction)) {
-						var colsWidth = GetSumMaxCharWidth (shown_text, 0, 1, TabWidth);
-						_lines = Format (shown_text, Size.Height, VerticalAlignment == VerticalTextAlignment.Justified, Size.Width > colsWidth && WordWrap,
-							PreserveTrailingSpaces, TabWidth, Direction, MultiLine);
-						if (!AutoSize) {
-							colsWidth = GetMaxColsForWidth (_lines, Size.Width, TabWidth);
-							if (_lines.Count > colsWidth) {
-								_lines.RemoveRange (colsWidth, _lines.Count - colsWidth);
-							}
-						}
-					} else {
-						_lines = Format (shown_text, Size.Width, Alignment == TextAlignment.Justified, Size.Height > 1 && WordWrap,
-							PreserveTrailingSpaces, TabWidth, Direction, MultiLine);
-						if (!AutoSize && _lines.Count > Size.Height) {
-							_lines.RemoveRange (Size.Height, _lines.Count - Size.Height);
-						}
-					}
-
-					NeedsFormat = false;
-				}
-				return _lines;
-			}
-		}
-
-		/// <summary>
-		/// Gets or sets whether the <see cref="TextFormatter"/> needs to format the text. 
-		/// </summary>
-		/// <remarks>
-		/// <para>
-		/// If <c>false</c> when Draw is called, the Draw call will be faster.
-		/// </para>
-		/// <para>
-		/// Used by <see cref="Draw(Rect, Attribute, Attribute, Rect, bool, ConsoleDriver)"/>
-		/// </para>
-		/// <para>
-		/// This is set to true when the properties of <see cref="TextFormatter"/> are set.
-		/// </para>
-		/// </remarks>
-		public bool NeedsFormat { get; set; }
-
-		/// <summary>
-		/// Gets or sets the number of columns used for a tab.
-		/// </summary>
-		public int TabWidth {
-			get => _tabWidth;
-			set => _tabWidth = EnableNeedsFormat (value);
-		}
-
-		/// <summary>
-		/// Gets or sets a value indicating whether multi line is allowed.
-		/// </summary>
-		/// <remarks>
-		///   Multi line is ignored if <see cref="WordWrap"/> is <see langword="true"/>.
-		/// </remarks>
-		public bool MultiLine {
-			get => _multiLine;
-			set {
-				_multiLine = EnableNeedsFormat (value);
-			}
-		}
-
-		private T EnableNeedsFormat<T> (T value)
-		{
-			NeedsFormat = true;
-			return value;
-		}
-
-		/// <summary>
-		/// Causes the <see cref="TextFormatter"/> to reformat the text. 
-		/// </summary>
-		/// <returns>The formatted text.</returns>
-		public string Format ()
-		{
-			var sb = new StringBuilder ();
-			// Lines_get causes a Format
-			foreach (var line in Lines) {
-				sb.AppendLine (line);
-			}
-			return sb.ToString ().TrimEnd (Environment.NewLine.ToCharArray ());
-		}
-
-		/// <summary>
-		/// Draws the text held by <see cref="TextFormatter"/> to <see cref="ConsoleDriver"/> using the colors specified.
-		/// </summary>
-		/// <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>
-		/// <param name="containerBounds">Specifies the screen-relative location and maximum container size.</param>
-		/// <param name="fillRemaining">Determines if the bounds width will be used (default) or only the text width will be used.</param>
-		/// <param name="driver">The console driver currently used by the application.</param>
-		public void Draw (Rect bounds, Attribute normalColor, Attribute hotColor, Rect containerBounds = default, bool fillRemaining = true, ConsoleDriver driver = null)
-		{
-			// With this check, we protect against subclasses with overrides of Text (like Button)
-			if (string.IsNullOrEmpty (_text)) {
-				return;
-			}
-
-			if (driver == null) {
-				driver = Application.Driver;
-			}
-			driver?.SetAttribute (normalColor);
-
-			// Use "Lines" to ensure a Format (don't use "lines"))
-
-			var linesFormated = Lines;
-			switch (Direction) {
-			case TextDirection.TopBottom_RightLeft:
-			case TextDirection.LeftRight_BottomTop:
-			case TextDirection.RightLeft_BottomTop:
-			case TextDirection.BottomTop_RightLeft:
-				linesFormated.Reverse ();
-				break;
-			}
-
-			var isVertical = IsVerticalDirection (Direction);
-			var maxBounds = bounds;
-			if (driver != null) {
-				maxBounds = containerBounds == default
-					? bounds
-					: new Rect (Math.Max (containerBounds.X, bounds.X),
-					Math.Max (containerBounds.Y, bounds.Y),
-					Math.Max (Math.Min (containerBounds.Width, containerBounds.Right - bounds.Left), 0),
-					Math.Max (Math.Min (containerBounds.Height, containerBounds.Bottom - bounds.Top), 0));
-			}
-			if (maxBounds.Width == 0 || maxBounds.Height == 0) {
-				return;
-			}
-
-			// BUGBUG: v2 - TextFormatter should not change the clip region. If a caller wants to break out of the clip region it should do
-			// so explicitly.
-			//var savedClip = Application.Driver?.Clip;
-			//if (Application.Driver != null) {
-			//	Application.Driver.Clip = maxBounds;
-			//}
-			var lineOffset = !isVertical && bounds.Y < 0 ? Math.Abs (bounds.Y) : 0;
-
-			for (int line = lineOffset; line < linesFormated.Count; line++) {
-				if ((isVertical && line > bounds.Width) || (!isVertical && line > bounds.Height))
-					continue;
-				if ((isVertical && line >= maxBounds.Left + maxBounds.Width)
-					|| (!isVertical && line >= maxBounds.Top + maxBounds.Height + lineOffset))
-
-					break;
-
-				var runes = _lines [line].ToRunes ();
-
-				switch (Direction) {
-				case TextDirection.RightLeft_BottomTop:
-				case TextDirection.RightLeft_TopBottom:
-				case TextDirection.BottomTop_LeftRight:
-				case TextDirection.BottomTop_RightLeft:
-					runes = runes.Reverse ().ToArray ();
-					break;
-				}
-
-				// When text is justified, we lost left or right, so we use the direction to align. 
-
-				int x, y;
-				// Horizontal Alignment
-				if (_textAlignment == TextAlignment.Right || (_textAlignment == TextAlignment.Justified && !IsLeftToRight (Direction))) {
-					if (isVertical) {
-						var runesWidth = GetSumMaxCharWidth (Lines, line, TabWidth);
-						x = bounds.Right - runesWidth;
-						CursorPosition = bounds.Width - runesWidth + (_hotKeyPos > -1 ? _hotKeyPos : 0);
-					} else {
-						var runesWidth = StringExtensions.ToString (runes).GetColumns ();
-						x = bounds.Right - runesWidth;
-						CursorPosition = bounds.Width - runesWidth + (_hotKeyPos > -1 ? _hotKeyPos : 0);
-					}
-				} else if (_textAlignment == TextAlignment.Left || _textAlignment == TextAlignment.Justified) {
-					if (isVertical) {
-						var runesWidth = line > 0 ? GetSumMaxCharWidth (Lines, 0, line, TabWidth) : 0;
-						x = bounds.Left + runesWidth;
-					} else {
-						x = bounds.Left;
-					}
-					CursorPosition = _hotKeyPos > -1 ? _hotKeyPos : 0;
-				} else if (_textAlignment == TextAlignment.Centered) {
-					if (isVertical) {
-						var runesWidth = GetSumMaxCharWidth (Lines, line, TabWidth);
-						x = bounds.Left + line + ((bounds.Width - runesWidth) / 2);
-						CursorPosition = (bounds.Width - runesWidth) / 2 + (_hotKeyPos > -1 ? _hotKeyPos : 0);
-					} else {
-						var runesWidth = StringExtensions.ToString (runes).GetColumns ();
-						x = bounds.Left + (bounds.Width - runesWidth) / 2;
-						CursorPosition = (bounds.Width - runesWidth) / 2 + (_hotKeyPos > -1 ? _hotKeyPos : 0);
-					}
-				} else {
-					throw new ArgumentOutOfRangeException ();
-				}
-
-				// Vertical Alignment
-				if (_textVerticalAlignment == VerticalTextAlignment.Bottom || (_textVerticalAlignment == VerticalTextAlignment.Justified && !IsTopToBottom (Direction))) {
-					if (isVertical) {
-						y = bounds.Bottom - runes.Length;
-					} else {
-						y = bounds.Bottom - Lines.Count + line;
-					}
-				} else if (_textVerticalAlignment == VerticalTextAlignment.Top || _textVerticalAlignment == VerticalTextAlignment.Justified) {
-					if (isVertical) {
-						y = bounds.Top;
-					} else {
-						y = bounds.Top + line;
-					}
-				} else if (_textVerticalAlignment == VerticalTextAlignment.Middle) {
-					if (isVertical) {
-						var s = (bounds.Height - runes.Length) / 2;
-						y = bounds.Top + s;
-					} else {
-						var s = (bounds.Height - Lines.Count) / 2;
-						y = bounds.Top + line + s;
-					}
-				} else {
-					throw new ArgumentOutOfRangeException ();
-				}
-
-				var colOffset = bounds.X < 0 ? Math.Abs (bounds.X) : 0;
-				var start = isVertical ? bounds.Top : bounds.Left;
-				var size = isVertical ? bounds.Height : bounds.Width;
-				var current = start + colOffset;
-				List<Point?> lastZeroWidthPos = null;
-				Rune rune = default;
-				Rune lastRuneUsed;
-				var zeroLengthCount = isVertical ? runes.Sum (r => r.GetColumns () == 0 ? 1 : 0) : 0;
-
-				for (var idx = (isVertical ? start - y : start - x) + colOffset; current < start + size + zeroLengthCount; idx++) {
-					lastRuneUsed = rune;
-					if (lastZeroWidthPos == null) {
-						if (idx < 0 || x + current + colOffset < 0) {
-							current++;
-							continue;
-						} else if (!fillRemaining && idx > runes.Length - 1) {
-							break;
-						}
-						if ((!isVertical && current - start > maxBounds.Left + maxBounds.Width - bounds.X + colOffset)
-							|| (isVertical && idx > maxBounds.Top + maxBounds.Height - bounds.Y)) {
-
-							break;
-						}
-					}
-					//if ((!isVertical && idx > maxBounds.Left + maxBounds.Width - bounds.X + colOffset)
-					//	|| (isVertical && idx > maxBounds.Top + maxBounds.Height - bounds.Y))
-
-					//	break;
-
-					rune = (Rune)' ';
-					if (isVertical) {
-						if (idx >= 0 && idx < runes.Length) {
-							rune = runes [idx];
-						}
-						if (lastZeroWidthPos == null) {
-							driver?.Move (x, current);
-						} else {
-							var foundIdx = lastZeroWidthPos.IndexOf (p => p.Value.Y == current);
-							if (foundIdx > -1) {
-								if (rune.IsCombiningMark ()) {
-									lastZeroWidthPos [foundIdx] = (new Point (lastZeroWidthPos [foundIdx].Value.X + 1, current));
-
-									driver?.Move (lastZeroWidthPos [foundIdx].Value.X, current);
-								} else if (!rune.IsCombiningMark () && lastRuneUsed.IsCombiningMark ()) {
-									current++;
-									driver?.Move (x, current);
-								} else {
-									driver?.Move (x, current);
-								}
-							} else {
-								driver?.Move (x, current);
-							}
-						}
-					} else {
-						driver?.Move (current, y);
-						if (idx >= 0 && idx < runes.Length) {
-							rune = runes [idx];
-						}
-					}
-
-					var runeWidth = GetRuneWidth (rune, TabWidth);
-
-					if (HotKeyPos > -1 && idx == HotKeyPos) {
-						if ((isVertical && _textVerticalAlignment == VerticalTextAlignment.Justified) ||
-						(!isVertical && _textAlignment == TextAlignment.Justified)) {
-							CursorPosition = idx - start;
-						}
-						driver?.SetAttribute (hotColor);
-						driver?.AddRune (rune);
-						driver?.SetAttribute (normalColor);
-					} else {
-						if (isVertical) {
-							if (runeWidth == 0) {
-								if (lastZeroWidthPos == null) {
-									lastZeroWidthPos = new List<Point?> ();
-								}
-								var foundIdx = lastZeroWidthPos.IndexOf (p => p.Value.Y == current);
-								if (foundIdx == -1) {
-									current--;
-									lastZeroWidthPos.Add ((new Point (x + 1, current)));
-								}
-								driver?.Move (x + 1, current);
-							}
-						}
-
-						driver?.AddRune (rune);
-					}
-
-					if (isVertical) {
-						if (runeWidth > 0) {
-							current++;
-						}
-					} else {
-						current += runeWidth;
-					}
-					var nextRuneWidth = idx + 1 > -1 && idx + 1 < runes.Length ? runes [idx + 1].GetColumns () : 0;
-					if (!isVertical && idx + 1 < runes.Length && current + nextRuneWidth > start + size) {
-						break;
-					}
-				}
-			}
-			//if (Application.Driver != null) {
-			//	Application.Driver.Clip = (Rect)savedClip;
-			//}
-		}
-	}
+namespace Terminal.Gui;
+
+/// <summary>Text alignment enumeration, controls how text is displayed.</summary>
+public enum TextAlignment
+{
+    /// <summary>The text will be left-aligned.</summary>
+    Left,
+
+    /// <summary>The text will be right-aligned.</summary>
+    Right,
+
+    /// <summary>The text will be centered horizontally.</summary>
+    Centered,
+
+    /// <summary>
+    ///     The text will be justified (spaces will be added to existing spaces such that the text fills the container
+    ///     horizontally).
+    /// </summary>
+    Justified
+}
+
+/// <summary>Vertical text alignment enumeration, controls how text is displayed.</summary>
+public enum VerticalTextAlignment
+{
+    /// <summary>The text will be top-aligned.</summary>
+    Top,
+
+    /// <summary>The text will be bottom-aligned.</summary>
+    Bottom,
+
+    /// <summary>The text will centered vertically.</summary>
+    Middle,
+
+    /// <summary>
+    ///     The text will be justified (spaces will be added to existing spaces such that the text fills the container
+    ///     vertically).
+    /// </summary>
+    Justified
+}
+
+/// <summary>Text direction enumeration, controls how text is displayed.</summary>
+/// <remarks>
+///     <para>TextDirection  [H] = Horizontal  [V] = Vertical</para>
+///     <table>
+///         <tr>
+///             <th>TextDirection</th> <th>Description</th>
+///         </tr>
+///         <tr>
+///             <td>LeftRight_TopBottom [H]</td> <td>Normal</td>
+///         </tr>
+///         <tr>
+///             <td>TopBottom_LeftRight [V]</td> <td>Normal</td>
+///         </tr>
+///         <tr>
+///             <td>RightLeft_TopBottom [H]</td> <td>Invert Text</td>
+///         </tr>
+///         <tr>
+///             <td>TopBottom_RightLeft [V]</td> <td>Invert Lines</td>
+///         </tr>
+///         <tr>
+///             <td>LeftRight_BottomTop [H]</td> <td>Invert Lines</td>
+///         </tr>
+///         <tr>
+///             <td>BottomTop_LeftRight [V]</td> <td>Invert Text</td>
+///         </tr>
+///         <tr>
+///             <td>RightLeft_BottomTop [H]</td> <td>Invert Text + Invert Lines</td>
+///         </tr>
+///         <tr>
+///             <td>BottomTop_RightLeft [V]</td> <td>Invert Text + Invert Lines</td>
+///         </tr>
+///     </table>
+/// </remarks>
+public enum TextDirection
+{
+    /// <summary>Normal horizontal direction. <code>HELLO<br/>WORLD</code></summary>
+    LeftRight_TopBottom,
+
+    /// <summary>Normal vertical direction. <code>H W<br/>E O<br/>L R<br/>L L<br/>O D</code></summary>
+    TopBottom_LeftRight,
+
+    /// <summary>This is a horizontal direction. <br/> RTL <code>OLLEH<br/>DLROW</code></summary>
+    RightLeft_TopBottom,
+
+    /// <summary>This is a vertical direction. <code>W H<br/>O E<br/>R L<br/>L L<br/>D O</code></summary>
+    TopBottom_RightLeft,
+
+    /// <summary>This is a horizontal direction. <code>WORLD<br/>HELLO</code></summary>
+    LeftRight_BottomTop,
+
+    /// <summary>This is a vertical direction. <code>O D<br/>L L<br/>L R<br/>E O<br/>H W</code></summary>
+    BottomTop_LeftRight,
+
+    /// <summary>This is a horizontal direction. <code>DLROW<br/>OLLEH</code></summary>
+    RightLeft_BottomTop,
+
+    /// <summary>This is a vertical direction. <code>D O<br/>L L<br/>R L<br/>O E<br/>W H</code></summary>
+    BottomTop_RightLeft
+}
+
+/// <summary>
+///     Provides text formatting. Supports <see cref="View.HotKey"/>s, horizontal alignment, vertical alignment, multiple
+///     lines, and word-based line wrap.
+/// </summary>
+public class TextFormatter
+{
+    private bool _autoSize;
+    private Key _hotKey = new ();
+    private int _hotKeyPos = -1;
+
+    private List<string> _lines = new ();
+    private bool _multiLine;
+    private bool _preserveTrailingSpaces;
+    private Size _size;
+    private int _tabWidth = 4;
+    private string _text;
+    private TextAlignment _textAlignment;
+    private TextDirection _textDirection;
+    private VerticalTextAlignment _textVerticalAlignment;
+    private bool _wordWrap = true;
+
+    /// <summary>Controls the horizontal text-alignment property.</summary>
+    /// <value>The text alignment.</value>
+    public TextAlignment Alignment { get => _textAlignment; set => _textAlignment = EnableNeedsFormat (value); }
+
+    /// <summary>Gets or sets whether the <see cref="Size"/> should be automatically changed to fit the <see cref="Text"/>.</summary>
+    /// <remarks>
+    ///     <para>Used by <see cref="View.AutoSize"/> to resize the view's <see cref="View.Bounds"/> to fit <see cref="Size"/>.</para>
+    ///     <para>
+    ///         AutoSize is ignored if <see cref="TextAlignment.Justified"/> and <see cref="VerticalTextAlignment.Justified"/>
+    ///         are used.
+    ///     </para>
+    /// </remarks>
+    public bool AutoSize
+    {
+        get => _autoSize;
+        set
+        {
+            _autoSize = EnableNeedsFormat (value);
+
+            if (_autoSize && Alignment != TextAlignment.Justified && VerticalAlignment != VerticalTextAlignment.Justified)
+            {
+                Size = CalcRect (0, 0, _text, Direction, TabWidth).Size;
+            }
+        }
+    }
+
+    /// <summary>
+    ///     Gets the cursor position from <see cref="HotKey"/>. If the <see cref="HotKey"/> is defined, the cursor will be
+    ///     positioned over it.
+    /// </summary>
+    public int CursorPosition { get; internal set; }
+
+    /// <summary>Controls the text-direction property.</summary>
+    /// <value>The text vertical alignment.</value>
+    public TextDirection Direction
+    {
+        get => _textDirection;
+        set
+        {
+            _textDirection = EnableNeedsFormat (value);
+
+            if (AutoSize && Alignment != TextAlignment.Justified && VerticalAlignment != VerticalTextAlignment.Justified)
+            {
+                Size = CalcRect (0, 0, Text, Direction, TabWidth).Size;
+            }
+        }
+    }
+
+    /// <summary>
+    ///     Gets or sets the hot key. Must be be an upper case letter or digit. Fires the <see cref="HotKeyChanged"/>
+    ///     event.
+    /// </summary>
+    public Key HotKey
+    {
+        get => _hotKey;
+        internal set
+        {
+            if (_hotKey != value)
+            {
+                Key oldKey = _hotKey;
+                _hotKey = value;
+                HotKeyChanged?.Invoke (this, new KeyChangedEventArgs (oldKey, value));
+            }
+        }
+    }
+
+    /// <summary>The position in the text of the hot key. The hot key will be rendered using the hot color.</summary>
+    public int HotKeyPos { get => _hotKeyPos; internal set => _hotKeyPos = value; }
+
+    /// <summary>
+    ///     The specifier character for the hot key (e.g. '_'). Set to '\xffff' to disable hot key support for this View
+    ///     instance. The default is '\xffff'.
+    /// </summary>
+    public Rune HotKeySpecifier { get; set; } = (Rune)0xFFFF;
+
+    /// <summary>Gets the formatted lines.</summary>
+    /// <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(string, int, bool, bool, bool, int, TextDirection, bool)"/> will be called internally.
+    ///     </para>
+    /// </remarks>
+    public List<string> Lines
+    {
+        get
+        {
+            // With this check, we protect against subclasses with overrides of Text
+            if (string.IsNullOrEmpty (Text) || Size.IsEmpty)
+            {
+                _lines = new List<string>
+                {
+                    string.Empty
+                };
+                NeedsFormat = false;
+
+                return _lines;
+            }
+
+            if (NeedsFormat)
+            {
+                string shown_text = _text;
+
+                if (FindHotKey (_text, HotKeySpecifier, out _hotKeyPos, out Key newHotKey))
+                {
+                    HotKey = newHotKey;
+                    shown_text = RemoveHotKeySpecifier (Text, _hotKeyPos, HotKeySpecifier);
+                    shown_text = ReplaceHotKeyWithTag (shown_text, _hotKeyPos);
+                }
+
+                if (IsVerticalDirection (Direction))
+                {
+                    int colsWidth = GetSumMaxCharWidth (shown_text, 0, 1, TabWidth);
+
+                    _lines = Format (
+                                     shown_text,
+                                     Size.Height,
+                                     VerticalAlignment == VerticalTextAlignment.Justified,
+                                     Size.Width > colsWidth && WordWrap,
+                                     PreserveTrailingSpaces,
+                                     TabWidth,
+                                     Direction,
+                                     MultiLine);
+
+                    if (!AutoSize)
+                    {
+                        colsWidth = GetMaxColsForWidth (_lines, Size.Width, TabWidth);
+
+                        if (_lines.Count > colsWidth)
+                        {
+                            _lines.RemoveRange (colsWidth, _lines.Count - colsWidth);
+                        }
+                    }
+                }
+                else
+                {
+                    _lines = Format (
+                                     shown_text,
+                                     Size.Width,
+                                     Alignment == TextAlignment.Justified,
+                                     Size.Height > 1 && WordWrap,
+                                     PreserveTrailingSpaces,
+                                     TabWidth,
+                                     Direction,
+                                     MultiLine);
+
+                    if (!AutoSize && _lines.Count > Size.Height)
+                    {
+                        _lines.RemoveRange (Size.Height, _lines.Count - Size.Height);
+                    }
+                }
+
+                NeedsFormat = false;
+            }
+
+            return _lines;
+        }
+    }
+
+    /// <summary>Gets or sets a value indicating whether multi line is allowed.</summary>
+    /// <remarks>Multi line is ignored if <see cref="WordWrap"/> is <see langword="true"/>.</remarks>
+    public bool MultiLine { get => _multiLine; set => _multiLine = EnableNeedsFormat (value); }
+
+    /// <summary>Gets or sets whether the <see cref="TextFormatter"/> needs to format the text.</summary>
+    /// <remarks>
+    ///     <para>If <c>false</c> when Draw is called, the Draw call will be faster.</para>
+    ///     <para>Used by <see cref="Draw(Rect, Attribute, Attribute, Rect, bool, ConsoleDriver)"/></para>
+    ///     <para>This is set to true when the properties of <see cref="TextFormatter"/> are set.</para>
+    /// </remarks>
+    public bool NeedsFormat { get; set; }
+
+    /// <summary>
+    ///     Gets or sets whether trailing spaces at the end of word-wrapped lines are preserved or not when
+    ///     <see cref="TextFormatter.WordWrap"/> is enabled. If <see langword="true"/> trailing spaces at the end of wrapped
+    ///     lines will be removed when <see cref="Text"/> is formatted for display. The default is <see langword="false"/>.
+    /// </summary>
+    public bool PreserveTrailingSpaces { get => _preserveTrailingSpaces; set => _preserveTrailingSpaces = EnableNeedsFormat (value); }
+
+    /// <summary>Gets or sets the size <see cref="Text"/> will be constrained to when formatted.</summary>
+    /// <remarks>
+    ///     Does not return the size of the formatted text but the size that will be used to constrain the text when
+    ///     formatted.
+    /// </remarks>
+    public Size Size
+    {
+        get => _size;
+        set
+        {
+            if (AutoSize && Alignment != TextAlignment.Justified && VerticalAlignment != VerticalTextAlignment.Justified)
+            {
+                _size = EnableNeedsFormat (CalcRect (0, 0, Text, Direction, TabWidth).Size);
+            }
+            else
+            {
+                _size = EnableNeedsFormat (value);
+            }
+        }
+    }
+
+    /// <summary>Gets or sets the number of columns used for a tab.</summary>
+    public int TabWidth { get => _tabWidth; set => _tabWidth = EnableNeedsFormat (value); }
+
+    /// <summary>The text to be displayed. This string is never modified.</summary>
+    public virtual string Text
+    {
+        get => _text;
+        set
+        {
+            bool textWasNull = _text == null && value != null;
+            _text = EnableNeedsFormat (value);
+
+            if ((AutoSize && Alignment != TextAlignment.Justified && VerticalAlignment != VerticalTextAlignment.Justified) || (textWasNull && Size.IsEmpty))
+            {
+                Size = CalcRect (0, 0, _text, Direction, TabWidth).Size;
+            }
+
+            //if (_text != null && _text.GetRuneCount () > 0 && (Size.Width == 0 || Size.Height == 0 || Size.Width != _text.GetColumns ())) {
+            //	// Provide a default size (width = length of longest line, height = 1)
+            //	// TODO: It might makes more sense for the default to be width = length of first line?
+            //	Size = new Size (TextFormatter.MaxWidth (Text, int.MaxValue), 1);
+            //}
+        }
+    }
+
+    /// <summary>Controls the vertical text-alignment property.</summary>
+    /// <value>The text vertical alignment.</value>
+    public VerticalTextAlignment VerticalAlignment { get => _textVerticalAlignment; set => _textVerticalAlignment = EnableNeedsFormat (value); }
+
+    /// <summary>Gets or sets whether word wrap will be used to fit <see cref="Text"/> to <see cref="Size"/>.</summary>
+    public bool WordWrap { get => _wordWrap; set => _wordWrap = EnableNeedsFormat (value); }
+
+    /// <summary>Draws the text held by <see cref="TextFormatter"/> to <see cref="ConsoleDriver"/> using the colors specified.</summary>
+    /// <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>
+    /// <param name="containerBounds">Specifies the screen-relative location and maximum container size.</param>
+    /// <param name="fillRemaining">Determines if the bounds width will be used (default) or only the text width will be used.</param>
+    /// <param name="driver">The console driver currently used by the application.</param>
+    public void Draw (
+        Rect bounds,
+        Attribute normalColor,
+        Attribute hotColor,
+        Rect containerBounds = default,
+        bool fillRemaining = true,
+        ConsoleDriver driver = null
+    )
+    {
+        // With this check, we protect against subclasses with overrides of Text (like Button)
+        if (string.IsNullOrEmpty (_text))
+        {
+            return;
+        }
+
+        if (driver == null)
+        {
+            driver = Application.Driver;
+        }
+
+        driver?.SetAttribute (normalColor);
+
+        // Use "Lines" to ensure a Format (don't use "lines"))
+
+        List<string> linesFormated = Lines;
+
+        switch (Direction)
+        {
+            case TextDirection.TopBottom_RightLeft:
+            case TextDirection.LeftRight_BottomTop:
+            case TextDirection.RightLeft_BottomTop:
+            case TextDirection.BottomTop_RightLeft:
+                linesFormated.Reverse ();
+
+                break;
+        }
+
+        bool isVertical = IsVerticalDirection (Direction);
+        Rect maxBounds = bounds;
+
+        if (driver != null)
+        {
+            maxBounds = containerBounds == default (Rect)
+                            ? bounds
+                            : new Rect (
+                                        Math.Max (containerBounds.X, bounds.X),
+                                        Math.Max (containerBounds.Y, bounds.Y),
+                                        Math.Max (Math.Min (containerBounds.Width, containerBounds.Right - bounds.Left), 0),
+                                        Math.Max (Math.Min (containerBounds.Height, containerBounds.Bottom - bounds.Top), 0));
+        }
+
+        if ((maxBounds.Width == 0) || (maxBounds.Height == 0))
+        {
+            return;
+        }
+
+        // BUGBUG: v2 - TextFormatter should not change the clip region. If a caller wants to break out of the clip region it should do
+        // so explicitly.
+        //var savedClip = Application.Driver?.Clip;
+        //if (Application.Driver != null) {
+        //	Application.Driver.Clip = maxBounds;
+        //}
+        int lineOffset = !isVertical && bounds.Y < 0 ? Math.Abs (bounds.Y) : 0;
+
+        for (int line = lineOffset; line < linesFormated.Count; line++)
+        {
+            if ((isVertical && line > bounds.Width) || (!isVertical && line > bounds.Height))
+            {
+                continue;
+            }
+
+            if ((isVertical && line >= maxBounds.Left + maxBounds.Width)
+                || (!isVertical && line >= maxBounds.Top + maxBounds.Height + lineOffset))
+
+            {
+                break;
+            }
+
+            Rune [] runes = _lines [line].ToRunes ();
+
+            switch (Direction)
+            {
+                case TextDirection.RightLeft_BottomTop:
+                case TextDirection.RightLeft_TopBottom:
+                case TextDirection.BottomTop_LeftRight:
+                case TextDirection.BottomTop_RightLeft:
+                    runes = runes.Reverse ().ToArray ();
+
+                    break;
+            }
+
+            // When text is justified, we lost left or right, so we use the direction to align. 
+
+            int x, y;
+
+            // Horizontal Alignment
+            if ((_textAlignment == TextAlignment.Right) || (_textAlignment == TextAlignment.Justified && !IsLeftToRight (Direction)))
+            {
+                if (isVertical)
+                {
+                    int runesWidth = GetSumMaxCharWidth (Lines, line, TabWidth);
+                    x = bounds.Right - runesWidth;
+                    CursorPosition = bounds.Width - runesWidth + (_hotKeyPos > -1 ? _hotKeyPos : 0);
+                }
+                else
+                {
+                    int runesWidth = StringExtensions.ToString (runes).GetColumns ();
+                    x = bounds.Right - runesWidth;
+                    CursorPosition = bounds.Width - runesWidth + (_hotKeyPos > -1 ? _hotKeyPos : 0);
+                }
+            }
+            else if ((_textAlignment == TextAlignment.Left) || (_textAlignment == TextAlignment.Justified))
+            {
+                if (isVertical)
+                {
+                    int runesWidth = line > 0 ? GetSumMaxCharWidth (Lines, 0, line, TabWidth) : 0;
+                    x = bounds.Left + runesWidth;
+                }
+                else
+                {
+                    x = bounds.Left;
+                }
+
+                CursorPosition = _hotKeyPos > -1 ? _hotKeyPos : 0;
+            }
+            else if (_textAlignment == TextAlignment.Centered)
+            {
+                if (isVertical)
+                {
+                    int runesWidth = GetSumMaxCharWidth (Lines, line, TabWidth);
+                    x = bounds.Left + line + (bounds.Width - runesWidth) / 2;
+                    CursorPosition = (bounds.Width - runesWidth) / 2 + (_hotKeyPos > -1 ? _hotKeyPos : 0);
+                }
+                else
+                {
+                    int runesWidth = StringExtensions.ToString (runes).GetColumns ();
+                    x = bounds.Left + (bounds.Width - runesWidth) / 2;
+                    CursorPosition = (bounds.Width - runesWidth) / 2 + (_hotKeyPos > -1 ? _hotKeyPos : 0);
+                }
+            }
+            else
+            {
+                throw new ArgumentOutOfRangeException ();
+            }
+
+            // Vertical Alignment
+            if ((_textVerticalAlignment == VerticalTextAlignment.Bottom)
+                || (_textVerticalAlignment == VerticalTextAlignment.Justified && !IsTopToBottom (Direction)))
+            {
+                if (isVertical)
+                {
+                    y = bounds.Bottom - runes.Length;
+                }
+                else
+                {
+                    y = bounds.Bottom - Lines.Count + line;
+                }
+            }
+            else if ((_textVerticalAlignment == VerticalTextAlignment.Top) || (_textVerticalAlignment == VerticalTextAlignment.Justified))
+            {
+                if (isVertical)
+                {
+                    y = bounds.Top;
+                }
+                else
+                {
+                    y = bounds.Top + line;
+                }
+            }
+            else if (_textVerticalAlignment == VerticalTextAlignment.Middle)
+            {
+                if (isVertical)
+                {
+                    int s = (bounds.Height - runes.Length) / 2;
+                    y = bounds.Top + s;
+                }
+                else
+                {
+                    int s = (bounds.Height - Lines.Count) / 2;
+                    y = bounds.Top + line + s;
+                }
+            }
+            else
+            {
+                throw new ArgumentOutOfRangeException ();
+            }
+
+            int colOffset = bounds.X < 0 ? Math.Abs (bounds.X) : 0;
+            int start = isVertical ? bounds.Top : bounds.Left;
+            int size = isVertical ? bounds.Height : bounds.Width;
+            int current = start + colOffset;
+            List<Point?> lastZeroWidthPos = null;
+            Rune rune = default;
+            Rune lastRuneUsed;
+            int zeroLengthCount = isVertical ? runes.Sum (r => r.GetColumns () == 0 ? 1 : 0) : 0;
+
+            for (int idx = (isVertical ? start - y : start - x) + colOffset; current < start + size + zeroLengthCount; idx++)
+            {
+                lastRuneUsed = rune;
+
+                if (lastZeroWidthPos == null)
+                {
+                    if ((idx < 0) || (x + current + colOffset < 0))
+                    {
+                        current++;
+
+                        continue;
+                    }
+
+                    if (!fillRemaining && idx > runes.Length - 1)
+                    {
+                        break;
+                    }
+
+                    if ((!isVertical && current - start > maxBounds.Left + maxBounds.Width - bounds.X + colOffset)
+                        || (isVertical && idx > maxBounds.Top + maxBounds.Height - bounds.Y))
+                    {
+                        break;
+                    }
+                }
+
+                //if ((!isVertical && idx > maxBounds.Left + maxBounds.Width - bounds.X + colOffset)
+                //	|| (isVertical && idx > maxBounds.Top + maxBounds.Height - bounds.Y))
+
+                //	break;
+
+                rune = (Rune)' ';
+
+                if (isVertical)
+                {
+                    if (idx >= 0 && idx < runes.Length)
+                    {
+                        rune = runes [idx];
+                    }
+
+                    if (lastZeroWidthPos == null)
+                    {
+                        driver?.Move (x, current);
+                    }
+                    else
+                    {
+                        int foundIdx = lastZeroWidthPos.IndexOf (p => p.Value.Y == current);
+
+                        if (foundIdx > -1)
+                        {
+                            if (rune.IsCombiningMark ())
+                            {
+                                lastZeroWidthPos [foundIdx] = new Point (lastZeroWidthPos [foundIdx].Value.X + 1, current);
+
+                                driver?.Move (lastZeroWidthPos [foundIdx].Value.X, current);
+                            }
+                            else if (!rune.IsCombiningMark () && lastRuneUsed.IsCombiningMark ())
+                            {
+                                current++;
+                                driver?.Move (x, current);
+                            }
+                            else
+                            {
+                                driver?.Move (x, current);
+                            }
+                        }
+                        else
+                        {
+                            driver?.Move (x, current);
+                        }
+                    }
+                }
+                else
+                {
+                    driver?.Move (current, y);
+
+                    if (idx >= 0 && idx < runes.Length)
+                    {
+                        rune = runes [idx];
+                    }
+                }
+
+                int runeWidth = GetRuneWidth (rune, TabWidth);
+
+                if (HotKeyPos > -1 && idx == HotKeyPos)
+                {
+                    if ((isVertical && _textVerticalAlignment == VerticalTextAlignment.Justified) || (!isVertical && _textAlignment == TextAlignment.Justified))
+                    {
+                        CursorPosition = idx - start;
+                    }
+
+                    driver?.SetAttribute (hotColor);
+                    driver?.AddRune (rune);
+                    driver?.SetAttribute (normalColor);
+                }
+                else
+                {
+                    if (isVertical)
+                    {
+                        if (runeWidth == 0)
+                        {
+                            if (lastZeroWidthPos == null)
+                            {
+                                lastZeroWidthPos = new List<Point?> ();
+                            }
+
+                            int foundIdx = lastZeroWidthPos.IndexOf (p => p.Value.Y == current);
+
+                            if (foundIdx == -1)
+                            {
+                                current--;
+                                lastZeroWidthPos.Add (new Point (x + 1, current));
+                            }
+
+                            driver?.Move (x + 1, current);
+                        }
+                    }
+
+                    driver?.AddRune (rune);
+                }
+
+                if (isVertical)
+                {
+                    if (runeWidth > 0)
+                    {
+                        current++;
+                    }
+                }
+                else
+                {
+                    current += runeWidth;
+                }
+
+                int nextRuneWidth = idx + 1 > -1 && idx + 1 < runes.Length ? runes [idx + 1].GetColumns () : 0;
+
+                if (!isVertical && idx + 1 < runes.Length && current + nextRuneWidth > start + size)
+                {
+                    break;
+                }
+            }
+        }
+
+        //if (Application.Driver != null) {
+        //	Application.Driver.Clip = (Rect)savedClip;
+        //}
+    }
+
+    /// <summary>Causes the <see cref="TextFormatter"/> to reformat the text.</summary>
+    /// <returns>The formatted text.</returns>
+    public string Format ()
+    {
+        var sb = new StringBuilder ();
+
+        // Lines_get causes a Format
+        foreach (string line in Lines)
+        {
+            sb.AppendLine (line);
+        }
+
+        return sb.ToString ().TrimEnd (Environment.NewLine.ToCharArray ());
+    }
+
+    /// <summary>Gets the size required to hold the formatted text, given the constraints placed by <see cref="Size"/>.</summary>
+    /// <remarks>Causes a format, resetting <see cref="NeedsFormat"/>.</remarks>
+    /// <returns></returns>
+    public Size GetFormattedSize ()
+    {
+        List<string> lines = Lines;
+
+        if (Lines.Count > 0)
+        {
+            int width = Lines.Max (line => line.GetColumns ());
+            int height = Lines.Count;
+
+            return new Size (width, height);
+        }
+
+        return Size.Empty;
+    }
+
+    /// <summary>Event invoked when the <see cref="HotKey"/> is changed.</summary>
+    public event EventHandler<KeyChangedEventArgs> HotKeyChanged;
+
+    /// <summary>Check if it is a horizontal direction</summary>
+    public static bool IsHorizontalDirection (TextDirection textDirection)
+    {
+        switch (textDirection)
+        {
+            case TextDirection.LeftRight_TopBottom:
+            case TextDirection.LeftRight_BottomTop:
+            case TextDirection.RightLeft_TopBottom:
+            case TextDirection.RightLeft_BottomTop:
+                return true;
+            default:
+                return false;
+        }
+    }
+
+    /// <summary>Check if it is Left to Right direction</summary>
+    public static bool IsLeftToRight (TextDirection textDirection)
+    {
+        switch (textDirection)
+        {
+            case TextDirection.LeftRight_TopBottom:
+            case TextDirection.LeftRight_BottomTop:
+                return true;
+            default:
+                return false;
+        }
+    }
+
+    /// <summary>Check if it is Top to Bottom direction</summary>
+    public static bool IsTopToBottom (TextDirection textDirection)
+    {
+        switch (textDirection)
+        {
+            case TextDirection.TopBottom_LeftRight:
+            case TextDirection.TopBottom_RightLeft:
+                return true;
+            default:
+                return false;
+        }
+    }
+
+    /// <summary>Check if it is a vertical direction</summary>
+    public static bool IsVerticalDirection (TextDirection textDirection)
+    {
+        switch (textDirection)
+        {
+            case TextDirection.TopBottom_LeftRight:
+            case TextDirection.TopBottom_RightLeft:
+            case TextDirection.BottomTop_LeftRight:
+            case TextDirection.BottomTop_RightLeft:
+                return true;
+            default:
+                return false;
+        }
+    }
+
+    private T EnableNeedsFormat<T> (T value)
+    {
+        NeedsFormat = true;
+
+        return value;
+    }
+
+    #region Static Members
+
+    private static string StripCRLF (string str, bool keepNewLine = false)
+    {
+        List<Rune> runes = str.ToRuneList ();
+
+        for (var i = 0; i < runes.Count; i++)
+        {
+            switch ((char)runes [i].Value)
+            {
+                case '\n':
+                    if (!keepNewLine)
+                    {
+                        runes.RemoveAt (i);
+                    }
+
+                    break;
+
+                case '\r':
+                    if (i + 1 < runes.Count && runes [i + 1].Value == '\n')
+                    {
+                        runes.RemoveAt (i);
+
+                        if (!keepNewLine)
+                        {
+                            runes.RemoveAt (i);
+                        }
+
+                        i++;
+                    }
+                    else
+                    {
+                        if (!keepNewLine)
+                        {
+                            runes.RemoveAt (i);
+                        }
+                    }
+
+                    break;
+            }
+        }
+
+        return StringExtensions.ToString (runes);
+    }
+
+    private static string ReplaceCRLFWithSpace (string str)
+    {
+        List<Rune> runes = str.ToRuneList ();
+
+        for (var i = 0; i < runes.Count; i++)
+        {
+            switch (runes [i].Value)
+            {
+                case '\n':
+                    runes [i] = (Rune)' ';
+
+                    break;
+
+                case '\r':
+                    if (i + 1 < runes.Count && runes [i + 1].Value == '\n')
+                    {
+                        runes [i] = (Rune)' ';
+                        runes.RemoveAt (i + 1);
+                        i++;
+                    }
+                    else
+                    {
+                        runes [i] = (Rune)' ';
+                    }
+
+                    break;
+            }
+        }
+
+        return StringExtensions.ToString (runes);
+    }
+
+    private static string ReplaceTABWithSpaces (string str, int tabWidth)
+    {
+        if (tabWidth == 0)
+        {
+            return str.Replace ("\t", "");
+        }
+
+        return str.Replace ("\t", new string (' ', tabWidth));
+    }
+
+    /// <summary>
+    ///     Splits all newlines in the <paramref name="text"/> into a list and supports both CRLF and LF, preserving the ending
+    ///     newline.
+    /// </summary>
+    /// <param name="text">The text.</param>
+    /// <returns>A list of text without the newline characters.</returns>
+    public static List<string> SplitNewLine (string text)
+    {
+        List<Rune> runes = text.ToRuneList ();
+        List<string> lines = new ();
+        var start = 0;
+        var end = 0;
+
+        for (var i = 0; i < runes.Count; i++)
+        {
+            end = i;
+
+            switch (runes [i].Value)
+            {
+                case '\n':
+                    lines.Add (StringExtensions.ToString (runes.GetRange (start, end - start)));
+                    i++;
+                    start = i;
+
+                    break;
+
+                case '\r':
+                    if (i + 1 < runes.Count && runes [i + 1].Value == '\n')
+                    {
+                        lines.Add (StringExtensions.ToString (runes.GetRange (start, end - start)));
+                        i += 2;
+                        start = i;
+                    }
+                    else
+                    {
+                        lines.Add (StringExtensions.ToString (runes.GetRange (start, end - start)));
+                        i++;
+                        start = i;
+                    }
+
+                    break;
+            }
+        }
+
+        if (runes.Count > 0 && lines.Count == 0)
+        {
+            lines.Add (StringExtensions.ToString (runes));
+        }
+        else if (runes.Count > 0 && start < runes.Count)
+        {
+            lines.Add (StringExtensions.ToString (runes.GetRange (start, runes.Count - start)));
+        }
+        else
+        {
+            lines.Add ("");
+        }
+
+        return lines;
+    }
+
+    /// <summary>
+    ///     Adds trailing whitespace or truncates <paramref name="text"/> so that it fits exactly <paramref name="width"/>
+    ///     console units. Note that some unicode characters take 2+ columns
+    /// </summary>
+    /// <param name="text"></param>
+    /// <param name="width"></param>
+    /// <returns></returns>
+    public static string ClipOrPad (string text, int width)
+    {
+        if (string.IsNullOrEmpty (text))
+        {
+            return text;
+        }
+
+        // if value is not wide enough
+        if (text.EnumerateRunes ().Sum (c => c.GetColumns ()) < width)
+        {
+            // pad it out with spaces to the given alignment
+            int toPad = width - text.EnumerateRunes ().Sum (c => c.GetColumns ());
+
+            return text + new string (' ', toPad);
+        }
+
+        // value is too wide
+        return new string (text.TakeWhile (c => (width -= ((Rune)c).GetColumns ()) >= 0).ToArray ());
+    }
+
+    /// <summary>Formats the provided text to fit within the width provided using word wrapping.</summary>
+    /// <param name="text">The text to word wrap</param>
+    /// <param name="width">The number of columns to constrain the text to</param>
+    /// <param name="preserveTrailingSpaces">
+    ///     If <see langword="true"/> trailing spaces at the end of wrapped lines will be preserved. If <see langword="false"/>
+    ///     , trailing spaces at the end of wrapped lines will be trimmed.
+    /// </param>
+    /// <param name="tabWidth">The number of columns used for a tab.</param>
+    /// <param name="textDirection">The text direction.</param>
+    /// <returns>A list of word wrapped lines.</returns>
+    /// <remarks>
+    ///     <para>This method does not do any justification.</para>
+    ///     <para>This method strips Newline ('\n' and '\r\n') sequences before processing.</para>
+    ///     <para>
+    ///         If <paramref name="preserveTrailingSpaces"/> is <see langword="false"/> at most one space will be preserved at
+    ///         the end of the last line.
+    ///     </para>
+    /// </remarks>
+    public static List<string> WordWrapText (
+        string text,
+        int width,
+        bool preserveTrailingSpaces = false,
+        int tabWidth = 0,
+        TextDirection textDirection = TextDirection.LeftRight_TopBottom
+    )
+    {
+        if (width < 0)
+        {
+            throw new ArgumentOutOfRangeException ("Width cannot be negative.");
+        }
+
+        int start = 0, end;
+        List<string> lines = new ();
+
+        if (string.IsNullOrEmpty (text))
+        {
+            return lines;
+        }
+
+        List<Rune> runes = StripCRLF (text).ToRuneList ();
+
+        if (preserveTrailingSpaces)
+        {
+            while ((end = start) < runes.Count)
+            {
+                end = GetNextWhiteSpace (start, width, out bool incomplete);
+
+                if (end == 0 && incomplete)
+                {
+                    start = text.GetRuneCount ();
+
+                    break;
+                }
+
+                lines.Add (StringExtensions.ToString (runes.GetRange (start, end - start)));
+                start = end;
+
+                if (incomplete)
+                {
+                    start = text.GetRuneCount ();
+
+                    break;
+                }
+            }
+        }
+        else
+        {
+            if (IsHorizontalDirection (textDirection))
+            {
+                //if (GetLengthThatFits (runes.GetRange (start, runes.Count - start), width) > 0) {
+                //	// while there's still runes left and end is not past end...
+                //	while (start < runes.Count &&
+                //		(end = start + Math.Max (GetLengthThatFits (runes.GetRange (start, runes.Count - start), width) - 1, 0)) < runes.Count) {
+                //		// end now points to start + LengthThatFits
+                //		// Walk back over trailing spaces
+                //		while (runes [end] == ' ' && end > start) {
+                //			end--;
+                //		}
+                //		// end now points to start + LengthThatFits - any trailing spaces; start saving new line
+                //		var line = runes.GetRange (start, end - start + 1);
+
+                //		if (end == start && width > 1) {
+                //			// it was all trailing spaces; now walk forward to next non-space
+                //			do {
+                //				start++;
+                //			} while (start < runes.Count && runes [start] == ' ');
+
+                //			// start now points to first non-space we haven't seen yet or we're done
+                //			if (start < runes.Count) {
+                //				// we're not done. we have remaining = width - line.Count columns left; 
+                //				var remaining = width - line.Count;
+                //				if (remaining > 1) {
+                //					// add a space for all the spaces we walked over 
+                //					line.Add (' ');
+                //				}
+                //				var count = GetLengthThatFits (runes.GetRange (start, runes.Count - start), width - line.Count);
+
+                //				// [start..count] now has rest of line
+                //				line.AddRange (runes.GetRange (start, count));
+                //				start += count;
+                //			}
+                //		} else {
+                //			start += line.Count;
+                //		}
+
+                //		//// if the previous line was just a ' ' and the new line is just a ' '
+                //		//// don't add new line
+                //		//if (line [0] == ' ' && (lines.Count > 0 && lines [lines.Count - 1] [0] == ' ')) {
+                //		//} else {
+                //		//}
+                //		lines.Add (string.Make (line));
+
+                //		// move forward to next non-space
+                //		while (width > 1 && start < runes.Count && runes [start] == ' ') {
+                //			start++;
+                //		}
+                //	}
+                //}
+
+                while ((end = start + GetLengthThatFits (runes.GetRange (start, runes.Count - start), width, tabWidth)) < runes.Count)
+                {
+                    while (runes [end].Value != ' ' && end > start)
+                    {
+                        end--;
+                    }
+
+                    if (end == start)
+                    {
+                        end = start + GetLengthThatFits (runes.GetRange (end, runes.Count - end), width, tabWidth);
+                    }
+
+                    var str = StringExtensions.ToString (runes.GetRange (start, end - start));
+
+                    if (end > start && GetRuneWidth (str, tabWidth) <= width)
+                    {
+                        lines.Add (str);
+                        start = end;
+
+                        if (runes [end].Value == ' ')
+                        {
+                            start++;
+                        }
+                    }
+                    else
+                    {
+                        end++;
+                        start = end;
+                    }
+                }
+            }
+            else
+            {
+                while ((end = start + width) < runes.Count)
+                {
+                    while (runes [end].Value != ' ' && end > start)
+                    {
+                        end--;
+                    }
+
+                    if (end == start)
+                    {
+                        end = start + width;
+                    }
+
+                    var zeroLength = 0;
+
+                    for (int i = end; i < runes.Count - start; i++)
+                    {
+                        Rune r = runes [i];
+
+                        if (r.GetColumns () == 0)
+                        {
+                            zeroLength++;
+                        }
+                        else
+                        {
+                            break;
+                        }
+                    }
+
+                    lines.Add (StringExtensions.ToString (runes.GetRange (start, end - start + zeroLength)));
+                    end += zeroLength;
+                    start = end;
+
+                    if (runes [end].Value == ' ')
+                    {
+                        start++;
+                    }
+                }
+            }
+        }
+
+        int GetNextWhiteSpace (int from, int cWidth, out bool incomplete, int cLength = 0)
+        {
+            int lastFrom = from;
+            int to = from;
+            int length = cLength;
+            incomplete = false;
+
+            while (length < cWidth && to < runes.Count)
+            {
+                Rune rune = runes [to];
+
+                if (IsHorizontalDirection (textDirection))
+                {
+                    length += rune.GetColumns ();
+                }
+                else
+                {
+                    length++;
+                }
+
+                if (length > cWidth)
+                {
+                    if ((to >= runes.Count) || (length > 1 && cWidth <= 1))
+                    {
+                        incomplete = true;
+                    }
+
+                    return to;
+                }
+
+                if (rune.Value == ' ')
+                {
+                    if (length == cWidth)
+                    {
+                        return to + 1;
+                    }
+
+                    if (length > cWidth)
+                    {
+                        return to;
+                    }
+
+                    return GetNextWhiteSpace (to + 1, cWidth, out incomplete, length);
+                }
+
+                if (rune.Value == '\t')
+                {
+                    length += tabWidth + 1;
+
+                    if (length == tabWidth && tabWidth > cWidth)
+                    {
+                        return to + 1;
+                    }
+
+                    if (length > cWidth && tabWidth > cWidth)
+                    {
+                        return to;
+                    }
+
+                    return GetNextWhiteSpace (to + 1, cWidth, out incomplete, length);
+                }
+
+                to++;
+            }
+
+            if (cLength > 0 && to < runes.Count && runes [to].Value != ' ' && runes [to].Value != '\t')
+            {
+                return from;
+            }
+
+            if (cLength > 0 && to < runes.Count && ((runes [to].Value == ' ') || (runes [to].Value == '\t')))
+            {
+                return lastFrom;
+            }
+
+            return to;
+        }
+
+        if (start < text.GetRuneCount ())
+        {
+            string str = ReplaceTABWithSpaces (StringExtensions.ToString (runes.GetRange (start, runes.Count - start)), tabWidth);
+
+            if (IsVerticalDirection (textDirection) || preserveTrailingSpaces || (!preserveTrailingSpaces && str.GetColumns () <= width))
+            {
+                lines.Add (str);
+            }
+        }
+
+        return lines;
+    }
+
+    /// <summary>Justifies text within a specified width.</summary>
+    /// <param name="text">The text to justify.</param>
+    /// <param name="width">
+    ///     The number of columns to clip the text to. Text longer than <paramref name="width"/> will be
+    ///     clipped.
+    /// </param>
+    /// <param name="talign">Alignment.</param>
+    /// <param name="textDirection">The text direction.</param>
+    /// <param name="tabWidth">The number of columns used for a tab.</param>
+    /// <returns>Justified and clipped text.</returns>
+    public static string ClipAndJustify (
+        string text,
+        int width,
+        TextAlignment talign,
+        TextDirection textDirection = TextDirection.LeftRight_TopBottom,
+        int tabWidth = 0
+    )
+    {
+        return ClipAndJustify (text, width, talign == TextAlignment.Justified, textDirection, tabWidth);
+    }
+
+    /// <summary>Justifies text within a specified width.</summary>
+    /// <param name="text">The text to justify.</param>
+    /// <param name="width">
+    ///     The number of columns to clip the text to. Text longer than <paramref name="width"/> will be
+    ///     clipped.
+    /// </param>
+    /// <param name="justify">Justify.</param>
+    /// <param name="textDirection">The text direction.</param>
+    /// <param name="tabWidth">The number of columns used for a tab.</param>
+    /// <returns>Justified and clipped text.</returns>
+    public static string ClipAndJustify (
+        string text,
+        int width,
+        bool justify,
+        TextDirection textDirection = TextDirection.LeftRight_TopBottom,
+        int tabWidth = 0
+    )
+    {
+        if (width < 0)
+        {
+            throw new ArgumentOutOfRangeException ("Width cannot be negative.");
+        }
+
+        if (string.IsNullOrEmpty (text))
+        {
+            return text;
+        }
+
+        text = ReplaceTABWithSpaces (text, tabWidth);
+        List<Rune> runes = text.ToRuneList ();
+        int slen = runes.Count;
+
+        if (slen > width)
+        {
+            if (IsHorizontalDirection (textDirection))
+            {
+                return StringExtensions.ToString (runes.GetRange (0, GetLengthThatFits (text, width, tabWidth)));
+            }
+
+            int zeroLength = runes.Sum (r => r.GetColumns () == 0 ? 1 : 0);
+
+            return StringExtensions.ToString (runes.GetRange (0, width + zeroLength));
+        }
+
+        if (justify)
+        {
+            return Justify (text, width, ' ', textDirection, tabWidth);
+        }
+
+        if (IsHorizontalDirection (textDirection) && GetRuneWidth (text, tabWidth) > width)
+        {
+            return StringExtensions.ToString (runes.GetRange (0, GetLengthThatFits (text, width, tabWidth)));
+        }
+
+        return text;
+    }
+
+    /// <summary>
+    ///     Justifies the text to fill the width provided. Space will be added between words (demarked by spaces and tabs) to
+    ///     make the text just fit <c>width</c>. Spaces will not be added to the ends.
+    /// </summary>
+    /// <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>
+    /// <param name="tabWidth">The number of columns used for a tab.</param>
+    /// <returns>The justified text.</returns>
+    public static string Justify (
+        string text,
+        int width,
+        char spaceChar = ' ',
+        TextDirection textDirection = TextDirection.LeftRight_TopBottom,
+        int tabWidth = 0
+    )
+    {
+        if (width < 0)
+        {
+            throw new ArgumentOutOfRangeException ("Width cannot be negative.");
+        }
+
+        if (string.IsNullOrEmpty (text))
+        {
+            return text;
+        }
+
+        text = ReplaceTABWithSpaces (text, tabWidth);
+        string [] words = text.Split (' ');
+        int textCount;
+
+        if (IsHorizontalDirection (textDirection))
+        {
+            textCount = words.Sum (arg => GetRuneWidth (arg, tabWidth));
+        }
+        else
+        {
+            textCount = words.Sum (arg => arg.GetRuneCount ());
+        }
+
+        int spaces = words.Length > 1 ? (width - textCount) / (words.Length - 1) : 0;
+        int extras = words.Length > 1 ? (width - textCount) % (words.Length - 1) : 0;
+
+        var s = new StringBuilder ();
+
+        for (var w = 0; w < words.Length; w++)
+        {
+            string x = words [w];
+            s.Append (x);
+
+            if (w + 1 < words.Length)
+            {
+                for (var i = 0; i < spaces; i++)
+                {
+                    s.Append (spaceChar);
+                }
+            }
+
+            if (extras > 0)
+            {
+                for (var i = 0; i < 1; i++)
+                {
+                    s.Append (spaceChar);
+                }
+
+                extras--;
+            }
+
+            if (w + 1 == words.Length - 1)
+            {
+                for (var i = 0; i < extras; i++)
+                {
+                    s.Append (spaceChar);
+                }
+            }
+        }
+
+        return s.ToString ();
+    }
+
+    //static char [] whitespace = new char [] { ' ', '\t' };
+
+    /// <summary>
+    ///     Reformats text into lines, applying text alignment and optionally wrapping text to new lines on word
+    ///     boundaries.
+    /// </summary>
+    /// <param name="text"></param>
+    /// <param name="width">The number of columns to constrain the text to for word wrapping and clipping.</param>
+    /// <param name="talign">Specifies how the text will be aligned horizontally.</param>
+    /// <param name="wordWrap">
+    ///     If <see langword="true"/>, the text will be wrapped to new lines no longer than <paramref name="width"/>. If
+    ///     <see langword="false"/>, forces text to fit a single line. Line breaks are converted to spaces. The text will be
+    ///     clipped to <paramref name="width"/>.
+    /// </param>
+    /// <param name="preserveTrailingSpaces">
+    ///     If <see langword="true"/> trailing spaces at the end of wrapped lines will be preserved. If <see langword="false"/>
+    ///     , trailing spaces at the end of wrapped lines will be trimmed.
+    /// </param>
+    /// <param name="tabWidth">The number of columns used for a tab.</param>
+    /// <param name="textDirection">The text direction.</param>
+    /// <param name="multiLine">If <see langword="true"/> new lines are allowed.</param>
+    /// <returns>A list of word wrapped lines.</returns>
+    /// <remarks>
+    ///     <para>An empty <paramref name="text"/> string will result in one empty line.</para>
+    ///     <para>If <paramref name="width"/> is 0, a single, empty line will be returned.</para>
+    ///     <para>If <paramref name="width"/> is int.MaxValue, the text will be formatted to the maximum width possible.</para>
+    /// </remarks>
+    public static List<string> Format (
+        string text,
+        int width,
+        TextAlignment talign,
+        bool wordWrap,
+        bool preserveTrailingSpaces = false,
+        int tabWidth = 0,
+        TextDirection textDirection = TextDirection.LeftRight_TopBottom,
+        bool multiLine = false
+    )
+    {
+        return Format (text, width, talign == TextAlignment.Justified, wordWrap, preserveTrailingSpaces, tabWidth, textDirection, multiLine);
+    }
+
+    /// <summary>
+    ///     Reformats text into lines, applying text alignment and optionally wrapping text to new lines on word
+    ///     boundaries.
+    /// </summary>
+    /// <param name="text"></param>
+    /// <param name="width">The number of columns to constrain the text to for word wrapping and clipping.</param>
+    /// <param name="justify">Specifies whether the text should be justified.</param>
+    /// <param name="wordWrap">
+    ///     If <see langword="true"/>, the text will be wrapped to new lines no longer than <paramref name="width"/>. If
+    ///     <see langword="false"/>, forces text to fit a single line. Line breaks are converted to spaces. The text will be
+    ///     clipped to <paramref name="width"/>.
+    /// </param>
+    /// <param name="preserveTrailingSpaces">
+    ///     If <see langword="true"/> trailing spaces at the end of wrapped lines will be preserved. If <see langword="false"/>
+    ///     , trailing spaces at the end of wrapped lines will be trimmed.
+    /// </param>
+    /// <param name="tabWidth">The number of columns used for a tab.</param>
+    /// <param name="textDirection">The text direction.</param>
+    /// <param name="multiLine">If <see langword="true"/> new lines are allowed.</param>
+    /// <returns>A list of word wrapped lines.</returns>
+    /// <remarks>
+    ///     <para>An empty <paramref name="text"/> string will result in one empty line.</para>
+    ///     <para>If <paramref name="width"/> is 0, a single, empty line will be returned.</para>
+    ///     <para>If <paramref name="width"/> is int.MaxValue, the text will be formatted to the maximum width possible.</para>
+    /// </remarks>
+    public static List<string> Format (
+        string text,
+        int width,
+        bool justify,
+        bool wordWrap,
+        bool preserveTrailingSpaces = false,
+        int tabWidth = 0,
+        TextDirection textDirection = TextDirection.LeftRight_TopBottom,
+        bool multiLine = false
+    )
+    {
+        if (width < 0)
+        {
+            throw new ArgumentOutOfRangeException ("width cannot be negative");
+        }
+
+        List<string> lineResult = new ();
+
+        if (string.IsNullOrEmpty (text) || (width == 0))
+        {
+            lineResult.Add (string.Empty);
+
+            return lineResult;
+        }
+
+        if (!wordWrap)
+        {
+            text = ReplaceTABWithSpaces (text, tabWidth);
+
+            if (multiLine)
+            {
+                string [] lines = null;
+
+                if (text.Contains ("\r\n"))
+                {
+                    lines = text.Split ("\r\n");
+                }
+                else if (text.Contains ('\n'))
+                {
+                    lines = text.Split ('\n');
+                }
+
+                if (lines == null)
+                {
+                    lines = new [] { text };
+                }
+
+                foreach (string line in lines)
+                {
+                    lineResult.Add (ClipAndJustify (line, width, justify, textDirection, tabWidth));
+                }
+
+                return lineResult;
+            }
+
+            text = ReplaceCRLFWithSpace (text);
+            lineResult.Add (ClipAndJustify (text, width, justify, textDirection, tabWidth));
+
+            return lineResult;
+        }
+
+        List<Rune> runes = StripCRLF (text, true).ToRuneList ();
+        int runeCount = runes.Count;
+        var lp = 0;
+
+        for (var i = 0; i < runeCount; i++)
+        {
+            Rune c = runes [i];
+
+            if (c.Value == '\n')
+            {
+                List<string> wrappedLines = WordWrapText (
+                                                          StringExtensions.ToString (runes.GetRange (lp, i - lp)),
+                                                          width,
+                                                          preserveTrailingSpaces,
+                                                          tabWidth,
+                                                          textDirection);
+
+                foreach (string line in wrappedLines)
+                {
+                    lineResult.Add (ClipAndJustify (line, width, justify, textDirection, tabWidth));
+                }
+
+                if (wrappedLines.Count == 0)
+                {
+                    lineResult.Add (string.Empty);
+                }
+
+                lp = i + 1;
+            }
+        }
+
+        foreach (string line in WordWrapText (
+                                              StringExtensions.ToString (runes.GetRange (lp, runeCount - lp)),
+                                              width,
+                                              preserveTrailingSpaces,
+                                              tabWidth,
+                                              textDirection))
+        {
+            lineResult.Add (ClipAndJustify (line, width, justify, textDirection, tabWidth));
+        }
+
+        return lineResult;
+    }
+
+    /// <summary>Computes the number of lines needed to render the specified text given the width.</summary>
+    /// <returns>Number of lines.</returns>
+    /// <param name="text">Text, may contain newlines.</param>
+    /// <param name="width">The minimum width for the text.</param>
+    public static int MaxLines (string text, int width)
+    {
+        List<string> result = Format (text, width, false, true);
+
+        return result.Count;
+    }
+
+    /// <summary>
+    ///     Computes the maximum width needed to render the text (single line or multiple lines, word wrapped) given a number
+    ///     of columns to constrain the text to.
+    /// </summary>
+    /// <returns>Width of the longest line after formatting the text constrained by <paramref name="maxColumns"/>.</returns>
+    /// <param name="text">Text, may contain newlines.</param>
+    /// <param name="maxColumns">The number of columns to constrain the text to for formatting.</param>
+    /// <param name="tabWidth">The number of columns used for a tab.</param>
+    public static int MaxWidth (string text, int maxColumns, int tabWidth = 0)
+    {
+        List<string> result = Format (text, maxColumns, false, true);
+        var max = 0;
+
+        result.ForEach (
+                        s =>
+                        {
+                            var m = 0;
+                            s.ToRuneList ().ForEach (r => m += GetRuneWidth (r, tabWidth));
+
+                            if (m > max)
+                            {
+                                max = m;
+                            }
+                        });
+
+        return max;
+    }
+
+    /// <summary>
+    ///     Returns the width of the widest line in the text, accounting for wide-glyphs (uses
+    ///     <see cref="StringExtensions.GetColumns"/>). <paramref name="text"/> if it contains newlines.
+    /// </summary>
+    /// <param name="text">Text, may contain newlines.</param>
+    /// <param name="tabWidth">The number of columns used for a tab.</param>
+    /// <returns>The length of the longest line.</returns>
+    public static int MaxWidthLine (string text, int tabWidth = 0)
+    {
+        List<string> result = SplitNewLine (text);
+
+        return result.Max (x => GetRuneWidth (x, tabWidth));
+    }
+
+    /// <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>
+    /// <param name="tabWidth">The number of columns used for a tab.</param>
+    /// <returns>The maximum characters width.</returns>
+    public static int GetSumMaxCharWidth (List<string> lines, int startIndex = -1, int length = -1, int tabWidth = 0)
+    {
+        var max = 0;
+
+        for (int i = startIndex == -1 ? 0 : startIndex; i < (length == -1 ? lines.Count : startIndex + length); i++)
+        {
+            string runes = lines [i];
+
+            if (runes.Length > 0)
+            {
+                max += runes.EnumerateRunes ().Max (r => GetRuneWidth (r, tabWidth));
+            }
+        }
+
+        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>
+    /// <param name="tabWidth">The number of columns used for a tab.</param>
+    /// <returns>The maximum characters width.</returns>
+    public static int GetSumMaxCharWidth (string text, int startIndex = -1, int length = -1, int tabWidth = 0)
+    {
+        var max = 0;
+        Rune [] runes = text.ToRunes ();
+
+        for (int i = startIndex == -1 ? 0 : startIndex; i < (length == -1 ? runes.Length : startIndex + length); i++)
+        {
+            max += GetRuneWidth (runes [i], tabWidth);
+        }
+
+        return max;
+    }
+
+    /// <summary>Gets the number of the Runes in a <see cref="string"/> that will fit in <paramref name="columns"/>.</summary>
+    /// <param name="text">The text.</param>
+    /// <param name="columns">The width.</param>
+    /// <param name="tabWidth">The number of columns used for a tab.</param>
+    /// <returns>The index of the text that fit the width.</returns>
+    public static int GetLengthThatFits (string text, int columns, int tabWidth = 0) { return GetLengthThatFits (text?.ToRuneList (), columns, tabWidth); }
+
+    /// <summary>Gets the number of the Runes in a list of Runes that will fit in <paramref name="columns"/>.</summary>
+    /// <param name="runes">The list of runes.</param>
+    /// <param name="columns">The width.</param>
+    /// <param name="tabWidth">The number of columns used for a tab.</param>
+    /// <returns>The index of the last Rune in <paramref name="runes"/> that fit in <paramref name="columns"/>.</returns>
+    public static int GetLengthThatFits (List<Rune> runes, int columns, int tabWidth = 0)
+    {
+        if ((runes == null) || (runes.Count == 0))
+        {
+            return 0;
+        }
+
+        var runesLength = 0;
+        var runeIdx = 0;
+
+        for (; runeIdx < runes.Count; runeIdx++)
+        {
+            int runeWidth = GetRuneWidth (runes [runeIdx], tabWidth);
+
+            if (runesLength + runeWidth > columns)
+            {
+                break;
+            }
+
+            runesLength += runeWidth;
+        }
+
+        return runeIdx;
+    }
+
+    private static int GetRuneWidth (string str, int tabWidth) { return GetRuneWidth (str.EnumerateRunes ().ToList (), tabWidth); }
+
+    private static int GetRuneWidth (List<Rune> runes, int tabWidth) { return runes.Sum (r => GetRuneWidth (r, tabWidth)); }
+
+    private static int GetRuneWidth (Rune rune, int tabWidth)
+    {
+        int runeWidth = rune.GetColumns ();
+
+        if (rune.Value == '\t')
+        {
+            return tabWidth;
+        }
+
+        if ((runeWidth < 0) || (runeWidth > 0))
+        {
+            return Math.Max (runeWidth, 1);
+        }
+
+        return runeWidth;
+    }
+
+    /// <summary>Gets the index position from the list based on the <paramref name="width"/>.</summary>
+    /// <param name="lines">The lines.</param>
+    /// <param name="width">The width.</param>
+    /// <param name="tabWidth">The number of columns used for a tab.</param>
+    /// <returns>The index of the list that fit the width.</returns>
+    public static int GetMaxColsForWidth (List<string> lines, int width, int tabWidth = 0)
+    {
+        var runesLength = 0;
+        var lineIdx = 0;
+
+        for (; lineIdx < lines.Count; lineIdx++)
+        {
+            List<Rune> runes = lines [lineIdx].ToRuneList ();
+
+            int maxRruneWidth = runes.Count > 0
+                                    ? runes.Max (r => GetRuneWidth (r, tabWidth))
+                                    : 1;
+
+            if (runesLength + maxRruneWidth > width)
+            {
+                break;
+            }
+
+            runesLength += maxRruneWidth;
+        }
+
+        return lineIdx;
+    }
+
+    /// <summary>Calculates the rectangle required to hold a formatted string of text.</summary>
+    /// <param name="x">The x location of the rectangle</param>
+    /// <param name="y">The y location of the rectangle</param>
+    /// <param name="text">The text to measure</param>
+    /// <param name="direction">The text direction.</param>
+    /// <param name="tabWidth">The number of columns used for a tab.</param>
+    /// <returns></returns>
+    public static Rect CalcRect (int x, int y, string text, TextDirection direction = TextDirection.LeftRight_TopBottom, int tabWidth = 0)
+    {
+        if (string.IsNullOrEmpty (text))
+        {
+            return new Rect (new Point (x, y), Size.Empty);
+        }
+
+        int w, h;
+
+        if (IsHorizontalDirection (direction))
+        {
+            var mw = 0;
+            var ml = 1;
+
+            var cols = 0;
+
+            foreach (Rune rune in text.EnumerateRunes ())
+            {
+                if (rune.Value == '\n')
+                {
+                    ml++;
+
+                    if (cols > mw)
+                    {
+                        mw = cols;
+                    }
+
+                    cols = 0;
+                }
+                else if (rune.Value != '\r')
+                {
+                    cols++;
+                    var rw = 0;
+
+                    if (rune.Value == '\t')
+                    {
+                        rw += tabWidth - 1;
+                    }
+                    else
+                    {
+                        rw = rune.GetColumns ();
+
+                        if (rw > 0)
+                        {
+                            rw--;
+                        }
+                        else if (rw == 0)
+                        {
+                            cols--;
+                        }
+                    }
+
+                    cols += rw;
+                }
+            }
+
+            if (cols > mw)
+            {
+                mw = cols;
+            }
+
+            w = mw;
+            h = ml;
+        }
+        else
+        {
+            int vw = 1, cw = 1;
+            var vh = 0;
+
+            var rows = 0;
+
+            foreach (Rune rune in text.EnumerateRunes ())
+            {
+                if (rune.Value == '\n')
+                {
+                    vw++;
+
+                    if (rows > vh)
+                    {
+                        vh = rows;
+                    }
+
+                    rows = 0;
+                    cw = 1;
+                }
+                else if (rune.Value != '\r')
+                {
+                    rows++;
+                    var rw = 0;
+
+                    if (rune.Value == '\t')
+                    {
+                        rw += tabWidth - 1;
+                        rows += rw;
+                    }
+                    else
+                    {
+                        rw = rune.GetColumns ();
+
+                        if (rw == 0)
+                        {
+                            rows--;
+                        }
+                        else if (cw < rw)
+                        {
+                            cw = rw;
+                            vw++;
+                        }
+                    }
+                }
+            }
+
+            if (rows > vh)
+            {
+                vh = rows;
+            }
+
+            w = vw;
+            h = vh;
+        }
+
+        return new Rect (x, y, w, h);
+    }
+
+    /// <summary>Finds the HotKey and its location in text.</summary>
+    /// <param name="text">The text to look in.</param>
+    /// <param name="hotKeySpecifier">The HotKey specifier (e.g. '_') to look for.</param>
+    /// <param name="hotPos">Outputs the Rune index into <c>text</c>.</param>
+    /// <param name="hotKey">Outputs the hotKey. <see cref="Key.Empty"/> if not found.</param>
+    /// <param name="firstUpperCase">
+    ///     If <c>true</c> the legacy behavior of identifying the first upper case character as the HotKey will be enabled.
+    ///     Regardless of the value of this parameter, <c>hotKeySpecifier</c> takes precedence. Defaults to
+    ///     <see langword="false"/>.
+    /// </param>
+    /// <returns><c>true</c> if a HotKey was found; <c>false</c> otherwise.</returns>
+    public static bool FindHotKey (string text, Rune hotKeySpecifier, out int hotPos, out Key hotKey, bool firstUpperCase = false)
+    {
+        if (string.IsNullOrEmpty (text) || (hotKeySpecifier == (Rune)0xFFFF))
+        {
+            hotPos = -1;
+            hotKey = KeyCode.Null;
+
+            return false;
+        }
+
+        var hot_key = (Rune)0;
+        int hot_pos = -1;
+
+        // Use first hot_key char passed into 'hotKey'.
+        // TODO: Ignore hot_key of two are provided
+        // TODO: Do not support non-alphanumeric chars that can't be typed
+        var i = 0;
+
+        foreach (Rune c in text.EnumerateRunes ())
+        {
+            if ((char)c.Value != 0xFFFD)
+            {
+                if (c == hotKeySpecifier)
+                {
+                    hot_pos = i;
+                }
+                else if (hot_pos > -1)
+                {
+                    hot_key = c;
+
+                    break;
+                }
+            }
+
+            i++;
+        }
+
+        // Legacy support - use first upper case char if the specifier was not found
+        if (hot_pos == -1 && firstUpperCase)
+        {
+            i = 0;
+
+            foreach (Rune c in text.EnumerateRunes ())
+            {
+                if ((char)c.Value != 0xFFFD)
+                {
+                    if (Rune.IsUpper (c))
+                    {
+                        hot_key = c;
+                        hot_pos = i;
+
+                        break;
+                    }
+                }
+
+                i++;
+            }
+        }
+
+        if (hot_key != (Rune)0 && hot_pos != -1)
+        {
+            hotPos = hot_pos;
+
+            var newHotKey = (KeyCode)hot_key.Value;
+
+            if (newHotKey != KeyCode.Null && !((newHotKey == KeyCode.Space) || Rune.IsControl (hot_key)))
+            {
+                if ((newHotKey & ~KeyCode.Space) is >= KeyCode.A and <= KeyCode.Z)
+                {
+                    newHotKey &= ~KeyCode.Space;
+                }
+
+                hotKey = newHotKey;
+
+                return true;
+            }
+        }
+
+        hotPos = -1;
+        hotKey = KeyCode.Null;
+
+        return false;
+    }
+
+    /// <summary>
+    ///     Replaces the Rune at the index specified by the <c>hotPos</c> parameter with a tag identifying it as the
+    ///     hotkey.
+    /// </summary>
+    /// <param name="text">The text to tag the hotkey in.</param>
+    /// <param name="hotPos">The Rune index of the hotkey in <c>text</c>.</param>
+    /// <returns>The text with the hotkey tagged.</returns>
+    /// <remarks>The returned string will not render correctly without first un-doing the tag. To undo the tag, search for</remarks>
+    public string ReplaceHotKeyWithTag (string text, int hotPos)
+    {
+        // Set the high bit
+        List<Rune> runes = text.ToRuneList ();
+
+        if (Rune.IsLetterOrDigit (runes [hotPos]))
+        {
+            runes [hotPos] = new Rune ((uint)runes [hotPos].Value);
+        }
+
+        return StringExtensions.ToString (runes);
+    }
+
+    /// <summary>Removes the hotkey specifier from text.</summary>
+    /// <param name="text">The text to manipulate.</param>
+    /// <param name="hotKeySpecifier">The hot-key specifier (e.g. '_') to look for.</param>
+    /// <param name="hotPos">Returns the position of the hot-key in the text. -1 if not found.</param>
+    /// <returns>The input text with the hotkey specifier ('_') removed.</returns>
+    public static string RemoveHotKeySpecifier (string text, int hotPos, Rune hotKeySpecifier)
+    {
+        if (string.IsNullOrEmpty (text))
+        {
+            return text;
+        }
+
+        // Scan 
+        var start = string.Empty;
+        var i = 0;
+
+        foreach (Rune c in text)
+        {
+            if (c == hotKeySpecifier && i == hotPos)
+            {
+                i++;
+
+                continue;
+            }
+
+            start += c;
+            i++;
+        }
+
+        return start;
+    }
+
+    #endregion // Static Members
 }

+ 960 - 948
Terminal.Gui/View/Layout/PosDim.cs

@@ -1,981 +1,993 @@
-using System;
-
-namespace Terminal.Gui;
+namespace Terminal.Gui;
 
 /// <summary>
-/// Describes the position of a <see cref="View"/> which can be an absolute value, a percentage, centered, or
-/// Describes the position of a <see cref="View"/> which can be an absolute value, a percentage, centered, or
-/// relative to the ending dimension. Integer values are implicitly convertible to
-/// an absolute <see cref="Pos"/>. These objects are created using the static methods Percent,
-/// AnchorEnd, and Center. The <see cref="Pos"/> objects can be combined with the addition and
-/// AnchorEnd, and Center. The <see cref="Pos"/> objects can be combined with the addition and
-/// subtraction operators.
+///     Describes the position of a <see cref="View"/> which can be an absolute value, a percentage, centered, or Describes
+///     the position of a <see cref="View"/> which can be an absolute value, a percentage, centered, or relative to the
+///     ending dimension. Integer values are implicitly convertible to an absolute <see cref="Pos"/>. These objects are
+///     created using the static methods Percent, AnchorEnd, and Center. The <see cref="Pos"/> objects can be combined with
+///     the addition and AnchorEnd, and Center. The <see cref="Pos"/> objects can be combined with the addition and
+///     subtraction operators.
 /// </summary>
 /// <remarks>
-///         <para>
-///         Use the <see cref="Pos"/> objects on the X or Y properties of a view to control the position.
-///         </para>
-///         <para>
-///         These can be used to set the absolute position, when merely assigning an
-///         integer value (via the implicit integer to <see cref="Pos"/> conversion), and they can be combined
-///         to produce more useful layouts, like: Pos.Center - 3, which would shift the position
-///         of the <see cref="View"/> 3 characters to the left after centering for example.
-///         </para>
-///         <para>
+///     <para>Use the <see cref="Pos"/> objects on the X or Y properties of a view to control the position.</para>
+///     <para>
+///         These can be used to set the absolute position, when merely assigning an integer value (via the implicit
+///         integer to <see cref="Pos"/> conversion), and they can be combined to produce more useful layouts, like:
+///         Pos.Center - 3, which would shift the position of the <see cref="View"/> 3 characters to the left after
+///         centering for example.
+///     </para>
+///     <para>
 ///         Reference coordinates of another view by using the methods Left(View), Right(View), Bottom(View), Top(View).
-///         The X(View) and Y(View) are
-///         aliases to Left(View) and Top(View) respectively.
-///         </para>
-///         <para>
-///                 <list type="table">
-///                         <listheader>
-///                                 <term>Pos Object</term>
-///                                 <description>Description</description>
-///                         </listheader>
-///                         <item>
-///                                 <term>
-///                                         <see cref="Pos.Function(Func{int})"/>
-///                                 </term>
-///                                 <description>
-///                                 Creates a <see cref="Pos"/> object that computes the position by executing the provided
-///                                 function. The function will be called every time the position is needed.
-///                                 </description>
-///                         </item>
-///                         <item>
-///                                 <term>
-///                                         <see cref="Pos.Percent(float)"/>
-///                                 </term>
-///                                 <description>
-///                                 Creates a <see cref="Pos"/> object that is a percentage of the width or height of the
-///                                 SuperView.
-///                                 </description>
-///                         </item>
-///                         <item>
-///                                 <term>
-///                                         <see cref="Pos.Anchor(int)"/>
-///                                 </term>
-///                                 <description>
-///                                 Creates a <see cref="Pos"/> object that is anchored to the end (right side or bottom)
-///                                 of the dimension,
-///                                 useful to flush the layout from the right or bottom.
-///                                 </description>
-///                         </item>
-///                         <item>
-///                                 <term>
-///                                         <see cref="Pos.Center"/>
-///                                 </term>
-///                                 <description>
-///                                 Creates a <see cref="Pos"/> object that can be used to center the <see cref="View"/>.
-///                                 </description>
-///                         </item>
-///                         <item>
-///                                 <term>
-///                                         <see cref="Pos.At(int)"/>
-///                                 </term>
-///                                 <description>
-///                                 Creates a <see cref="Pos"/> object that is an absolute position based on the specified
-///                                 integer value.
-///                                 </description>
-///                         </item>
-///                         <item>
-///                                 <term>
-///                                         <see cref="Pos.Left"/>
-///                                 </term>
-///                                 <description>
-///                                 Creates a <see cref="Pos"/> object that tracks the Left (X) position of the specified
-///                                 <see cref="View"/>.
-///                                 </description>
-///                         </item>
-///                         <item>
-///                                 <term>
-///                                         <see cref="Pos.X(View)"/>
-///                                 </term>
-///                                 <description>
-///                                 Creates a <see cref="Pos"/> object that tracks the Left (X) position of the specified
-///                                 <see cref="View"/>.
-///                                 </description>
-///                         </item>
-///                         <item>
-///                                 <term>
-///                                         <see cref="Pos.Top(View)"/>
-///                                 </term>
-///                                 <description>
-///                                 Creates a <see cref="Pos"/> object that tracks the Top (Y) position of the specified
-///                                 <see cref="View"/>.
-///                                 </description>
-///                         </item>
-///                         <item>
-///                                 <term>
-///                                         <see cref="Pos.Y(View)"/>
-///                                 </term>
-///                                 <description>
-///                                 Creates a <see cref="Pos"/> object that tracks the Top (Y) position of the specified
-///                                 <see cref="View"/>.
-///                                 </description>
-///                         </item>
-///                         <item>
-///                                 <term>
-///                                         <see cref="Pos.Right(View)"/>
-///                                 </term>
-///                                 <description>
-///                                 Creates a <see cref="Pos"/> object that tracks the Right (X+Width) coordinate of the
-///                                 specified <see cref="View"/>.
-///                                 </description>
-///                         </item>
-///                         <item>
-///                                 <term>
-///                                         <see cref="Pos.Bottom(View)"/>
-///                                 </term>
-///                                 <description>
-///                                 Creates a <see cref="Pos"/> object that tracks the Bottom (Y+Height) coordinate of the
-///                                 specified <see cref="View"/>
-///                                 </description>
-///                         </item>
-/// 
-///                 </list>
-///         </para>
-///         <para>
-///         Use the <see cref="Pos"/> objects on the X or Y properties of a view to control the position.
-///         </para>
-///         <para>
-///         These can be used to set the absolute position, when merely assigning an
-///         integer value (via the implicit integer to <see cref="Pos"/> conversion), and they can be combined
-///         to produce more useful layouts, like: Pos.Center - 3, which would shift the position
-///         of the <see cref="View"/> 3 characters to the left after centering for example.
-///         </para>
-///         <para>
+///         The X(View) and Y(View) are aliases to Left(View) and Top(View) respectively.
+///     </para>
+///     <para>
+///         <list type="table">
+///             <listheader>
+///                 <term>Pos Object</term> <description>Description</description>
+///             </listheader>
+///             <item>
+///                 <term>
+///                     <see cref="Pos.Function(Func{int})"/>
+///                 </term>
+///                 <description>
+///                     Creates a <see cref="Pos"/> object that computes the position by executing the provided function.
+///                     The function will be called every time the position is needed.
+///                 </description>
+///             </item>
+///             <item>
+///                 <term>
+///                     <see cref="Pos.Percent(float)"/>
+///                 </term>
+///                 <description>
+///                     Creates a <see cref="Pos"/> object that is a percentage of the width or height of the
+///                     SuperView.
+///                 </description>
+///             </item>
+///             <item>
+///                 <term>
+///                     <see cref="Pos.Anchor(int)"/>
+///                 </term>
+///                 <description>
+///                     Creates a <see cref="Pos"/> object that is anchored to the end (right side or bottom) of the
+///                     dimension, useful to flush the layout from the right or bottom.
+///                 </description>
+///             </item>
+///             <item>
+///                 <term>
+///                     <see cref="Pos.Center"/>
+///                 </term>
+///                 <description>Creates a <see cref="Pos"/> object that can be used to center the <see cref="View"/>.</description>
+///             </item>
+///             <item>
+///                 <term>
+///                     <see cref="Pos.At(int)"/>
+///                 </term>
+///                 <description>
+///                     Creates a <see cref="Pos"/> object that is an absolute position based on the specified
+///                     integer value.
+///                 </description>
+///             </item>
+///             <item>
+///                 <term>
+///                     <see cref="Pos.Left"/>
+///                 </term>
+///                 <description>
+///                     Creates a <see cref="Pos"/> object that tracks the Left (X) position of the specified
+///                     <see cref="View"/>.
+///                 </description>
+///             </item>
+///             <item>
+///                 <term>
+///                     <see cref="Pos.X(View)"/>
+///                 </term>
+///                 <description>
+///                     Creates a <see cref="Pos"/> object that tracks the Left (X) position of the specified
+///                     <see cref="View"/>.
+///                 </description>
+///             </item>
+///             <item>
+///                 <term>
+///                     <see cref="Pos.Top(View)"/>
+///                 </term>
+///                 <description>
+///                     Creates a <see cref="Pos"/> object that tracks the Top (Y) position of the specified
+///                     <see cref="View"/>.
+///                 </description>
+///             </item>
+///             <item>
+///                 <term>
+///                     <see cref="Pos.Y(View)"/>
+///                 </term>
+///                 <description>
+///                     Creates a <see cref="Pos"/> object that tracks the Top (Y) position of the specified
+///                     <see cref="View"/>.
+///                 </description>
+///             </item>
+///             <item>
+///                 <term>
+///                     <see cref="Pos.Right(View)"/>
+///                 </term>
+///                 <description>
+///                     Creates a <see cref="Pos"/> object that tracks the Right (X+Width) coordinate of the
+///                     specified <see cref="View"/>.
+///                 </description>
+///             </item>
+///             <item>
+///                 <term>
+///                     <see cref="Pos.Bottom(View)"/>
+///                 </term>
+///                 <description>
+///                     Creates a <see cref="Pos"/> object that tracks the Bottom (Y+Height) coordinate of the
+///                     specified <see cref="View"/>
+///                 </description>
+///             </item>
+///         </list>
+///     </para>
+///     <para>Use the <see cref="Pos"/> objects on the X or Y properties of a view to control the position.</para>
+///     <para>
+///         These can be used to set the absolute position, when merely assigning an integer value (via the implicit
+///         integer to <see cref="Pos"/> conversion), and they can be combined to produce more useful layouts, like:
+///         Pos.Center - 3, which would shift the position of the <see cref="View"/> 3 characters to the left after
+///         centering for example.
+///     </para>
+///     <para>
 ///         Reference coordinates of another view by using the methods Left(View), Right(View), Bottom(View), Top(View).
-///         The X(View) and Y(View) are
-///         aliases to Left(View) and Top(View) respectively.
-///         </para>
-///         <para>
-///                 <list type="table">
-///                         <listheader>
-///                                 <term>Pos Object</term>
-///                                 <description>Description</description>
-///                         </listheader>
-///                         <item>
-///                                 <term>
-///                                         <see cref="Pos.Function(Func{int})"/>
-///                                 </term>
-///                                 <description>
-///                                 Creates a <see cref="Pos"/> object that computes the position by executing the provided
-///                                 function. The function will be called every time the position is needed.
-///                                 </description>
-///                         </item>
-///                         <item>
-///                                 <term>
-///                                         <see cref="Pos.Percent(float)"/>
-///                                 </term>
-///                                 <description>
-///                                 Creates a <see cref="Pos"/> object that is a percentage of the width or height of the
-///                                 SuperView.
-///                                 </description>
-///                         </item>
-///                         <item>
-///                                 <term>
-///                                         <see cref="Pos.Anchor(int)"/>
-///                                 </term>
-///                                 <description>
-///                                 Creates a <see cref="Pos"/> object that is anchored to the end (right side or bottom)
-///                                 of the dimension,
-///                                 useful to flush the layout from the right or bottom.
-///                                 </description>
-///                         </item>
-///                         <item>
-///                                 <term>
-///                                         <see cref="Pos.Center"/>
-///                                 </term>
-///                                 <description>
-///                                 Creates a <see cref="Pos"/> object that can be used to center the <see cref="View"/>.
-///                                 </description>
-///                         </item>
-///                         <item>
-///                                 <term>
-///                                         <see cref="Pos.At(int)"/>
-///                                 </term>
-///                                 <description>
-///                                 Creates a <see cref="Pos"/> object that is an absolute position based on the specified
-///                                 integer value.
-///                                 </description>
-///                         </item>
-///                         <item>
-///                                 <term>
-///                                         <see cref="Pos.Left"/>
-///                                 </term>
-///                                 <description>
-///                                 Creates a <see cref="Pos"/> object that tracks the Left (X) position of the specified
-///                                 <see cref="View"/>.
-///                                 </description>
-///                         </item>
-///                         <item>
-///                                 <term>
-///                                         <see cref="Pos.X(View)"/>
-///                                 </term>
-///                                 <description>
-///                                 Creates a <see cref="Pos"/> object that tracks the Left (X) position of the specified
-///                                 <see cref="View"/>.
-///                                 </description>
-///                         </item>
-///                         <item>
-///                                 <term>
-///                                         <see cref="Pos.Top(View)"/>
-///                                 </term>
-///                                 <description>
-///                                 Creates a <see cref="Pos"/> object that tracks the Top (Y) position of the specified
-///                                 <see cref="View"/>.
-///                                 </description>
-///                         </item>
-///                         <item>
-///                                 <term>
-///                                         <see cref="Pos.Y(View)"/>
-///                                 </term>
-///                                 <description>
-///                                 Creates a <see cref="Pos"/> object that tracks the Top (Y) position of the specified
-///                                 <see cref="View"/>.
-///                                 </description>
-///                         </item>
-///                         <item>
-///                                 <term>
-///                                         <see cref="Pos.Right(View)"/>
-///                                 </term>
-///                                 <description>
-///                                 Creates a <see cref="Pos"/> object that tracks the Right (X+Width) coordinate of the
-///                                 specified <see cref="View"/>.
-///                                 </description>
-///                         </item>
-///                         <item>
-///                                 <term>
-///                                         <see cref="Pos.Bottom(View)"/>
-///                                 </term>
-///                                 <description>
-///                                 Creates a <see cref="Pos"/> object that tracks the Bottom (Y+Height) coordinate of the
-///                                 specified <see cref="View"/>
-///                                 </description>
-///                         </item>
-/// 
-///                 </list>
-///         </para>
+///         The X(View) and Y(View) are aliases to Left(View) and Top(View) respectively.
+///     </para>
+///     <para>
+///         <list type="table">
+///             <listheader>
+///                 <term>Pos Object</term> <description>Description</description>
+///             </listheader>
+///             <item>
+///                 <term>
+///                     <see cref="Pos.Function(Func{int})"/>
+///                 </term>
+///                 <description>
+///                     Creates a <see cref="Pos"/> object that computes the position by executing the provided function.
+///                     The function will be called every time the position is needed.
+///                 </description>
+///             </item>
+///             <item>
+///                 <term>
+///                     <see cref="Pos.Percent(float)"/>
+///                 </term>
+///                 <description>
+///                     Creates a <see cref="Pos"/> object that is a percentage of the width or height of the
+///                     SuperView.
+///                 </description>
+///             </item>
+///             <item>
+///                 <term>
+///                     <see cref="Pos.Anchor(int)"/>
+///                 </term>
+///                 <description>
+///                     Creates a <see cref="Pos"/> object that is anchored to the end (right side or bottom) of the
+///                     dimension, useful to flush the layout from the right or bottom.
+///                 </description>
+///             </item>
+///             <item>
+///                 <term>
+///                     <see cref="Pos.Center"/>
+///                 </term>
+///                 <description>Creates a <see cref="Pos"/> object that can be used to center the <see cref="View"/>.</description>
+///             </item>
+///             <item>
+///                 <term>
+///                     <see cref="Pos.At(int)"/>
+///                 </term>
+///                 <description>
+///                     Creates a <see cref="Pos"/> object that is an absolute position based on the specified
+///                     integer value.
+///                 </description>
+///             </item>
+///             <item>
+///                 <term>
+///                     <see cref="Pos.Left"/>
+///                 </term>
+///                 <description>
+///                     Creates a <see cref="Pos"/> object that tracks the Left (X) position of the specified
+///                     <see cref="View"/>.
+///                 </description>
+///             </item>
+///             <item>
+///                 <term>
+///                     <see cref="Pos.X(View)"/>
+///                 </term>
+///                 <description>
+///                     Creates a <see cref="Pos"/> object that tracks the Left (X) position of the specified
+///                     <see cref="View"/>.
+///                 </description>
+///             </item>
+///             <item>
+///                 <term>
+///                     <see cref="Pos.Top(View)"/>
+///                 </term>
+///                 <description>
+///                     Creates a <see cref="Pos"/> object that tracks the Top (Y) position of the specified
+///                     <see cref="View"/>.
+///                 </description>
+///             </item>
+///             <item>
+///                 <term>
+///                     <see cref="Pos.Y(View)"/>
+///                 </term>
+///                 <description>
+///                     Creates a <see cref="Pos"/> object that tracks the Top (Y) position of the specified
+///                     <see cref="View"/>.
+///                 </description>
+///             </item>
+///             <item>
+///                 <term>
+///                     <see cref="Pos.Right(View)"/>
+///                 </term>
+///                 <description>
+///                     Creates a <see cref="Pos"/> object that tracks the Right (X+Width) coordinate of the
+///                     specified <see cref="View"/>.
+///                 </description>
+///             </item>
+///             <item>
+///                 <term>
+///                     <see cref="Pos.Bottom(View)"/>
+///                 </term>
+///                 <description>
+///                     Creates a <see cref="Pos"/> object that tracks the Bottom (Y+Height) coordinate of the
+///                     specified <see cref="View"/>
+///                 </description>
+///             </item>
+///         </list>
+///     </para>
 /// </remarks>
-public class Pos {
-	internal virtual int Anchor (int width) => 0;
-
-	/// <summary>
-	/// Creates a <see cref="Pos"/> object that computes the position by executing the provided function. The function will be
-	/// called every time the position is needed.
-	/// </summary>
-	/// <param name="function">The function to be executed.</param>
-	/// <returns>The <see cref="Pos" /> returned from the function.</returns>
-	public static Pos Function (Func<int> function) => new PosFunc (function);
-
-	/// <summary>
-	/// Creates a percentage <see cref="Pos"/> object
-	/// </summary>
-	/// <returns>The percent <see cref="Pos"/> object.</returns>
-	/// <param name="n">A value between 0 and 100 representing the percentage.</param>
-	/// <example>
-	/// This creates a <see cref="TextField"/>that is centered horizontally, is 50% of the way down,
-	/// is 30% the height, and is 80% the width of the <see cref="View"/> it added to.
-	/// <code>
-	///  var textView = new TextView () {
-	/// 	X = Pos.Center (),
-	/// 	Y = Pos.Percent (50),
-	/// 	Width = Dim.Percent (80),
-	///  	Height = Dim.Percent (30),
-	///  };
-	///  </code>
-	/// </example>
-	public static Pos Percent (float n)
-	{
-		if (n is < 0 or > 100) {
-			throw new ArgumentException ("Percent value must be between 0 and 100");
-		}
-
-		return new PosFactor (n / 100);
-	}
-
-	/// <summary>
-	/// Creates a <see cref="Pos"/> object that is anchored to the end (right side or bottom) of the dimension,
-	/// useful to flush the layout from the right or bottom.
-	/// </summary>
-	/// <returns>The <see cref="Pos"/> object anchored to the end (the bottom or the right side).</returns>
-	/// <param name="offset">The view will be shifted left or up by the amount specified.</param>
-	/// <example>
-	/// This sample shows how align a <see cref="Button"/> to the bottom-right of a <see cref="View"/>.
-	/// <code>
-	/// // See Issue #502 
-	/// anchorButton.X = Pos.AnchorEnd () - (Pos.Right (anchorButton) - Pos.Left (anchorButton));
-	/// anchorButton.Y = Pos.AnchorEnd (1);
-	/// </code>
-	/// </example>
-	public static Pos AnchorEnd (int offset = 0)
-	{
-		if (offset < 0) {
-			throw new ArgumentException (@"Must be positive", nameof (offset));
-		}
-
-		return new PosAnchorEnd (offset);
-	}
-
-	/// <summary>
-	/// Creates a <see cref="Pos"/> object that can be used to center the <see cref="View"/>.
-	/// </summary>
-	/// <returns>The center Pos.</returns>
-	/// <example>
-	/// This creates a <see cref="TextField"/>that is centered horizontally, is 50% of the way down,
-	/// is 30% the height, and is 80% the width of the <see cref="View"/> it added to.
-	/// <code>
-	///  var textView = new TextView () {
-	/// 	X = Pos.Center (),
-	/// 	Y = Pos.Percent (50),
-	/// 	Width = Dim.Percent (80),
-	///  	Height = Dim.Percent (30),
-	///  };
-	///  </code>
-	/// </example>
-	public static Pos Center () => new PosCenter ();
-
-	/// <summary>
-	/// Creates a <see cref="Pos"/> object that is an absolute position based on the specified integer value.
-	/// </summary>
-	/// <returns>The Absolute <see cref="Pos"/>.</returns>
-	/// <param name="n">The value to convert to the <see cref="Pos"/>.</param>
-	public static Pos At (int n) => new PosAbsolute (n);
-
-	/// <summary>
-	/// Creates an Absolute <see cref="Pos"/> from the specified integer value.
-	/// </summary>
-	/// <returns>The Absolute <see cref="Pos"/>.</returns>
-	/// <param name="n">The value to convert to the <see cref="Pos"/> .</param>
-	public static implicit operator Pos (int n) => new PosAbsolute (n);
-
-	/// <summary>
-	/// Adds a <see cref="Terminal.Gui.Pos"/> to a <see cref="Terminal.Gui.Pos"/>, yielding a new <see cref="Pos"/>.
-	/// </summary>
-	/// <param name="left">The first <see cref="Terminal.Gui.Pos"/> to add.</param>
-	/// <param name="right">The second <see cref="Terminal.Gui.Pos"/> to add.</param>
-	/// <returns>The <see cref="Pos"/> that is the sum of the values of <c>left</c> and <c>right</c>.</returns>
-	public static Pos operator + (Pos left, Pos right)
-	{
-		if (left is PosAbsolute && right is PosAbsolute) {
-			return new PosAbsolute (left.Anchor (0) + right.Anchor (0));
-		}
-		var newPos = new PosCombine (true, left, right);
-		SetPosCombine (left, newPos);
-		return newPos;
-	}
-
-	/// <summary>
-	/// Subtracts a <see cref="Terminal.Gui.Pos"/> from a <see cref="Terminal.Gui.Pos"/>, yielding a new <see cref="Pos"/>.
-	/// </summary>
-	/// <param name="left">The <see cref="Terminal.Gui.Pos"/> to subtract from (the minuend).</param>
-	/// <param name="right">The <see cref="Terminal.Gui.Pos"/> to subtract (the subtrahend).</param>
-	/// <returns>The <see cref="Pos"/> that is the <c>left</c> minus <c>right</c>.</returns>
-	public static Pos operator - (Pos left, Pos right)
-	{
-		if (left is PosAbsolute && right is PosAbsolute) {
-			return new PosAbsolute (left.Anchor (0) - right.Anchor (0));
-		}
-		var newPos = new PosCombine (false, left, right);
-		SetPosCombine (left, newPos);
-		return newPos;
-	}
-
-	static void SetPosCombine (Pos left, PosCombine newPos)
-	{
-		var view = left as PosView;
-		if (view != null) {
-			view.Target.SetNeedsLayout ();
-		}
-	}
-
-	/// <summary>
-	/// Creates a <see cref="Pos"/> object that tracks the Left (X) position of the specified <see cref="View"/>.
-	/// </summary>
-	/// <returns>The <see cref="Pos"/> that depends on the other view.</returns>
-	/// <param name="view">The <see cref="View"/>  that will be tracked.</param>
-	public static Pos Left (View view) => new PosView (view, 0);
-
-	/// <summary>
-	/// Creates a <see cref="Pos"/> object that tracks the Left (X) position of the specified <see cref="View"/>.
-	/// </summary>
-	/// <returns>The <see cref="Pos"/> that depends on the other view.</returns>
-	/// <param name="view">The <see cref="View"/>  that will be tracked.</param>
-	public static Pos X (View view) => new PosView (view, 0);
-
-	/// <summary>
-	/// Creates a <see cref="Pos"/> object that tracks the Top (Y) position of the specified <see cref="View"/>.
-	/// </summary>
-	/// <returns>The <see cref="Pos"/> that depends on the other view.</returns>
-	/// <param name="view">The <see cref="View"/>  that will be tracked.</param>
-	public static Pos Top (View view) => new PosView (view, 1);
-
-	/// <summary>
-	/// Creates a <see cref="Pos"/> object that tracks the Top (Y) position of the specified <see cref="View"/>.
-	/// </summary>
-	/// <returns>The <see cref="Pos"/> that depends on the other view.</returns>
-	/// <param name="view">The <see cref="View"/>  that will be tracked.</param>
-	public static Pos Y (View view) => new PosView (view, 1);
-
-	/// <summary>
-	/// Creates a <see cref="Pos"/> object that tracks the Right (X+Width) coordinate of the specified <see cref="View"/>.
-	/// </summary>
-	/// <returns>The <see cref="Pos"/> that depends on the other view.</returns>
-	/// <param name="view">The <see cref="View"/>  that will be tracked.</param>
-	public static Pos Right (View view) => new PosView (view, 2);
-
-	/// <summary>
-	/// Creates a <see cref="Pos"/> object that tracks the Bottom (Y+Height) coordinate of the specified <see cref="View"/>
-	/// </summary>
-	/// <returns>The <see cref="Pos"/> that depends on the other view.</returns>
-	/// <param name="view">The <see cref="View"/>  that will be tracked.</param>
-	public static Pos Bottom (View view) => new PosView (view, 3);
-
-	/// <summary>Serves as the default hash function. </summary>
-	/// <returns>A hash code for the current object.</returns>
-	public override int GetHashCode () => Anchor (0).GetHashCode ();
-
-	/// <summary>Determines whether the specified object is equal to the current object.</summary>
-	/// <param name="other">The object to compare with the current object. </param>
-	/// <returns>
-	/// <see langword="true"/> if the specified object  is equal to the current object; otherwise, <see langword="false"/>.
-	/// </returns>
-	public override bool Equals (object other) => other is Pos abs && abs == this;
-
-	internal class PosFactor : Pos {
-		readonly float _factor;
-
-		public PosFactor (float n) => _factor = n;
-
-		internal override int Anchor (int width) => (int)(width * _factor);
-
-		public override string ToString () => $"Factor({_factor})";
-
-		public override int GetHashCode () => _factor.GetHashCode ();
-
-		public override bool Equals (object other) => other is PosFactor f && f._factor == _factor;
-	}
-
-	// Helper class to provide dynamic value by the execution of a function that returns an integer.
-	internal class PosFunc : Pos {
-		readonly Func<int> _function;
-
-		public PosFunc (Func<int> n) => _function = n;
-
-		internal override int Anchor (int width) => _function ();
-
-		public override string ToString () => $"PosFunc({_function ()})";
-
-		public override int GetHashCode () => _function.GetHashCode ();
-
-		public override bool Equals (object other) => other is PosFunc f && f._function () == _function ();
-	}
-
-	internal class PosAnchorEnd : Pos {
-		readonly int _offset;
-
-		public PosAnchorEnd (int offset) => _offset = offset;
-
-		internal override int Anchor (int width) => width - _offset;
-
-		public override string ToString () => $"AnchorEnd({_offset})";
-
-		public override int GetHashCode () => _offset.GetHashCode ();
-
-		public override bool Equals (object other) => other is PosAnchorEnd anchorEnd && anchorEnd._offset == _offset;
-	}
-
-	internal class PosAbsolute : Pos {
-		readonly int _n;
-		public PosAbsolute (int n) => _n = n;
-
-		public override string ToString () => $"Absolute({_n})";
-
-		internal override int Anchor (int width) => _n;
-
-		public override int GetHashCode () => _n.GetHashCode ();
-
-		public override bool Equals (object other) => other is PosAbsolute abs && abs._n == _n;
-	}
-
-	internal class PosCenter : Pos {
-		internal override int Anchor (int width) => width / 2;
-
-		public override string ToString () => "Center";
-	}
-
-	internal class PosCombine : Pos {
-		internal bool _add;
-		internal Pos _left, _right;
-
-		public PosCombine (bool add, Pos left, Pos right)
-		{
-			_left = left;
-			_right = right;
-			_add = add;
-		}
-
-		internal override int Anchor (int width)
-		{
-			var la = _left.Anchor (width);
-			var ra = _right.Anchor (width);
-			if (_add) {
-				return la + ra;
-			}
-			return la - ra;
-		}
-
-		public override string ToString () => $"Combine({_left}{(_add ? '+' : '-')}{_right})";
-	}
-
-	internal class PosView : Pos {
-		public readonly View Target;
-		readonly int side;
-
-		public PosView (View view, int side)
-		{
-			Target = view;
-			this.side = side;
-		}
+public class Pos
+{
+    /// <summary>
+    ///     Creates a <see cref="Pos"/> object that is anchored to the end (right side or bottom) of the dimension, useful to
+    ///     flush the layout from the right or bottom.
+    /// </summary>
+    /// <returns>The <see cref="Pos"/> object anchored to the end (the bottom or the right side).</returns>
+    /// <param name="offset">The view will be shifted left or up by the amount specified.</param>
+    /// <example>
+    ///     This sample shows how align a <see cref="Button"/> to the bottom-right of a <see cref="View"/>.
+    ///     <code>
+    /// // See Issue #502 
+    /// anchorButton.X = Pos.AnchorEnd () - (Pos.Right (anchorButton) - Pos.Left (anchorButton));
+    /// anchorButton.Y = Pos.AnchorEnd (1);
+    /// </code>
+    /// </example>
+    public static Pos AnchorEnd (int offset = 0)
+    {
+        if (offset < 0)
+        {
+            throw new ArgumentException (@"Must be positive", nameof (offset));
+        }
+
+        return new PosAnchorEnd (offset);
+    }
+
+    /// <summary>Creates a <see cref="Pos"/> object that is an absolute position based on the specified integer value.</summary>
+    /// <returns>The Absolute <see cref="Pos"/>.</returns>
+    /// <param name="n">The value to convert to the <see cref="Pos"/>.</param>
+    public static Pos At (int n) { return new PosAbsolute (n); }
+
+    /// <summary>
+    ///     Creates a <see cref="Pos"/> object that tracks the Bottom (Y+Height) coordinate of the specified
+    ///     <see cref="View"/>
+    /// </summary>
+    /// <returns>The <see cref="Pos"/> that depends on the other view.</returns>
+    /// <param name="view">The <see cref="View"/>  that will be tracked.</param>
+    public static Pos Bottom (View view) { return new PosView (view, 3); }
+
+    /// <summary>Creates a <see cref="Pos"/> object that can be used to center the <see cref="View"/>.</summary>
+    /// <returns>The center Pos.</returns>
+    /// <example>
+    ///     This creates a <see cref="TextField"/>that is centered horizontally, is 50% of the way down, is 30% the height, and
+    ///     is 80% the width of the <see cref="View"/> it added to.
+    ///     <code>
+    ///  var textView = new TextView () {
+    /// 	X = Pos.Center (),
+    /// 	Y = Pos.Percent (50),
+    /// 	Width = Dim.Percent (80),
+    ///  	Height = Dim.Percent (30),
+    ///  };
+    ///  </code>
+    /// </example>
+    public static Pos Center () { return new PosCenter (); }
+
+    /// <summary>Determines whether the specified object is equal to the current object.</summary>
+    /// <param name="other">The object to compare with the current object. </param>
+    /// <returns>
+    ///     <see langword="true"/> if the specified object  is equal to the current object; otherwise,
+    ///     <see langword="false"/>.
+    /// </returns>
+    public override bool Equals (object other) { return other is Pos abs && abs == this; }
+
+    /// <summary>
+    ///     Creates a <see cref="Pos"/> object that computes the position by executing the provided function. The function will
+    ///     be called every time the position is needed.
+    /// </summary>
+    /// <param name="function">The function to be executed.</param>
+    /// <returns>The <see cref="Pos"/> returned from the function.</returns>
+    public static Pos Function (Func<int> function) { return new PosFunc (function); }
+
+    /// <summary>Serves as the default hash function. </summary>
+    /// <returns>A hash code for the current object.</returns>
+    public override int GetHashCode () { return Anchor (0).GetHashCode (); }
+
+    /// <summary>Creates a <see cref="Pos"/> object that tracks the Left (X) position of the specified <see cref="View"/>.</summary>
+    /// <returns>The <see cref="Pos"/> that depends on the other view.</returns>
+    /// <param name="view">The <see cref="View"/>  that will be tracked.</param>
+    public static Pos Left (View view) { return new PosView (view, 0); }
+
+    /// <summary>Adds a <see cref="Terminal.Gui.Pos"/> to a <see cref="Terminal.Gui.Pos"/>, yielding a new <see cref="Pos"/>.</summary>
+    /// <param name="left">The first <see cref="Terminal.Gui.Pos"/> to add.</param>
+    /// <param name="right">The second <see cref="Terminal.Gui.Pos"/> to add.</param>
+    /// <returns>The <see cref="Pos"/> that is the sum of the values of <c>left</c> and <c>right</c>.</returns>
+    public static Pos operator + (Pos left, Pos right)
+    {
+        if (left is PosAbsolute && right is PosAbsolute)
+        {
+            return new PosAbsolute (left.Anchor (0) + right.Anchor (0));
+        }
+
+        var newPos = new PosCombine (true, left, right);
+        SetPosCombine (left, newPos);
+
+        return newPos;
+    }
+
+    /// <summary>Creates an Absolute <see cref="Pos"/> from the specified integer value.</summary>
+    /// <returns>The Absolute <see cref="Pos"/>.</returns>
+    /// <param name="n">The value to convert to the <see cref="Pos"/> .</param>
+    public static implicit operator Pos (int n) { return new PosAbsolute (n); }
+
+    /// <summary>
+    ///     Subtracts a <see cref="Terminal.Gui.Pos"/> from a <see cref="Terminal.Gui.Pos"/>, yielding a new
+    ///     <see cref="Pos"/>.
+    /// </summary>
+    /// <param name="left">The <see cref="Terminal.Gui.Pos"/> to subtract from (the minuend).</param>
+    /// <param name="right">The <see cref="Terminal.Gui.Pos"/> to subtract (the subtrahend).</param>
+    /// <returns>The <see cref="Pos"/> that is the <c>left</c> minus <c>right</c>.</returns>
+    public static Pos operator - (Pos left, Pos right)
+    {
+        if (left is PosAbsolute && right is PosAbsolute)
+        {
+            return new PosAbsolute (left.Anchor (0) - right.Anchor (0));
+        }
+
+        var newPos = new PosCombine (false, left, right);
+        SetPosCombine (left, newPos);
+
+        return newPos;
+    }
+
+    /// <summary>Creates a percentage <see cref="Pos"/> object</summary>
+    /// <returns>The percent <see cref="Pos"/> object.</returns>
+    /// <param name="n">A value between 0 and 100 representing the percentage.</param>
+    /// <example>
+    ///     This creates a <see cref="TextField"/>that is centered horizontally, is 50% of the way down, is 30% the height, and
+    ///     is 80% the width of the <see cref="View"/> it added to.
+    ///     <code>
+    ///  var textView = new TextView () {
+    /// 	X = Pos.Center (),
+    /// 	Y = Pos.Percent (50),
+    /// 	Width = Dim.Percent (80),
+    ///  	Height = Dim.Percent (30),
+    ///  };
+    ///  </code>
+    /// </example>
+    public static Pos Percent (float n)
+    {
+        if (n is < 0 or > 100)
+        {
+            throw new ArgumentException ("Percent value must be between 0 and 100");
+        }
+
+        return new PosFactor (n / 100);
+    }
+
+    /// <summary>
+    ///     Creates a <see cref="Pos"/> object that tracks the Right (X+Width) coordinate of the specified
+    ///     <see cref="View"/>.
+    /// </summary>
+    /// <returns>The <see cref="Pos"/> that depends on the other view.</returns>
+    /// <param name="view">The <see cref="View"/>  that will be tracked.</param>
+    public static Pos Right (View view) { return new PosView (view, 2); }
+
+    /// <summary>Creates a <see cref="Pos"/> object that tracks the Top (Y) position of the specified <see cref="View"/>.</summary>
+    /// <returns>The <see cref="Pos"/> that depends on the other view.</returns>
+    /// <param name="view">The <see cref="View"/>  that will be tracked.</param>
+    public static Pos Top (View view) { return new PosView (view, 1); }
+
+    /// <summary>Creates a <see cref="Pos"/> object that tracks the Left (X) position of the specified <see cref="View"/>.</summary>
+    /// <returns>The <see cref="Pos"/> that depends on the other view.</returns>
+    /// <param name="view">The <see cref="View"/>  that will be tracked.</param>
+    public static Pos X (View view) { return new PosView (view, 0); }
+
+    /// <summary>Creates a <see cref="Pos"/> object that tracks the Top (Y) position of the specified <see cref="View"/>.</summary>
+    /// <returns>The <see cref="Pos"/> that depends on the other view.</returns>
+    /// <param name="view">The <see cref="View"/>  that will be tracked.</param>
+    public static Pos Y (View view) { return new PosView (view, 1); }
+
+    internal virtual int Anchor (int width) { return 0; }
+
+    private static void SetPosCombine (Pos left, PosCombine newPos)
+    {
+        var view = left as PosView;
+
+        if (view != null)
+        {
+            view.Target.SetNeedsLayout ();
+        }
+    }
+
+    internal class PosAbsolute : Pos
+    {
+        private readonly int _n;
+        public PosAbsolute (int n) { _n = n; }
+
+        public override bool Equals (object other) { return other is PosAbsolute abs && abs._n == _n; }
+
+        public override int GetHashCode () { return _n.GetHashCode (); }
+
+        public override string ToString () { return $"Absolute({_n})"; }
+
+        internal override int Anchor (int width) { return _n; }
+    }
+
+    internal class PosAnchorEnd : Pos
+    {
+        private readonly int _offset;
+
+        public PosAnchorEnd (int offset) { _offset = offset; }
+
+        public override bool Equals (object other) { return other is PosAnchorEnd anchorEnd && anchorEnd._offset == _offset; }
+
+        public override int GetHashCode () { return _offset.GetHashCode (); }
+
+        public override string ToString () { return $"AnchorEnd({_offset})"; }
+
+        internal override int Anchor (int width) { return width - _offset; }
+    }
+
+    internal class PosCenter : Pos
+    {
+        public override string ToString () { return "Center"; }
+
+        internal override int Anchor (int width) { return width / 2; }
+    }
+
+    internal class PosCombine : Pos
+    {
+        internal bool _add;
+        internal Pos _left, _right;
+
+        public PosCombine (bool add, Pos left, Pos right)
+        {
+            _left = left;
+            _right = right;
+            _add = add;
+        }
+
+        public override string ToString () { return $"Combine({_left}{(_add ? '+' : '-')}{_right})"; }
+
+        internal override int Anchor (int width)
+        {
+            int la = _left.Anchor (width);
+            int ra = _right.Anchor (width);
+
+            if (_add)
+            {
+                return la + ra;
+            }
+
+            return la - ra;
+        }
+    }
+
+    internal class PosFactor : Pos
+    {
+        private readonly float _factor;
+
+        public PosFactor (float n) { _factor = n; }
+
+        public override bool Equals (object other) { return other is PosFactor f && f._factor == _factor; }
+
+        public override int GetHashCode () { return _factor.GetHashCode (); }
+
+        public override string ToString () { return $"Factor({_factor})"; }
+
+        internal override int Anchor (int width) { return (int)(width * _factor); }
+    }
+
+    // Helper class to provide dynamic value by the execution of a function that returns an integer.
+    internal class PosFunc : Pos
+    {
+        private readonly Func<int> _function;
+
+        public PosFunc (Func<int> n) { _function = n; }
+
+        public override bool Equals (object other) { return other is PosFunc f && f._function () == _function (); }
+
+        public override int GetHashCode () { return _function.GetHashCode (); }
+
+        public override string ToString () { return $"PosFunc({_function ()})"; }
+
+        internal override int Anchor (int width) { return _function (); }
+    }
 
-		internal override int Anchor (int width)
-		{
-			switch (side) {
-			case 0: return Target.Frame.X;
-			case 1: return Target.Frame.Y;
-			case 2: return Target.Frame.Right;
-			case 3: return Target.Frame.Bottom;
-			default:
-				return 0;
-			}
-		}
-
-		public override string ToString ()
-		{
-			string tside;
-			switch (side) {
-			case 0:
-				tside = "x";
-				break;
-			case 1:
-				tside = "y";
-				break;
-			case 2:
-				tside = "right";
-				break;
-			case 3:
-				tside = "bottom";
-				break;
-			default:
-				tside = "unknown";
-				break;
-			}
-			if (Target == null) {
-				throw new NullReferenceException (nameof (Target));
-			}
-			return $"View(side={tside},target={Target})";
-		}
-
-		public override int GetHashCode () => Target.GetHashCode ();
-
-		public override bool Equals (object other) => other is PosView abs && abs.Target == Target;
-	}
+    internal class PosView : Pos
+    {
+        public readonly View Target;
+        private readonly int side;
+
+        public PosView (View view, int side)
+        {
+            Target = view;
+            this.side = side;
+        }
+
+        public override bool Equals (object other) { return other is PosView abs && abs.Target == Target; }
+
+        public override int GetHashCode () { return Target.GetHashCode (); }
+
+        public override string ToString ()
+        {
+            string tside;
+
+            switch (side)
+            {
+                case 0:
+                    tside = "x";
+
+                    break;
+                case 1:
+                    tside = "y";
+
+                    break;
+                case 2:
+                    tside = "right";
+
+                    break;
+                case 3:
+                    tside = "bottom";
+
+                    break;
+                default:
+                    tside = "unknown";
+
+                    break;
+            }
+
+            if (Target == null)
+            {
+                throw new NullReferenceException (nameof (Target));
+            }
+
+            return $"View(side={tside},target={Target})";
+        }
+
+        internal override int Anchor (int width)
+        {
+            switch (side)
+            {
+                case 0: return Target.Frame.X;
+                case 1: return Target.Frame.Y;
+                case 2: return Target.Frame.Right;
+                case 3: return Target.Frame.Bottom;
+                default:
+                    return 0;
+            }
+        }
+    }
 }
 
 /// <summary>
-///         <para>
+///     <para>
 ///         A Dim object describes the dimensions of a <see cref="View"/>. Dim is the type of the <see cref="View.Width"/>
-///         and
-///         <see cref="View.Height"/> properties of <see cref="View"/>. Dim objects enable Computed Layout (see
-///         <see cref="LayoutStyle.Computed"/>)
-///         to automatically manage the dimensions of a view.
-///         </para>
-///         <para>
+///         and <see cref="View.Height"/> properties of <see cref="View"/>. Dim objects enable Computed Layout (see
+///         <see cref="LayoutStyle.Computed"/>) to automatically manage the dimensions of a view.
+///     </para>
+///     <para>
 ///         Integer values are implicitly convertible to an absolute <see cref="Dim"/>. These objects are created using the
-///         static methods described below.
-///         The <see cref="Dim"/> objects can be combined with the addition and subtraction operators.
-///         </para>
+///         static methods described below. The <see cref="Dim"/> objects can be combined with the addition and subtraction
+///         operators.
+///     </para>
 /// </summary>
 /// <remarks>
-///         <para>
-///                 <list type="table">
-///                         <listheader>
-///                                 <term>Dim Object</term>
-///                                 <description>Description</description>
-///                         </listheader>
-///                         <item>
-///                                 <term>
-///                                         <see cref="Dim.Auto"/>
-///                                 </term>
-///                                 <description>
-///                                  Creates a <see cref="Dim" /> object that automatically sizes the view to fit
-///                                  all of the view's SubViews.
-///                                 </description>
-///                         </item>
-///                         <item>
-///                                 <term>
-///                                         <see cref="Dim.Function(Func{int})"/>
-///                                 </term>
-///                                 <description>
-///                                 Creates a <see cref="Dim"/> object that computes the dimension by executing the
-///                                 provided function. The function will be called every time the dimension is needed.
-///                                 </description>
-///                         </item>
-///                         <item>
-///                                 <term>
-///                                         <see cref="Dim.Percent(float, bool)"/>
-///                                 </term>
-///                                 <description>
-///                                 Creates a <see cref="Dim"/> object that is a percentage of the width or height of the
-///                                 SuperView.
-///                                 </description>
-///                         </item>
-///                         <item>
-///                                 <term>
-///                                         <see cref="Dim.Fill(int)"/>
-///                                 </term>
-///                                 <description>
-///                                 Creates a <see cref="Dim"/> object that fills the dimension, leaving the specified
-///                                 number of columns for a margin.
-///                                 </description>
-///                         </item>
-///                         <item>
-///                                 <term>
-///                                         <see cref="Dim.Width(View)"/>
-///                                 </term>
-///                                 <description>
-///                                 Creates a <see cref="Dim"/> object that tracks the Width of the specified
-///                                 <see cref="View"/>.
-///                                 </description>
-///                         </item>
-///                         <item>
-///                                 <term>
-///                                         <see cref="Dim.Height(View)"/>
-///                                 </term>
-///                                 <description>
-///                                 Creates a <see cref="Dim"/> object that tracks the Height of the specified
-///                                 <see cref="View"/>.
-///                                 </description>
-///                         </item>
-///                 </list>
-///         </para>
-///         <para>
-///         </para>
+///     <para>
+///         <list type="table">
+///             <listheader>
+///                 <term>Dim Object</term> <description>Description</description>
+///             </listheader>
+///             <item>
+///                 <term>
+///                     <see cref="Dim.Auto"/>
+///                 </term>
+///                 <description>
+///                     Creates a <see cref="Dim"/> object that automatically sizes the view to fit all of the
+///                     view's SubViews.
+///                 </description>
+///             </item>
+///             <item>
+///                 <term>
+///                     <see cref="Dim.Function(Func{int})"/>
+///                 </term>
+///                 <description>
+///                     Creates a <see cref="Dim"/> object that computes the dimension by executing the provided function.
+///                     The function will be called every time the dimension is needed.
+///                 </description>
+///             </item>
+///             <item>
+///                 <term>
+///                     <see cref="Dim.Percent(float, bool)"/>
+///                 </term>
+///                 <description>
+///                     Creates a <see cref="Dim"/> object that is a percentage of the width or height of the
+///                     SuperView.
+///                 </description>
+///             </item>
+///             <item>
+///                 <term>
+///                     <see cref="Dim.Fill(int)"/>
+///                 </term>
+///                 <description>
+///                     Creates a <see cref="Dim"/> object that fills the dimension, leaving the specified number
+///                     of columns for a margin.
+///                 </description>
+///             </item>
+///             <item>
+///                 <term>
+///                     <see cref="Dim.Width(View)"/>
+///                 </term>
+///                 <description>
+///                     Creates a <see cref="Dim"/> object that tracks the Width of the specified
+///                     <see cref="View"/>.
+///                 </description>
+///             </item>
+///             <item>
+///                 <term>
+///                     <see cref="Dim.Height(View)"/>
+///                 </term>
+///                 <description>
+///                     Creates a <see cref="Dim"/> object that tracks the Height of the specified
+///                     <see cref="View"/>.
+///                 </description>
+///             </item>
+///         </list>
+///     </para>
+///     <para></para>
 /// </remarks>
-public class Dim {
-	internal virtual int Anchor (int width) => 0;
-
-	/// <summary>
-	/// Specifies how <see cref="DimAuto" /> will compute the dimension.
-	/// </summary>
-	public enum DimAutoStyle {
-		/// <summary>
-		/// The dimension will be computed using both the view's <see cref="View.Text"/> and
-		/// <see cref="View.Subviews" />.
-		/// The larger of the corresponding text dimension or Subview in <see cref="View.Subviews" />
-		/// with the largest corresponding position plus dimension will determine the dimension.
-		/// </summary>
-		Auto,
-
-		/// <summary>
-		/// The Subview in <see cref="View.Subviews" /> with the largest corresponding position plus dimension
-		/// will determine the dimension.
-		/// The corresponding dimension of the view's <see cref="View.Text"/> will be ignored.
-		/// </summary>
-		Subviews,
-
-		/// <summary>
-		/// The corresponding dimension of the view's <see cref="View.Text" />, formatted using the <see cref="View.TextFormatter"/> settings,
-		/// will be used to determine the dimension. 
-		/// The corresponding dimensions of the <see cref="View.Subviews"/> will be ignored.
-		/// </summary>
-		Text
-	}
-
-	/// <summary>
-	/// Creates a <see cref="Dim" /> object that automatically sizes the view to fit all of the view's SubViews and/or Text.
-	/// </summary>
-	/// <example>
-	/// This initializes a <see cref="View" /> with two SubViews. The view will be automatically sized to fit the two
-	/// SubViews.
-	/// <code>
-	/// var button = new Button () { Text = "Click Me!", X = 1, Y = 1, Width = 10, Height = 1 };
-	/// var textField = new TextField { Text = "Type here", X = 1, Y = 2, Width = 20, Height = 1 };
-	/// var view = new Window () { Title = "MyWindow", X = 0, Y = 0, Width = Dim.AutoSize (), Height = Dim.AutoSize () };
-	/// view.Add (button, textField);
-	/// </code>
-	/// </example>
-	/// <returns>The AutoSize <see cref="Dim" /> object.</returns>
-	/// <param name="style">
-	/// Specifies how <see cref="DimAuto" /> will compute the dimension. The default is <see cref="DimAutoStyle.Auto" />. 
-	/// </param>
-	/// <param name="min">Specifies the minimum dimension that view will be automatically sized to.</param>
-	/// <param name="max">Specifies the maximum dimension that view will be automatically sized to. NOT CURRENTLY SUPPORTED.</param>
-	public static Dim Auto (DimAutoStyle style = DimAutoStyle.Auto, Dim min = null, Dim max = null)
-	{
-		if (max != null) {
-			throw new NotImplementedException (@"max is not implemented");
-		}
-		return new DimAuto (style, min, max);
-	}
-	
-	/// <summary>
-	/// Creates a function <see cref="Dim"/> object that computes the dimension by executing the provided function.
-	/// The function will be called every time the dimension is needed.
-	/// </summary>
-	/// <param name="function">The function to be executed.</param>
-	/// <returns>The <see cref="Dim"/> returned from the function.</returns>
-	public static Dim Function (Func<int> function) => new DimFunc (function);
-
-	/// <summary>
-	/// Creates a percentage <see cref="Dim"/> object that is a percentage of the width or height of the SuperView.
-	/// </summary>
-	/// <returns>The percent <see cref="Dim"/> object.</returns>
-	/// <param name="n">A value between 0 and 100 representing the percentage.</param>
-	/// <param name="r">
-	/// If <c>true</c> the Percent is computed based on the remaining space after the X/Y anchor positions.
-	/// If <c>false</c> is computed based on the whole original space.
-	/// </param>
-	/// <example>
-	/// This initializes a <see cref="TextField"/>that is centered horizontally, is 50% of the way down,
-	/// is 30% the height, and is 80% the width of the <see cref="View"/> it added to.
-	/// <code>
-	///  var textView = new TextView () {
-	/// 	X = Pos.Center (),
-	/// 	Y = Pos.Percent (50),
-	/// 	Width = Dim.Percent (80),
-	///  	Height = Dim.Percent (30),
-	///  };
-	///  </code>
-	/// </example>
-	public static Dim Percent (float n, bool r = false)
-	{
-		if (n is < 0 or > 100) {
-			throw new ArgumentException ("Percent value must be between 0 and 100");
-		}
-
-		return new DimFactor (n / 100, r);
-	}
-
-	/// <summary>
-	/// Creates a <see cref="Dim"/> object that fills the dimension, leaving the specified number of columns for a margin.
-	/// </summary>
-	/// <returns>The Fill dimension.</returns>
-	/// <param name="margin">Margin to use.</param>
-	public static Dim Fill (int margin = 0) => new DimFill (margin);
-
-	/// <summary>
-	/// Creates an Absolute <see cref="Dim"/> from the specified integer value.
-	/// </summary>
-	/// <returns>The Absolute <see cref="Dim"/>.</returns>
-	/// <param name="n">The value to convert to the pos.</param>
-	public static implicit operator Dim (int n) => new DimAbsolute (n);
-
-	/// <summary>
-	/// Creates an Absolute <see cref="Dim"/> from the specified integer value.
-	/// </summary>
-	/// <returns>The Absolute <see cref="Dim"/>.</returns>
-	/// <param name="n">The value to convert to the <see cref="Dim"/>.</param>
-	public static Dim Sized (int n) => new DimAbsolute (n);
-
-	/// <summary>
-	/// Adds a <see cref="Terminal.Gui.Dim"/> to a <see cref="Terminal.Gui.Dim"/>, yielding a new <see cref="Dim"/>.
-	/// </summary>
-	/// <param name="left">The first <see cref="Terminal.Gui.Dim"/> to add.</param>
-	/// <param name="right">The second <see cref="Terminal.Gui.Dim"/> to add.</param>
-	/// <returns>The <see cref="Dim"/> that is the sum of the values of <c>left</c> and <c>right</c>.</returns>
-	public static Dim operator + (Dim left, Dim right)
-	{
-		if (left is DimAbsolute && right is DimAbsolute) {
-			return new DimAbsolute (left.Anchor (0) + right.Anchor (0));
-		}
-		var newDim = new DimCombine (true, left, right);
-		SetDimCombine (left, newDim);
-		return newDim;
-	}
-
-	/// <summary>
-	/// Subtracts a <see cref="Terminal.Gui.Dim"/> from a <see cref="Terminal.Gui.Dim"/>, yielding a new <see cref="Dim"/>.
-	/// </summary>
-	/// <param name="left">The <see cref="Terminal.Gui.Dim"/> to subtract from (the minuend).</param>
-	/// <param name="right">The <see cref="Terminal.Gui.Dim"/> to subtract (the subtrahend).</param>
-	/// <returns>The <see cref="Dim"/> that is the <c>left</c> minus <c>right</c>.</returns>
-	public static Dim operator - (Dim left, Dim right)
-	{
-		if (left is DimAbsolute && right is DimAbsolute) {
-			return new DimAbsolute (left.Anchor (0) - right.Anchor (0));
-		}
-		var newDim = new DimCombine (false, left, right);
-		SetDimCombine (left, newDim);
-		return newDim;
-	}
-
-	// BUGBUG: newPos is never used.
-	static void SetDimCombine (Dim left, DimCombine newPos) => (left as DimView)?.Target.SetNeedsLayout ();
-
-	/// <summary>
-	/// Creates a <see cref="Dim"/> object that tracks the Width of the specified <see cref="View"/>.
-	/// </summary>
-	/// <returns>The width <see cref="Dim"/> of the other <see cref="View"/>.</returns>
-	/// <param name="view">The view that will be tracked.</param>
-	public static Dim Width (View view) => new DimView (view, 1);
-
-	/// <summary>
-	/// Creates a <see cref="Dim"/> object that tracks the Height of the specified <see cref="View"/>.
-	/// </summary>
-	/// <returns>The height <see cref="Dim"/> of the other <see cref="View"/>.</returns>
-	/// <param name="view">The view that will be tracked.</param>
-	public static Dim Height (View view) => new DimView (view, 0);
-
-	/// <summary>Serves as the default hash function. </summary>
-	/// <returns>A hash code for the current object.</returns>
-	public override int GetHashCode () => Anchor (0).GetHashCode ();
-
-	/// <summary>Determines whether the specified object is equal to the current object.</summary>
-	/// <param name="other">The object to compare with the current object. </param>
-	/// <returns>
-	/// <see langword="true"/> if the specified object  is equal to the current object; otherwise, <see langword="false"/>.
-	/// </returns>
-	public override bool Equals (object other) => other is Dim abs && abs == this;
-
-	// Helper class to provide dynamic value by the execution of a function that returns an integer.
-	internal class DimFunc : Dim {
-		readonly Func<int> _function;
-
-		public DimFunc (Func<int> n) => _function = n;
-
-		internal override int Anchor (int width) => _function ();
-
-		public override string ToString () => $"DimFunc({_function ()})";
-
-		public override int GetHashCode () => _function.GetHashCode ();
-
-		public override bool Equals (object other) => other is DimFunc f && f._function () == _function ();
-	}
-
-	internal class DimFactor : Dim {
-		readonly float _factor;
-		readonly bool _remaining;
-
-		public DimFactor (float n, bool r = false)
-		{
-			_factor = n;
-			_remaining = r;
-		}
-
-		internal override int Anchor (int width) => (int)(width * _factor);
-
-		public bool IsFromRemaining () => _remaining;
-
-		public override string ToString () => $"Factor({_factor},{_remaining})";
-
-		public override int GetHashCode () => _factor.GetHashCode ();
-
-		public override bool Equals (object other) => other is DimFactor f && f._factor == _factor && f._remaining == _remaining;
-	}
-
-
-	internal class DimAbsolute : Dim {
-		readonly int _n;
-		public DimAbsolute (int n) => _n = n;
-
-		public override string ToString () => $"Absolute({_n})";
-
-		internal override int Anchor (int width) => _n;
-
-		public override int GetHashCode () => _n.GetHashCode ();
-
-		public override bool Equals (object other) => other is DimAbsolute abs && abs._n == _n;
-	}
-
-	internal class DimFill : Dim {
-		readonly int _margin;
-		public DimFill (int margin) => _margin = margin;
-
-		public override string ToString () => $"Fill({_margin})";
+public class Dim
+{
+    /// <summary>Specifies how <see cref="DimAuto"/> will compute the dimension.</summary>
+    public enum DimAutoStyle
+    {
+        /// <summary>
+        ///     The dimension will be computed using both the view's <see cref="View.Text"/> and <see cref="View.Subviews"/>. The
+        ///     larger of the corresponding text dimension or Subview in <see cref="View.Subviews"/> with the largest corresponding
+        ///     position plus dimension will determine the dimension.
+        /// </summary>
+        Auto,
+
+        /// <summary>
+        ///     The Subview in <see cref="View.Subviews"/> with the largest corresponding position plus dimension will determine
+        ///     the dimension. The corresponding dimension of the view's <see cref="View.Text"/> will be ignored.
+        /// </summary>
+        Subviews,
+
+        /// <summary>
+        ///     The corresponding dimension of the view's <see cref="View.Text"/>, formatted using the
+        ///     <see cref="View.TextFormatter"/> settings, will be used to determine the dimension. The corresponding dimensions of
+        ///     the <see cref="View.Subviews"/> will be ignored.
+        /// </summary>
+        Text
+    }
+
+    /// <summary>
+    ///     Creates a <see cref="Dim"/> object that automatically sizes the view to fit all of the view's SubViews and/or
+    ///     Text.
+    /// </summary>
+    /// <example>
+    ///     This initializes a <see cref="View"/> with two SubViews. The view will be automatically sized to fit the two
+    ///     SubViews.
+    ///     <code>
+    /// var button = new Button () { Text = "Click Me!", X = 1, Y = 1, Width = 10, Height = 1 };
+    /// var textField = new TextField { Text = "Type here", X = 1, Y = 2, Width = 20, Height = 1 };
+    /// var view = new Window () { Title = "MyWindow", X = 0, Y = 0, Width = Dim.AutoSize (), Height = Dim.AutoSize () };
+    /// view.Add (button, textField);
+    /// </code>
+    /// </example>
+    /// <returns>The AutoSize <see cref="Dim"/> object.</returns>
+    /// <param name="style">
+    ///     Specifies how <see cref="DimAuto"/> will compute the dimension. The default is
+    ///     <see cref="DimAutoStyle.Auto"/>.
+    /// </param>
+    /// <param name="min">Specifies the minimum dimension that view will be automatically sized to.</param>
+    /// <param name="max">Specifies the maximum dimension that view will be automatically sized to. NOT CURRENTLY SUPPORTED.</param>
+    public static Dim Auto (DimAutoStyle style = DimAutoStyle.Auto, Dim min = null, Dim max = null)
+    {
+        if (max != null)
+        {
+            throw new NotImplementedException (@"max is not implemented");
+        }
+
+        return new DimAuto (style, min, max);
+    }
+
+    /// <summary>Determines whether the specified object is equal to the current object.</summary>
+    /// <param name="other">The object to compare with the current object. </param>
+    /// <returns>
+    ///     <see langword="true"/> if the specified object  is equal to the current object; otherwise,
+    ///     <see langword="false"/>.
+    /// </returns>
+    public override bool Equals (object other) { return other is Dim abs && abs == this; }
+
+    /// <summary>
+    ///     Creates a <see cref="Dim"/> object that fills the dimension, leaving the specified number of columns for a
+    ///     margin.
+    /// </summary>
+    /// <returns>The Fill dimension.</returns>
+    /// <param name="margin">Margin to use.</param>
+    public static Dim Fill (int margin = 0) { return new DimFill (margin); }
+
+    /// <summary>
+    ///     Creates a function <see cref="Dim"/> object that computes the dimension by executing the provided function. The
+    ///     function will be called every time the dimension is needed.
+    /// </summary>
+    /// <param name="function">The function to be executed.</param>
+    /// <returns>The <see cref="Dim"/> returned from the function.</returns>
+    public static Dim Function (Func<int> function) { return new DimFunc (function); }
+
+    /// <summary>Serves as the default hash function. </summary>
+    /// <returns>A hash code for the current object.</returns>
+    public override int GetHashCode () { return Anchor (0).GetHashCode (); }
+
+    /// <summary>Creates a <see cref="Dim"/> object that tracks the Height of the specified <see cref="View"/>.</summary>
+    /// <returns>The height <see cref="Dim"/> of the other <see cref="View"/>.</returns>
+    /// <param name="view">The view that will be tracked.</param>
+    public static Dim Height (View view) { return new DimView (view, 0); }
+
+    /// <summary>Adds a <see cref="Terminal.Gui.Dim"/> to a <see cref="Terminal.Gui.Dim"/>, yielding a new <see cref="Dim"/>.</summary>
+    /// <param name="left">The first <see cref="Terminal.Gui.Dim"/> to add.</param>
+    /// <param name="right">The second <see cref="Terminal.Gui.Dim"/> to add.</param>
+    /// <returns>The <see cref="Dim"/> that is the sum of the values of <c>left</c> and <c>right</c>.</returns>
+    public static Dim operator + (Dim left, Dim right)
+    {
+        if (left is DimAbsolute && right is DimAbsolute)
+        {
+            return new DimAbsolute (left.Anchor (0) + right.Anchor (0));
+        }
+
+        var newDim = new DimCombine (true, left, right);
+        SetDimCombine (left, newDim);
+
+        return newDim;
+    }
+
+    /// <summary>Creates an Absolute <see cref="Dim"/> from the specified integer value.</summary>
+    /// <returns>The Absolute <see cref="Dim"/>.</returns>
+    /// <param name="n">The value to convert to the pos.</param>
+    public static implicit operator Dim (int n) { return new DimAbsolute (n); }
+
+    /// <summary>
+    ///     Subtracts a <see cref="Terminal.Gui.Dim"/> from a <see cref="Terminal.Gui.Dim"/>, yielding a new
+    ///     <see cref="Dim"/>.
+    /// </summary>
+    /// <param name="left">The <see cref="Terminal.Gui.Dim"/> to subtract from (the minuend).</param>
+    /// <param name="right">The <see cref="Terminal.Gui.Dim"/> to subtract (the subtrahend).</param>
+    /// <returns>The <see cref="Dim"/> that is the <c>left</c> minus <c>right</c>.</returns>
+    public static Dim operator - (Dim left, Dim right)
+    {
+        if (left is DimAbsolute && right is DimAbsolute)
+        {
+            return new DimAbsolute (left.Anchor (0) - right.Anchor (0));
+        }
+
+        var newDim = new DimCombine (false, left, right);
+        SetDimCombine (left, newDim);
+
+        return newDim;
+    }
+
+    /// <summary>Creates a percentage <see cref="Dim"/> object that is a percentage of the width or height of the SuperView.</summary>
+    /// <returns>The percent <see cref="Dim"/> object.</returns>
+    /// <param name="n">A value between 0 and 100 representing the percentage.</param>
+    /// <param name="r">
+    ///     If <c>true</c> the Percent is computed based on the remaining space after the X/Y anchor positions. If <c>false</c>
+    ///     is computed based on the whole original space.
+    /// </param>
+    /// <example>
+    ///     This initializes a <see cref="TextField"/>that is centered horizontally, is 50% of the way down, is 30% the height,
+    ///     and is 80% the width of the <see cref="View"/> it added to.
+    ///     <code>
+    ///  var textView = new TextView () {
+    /// 	X = Pos.Center (),
+    /// 	Y = Pos.Percent (50),
+    /// 	Width = Dim.Percent (80),
+    ///  	Height = Dim.Percent (30),
+    ///  };
+    ///  </code>
+    /// </example>
+    public static Dim Percent (float n, bool r = false)
+    {
+        if (n is < 0 or > 100)
+        {
+            throw new ArgumentException ("Percent value must be between 0 and 100");
+        }
+
+        return new DimFactor (n / 100, r);
+    }
+
+    /// <summary>Creates an Absolute <see cref="Dim"/> from the specified integer value.</summary>
+    /// <returns>The Absolute <see cref="Dim"/>.</returns>
+    /// <param name="n">The value to convert to the <see cref="Dim"/>.</param>
+    public static Dim Sized (int n) { return new DimAbsolute (n); }
+
+    /// <summary>Creates a <see cref="Dim"/> object that tracks the Width of the specified <see cref="View"/>.</summary>
+    /// <returns>The width <see cref="Dim"/> of the other <see cref="View"/>.</returns>
+    /// <param name="view">The view that will be tracked.</param>
+    public static Dim Width (View view) { return new DimView (view, 1); }
+
+    internal virtual int Anchor (int width) { return 0; }
+
+    // BUGBUG: newPos is never used.
+    private static void SetDimCombine (Dim left, DimCombine newPos) { (left as DimView)?.Target.SetNeedsLayout (); }
+
+    internal class DimAbsolute : Dim
+    {
+        private readonly int _n;
+        public DimAbsolute (int n) { _n = n; }
+
+        public override bool Equals (object other) { return other is DimAbsolute abs && abs._n == _n; }
+
+        public override int GetHashCode () { return _n.GetHashCode (); }
+
+        public override string ToString () { return $"Absolute({_n})"; }
+
+        internal override int Anchor (int width) { return _n; }
+    }
+
+    internal class DimAuto : Dim
+    {
+        internal readonly Dim _max;
+        internal readonly Dim _min;
+        internal readonly DimAutoStyle _style;
+
+        public DimAuto (DimAutoStyle style, Dim min, Dim max)
+        {
+            _min = min;
+            _max = max;
+            _style = style;
+        }
+
+        public override bool Equals (object other) { return other is DimAuto auto && auto._min == _min && auto._max == _max && auto._style == _style; }
+
+        public override int GetHashCode () { return HashCode.Combine (base.GetHashCode (), _min, _max, _style); }
+
+        public override string ToString () { return $"Auto({_style},{_min},{_max})"; }
+    }
+
+    internal class DimCombine : Dim
+    {
+        internal bool _add;
+        internal Dim _left, _right;
+
+        public DimCombine (bool add, Dim left, Dim right)
+        {
+            _left = left;
+            _right = right;
+            _add = add;
+        }
 
-		internal override int Anchor (int width) => width - _margin;
-
-		public override int GetHashCode () => _margin.GetHashCode ();
+        public override string ToString () { return $"Combine({_left}{(_add ? '+' : '-')}{_right})"; }
 
-		public override bool Equals (object other) => other is DimFill fill && fill._margin == _margin;
-	}
+        internal override int Anchor (int width)
+        {
+            int la = _left.Anchor (width);
+            int ra = _right.Anchor (width);
 
-	internal class DimAuto : Dim {
-		internal readonly Dim _max;
-		internal readonly Dim _min;
-		internal readonly DimAutoStyle _style;
+            if (_add)
+            {
+                return la + ra;
+            }
+
+            return la - ra;
+        }
+    }
+
+    internal class DimFactor : Dim
+    {
+        private readonly float _factor;
+        private readonly bool _remaining;
 
-		public DimAuto (DimAutoStyle style, Dim min, Dim max)
-		{
-			_min = min;
-			_max = max;
-			_style = style;
-		}
+        public DimFactor (float n, bool r = false)
+        {
+            _factor = n;
+            _remaining = r;
+        }
 
-		public override string ToString () => $"Auto({_style},{_min},{_max})";
+        public override bool Equals (object other) { return other is DimFactor f && f._factor == _factor && f._remaining == _remaining; }
 
-		public override int GetHashCode () => HashCode.Combine (base.GetHashCode (), _min, _max, _style);
+        public override int GetHashCode () { return _factor.GetHashCode (); }
 
-		public override bool Equals (object other) => other is DimAuto auto && auto._min == _min && auto._max == _max && auto._style == _style;
-	}
+        public bool IsFromRemaining () { return _remaining; }
 
-	internal class DimCombine : Dim {
-		internal bool _add;
-		internal Dim _left, _right;
+        public override string ToString () { return $"Factor({_factor},{_remaining})"; }
 
-		public DimCombine (bool add, Dim left, Dim right)
-		{
-			_left = left;
-			_right = right;
-			_add = add;
-		}
+        internal override int Anchor (int width) { return (int)(width * _factor); }
+    }
 
-		internal override int Anchor (int width)
-		{
-			var la = _left.Anchor (width);
-			var ra = _right.Anchor (width);
-			if (_add) {
-				return la + ra;
-			}
-			return la - ra;
-		}
+    internal class DimFill : Dim
+    {
+        private readonly int _margin;
+        public DimFill (int margin) { _margin = margin; }
 
-		public override string ToString () => $"Combine({_left}{(_add ? '+' : '-')}{_right})";
-	}
+        public override bool Equals (object other) { return other is DimFill fill && fill._margin == _margin; }
 
-	internal class DimView : Dim {
-		readonly int _side;
+        public override int GetHashCode () { return _margin.GetHashCode (); }
 
-		public DimView (View view, int side)
-		{
-			Target = view;
-			_side = side;
-		}
+        public override string ToString () { return $"Fill({_margin})"; }
 
-		public View Target { get; init; }
+        internal override int Anchor (int width) { return width - _margin; }
+    }
 
-		internal override int Anchor (int width) => _side switch {
-			0 => Target.Frame.Height,
-			1 => Target.Frame.Width,
-			_ => 0
-		};
+    // Helper class to provide dynamic value by the execution of a function that returns an integer.
+    internal class DimFunc : Dim
+    {
+        private readonly Func<int> _function;
 
-		public override string ToString ()
-		{
-			if (Target == null) {
-				throw new NullReferenceException ();
-			}
-			var tside = _side switch {
-				0 => "Height",
-				1 => "Width",
-				_ => "unknown"
-			};
-			return $"View({tside},{Target})";
-		}
+        public DimFunc (Func<int> n) { _function = n; }
 
-		public override int GetHashCode () => Target.GetHashCode ();
-
-		public override bool Equals (object other) => other is DimView abs && abs.Target == Target;
-	}
-}
+        public override bool Equals (object other) { return other is DimFunc f && f._function () == _function (); }
+
+        public override int GetHashCode () { return _function.GetHashCode (); }
+
+        public override string ToString () { return $"DimFunc({_function ()})"; }
+
+        internal override int Anchor (int width) { return _function (); }
+    }
+
+    internal class DimView : Dim
+    {
+        private readonly int _side;
+
+        public DimView (View view, int side)
+        {
+            Target = view;
+            _side = side;
+        }
+
+        public View Target { get; init; }
+
+        public override bool Equals (object other) { return other is DimView abs && abs.Target == Target; }
+
+        public override int GetHashCode () { return Target.GetHashCode (); }
+
+        public override string ToString ()
+        {
+            if (Target == null)
+            {
+                throw new NullReferenceException ();
+            }
+
+            string tside = _side switch
+                           {
+                               0 => "Height",
+                               1 => "Width",
+                               _ => "unknown"
+                           };
+
+            return $"View({tside},{Target})";
+        }
+
+        internal override int Anchor (int width)
+        {
+            return _side switch
+                   {
+                       0 => Target.Frame.Height,
+                       1 => Target.Frame.Width,
+                       _ => 0
+                   };
+        }
+    }
+}

+ 12 - 21
Terminal.Gui/View/Layout/SizeChangedEventArgs.cs

@@ -1,24 +1,15 @@
-using System;
+namespace Terminal.Gui;
 
-namespace Terminal.Gui; 
+/// <summary>Args for events about Size (e.g. Resized)</summary>
+public class SizeChangedEventArgs : EventArgs
+{
+    /// <summary>Creates a new instance of the <see cref="SizeChangedEventArgs"/> class.</summary>
+    /// <param name="size"></param>
+    public SizeChangedEventArgs (Size size) { Size = size; }
 
-/// <summary>
-/// Args for events about Size (e.g. Resized)
-/// </summary>
-public class SizeChangedEventArgs : EventArgs {
-	/// <summary>
-	/// Creates a new instance of the <see cref="SizeChangedEventArgs" /> class.
-	/// </summary>
-	/// <param name="size"></param>
-	public SizeChangedEventArgs (Size size) => Size = size;
+    /// <summary>Set to <see langword="true"/> to cause the resize to be cancelled, if appropriate.</summary>
+    public bool Cancel { get; set; }
 
-	/// <summary>
-	/// Gets the size the event describes. This should reflect the new/current size after the event.
-	/// </summary>
-	public Size Size { get; }
-
-	/// <summary>
-	/// Set to <see langword="true" /> to cause the resize to be cancelled, if appropriate.
-	/// </summary>
-	public bool Cancel { get; set; }
-}
+    /// <summary>Gets the size the event describes. This should reflect the new/current size after the event.</summary>
+    public Size Size { get; }
+}

+ 1492 - 1365
Terminal.Gui/View/Layout/ViewLayout.cs

@@ -1,1386 +1,1513 @@
-using System;
-using System.Collections.Generic;
-using System.ComponentModel;
+using System.ComponentModel;
 using System.Diagnostics;
-using System.Linq;
 
 namespace Terminal.Gui;
 
 /// <summary>
-///         <para>
-///         Indicates the LayoutStyle for the <see cref="View"/>.
-///         </para>
-///         <para>
+///     <para>Indicates the LayoutStyle for the <see cref="View"/>.</para>
+///     <para>
 ///         If Absolute, the <see cref="View.X"/>, <see cref="View.Y"/>, <see cref="View.Width"/>, and
-///         <see cref="View.Height"/>
-///         objects are all absolute values and are not relative. The position and size of the view is described by
-///         <see cref="View.Frame"/>.
-///         </para>
-///         <para>
+///         <see cref="View.Height"/> objects are all absolute values and are not relative. The position and size of the
+///         view is described by <see cref="View.Frame"/>.
+///     </para>
+///     <para>
 ///         If Computed, one or more of the <see cref="View.X"/>, <see cref="View.Y"/>, <see cref="View.Width"/>, or
-///         <see cref="View.Height"/>
-///         objects are relative to the <see cref="View.SuperView"/> and are computed at layout time.
-///         </para>
-///         <para>
-///         Indicates the LayoutStyle for the <see cref="View"/>.
-///         </para>
-///         <para>
+///         <see cref="View.Height"/> objects are relative to the <see cref="View.SuperView"/> and are computed at layout
+///         time.
+///     </para>
+///     <para>Indicates the LayoutStyle for the <see cref="View"/>.</para>
+///     <para>
 ///         If Absolute, the <see cref="View.X"/>, <see cref="View.Y"/>, <see cref="View.Width"/>, and
-///         <see cref="View.Height"/>
-///         objects are all absolute values and are not relative. The position and size of the view is described by
-///         <see cref="View.Frame"/>.
-///         </para>
-///         <para>
+///         <see cref="View.Height"/> objects are all absolute values and are not relative. The position and size of the
+///         view is described by <see cref="View.Frame"/>.
+///     </para>
+///     <para>
 ///         If Computed, one or more of the <see cref="View.X"/>, <see cref="View.Y"/>, <see cref="View.Width"/>, or
-///         <see cref="View.Height"/>
-///         objects are relative to the <see cref="View.SuperView"/> and are computed at layout time.
-///         </para>
+///         <see cref="View.Height"/> objects are relative to the <see cref="View.SuperView"/> and are computed at layout
+///         time.
+///     </para>
 /// </summary>
-public enum LayoutStyle {
-	/// <summary>
-	/// Indicates the <see cref="View.X"/>, <see cref="View.Y"/>, <see cref="View.Width"/>, and <see cref="View.Height"/>
-	/// objects are all absolute values and are not relative. The position and size of the view is described by
-	/// <see cref="View.Frame"/>.
-	/// </summary>
-	Absolute,
-
-	/// <summary>
-	/// Indicates one or more of the <see cref="View.X"/>, <see cref="View.Y"/>, <see cref="View.Width"/>, or
-	/// <see cref="View.Height"/>
-	/// objects are relative to the <see cref="View.SuperView"/> and are computed at layout time.  The position and size of the
-	/// view
-	/// will be computed based on these objects at layout time. <see cref="View.Frame"/> will provide the absolute computed
-	/// values.
-	/// </summary>
-	Computed
+public enum LayoutStyle
+{
+    /// <summary>
+    ///     Indicates the <see cref="View.X"/>, <see cref="View.Y"/>, <see cref="View.Width"/>, and <see cref="View.Height"/>
+    ///     objects are all absolute values and are not relative. The position and size of the view is described by
+    ///     <see cref="View.Frame"/>.
+    /// </summary>
+    Absolute,
+
+    /// <summary>
+    ///     Indicates one or more of the <see cref="View.X"/>, <see cref="View.Y"/>, <see cref="View.Width"/>, or
+    ///     <see cref="View.Height"/> objects are relative to the <see cref="View.SuperView"/> and are computed at layout time.
+    ///     The position and size of the view will be computed based on these objects at layout time. <see cref="View.Frame"/>
+    ///     will provide the absolute computed values.
+    /// </summary>
+    Computed
 }
 
-public partial class View {
-	bool _autoSize;
-	Rect _frame;
-	Dim _height = Dim.Sized (0);
-	Dim _width = Dim.Sized (0);
-	Pos _x = Pos.At (0);
-	Pos _y = Pos.At (0);
-
-	/// <summary>
-	/// Gets or sets the absolute location and dimension of the view.
-	/// </summary>
-	/// <value>
-	/// The rectangle describing absolute location and dimension of the view,
-	/// in coordinates relative to the <see cref="SuperView"/>'s <see cref="Bounds"/>.
-	/// </value>
-	/// <remarks>
-	///         <para>
-	///         Frame is relative to the <see cref="SuperView"/>'s <see cref="Bounds"/>.
-	///         </para>
-	///         <para>
-	///         Setting Frame will set <see cref="X"/>, <see cref="Y"/>, <see cref="Width"/>, and <see cref="Height"/>
-	///         to the values of the corresponding properties of the <paramref name="value"/> parameter.
-	///         </para>
-	///         <para>
-	///         This causes <see cref="LayoutStyle"/> to be <see cref="LayoutStyle.Absolute"/>.
-	///         </para>
-	///         <para>
-	///         Altering the Frame will eventually (when the view hierarchy is next laid out via  see cref="LayoutSubviews"/>)
-	///         cause <see cref="LayoutSubview(View, Rect)"/> and <see cref="OnDrawContent(Rect)"/> methods to be called.
-	///         </para>
-	/// </remarks>
-	public Rect Frame {
-		get => _frame;
-		set {
-			_frame = new Rect (value.X, value.Y, Math.Max (value.Width, 0), Math.Max (value.Height, 0));
-
-			// If Frame gets set, by definition, the View is now LayoutStyle.Absolute, so
-			// set all Pos/Dim to Absolute values.
-			_x = _frame.X;
-			_y = _frame.Y;
-			_width = _frame.Width;
-			_height = _frame.Height;
-
-			// TODO: Figure out if the below can be optimized.
-			if (IsInitialized /*|| LayoutStyle == LayoutStyle.Absolute*/) {
-				LayoutAdornments ();
-				SetTextFormatterSize ();
-				SetNeedsLayout ();
-				SetNeedsDisplay ();
-			}
-		}
-	}
-
-	/// <summary>
-	/// The frame (specified as a <see cref="Thickness"/>) that separates a View from other SubViews of the same SuperView.
-	/// The margin offsets the <see cref="Bounds"/> from the <see cref="Frame"/>.
-	/// </summary>
-	/// <remarks>
-	///         <para>
-	///         The adornments (<see cref="Margin"/>, <see cref="Border"/>, and <see cref="Padding"/>) are not part of the View's
-	///         content and are not clipped by the View's Clip Area.
-	///         </para>
-	///         <para>
-	///         Changing the size of an adornment (<see cref="Margin"/>, <see cref="Border"/>, or <see cref="Padding"/>)
-	///         will change the size of <see cref="Frame"/> and trigger <see cref="LayoutSubviews"/> to update the layout
-	///         of the <see cref="SuperView"/> and its <see cref="Subviews"/>.
-	///         </para>
-	/// </remarks>
-	public Margin Margin { get; private set; }
-
-	/// <summary>
-	/// The adornment (specified as a <see cref="Thickness"/>) inside of the view that offsets the <see cref="Bounds"/> from the
-	/// <see cref="Margin"/>.
-	/// The Border provides the space for a visual border (drawn using line-drawing glyphs) and the Title.
-	/// The Border expands inward; in other words if `Border.Thickness.Top == 2` the border and
-	/// title will take up the first row and the second row will be filled with spaces.
-	/// </summary>
-	/// <remarks>
-	///         <para>
-	///         <see cref="BorderStyle"/> provides a simple helper for turning a simple border frame on or off.
-	///         </para>
-	///         <para>
-	///         The adornments (<see cref="Margin"/>, <see cref="Border"/>, and <see cref="Padding"/>) are not part of the View's
-	///         content and are not clipped by the View's Clip Area.
-	///         </para>
-	///         <para>
-	///         Changing the size of a frame (<see cref="Margin"/>, <see cref="Border"/>, or <see cref="Padding"/>)
-	///         will change the size of the <see cref="Frame"/> and trigger <see cref="LayoutSubviews"/> to update the layout
-	///         of the
-	///         <see cref="SuperView"/> and its <see cref="Subviews"/>.
-	///         </para>
-	/// </remarks>
-	public Border Border { get; private set; }
-
-	/// <summary>
-	/// Gets or sets whether the view has a one row/col thick border.
-	/// </summary>
-	/// <remarks>
-	///         <para>
-	///         This is a helper for manipulating the view's <see cref="Border"/>. Setting this property to any value other
-	///         than
-	///         <see cref="LineStyle.None"/> is equivalent to setting <see cref="Border"/>'s <see cref="Adornment.Thickness"/>
-	///         to `1` and <see cref="BorderStyle"/> to the value.
-	///         </para>
-	///         <para>
-	///         Setting this property to <see cref="LineStyle.None"/> is equivalent to setting <see cref="Border"/>'s
-	///         <see cref="Adornment.Thickness"/>
-	///         to `0` and <see cref="BorderStyle"/> to <see cref="LineStyle.None"/>.
-	///         </para>
-	///         <para>
-	///         For more advanced customization of the view's border, manipulate see <see cref="Border"/> directly.
-	///         </para>
-	/// </remarks>
-	public LineStyle BorderStyle {
-		get => Border.LineStyle;
-		set {
-			if (value != LineStyle.None) {
-				Border.Thickness = new Thickness (1);
-			} else {
-				Border.Thickness = new Thickness (0);
-			}
-			Border.LineStyle = value;
-			LayoutAdornments ();
-			SetNeedsLayout ();
-		}
-	}
-
-	/// <summary>
-	/// The frame (specified as a <see cref="Thickness"/>) inside of the view that offsets the <see cref="Bounds"/> from the
-	/// <see cref="Border"/>.
-	/// </summary>
-	/// <remarks>
-	///         <para>
-	///         The adornments (<see cref="Margin"/>, <see cref="Border"/>, and <see cref="Padding"/>) are not part of the View's
-	///         content and are not clipped by the View's Clip Area.
-	///         </para>
-	///         <para>
-	///         Changing the size of a frame (<see cref="Margin"/>, <see cref="Border"/>, or <see cref="Padding"/>)
-	///         will change the size of the <see cref="Frame"/> and trigger <see cref="LayoutSubviews"/> to update the layout
-	///         of the
-	///         <see cref="SuperView"/> and its <see cref="Subviews"/>.
-	///         </para>
-	/// </remarks>
-	public Padding Padding { get; private set; }
-
-	/// <summary>
-	///         <para>
-	///         Gets the thickness describing the sum of the Adornments' thicknesses.
-	///         </para>
-	/// </summary>
-	/// <returns>A thickness that describes the sum of the Adornments' thicknesses.</returns>
-	public Thickness GetAdornmentsThickness ()
-	{
-		int left = Margin.Thickness.Left + Border.Thickness.Left + Padding.Thickness.Left;
-		int top = Margin.Thickness.Top + Border.Thickness.Top + Padding.Thickness.Top;
-		int right = Margin.Thickness.Right + Border.Thickness.Right + Padding.Thickness.Right;
-		int bottom = Margin.Thickness.Bottom + Border.Thickness.Bottom + Padding.Thickness.Bottom;
-		return new Thickness (left, top, right, bottom);
-	}
-
-	/// <summary>
-	/// Helper to get the X and Y offset of the Bounds from the Frame. This is the sum of the Left and Top properties of
-	/// <see cref="Margin"/>, <see cref="Border"/> and <see cref="Padding"/>.
-	/// </summary>
-	public Point GetBoundsOffset () => new (Padding?.Thickness.GetInside (Padding.Frame).X ?? 0, Padding?.Thickness.GetInside (Padding.Frame).Y ?? 0);
-
-	/// <summary>
-	/// This internal method is overridden by Adornment to do nothing to prevent recursion during View construction.
-	/// And, because Adornments don't have Adornments. It's internal to support unit tests.
-	/// </summary>
-	/// <param name="adornmentType"></param>
-	/// <exception cref="ArgumentNullException"></exception>
-	/// <exception cref="ArgumentException"></exception>
-	internal virtual Adornment CreateAdornment (Type adornmentType)
-	{
-		void ThicknessChangedHandler (object sender, EventArgs e)
-		{
-			if (IsInitialized) {
-				LayoutAdornments ();
-			}
-			SetNeedsLayout ();
-			SetNeedsDisplay ();
-		}
-
-		Adornment adornment;
-
-		adornment = Activator.CreateInstance (adornmentType, this) as Adornment;
-		adornment.ThicknessChanged += ThicknessChangedHandler;
-
-		return adornment;
-	}
-
-	/// <summary>
-	/// Controls how the View's <see cref="Frame"/> is computed during <see cref="LayoutSubviews"/>. If the style is set to
-	/// <see cref="LayoutStyle.Absolute"/>, LayoutSubviews does not change the <see cref="Frame"/>.
-	/// If the style is <see cref="LayoutStyle.Computed"/> the <see cref="Frame"/> is updated using
-	/// the <see cref="X"/>, <see cref="Y"/>, <see cref="Width"/>, and <see cref="Height"/> properties.
-	/// </summary>
-	/// <remarks>
-	/// <para>
-	/// Setting this property to <see cref="LayoutStyle.Absolute"/> will cause <see cref="Frame"/> to determine the
-	/// size and position of the view. <see cref="X"/> and <see cref="Y"/> will be set to <see cref="Dim.DimAbsolute"/> using <see cref="Frame"/>.
-	/// </para>
-	/// <para>
-	/// Setting this property to <see cref="LayoutStyle.Computed"/> will cause the view to use the <see cref="LayoutSubviews"/> method to 
-	/// size and position of the view. If either of the <see cref="X"/> and <see cref="Y"/> properties are `null` they will be set to <see cref="Pos.PosAbsolute"/> using
-	/// the current value of <see cref="Frame"/>. 
-	/// If either of the <see cref="Width"/> and <see cref="Height"/> properties are `null` they will be set to <see cref="Dim.DimAbsolute"/> using <see cref="Frame"/>.
-	/// </para>
-	/// </remarks>
-	/// <value>The layout style.</value>
-	public LayoutStyle LayoutStyle {
-		get {
-			if (_x is Pos.PosAbsolute && _y is Pos.PosAbsolute && _width is Dim.DimAbsolute && _height is Dim.DimAbsolute) {
-				return LayoutStyle.Absolute;
-			}
-			return LayoutStyle.Computed;
-		}
-	}
-
-	/// <summary>
-	/// The bounds represent the View-relative rectangle used for this view; the area inside of the view where subviews and
-	/// content are presented.
-	/// </summary>
-	/// <value>The rectangle describing the location and size of the area where the views' subviews and content are drawn.</value>
-	/// <remarks>
-	///         <para>
-	///         If <see cref="LayoutStyle"/> is <see cref="LayoutStyle.Computed"/> the value of Bounds is indeterminate until
-	///         the view has been initialized (<see cref="IsInitialized"/> is true) and <see cref="LayoutSubviews"/> has been
-	///         called.
-	///         </para>
-	///         <para>
-	///         Updates to the Bounds updates <see cref="Frame"/>, and has the same effect as updating the
-	///         <see cref="Frame"/>.
-	///         </para>
-	///         <para>
-	///         Altering the Bounds will eventually (when the view is next laid out) cause the
-	///         <see cref="LayoutSubview(View, Rect)"/>
-	///         and <see cref="OnDrawContent(Rect)"/> methods to be called.
-	///         </para>
-	///         <para>
-	///         Because <see cref="Bounds"/> coordinates are relative to the upper-left corner of the <see cref="View"/>,
-	///         the coordinates of the upper-left corner of the rectangle returned by this property are (0,0).
-	///         Use this property to obtain the size of the area of the view for tasks such as drawing the view's contents.
-	///         </para>
-	/// </remarks>
-	public virtual Rect Bounds {
-		get {
+public partial class View
+{
+    private bool _autoSize;
+    private Rect _frame;
+    private Dim _height = Dim.Sized (0);
+    private Dim _width = Dim.Sized (0);
+    private Pos _x = Pos.At (0);
+    private Pos _y = Pos.At (0);
+
+    /// <summary>
+    ///     Gets or sets a flag that determines whether the View will be automatically resized to fit the <see cref="Text"/>
+    ///     within <see cref="Bounds"/>.
+    ///     <para>
+    ///         The default is <see langword="false"/>. Set to <see langword="true"/> to turn on AutoSize. If
+    ///         <see langword="true"/> then <see cref="Width"/> and <see cref="Height"/> will be used if <see cref="Text"/> can
+    ///         fit; if <see cref="Text"/> won't fit the view will be resized as needed.
+    ///     </para>
+    ///     <para>
+    ///         If <see cref="AutoSize"/> is set to <see langword="true"/> then <see cref="Width"/> and <see cref="Height"/>
+    ///         will be changed to <see cref="Dim.DimAbsolute"/> if they are not already.
+    ///     </para>
+    ///     <para>
+    ///         If <see cref="AutoSize"/> is set to <see langword="false"/> then <see cref="Width"/> and <see cref="Height"/>
+    ///         will left unchanged.
+    ///     </para>
+    /// </summary>
+    public virtual bool AutoSize
+    {
+        get => _autoSize;
+        set
+        {
+            bool v = ResizeView (value);
+            TextFormatter.AutoSize = v;
+
+            if (_autoSize != v)
+            {
+                _autoSize = v;
+                TextFormatter.NeedsFormat = true;
+                UpdateTextFormatterText ();
+                OnResizeNeeded ();
+            }
+        }
+    }
+
+    /// <summary>
+    ///     The adornment (specified as a <see cref="Thickness"/>) inside of the view that offsets the <see cref="Bounds"/>
+    ///     from the <see cref="Margin"/>. The Border provides the space for a visual border (drawn using line-drawing glyphs)
+    ///     and the Title. The Border expands inward; in other words if `Border.Thickness.Top == 2` the border and title will
+    ///     take up the first row and the second row will be filled with spaces.
+    /// </summary>
+    /// <remarks>
+    ///     <para><see cref="BorderStyle"/> provides a simple helper for turning a simple border frame on or off.</para>
+    ///     <para>
+    ///         The adornments (<see cref="Margin"/>, <see cref="Border"/>, and <see cref="Padding"/>) are not part of the
+    ///         View's content and are not clipped by the View's Clip Area.
+    ///     </para>
+    ///     <para>
+    ///         Changing the size of a frame (<see cref="Margin"/>, <see cref="Border"/>, or <see cref="Padding"/>) will change
+    ///         the size of the <see cref="Frame"/> and trigger <see cref="LayoutSubviews"/> to update the layout of the
+    ///         <see cref="SuperView"/> and its <see cref="Subviews"/>.
+    ///     </para>
+    /// </remarks>
+    public Border Border { get; private set; }
+
+    /// <summary>Gets or sets whether the view has a one row/col thick border.</summary>
+    /// <remarks>
+    ///     <para>
+    ///         This is a helper for manipulating the view's <see cref="Border"/>. Setting this property to any value other
+    ///         than <see cref="LineStyle.None"/> is equivalent to setting <see cref="Border"/>'s
+    ///         <see cref="Adornment.Thickness"/> to `1` and <see cref="BorderStyle"/> to the value.
+    ///     </para>
+    ///     <para>
+    ///         Setting this property to <see cref="LineStyle.None"/> is equivalent to setting <see cref="Border"/>'s
+    ///         <see cref="Adornment.Thickness"/> to `0` and <see cref="BorderStyle"/> to <see cref="LineStyle.None"/>.
+    ///     </para>
+    ///     <para>For more advanced customization of the view's border, manipulate see <see cref="Border"/> directly.</para>
+    /// </remarks>
+    public LineStyle BorderStyle
+    {
+        get => Border.LineStyle;
+        set
+        {
+            if (value != LineStyle.None)
+            {
+                Border.Thickness = new Thickness (1);
+            }
+            else
+            {
+                Border.Thickness = new Thickness (0);
+            }
+
+            Border.LineStyle = value;
+            LayoutAdornments ();
+            SetNeedsLayout ();
+        }
+    }
+
+    /// <summary>
+    ///     The bounds represent the View-relative rectangle used for this view; the area inside of the view where subviews and
+    ///     content are presented.
+    /// </summary>
+    /// <value>The rectangle describing the location and size of the area where the views' subviews and content are drawn.</value>
+    /// <remarks>
+    ///     <para>
+    ///         If <see cref="LayoutStyle"/> is <see cref="LayoutStyle.Computed"/> the value of Bounds is indeterminate until
+    ///         the view has been initialized (<see cref="IsInitialized"/> is true) and <see cref="LayoutSubviews"/> has been
+    ///         called.
+    ///     </para>
+    ///     <para>
+    ///         Updates to the Bounds updates <see cref="Frame"/>, and has the same effect as updating the
+    ///         <see cref="Frame"/>.
+    ///     </para>
+    ///     <para>
+    ///         Altering the Bounds will eventually (when the view is next laid out) cause the
+    ///         <see cref="LayoutSubview(View, Rect)"/> and <see cref="OnDrawContent(Rect)"/> methods to be called.
+    ///     </para>
+    ///     <para>
+    ///         Because <see cref="Bounds"/> coordinates are relative to the upper-left corner of the <see cref="View"/>, the
+    ///         coordinates of the upper-left corner of the rectangle returned by this property are (0,0). Use this property to
+    ///         obtain the size of the area of the view for tasks such as drawing the view's contents.
+    ///     </para>
+    /// </remarks>
+    public virtual Rect Bounds
+    {
+        get
+        {
 #if DEBUG
-			if (LayoutStyle == LayoutStyle.Computed && !IsInitialized) {
-				Debug.WriteLine ($"WARNING: Bounds is being accessed before the View has been initialized. This is likely a bug in {this}");
-			}
+            if (LayoutStyle == LayoutStyle.Computed && !IsInitialized)
+            {
+                Debug.WriteLine ($"WARNING: Bounds is being accessed before the View has been initialized. This is likely a bug in {this}");
+            }
 #endif // DEBUG
-			// BUGBUG: I think there's a bug here. This should be && not ||
-			if (Margin == null || Border == null || Padding == null) {
-				return new Rect (default, Frame.Size);
-			}
-			var width = Math.Max (0, Frame.Size.Width - Margin.Thickness.Horizontal - Border.Thickness.Horizontal - Padding.Thickness.Horizontal);
-			var height = Math.Max (0, Frame.Size.Height - Margin.Thickness.Vertical - Border.Thickness.Vertical - Padding.Thickness.Vertical);
-			return new Rect (Point.Empty, new Size (width, height));
-		}
-		set {
-			// TODO: Should we enforce Bounds.X/Y == 0? The code currently ignores value.X/Y which is
-			// TODO: correct behavior, but is silent. Perhaps an exception?
+
+            // BUGBUG: I think there's a bug here. This should be && not ||
+            if (Margin == null || Border == null || Padding == null)
+            {
+                return new Rect (default (Point), Frame.Size);
+            }
+
+            int width = Math.Max (0, Frame.Size.Width - Margin.Thickness.Horizontal - Border.Thickness.Horizontal - Padding.Thickness.Horizontal);
+            int height = Math.Max (0, Frame.Size.Height - Margin.Thickness.Vertical - Border.Thickness.Vertical - Padding.Thickness.Vertical);
+
+            return new Rect (Point.Empty, new Size (width, height));
+        }
+        set
+        {
+            // TODO: Should we enforce Bounds.X/Y == 0? The code currently ignores value.X/Y which is
+            // TODO: correct behavior, but is silent. Perhaps an exception?
 #if DEBUG
-			if (value.Location != Point.Empty) {
-				Debug.WriteLine ($"WARNING: Bounds.Location must always be 0,0. Location ({value.Location}) is ignored. {this}");
-			}
+            if (value.Location != Point.Empty)
+            {
+                Debug.WriteLine ($"WARNING: Bounds.Location must always be 0,0. Location ({value.Location}) is ignored. {this}");
+            }
 #endif // DEBUG
-			Frame = new Rect (Frame.Location,
-				new Size (
-					value.Size.Width + Margin.Thickness.Horizontal + Border.Thickness.Horizontal + Padding.Thickness.Horizontal,
-					value.Size.Height + Margin.Thickness.Vertical + Border.Thickness.Vertical + Padding.Thickness.Vertical
-				)
-			);
-		}
-	}
-
-	/// <summary>
-	/// Gets or sets the X position for the view (the column).
-	/// </summary>
-	/// <value>The <see cref="Pos"/> object representing the X position.</value>
-	/// <remarks>
-	///         <para>
-	///         If set to a relative value (e.g. <see cref="Pos.Center"/>) the value is indeterminate until the
-	///         view has been initialized (<see cref="IsInitialized"/> is true) and <see cref="SetRelativeLayout(Rect)"/> has been
-	///         called.
-	///         </para>
-	///         <para>
-	///         Changing this property will eventually (when the view is next drawn) cause the
-	///          <see cref="LayoutSubview(View, Rect)"/> and
-	///         <see cref="OnDrawContent(Rect)"/> methods to be called.
-	///         </para>
-	///         <para>
-	///         Changing this property will cause <see cref="Frame"/> to be updated. If
-	///         the new value is not of type <see cref="Pos.PosAbsolute"/> the <see cref="LayoutStyle"/> will change to
-	///         <see cref="LayoutStyle.Computed"/>.
-	///         </para>
-	///         <para>
-	///         The default value is <c>Pos.At (0)</c>.
-	///         </para>
-	/// </remarks>
-	public Pos X {
-		get => VerifyIsInitialized (_x, nameof (X));
-		set {
-			_x = value ?? throw new ArgumentNullException (nameof (value), @$"{nameof (X)} cannot be null");
-			OnResizeNeeded ();
-		}
-	}
-
-	/// <summary>
-	/// Gets or sets the Y position for the view (the row).
-	/// </summary>
-	/// <value>The <see cref="Pos"/> object representing the Y position.</value>
-	/// <remarks>
-	///         <para>
-	///         If set to a relative value (e.g. <see cref="Pos.Center"/>) the value is indeterminate until the
-	///         view has been initialized (<see cref="IsInitialized"/> is true) and <see cref="SetRelativeLayout(Rect)"/> has been
-	///         called.
-	///         </para>
-	///         <para>
-	///         Changing this property will eventually (when the view is next drawn) cause the
-	///         <see cref="LayoutSubview(View, Rect)"/> and
-	///         <see cref="OnDrawContent(Rect)"/> methods to be called.
-	///         </para>
-	///         <para>
-	///         Changing this property will cause <see cref="Frame"/> to be updated. If
-	///         the new value is not of type <see cref="Pos.PosAbsolute"/> the <see cref="LayoutStyle"/> will change to
-	///         <see cref="LayoutStyle.Computed"/>.
-	///         </para>
-	///         <para>
-	///         The default value is <c>Pos.At (0)</c>.
-	///         </para>
-	/// </remarks>
-	public Pos Y {
-		get => VerifyIsInitialized (_y, nameof (Y));
-		set {
-			_y = value ?? throw new ArgumentNullException (nameof (value), @$"{nameof (Y)} cannot be null");
-			OnResizeNeeded ();
-		}
-	}
-
-	/// <summary>
-	/// Gets or sets the width dimension of the view.
-	/// </summary>
-	/// <value>The <see cref="Dim"/> object representing the width of the view (the number of columns).</value>
-	/// <remarks>
-	///         <para>
-	///         If set to a relative value (e.g. <see cref="Dim.Fill(int)"/>) the value is indeterminate until the
-	///         view has been initialized (<see cref="IsInitialized"/> is true) and <see cref="SetRelativeLayout(Rect)"/> has been
-	///         called.
-	///         </para>
-	///         <para>
-	///         Changing this property will eventually (when the view is next drawn) cause the
-	///         <see cref="LayoutSubview(View, Rect)"/>
-	///         and <see cref="OnDrawContent(Rect)"/> methods to be called.
-	///         </para>
-	///         <para>
-	///         Changing this property will cause <see cref="Frame"/> to be updated. If
-	///         the new value is not of type <see cref="Dim.DimAbsolute"/> the <see cref="LayoutStyle"/> will change to
-	///         <see cref="LayoutStyle.Computed"/>.
-	///         </para>
-	///         <para>
-	///         The default value is <c>Dim.Sized (0)</c>.
-	///         </para>
-	/// </remarks>
-	public Dim Width {
-		get => VerifyIsInitialized (_width, nameof (Width));
-		set {
-			_width = value ?? throw new ArgumentNullException (nameof (value), @$"{nameof (Width)} cannot be null");
-
-			if (ValidatePosDim) {
-				CheckDimAuto ();
-				var isValidNewAutSize = AutoSize && IsValidAutoSizeWidth (_width);
-
-				if (IsAdded && AutoSize && !isValidNewAutSize) {
-					throw new InvalidOperationException ("Must set AutoSize to false before set the Width.");
-				}
-			}
-			OnResizeNeeded ();
-		}
-	}
-
-	/// <summary>
-	/// Gets or sets the height dimension of the view.
-	/// Gets or sets whether validation of <see cref="Pos"/> and <see cref="Dim"/> occurs.
-	/// </summary>
-	/// <value>The <see cref="Dim"/> object representing the height of the view (the number of rows).</value>
-	/// <remarks>
-	///         <para>
-	///         If set to a relative value (e.g. <see cref="Dim.Fill(int)"/>) the value is indeterminate until the
-	///         view has been initialized (<see cref="IsInitialized"/> is true) and <see cref="SetRelativeLayout(Rect)"/> has been
-	///         called.
-	///         </para>
-	///         <para>
-	///         Changing this property will eventually (when the view is next drawn) cause the
-	///         <see cref="LayoutSubview(View, Rect)"/>
-	///         and <see cref="OnDrawContent(Rect)"/> methods to be called.
-	///         </para>
-	///         <para>
-	///         Changing this property will cause <see cref="Frame"/> to be updated. If
-	///         the new value is not of type <see cref="Dim.DimAbsolute"/> the <see cref="LayoutStyle"/> will change to
-	///         <see cref="LayoutStyle.Computed"/>.
-	///         </para>
-	///         <para>
-	///         The default value is <c>Dim.Sized (0)</c>.
-	///         </para>
-	/// </remarks>
-	public Dim Height {
-		get => VerifyIsInitialized (_height, nameof (Height));
-		set {
-			_height = value ?? throw new ArgumentNullException (nameof (value), @$"{nameof (Height)} cannot be null");
-
-			if (ValidatePosDim) {
-				CheckDimAuto ();
-				var isValidNewAutSize = AutoSize && IsValidAutoSizeHeight (_height);
-
-				if (IsAdded && AutoSize && !isValidNewAutSize) {
-					throw new InvalidOperationException ("Must set AutoSize to false before setting the Height.");
-				}
-			}
-			OnResizeNeeded ();
-		}
-	}
-
-	/// <summary>
-	/// Gets or sets whether validation of <see cref="Pos"/> and <see cref="Dim"/> occurs.
-	/// </summary>
-	/// <remarks>
-	/// Setting this to <see langword="true"/> will enable validation of <see cref="X"/>, <see cref="Y"/>, <see cref="Width"/>,
-	/// and <see cref="Height"/>
-	/// during set operations and in <see cref="LayoutSubviews"/>. If invalid settings are discovered exceptions will be thrown
-	/// indicating the error.
-	/// This will impose a performance penalty and thus should only be used for debugging.
-	/// </remarks>
-	public bool ValidatePosDim { get; set; }
-
-	/// <summary>
-	/// Throws an <see cref="InvalidOperationException"/> if any SubViews are using Dim objects that depend on this
-	/// Views dimensions.
-	/// </summary>
-	/// <exception cref="InvalidOperationException"></exception>
-	void CheckDimAuto ()
-	{
-		if (!ValidatePosDim || !IsInitialized || (Width is not Dim.DimAuto && Height is not Dim.DimAuto)) {
-			return;
-		}
-
-		void ThrowInvalid (View view, object checkPosDim, string name)
-		{
-			// TODO: Figure out how to make CheckDimAuto deal with PosCombine
-			object bad = null;
-			switch (checkPosDim) {
-			case Pos pos and not Pos.PosAbsolute and not Pos.PosView and not Pos.PosCombine:
-				bad = pos;
-				break;
-			case Pos pos and Pos.PosCombine:
-				// Recursively check for not Absolute or not View
-				ThrowInvalid (view, (pos as Pos.PosCombine)._left, name);
-				ThrowInvalid (view, (pos as Pos.PosCombine)._right, name);
-				break;
-
-			case Dim dim and not Dim.DimAbsolute and not Dim.DimView and not Dim.DimCombine:
-				bad = dim;
-				break;
-			case Dim dim and Dim.DimCombine:
-				// Recursively check for not Absolute or not View
-				ThrowInvalid (view, (dim as Dim.DimCombine)._left, name);
-				ThrowInvalid (view, (dim as Dim.DimCombine)._right, name);
-				break;
-			}
-
-			if (bad != null) {
-				throw new InvalidOperationException (
-					@$"{view.GetType ().Name}.{name} = {bad.GetType ().Name} which depends on the SuperView's dimensions and the SuperView uses Dim.Auto.");
-			}
-		}
-
-		// Verify none of the subviews are using Dim objects that depend on the SuperView's dimensions.
-		foreach (var view in Subviews) {
-			if (Width is Dim.DimAuto { _min: null }) {
-				ThrowInvalid (view, view.Width, nameof (view.Width));
-				ThrowInvalid (view, view.X, nameof (view.X));
-			}
-			if (Height is Dim.DimAuto { _min: null }) {
-				ThrowInvalid (view, view.Height, nameof (view.Height));
-				ThrowInvalid (view, view.Y, nameof (view.Y));
-			}
-		}
-	}
-
-	internal bool LayoutNeeded { get; private set; } = true;
-
-	/// <summary>
-	/// Gets or sets a flag that determines whether the View will be automatically resized to fit the <see cref="Text"/>
-	/// within <see cref="Bounds"/>.
-	/// <para>
-	/// The default is <see langword="false"/>. Set to <see langword="true"/> to turn on AutoSize. If <see langword="true"/>
-	/// then
-	/// <see cref="Width"/> and <see cref="Height"/> will be used if <see cref="Text"/> can fit;
-	/// if <see cref="Text"/> won't fit the view will be resized as needed.
-	/// </para>
-	/// <para>
-	/// If <see cref="AutoSize"/> is set to <see langword="true"/> then <see cref="Width"/> and <see cref="Height"/>
-	/// will be changed to <see cref="Dim.DimAbsolute"/> if they are not already.
-	/// </para>
-	/// <para>
-	/// If <see cref="AutoSize"/> is set to <see langword="false"/> then <see cref="Width"/> and <see cref="Height"/>
-	/// will left unchanged.
-	/// </para>
-	/// </summary>
-	public virtual bool AutoSize {
-		get => _autoSize;
-		set {
-			var v = ResizeView (value);
-			TextFormatter.AutoSize = v;
-			if (_autoSize != v) {
-				_autoSize = v;
-				TextFormatter.NeedsFormat = true;
-				UpdateTextFormatterText ();
-				OnResizeNeeded ();
-			}
-		}
-	}
-
-	/// <summary>
-	/// Event called only once when the <see cref="View"/> is being initialized for the first time.
-	/// Allows configurations and assignments to be performed before the <see cref="View"/> being shown.
-	/// This derived from <see cref="ISupportInitializeNotification"/> to allow notify all the views that are being
-	/// initialized.
-	/// </summary>
-	public event EventHandler Initialized;
-
-
-
-	// Diagnostics to highlight when X or Y is read before the view has been initialized
-	Pos VerifyIsInitialized (Pos pos, string member)
-	{
+            Frame = new Rect (
+                              Frame.Location,
+                              new Size (
+                                        value.Size.Width + Margin.Thickness.Horizontal + Border.Thickness.Horizontal + Padding.Thickness.Horizontal,
+                                        value.Size.Height + Margin.Thickness.Vertical + Border.Thickness.Vertical + Padding.Thickness.Vertical
+                                       )
+                             );
+        }
+    }
+
+    /// <summary>Gets or sets the absolute location and dimension of the view.</summary>
+    /// <value>
+    ///     The rectangle describing absolute location and dimension of the view, in coordinates relative to the
+    ///     <see cref="SuperView"/>'s <see cref="Bounds"/>.
+    /// </value>
+    /// <remarks>
+    ///     <para>Frame is relative to the <see cref="SuperView"/>'s <see cref="Bounds"/>.</para>
+    ///     <para>
+    ///         Setting Frame will set <see cref="X"/>, <see cref="Y"/>, <see cref="Width"/>, and <see cref="Height"/> to the
+    ///         values of the corresponding properties of the <paramref name="value"/> parameter.
+    ///     </para>
+    ///     <para>This causes <see cref="LayoutStyle"/> to be <see cref="LayoutStyle.Absolute"/>.</para>
+    ///     <para>
+    ///         Altering the Frame will eventually (when the view hierarchy is next laid out via  see cref="LayoutSubviews"/>)
+    ///         cause <see cref="LayoutSubview(View, Rect)"/> and <see cref="OnDrawContent(Rect)"/> methods to be called.
+    ///     </para>
+    /// </remarks>
+    public Rect Frame
+    {
+        get => _frame;
+        set
+        {
+            _frame = new Rect (value.X, value.Y, Math.Max (value.Width, 0), Math.Max (value.Height, 0));
+
+            // If Frame gets set, by definition, the View is now LayoutStyle.Absolute, so
+            // set all Pos/Dim to Absolute values.
+            _x = _frame.X;
+            _y = _frame.Y;
+            _width = _frame.Width;
+            _height = _frame.Height;
+
+            // TODO: Figure out if the below can be optimized.
+            if (IsInitialized /*|| LayoutStyle == LayoutStyle.Absolute*/)
+            {
+                LayoutAdornments ();
+                SetTextFormatterSize ();
+                SetNeedsLayout ();
+                SetNeedsDisplay ();
+            }
+        }
+    }
+
+    /// <summary>
+    ///     Gets or sets the height dimension of the view. Gets or sets whether validation of <see cref="Pos"/> and
+    ///     <see cref="Dim"/> occurs.
+    /// </summary>
+    /// <value>The <see cref="Dim"/> object representing the height of the view (the number of rows).</value>
+    /// <remarks>
+    ///     <para>
+    ///         If set to a relative value (e.g. <see cref="Dim.Fill(int)"/>) the value is indeterminate until the view has
+    ///         been initialized (<see cref="IsInitialized"/> is true) and <see cref="SetRelativeLayout(Rect)"/> has been
+    ///         called.
+    ///     </para>
+    ///     <para>
+    ///         Changing this property will eventually (when the view is next drawn) cause the
+    ///         <see cref="LayoutSubview(View, Rect)"/> and <see cref="OnDrawContent(Rect)"/> methods to be called.
+    ///     </para>
+    ///     <para>
+    ///         Changing this property will cause <see cref="Frame"/> to be updated. If the new value is not of type
+    ///         <see cref="Dim.DimAbsolute"/> the <see cref="LayoutStyle"/> will change to <see cref="LayoutStyle.Computed"/>.
+    ///     </para>
+    ///     <para>The default value is <c>Dim.Sized (0)</c>.</para>
+    /// </remarks>
+    public Dim Height
+    {
+        get => VerifyIsInitialized (_height, nameof (Height));
+        set
+        {
+            _height = value ?? throw new ArgumentNullException (nameof (value), @$"{nameof (Height)} cannot be null");
+
+            if (ValidatePosDim)
+            {
+                CheckDimAuto ();
+                bool isValidNewAutSize = AutoSize && IsValidAutoSizeHeight (_height);
+
+                if (IsAdded && AutoSize && !isValidNewAutSize)
+                {
+                    throw new InvalidOperationException ("Must set AutoSize to false before setting the Height.");
+                }
+            }
+
+            OnResizeNeeded ();
+        }
+    }
+
+    /// <summary>
+    ///     Controls how the View's <see cref="Frame"/> is computed during <see cref="LayoutSubviews"/>. If the style is set to
+    ///     <see cref="LayoutStyle.Absolute"/>, LayoutSubviews does not change the <see cref="Frame"/>. If the style is
+    ///     <see cref="LayoutStyle.Computed"/> the <see cref="Frame"/> is updated using the <see cref="X"/>, <see cref="Y"/>,
+    ///     <see cref="Width"/>, and <see cref="Height"/> properties.
+    /// </summary>
+    /// <remarks>
+    ///     <para>
+    ///         Setting this property to <see cref="LayoutStyle.Absolute"/> will cause <see cref="Frame"/> to determine the
+    ///         size and position of the view. <see cref="X"/> and <see cref="Y"/> will be set to <see cref="Dim.DimAbsolute"/>
+    ///         using <see cref="Frame"/>.
+    ///     </para>
+    ///     <para>
+    ///         Setting this property to <see cref="LayoutStyle.Computed"/> will cause the view to use the
+    ///         <see cref="LayoutSubviews"/> method to size and position of the view. If either of the <see cref="X"/> and
+    ///         <see cref="Y"/> properties are `null` they will be set to <see cref="Pos.PosAbsolute"/> using the current value
+    ///         of <see cref="Frame"/>. If either of the <see cref="Width"/> and <see cref="Height"/> properties are `null`
+    ///         they will be set to <see cref="Dim.DimAbsolute"/> using <see cref="Frame"/>.
+    ///     </para>
+    /// </remarks>
+    /// <value>The layout style.</value>
+    public LayoutStyle LayoutStyle
+    {
+        get
+        {
+            if (_x is Pos.PosAbsolute && _y is Pos.PosAbsolute && _width is Dim.DimAbsolute && _height is Dim.DimAbsolute)
+            {
+                return LayoutStyle.Absolute;
+            }
+
+            return LayoutStyle.Computed;
+        }
+    }
+
+    /// <summary>
+    ///     The frame (specified as a <see cref="Thickness"/>) that separates a View from other SubViews of the same SuperView.
+    ///     The margin offsets the <see cref="Bounds"/> from the <see cref="Frame"/>.
+    /// </summary>
+    /// <remarks>
+    ///     <para>
+    ///         The adornments (<see cref="Margin"/>, <see cref="Border"/>, and <see cref="Padding"/>) are not part of the
+    ///         View's content and are not clipped by the View's Clip Area.
+    ///     </para>
+    ///     <para>
+    ///         Changing the size of an adornment (<see cref="Margin"/>, <see cref="Border"/>, or <see cref="Padding"/>) will
+    ///         change the size of <see cref="Frame"/> and trigger <see cref="LayoutSubviews"/> to update the layout of the
+    ///         <see cref="SuperView"/> and its <see cref="Subviews"/>.
+    ///     </para>
+    /// </remarks>
+    public Margin Margin { get; private set; }
+
+    /// <summary>
+    ///     The frame (specified as a <see cref="Thickness"/>) inside of the view that offsets the <see cref="Bounds"/> from
+    ///     the <see cref="Border"/>.
+    /// </summary>
+    /// <remarks>
+    ///     <para>
+    ///         The adornments (<see cref="Margin"/>, <see cref="Border"/>, and <see cref="Padding"/>) are not part of the
+    ///         View's content and are not clipped by the View's Clip Area.
+    ///     </para>
+    ///     <para>
+    ///         Changing the size of a frame (<see cref="Margin"/>, <see cref="Border"/>, or <see cref="Padding"/>) will change
+    ///         the size of the <see cref="Frame"/> and trigger <see cref="LayoutSubviews"/> to update the layout of the
+    ///         <see cref="SuperView"/> and its <see cref="Subviews"/>.
+    ///     </para>
+    /// </remarks>
+    public Padding Padding { get; private set; }
+
+    /// <summary>Gets or sets whether validation of <see cref="Pos"/> and <see cref="Dim"/> occurs.</summary>
+    /// <remarks>
+    ///     Setting this to <see langword="true"/> will enable validation of <see cref="X"/>, <see cref="Y"/>,
+    ///     <see cref="Width"/>, and <see cref="Height"/> during set operations and in <see cref="LayoutSubviews"/>. If invalid
+    ///     settings are discovered exceptions will be thrown indicating the error. This will impose a performance penalty and
+    ///     thus should only be used for debugging.
+    /// </remarks>
+    public bool ValidatePosDim { get; set; }
+
+    /// <summary>Gets or sets the width dimension of the view.</summary>
+    /// <value>The <see cref="Dim"/> object representing the width of the view (the number of columns).</value>
+    /// <remarks>
+    ///     <para>
+    ///         If set to a relative value (e.g. <see cref="Dim.Fill(int)"/>) the value is indeterminate until the view has
+    ///         been initialized (<see cref="IsInitialized"/> is true) and <see cref="SetRelativeLayout(Rect)"/> has been
+    ///         called.
+    ///     </para>
+    ///     <para>
+    ///         Changing this property will eventually (when the view is next drawn) cause the
+    ///         <see cref="LayoutSubview(View, Rect)"/> and <see cref="OnDrawContent(Rect)"/> methods to be called.
+    ///     </para>
+    ///     <para>
+    ///         Changing this property will cause <see cref="Frame"/> to be updated. If the new value is not of type
+    ///         <see cref="Dim.DimAbsolute"/> the <see cref="LayoutStyle"/> will change to <see cref="LayoutStyle.Computed"/>.
+    ///     </para>
+    ///     <para>The default value is <c>Dim.Sized (0)</c>.</para>
+    /// </remarks>
+    public Dim Width
+    {
+        get => VerifyIsInitialized (_width, nameof (Width));
+        set
+        {
+            _width = value ?? throw new ArgumentNullException (nameof (value), @$"{nameof (Width)} cannot be null");
+
+            if (ValidatePosDim)
+            {
+                CheckDimAuto ();
+                bool isValidNewAutSize = AutoSize && IsValidAutoSizeWidth (_width);
+
+                if (IsAdded && AutoSize && !isValidNewAutSize)
+                {
+                    throw new InvalidOperationException ("Must set AutoSize to false before set the Width.");
+                }
+            }
+
+            OnResizeNeeded ();
+        }
+    }
+
+    /// <summary>Gets or sets the X position for the view (the column).</summary>
+    /// <value>The <see cref="Pos"/> object representing the X position.</value>
+    /// <remarks>
+    ///     <para>
+    ///         If set to a relative value (e.g. <see cref="Pos.Center"/>) the value is indeterminate until the view has been
+    ///         initialized (<see cref="IsInitialized"/> is true) and <see cref="SetRelativeLayout(Rect)"/> has been called.
+    ///     </para>
+    ///     <para>
+    ///         Changing this property will eventually (when the view is next drawn) cause the
+    ///         <see cref="LayoutSubview(View, Rect)"/> and <see cref="OnDrawContent(Rect)"/> methods to be called.
+    ///     </para>
+    ///     <para>
+    ///         Changing this property will cause <see cref="Frame"/> to be updated. If the new value is not of type
+    ///         <see cref="Pos.PosAbsolute"/> the <see cref="LayoutStyle"/> will change to <see cref="LayoutStyle.Computed"/>.
+    ///     </para>
+    ///     <para>The default value is <c>Pos.At (0)</c>.</para>
+    /// </remarks>
+    public Pos X
+    {
+        get => VerifyIsInitialized (_x, nameof (X));
+        set
+        {
+            _x = value ?? throw new ArgumentNullException (nameof (value), @$"{nameof (X)} cannot be null");
+            OnResizeNeeded ();
+        }
+    }
+
+    /// <summary>Gets or sets the Y position for the view (the row).</summary>
+    /// <value>The <see cref="Pos"/> object representing the Y position.</value>
+    /// <remarks>
+    ///     <para>
+    ///         If set to a relative value (e.g. <see cref="Pos.Center"/>) the value is indeterminate until the view has been
+    ///         initialized (<see cref="IsInitialized"/> is true) and <see cref="SetRelativeLayout(Rect)"/> has been called.
+    ///     </para>
+    ///     <para>
+    ///         Changing this property will eventually (when the view is next drawn) cause the
+    ///         <see cref="LayoutSubview(View, Rect)"/> and <see cref="OnDrawContent(Rect)"/> methods to be called.
+    ///     </para>
+    ///     <para>
+    ///         Changing this property will cause <see cref="Frame"/> to be updated. If the new value is not of type
+    ///         <see cref="Pos.PosAbsolute"/> the <see cref="LayoutStyle"/> will change to <see cref="LayoutStyle.Computed"/>.
+    ///     </para>
+    ///     <para>The default value is <c>Pos.At (0)</c>.</para>
+    /// </remarks>
+    public Pos Y
+    {
+        get => VerifyIsInitialized (_y, nameof (Y));
+        set
+        {
+            _y = value ?? throw new ArgumentNullException (nameof (value), @$"{nameof (Y)} cannot be null");
+            OnResizeNeeded ();
+        }
+    }
+
+    internal bool LayoutNeeded { get; private set; } = true;
+
+    /// <summary>
+    ///     Event called only once when the <see cref="View"/> is being initialized for the first time. Allows configurations
+    ///     and assignments to be performed before the <see cref="View"/> being shown. This derived from
+    ///     <see cref="ISupportInitializeNotification"/> to allow notify all the views that are being initialized.
+    /// </summary>
+    public event EventHandler Initialized;
+
+    /// <summary>
+    ///     Converts a <see cref="Bounds"/>-relative coordinate to a screen-relative coordinate. The output is optionally
+    ///     clamped to the screen dimensions.
+    /// </summary>
+    /// <param name="x"><see cref="Bounds"/>-relative column.</param>
+    /// <param name="y"><see cref="Bounds"/>-relative row.</param>
+    /// <param name="rx">Absolute column; screen-relative.</param>
+    /// <param name="ry">Absolute row; screen-relative.</param>
+    /// <param name="clamped">
+    ///     If <see langword="true"/>, <paramref name="rx"/> and <paramref name="ry"/> will be clamped to the screen dimensions
+    ///     (will never be negative and will always be less than <see cref="ConsoleDriver.Cols"/> and
+    ///     <see cref="ConsoleDriver.Rows"/>, respectively.
+    /// </param>
+    public virtual void BoundsToScreen (int x, int y, out int rx, out int ry, bool clamped = true)
+    {
+        Point boundsOffset = GetBoundsOffset ();
+        rx = x + Frame.X + boundsOffset.X;
+        ry = y + Frame.Y + boundsOffset.Y;
+
+        View super = SuperView;
+
+        while (super != null)
+        {
+            boundsOffset = super.GetBoundsOffset ();
+            rx += super.Frame.X + boundsOffset.X;
+            ry += super.Frame.Y + boundsOffset.Y;
+            super = super.SuperView;
+        }
+
+        // The following ensures that the cursor is always in the screen boundaries.
+        if (clamped)
+        {
+            ry = Math.Min (ry, Driver.Rows - 1);
+            rx = Math.Min (rx, Driver.Cols - 1);
+        }
+    }
+
+    /// <summary>Converts a <see cref="Bounds"/>-relative region to a screen-relative region.</summary>
+    public Rect BoundsToScreen (Rect region)
+    {
+        BoundsToScreen (region.X, region.Y, out int x, out int y, false);
+
+        return new Rect (x, y, region.Width, region.Height);
+    }
+
+    /// <summary>Finds which view that belong to the <paramref name="start"/> superview at the provided location.</summary>
+    /// <param name="start">The superview where to look for.</param>
+    /// <param name="x">The column location in the superview.</param>
+    /// <param name="y">The row location in the superview.</param>
+    /// <param name="resx">The found view screen relative column location.</param>
+    /// <param name="resy">The found view screen relative row location.</param>
+    /// <returns>
+    ///     The view that was found at the <praramref name="x"/> and <praramref name="y"/> coordinates. <see langword="null"/>
+    ///     if no view was found.
+    /// </returns>
+    public static View FindDeepestView (View start, int x, int y, out int resx, out int resy)
+    {
+        resy = resx = 0;
+
+        if (start == null || !start.Frame.Contains (x, y))
+        {
+            return null;
+        }
+
+        Rect startFrame = start.Frame;
+
+        if (start.InternalSubviews != null)
+        {
+            int count = start.InternalSubviews.Count;
+
+            if (count > 0)
+            {
+                Point boundsOffset = start.GetBoundsOffset ();
+                int rx = x - (startFrame.X + boundsOffset.X);
+                int ry = y - (startFrame.Y + boundsOffset.Y);
+
+                for (int i = count - 1; i >= 0; i--)
+                {
+                    View v = start.InternalSubviews [i];
+
+                    if (v.Visible && v.Frame.Contains (rx, ry))
+                    {
+                        View deep = FindDeepestView (v, rx, ry, out resx, out resy);
+
+                        if (deep == null)
+                        {
+                            return v;
+                        }
+
+                        return deep;
+                    }
+                }
+            }
+        }
+
+        resx = x - startFrame.X;
+        resy = y - startFrame.Y;
+
+        return start;
+    }
+
+    /// <summary>Gets the <see cref="Frame"/> with a screen-relative location.</summary>
+    /// <returns>The location and size of the view in screen-relative coordinates.</returns>
+    public virtual Rect FrameToScreen ()
+    {
+        Rect ret = Frame;
+        View super = SuperView;
+
+        while (super != null)
+        {
+            Point boundsOffset = super.GetBoundsOffset ();
+            ret.X += super.Frame.X + boundsOffset.X;
+            ret.Y += super.Frame.Y + boundsOffset.Y;
+            super = super.SuperView;
+        }
+
+        return ret;
+    }
+
+    /// <summary>
+    ///     <para>Gets the thickness describing the sum of the Adornments' thicknesses.</para>
+    /// </summary>
+    /// <returns>A thickness that describes the sum of the Adornments' thicknesses.</returns>
+    public Thickness GetAdornmentsThickness ()
+    {
+        int left = Margin.Thickness.Left + Border.Thickness.Left + Padding.Thickness.Left;
+        int top = Margin.Thickness.Top + Border.Thickness.Top + Padding.Thickness.Top;
+        int right = Margin.Thickness.Right + Border.Thickness.Right + Padding.Thickness.Right;
+        int bottom = Margin.Thickness.Bottom + Border.Thickness.Bottom + Padding.Thickness.Bottom;
+
+        return new Thickness (left, top, right, bottom);
+    }
+
+    /// <summary>
+    ///     Helper to get the X and Y offset of the Bounds from the Frame. This is the sum of the Left and Top properties of
+    ///     <see cref="Margin"/>, <see cref="Border"/> and <see cref="Padding"/>.
+    /// </summary>
+    public Point GetBoundsOffset ()
+    {
+        return new Point (Padding?.Thickness.GetInside (Padding.Frame).X ?? 0, Padding?.Thickness.GetInside (Padding.Frame).Y ?? 0);
+    }
+
+    /// <summary>Fired after the View's <see cref="LayoutSubviews"/> method has completed.</summary>
+    /// <remarks>
+    ///     Subscribe to this event to perform tasks when the <see cref="View"/> has been resized or the layout has otherwise
+    ///     changed.
+    /// </remarks>
+    public event EventHandler<LayoutEventArgs> LayoutComplete;
+
+    /// <summary>Fired after the View's <see cref="LayoutSubviews"/> method has completed.</summary>
+    /// <remarks>
+    ///     Subscribe to this event to perform tasks when the <see cref="View"/> has been resized or the layout has otherwise
+    ///     changed.
+    /// </remarks>
+    public event EventHandler<LayoutEventArgs> LayoutStarted;
+
+    /// <summary>
+    ///     Invoked when a view starts executing or when the dimensions of the view have changed, for example in response to
+    ///     the container view or terminal resizing.
+    /// </summary>
+    /// <remarks>
+    ///     <para>
+    ///         The position and dimensions of the view are indeterminate until the view has been initialized. Therefore, the
+    ///         behavior of this method is indeterminate if <see cref="IsInitialized"/> is <see langword="false"/>.
+    ///     </para>
+    ///     <para>Raises the <see cref="LayoutComplete"/> event) before it returns.</para>
+    /// </remarks>
+    public virtual void LayoutSubviews ()
+    {
+        if (!IsInitialized)
+        {
+            Debug.WriteLine ($"WARNING: LayoutSubviews called before view has been initialized. This is likely a bug in {this}");
+        }
+
+        if (!LayoutNeeded)
+        {
+            return;
+        }
+
+        CheckDimAuto ();
+
+        LayoutAdornments ();
+
+        Rect oldBounds = Bounds;
+        OnLayoutStarted (new LayoutEventArgs { OldBounds = oldBounds });
+
+        SetTextFormatterSize ();
+
+        // Sort out the dependencies of the X, Y, Width, Height properties
+        HashSet<View> nodes = new ();
+        HashSet<(View, View)> edges = new ();
+        CollectAll (this, ref nodes, ref edges);
+        List<View> ordered = TopologicalSort (SuperView, nodes, edges);
+
+        foreach (View v in ordered)
+        {
+            if (v.Width is Dim.DimAuto || v.Height is Dim.DimAuto)
+            {
+                // If the view is auto-sized...
+                Rect f = v.Frame;
+                v._frame = new Rect (v.Frame.X, v.Frame.Y, 0, 0);
+                LayoutSubview (v, new Rect (GetBoundsOffset (), Bounds.Size));
+
+                if (v.Frame != f)
+                {
+                    // The subviews changed; do it again
+                    v.LayoutNeeded = true;
+                    LayoutSubview (v, new Rect (GetBoundsOffset (), Bounds.Size));
+                }
+            }
+            else
+            {
+                LayoutSubview (v, new Rect (GetBoundsOffset (), Bounds.Size));
+            }
+        }
+
+        // If the 'to' is rooted to 'from' and the layoutstyle is Computed it's a special-case.
+        // Use LayoutSubview with the Frame of the 'from' 
+        if (SuperView != null && GetTopSuperView () != null && LayoutNeeded && edges.Count > 0)
+        {
+            foreach ((View from, View to) in edges)
+            {
+                LayoutSubview (to, from.Frame);
+            }
+        }
+
+        LayoutNeeded = false;
+
+        OnLayoutComplete (new LayoutEventArgs { OldBounds = oldBounds });
+    }
+
+    /// <summary>Converts a screen-relative coordinate to a bounds-relative coordinate.</summary>
+    /// <returns>The coordinate relative to this view's <see cref="Bounds"/>.</returns>
+    /// <param name="x">Screen-relative column.</param>
+    /// <param name="y">Screen-relative row.</param>
+    public Point ScreenToBounds (int x, int y)
+    {
+        Point screen = ScreenToFrame (x, y);
+        Point boundsOffset = GetBoundsOffset ();
+
+        return new Point (screen.X - boundsOffset.X, screen.Y - boundsOffset.Y);
+    }
+
+    /// <summary>
+    ///     Converts a screen-relative coordinate to a Frame-relative coordinate. Frame-relative means relative to the View's
+    ///     <see cref="SuperView"/>'s <see cref="Bounds"/>.
+    /// </summary>
+    /// <returns>The coordinate relative to the <see cref="SuperView"/>'s <see cref="Bounds"/>.</returns>
+    /// <param name="x">Screen-relative column.</param>
+    /// <param name="y">Screen-relative row.</param>
+    public Point ScreenToFrame (int x, int y)
+    {
+        Point superViewBoundsOffset = SuperView?.GetBoundsOffset () ?? Point.Empty;
+        var ret = new Point (x - Frame.X - superViewBoundsOffset.X, y - Frame.Y - superViewBoundsOffset.Y);
+
+        if (SuperView != null)
+        {
+            Point superFrame = SuperView.ScreenToFrame (x - superViewBoundsOffset.X, y - superViewBoundsOffset.Y);
+            ret = new Point (superFrame.X - Frame.X, superFrame.Y - Frame.Y);
+        }
+
+        return ret;
+    }
+
+    /// <summary>Indicates that the view does not need to be laid out.</summary>
+    protected void ClearLayoutNeeded () { LayoutNeeded = false; }
+
+    internal void CollectAll (View from, ref HashSet<View> nNodes, ref HashSet<(View, View)> nEdges)
+    {
+        // BUGBUG: This should really only work on initialized subviews
+        foreach (View v in from.InternalSubviews /*.Where(v => v.IsInitialized)*/)
+        {
+            nNodes.Add (v);
+
+            if (v.LayoutStyle != LayoutStyle.Computed)
+            {
+                continue;
+            }
+
+            CollectPos (v.X, v, ref nNodes, ref nEdges);
+            CollectPos (v.Y, v, ref nNodes, ref nEdges);
+            CollectDim (v.Width, v, ref nNodes, ref nEdges);
+            CollectDim (v.Height, v, ref nNodes, ref nEdges);
+        }
+    }
+
+    internal void CollectDim (Dim dim, View from, ref HashSet<View> nNodes, ref HashSet<(View, View)> nEdges)
+    {
+        switch (dim)
+        {
+            case Dim.DimView dv:
+                // See #2461
+                //if (!from.InternalSubviews.Contains (dv.Target)) {
+                //	throw new InvalidOperationException ($"View {dv.Target} is not a subview of {from}");
+                //}
+                if (dv.Target != this)
+                {
+                    nEdges.Add ((dv.Target, from));
+                }
+
+                return;
+            case Dim.DimCombine dc:
+                CollectDim (dc._left, from, ref nNodes, ref nEdges);
+                CollectDim (dc._right, from, ref nNodes, ref nEdges);
+
+                break;
+        }
+    }
+
+    internal void CollectPos (Pos pos, View from, ref HashSet<View> nNodes, ref HashSet<(View, View)> nEdges)
+    {
+        switch (pos)
+        {
+            case Pos.PosView pv:
+                // See #2461
+                //if (!from.InternalSubviews.Contains (pv.Target)) {
+                //	throw new InvalidOperationException ($"View {pv.Target} is not a subview of {from}");
+                //}
+                if (pv.Target != this)
+                {
+                    nEdges.Add ((pv.Target, from));
+                }
+
+                return;
+            case Pos.PosCombine pc:
+                CollectPos (pc._left, from, ref nNodes, ref nEdges);
+                CollectPos (pc._right, from, ref nNodes, ref nEdges);
+
+                break;
+        }
+    }
+
+    /// <summary>
+    ///     This internal method is overridden by Adornment to do nothing to prevent recursion during View construction. And,
+    ///     because Adornments don't have Adornments. It's internal to support unit tests.
+    /// </summary>
+    /// <param name="adornmentType"></param>
+    /// <exception cref="ArgumentNullException"></exception>
+    /// <exception cref="ArgumentException"></exception>
+    internal virtual Adornment CreateAdornment (Type adornmentType)
+    {
+        void ThicknessChangedHandler (object sender, EventArgs e)
+        {
+            if (IsInitialized)
+            {
+                LayoutAdornments ();
+            }
+
+            SetNeedsLayout ();
+            SetNeedsDisplay ();
+        }
+
+        Adornment adornment;
+
+        adornment = Activator.CreateInstance (adornmentType, this) as Adornment;
+        adornment.ThicknessChanged += ThicknessChangedHandler;
+
+        return adornment;
+    }
+
+    /// <summary>Overriden by <see cref="Adornment"/> to do nothing, as the <see cref="Adornment"/> does not have adornments.</summary>
+    internal virtual void LayoutAdornments ()
+    {
+        if (Margin == null)
+        {
+            return; // CreateAdornments () has not been called yet
+        }
+
+        if (Margin.Frame.Size != Frame.Size)
+        {
+            Margin._frame = new Rect (Point.Empty, Frame.Size);
+            Margin.X = 0;
+            Margin.Y = 0;
+            Margin.Width = Frame.Size.Width;
+            Margin.Height = Frame.Size.Height;
+            Margin.SetNeedsLayout ();
+            Margin.SetNeedsDisplay ();
+        }
+
+        Rect border = Margin.Thickness.GetInside (Margin.Frame);
+
+        if (border != Border.Frame)
+        {
+            Border._frame = new Rect (new Point (border.Location.X, border.Location.Y), border.Size);
+            Border.X = border.Location.X;
+            Border.Y = border.Location.Y;
+            Border.Width = border.Size.Width;
+            Border.Height = border.Size.Height;
+            Border.SetNeedsLayout ();
+            Border.SetNeedsDisplay ();
+        }
+
+        Rect padding = Border.Thickness.GetInside (Border.Frame);
+
+        if (padding != Padding.Frame)
+        {
+            Padding._frame = new Rect (new Point (padding.Location.X, padding.Location.Y), padding.Size);
+            Padding.X = padding.Location.X;
+            Padding.Y = padding.Location.Y;
+            Padding.Width = padding.Size.Width;
+            Padding.Height = padding.Size.Height;
+            Padding.SetNeedsLayout ();
+            Padding.SetNeedsDisplay ();
+        }
+    }
+
+    /// <summary>
+    ///     Raises the <see cref="LayoutComplete"/> event. Called from  <see cref="LayoutSubviews"/> before all sub-views have
+    ///     been laid out.
+    /// </summary>
+    internal virtual void OnLayoutComplete (LayoutEventArgs args) { LayoutComplete?.Invoke (this, args); }
+
+    /// <summary>
+    ///     Raises the <see cref="LayoutStarted"/> event. Called from  <see cref="LayoutSubviews"/> before any subviews have
+    ///     been laid out.
+    /// </summary>
+    internal virtual void OnLayoutStarted (LayoutEventArgs args) { LayoutStarted?.Invoke (this, args); }
+
+    /// <summary>
+    ///     Called whenever the view needs to be resized. This is called whenever <see cref="Frame"/>, <see cref="View.X"/>,
+    ///     <see cref="View.Y"/>, <see cref="View.Width"/>, or <see cref="View.Height"/> changes.
+    /// </summary>
+    /// <remarks>
+    ///     <para>
+    ///         Determines the relative bounds of the <see cref="View"/> and its <see cref="Frame"/>s, and then calls
+    ///         <see cref="SetRelativeLayout(Rect)"/> to update the view.
+    ///     </para>
+    /// </remarks>
+    internal void OnResizeNeeded ()
+    {
+        // TODO: Identify a real-world use-case where this API should be virtual. 
+        // TODO: Until then leave it `internal` and non-virtual
+        // First try SuperView.Bounds, then Application.Top, then Driver.Bounds.
+        // Finally, if none of those are valid, use int.MaxValue (for Unit tests).
+        Rect relativeBounds = SuperView is { IsInitialized: true } ? SuperView.Bounds :
+                              Application.Top != null && Application.Top.IsInitialized ? Application.Top.Bounds :
+                              Application.Driver?.Bounds ?? new Rect (0, 0, int.MaxValue, int.MaxValue);
+        SetRelativeLayout (relativeBounds);
+
+        // TODO: Determine what, if any of the below is actually needed here.
+        if (IsInitialized)
+        {
+            SetFrameToFitText ();
+            LayoutAdornments ();
+            SetTextFormatterSize ();
+            SetNeedsLayout ();
+            SetNeedsDisplay ();
+        }
+    }
+
+    /// <summary>
+    ///     Sets the internal <see cref="LayoutNeeded"/> flag for this View and all of it's subviews and it's SuperView. The
+    ///     main loop will call SetRelativeLayout and LayoutSubviews for any view with <see cref="LayoutNeeded"/> set.
+    /// </summary>
+    internal void SetNeedsLayout ()
+    {
+        if (LayoutNeeded)
+        {
+            return;
+        }
+
+        LayoutNeeded = true;
+
+        foreach (View view in Subviews)
+        {
+            view.SetNeedsLayout ();
+        }
+
+        TextFormatter.NeedsFormat = true;
+        SuperView?.SetNeedsLayout ();
+    }
+
+    /// <summary>
+    ///     Applies the view's position (<see cref="X"/>, <see cref="Y"/>) and dimension (<see cref="Width"/>, and
+    ///     <see cref="Height"/>) to <see cref="Frame"/>, given a rectangle describing the SuperView's Bounds (nominally the
+    ///     same as <c>this.SuperView.Bounds</c>).
+    /// </summary>
+    /// <param name="superviewBounds">
+    ///     The rectangle describing the SuperView's Bounds (nominally the same as
+    ///     <c>this.SuperView.Bounds</c>).
+    /// </param>
+    internal void SetRelativeLayout (Rect superviewBounds)
+    {
+        Debug.Assert (_x != null);
+        Debug.Assert (_y != null);
+        Debug.Assert (_width != null);
+        Debug.Assert (_height != null);
+
+        CheckDimAuto ();
+
+        int newX, newW, newY, newH;
+        var autosize = Size.Empty;
+
+        if (AutoSize)
+        {
+            // Note this is global to this function and used as such within the local functions defined
+            // below. In v2 AutoSize will be re-factored to not need to be dealt with in this function.
+            autosize = GetTextAutoSize ();
+        }
+
+        // TODO: Since GetNewLocationAndDimension does not depend on View, it can be moved into PosDim.cs
+        // TODO: to make architecture more clean. Do this after DimAuto is implemented and the 
+        // TODO: View.AutoSize stuff is removed.
+
+        // Returns the new dimension (width or height) and location (x or y) for the View given
+        //   the superview's Bounds
+        //   the current Pos (View.X or View.Y)
+        //   the current Dim (View.Width or View.Height)
+        // This method is called recursively if pos is Pos.PosCombine
+        (int newLocation, int newDimension) GetNewLocationAndDimension (bool width, Rect superviewBounds, Pos pos, Dim dim, int autosizeDimension)
+        {
+            // Gets the new dimension (width or height, dependent on `width`) of the given Dim given:
+            //   location: the current location (x or y)
+            //   dimension: the new dimension (width or height) (if relevant for Dim type)
+            //   autosize: the size to use if autosize = true
+            // This method is recursive if d is Dim.DimCombine
+            int GetNewDimension (Dim d, int location, int dimension, int autosize)
+            {
+                int newDimension;
+
+                switch (d)
+                {
+                    case Dim.DimCombine combine:
+                        // TODO: Move combine logic into DimCombine?
+                        int leftNewDim = GetNewDimension (combine._left, location, dimension, autosize);
+                        int rightNewDim = GetNewDimension (combine._right, location, dimension, autosize);
+
+                        if (combine._add)
+                        {
+                            newDimension = leftNewDim + rightNewDim;
+                        }
+                        else
+                        {
+                            newDimension = leftNewDim - rightNewDim;
+                        }
+
+                        newDimension = AutoSize && autosize > newDimension ? autosize : newDimension;
+
+                        break;
+
+                    case Dim.DimFactor factor when !factor.IsFromRemaining ():
+                        newDimension = d.Anchor (dimension);
+                        newDimension = AutoSize && autosize > newDimension ? autosize : newDimension;
+
+                        break;
+
+                    case Dim.DimAuto auto:
+                        Thickness thickness = GetAdornmentsThickness ();
+                        var text = 0;
+                        var subviews = 0;
+
+                        if (auto._style is Dim.DimAutoStyle.Text or Dim.DimAutoStyle.Auto)
+                        {
+                            if (Id == "vlabel")
+                            { }
+
+                            text = int.Max (
+                                            width ? TextFormatter.Size.Width : TextFormatter.Size.Height,
+                                            auto._min?.Anchor (width ? superviewBounds.Width : superviewBounds.Height) ?? 0);
+                        }
+
+                        if (auto._style is Dim.DimAutoStyle.Subviews or Dim.DimAutoStyle.Auto)
+                        {
+                            subviews = Subviews.Count == 0
+                                           ? 0
+                                           : Subviews
+                                             .Where (v => width ? v.X is not Pos.PosAnchorEnd : v.Y is not Pos.PosAnchorEnd)
+                                             .Max (v => width ? v.Frame.X + v.Frame.Width : v.Frame.Y + v.Frame.Height);
+                        }
+
+                        int max = int.Max (text, subviews);
+
+                        newDimension = int.Max (
+                                                width ? max + thickness.Left + thickness.Right : max + thickness.Top + thickness.Bottom,
+                                                auto._min?.Anchor (width ? superviewBounds.Width : superviewBounds.Height) ?? 0);
+
+                        break;
+
+                    case Dim.DimAbsolute:
+                        // DimAbsoulte.Anchor (int width) ignores width and returns n
+                        newDimension = Math.Max (d.Anchor (0), 0);
+                        newDimension = AutoSize && autosize > newDimension ? autosize : newDimension;
+
+                        break;
+
+                    case Dim.DimFill:
+                    default:
+                        newDimension = Math.Max (d.Anchor (dimension - location), 0);
+                        newDimension = AutoSize && autosize > newDimension ? autosize : newDimension;
+
+                        break;
+                }
+
+                return newDimension;
+            }
+
+            int newDimension, newLocation;
+            int superviewDimension = width ? superviewBounds.Width : superviewBounds.Height;
+
+            // Determine new location
+            switch (pos)
+            {
+                case Pos.PosCenter posCenter:
+                    // For Center, the dimension is dependent on location, but we need to force getting the dimension first
+                    // using a location of 0
+                    newDimension = Math.Max (GetNewDimension (dim, 0, superviewDimension, autosizeDimension), 0);
+                    newLocation = posCenter.Anchor (superviewDimension - newDimension);
+                    newDimension = Math.Max (GetNewDimension (dim, newLocation, superviewDimension, autosizeDimension), 0);
+
+                    break;
+
+                case Pos.PosCombine combine:
+                    // TODO: Move combine logic into PosCombine?
+                    // TODO: Move combine logic into PosCombine?
+                    int left, right;
+                    (left, newDimension) = GetNewLocationAndDimension (width, superviewBounds, combine._left, dim, autosizeDimension);
+                    (right, newDimension) = GetNewLocationAndDimension (width, superviewBounds, combine._right, dim, autosizeDimension);
+
+                    if (combine._add)
+                    {
+                        newLocation = left + right;
+                    }
+                    else
+                    {
+                        newLocation = left - right;
+                    }
+
+                    newDimension = Math.Max (GetNewDimension (dim, newLocation, superviewDimension, autosizeDimension), 0);
+
+                    break;
+
+                case Pos.PosAnchorEnd:
+                case Pos.PosAbsolute:
+                case Pos.PosFactor:
+                case Pos.PosFunc:
+                case Pos.PosView:
+                default:
+                    newLocation = pos?.Anchor (superviewDimension) ?? 0;
+                    newDimension = Math.Max (GetNewDimension (dim, newLocation, superviewDimension, autosizeDimension), 0);
+
+                    break;
+            }
+
+            return (newLocation, newDimension);
+        }
+
+        // horizontal/width
+        (newX, newW) = GetNewLocationAndDimension (true, superviewBounds, _x, _width, autosize.Width);
+
+        // vertical/height
+        (newY, newH) = GetNewLocationAndDimension (false, superviewBounds, _y, _height, autosize.Height);
+
+        var r = new Rect (newX, newY, newW, newH);
+
+        if (Frame != r)
+        {
+            // Set the frame. Do NOT use `Frame` as it overwrites X, Y, Width, and Height, making
+            // the view LayoutStyle.Absolute.
+            _frame = r;
+
+            if (_x is Pos.PosAbsolute)
+            {
+                _x = Frame.X;
+            }
+
+            if (_y is Pos.PosAbsolute)
+            {
+                _y = Frame.Y;
+            }
+
+            if (_width is Dim.DimAbsolute)
+            {
+                _width = Frame.Width;
+            }
+
+            if (_height is Dim.DimAbsolute)
+            {
+                _height = Frame.Height;
+            }
+
+            if (IsInitialized)
+            {
+                // TODO: Figure out what really is needed here. All unit tests (except AutoSize) pass as-is
+                SetTextFormatterSize ();
+                SetNeedsLayout ();
+            }
+
+            // BUGBUG: Why is this AFTER setting Frame? Seems duplicative.
+            if (!SetFrameToFitText ())
+            {
+                SetTextFormatterSize ();
+            }
+        }
+    }
+
+    // https://en.wikipedia.org/wiki/Topological_sorting
+    internal static List<View> TopologicalSort (View superView, IEnumerable<View> nodes, ICollection<(View From, View To)> edges)
+    {
+        List<View> result = new ();
+
+        // Set of all nodes with no incoming edges
+        HashSet<View> noEdgeNodes = new (nodes.Where (n => edges.All (e => !e.To.Equals (n))));
+
+        while (noEdgeNodes.Any ())
+        {
+            //  remove a node n from S
+            View n = noEdgeNodes.First ();
+            noEdgeNodes.Remove (n);
+
+            // add n to tail of L
+            if (n != superView)
+            {
+                result.Add (n);
+            }
+
+            // for each node m with an edge e from n to m do
+            foreach ((View From, View To) e in edges.Where (e => e.From.Equals (n)).ToArray ())
+            {
+                View m = e.To;
+
+                // remove edge e from the graph
+                edges.Remove (e);
+
+                // if m has no other incoming edges then
+                if (edges.All (me => !me.To.Equals (m)) && m != superView)
+                {
+                    // insert m into S
+                    noEdgeNodes.Add (m);
+                }
+            }
+        }
+
+        if (!edges.Any ())
+        {
+            return result;
+        }
+
+        foreach ((View from, View to) in edges)
+        {
+            if (from == to)
+            {
+                // if not yet added to the result, add it and remove from edge
+                if (result.Find (v => v == from) == null)
+                {
+                    result.Add (from);
+                }
+
+                edges.Remove ((from, to));
+            }
+            else if (from.SuperView == to.SuperView)
+            {
+                // if 'from' is not yet added to the result, add it
+                if (result.Find (v => v == from) == null)
+                {
+                    result.Add (from);
+                }
+
+                // if 'to' is not yet added to the result, add it
+                if (result.Find (v => v == to) == null)
+                {
+                    result.Add (to);
+                }
+
+                // remove from edge
+                edges.Remove ((from, to));
+            }
+            else if (from != superView?.GetTopSuperView (to, from) && !ReferenceEquals (from, to))
+            {
+                if (ReferenceEquals (from.SuperView, to))
+                {
+                    throw new InvalidOperationException ($"ComputedLayout for \"{superView}\": \"{to}\" references a SubView (\"{from}\").");
+                }
+
+                throw new InvalidOperationException (
+                                                     $"ComputedLayout for \"{superView}\": \"{from}\" linked with \"{to}\" was not found. Did you forget to add it to {superView}?");
+            }
+        }
+
+        // return L (a topologically sorted order)
+        return result;
+    } // TopologicalSort
+
+    /// <summary>Determines if the View's <see cref="Height"/> can be set to a new value.</summary>
+    /// <param name="desiredHeight"></param>
+    /// <param name="resultHeight">
+    ///     Contains the width that would result if <see cref="Height"/> were set to
+    ///     <paramref name="desiredHeight"/>"/>
+    /// </param>
+    /// <returns>
+    ///     <see langword="true"/> if the View's <see cref="Height"/> can be changed to the specified value. False
+    ///     otherwise.
+    /// </returns>
+    internal bool TrySetHeight (int desiredHeight, out int resultHeight)
+    {
+        int h = desiredHeight;
+        bool canSetHeight;
+
+        switch (Height)
+        {
+            case Dim.DimCombine _:
+            case Dim.DimView _:
+            case Dim.DimFill _:
+                // It's a Dim.DimCombine and so can't be assigned. Let it have it's height anchored.
+                h = Height.Anchor (h);
+                canSetHeight = !ValidatePosDim;
+
+                break;
+            case Dim.DimFactor factor:
+                // Tries to get the SuperView height otherwise the view height.
+                int sh = SuperView != null ? SuperView.Frame.Height : h;
+
+                if (factor.IsFromRemaining ())
+                {
+                    sh -= Frame.Y;
+                }
+
+                h = Height.Anchor (sh);
+                canSetHeight = !ValidatePosDim;
+
+                break;
+            default:
+                canSetHeight = true;
+
+                break;
+        }
+
+        resultHeight = h;
+
+        return canSetHeight;
+    }
+
+    /// <summary>Determines if the View's <see cref="Width"/> can be set to a new value.</summary>
+    /// <param name="desiredWidth"></param>
+    /// <param name="resultWidth">
+    ///     Contains the width that would result if <see cref="Width"/> were set to
+    ///     <paramref name="desiredWidth"/>"/>
+    /// </param>
+    /// <returns>
+    ///     <see langword="true"/> if the View's <see cref="Width"/> can be changed to the specified value. False
+    ///     otherwise.
+    /// </returns>
+    internal bool TrySetWidth (int desiredWidth, out int resultWidth)
+    {
+        int w = desiredWidth;
+        bool canSetWidth;
+
+        switch (Width)
+        {
+            case Dim.DimCombine _:
+            case Dim.DimView _:
+            case Dim.DimFill _:
+                // It's a Dim.DimCombine and so can't be assigned. Let it have it's Width anchored.
+                w = Width.Anchor (w);
+                canSetWidth = !ValidatePosDim;
+
+                break;
+            case Dim.DimFactor factor:
+                // Tries to get the SuperView Width otherwise the view Width.
+                int sw = SuperView != null ? SuperView.Frame.Width : w;
+
+                if (factor.IsFromRemaining ())
+                {
+                    sw -= Frame.X;
+                }
+
+                w = Width.Anchor (sw);
+                canSetWidth = !ValidatePosDim;
+
+                break;
+            default:
+                canSetWidth = true;
+
+                break;
+        }
+
+        resultWidth = w;
+
+        return canSetWidth;
+    }
+
+    /// <summary>
+    ///     Throws an <see cref="InvalidOperationException"/> if any SubViews are using Dim objects that depend on this Views
+    ///     dimensions.
+    /// </summary>
+    /// <exception cref="InvalidOperationException"></exception>
+    private void CheckDimAuto ()
+    {
+        if (!ValidatePosDim || !IsInitialized || (Width is not Dim.DimAuto && Height is not Dim.DimAuto))
+        {
+            return;
+        }
+
+        void ThrowInvalid (View view, object checkPosDim, string name)
+        {
+            // TODO: Figure out how to make CheckDimAuto deal with PosCombine
+            object bad = null;
+
+            switch (checkPosDim)
+            {
+                case Pos pos and not Pos.PosAbsolute and not Pos.PosView and not Pos.PosCombine:
+                    bad = pos;
+
+                    break;
+                case Pos pos and Pos.PosCombine:
+                    // Recursively check for not Absolute or not View
+                    ThrowInvalid (view, (pos as Pos.PosCombine)._left, name);
+                    ThrowInvalid (view, (pos as Pos.PosCombine)._right, name);
+
+                    break;
+
+                case Dim dim and not Dim.DimAbsolute and not Dim.DimView and not Dim.DimCombine:
+                    bad = dim;
+
+                    break;
+                case Dim dim and Dim.DimCombine:
+                    // Recursively check for not Absolute or not View
+                    ThrowInvalid (view, (dim as Dim.DimCombine)._left, name);
+                    ThrowInvalid (view, (dim as Dim.DimCombine)._right, name);
+
+                    break;
+            }
+
+            if (bad != null)
+            {
+                throw new InvalidOperationException (
+                                                     @$"{view.GetType ().Name}.{name} = {bad.GetType ().Name} which depends on the SuperView's dimensions and the SuperView uses Dim.Auto.");
+            }
+        }
+
+        // Verify none of the subviews are using Dim objects that depend on the SuperView's dimensions.
+        foreach (View view in Subviews)
+        {
+            if (Width is Dim.DimAuto { _min: null })
+            {
+                ThrowInvalid (view, view.Width, nameof (view.Width));
+                ThrowInvalid (view, view.X, nameof (view.X));
+            }
+
+            if (Height is Dim.DimAuto { _min: null })
+            {
+                ThrowInvalid (view, view.Height, nameof (view.Height));
+                ThrowInvalid (view, view.Y, nameof (view.Y));
+            }
+        }
+    }
+
+    private void LayoutSubview (View v, Rect contentArea)
+    {
+        //if (v.LayoutStyle == LayoutStyle.Computed) {
+        v.SetRelativeLayout (contentArea);
+
+        //}
+
+        v.LayoutSubviews ();
+        v.LayoutNeeded = false;
+    }
+
+    /// <summary>Resizes the View to fit the specified size. Factors in the HotKey.</summary>
+    /// <param name="size"></param>
+    /// <returns>whether the Bounds was changed or not</returns>
+    private bool ResizeBoundsToFit (Size size)
+    {
+        var boundsChanged = false;
+        bool canSizeW = TrySetWidth (size.Width - GetHotKeySpecifierLength (), out int rW);
+        bool canSizeH = TrySetHeight (size.Height - GetHotKeySpecifierLength (false), out int rH);
+
+        if (canSizeW)
+        {
+            boundsChanged = true;
+            _width = rW;
+        }
+
+        if (canSizeH)
+        {
+            boundsChanged = true;
+            _height = rH;
+        }
+
+        if (boundsChanged)
+        {
+            Bounds = new Rect (Bounds.X, Bounds.Y, canSizeW ? rW : Bounds.Width, canSizeH ? rH : Bounds.Height);
+        }
+
+        return boundsChanged;
+    }
+
+    private bool ResizeView (bool autoSize)
+    {
+        if (!autoSize)
+        {
+            return false;
+        }
+
+        var boundsChanged = true;
+        Size newFrameSize = GetTextAutoSize ();
+
+        if (IsInitialized && newFrameSize != Frame.Size)
+        {
+            if (ValidatePosDim)
+            {
+                // BUGBUG: This ain't right, obviously.  We need to figure out how to handle this.
+                boundsChanged = ResizeBoundsToFit (newFrameSize);
+            }
+            else
+            {
+                Height = newFrameSize.Height;
+                Width = newFrameSize.Width;
+            }
+        }
+
+        return boundsChanged;
+    }
+
+    // Diagnostics to highlight when X or Y is read before the view has been initialized
+    private Pos VerifyIsInitialized (Pos pos, string member)
+    {
 #if DEBUG
-		if (LayoutStyle == LayoutStyle.Computed && !IsInitialized) {
-			Debug.WriteLine ($"WARNING: \"{this}\" has not been initialized; {member} is indeterminate {pos}. This is potentially a bug.");
-		}
+        if (LayoutStyle == LayoutStyle.Computed && !IsInitialized)
+        {
+            Debug.WriteLine ($"WARNING: \"{this}\" has not been initialized; {member} is indeterminate {pos}. This is potentially a bug.");
+        }
 #endif // DEBUG
-		return pos;
-	}
+        return pos;
+    }
 
-	// Diagnostics to highlight when Width or Height is read before the view has been initialized
-	Dim VerifyIsInitialized (Dim dim, string member)
-	{
+    // Diagnostics to highlight when Width or Height is read before the view has been initialized
+    private Dim VerifyIsInitialized (Dim dim, string member)
+    {
 #if DEBUG
-		if (LayoutStyle == LayoutStyle.Computed && !IsInitialized) {
-			Debug.WriteLine ($"WARNING: \"{this}\" has not been initialized; {member} is indeterminate: {dim}. This is potentially a bug.");
-		}
+        if (LayoutStyle == LayoutStyle.Computed && !IsInitialized)
+        {
+            Debug.WriteLine ($"WARNING: \"{this}\" has not been initialized; {member} is indeterminate: {dim}. This is potentially a bug.");
+        }
 #endif // DEBUG		
-		return dim;
-	}
-
-	/// <summary>
-	/// Called whenever the view needs to be resized. This is called whenever <see cref="Frame"/>,
-	/// <see cref="View.X"/>, <see cref="View.Y"/>, <see cref="View.Width"/>, or <see cref="View.Height"/> changes.
-	/// </summary>
-	/// <remarks>
-	///         <para>
-	///         Determines the relative bounds of the <see cref="View"/> and its <see cref="Frame"/>s, and then calls
-	///         <see cref="SetRelativeLayout(Rect)"/> to update the view. 
-	///         </para>
-	/// </remarks>
-	internal void OnResizeNeeded ()
-	{
-		// TODO: Identify a real-world use-case where this API should be virtual. 
-		// TODO: Until then leave it `internal` and non-virtual
-		// First try SuperView.Bounds, then Application.Top, then Driver.Bounds.
-		// Finally, if none of those are valid, use int.MaxValue (for Unit tests).
-		var relativeBounds = SuperView is { IsInitialized: true } ? SuperView.Bounds :
-			Application.Top != null && Application.Top.IsInitialized ? Application.Top.Bounds :
-										   Application.Driver?.Bounds ??
-										   new Rect (0, 0, int.MaxValue, int.MaxValue);
-		SetRelativeLayout (relativeBounds);
-
-		// TODO: Determine what, if any of the below is actually needed here.
-		if (IsInitialized) {
-			SetFrameToFitText ();
-			LayoutAdornments ();
-			SetTextFormatterSize ();
-			SetNeedsLayout ();
-			SetNeedsDisplay ();
-		}
-	}
-
-	/// <summary>
-	/// Sets the internal <see cref="LayoutNeeded"/> flag for this View and all of it's
-	/// subviews and it's SuperView. The main loop will call SetRelativeLayout and LayoutSubviews
-	/// for any view with <see cref="LayoutNeeded"/> set.
-	/// </summary>
-	internal void SetNeedsLayout ()
-	{
-		if (LayoutNeeded) {
-			return;
-		}
-		LayoutNeeded = true;
-		foreach (var view in Subviews) {
-			view.SetNeedsLayout ();
-		}
-		TextFormatter.NeedsFormat = true;
-		SuperView?.SetNeedsLayout ();
-	}
-
-	/// <summary>
-	/// Indicates that the view does not need to be laid out.
-	/// </summary>
-	protected void ClearLayoutNeeded () => LayoutNeeded = false;
-
-	/// <summary>
-	/// Converts a screen-relative coordinate to a Frame-relative coordinate. Frame-relative means
-	/// relative to the View's <see cref="SuperView"/>'s <see cref="Bounds"/>.
-	/// </summary>
-	/// <returns>The coordinate relative to the <see cref="SuperView"/>'s <see cref="Bounds"/>.</returns>
-	/// <param name="x">Screen-relative column.</param>
-	/// <param name="y">Screen-relative row.</param>
-	public Point ScreenToFrame (int x, int y)
-	{
-		var superViewBoundsOffset = SuperView?.GetBoundsOffset () ?? Point.Empty;
-		var ret = new Point (x - Frame.X - superViewBoundsOffset.X, y - Frame.Y - superViewBoundsOffset.Y);
-		if (SuperView != null) {
-			var superFrame = SuperView.ScreenToFrame (x - superViewBoundsOffset.X, y - superViewBoundsOffset.Y);
-			ret = new Point (superFrame.X - Frame.X, superFrame.Y - Frame.Y);
-		}
-		return ret;
-	}
-
-	/// <summary>
-	/// Converts a screen-relative coordinate to a bounds-relative coordinate.
-	/// </summary>
-	/// <returns>The coordinate relative to this view's <see cref="Bounds"/>.</returns>
-	/// <param name="x">Screen-relative column.</param>
-	/// <param name="y">Screen-relative row.</param>
-	public Point ScreenToBounds (int x, int y)
-	{
-		var screen = ScreenToFrame (x, y);
-		var boundsOffset = GetBoundsOffset ();
-		return new Point (screen.X - boundsOffset.X, screen.Y - boundsOffset.Y);
-	}
-
-	/// <summary>
-	/// Converts a <see cref="Bounds"/>-relative coordinate to a screen-relative coordinate. The output is optionally clamped
-	/// to the screen dimensions.
-	/// </summary>
-	/// <param name="x"><see cref="Bounds"/>-relative column.</param>
-	/// <param name="y"><see cref="Bounds"/>-relative row.</param>
-	/// <param name="rx">Absolute column; screen-relative.</param>
-	/// <param name="ry">Absolute row; screen-relative.</param>
-	/// <param name="clamped">
-	/// If <see langword="true"/>, <paramref name="rx"/> and <paramref name="ry"/> will be clamped to the
-	/// screen dimensions (will never be negative and will always be less than <see cref="ConsoleDriver.Cols"/> and
-	/// <see cref="ConsoleDriver.Rows"/>, respectively.
-	/// </param>
-	public virtual void BoundsToScreen (int x, int y, out int rx, out int ry, bool clamped = true)
-	{
-		var boundsOffset = GetBoundsOffset ();
-		rx = x + Frame.X + boundsOffset.X;
-		ry = y + Frame.Y + boundsOffset.Y;
-
-		var super = SuperView;
-		while (super != null) {
-			boundsOffset = super.GetBoundsOffset ();
-			rx += super.Frame.X + boundsOffset.X;
-			ry += super.Frame.Y + boundsOffset.Y;
-			super = super.SuperView;
-		}
-
-		// The following ensures that the cursor is always in the screen boundaries.
-		if (clamped) {
-			ry = Math.Min (ry, Driver.Rows - 1);
-			rx = Math.Min (rx, Driver.Cols - 1);
-		}
-	}
-
-	/// <summary>
-	/// Converts a <see cref="Bounds"/>-relative region to a screen-relative region.
-	/// </summary>
-	public Rect BoundsToScreen (Rect region)
-	{
-		BoundsToScreen (region.X, region.Y, out var x, out var y, false);
-		return new Rect (x, y, region.Width, region.Height);
-	}
-
-	/// <summary>
-	/// Gets the <see cref="Frame"/> with a screen-relative location.
-	/// </summary>
-	/// <returns>The location and size of the view in screen-relative coordinates.</returns>
-	public virtual Rect FrameToScreen ()
-	{
-		var ret = Frame;
-		var super = SuperView;
-		while (super != null) {
-			var boundsOffset = super.GetBoundsOffset ();
-			ret.X += super.Frame.X + boundsOffset.X;
-			ret.Y += super.Frame.Y + boundsOffset.Y;
-			super = super.SuperView;
-		}
-		return ret;
-	}
-
-	/// <summary>
-	/// Applies the view's position (<see cref="X"/>, <see cref="Y"/>) and dimension (<see cref="Width"/>, and
-	/// <see cref="Height"/>) to
-	/// <see cref="Frame"/>, given a rectangle describing the SuperView's Bounds (nominally the same as
-	/// <c>this.SuperView.Bounds</c>).
-	/// </summary>
-	/// <param name="superviewBounds">
-	/// The rectangle describing the SuperView's Bounds (nominally the same as
-	/// <c>this.SuperView.Bounds</c>).
-	/// </param>
-	internal void SetRelativeLayout (Rect superviewBounds)
-	{
-		Debug.Assert (_x != null);
-		Debug.Assert (_y != null);
-		Debug.Assert (_width != null);
-		Debug.Assert (_height != null);
-
-		CheckDimAuto ();
-
-		int newX, newW, newY, newH;
-		var autosize = Size.Empty;
-
-		if (AutoSize) {
-			// Note this is global to this function and used as such within the local functions defined
-			// below. In v2 AutoSize will be re-factored to not need to be dealt with in this function.
-			autosize = GetTextAutoSize ();
-		}
-
-		// TODO: Since GetNewLocationAndDimension does not depend on View, it can be moved into PosDim.cs
-		// TODO: to make architecture more clean. Do this after DimAuto is implemented and the 
-		// TODO: View.AutoSize stuff is removed.
-
-		// Returns the new dimension (width or height) and location (x or y) for the View given
-		//   the superview's Bounds
-		//   the current Pos (View.X or View.Y)
-		//   the current Dim (View.Width or View.Height)
-		// This method is called recursively if pos is Pos.PosCombine
-		(int newLocation, int newDimension) GetNewLocationAndDimension (bool width, Rect superviewBounds, Pos pos, Dim dim, int autosizeDimension)
-		{
-			// Gets the new dimension (width or height, dependent on `width`) of the given Dim given:
-			//   location: the current location (x or y)
-			//   dimension: the new dimension (width or height) (if relevant for Dim type)
-			//   autosize: the size to use if autosize = true
-			// This method is recursive if d is Dim.DimCombine
-			int GetNewDimension (Dim d, int location, int dimension, int autosize)
-			{
-				int newDimension;
-				switch (d) {
-
-				case Dim.DimCombine combine:
-					// TODO: Move combine logic into DimCombine?
-					var leftNewDim = GetNewDimension (combine._left, location, dimension, autosize);
-					var rightNewDim = GetNewDimension (combine._right, location, dimension, autosize);
-					if (combine._add) {
-						newDimension = leftNewDim + rightNewDim;
-					} else {
-						newDimension = leftNewDim - rightNewDim;
-					}
-					newDimension = AutoSize && autosize > newDimension ? autosize : newDimension;
-					break;
-
-				case Dim.DimFactor factor when !factor.IsFromRemaining ():
-					newDimension = d.Anchor (dimension);
-					newDimension = AutoSize && autosize > newDimension ? autosize : newDimension;
-					break;
-
-				case Dim.DimAuto auto:
-					var thickness = GetAdornmentsThickness ();
-					var text = 0;
-					var subviews = 0;
-
-					if (auto._style is Dim.DimAutoStyle.Text or Dim.DimAutoStyle.Auto) {
-						if (Id == "vlabel") {
-
-						}
-
-						text = int.Max (width ? TextFormatter.Size.Width : TextFormatter.Size.Height, 
-							auto._min?.Anchor (width ? superviewBounds.Width : superviewBounds.Height) ?? 0);
-					} 
-					
-					if (auto._style is Dim.DimAutoStyle.Subviews or Dim.DimAutoStyle.Auto) {
-						subviews = Subviews.Count == 0 ? 0 : Subviews
-							.Where (v => width ? v.X is not Pos.PosAnchorEnd : v.Y is not Pos.PosAnchorEnd)
-							.Max (v => width ? v.Frame.X + v.Frame.Width : v.Frame.Y + v.Frame.Height);
-					} 
-					
-					var max = int.Max (text, subviews);
-					newDimension = int.Max (width ? max + thickness.Left + thickness.Right : max + thickness.Top + thickness.Bottom,
-						auto._min?.Anchor (width ? superviewBounds.Width : superviewBounds.Height) ?? 0);
-					break;
-
-				case Dim.DimAbsolute:
-					// DimAbsoulte.Anchor (int width) ignores width and returns n
-					newDimension = Math.Max (d.Anchor (0), 0);
-					newDimension = AutoSize && autosize > newDimension ? autosize : newDimension;
-					break;
-
-				case Dim.DimFill:
-				default:
-					newDimension = Math.Max (d.Anchor (dimension - location), 0);
-					newDimension = AutoSize && autosize > newDimension ? autosize : newDimension;
-					break;
-				}
-
-				return newDimension;
-			}
-
-			int newDimension, newLocation;
-			var superviewDimension = width ? superviewBounds.Width : superviewBounds.Height;
-
-			// Determine new location
-			switch (pos) {
-			case Pos.PosCenter posCenter:
-				// For Center, the dimension is dependent on location, but we need to force getting the dimension first
-				// using a location of 0
-				newDimension = Math.Max (GetNewDimension (dim, 0, superviewDimension, autosizeDimension), 0);
-				newLocation = posCenter.Anchor (superviewDimension - newDimension);
-				newDimension = Math.Max (GetNewDimension (dim, newLocation, superviewDimension, autosizeDimension), 0);
-				break;
-
-			case Pos.PosCombine combine:
-				// TODO: Move combine logic into PosCombine?
-				// TODO: Move combine logic into PosCombine?
-				int left, right;
-				(left, newDimension) = GetNewLocationAndDimension (width, superviewBounds, combine._left, dim, autosizeDimension);
-				(right, newDimension) = GetNewLocationAndDimension (width, superviewBounds, combine._right, dim, autosizeDimension);
-				if (combine._add) {
-					newLocation = left + right;
-				} else {
-					newLocation = left - right;
-				}
-				newDimension = Math.Max (GetNewDimension (dim, newLocation, superviewDimension, autosizeDimension), 0);
-				break;
-
-			case Pos.PosAnchorEnd:
-			case Pos.PosAbsolute:
-			case Pos.PosFactor:
-			case Pos.PosFunc:
-			case Pos.PosView:
-			default:
-				newLocation = pos?.Anchor (superviewDimension) ?? 0;
-				newDimension = Math.Max (GetNewDimension (dim, newLocation, superviewDimension, autosizeDimension), 0);
-				break;
-			}
-
-
-			return (newLocation, newDimension);
-		}
-
-		// horizontal/width
-		(newX, newW) = GetNewLocationAndDimension (true, superviewBounds, _x, _width, autosize.Width);
-
-		// vertical/height
-		(newY, newH) = GetNewLocationAndDimension (false, superviewBounds, _y, _height, autosize.Height);
-
-		var r = new Rect (newX, newY, newW, newH);
-		if (Frame != r) {
-			// Set the frame. Do NOT use `Frame` as it overwrites X, Y, Width, and Height, making
-			// the view LayoutStyle.Absolute.
-			_frame = r;
-			if (_x is Pos.PosAbsolute) {
-				_x = Frame.X;
-			}
-			if (_y is Pos.PosAbsolute) {
-				_y = Frame.Y;
-			}
-			if (_width is Dim.DimAbsolute) {
-				_width = Frame.Width;
-			}
-			if (_height is Dim.DimAbsolute) {
-				_height = Frame.Height;
-			}
-
-			if (IsInitialized) {
-				// TODO: Figure out what really is needed here. All unit tests (except AutoSize) pass as-is
-				SetTextFormatterSize ();
-				SetNeedsLayout ();
-			}
-
-			// BUGBUG: Why is this AFTER setting Frame? Seems duplicative.
-			if (!SetFrameToFitText ()) {
-				SetTextFormatterSize ();
-			}
-		}
-	}
-
-	/// <summary>
-	/// Fired after the View's <see cref="LayoutSubviews"/> method has completed.
-	/// </summary>
-	/// <remarks>
-	/// Subscribe to this event to perform tasks when the <see cref="View"/> has been resized or the layout has otherwise
-	/// changed.
-	/// </remarks>
-	public event EventHandler<LayoutEventArgs> LayoutStarted;
-
-	/// <summary>
-	/// Raises the <see cref="LayoutStarted"/> event. Called from  <see cref="LayoutSubviews"/> before any subviews have been
-	/// laid out.
-	/// </summary>
-	internal virtual void OnLayoutStarted (LayoutEventArgs args) => LayoutStarted?.Invoke (this, args);
-
-	/// <summary>
-	/// Fired after the View's <see cref="LayoutSubviews"/> method has completed.
-	/// </summary>
-	/// <remarks>
-	/// Subscribe to this event to perform tasks when the <see cref="View"/> has been resized or the layout has otherwise
-	/// changed.
-	/// </remarks>
-	public event EventHandler<LayoutEventArgs> LayoutComplete;
-
-	/// <summary>
-	/// Raises the <see cref="LayoutComplete"/> event. Called from  <see cref="LayoutSubviews"/> before all sub-views have been
-	/// laid out.
-	/// </summary>
-	internal virtual void OnLayoutComplete (LayoutEventArgs args) => LayoutComplete?.Invoke (this, args);
-
-	internal void CollectPos (Pos pos, View from, ref HashSet<View> nNodes, ref HashSet<(View, View)> nEdges)
-	{
-		switch (pos) {
-		case Pos.PosView pv:
-			// See #2461
-			//if (!from.InternalSubviews.Contains (pv.Target)) {
-			//	throw new InvalidOperationException ($"View {pv.Target} is not a subview of {from}");
-			//}
-			if (pv.Target != this) {
-				nEdges.Add ((pv.Target, from));
-			}
-			return;
-		case Pos.PosCombine pc:
-			CollectPos (pc._left, from, ref nNodes, ref nEdges);
-			CollectPos (pc._right, from, ref nNodes, ref nEdges);
-			break;
-		}
-	}
-
-	internal void CollectDim (Dim dim, View from, ref HashSet<View> nNodes, ref HashSet<(View, View)> nEdges)
-	{
-		switch (dim) {
-		case Dim.DimView dv:
-			// See #2461
-			//if (!from.InternalSubviews.Contains (dv.Target)) {
-			//	throw new InvalidOperationException ($"View {dv.Target} is not a subview of {from}");
-			//}
-			if (dv.Target != this) {
-				nEdges.Add ((dv.Target, from));
-			}
-			return;
-		case Dim.DimCombine dc:
-			CollectDim (dc._left, from, ref nNodes, ref nEdges);
-			CollectDim (dc._right, from, ref nNodes, ref nEdges);
-			break;
-		}
-	}
-
-	internal void CollectAll (View from, ref HashSet<View> nNodes, ref HashSet<(View, View)> nEdges)
-	{
-		// BUGBUG: This should really only work on initialized subviews
-		foreach (var v in from.InternalSubviews /*.Where(v => v.IsInitialized)*/) {
-			nNodes.Add (v);
-			if (v.LayoutStyle != LayoutStyle.Computed) {
-				continue;
-			}
-			CollectPos (v.X, v, ref nNodes, ref nEdges);
-			CollectPos (v.Y, v, ref nNodes, ref nEdges);
-			CollectDim (v.Width, v, ref nNodes, ref nEdges);
-			CollectDim (v.Height, v, ref nNodes, ref nEdges);
-		}
-	}
-
-	// https://en.wikipedia.org/wiki/Topological_sorting
-	internal static List<View> TopologicalSort (View superView, IEnumerable<View> nodes, ICollection<(View From, View To)> edges)
-	{
-		var result = new List<View> ();
-
-		// Set of all nodes with no incoming edges
-		var noEdgeNodes = new HashSet<View> (nodes.Where (n => edges.All (e => !e.To.Equals (n))));
-
-		while (noEdgeNodes.Any ()) {
-			//  remove a node n from S
-			var n = noEdgeNodes.First ();
-			noEdgeNodes.Remove (n);
-
-			// add n to tail of L
-			if (n != superView) {
-				result.Add (n);
-			}
-
-			// for each node m with an edge e from n to m do
-			foreach (var e in edges.Where (e => e.From.Equals (n)).ToArray ()) {
-				var m = e.To;
-
-				// remove edge e from the graph
-				edges.Remove (e);
-
-				// if m has no other incoming edges then
-				if (edges.All (me => !me.To.Equals (m)) && m != superView) {
-					// insert m into S
-					noEdgeNodes.Add (m);
-				}
-			}
-		}
-
-		if (!edges.Any ()) {
-			return result;
-		}
-
-		foreach ((var from, var to) in edges) {
-			if (from == to) {
-				// if not yet added to the result, add it and remove from edge
-				if (result.Find (v => v == from) == null) {
-					result.Add (from);
-				}
-				edges.Remove ((from, to));
-			} else if (from.SuperView == to.SuperView) {
-				// if 'from' is not yet added to the result, add it
-				if (result.Find (v => v == from) == null) {
-					result.Add (from);
-				}
-				// if 'to' is not yet added to the result, add it
-				if (result.Find (v => v == to) == null) {
-					result.Add (to);
-				}
-				// remove from edge
-				edges.Remove ((from, to));
-			} else if (from != superView?.GetTopSuperView (to, from) && !ReferenceEquals (from, to)) {
-				if (ReferenceEquals (from.SuperView, to)) {
-					throw new InvalidOperationException ($"ComputedLayout for \"{superView}\": \"{to}\" references a SubView (\"{from}\").");
-				}
-				throw new InvalidOperationException ($"ComputedLayout for \"{superView}\": \"{from}\" linked with \"{to}\" was not found. Did you forget to add it to {superView}?");
-			}
-		}
-		// return L (a topologically sorted order)
-		return result;
-	} // TopologicalSort
-
-	/// <summary>
-	/// Overriden by <see cref="Adornment"/> to do nothing, as the <see cref="Adornment"/> does not have adornments.
-	/// </summary>
-	internal virtual void LayoutAdornments ()
-	{
-		if (Margin == null) {
-			return; // CreateAdornments () has not been called yet
-		}
-
-		if (Margin.Frame.Size != Frame.Size) {
-			Margin._frame = new Rect (Point.Empty, Frame.Size);
-			Margin.X = 0;
-			Margin.Y = 0;
-			Margin.Width = Frame.Size.Width;
-			Margin.Height = Frame.Size.Height;
-			Margin.SetNeedsLayout ();
-			Margin.SetNeedsDisplay ();
-		}
-
-		var border = Margin.Thickness.GetInside (Margin.Frame);
-		if (border != Border.Frame) {
-			Border._frame = new Rect (new Point (border.Location.X, border.Location.Y), border.Size);
-			Border.X = border.Location.X;
-			Border.Y = border.Location.Y;
-			Border.Width = border.Size.Width;
-			Border.Height = border.Size.Height;
-			Border.SetNeedsLayout ();
-			Border.SetNeedsDisplay ();
-		}
-
-		var padding = Border.Thickness.GetInside (Border.Frame);
-		if (padding != Padding.Frame) {
-			Padding._frame = new Rect (new Point (padding.Location.X, padding.Location.Y), padding.Size);
-			Padding.X = padding.Location.X;
-			Padding.Y = padding.Location.Y;
-			Padding.Width = padding.Size.Width;
-			Padding.Height = padding.Size.Height;
-			Padding.SetNeedsLayout ();
-			Padding.SetNeedsDisplay ();
-		}
-	}
-
-	/// <summary>
-	/// Invoked when a view starts executing or when the dimensions of the view have changed, for example in
-	/// response to the container view or terminal resizing.
-	/// </summary>
-	/// <remarks>
-	///         <para>
-	///         The position and dimensions of the view are indeterminate until the view has been initialized. Therefore,
-	///         the behavior of this method is indeterminate if <see cref="IsInitialized"/> is <see langword="false"/>.
-	///         </para>
-	///         <para>
-	///         Raises the <see cref="LayoutComplete"/> event) before it returns.
-	///         </para>
-	/// </remarks>
-	public virtual void LayoutSubviews ()
-	{
-		if (!IsInitialized) {
-			Debug.WriteLine ($"WARNING: LayoutSubviews called before view has been initialized. This is likely a bug in {this}");
-		}
-
-		if (!LayoutNeeded) {
-			return;
-		}
-
-		CheckDimAuto ();
-
-		LayoutAdornments ();
-
-		var oldBounds = Bounds;
-		OnLayoutStarted (new LayoutEventArgs { OldBounds = oldBounds });
-
-		SetTextFormatterSize ();
-
-		// Sort out the dependencies of the X, Y, Width, Height properties
-		var nodes = new HashSet<View> ();
-		var edges = new HashSet<(View, View)> ();
-		CollectAll (this, ref nodes, ref edges);
-		var ordered = TopologicalSort (SuperView, nodes, edges);
-		foreach (var v in ordered) {
-			if (v.Width is Dim.DimAuto || v.Height is Dim.DimAuto) {
-				// If the view is auto-sized...
-				var f = v.Frame;
-				v._frame = new Rect (v.Frame.X, v.Frame.Y, 0, 0);
-				LayoutSubview (v, new Rect (GetBoundsOffset (), Bounds.Size));
-				if (v.Frame != f) {
-					// The subviews changed; do it again
-					v.LayoutNeeded = true;
-					LayoutSubview (v, new Rect (GetBoundsOffset (), Bounds.Size));
-				}
-			} else {
-				LayoutSubview (v, new Rect (GetBoundsOffset (), Bounds.Size));
-			}
-		}
-
-		// If the 'to' is rooted to 'from' and the layoutstyle is Computed it's a special-case.
-		// Use LayoutSubview with the Frame of the 'from' 
-		if (SuperView != null && GetTopSuperView () != null && LayoutNeeded && edges.Count > 0) {
-			foreach ((var from, var to) in edges) {
-				LayoutSubview (to, from.Frame);
-			}
-		}
-
-		LayoutNeeded = false;
-
-		OnLayoutComplete (new LayoutEventArgs { OldBounds = oldBounds });
-	}
-
-	void LayoutSubview (View v, Rect contentArea)
-	{
-		//if (v.LayoutStyle == LayoutStyle.Computed) {
-		v.SetRelativeLayout (contentArea);
-		//}
-
-		v.LayoutSubviews ();
-		v.LayoutNeeded = false;
-	}
-
-	bool ResizeView (bool autoSize)
-	{
-		if (!autoSize) {
-			return false;
-		}
-
-		var boundsChanged = true;
-		var newFrameSize = GetTextAutoSize ();
-		if (IsInitialized && newFrameSize != Frame.Size) {
-			if (ValidatePosDim) {
-				// BUGBUG: This ain't right, obviously.  We need to figure out how to handle this.
-				boundsChanged = ResizeBoundsToFit (newFrameSize);
-			} else {
-				Height = newFrameSize.Height;
-				Width = newFrameSize.Width;
-			}
-		}
-		return boundsChanged;
-	}
-
-	/// <summary>
-	/// Resizes the View to fit the specified size. Factors in the HotKey.
-	/// </summary>
-	/// <param name="size"></param>
-	/// <returns>whether the Bounds was changed or not</returns>
-	bool ResizeBoundsToFit (Size size)
-	{
-		var boundsChanged = false;
-		var canSizeW = TrySetWidth (size.Width - GetHotKeySpecifierLength (), out var rW);
-		var canSizeH = TrySetHeight (size.Height - GetHotKeySpecifierLength (false), out var rH);
-		if (canSizeW) {
-			boundsChanged = true;
-			_width = rW;
-		}
-		if (canSizeH) {
-			boundsChanged = true;
-			_height = rH;
-		}
-		if (boundsChanged) {
-			Bounds = new Rect (Bounds.X, Bounds.Y, canSizeW ? rW : Bounds.Width, canSizeH ? rH : Bounds.Height);
-		}
-
-		return boundsChanged;
-	}
-
-
-	/// <summary>
-	/// Determines if the View's <see cref="Width"/> can be set to a new value.
-	/// </summary>
-	/// <param name="desiredWidth"></param>
-	/// <param name="resultWidth">
-	/// Contains the width that would result if <see cref="Width"/> were set to
-	/// <paramref name="desiredWidth"/>"/>
-	/// </param>
-	/// <returns>
-	/// <see langword="true"/> if the View's <see cref="Width"/> can be changed to the specified value. False
-	/// otherwise.
-	/// </returns>
-	internal bool TrySetWidth (int desiredWidth, out int resultWidth)
-	{
-		var w = desiredWidth;
-		bool canSetWidth;
-		switch (Width) {
-		case Dim.DimCombine _:
-		case Dim.DimView _:
-		case Dim.DimFill _:
-			// It's a Dim.DimCombine and so can't be assigned. Let it have it's Width anchored.
-			w = Width.Anchor (w);
-			canSetWidth = !ValidatePosDim;
-			break;
-		case Dim.DimFactor factor:
-			// Tries to get the SuperView Width otherwise the view Width.
-			var sw = SuperView != null ? SuperView.Frame.Width : w;
-			if (factor.IsFromRemaining ()) {
-				sw -= Frame.X;
-			}
-			w = Width.Anchor (sw);
-			canSetWidth = !ValidatePosDim;
-			break;
-		default:
-			canSetWidth = true;
-			break;
-		}
-		resultWidth = w;
-
-		return canSetWidth;
-	}
-
-	/// <summary>
-	/// Determines if the View's <see cref="Height"/> can be set to a new value.
-	/// </summary>
-	/// <param name="desiredHeight"></param>
-	/// <param name="resultHeight">
-	/// Contains the width that would result if <see cref="Height"/> were set to
-	/// <paramref name="desiredHeight"/>"/>
-	/// </param>
-	/// <returns>
-	/// <see langword="true"/> if the View's <see cref="Height"/> can be changed to the specified value. False
-	/// otherwise.
-	/// </returns>
-	internal bool TrySetHeight (int desiredHeight, out int resultHeight)
-	{
-		var h = desiredHeight;
-		bool canSetHeight;
-		switch (Height) {
-		case Dim.DimCombine _:
-		case Dim.DimView _:
-		case Dim.DimFill _:
-			// It's a Dim.DimCombine and so can't be assigned. Let it have it's height anchored.
-			h = Height.Anchor (h);
-			canSetHeight = !ValidatePosDim;
-			break;
-		case Dim.DimFactor factor:
-			// Tries to get the SuperView height otherwise the view height.
-			var sh = SuperView != null ? SuperView.Frame.Height : h;
-			if (factor.IsFromRemaining ()) {
-				sh -= Frame.Y;
-			}
-			h = Height.Anchor (sh);
-			canSetHeight = !ValidatePosDim;
-			break;
-		default:
-			canSetHeight = true;
-			break;
-		}
-		resultHeight = h;
-
-		return canSetHeight;
-	}
-
-	/// <summary>
-	/// Finds which view that belong to the <paramref name="start"/> superview at the provided location.
-	/// </summary>
-	/// <param name="start">The superview where to look for.</param>
-	/// <param name="x">The column location in the superview.</param>
-	/// <param name="y">The row location in the superview.</param>
-	/// <param name="resx">The found view screen relative column location.</param>
-	/// <param name="resy">The found view screen relative row location.</param>
-	/// <returns>
-	/// The view that was found at the <praramref name="x"/> and <praramref name="y"/> coordinates.
-	/// <see langword="null"/> if no view was found.
-	/// </returns>
-	public static View FindDeepestView (View start, int x, int y, out int resx, out int resy)
-	{
-		resy = resx = 0;
-		if (start == null || !start.Frame.Contains (x, y)) {
-			return null;
-		}
-
-		var startFrame = start.Frame;
-		if (start.InternalSubviews != null) {
-			var count = start.InternalSubviews.Count;
-			if (count > 0) {
-				var boundsOffset = start.GetBoundsOffset ();
-				var rx = x - (startFrame.X + boundsOffset.X);
-				var ry = y - (startFrame.Y + boundsOffset.Y);
-				for (var i = count - 1; i >= 0; i--) {
-					var v = start.InternalSubviews [i];
-					if (v.Visible && v.Frame.Contains (rx, ry)) {
-						var deep = FindDeepestView (v, rx, ry, out resx, out resy);
-						if (deep == null) {
-							return v;
-						}
-						return deep;
-					}
-				}
-			}
-		}
-		resx = x - startFrame.X;
-		resy = y - startFrame.Y;
-		return start;
-	}
-}
+        return dim;
+    }
+}

+ 927 - 725
Terminal.Gui/View/ViewSubViews.cs

@@ -1,725 +1,927 @@
-using System;
-using System.Collections.Generic;
-
-namespace Terminal.Gui;
-
-public partial class View {
-	static readonly IList<View> _empty = new List<View> (0).AsReadOnly ();
-
-	internal bool _addingView;
-
-	List<View> _subviews; // This is null, and allocated on demand.
-
-	View _superView;
-
-	/// <summary>
-	/// Returns the container for this view, or null if this view has not been added to a container.
-	/// </summary>
-	/// <value>The super view.</value>
-	public virtual View SuperView {
-		get => _superView;
-		set => throw new NotImplementedException ();
-	}
-
-	/// <summary>
-	/// This returns a list of the subviews contained by this view.
-	/// </summary>
-	/// <value>The subviews.</value>
-	public IList<View> Subviews => _subviews?.AsReadOnly () ?? _empty;
-
-	// Internally, we use InternalSubviews rather than subviews, as we do not expect us
-	// to make the same mistakes our users make when they poke at the Subviews.
-	internal IList<View> InternalSubviews => _subviews ?? _empty;
-
-	/// <summary>
-	/// Returns a value indicating if this View is currently on Top (Active)
-	/// </summary>
-	public bool IsCurrentTop => Application.Current == this;
-
-	/// <summary>
-	/// Indicates whether the view was added to <see cref="SuperView"/>.
-	/// </summary>
-	public bool IsAdded { get; private set; }
-
-	/// <summary>
-	/// Event fired when this view is added to another.
-	/// </summary>
-	public event EventHandler<SuperViewChangedEventArgs> Added;
-
-	/// <summary>
-	/// Adds a subview (child) to this view.
-	/// </summary>
-	/// <remarks>
-	/// The Views that have been added to this view can be retrieved via the <see cref="Subviews"/> property.
-	/// See also <seealso cref="Remove(View)"/> <seealso cref="RemoveAll"/>
-	/// </remarks>
-	public virtual void Add (View view)
-	{
-		if (view == null) {
-			return;
-		}
-		if (_subviews == null) {
-			_subviews = new List<View> ();
-		}
-		if (_tabIndexes == null) {
-			_tabIndexes = new List<View> ();
-		}
-		_subviews.Add (view);
-		_tabIndexes.Add (view);
-		view._superView = this;
-		if (view.CanFocus) {
-			_addingView = true;
-			if (SuperView?.CanFocus == false) {
-				SuperView._addingView = true;
-				SuperView.CanFocus = true;
-				SuperView._addingView = false;
-			}
-			CanFocus = true;
-			view._tabIndex = _tabIndexes.IndexOf (view);
-			_addingView = false;
-		}
-		if (view.Enabled && !Enabled) {
-			view._oldEnabled = true;
-			view.Enabled = false;
-		}
-
-		OnAdded (new SuperViewChangedEventArgs (this, view));
-		if (IsInitialized && !view.IsInitialized) {
-			view.BeginInit ();
-			view.EndInit ();
-		}
-		CheckDimAuto ();
-		SetNeedsLayout ();
-		SetNeedsDisplay ();
-	}
-
-	/// <summary>
-	/// Adds the specified views (children) to the view.
-	/// </summary>
-	/// <param name="views">Array of one or more views (can be optional parameter).</param>
-	/// <remarks>
-	/// The Views that have been added to this view can be retrieved via the <see cref="Subviews"/> property.
-	/// See also <seealso cref="Remove(View)"/> <seealso cref="RemoveAll"/>
-	/// </remarks>
-	public void Add (params View [] views)
-	{
-		if (views == null) {
-			return;
-		}
-		foreach (var view in views) {
-			Add (view);
-		}
-	}
-
-	/// <summary>
-	/// Method invoked when a subview is being added to this view.
-	/// </summary>
-	/// <param name="e">Event where <see cref="ViewEventArgs.View"/> is the subview being added.</param>
-	public virtual void OnAdded (SuperViewChangedEventArgs e)
-	{
-		var view = e.Child;
-		view.IsAdded = true;
-		view.OnResizeNeeded ();
-		view.Added?.Invoke (this, e);
-	}
-
-	/// <summary>
-	/// Event fired when this view is removed from another.
-	/// </summary>
-	public event EventHandler<SuperViewChangedEventArgs> Removed;
-
-	/// <summary>
-	/// Removes all subviews (children) added via <see cref="Add(View)"/> or <see cref="Add(View[])"/> from this View.
-	/// </summary>
-	public virtual void RemoveAll ()
-	{
-		if (_subviews == null) {
-			return;
-		}
-
-		while (_subviews.Count > 0) {
-			Remove (_subviews [0]);
-		}
-	}
-
-	/// <summary>
-	/// Removes a subview added via <see cref="Add(View)"/> or <see cref="Add(View[])"/> from this View.
-	/// </summary>
-	/// <remarks>
-	/// </remarks>
-	public virtual void Remove (View view)
-	{
-		if (view == null || _subviews == null) {
-			return;
-		}
-
-		var touched = view.Frame;
-		_subviews.Remove (view);
-		_tabIndexes.Remove (view);
-		view._superView = null;
-		view._tabIndex = -1;
-		SetNeedsLayout ();
-		SetNeedsDisplay ();
-
-		foreach (var v in _subviews) {
-			if (v.Frame.IntersectsWith (touched)) {
-				view.SetNeedsDisplay ();
-			}
-		}
-		OnRemoved (new SuperViewChangedEventArgs (this, view));
-		if (Focused == view) {
-			Focused = null;
-		}
-	}
-
-	/// <summary>
-	/// Method invoked when a subview is being removed from this view.
-	/// </summary>
-	/// <param name="e">Event args describing the subview being removed.</param>
-	public virtual void OnRemoved (SuperViewChangedEventArgs e)
-	{
-		var view = e.Child;
-		view.IsAdded = false;
-		view.Removed?.Invoke (this, e);
-	}
-
-
-	void PerformActionForSubview (View subview, Action<View> action)
-	{
-		if (_subviews.Contains (subview)) {
-			action (subview);
-		}
-
-		SetNeedsDisplay ();
-		subview.SetNeedsDisplay ();
-	}
-
-	/// <summary>
-	/// Brings the specified subview to the front so it is drawn on top of any other views.
-	/// </summary>
-	/// <param name="subview">The subview to send to the front</param>
-	/// <remarks>
-	/// <seealso cref="SendSubviewToBack"/>.
-	/// </remarks>
-	public void BringSubviewToFront (View subview) => PerformActionForSubview (subview, x => {
-		_subviews.Remove (x);
-		_subviews.Add (x);
-	});
-
-	/// <summary>
-	/// Sends the specified subview to the front so it is the first view drawn
-	/// </summary>
-	/// <param name="subview">The subview to send to the front</param>
-	/// <remarks>
-	/// <seealso cref="BringSubviewToFront(View)"/>.
-	/// </remarks>
-	public void SendSubviewToBack (View subview) => PerformActionForSubview (subview, x => {
-		_subviews.Remove (x);
-		_subviews.Insert (0, subview);
-	});
-
-	/// <summary>
-	/// Moves the subview backwards in the hierarchy, only one step
-	/// </summary>
-	/// <param name="subview">The subview to send backwards</param>
-	/// <remarks>
-	/// If you want to send the view all the way to the back use SendSubviewToBack.
-	/// </remarks>
-	public void SendSubviewBackwards (View subview) => PerformActionForSubview (subview, x => {
-		var idx = _subviews.IndexOf (x);
-		if (idx > 0) {
-			_subviews.Remove (x);
-			_subviews.Insert (idx - 1, x);
-		}
-	});
-
-	/// <summary>
-	/// Moves the subview backwards in the hierarchy, only one step
-	/// </summary>
-	/// <param name="subview">The subview to send backwards</param>
-	/// <remarks>
-	/// If you want to send the view all the way to the back use SendSubviewToBack.
-	/// </remarks>
-	public void BringSubviewForward (View subview) => PerformActionForSubview (subview, x => {
-		var idx = _subviews.IndexOf (x);
-		if (idx + 1 < _subviews.Count) {
-			_subviews.Remove (x);
-			_subviews.Insert (idx + 1, x);
-		}
-	});
-
-	/// <summary>
-	/// Get the top superview of a given <see cref="View"/>.
-	/// </summary>
-	/// <returns>The superview view.</returns>
-	public View GetTopSuperView (View view = null, View superview = null)
-	{
-		var top = superview ?? Application.Top;
-		for (var v = view?.SuperView ?? this?.SuperView; v != null; v = v.SuperView) {
-			top = v;
-			if (top == superview) {
-				break;
-			}
-		}
-
-		return top;
-	}
-
-
-
-	#region Focus
-	internal enum Direction {
-		Forward,
-		Backward
-	}
-
-	/// <summary>
-	/// Event fired when the view gets focus.
-	/// </summary>
-	public event EventHandler<FocusEventArgs> Enter;
-
-	/// <summary>
-	/// Event fired when the view looses focus.
-	/// </summary>
-	public event EventHandler<FocusEventArgs> Leave;
-
-	Direction _focusDirection;
-
-	internal Direction FocusDirection {
-		get => SuperView?.FocusDirection ?? _focusDirection;
-		set {
-			if (SuperView != null) {
-				SuperView.FocusDirection = value;
-			} else {
-				_focusDirection = value;
-			}
-		}
-	}
-
-
-	// BUGBUG: v2 - Seems weird that this is in View and not Responder.
-	bool _hasFocus;
-
-	/// <inheritdoc/>
-	public override bool HasFocus => _hasFocus;
-
-	void SetHasFocus (bool value, View view, bool force = false)
-	{
-		if (_hasFocus != value || force) {
-			_hasFocus = value;
-			if (value) {
-				OnEnter (view);
-			} else {
-				OnLeave (view);
-			}
-			SetNeedsDisplay ();
-		}
-
-		// Remove focus down the chain of subviews if focus is removed
-		if (!value && Focused != null) {
-			var f = Focused;
-			f.OnLeave (view);
-			f.SetHasFocus (false, view);
-			Focused = null;
-		}
-	}
-
-	/// <summary>
-	/// Event fired when the <see cref="CanFocus"/> value is being changed.
-	/// </summary>
-	public event EventHandler CanFocusChanged;
-
-	/// <inheritdoc/>
-	public override void OnCanFocusChanged () => CanFocusChanged?.Invoke (this, EventArgs.Empty);
-
-	bool _oldCanFocus;
-
-	/// <inheritdoc/>
-	public override bool CanFocus {
-		get => base.CanFocus;
-		set {
-			if (!_addingView && IsInitialized && SuperView?.CanFocus == false && value) {
-				throw new InvalidOperationException ("Cannot set CanFocus to true if the SuperView CanFocus is false!");
-			}
-			if (base.CanFocus != value) {
-				base.CanFocus = value;
-
-				switch (value) {
-				case false when _tabIndex > -1:
-					TabIndex = -1;
-					break;
-				case true when SuperView?.CanFocus == false && _addingView:
-					SuperView.CanFocus = true;
-					break;
-				}
-
-				if (value && _tabIndex == -1) {
-					TabIndex = SuperView != null ? SuperView._tabIndexes.IndexOf (this) : -1;
-				}
-				TabStop = value;
-
-				if (!value && SuperView?.Focused == this) {
-					SuperView.Focused = null;
-				}
-				if (!value && HasFocus) {
-					SetHasFocus (false, this);
-					SuperView?.EnsureFocus ();
-					if (SuperView != null && SuperView.Focused == null) {
-						SuperView.FocusNext ();
-						if (SuperView.Focused == null && Application.Current != null) {
-							Application.Current.FocusNext ();
-						}
-						Application.BringOverlappedTopToFront ();
-					}
-				}
-				if (_subviews != null && IsInitialized) {
-					foreach (var view in _subviews) {
-						if (view.CanFocus != value) {
-							if (!value) {
-								view._oldCanFocus = view.CanFocus;
-								view._oldTabIndex = view._tabIndex;
-								view.CanFocus = false;
-								view._tabIndex = -1;
-							} else {
-								if (_addingView) {
-									view._addingView = true;
-								}
-								view.CanFocus = view._oldCanFocus;
-								view._tabIndex = view._oldTabIndex;
-								view._addingView = false;
-							}
-						}
-					}
-				}
-				OnCanFocusChanged ();
-				SetNeedsDisplay ();
-			}
-		}
-	}
-
-
-	/// <inheritdoc/>
-	public override bool OnEnter (View view)
-	{
-		var args = new FocusEventArgs (view);
-		Enter?.Invoke (this, args);
-		if (args.Handled) {
-			return true;
-		}
-		if (base.OnEnter (view)) {
-			return true;
-		}
-
-		return false;
-	}
-
-	/// <inheritdoc/>
-	public override bool OnLeave (View view)
-	{
-		var args = new FocusEventArgs (view);
-		Leave?.Invoke (this, args);
-		if (args.Handled) {
-			return true;
-		}
-		if (base.OnLeave (view)) {
-			return true;
-		}
-
-		Driver?.SetCursorVisibility (CursorVisibility.Invisible);
-		return false;
-	}
-
-	/// <summary>
-	/// Returns the currently focused view inside this view, or null if nothing is focused.
-	/// </summary>
-	/// <value>The focused.</value>
-	public View Focused { get; private set; }
-
-	/// <summary>
-	/// Returns the most focused view in the chain of subviews (the leaf view that has the focus).
-	/// </summary>
-	/// <value>The most focused View.</value>
-	public View MostFocused {
-		get {
-			if (Focused == null) {
-				return null;
-			}
-			var most = Focused.MostFocused;
-			if (most != null) {
-				return most;
-			}
-			return Focused;
-		}
-	}
-
-	/// <summary>
-	/// Causes the specified subview to have focus.
-	/// </summary>
-	/// <param name="view">View.</param>
-	void SetFocus (View view)
-	{
-		if (view == null) {
-			return;
-		}
-		//Console.WriteLine ($"Request to focus {view}");
-		if (!view.CanFocus || !view.Visible || !view.Enabled) {
-			return;
-		}
-		if (Focused?._hasFocus == true && Focused == view) {
-			return;
-		}
-		if (Focused?._hasFocus == true && Focused?.SuperView == view || view == this) {
-
-			if (!view._hasFocus) {
-				view._hasFocus = true;
-			}
-			return;
-		}
-		// Make sure that this view is a subview
-		View c;
-		for (c = view._superView; c != null; c = c._superView) {
-			if (c == this) {
-				break;
-			}
-		}
-		if (c == null) {
-			throw new ArgumentException ("the specified view is not part of the hierarchy of this view");
-		}
-
-		if (Focused != null) {
-			Focused.SetHasFocus (false, view);
-		}
-
-		var f = Focused;
-		Focused = view;
-		Focused.SetHasFocus (true, f);
-		Focused.EnsureFocus ();
-
-		// Send focus upwards
-		if (SuperView != null) {
-			SuperView.SetFocus (this);
-		} else {
-			SetFocus (this);
-		}
-	}
-
-	/// <summary>
-	/// Causes the specified view and the entire parent hierarchy to have the focused order updated.
-	/// </summary>
-	public void SetFocus ()
-	{
-		if (!CanBeVisible (this) || !Enabled) {
-			if (HasFocus) {
-				SetHasFocus (false, this);
-			}
-			return;
-		}
-
-		if (SuperView != null) {
-			SuperView.SetFocus (this);
-		} else {
-			SetFocus (this);
-		}
-	}
-
-	/// <summary>
-	/// Finds the first view in the hierarchy that wants to get the focus if nothing is currently focused, otherwise, does
-	/// nothing.
-	/// </summary>
-	public void EnsureFocus ()
-	{
-		if (Focused == null && _subviews?.Count > 0) {
-			if (FocusDirection == Direction.Forward) {
-				FocusFirst ();
-			} else {
-				FocusLast ();
-			}
-		}
-	}
-
-	/// <summary>
-	/// Focuses the first focusable subview if one exists.
-	/// </summary>
-	public void FocusFirst ()
-	{
-		if (!CanBeVisible (this)) {
-			return;
-		}
-
-		if (_tabIndexes == null) {
-			SuperView?.SetFocus (this);
-			return;
-		}
-
-		foreach (var view in _tabIndexes) {
-			if (view.CanFocus && view._tabStop && view.Visible && view.Enabled) {
-				SetFocus (view);
-				return;
-			}
-		}
-	}
-
-	/// <summary>
-	/// Focuses the last focusable subview if one exists.
-	/// </summary>
-	public void FocusLast ()
-	{
-		if (!CanBeVisible (this)) {
-			return;
-		}
-
-		if (_tabIndexes == null) {
-			SuperView?.SetFocus (this);
-			return;
-		}
-
-		for (var i = _tabIndexes.Count; i > 0;) {
-			i--;
-
-			var v = _tabIndexes [i];
-			if (v.CanFocus && v._tabStop && v.Visible && v.Enabled) {
-				SetFocus (v);
-				return;
-			}
-		}
-	}
-
-	/// <summary>
-	/// Focuses the previous view.
-	/// </summary>
-	/// <returns><see langword="true"/> if previous was focused, <see langword="false"/> otherwise.</returns>
-	public bool FocusPrev ()
-	{
-		if (!CanBeVisible (this)) {
-			return false;
-		}
-
-		FocusDirection = Direction.Backward;
-		if (_tabIndexes == null || _tabIndexes.Count == 0) {
-			return false;
-		}
-
-		if (Focused == null) {
-			FocusLast ();
-			return Focused != null;
-		}
-
-		var focusedIdx = -1;
-		for (var i = _tabIndexes.Count; i > 0;) {
-			i--;
-			var w = _tabIndexes [i];
-
-			if (w.HasFocus) {
-				if (w.FocusPrev ()) {
-					return true;
-				}
-				focusedIdx = i;
-				continue;
-			}
-			if (w.CanFocus && focusedIdx != -1 && w._tabStop && w.Visible && w.Enabled) {
-				Focused.SetHasFocus (false, w);
-
-				if (w.CanFocus && w._tabStop && w.Visible && w.Enabled) {
-					w.FocusLast ();
-				}
-
-				SetFocus (w);
-				return true;
-			}
-		}
-		if (Focused != null) {
-			Focused.SetHasFocus (false, this);
-			Focused = null;
-		}
-		return false;
-	}
-
-	/// <summary>
-	/// Focuses the next view.
-	/// </summary>
-	/// <returns><see langword="true"/> if next was focused, <see langword="false"/> otherwise.</returns>
-	public bool FocusNext ()
-	{
-		if (!CanBeVisible (this)) {
-			return false;
-		}
-
-		FocusDirection = Direction.Forward;
-		if (_tabIndexes == null || _tabIndexes.Count == 0) {
-			return false;
-		}
-
-		if (Focused == null) {
-			FocusFirst ();
-			return Focused != null;
-		}
-		var focusedIdx = -1;
-		for (var i = 0; i < _tabIndexes.Count; i++) {
-			var w = _tabIndexes [i];
-
-			if (w.HasFocus) {
-				if (w.FocusNext ()) {
-					return true;
-				}
-				focusedIdx = i;
-				continue;
-			}
-			if (w.CanFocus && focusedIdx != -1 && w._tabStop && w.Visible && w.Enabled) {
-				Focused.SetHasFocus (false, w);
-
-				if (w.CanFocus && w._tabStop && w.Visible && w.Enabled) {
-					w.FocusFirst ();
-				}
-
-				SetFocus (w);
-				return true;
-			}
-		}
-		if (Focused != null) {
-			Focused.SetHasFocus (false, this);
-			Focused = null;
-		}
-		return false;
-	}
-
-	View GetMostFocused (View view)
-	{
-		if (view == null) {
-			return null;
-		}
-
-		return view.Focused != null ? GetMostFocused (view.Focused) : view;
-	}
-
-	/// <summary>
-	/// Positions the cursor in the right position based on the currently focused view in the chain.
-	/// </summary>
-	/// Views that are focusable should override
-	/// <see cref="PositionCursor"/>
-	/// to ensure
-	/// the cursor is placed in a location that makes sense. Unix terminals do not have
-	/// a way of hiding the cursor, so it can be distracting to have the cursor left at
-	/// the last focused view. Views should make sure that they place the cursor
-	/// in a visually sensible place.
-	public virtual void PositionCursor ()
-	{
-		if (!CanBeVisible (this) || !Enabled) {
-			return;
-		}
-
-		// BUGBUG: v2 - This needs to support children of Frames too
-
-		if (Focused == null && SuperView != null) {
-			SuperView.EnsureFocus ();
-		} else if (Focused?.Visible == true && Focused?.Enabled == true && Focused?.Frame.Width > 0 && Focused.Frame.Height > 0) {
-			Focused.PositionCursor ();
-		} else if (Focused?.Visible == true && Focused?.Enabled == false) {
-			Focused = null;
-		} else if (CanFocus && HasFocus && Visible && Frame.Width > 0 && Frame.Height > 0) {
-			Move (TextFormatter.HotKeyPos == -1 ? 0 : TextFormatter.CursorPosition, 0);
-		} else {
-			Move (_frame.X, _frame.Y);
-		}
-	}
-	#endregion Focus
-}
+namespace Terminal.Gui;
+
+public partial class View
+{
+    private static readonly IList<View> _empty = new List<View> (0).AsReadOnly ();
+
+    internal bool _addingView;
+
+    private List<View> _subviews; // This is null, and allocated on demand.
+
+    private View _superView;
+
+    /// <summary>
+    ///     Indicates whether the view was added to <see cref="SuperView"/>.
+    /// </summary>
+    public bool IsAdded { get; private set; }
+
+    /// <summary>
+    ///     Returns a value indicating if this View is currently on Top (Active)
+    /// </summary>
+    public bool IsCurrentTop => Application.Current == this;
+
+    /// <summary>
+    ///     This returns a list of the subviews contained by this view.
+    /// </summary>
+    /// <value>The subviews.</value>
+    public IList<View> Subviews => _subviews?.AsReadOnly () ?? _empty;
+
+    /// <summary>
+    ///     Returns the container for this view, or null if this view has not been added to a container.
+    /// </summary>
+    /// <value>The super view.</value>
+    public virtual View SuperView
+    {
+        get => _superView;
+        set => throw new NotImplementedException ();
+    }
+
+    // Internally, we use InternalSubviews rather than subviews, as we do not expect us
+    // to make the same mistakes our users make when they poke at the Subviews.
+    internal IList<View> InternalSubviews => _subviews ?? _empty;
+
+    /// <summary>
+    ///     Adds a subview (child) to this view.
+    /// </summary>
+    /// <remarks>
+    ///     The Views that have been added to this view can be retrieved via the <see cref="Subviews"/> property.
+    ///     See also <seealso cref="Remove(View)"/> <seealso cref="RemoveAll"/>
+    /// </remarks>
+    public virtual void Add (View view)
+    {
+        if (view == null)
+        {
+            return;
+        }
+
+        if (_subviews == null)
+        {
+            _subviews = new List<View> ();
+        }
+
+        if (_tabIndexes == null)
+        {
+            _tabIndexes = new List<View> ();
+        }
+
+        _subviews.Add (view);
+        _tabIndexes.Add (view);
+        view._superView = this;
+
+        if (view.CanFocus)
+        {
+            _addingView = true;
+
+            if (SuperView?.CanFocus == false)
+            {
+                SuperView._addingView = true;
+                SuperView.CanFocus = true;
+                SuperView._addingView = false;
+            }
+
+            CanFocus = true;
+            view._tabIndex = _tabIndexes.IndexOf (view);
+            _addingView = false;
+        }
+
+        if (view.Enabled && !Enabled)
+        {
+            view._oldEnabled = true;
+            view.Enabled = false;
+        }
+
+        OnAdded (new SuperViewChangedEventArgs (this, view));
+
+        if (IsInitialized && !view.IsInitialized)
+        {
+            view.BeginInit ();
+            view.EndInit ();
+        }
+
+        CheckDimAuto ();
+        SetNeedsLayout ();
+        SetNeedsDisplay ();
+    }
+
+    /// <summary>
+    ///     Adds the specified views (children) to the view.
+    /// </summary>
+    /// <param name="views">Array of one or more views (can be optional parameter).</param>
+    /// <remarks>
+    ///     The Views that have been added to this view can be retrieved via the <see cref="Subviews"/> property.
+    ///     See also <seealso cref="Remove(View)"/> <seealso cref="RemoveAll"/>
+    /// </remarks>
+    public void Add (params View [] views)
+    {
+        if (views == null)
+        {
+            return;
+        }
+
+        foreach (View view in views)
+        {
+            Add (view);
+        }
+    }
+
+    /// <summary>
+    ///     Event fired when this view is added to another.
+    /// </summary>
+    public event EventHandler<SuperViewChangedEventArgs> Added;
+
+    /// <summary>
+    ///     Moves the subview backwards in the hierarchy, only one step
+    /// </summary>
+    /// <param name="subview">The subview to send backwards</param>
+    /// <remarks>
+    ///     If you want to send the view all the way to the back use SendSubviewToBack.
+    /// </remarks>
+    public void BringSubviewForward (View subview)
+    {
+        PerformActionForSubview (
+                                 subview,
+                                 x =>
+                                 {
+                                     int idx = _subviews.IndexOf (x);
+
+                                     if (idx + 1 < _subviews.Count)
+                                     {
+                                         _subviews.Remove (x);
+                                         _subviews.Insert (idx + 1, x);
+                                     }
+                                 });
+    }
+
+    /// <summary>
+    ///     Brings the specified subview to the front so it is drawn on top of any other views.
+    /// </summary>
+    /// <param name="subview">The subview to send to the front</param>
+    /// <remarks>
+    ///     <seealso cref="SendSubviewToBack"/>.
+    /// </remarks>
+    public void BringSubviewToFront (View subview)
+    {
+        PerformActionForSubview (
+                                 subview,
+                                 x =>
+                                 {
+                                     _subviews.Remove (x);
+                                     _subviews.Add (x);
+                                 });
+    }
+
+    /// <summary>
+    ///     Get the top superview of a given <see cref="View"/>.
+    /// </summary>
+    /// <returns>The superview view.</returns>
+    public View GetTopSuperView (View view = null, View superview = null)
+    {
+        View top = superview ?? Application.Top;
+
+        for (View v = view?.SuperView ?? this?.SuperView; v != null; v = v.SuperView)
+        {
+            top = v;
+
+            if (top == superview)
+            {
+                break;
+            }
+        }
+
+        return top;
+    }
+
+    /// <summary>
+    ///     Method invoked when a subview is being added to this view.
+    /// </summary>
+    /// <param name="e">Event where <see cref="ViewEventArgs.View"/> is the subview being added.</param>
+    public virtual void OnAdded (SuperViewChangedEventArgs e)
+    {
+        View view = e.Child;
+        view.IsAdded = true;
+        view.OnResizeNeeded ();
+        view.Added?.Invoke (this, e);
+    }
+
+    /// <summary>
+    ///     Method invoked when a subview is being removed from this view.
+    /// </summary>
+    /// <param name="e">Event args describing the subview being removed.</param>
+    public virtual void OnRemoved (SuperViewChangedEventArgs e)
+    {
+        View view = e.Child;
+        view.IsAdded = false;
+        view.Removed?.Invoke (this, e);
+    }
+
+    /// <summary>
+    ///     Removes a subview added via <see cref="Add(View)"/> or <see cref="Add(View[])"/> from this View.
+    /// </summary>
+    /// <remarks>
+    /// </remarks>
+    public virtual void Remove (View view)
+    {
+        if (view == null || _subviews == null)
+        {
+            return;
+        }
+
+        Rect touched = view.Frame;
+        _subviews.Remove (view);
+        _tabIndexes.Remove (view);
+        view._superView = null;
+        view._tabIndex = -1;
+        SetNeedsLayout ();
+        SetNeedsDisplay ();
+
+        foreach (View v in _subviews)
+        {
+            if (v.Frame.IntersectsWith (touched))
+            {
+                view.SetNeedsDisplay ();
+            }
+        }
+
+        OnRemoved (new SuperViewChangedEventArgs (this, view));
+
+        if (Focused == view)
+        {
+            Focused = null;
+        }
+    }
+
+    /// <summary>
+    ///     Removes all subviews (children) added via <see cref="Add(View)"/> or <see cref="Add(View[])"/> from this View.
+    /// </summary>
+    public virtual void RemoveAll ()
+    {
+        if (_subviews == null)
+        {
+            return;
+        }
+
+        while (_subviews.Count > 0)
+        {
+            Remove (_subviews [0]);
+        }
+    }
+
+    /// <summary>
+    ///     Event fired when this view is removed from another.
+    /// </summary>
+    public event EventHandler<SuperViewChangedEventArgs> Removed;
+
+    /// <summary>
+    ///     Moves the subview backwards in the hierarchy, only one step
+    /// </summary>
+    /// <param name="subview">The subview to send backwards</param>
+    /// <remarks>
+    ///     If you want to send the view all the way to the back use SendSubviewToBack.
+    /// </remarks>
+    public void SendSubviewBackwards (View subview)
+    {
+        PerformActionForSubview (
+                                 subview,
+                                 x =>
+                                 {
+                                     int idx = _subviews.IndexOf (x);
+
+                                     if (idx > 0)
+                                     {
+                                         _subviews.Remove (x);
+                                         _subviews.Insert (idx - 1, x);
+                                     }
+                                 });
+    }
+
+    /// <summary>
+    ///     Sends the specified subview to the front so it is the first view drawn
+    /// </summary>
+    /// <param name="subview">The subview to send to the front</param>
+    /// <remarks>
+    ///     <seealso cref="BringSubviewToFront(View)"/>.
+    /// </remarks>
+    public void SendSubviewToBack (View subview)
+    {
+        PerformActionForSubview (
+                                 subview,
+                                 x =>
+                                 {
+                                     _subviews.Remove (x);
+                                     _subviews.Insert (0, subview);
+                                 });
+    }
+
+    private void PerformActionForSubview (View subview, Action<View> action)
+    {
+        if (_subviews.Contains (subview))
+        {
+            action (subview);
+        }
+
+        SetNeedsDisplay ();
+        subview.SetNeedsDisplay ();
+    }
+
+    #region Focus
+
+    internal enum Direction
+    {
+        Forward,
+        Backward
+    }
+
+    /// <summary>
+    ///     Event fired when the view gets focus.
+    /// </summary>
+    public event EventHandler<FocusEventArgs> Enter;
+
+    /// <summary>
+    ///     Event fired when the view looses focus.
+    /// </summary>
+    public event EventHandler<FocusEventArgs> Leave;
+
+    private Direction _focusDirection;
+
+    internal Direction FocusDirection
+    {
+        get => SuperView?.FocusDirection ?? _focusDirection;
+        set
+        {
+            if (SuperView != null)
+            {
+                SuperView.FocusDirection = value;
+            }
+            else
+            {
+                _focusDirection = value;
+            }
+        }
+    }
+
+    // BUGBUG: v2 - Seems weird that this is in View and not Responder.
+    private bool _hasFocus;
+
+    /// <inheritdoc/>
+    public override bool HasFocus => _hasFocus;
+
+    private void SetHasFocus (bool value, View view, bool force = false)
+    {
+        if (_hasFocus != value || force)
+        {
+            _hasFocus = value;
+
+            if (value)
+            {
+                OnEnter (view);
+            }
+            else
+            {
+                OnLeave (view);
+            }
+
+            SetNeedsDisplay ();
+        }
+
+        // Remove focus down the chain of subviews if focus is removed
+        if (!value && Focused != null)
+        {
+            View f = Focused;
+            f.OnLeave (view);
+            f.SetHasFocus (false, view);
+            Focused = null;
+        }
+    }
+
+    /// <summary>
+    ///     Event fired when the <see cref="CanFocus"/> value is being changed.
+    /// </summary>
+    public event EventHandler CanFocusChanged;
+
+    /// <inheritdoc/>
+    public override void OnCanFocusChanged () { CanFocusChanged?.Invoke (this, EventArgs.Empty); }
+
+    private bool _oldCanFocus;
+
+    /// <inheritdoc/>
+    public override bool CanFocus
+    {
+        get => base.CanFocus;
+        set
+        {
+            if (!_addingView && IsInitialized && SuperView?.CanFocus == false && value)
+            {
+                throw new InvalidOperationException ("Cannot set CanFocus to true if the SuperView CanFocus is false!");
+            }
+
+            if (base.CanFocus != value)
+            {
+                base.CanFocus = value;
+
+                switch (value)
+                {
+                    case false when _tabIndex > -1:
+                        TabIndex = -1;
+
+                        break;
+                    case true when SuperView?.CanFocus == false && _addingView:
+                        SuperView.CanFocus = true;
+
+                        break;
+                }
+
+                if (value && _tabIndex == -1)
+                {
+                    TabIndex = SuperView != null ? SuperView._tabIndexes.IndexOf (this) : -1;
+                }
+
+                TabStop = value;
+
+                if (!value && SuperView?.Focused == this)
+                {
+                    SuperView.Focused = null;
+                }
+
+                if (!value && HasFocus)
+                {
+                    SetHasFocus (false, this);
+                    SuperView?.EnsureFocus ();
+
+                    if (SuperView != null && SuperView.Focused == null)
+                    {
+                        SuperView.FocusNext ();
+
+                        if (SuperView.Focused == null && Application.Current != null)
+                        {
+                            Application.Current.FocusNext ();
+                        }
+
+                        Application.BringOverlappedTopToFront ();
+                    }
+                }
+
+                if (_subviews != null && IsInitialized)
+                {
+                    foreach (View view in _subviews)
+                    {
+                        if (view.CanFocus != value)
+                        {
+                            if (!value)
+                            {
+                                view._oldCanFocus = view.CanFocus;
+                                view._oldTabIndex = view._tabIndex;
+                                view.CanFocus = false;
+                                view._tabIndex = -1;
+                            }
+                            else
+                            {
+                                if (_addingView)
+                                {
+                                    view._addingView = true;
+                                }
+
+                                view.CanFocus = view._oldCanFocus;
+                                view._tabIndex = view._oldTabIndex;
+                                view._addingView = false;
+                            }
+                        }
+                    }
+                }
+
+                OnCanFocusChanged ();
+                SetNeedsDisplay ();
+            }
+        }
+    }
+
+    /// <inheritdoc/>
+    public override bool OnEnter (View view)
+    {
+        var args = new FocusEventArgs (view);
+        Enter?.Invoke (this, args);
+
+        if (args.Handled)
+        {
+            return true;
+        }
+
+        if (base.OnEnter (view))
+        {
+            return true;
+        }
+
+        return false;
+    }
+
+    /// <inheritdoc/>
+    public override bool OnLeave (View view)
+    {
+        var args = new FocusEventArgs (view);
+        Leave?.Invoke (this, args);
+
+        if (args.Handled)
+        {
+            return true;
+        }
+
+        if (base.OnLeave (view))
+        {
+            return true;
+        }
+
+        Driver?.SetCursorVisibility (CursorVisibility.Invisible);
+
+        return false;
+    }
+
+    /// <summary>
+    ///     Returns the currently focused view inside this view, or null if nothing is focused.
+    /// </summary>
+    /// <value>The focused.</value>
+    public View Focused { get; private set; }
+
+    /// <summary>
+    ///     Returns the most focused view in the chain of subviews (the leaf view that has the focus).
+    /// </summary>
+    /// <value>The most focused View.</value>
+    public View MostFocused
+    {
+        get
+        {
+            if (Focused == null)
+            {
+                return null;
+            }
+
+            View most = Focused.MostFocused;
+
+            if (most != null)
+            {
+                return most;
+            }
+
+            return Focused;
+        }
+    }
+
+    /// <summary>
+    ///     Causes the specified subview to have focus.
+    /// </summary>
+    /// <param name="view">View.</param>
+    private void SetFocus (View view)
+    {
+        if (view == null)
+        {
+            return;
+        }
+
+        //Console.WriteLine ($"Request to focus {view}");
+        if (!view.CanFocus || !view.Visible || !view.Enabled)
+        {
+            return;
+        }
+
+        if (Focused?._hasFocus == true && Focused == view)
+        {
+            return;
+        }
+
+        if ((Focused?._hasFocus == true && Focused?.SuperView == view) || view == this)
+        {
+            if (!view._hasFocus)
+            {
+                view._hasFocus = true;
+            }
+
+            return;
+        }
+
+        // Make sure that this view is a subview
+        View c;
+
+        for (c = view._superView; c != null; c = c._superView)
+        {
+            if (c == this)
+            {
+                break;
+            }
+        }
+
+        if (c == null)
+        {
+            throw new ArgumentException ("the specified view is not part of the hierarchy of this view");
+        }
+
+        if (Focused != null)
+        {
+            Focused.SetHasFocus (false, view);
+        }
+
+        View f = Focused;
+        Focused = view;
+        Focused.SetHasFocus (true, f);
+        Focused.EnsureFocus ();
+
+        // Send focus upwards
+        if (SuperView != null)
+        {
+            SuperView.SetFocus (this);
+        }
+        else
+        {
+            SetFocus (this);
+        }
+    }
+
+    /// <summary>
+    ///     Causes the specified view and the entire parent hierarchy to have the focused order updated.
+    /// </summary>
+    public void SetFocus ()
+    {
+        if (!CanBeVisible (this) || !Enabled)
+        {
+            if (HasFocus)
+            {
+                SetHasFocus (false, this);
+            }
+
+            return;
+        }
+
+        if (SuperView != null)
+        {
+            SuperView.SetFocus (this);
+        }
+        else
+        {
+            SetFocus (this);
+        }
+    }
+
+    /// <summary>
+    ///     Finds the first view in the hierarchy that wants to get the focus if nothing is currently focused, otherwise, does
+    ///     nothing.
+    /// </summary>
+    public void EnsureFocus ()
+    {
+        if (Focused == null && _subviews?.Count > 0)
+        {
+            if (FocusDirection == Direction.Forward)
+            {
+                FocusFirst ();
+            }
+            else
+            {
+                FocusLast ();
+            }
+        }
+    }
+
+    /// <summary>
+    ///     Focuses the first focusable subview if one exists.
+    /// </summary>
+    public void FocusFirst ()
+    {
+        if (!CanBeVisible (this))
+        {
+            return;
+        }
+
+        if (_tabIndexes == null)
+        {
+            SuperView?.SetFocus (this);
+
+            return;
+        }
+
+        foreach (View view in _tabIndexes)
+        {
+            if (view.CanFocus && view._tabStop && view.Visible && view.Enabled)
+            {
+                SetFocus (view);
+
+                return;
+            }
+        }
+    }
+
+    /// <summary>
+    ///     Focuses the last focusable subview if one exists.
+    /// </summary>
+    public void FocusLast ()
+    {
+        if (!CanBeVisible (this))
+        {
+            return;
+        }
+
+        if (_tabIndexes == null)
+        {
+            SuperView?.SetFocus (this);
+
+            return;
+        }
+
+        for (int i = _tabIndexes.Count; i > 0;)
+        {
+            i--;
+
+            View v = _tabIndexes [i];
+
+            if (v.CanFocus && v._tabStop && v.Visible && v.Enabled)
+            {
+                SetFocus (v);
+
+                return;
+            }
+        }
+    }
+
+    /// <summary>
+    ///     Focuses the previous view.
+    /// </summary>
+    /// <returns><see langword="true"/> if previous was focused, <see langword="false"/> otherwise.</returns>
+    public bool FocusPrev ()
+    {
+        if (!CanBeVisible (this))
+        {
+            return false;
+        }
+
+        FocusDirection = Direction.Backward;
+
+        if (_tabIndexes == null || _tabIndexes.Count == 0)
+        {
+            return false;
+        }
+
+        if (Focused == null)
+        {
+            FocusLast ();
+
+            return Focused != null;
+        }
+
+        int focusedIdx = -1;
+
+        for (int i = _tabIndexes.Count; i > 0;)
+        {
+            i--;
+            View w = _tabIndexes [i];
+
+            if (w.HasFocus)
+            {
+                if (w.FocusPrev ())
+                {
+                    return true;
+                }
+
+                focusedIdx = i;
+
+                continue;
+            }
+
+            if (w.CanFocus && focusedIdx != -1 && w._tabStop && w.Visible && w.Enabled)
+            {
+                Focused.SetHasFocus (false, w);
+
+                if (w.CanFocus && w._tabStop && w.Visible && w.Enabled)
+                {
+                    w.FocusLast ();
+                }
+
+                SetFocus (w);
+
+                return true;
+            }
+        }
+
+        if (Focused != null)
+        {
+            Focused.SetHasFocus (false, this);
+            Focused = null;
+        }
+
+        return false;
+    }
+
+    /// <summary>
+    ///     Focuses the next view.
+    /// </summary>
+    /// <returns><see langword="true"/> if next was focused, <see langword="false"/> otherwise.</returns>
+    public bool FocusNext ()
+    {
+        if (!CanBeVisible (this))
+        {
+            return false;
+        }
+
+        FocusDirection = Direction.Forward;
+
+        if (_tabIndexes == null || _tabIndexes.Count == 0)
+        {
+            return false;
+        }
+
+        if (Focused == null)
+        {
+            FocusFirst ();
+
+            return Focused != null;
+        }
+
+        int focusedIdx = -1;
+
+        for (var i = 0; i < _tabIndexes.Count; i++)
+        {
+            View w = _tabIndexes [i];
+
+            if (w.HasFocus)
+            {
+                if (w.FocusNext ())
+                {
+                    return true;
+                }
+
+                focusedIdx = i;
+
+                continue;
+            }
+
+            if (w.CanFocus && focusedIdx != -1 && w._tabStop && w.Visible && w.Enabled)
+            {
+                Focused.SetHasFocus (false, w);
+
+                if (w.CanFocus && w._tabStop && w.Visible && w.Enabled)
+                {
+                    w.FocusFirst ();
+                }
+
+                SetFocus (w);
+
+                return true;
+            }
+        }
+
+        if (Focused != null)
+        {
+            Focused.SetHasFocus (false, this);
+            Focused = null;
+        }
+
+        return false;
+    }
+
+    private View GetMostFocused (View view)
+    {
+        if (view == null)
+        {
+            return null;
+        }
+
+        return view.Focused != null ? GetMostFocused (view.Focused) : view;
+    }
+
+    /// <summary>
+    ///     Positions the cursor in the right position based on the currently focused view in the chain.
+    /// </summary>
+    /// Views that are focusable should override
+    /// <see cref="PositionCursor"/>
+    /// to ensure
+    /// the cursor is placed in a location that makes sense. Unix terminals do not have
+    /// a way of hiding the cursor, so it can be distracting to have the cursor left at
+    /// the last focused view. Views should make sure that they place the cursor
+    /// in a visually sensible place.
+    public virtual void PositionCursor ()
+    {
+        if (!CanBeVisible (this) || !Enabled)
+        {
+            return;
+        }
+
+        // BUGBUG: v2 - This needs to support children of Frames too
+
+        if (Focused == null && SuperView != null)
+        {
+            SuperView.EnsureFocus ();
+        }
+        else if (Focused?.Visible == true && Focused?.Enabled == true && Focused?.Frame.Width > 0 && Focused.Frame.Height > 0)
+        {
+            Focused.PositionCursor ();
+        }
+        else if (Focused?.Visible == true && Focused?.Enabled == false)
+        {
+            Focused = null;
+        }
+        else if (CanFocus && HasFocus && Visible && Frame.Width > 0 && Frame.Height > 0)
+        {
+            Move (TextFormatter.HotKeyPos == -1 ? 0 : TextFormatter.CursorPosition, 0);
+        }
+        else
+        {
+            Move (_frame.X, _frame.Y);
+        }
+    }
+
+    #endregion Focus
+}

+ 421 - 357
Terminal.Gui/View/ViewText.cs

@@ -1,360 +1,424 @@
-using System;
-using System.Collections.Generic;
-using System.Diagnostics;
-
-namespace Terminal.Gui;
-
-public partial class View {
-	string _text;
-
-	/// <summary>
-	/// The text displayed by the <see cref="View"/>.
-	/// </summary>
-	/// <remarks>
-	///         <para>
-	///         The text will be drawn before any subviews are drawn.
-	///         </para>
-	///         <para>
-	///         The text will be drawn starting at the view origin (0, 0) and will be formatted according
-	///         to <see cref="TextAlignment"/> and <see cref="TextDirection"/>.
-	///         </para>
-	///         <para>
-	///         The text will word-wrap to additional lines if it does not fit horizontally. If <see cref="Bounds"/>'s height
-	///         is 1, the text will be clipped.
-	///         </para>
-	///         <para>
-	///         Set the <see cref="HotKeySpecifier"/> to enable hotkey support. To disable hotkey support set
-	///         <see cref="HotKeySpecifier"/> to
-	///         <c>(Rune)0xffff</c>.
-	///         </para>
-	///         <para>
-	///         If <see cref="AutoSize"/> is <c>true</c>, the <see cref="Bounds"/> will be adjusted to fit the text.
-	///         </para>
-	/// </remarks>
-	public virtual string Text {
-		get => _text;
-		set {
-			_text = value;
-			SetHotKey ();
-			UpdateTextFormatterText ();
-			OnResizeNeeded ();
+namespace Terminal.Gui;
+
+public partial class View
+{
+    private string _text;
+
+    /// <summary>
+    ///     Gets or sets whether trailing spaces at the end of word-wrapped lines are preserved
+    ///     or not when <see cref="TextFormatter.WordWrap"/> is enabled.
+    ///     If <see langword="true"/> trailing spaces at the end of wrapped lines will be removed when
+    ///     <see cref="Text"/> is formatted for display. The default is <see langword="false"/>.
+    /// </summary>
+    public virtual bool PreserveTrailingSpaces
+    {
+        get => TextFormatter.PreserveTrailingSpaces;
+        set
+        {
+            if (TextFormatter.PreserveTrailingSpaces != value)
+            {
+                TextFormatter.PreserveTrailingSpaces = value;
+                TextFormatter.NeedsFormat = true;
+            }
+        }
+    }
+
+    /// <summary>
+    ///     The text displayed by the <see cref="View"/>.
+    /// </summary>
+    /// <remarks>
+    ///     <para>
+    ///         The text will be drawn before any subviews are drawn.
+    ///     </para>
+    ///     <para>
+    ///         The text will be drawn starting at the view origin (0, 0) and will be formatted according
+    ///         to <see cref="TextAlignment"/> and <see cref="TextDirection"/>.
+    ///     </para>
+    ///     <para>
+    ///         The text will word-wrap to additional lines if it does not fit horizontally. If <see cref="Bounds"/>'s height
+    ///         is 1, the text will be clipped.
+    ///     </para>
+    ///     <para>
+    ///         Set the <see cref="HotKeySpecifier"/> to enable hotkey support. To disable hotkey support set
+    ///         <see cref="HotKeySpecifier"/> to
+    ///         <c>(Rune)0xffff</c>.
+    ///     </para>
+    ///     <para>
+    ///         If <see cref="AutoSize"/> is <c>true</c>, the <see cref="Bounds"/> will be adjusted to fit the text.
+    ///     </para>
+    /// </remarks>
+    public virtual string Text
+    {
+        get => _text;
+        set
+        {
+            _text = value;
+            SetHotKey ();
+            UpdateTextFormatterText ();
+            OnResizeNeeded ();
 
 #if DEBUG
-			if (_text != null && string.IsNullOrEmpty (Id)) {
-				Id = _text;
-			}
+            if (_text != null && string.IsNullOrEmpty (Id))
+            {
+                Id = _text;
+            }
 #endif
-		}
-	}
-
-	/// <summary>
-	/// Gets or sets the <see cref="Gui.TextFormatter"/> used to format <see cref="Text"/>.
-	/// </summary>
-	public TextFormatter TextFormatter { get; set; }
-
-	/// <summary>
-	/// Gets or sets whether trailing spaces at the end of word-wrapped lines are preserved
-	/// or not when <see cref="TextFormatter.WordWrap"/> is enabled.
-	/// If <see langword="true"/> trailing spaces at the end of wrapped lines will be removed when
-	/// <see cref="Text"/> is formatted for display. The default is <see langword="false"/>.
-	/// </summary>
-	public virtual bool PreserveTrailingSpaces {
-		get => TextFormatter.PreserveTrailingSpaces;
-		set {
-			if (TextFormatter.PreserveTrailingSpaces != value) {
-				TextFormatter.PreserveTrailingSpaces = value;
-				TextFormatter.NeedsFormat = true;
-			}
-		}
-	}
-
-	/// <summary>
-	/// Gets or sets how the View's <see cref="Text"/> is aligned horizontally when drawn. Changing this property will
-	/// redisplay the <see cref="View"/>.
-	/// </summary>
-	/// <remarks>
-	///         <para>
-	///         If <see cref="AutoSize"/> is <c>true</c>, the <see cref="Bounds"/> will be adjusted to fit the text.
-	///         </para>
-	/// </remarks>
-	/// <value>The text alignment.</value>
-	public virtual TextAlignment TextAlignment {
-		get => TextFormatter.Alignment;
-		set {
-			TextFormatter.Alignment = value;
-			UpdateTextFormatterText ();
-			OnResizeNeeded ();
-		}
-	}
-
-	/// <summary>
-	/// Gets or sets how the View's <see cref="Text"/> is aligned vertically when drawn. Changing this property will redisplay
-	/// the <see cref="View"/>.
-	/// </summary>
-	/// <remarks>
-	///         <para>
-	///         If <see cref="AutoSize"/> is <c>true</c>, the <see cref="Bounds"/> will be adjusted to fit the text.
-	///         </para>
-	/// </remarks>
-	/// <value>The text alignment.</value>
-	public virtual VerticalTextAlignment VerticalTextAlignment {
-		get => TextFormatter.VerticalAlignment;
-		set {
-			TextFormatter.VerticalAlignment = value;
-			SetNeedsDisplay ();
-		}
-	}
-
-	/// <summary>
-	/// Gets or sets the direction of the View's <see cref="Text"/>. Changing this property will redisplay the
-	/// <see cref="View"/>.
-	/// </summary>
-	/// <remarks>
-	///         <para>
-	///         If <see cref="AutoSize"/> is <c>true</c>, the <see cref="Bounds"/> will be adjusted to fit the text.
-	///         </para>
-	/// </remarks>
-	/// <value>The text alignment.</value>
-	public virtual TextDirection TextDirection {
-		get => TextFormatter.Direction;
-		set {
-			UpdateTextDirection (value);
-			TextFormatter.Direction = value;
-		}
-	}
-
-	/// <summary>
-	/// Can be overridden if the <see cref="Terminal.Gui.TextFormatter.Text"/> has
-	/// different format than the default.
-	/// </summary>
-	protected virtual void UpdateTextFormatterText ()
-	{
-		if (TextFormatter != null) {
-			TextFormatter.Text = _text;
-		}
-	}
-
-	void UpdateTextDirection (TextDirection newDirection)
-	{
-		var directionChanged = TextFormatter.IsHorizontalDirection (TextFormatter.Direction) != TextFormatter.IsHorizontalDirection (newDirection);
-		TextFormatter.Direction = newDirection;
-
-		var isValidOldAutoSize = AutoSize && IsValidAutoSize (out var _);
-
-		UpdateTextFormatterText ();
-
-		if (!ValidatePosDim && directionChanged && AutoSize || ValidatePosDim && directionChanged && AutoSize && isValidOldAutoSize) {
-			OnResizeNeeded ();
-		} else if (directionChanged && IsAdded) {
-			ResizeBoundsToFit (Bounds.Size);
-			// BUGBUG: I think this call is redundant.
-			SetFrameToFitText ();
-		} else {
-			SetFrameToFitText ();
-		}
-		SetTextFormatterSize ();
-		SetNeedsDisplay ();
-	}
-
-
-	/// <summary>
-	/// Sets the size of the View to the minimum width or height required to fit <see cref="Text"/>.
-	/// </summary>
-	/// <returns>
-	/// <see langword="true"/> if the size was changed; <see langword="false"/> if <see cref="AutoSize"/> ==
-	/// <see langword="true"/> or
-	/// <see cref="Text"/> will not fit.
-	/// </returns>
-	/// <remarks>
-	/// Always returns <see langword="false"/> if <see cref="AutoSize"/> is <see langword="true"/> or
-	/// if <see cref="Height"/> (Horizontal) or <see cref="Width"/> (Vertical) are not not set or zero.
-	/// Does not take into account word wrapping.
-	/// </remarks>
-	bool SetFrameToFitText ()
-	{
-		// BUGBUG: This API is broken - should not assume Frame.Height == Bounds.Height
-		// <summary>
-		// Gets the minimum dimensions required to fit the View's <see cref="Text"/>, factoring in <see cref="TextDirection"/>.
-		// </summary>
-		// <param name="sizeRequired">The minimum dimensions required.</param>
-		// <returns><see langword="true"/> if the dimensions fit within the View's <see cref="Bounds"/>, <see langword="false"/> otherwise.</returns>
-		// <remarks>
-		// Always returns <see langword="false"/> if <see cref="AutoSize"/> is <see langword="true"/> or
-		// if <see cref="Height"/> (Horizontal) or <see cref="Width"/> (Vertical) are not not set or zero.
-		// Does not take into account word wrapping.
-		// </remarks>
-		bool GetMinimumSizeOfText (out Size sizeRequired)
-		{
-			if (!IsInitialized) {
-				sizeRequired = new Size (0, 0);
-				return false;
-			}
-			sizeRequired = Bounds.Size;
-
-			if (AutoSize || string.IsNullOrEmpty (TextFormatter.Text)) {
-				return false;
-			}
-
-			switch (TextFormatter.IsVerticalDirection (TextDirection)) {
-			case true:
-				var colWidth = TextFormatter.GetSumMaxCharWidth (new List<string> { TextFormatter.Text }, 0, 1);
-				// TODO: v2 - This uses frame.Width; it should only use Bounds
-				if (_frame.Width < colWidth &&
-				    (Width == null ||
-				     Bounds.Width >= 0 &&
-				     Width is Dim.DimAbsolute &&
-				     Width.Anchor (0) >= 0 &&
-				     Width.Anchor (0) < colWidth)) {
-					sizeRequired = new Size (colWidth, Bounds.Height);
-					return true;
-				}
-				break;
-			default:
-				if (_frame.Height < 1 &&
-				    (Height == null ||
-				     Height is Dim.DimAbsolute &&
-				     Height.Anchor (0) == 0)) {
-					sizeRequired = new Size (Bounds.Width, 1);
-					return true;
-				}
-				break;
-			}
-			return false;
-		}
-
-		if (GetMinimumSizeOfText (out var size)) {
-			// TODO: This is a hack.
-			//_width  = size.Width;
-			//_height = size.Height;
-			_frame = new Rect (_frame.Location, size);
-			//throw new InvalidOperationException ("This is a hack.");
-			return true;
-		}
-		return false;
-	}
-
-	/// <summary>
-	/// Gets the width or height of the <see cref="TextFormatter.HotKeySpecifier"/> characters
-	/// in the <see cref="Text"/> property.
-	/// </summary>
-	/// <remarks>
-	/// Only the first HotKey specifier found in <see cref="Text"/> is supported.
-	/// </remarks>
-	/// <param name="isWidth">
-	/// If <see langword="true"/> (the default) the width required for the HotKey specifier is returned. Otherwise the height
-	/// is returned.
-	/// </param>
-	/// <returns>
-	/// The number of characters required for the <see cref="TextFormatter.HotKeySpecifier"/>. If the text
-	/// direction specified
-	/// by <see cref="TextDirection"/> does not match the <paramref name="isWidth"/> parameter, <c>0</c> is returned.
-	/// </returns>
-	public int GetHotKeySpecifierLength (bool isWidth = true)
-	{
-		if (isWidth) {
-			return TextFormatter.IsHorizontalDirection (TextDirection) &&
-			       TextFormatter.Text?.Contains ((char)HotKeySpecifier.Value) == true
-				? Math.Max (HotKeySpecifier.GetColumns (), 0) : 0;
-		}
-		return TextFormatter.IsVerticalDirection (TextDirection) &&
-		       TextFormatter.Text?.Contains ((char)HotKeySpecifier.Value) == true
-			? Math.Max (HotKeySpecifier.GetColumns (), 0) : 0;
-	}
-
-	/// <summary>
-	/// Gets the dimensions required for <see cref="Text"/> ignoring a <see cref="TextFormatter.HotKeySpecifier"/>.
-	/// </summary>
-	/// <returns></returns>
-	internal Size GetSizeNeededForTextWithoutHotKey () => new (TextFormatter.Size.Width - GetHotKeySpecifierLength (),
-		TextFormatter.Size.Height - GetHotKeySpecifierLength (false));
-
-	/// <summary>
-	/// Sets <see cref="TextFormatter"/>.Size to the current <see cref="Bounds"/> size, adjusted for
-	/// <see cref="TextFormatter.HotKeySpecifier"/>.
-	/// </summary>
-	/// <remarks>
-	/// Use this API to set <see cref="TextFormatter.Size"/> when the view has changed such that the
-	/// size required to fit the text has changed.
-	/// changes.
-	/// </remarks>
-	/// <returns></returns>
-	internal void SetTextFormatterSize ()
-	{
-		if (!IsInitialized) {
-			TextFormatter.Size = Size.Empty;
-			return;
-		}
-
-		if (string.IsNullOrEmpty (TextFormatter.Text)) {
-			TextFormatter.Size = Bounds.Size;
-			return;
-		}
-
-		var w = Bounds.Size.Width + GetHotKeySpecifierLength ();
-		if (Width is Dim.DimAuto widthAuto && widthAuto._style != Dim.DimAutoStyle.Subviews) {
-			if (Height is Dim.DimAuto) {
-				// Both are auto. 
-				TextFormatter.Size = new Size (SuperView?.Bounds.Width ?? 0, SuperView?.Bounds.Height ?? 0);
-			} else {
-				TextFormatter.Size = new Size (SuperView?.Bounds.Width ?? 0, Bounds.Size.Height + GetHotKeySpecifierLength ());
-			}
-			w = TextFormatter.GetFormattedSize ().Width;
-		} else {
-			TextFormatter.Size = new Size (w, SuperView?.Bounds.Height ?? 0);
-		}
-
-		var h = Bounds.Size.Height + GetHotKeySpecifierLength ();
-		if (Height is Dim.DimAuto heightAuto && heightAuto._style != Dim.DimAutoStyle.Subviews) {
-			TextFormatter.NeedsFormat = true;
-			h = TextFormatter.GetFormattedSize ().Height;
-		}
-		TextFormatter.Size = new Size (w, h);
-	}
-
-	// TODO: Refactor this to return the Bounds size, not Frame. Move code that accounts for
-	// TODO: Thickness out to callers.
-	/// <summary>
-	/// Gets the size of the <see cref="Frame"/> required to fit <see cref="Text"/> within <see cref="Bounds"/> using the text
-	/// formatting settings of <see cref="View.TextFormatter"/> and accounting for <see cref="HotKeySpecifier"/>.
-	/// </summary>
-	/// <remarks>
-	/// </remarks>
-	/// <returns>The <see cref="Size"/> of the <see cref="Frame"/> required to fit the formatted text.</returns>
-	public Size GetTextAutoSize ()
-	{
-		var x = 0;
-		var y = 0;
-		if (IsInitialized) {
-			x = Bounds.X;
-			y = Bounds.Y;
-		}
-		var rect = TextFormatter.CalcRect (x, y, TextFormatter.Text, TextFormatter.Direction);
-		int newWidth = rect.Size.Width - GetHotKeySpecifierLength () + (Margin == null ? 0 : Margin.Thickness.Horizontal + Border.Thickness.Horizontal + Padding.Thickness.Horizontal);
-		int newHeight = rect.Size.Height - GetHotKeySpecifierLength (false) + (Margin == null ? 0 : Margin.Thickness.Vertical + Border.Thickness.Vertical + Padding.Thickness.Vertical);
-		return new Size (newWidth, newHeight);
-	}
-
-	bool IsValidAutoSize (out Size autoSize)
-	{
-		var rect = TextFormatter.CalcRect (_frame.X, _frame.Y, TextFormatter.Text, TextDirection);
-		autoSize = new Size (rect.Size.Width - GetHotKeySpecifierLength (),
-			rect.Size.Height - GetHotKeySpecifierLength (false));
-		return !(ValidatePosDim && (!(Width is Dim.DimAbsolute) || !(Height is Dim.DimAbsolute)) ||
-			 _frame.Size.Width != rect.Size.Width - GetHotKeySpecifierLength () ||
-			 _frame.Size.Height != rect.Size.Height - GetHotKeySpecifierLength (false));
-	}
-
-	bool IsValidAutoSizeWidth (Dim width)
-	{
-		var rect = TextFormatter.CalcRect (_frame.X, _frame.Y, TextFormatter.Text, TextDirection);
-		var dimValue = width.Anchor (0);
-		return !(ValidatePosDim && !(width is Dim.DimAbsolute) || dimValue != rect.Size.Width - GetHotKeySpecifierLength ());
-	}
-
-	bool IsValidAutoSizeHeight (Dim height)
-	{
-		var rect = TextFormatter.CalcRect (_frame.X, _frame.Y, TextFormatter.Text, TextDirection);
-		var dimValue = height.Anchor (0);
-		return !(ValidatePosDim && !(height is Dim.DimAbsolute) || dimValue != rect.Size.Height - GetHotKeySpecifierLength (false));
-	}
-}
+        }
+    }
+
+    /// <summary>
+    ///     Gets or sets how the View's <see cref="Text"/> is aligned horizontally when drawn. Changing this property will
+    ///     redisplay the <see cref="View"/>.
+    /// </summary>
+    /// <remarks>
+    ///     <para>
+    ///         If <see cref="AutoSize"/> is <c>true</c>, the <see cref="Bounds"/> will be adjusted to fit the text.
+    ///     </para>
+    /// </remarks>
+    /// <value>The text alignment.</value>
+    public virtual TextAlignment TextAlignment
+    {
+        get => TextFormatter.Alignment;
+        set
+        {
+            TextFormatter.Alignment = value;
+            UpdateTextFormatterText ();
+            OnResizeNeeded ();
+        }
+    }
+
+    /// <summary>
+    ///     Gets or sets the direction of the View's <see cref="Text"/>. Changing this property will redisplay the
+    ///     <see cref="View"/>.
+    /// </summary>
+    /// <remarks>
+    ///     <para>
+    ///         If <see cref="AutoSize"/> is <c>true</c>, the <see cref="Bounds"/> will be adjusted to fit the text.
+    ///     </para>
+    /// </remarks>
+    /// <value>The text alignment.</value>
+    public virtual TextDirection TextDirection
+    {
+        get => TextFormatter.Direction;
+        set
+        {
+            UpdateTextDirection (value);
+            TextFormatter.Direction = value;
+        }
+    }
+
+    /// <summary>
+    ///     Gets or sets the <see cref="Gui.TextFormatter"/> used to format <see cref="Text"/>.
+    /// </summary>
+    public TextFormatter TextFormatter { get; set; }
+
+    /// <summary>
+    ///     Gets or sets how the View's <see cref="Text"/> is aligned vertically when drawn. Changing this property will
+    ///     redisplay
+    ///     the <see cref="View"/>.
+    /// </summary>
+    /// <remarks>
+    ///     <para>
+    ///         If <see cref="AutoSize"/> is <c>true</c>, the <see cref="Bounds"/> will be adjusted to fit the text.
+    ///     </para>
+    /// </remarks>
+    /// <value>The text alignment.</value>
+    public virtual VerticalTextAlignment VerticalTextAlignment
+    {
+        get => TextFormatter.VerticalAlignment;
+        set
+        {
+            TextFormatter.VerticalAlignment = value;
+            SetNeedsDisplay ();
+        }
+    }
+
+    /// <summary>
+    ///     Gets the width or height of the <see cref="TextFormatter.HotKeySpecifier"/> characters
+    ///     in the <see cref="Text"/> property.
+    /// </summary>
+    /// <remarks>
+    ///     Only the first HotKey specifier found in <see cref="Text"/> is supported.
+    /// </remarks>
+    /// <param name="isWidth">
+    ///     If <see langword="true"/> (the default) the width required for the HotKey specifier is returned. Otherwise the
+    ///     height
+    ///     is returned.
+    /// </param>
+    /// <returns>
+    ///     The number of characters required for the <see cref="TextFormatter.HotKeySpecifier"/>. If the text
+    ///     direction specified
+    ///     by <see cref="TextDirection"/> does not match the <paramref name="isWidth"/> parameter, <c>0</c> is returned.
+    /// </returns>
+    public int GetHotKeySpecifierLength (bool isWidth = true)
+    {
+        if (isWidth)
+        {
+            return TextFormatter.IsHorizontalDirection (TextDirection) && TextFormatter.Text?.Contains ((char)HotKeySpecifier.Value) == true
+                       ? Math.Max (HotKeySpecifier.GetColumns (), 0)
+                       : 0;
+        }
+
+        return TextFormatter.IsVerticalDirection (TextDirection) && TextFormatter.Text?.Contains ((char)HotKeySpecifier.Value) == true
+                   ? Math.Max (HotKeySpecifier.GetColumns (), 0)
+                   : 0;
+    }
+
+    // TODO: Refactor this to return the Bounds size, not Frame. Move code that accounts for
+    // TODO: Thickness out to callers.
+    /// <summary>
+    ///     Gets the size of the <see cref="Frame"/> required to fit <see cref="Text"/> within <see cref="Bounds"/> using the
+    ///     text
+    ///     formatting settings of <see cref="View.TextFormatter"/> and accounting for <see cref="HotKeySpecifier"/>.
+    /// </summary>
+    /// <remarks>
+    /// </remarks>
+    /// <returns>The <see cref="Size"/> of the <see cref="Frame"/> required to fit the formatted text.</returns>
+    public Size GetTextAutoSize ()
+    {
+        var x = 0;
+        var y = 0;
+
+        if (IsInitialized)
+        {
+            x = Bounds.X;
+            y = Bounds.Y;
+        }
+
+        Rect rect = TextFormatter.CalcRect (x, y, TextFormatter.Text, TextFormatter.Direction);
+
+        int newWidth = rect.Size.Width
+                       - GetHotKeySpecifierLength ()
+                       + (Margin == null ? 0 : Margin.Thickness.Horizontal + Border.Thickness.Horizontal + Padding.Thickness.Horizontal);
+
+        int newHeight = rect.Size.Height
+                        - GetHotKeySpecifierLength (false)
+                        + (Margin == null ? 0 : Margin.Thickness.Vertical + Border.Thickness.Vertical + Padding.Thickness.Vertical);
+
+        return new Size (newWidth, newHeight);
+    }
+
+    /// <summary>
+    ///     Can be overridden if the <see cref="Terminal.Gui.TextFormatter.Text"/> has
+    ///     different format than the default.
+    /// </summary>
+    protected virtual void UpdateTextFormatterText ()
+    {
+        if (TextFormatter != null)
+        {
+            TextFormatter.Text = _text;
+        }
+    }
+
+    /// <summary>
+    ///     Gets the dimensions required for <see cref="Text"/> ignoring a <see cref="TextFormatter.HotKeySpecifier"/>.
+    /// </summary>
+    /// <returns></returns>
+    internal Size GetSizeNeededForTextWithoutHotKey ()
+    {
+        return new Size (
+                         TextFormatter.Size.Width - GetHotKeySpecifierLength (),
+                         TextFormatter.Size.Height - GetHotKeySpecifierLength (false));
+    }
+
+    /// <summary>
+    ///     Sets <see cref="TextFormatter"/>.Size to the current <see cref="Bounds"/> size, adjusted for
+    ///     <see cref="TextFormatter.HotKeySpecifier"/>.
+    /// </summary>
+    /// <remarks>
+    ///     Use this API to set <see cref="TextFormatter.Size"/> when the view has changed such that the
+    ///     size required to fit the text has changed.
+    ///     changes.
+    /// </remarks>
+    /// <returns></returns>
+    internal void SetTextFormatterSize ()
+    {
+        if (!IsInitialized)
+        {
+            TextFormatter.Size = Size.Empty;
+
+            return;
+        }
+
+        if (string.IsNullOrEmpty (TextFormatter.Text))
+        {
+            TextFormatter.Size = Bounds.Size;
+
+            return;
+        }
+
+        int w = Bounds.Size.Width + GetHotKeySpecifierLength ();
+
+        if (Width is Dim.DimAuto widthAuto && widthAuto._style != Dim.DimAutoStyle.Subviews)
+        {
+            if (Height is Dim.DimAuto)
+            {
+                // Both are auto. 
+                TextFormatter.Size = new Size (SuperView?.Bounds.Width ?? 0, SuperView?.Bounds.Height ?? 0);
+            }
+            else
+            {
+                TextFormatter.Size = new Size (SuperView?.Bounds.Width ?? 0, Bounds.Size.Height + GetHotKeySpecifierLength ());
+            }
+
+            w = TextFormatter.GetFormattedSize ().Width;
+        }
+        else
+        {
+            TextFormatter.Size = new Size (w, SuperView?.Bounds.Height ?? 0);
+        }
+
+        int h = Bounds.Size.Height + GetHotKeySpecifierLength ();
+
+        if (Height is Dim.DimAuto heightAuto && heightAuto._style != Dim.DimAutoStyle.Subviews)
+        {
+            TextFormatter.NeedsFormat = true;
+            h = TextFormatter.GetFormattedSize ().Height;
+        }
+
+        TextFormatter.Size = new Size (w, h);
+    }
+
+    private bool IsValidAutoSize (out Size autoSize)
+    {
+        Rect rect = TextFormatter.CalcRect (_frame.X, _frame.Y, TextFormatter.Text, TextDirection);
+
+        autoSize = new Size (
+                             rect.Size.Width - GetHotKeySpecifierLength (),
+                             rect.Size.Height - GetHotKeySpecifierLength (false));
+
+        return !((ValidatePosDim && (!(Width is Dim.DimAbsolute) || !(Height is Dim.DimAbsolute)))
+                 || _frame.Size.Width != rect.Size.Width - GetHotKeySpecifierLength ()
+                 || _frame.Size.Height != rect.Size.Height - GetHotKeySpecifierLength (false));
+    }
+
+    private bool IsValidAutoSizeHeight (Dim height)
+    {
+        Rect rect = TextFormatter.CalcRect (_frame.X, _frame.Y, TextFormatter.Text, TextDirection);
+        int dimValue = height.Anchor (0);
+
+        return !((ValidatePosDim && !(height is Dim.DimAbsolute)) || dimValue != rect.Size.Height - GetHotKeySpecifierLength (false));
+    }
+
+    private bool IsValidAutoSizeWidth (Dim width)
+    {
+        Rect rect = TextFormatter.CalcRect (_frame.X, _frame.Y, TextFormatter.Text, TextDirection);
+        int dimValue = width.Anchor (0);
+
+        return !((ValidatePosDim && !(width is Dim.DimAbsolute)) || dimValue != rect.Size.Width - GetHotKeySpecifierLength ());
+    }
+
+    /// <summary>
+    ///     Sets the size of the View to the minimum width or height required to fit <see cref="Text"/>.
+    /// </summary>
+    /// <returns>
+    ///     <see langword="true"/> if the size was changed; <see langword="false"/> if <see cref="AutoSize"/> ==
+    ///     <see langword="true"/> or
+    ///     <see cref="Text"/> will not fit.
+    /// </returns>
+    /// <remarks>
+    ///     Always returns <see langword="false"/> if <see cref="AutoSize"/> is <see langword="true"/> or
+    ///     if <see cref="Height"/> (Horizontal) or <see cref="Width"/> (Vertical) are not not set or zero.
+    ///     Does not take into account word wrapping.
+    /// </remarks>
+    private bool SetFrameToFitText ()
+    {
+        // BUGBUG: This API is broken - should not assume Frame.Height == Bounds.Height
+        // <summary>
+        // Gets the minimum dimensions required to fit the View's <see cref="Text"/>, factoring in <see cref="TextDirection"/>.
+        // </summary>
+        // <param name="sizeRequired">The minimum dimensions required.</param>
+        // <returns><see langword="true"/> if the dimensions fit within the View's <see cref="Bounds"/>, <see langword="false"/> otherwise.</returns>
+        // <remarks>
+        // Always returns <see langword="false"/> if <see cref="AutoSize"/> is <see langword="true"/> or
+        // if <see cref="Height"/> (Horizontal) or <see cref="Width"/> (Vertical) are not not set or zero.
+        // Does not take into account word wrapping.
+        // </remarks>
+        bool GetMinimumSizeOfText (out Size sizeRequired)
+        {
+            if (!IsInitialized)
+            {
+                sizeRequired = new Size (0, 0);
+
+                return false;
+            }
+
+            sizeRequired = Bounds.Size;
+
+            if (AutoSize || string.IsNullOrEmpty (TextFormatter.Text))
+            {
+                return false;
+            }
+
+            switch (TextFormatter.IsVerticalDirection (TextDirection))
+            {
+                case true:
+                    int colWidth = TextFormatter.GetSumMaxCharWidth (new List<string> { TextFormatter.Text }, 0, 1);
+
+                    // TODO: v2 - This uses frame.Width; it should only use Bounds
+                    if (_frame.Width < colWidth
+                        && (Width == null || (Bounds.Width >= 0 && Width is Dim.DimAbsolute && Width.Anchor (0) >= 0 && Width.Anchor (0) < colWidth)))
+                    {
+                        sizeRequired = new Size (colWidth, Bounds.Height);
+
+                        return true;
+                    }
+
+                    break;
+                default:
+                    if (_frame.Height < 1 && (Height == null || (Height is Dim.DimAbsolute && Height.Anchor (0) == 0)))
+                    {
+                        sizeRequired = new Size (Bounds.Width, 1);
+
+                        return true;
+                    }
+
+                    break;
+            }
+
+            return false;
+        }
+
+        if (GetMinimumSizeOfText (out Size size))
+        {
+            // TODO: This is a hack.
+            //_width  = size.Width;
+            //_height = size.Height;
+            _frame = new Rect (_frame.Location, size);
+
+            //throw new InvalidOperationException ("This is a hack.");
+            return true;
+        }
+
+        return false;
+    }
+
+    private void UpdateTextDirection (TextDirection newDirection)
+    {
+        bool directionChanged = TextFormatter.IsHorizontalDirection (TextFormatter.Direction) != TextFormatter.IsHorizontalDirection (newDirection);
+        TextFormatter.Direction = newDirection;
+
+        bool isValidOldAutoSize = AutoSize && IsValidAutoSize (out Size _);
+
+        UpdateTextFormatterText ();
+
+        if ((!ValidatePosDim && directionChanged && AutoSize) || (ValidatePosDim && directionChanged && AutoSize && isValidOldAutoSize))
+        {
+            OnResizeNeeded ();
+        }
+        else if (directionChanged && IsAdded)
+        {
+            ResizeBoundsToFit (Bounds.Size);
+
+            // BUGBUG: I think this call is redundant.
+            SetFrameToFitText ();
+        }
+        else
+        {
+            SetFrameToFitText ();
+        }
+
+        SetTextFormatterSize ();
+        SetNeedsDisplay ();
+    }
+}

Файлын зөрүү хэтэрхий том тул дарагдсан байна
+ 1116 - 294
Terminal.Gui/Views/Toplevel.cs


+ 397 - 0
Terminal.sln.DotSettings

@@ -0,0 +1,397 @@
+<wpf:ResourceDictionary xml:space="preserve" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:s="clr-namespace:System;assembly=mscorlib" xmlns:ss="urn:shemas-jetbrains-com:settings-storage-xaml" xmlns:wpf="http://schemas.microsoft.com/winfx/2006/xaml/presentation">
+	<s:Boolean x:Key="/Default/CodeEditing/GenerateMemberBody/PlaceBackingFieldAboveProperty/@EntryValue">True</s:Boolean>
+	<s:Int64 x:Key="/Default/CodeEditing/NullCheckPatterns/PatternTypeNamesToPriority/=JetBrains_002EReSharper_002EFeature_002EServices_002ECSharp_002ENullChecking_002EArgumentNullExceptionThrowIfNullPattern/@EntryIndexedValue">5000</s:Int64>
+	<s:Int64 x:Key="/Default/CodeEditing/NullCheckPatterns/PatternTypeNamesToPriority/=JetBrains_002EReSharper_002EFeature_002EServices_002ECSharp_002ENullChecking_002EIfThenThrowPattern/@EntryIndexedValue">1000</s:Int64>
+	<s:Int64 x:Key="/Default/CodeEditing/NullCheckPatterns/PatternTypeNamesToPriority/=JetBrains_002EReSharper_002EFeature_002EServices_002ECSharp_002ENullChecking_002EPatternMatchingIfThenThrowPattern/@EntryIndexedValue">3000</s:Int64>
+	<s:Int64 x:Key="/Default/CodeEditing/NullCheckPatterns/PatternTypeNamesToPriority/=JetBrains_002EReSharper_002EFeature_002EServices_002ECSharp_002ENullChecking_002EThrowExpressionNullCheckPattern/@EntryIndexedValue">2000</s:Int64>
+	<s:String x:Key="/Default/CodeInspection/Highlighting/InspectionSeverities/=ArrangeAttributes/@EntryIndexedValue">SUGGESTION</s:String>
+	<s:String x:Key="/Default/CodeInspection/Highlighting/InspectionSeverities/=ArrangeConstructorOrDestructorBody/@EntryIndexedValue">ERROR</s:String>
+	<s:String x:Key="/Default/CodeInspection/Highlighting/InspectionSeverities/=ArrangeDefaultValueWhenTypeNotEvident/@EntryIndexedValue">SUGGESTION</s:String>
+	<s:String x:Key="/Default/CodeInspection/Highlighting/InspectionSeverities/=ArrangeLocalFunctionBody/@EntryIndexedValue">WARNING</s:String>
+	<s:String x:Key="/Default/CodeInspection/Highlighting/InspectionSeverities/=ArrangeMethodOrOperatorBody/@EntryIndexedValue">HINT</s:String>
+	<s:String x:Key="/Default/CodeInspection/Highlighting/InspectionSeverities/=ArrangeMissingParentheses/@EntryIndexedValue">SUGGESTION</s:String>
+	<s:String x:Key="/Default/CodeInspection/Highlighting/InspectionSeverities/=ArrangeModifiersOrder/@EntryIndexedValue">WARNING</s:String>
+	<s:String x:Key="/Default/CodeInspection/Highlighting/InspectionSeverities/=ArrangeNamespaceBody/@EntryIndexedValue">ERROR</s:String>
+	<s:String x:Key="/Default/CodeInspection/Highlighting/InspectionSeverities/=ArrangeNullCheckingPattern/@EntryIndexedValue">ERROR</s:String>
+	<s:String x:Key="/Default/CodeInspection/Highlighting/InspectionSeverities/=ArrangeObjectCreationWhenTypeNotEvident/@EntryIndexedValue">WARNING</s:String>
+	<s:String x:Key="/Default/CodeInspection/Highlighting/InspectionSeverities/=ArrangeRedundantParentheses/@EntryIndexedValue">WARNING</s:String>
+	<s:String x:Key="/Default/CodeInspection/Highlighting/InspectionSeverities/=ArrangeStaticMemberQualifier/@EntryIndexedValue">WARNING</s:String>
+	<s:String x:Key="/Default/CodeInspection/Highlighting/InspectionSeverities/=ArrangeThisQualifier/@EntryIndexedValue">WARNING</s:String>
+	<s:String x:Key="/Default/CodeInspection/Highlighting/InspectionSeverities/=ArrangeTypeMemberModifiers/@EntryIndexedValue">SUGGESTION</s:String>
+	<s:String x:Key="/Default/CodeInspection/Highlighting/InspectionSeverities/=ArrangeTypeModifiers/@EntryIndexedValue">SUGGESTION</s:String>
+	<s:String x:Key="/Default/CodeInspection/Highlighting/InspectionSeverities/=BuiltInTypeReferenceStyle/@EntryIndexedValue">ERROR</s:String>
+	<s:String x:Key="/Default/CodeInspection/Highlighting/InspectionSeverities/=BuiltInTypeReferenceStyleForMemberAccess/@EntryIndexedValue">ERROR</s:String>
+	<s:String x:Key="/Default/CodeInspection/Highlighting/InspectionSeverities/=CompareNonConstrainedGenericWithNull/@EntryIndexedValue">WARNING</s:String>
+	<s:String x:Key="/Default/CodeInspection/Highlighting/InspectionSeverities/=EnforceDoWhileStatementBraces/@EntryIndexedValue">ERROR</s:String>
+	<s:String x:Key="/Default/CodeInspection/Highlighting/InspectionSeverities/=EnforceFixedStatementBraces/@EntryIndexedValue">ERROR</s:String>
+	<s:String x:Key="/Default/CodeInspection/Highlighting/InspectionSeverities/=EnforceForeachStatementBraces/@EntryIndexedValue">ERROR</s:String>
+	<s:String x:Key="/Default/CodeInspection/Highlighting/InspectionSeverities/=EnforceForStatementBraces/@EntryIndexedValue">ERROR</s:String>
+	<s:String x:Key="/Default/CodeInspection/Highlighting/InspectionSeverities/=EnforceIfStatementBraces/@EntryIndexedValue">ERROR</s:String>
+	<s:String x:Key="/Default/CodeInspection/Highlighting/InspectionSeverities/=EnforceLockStatementBraces/@EntryIndexedValue">ERROR</s:String>
+	<s:String x:Key="/Default/CodeInspection/Highlighting/InspectionSeverities/=EnforceUsingStatementBraces/@EntryIndexedValue">ERROR</s:String>
+	<s:String x:Key="/Default/CodeInspection/Highlighting/InspectionSeverities/=EnforceWhileStatementBraces/@EntryIndexedValue">ERROR</s:String>
+	<s:String x:Key="/Default/CodeInspection/Highlighting/InspectionSeverities/=RemoveRedundantBraces/@EntryIndexedValue">WARNING</s:String>
+	<s:String x:Key="/Default/CodeInspection/Highlighting/InspectionSeverities/=SuggestVarOrType_005FElsewhere/@EntryIndexedValue">SUGGESTION</s:String>
+	<s:String x:Key="/Default/CodeInspection/Highlighting/InspectionSeverities/=TabsAndSpacesMismatch/@EntryIndexedValue">WARNING</s:String>
+	<s:String x:Key="/Default/CodeInspection/Highlighting/InspectionSeverities/=TabsAreDisallowed/@EntryIndexedValue">WARNING</s:String>
+	<s:String x:Key="/Default/CodeInspection/Highlighting/InspectionSeverities/=UnnecessaryWhitespace/@EntryIndexedValue">WARNING</s:String>
+	<s:String x:Key="/Default/CodeInspection/Highlighting/InspectionSeverities/=UseThrowIfNullMethod/@EntryIndexedValue">WARNING</s:String>
+	
+	
+	
+	
+	<s:String x:Key="/Default/CodeStyle/CodeCleanup/SilentCleanupProfile/@EntryValue">Built-in: Full Cleanup</s:String>
+	<s:Boolean x:Key="/Default/CodeStyle/CodeFormatting/CSharpCodeStyle/APPLY_ON_COMPLETION/@EntryValue">True</s:Boolean>
+	<s:String x:Key="/Default/CodeStyle/CodeFormatting/CSharpCodeStyle/BRACES_FOR_FOR/@EntryValue">Required</s:String>
+	<s:String x:Key="/Default/CodeStyle/CodeFormatting/CSharpCodeStyle/BRACES_FOR_FOREACH/@EntryValue">Required</s:String>
+	<s:String x:Key="/Default/CodeStyle/CodeFormatting/CSharpCodeStyle/BRACES_FOR_IFELSE/@EntryValue">Required</s:String>
+	<s:String x:Key="/Default/CodeStyle/CodeFormatting/CSharpCodeStyle/BRACES_FOR_WHILE/@EntryValue">Required</s:String>
+	<s:Boolean x:Key="/Default/CodeStyle/CodeFormatting/CSharpCodeStyle/BUILTIN_TYPE_APPLY_TO_NATIVE_INTEGER/@EntryValue">True</s:Boolean>
+	<s:String x:Key="/Default/CodeStyle/CodeFormatting/CSharpCodeStyle/DEFAULT_VALUE_WHEN_TYPE_NOT_EVIDENT/@EntryValue">DefaultExpression</s:String>
+	<s:String x:Key="/Default/CodeStyle/CodeFormatting/CSharpCodeStyle/NULL_CHECKING_PATTERN_STYLE/@EntryValue">EmptyRecursivePattern</s:String>
+	<s:String x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/ACCESSOR_DECLARATION_BRACES/@EntryValue">NEXT_LINE</s:String>
+	<s:String x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/ACCESSOR_OWNER_DECLARATION_BRACES/@EntryValue">NEXT_LINE</s:String>
+	<s:Boolean x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/ALIGN_FIRST_ARG_BY_PAREN/@EntryValue">True</s:Boolean>
+	<s:Boolean x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/ALIGN_LINQ_QUERY/@EntryValue">True</s:Boolean>
+	<s:Boolean x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/ALIGN_MULTILINE_ARGUMENT/@EntryValue">True</s:Boolean>
+	<s:Boolean x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/ALIGN_MULTILINE_ARRAY_AND_OBJECT_INITIALIZER/@EntryValue">False</s:Boolean>
+	<s:Boolean x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/ALIGN_MULTILINE_BINARY_PATTERNS/@EntryValue">True</s:Boolean>
+	<s:Boolean x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/ALIGN_MULTILINE_CALLS_CHAIN/@EntryValue">True</s:Boolean>
+	<s:Boolean x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/ALIGN_MULTILINE_EXPRESSION/@EntryValue">True</s:Boolean>
+	<s:Boolean x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/ALIGN_MULTILINE_EXTENDS_LIST/@EntryValue">True</s:Boolean>
+	<s:Boolean x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/ALIGN_MULTILINE_LIST_PATTERN/@EntryValue">True</s:Boolean>
+	<s:Boolean x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/ALIGN_MULTILINE_PARAMETER/@EntryValue">True</s:Boolean>
+	<s:Boolean x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/ALIGN_MULTILINE_PROPERTY_PATTERN/@EntryValue">True</s:Boolean>
+	<s:Boolean x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/ALIGN_MULTILINE_SWITCH_EXPRESSION/@EntryValue">True</s:Boolean>
+	<s:Boolean x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/ALIGN_MULTIPLE_DECLARATION/@EntryValue">True</s:Boolean>
+	<s:Boolean x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/ALIGN_MULTLINE_TYPE_PARAMETER_CONSTRAINS/@EntryValue">True</s:Boolean>
+	<s:Boolean x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/ALIGN_MULTLINE_TYPE_PARAMETER_LIST/@EntryValue">True</s:Boolean>
+	<s:Boolean x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/ALIGN_TUPLE_COMPONENTS/@EntryValue">True</s:Boolean>
+	<s:Boolean x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/ALLOW_COMMENT_AFTER_LBRACE/@EntryValue">True</s:Boolean>
+	<s:String x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/ANONYMOUS_METHOD_DECLARATION_BRACES/@EntryValue">NEXT_LINE</s:String>
+	<s:Int64 x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/BLANK_LINES_AFTER_CONTROL_TRANSFER_STATEMENTS/@EntryValue">1</s:Int64>
+	<s:Int64 x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/BLANK_LINES_AFTER_FILE_SCOPED_NAMESPACE_DIRECTIVE/@EntryValue">1</s:Int64>
+	<s:Int64 x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/BLANK_LINES_AFTER_USING_LIST/@EntryValue">1</s:Int64>
+	<s:Int64 x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/BLANK_LINES_AROUND_SINGLE_LINE_LOCAL_METHOD/@EntryValue">1</s:Int64>
+	<s:Int64 x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/BLANK_LINES_AROUND_SINGLE_LINE_TYPE/@EntryValue">1</s:Int64>
+	
+	<s:Int64 x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/BLANK_LINES_BEFORE_CONTROL_TRANSFER_STATEMENTS/@EntryValue">1</s:Int64>
+	<s:Int64 x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/BLANK_LINES_BEFORE_MULTILINE_STATEMENTS/@EntryValue">1</s:Int64>
+	<s:Int64 x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/BLANK_LINES_BEFORE_SINGLE_LINE_COMMENT/@EntryValue">1</s:Int64>
+	<s:Int64 x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/BLANK_LINES_INSIDE_TYPE/@EntryValue">0</s:Int64>
+	<s:String x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/CASE_BLOCK_BRACES/@EntryValue">NEXT_LINE</s:String>
+	<s:String x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/EMPTY_BLOCK_STYLE/@EntryValue">TOGETHER</s:String>
+	<s:Boolean x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/INDENT_ANONYMOUS_METHOD_BLOCK/@EntryValue">True</s:Boolean>
+	<s:Int64 x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/INDENT_SIZE/@EntryValue">4</s:Int64>
+	<s:String x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/INDENT_STYLE/@EntryValue">Spaces</s:String>
+	<s:String x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/INITIALIZER_BRACES/@EntryValue">NEXT_LINE</s:String>
+	<s:String x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/INVOCABLE_DECLARATION_BRACES/@EntryValue">NEXT_LINE</s:String>
+	<s:Int64 x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/KEEP_BLANK_LINES_IN_CODE/@EntryValue">1</s:Int64>
+	<s:Int64 x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/KEEP_BLANK_LINES_IN_DECLARATIONS/@EntryValue">1</s:Int64>
+	<s:Boolean x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/LINE_FEED_AT_FILE_END/@EntryValue">True</s:Boolean>
+	<s:Int64 x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/MAX_ATTRIBUTE_LENGTH_FOR_SAME_LINE/@EntryValue">60</s:Int64>
+	<s:Int64 x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/MAX_ENUM_MEMBERS_ON_LINE/@EntryValue">1</s:Int64>
+	<s:Int64 x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/MAX_FORMAL_PARAMETERS_ON_LINE/@EntryValue">8</s:Int64>
+	<s:Int64 x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/MAX_INVOCATION_ARGUMENTS_ON_LINE/@EntryValue">8</s:Int64>
+	<s:Int64 x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/MAX_PRIMARY_CONSTRUCTOR_PARAMETERS_ON_LINE/@EntryValue">8</s:Int64>
+	<s:String x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/OTHER_BRACES/@EntryValue">NEXT_LINE</s:String>
+	<s:Boolean x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/OUTDENT_COMMAS/@EntryValue">True</s:Boolean>
+	<s:String x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/PLACE_ACCESSOR_ATTRIBUTE_ON_SAME_LINE_EX/@EntryValue">NEVER</s:String>
+	<s:String x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/PLACE_ACCESSORHOLDER_ATTRIBUTE_ON_SAME_LINE_EX/@EntryValue">NEVER</s:String>
+	<s:Boolean x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/PLACE_ELSE_ON_NEW_LINE/@EntryValue">True</s:Boolean>
+	<s:String x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/PLACE_FIELD_ATTRIBUTE_ON_SAME_LINE_EX/@EntryValue">NEVER</s:String>
+	
+	<s:String x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/PLACE_SIMPLE_EMBEDDED_STATEMENT_ON_SAME_LINE/@EntryValue">NEVER</s:String>
+	<s:Boolean x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/PLACE_SIMPLE_METHOD_ON_SINGLE_LINE/@EntryValue">True</s:Boolean>
+	<s:Boolean x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/PLACE_WHILE_ON_NEW_LINE/@EntryValue">True</s:Boolean>
+	<s:Boolean x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/SPACE_BEFORE_ARRAY_ACCESS_BRACKETS/@EntryValue">True</s:Boolean>
+	<s:Boolean x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/SPACE_BEFORE_ARRAY_RANK_BRACKETS/@EntryValue">True</s:Boolean>
+	<s:Boolean x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/SPACE_BEFORE_CHECKED_PARENTHESES/@EntryValue">True</s:Boolean>
+	<s:Boolean x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/SPACE_BEFORE_DEFAULT_PARENTHESES/@EntryValue">True</s:Boolean>
+	<s:Boolean x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/SPACE_BEFORE_EMPTY_METHOD_CALL_PARENTHESES/@EntryValue">True</s:Boolean>
+	<s:Boolean x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/SPACE_BEFORE_EMPTY_METHOD_PARENTHESES/@EntryValue">True</s:Boolean>
+	<s:Boolean x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/SPACE_BEFORE_METHOD_CALL_PARENTHESES/@EntryValue">True</s:Boolean>
+	<s:Boolean x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/SPACE_BEFORE_METHOD_PARENTHESES/@EntryValue">True</s:Boolean>
+	<s:Boolean x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/SPACE_BEFORE_NAMEOF_PARENTHESES/@EntryValue">True</s:Boolean>
+	<s:Boolean x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/SPACE_BEFORE_NEW_PARENTHESES/@EntryValue">True</s:Boolean>
+	<s:Boolean x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/SPACE_BEFORE_SIZEOF_PARENTHESES/@EntryValue">True</s:Boolean>
+	<s:Boolean x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/SPACE_BEFORE_TYPEOF_PARENTHESES/@EntryValue">True</s:Boolean>
+	
+	<s:Int64 x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/TAB_WIDTH/@EntryValue">4</s:Int64>
+	<s:String x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/TYPE_DECLARATION_BRACES/@EntryValue">NEXT_LINE</s:String>
+	<s:Boolean x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/USE_INDENT_FROM_VS/@EntryValue">False</s:Boolean>
+	<s:Boolean x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/WRAP_AFTER_DECLARATION_LPAR/@EntryValue">True</s:Boolean>
+	<s:Boolean x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/WRAP_AFTER_INVOCATION_LPAR/@EntryValue">True</s:Boolean>
+	<s:String x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/WRAP_ARGUMENTS_STYLE/@EntryValue">CHOP_IF_LONG</s:String>
+	<s:Boolean x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/WRAP_BEFORE_BINARY_OPSIGN/@EntryValue">True</s:Boolean>
+	<s:Boolean x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/WRAP_BEFORE_DECLARATION_RPAR/@EntryValue">True</s:Boolean>
+	<s:Boolean x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/WRAP_BEFORE_FIRST_TYPE_PARAMETER_CONSTRAINT/@EntryValue">True</s:Boolean>
+	<s:Boolean x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/WRAP_BEFORE_PRIMARY_CONSTRUCTOR_DECLARATION_LPAR/@EntryValue">False</s:Boolean>
+	<s:Boolean x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/WRAP_BEFORE_PRIMARY_CONSTRUCTOR_DECLARATION_RPAR/@EntryValue">False</s:Boolean>
+	<s:Boolean x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/WRAP_BEFORE_TYPE_PARAMETER_LANGLE/@EntryValue">True</s:Boolean>
+	<s:String x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/WRAP_CHAINED_BINARY_EXPRESSIONS/@EntryValue">CHOP_IF_LONG</s:String>
+	<s:String x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/WRAP_CHAINED_BINARY_PATTERNS/@EntryValue">CHOP_IF_LONG</s:String>
+	<s:String x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/WRAP_CHAINED_METHOD_CALLS/@EntryValue">CHOP_IF_LONG</s:String>
+	<s:Int64 x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/WRAP_LIMIT/@EntryValue">160</s:Int64>
+	<s:String x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/WRAP_LIST_PATTERN/@EntryValue">CHOP_IF_LONG</s:String>
+	<s:String x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/WRAP_PARAMETERS_STYLE/@EntryValue">CHOP_IF_LONG</s:String>
+	<s:Boolean x:Key="/Default/CodeStyle/CodeFormatting/XmlDocFormatter/ALLOW_FAR_ALIGNMENT/@EntryValue">True</s:Boolean>
+	<s:String x:Key="/Default/CodeStyle/CodeFormatting/XmlDocFormatter/IndentSubtags/@EntryValue">OneIndent</s:String>
+	<s:String x:Key="/Default/CodeStyle/CodeFormatting/XmlDocFormatter/IndentTagContent/@EntryValue">OneIndent</s:String>
+	<s:Int64 x:Key="/Default/CodeStyle/CodeFormatting/XmlDocFormatter/MaxSingleLineTagLength/@EntryValue">120</s:Int64>
+	<s:String x:Key="/Default/CodeStyle/CodeFormatting/XmlDocFormatter/TagAttributeIndenting/@EntryValue">ByFirstAttr</s:String>
+	<s:Boolean x:Key="/Default/CodeStyle/CodeFormatting/XmlDocFormatter/TagSpaceBeforeHeaderEnd1/@EntryValue">False</s:Boolean>
+	<s:Boolean x:Key="/Default/CodeStyle/CodeFormatting/XmlDocFormatter/USE_INDENT_FROM_VS/@EntryValue">False</s:Boolean>
+	<s:Boolean x:Key="/Default/CodeStyle/CodeFormatting/XmlDocFormatter/WRAP_LINES/@EntryValue">True</s:Boolean>
+	<s:String x:Key="/Default/CodeStyle/CSharpFileLayoutPatterns/Pattern/@EntryValue">&lt;Patterns xmlns="urn:schemas-jetbrains-com:member-reordering-patterns"&gt;&#xD;
+  &lt;TypePattern DisplayName="Non-reorderable types"&gt;&#xD;
+    &lt;TypePattern.Match&gt;&#xD;
+      &lt;Or&gt;&#xD;
+        &lt;And&gt;&#xD;
+          &lt;Kind Is="Interface" /&gt;&#xD;
+          &lt;Or&gt;&#xD;
+            &lt;HasAttribute Name="System.Runtime.InteropServices.InterfaceTypeAttribute" /&gt;&#xD;
+            &lt;HasAttribute Name="System.Runtime.InteropServices.ComImport" /&gt;&#xD;
+          &lt;/Or&gt;&#xD;
+        &lt;/And&gt;&#xD;
+        &lt;Kind Is="Struct" /&gt;&#xD;
+        &lt;HasAttribute Name="JetBrains.Annotations.NoReorderAttribute" /&gt;&#xD;
+        &lt;HasAttribute Name="JetBrains.Annotations.NoReorder" /&gt;&#xD;
+      &lt;/Or&gt;&#xD;
+    &lt;/TypePattern.Match&gt;&#xD;
+  &lt;/TypePattern&gt;&#xD;
+  &lt;TypePattern DisplayName="xUnit.net Test Classes" RemoveRegions="All"&gt;&#xD;
+    &lt;TypePattern.Match&gt;&#xD;
+      &lt;And&gt;&#xD;
+        &lt;Kind Is="Class" /&gt;&#xD;
+        &lt;HasMember&gt;&#xD;
+          &lt;And&gt;&#xD;
+            &lt;Kind Is="Method" /&gt;&#xD;
+            &lt;HasAttribute Inherited="True" Name="Xunit.FactAttribute" /&gt;&#xD;
+            &lt;HasAttribute Inherited="True" Name="Xunit.TheoryAttribute" /&gt;&#xD;
+          &lt;/And&gt;&#xD;
+        &lt;/HasMember&gt;&#xD;
+      &lt;/And&gt;&#xD;
+    &lt;/TypePattern.Match&gt;&#xD;
+    &lt;Entry DisplayName="Fields"&gt;&#xD;
+      &lt;Entry.Match&gt;&#xD;
+        &lt;And&gt;&#xD;
+          &lt;Kind Is="Field" /&gt;&#xD;
+          &lt;Not&gt;&#xD;
+            &lt;Static /&gt;&#xD;
+          &lt;/Not&gt;&#xD;
+        &lt;/And&gt;&#xD;
+      &lt;/Entry.Match&gt;&#xD;
+      &lt;Entry.SortBy&gt;&#xD;
+        &lt;Readonly /&gt;&#xD;
+        &lt;Name /&gt;&#xD;
+      &lt;/Entry.SortBy&gt;&#xD;
+    &lt;/Entry&gt;&#xD;
+    &lt;Entry DisplayName="Constructors"&gt;&#xD;
+      &lt;Entry.Match&gt;&#xD;
+        &lt;Kind Is="Constructor" /&gt;&#xD;
+      &lt;/Entry.Match&gt;&#xD;
+      &lt;Entry.SortBy&gt;&#xD;
+        &lt;Static /&gt;&#xD;
+      &lt;/Entry.SortBy&gt;&#xD;
+    &lt;/Entry&gt;&#xD;
+    &lt;Entry DisplayName="Teardown Methods"&gt;&#xD;
+      &lt;Entry.Match&gt;&#xD;
+        &lt;And&gt;&#xD;
+          &lt;Kind Is="Method" /&gt;&#xD;
+          &lt;ImplementsInterface Name="System.IDisposable" /&gt;&#xD;
+        &lt;/And&gt;&#xD;
+      &lt;/Entry.Match&gt;&#xD;
+    &lt;/Entry&gt;&#xD;
+    &lt;Entry DisplayName="All other members" /&gt;&#xD;
+    &lt;Entry DisplayName="Test Methods" Priority="100"&gt;&#xD;
+      &lt;Entry.Match&gt;&#xD;
+        &lt;And&gt;&#xD;
+          &lt;Kind Is="Method" /&gt;&#xD;
+          &lt;HasAttribute Name="Xunit.FactAttribute" /&gt;&#xD;
+          &lt;HasAttribute Name="Xunit.TheoryAttribute" /&gt;&#xD;
+        &lt;/And&gt;&#xD;
+      &lt;/Entry.Match&gt;&#xD;
+      &lt;Entry.SortBy&gt;&#xD;
+        &lt;Name /&gt;&#xD;
+      &lt;/Entry.SortBy&gt;&#xD;
+    &lt;/Entry&gt;&#xD;
+  &lt;/TypePattern&gt;&#xD;
+  &lt;TypePattern DisplayName="NUnit Test Fixtures" RemoveRegions="All"&gt;&#xD;
+    &lt;TypePattern.Match&gt;&#xD;
+      &lt;And&gt;&#xD;
+        &lt;Kind Is="Class" /&gt;&#xD;
+        &lt;Or&gt;&#xD;
+          &lt;HasAttribute Inherited="True" Name="NUnit.Framework.TestFixtureAttribute" /&gt;&#xD;
+          &lt;HasAttribute Inherited="True" Name="NUnit.Framework.TestFixtureSourceAttribute" /&gt;&#xD;
+          &lt;HasMember&gt;&#xD;
+            &lt;And&gt;&#xD;
+              &lt;Kind Is="Method" /&gt;&#xD;
+              &lt;HasAttribute Name="NUnit.Framework.TestAttribute" /&gt;&#xD;
+              &lt;HasAttribute Name="NUnit.Framework.TestCaseAttribute" /&gt;&#xD;
+              &lt;HasAttribute Name="NUnit.Framework.TestCaseSourceAttribute" /&gt;&#xD;
+            &lt;/And&gt;&#xD;
+          &lt;/HasMember&gt;&#xD;
+        &lt;/Or&gt;&#xD;
+      &lt;/And&gt;&#xD;
+    &lt;/TypePattern.Match&gt;&#xD;
+    &lt;Entry DisplayName="Setup/Teardown Methods"&gt;&#xD;
+      &lt;Entry.Match&gt;&#xD;
+        &lt;And&gt;&#xD;
+          &lt;Kind Is="Method" /&gt;&#xD;
+          &lt;Or&gt;&#xD;
+            &lt;HasAttribute Inherited="True" Name="NUnit.Framework.SetUpAttribute" /&gt;&#xD;
+            &lt;HasAttribute Inherited="True" Name="NUnit.Framework.TearDownAttribute" /&gt;&#xD;
+            &lt;HasAttribute Inherited="True" Name="NUnit.Framework.TestFixtureSetUpAttribute" /&gt;&#xD;
+            &lt;HasAttribute Inherited="True" Name="NUnit.Framework.TestFixtureTearDownAttribute" /&gt;&#xD;
+            &lt;HasAttribute Inherited="True" Name="NUnit.Framework.OneTimeSetUpAttribute" /&gt;&#xD;
+            &lt;HasAttribute Inherited="True" Name="NUnit.Framework.OneTimeTearDownAttribute" /&gt;&#xD;
+          &lt;/Or&gt;&#xD;
+        &lt;/And&gt;&#xD;
+      &lt;/Entry.Match&gt;&#xD;
+    &lt;/Entry&gt;&#xD;
+    &lt;Entry DisplayName="All other members" /&gt;&#xD;
+    &lt;Entry DisplayName="Test Methods" Priority="100"&gt;&#xD;
+      &lt;Entry.Match&gt;&#xD;
+        &lt;And&gt;&#xD;
+          &lt;Kind Is="Method" /&gt;&#xD;
+          &lt;HasAttribute Name="NUnit.Framework.TestAttribute" /&gt;&#xD;
+          &lt;HasAttribute Name="NUnit.Framework.TestCaseAttribute" /&gt;&#xD;
+          &lt;HasAttribute Name="NUnit.Framework.TestCaseSourceAttribute" /&gt;&#xD;
+        &lt;/And&gt;&#xD;
+      &lt;/Entry.Match&gt;&#xD;
+      &lt;Entry.SortBy&gt;&#xD;
+        &lt;Name /&gt;&#xD;
+      &lt;/Entry.SortBy&gt;&#xD;
+    &lt;/Entry&gt;&#xD;
+  &lt;/TypePattern&gt;&#xD;
+  &lt;TypePattern DisplayName="Default Pattern"&gt;&#xD;
+    &lt;Entry DisplayName="Public Delegates" Priority="100"&gt;&#xD;
+      &lt;Entry.Match&gt;&#xD;
+        &lt;And&gt;&#xD;
+          &lt;Access Is="Public" /&gt;&#xD;
+          &lt;Kind Is="Delegate" /&gt;&#xD;
+        &lt;/And&gt;&#xD;
+      &lt;/Entry.Match&gt;&#xD;
+      &lt;Entry.SortBy&gt;&#xD;
+        &lt;Name /&gt;&#xD;
+      &lt;/Entry.SortBy&gt;&#xD;
+    &lt;/Entry&gt;&#xD;
+    &lt;Entry DisplayName="Public Enums" Priority="100"&gt;&#xD;
+      &lt;Entry.Match&gt;&#xD;
+        &lt;And&gt;&#xD;
+          &lt;Access Is="Public" /&gt;&#xD;
+          &lt;Kind Is="Enum" /&gt;&#xD;
+        &lt;/And&gt;&#xD;
+      &lt;/Entry.Match&gt;&#xD;
+      &lt;Entry.SortBy&gt;&#xD;
+        &lt;Name /&gt;&#xD;
+      &lt;/Entry.SortBy&gt;&#xD;
+    &lt;/Entry&gt;&#xD;
+    &lt;Entry DisplayName="Static Fields and Constants"&gt;&#xD;
+      &lt;Entry.Match&gt;&#xD;
+        &lt;Or&gt;&#xD;
+          &lt;Kind Is="Constant" /&gt;&#xD;
+          &lt;And&gt;&#xD;
+            &lt;Kind Is="Field" /&gt;&#xD;
+            &lt;Static /&gt;&#xD;
+          &lt;/And&gt;&#xD;
+        &lt;/Or&gt;&#xD;
+      &lt;/Entry.Match&gt;&#xD;
+      &lt;Entry.SortBy&gt;&#xD;
+        &lt;Kind Is="Member" /&gt;&#xD;
+        &lt;Access Is="0" /&gt;&#xD;
+        &lt;Name /&gt;&#xD;
+      &lt;/Entry.SortBy&gt;&#xD;
+    &lt;/Entry&gt;&#xD;
+    &lt;Entry DisplayName="Fields"&gt;&#xD;
+      &lt;Entry.Match&gt;&#xD;
+        &lt;And&gt;&#xD;
+          &lt;Kind Is="Field" /&gt;&#xD;
+          &lt;Not&gt;&#xD;
+            &lt;Static /&gt;&#xD;
+          &lt;/Not&gt;&#xD;
+        &lt;/And&gt;&#xD;
+      &lt;/Entry.Match&gt;&#xD;
+      &lt;Entry.SortBy&gt;&#xD;
+        &lt;Access Is="0" /&gt;&#xD;
+        &lt;Readonly /&gt;&#xD;
+        &lt;Name /&gt;&#xD;
+      &lt;/Entry.SortBy&gt;&#xD;
+    &lt;/Entry&gt;&#xD;
+    &lt;Entry DisplayName="Constructors"&gt;&#xD;
+      &lt;Entry.Match&gt;&#xD;
+        &lt;Kind Is="Constructor" /&gt;&#xD;
+      &lt;/Entry.Match&gt;&#xD;
+      &lt;Entry.SortBy&gt;&#xD;
+        &lt;Static /&gt;&#xD;
+        &lt;Access Is="0" /&gt;&#xD;
+      &lt;/Entry.SortBy&gt;&#xD;
+    &lt;/Entry&gt;&#xD;
+    &lt;Property DisplayName="Properties w/ Backing Field" Priority="100"&gt;&#xD;
+      &lt;Entry DisplayName="Backing Field" Priority="100"&gt;&#xD;
+        &lt;Entry.Match&gt;&#xD;
+          &lt;PropertyPart Match="Field" /&gt;&#xD;
+        &lt;/Entry.Match&gt;&#xD;
+      &lt;/Entry&gt;&#xD;
+      &lt;Entry DisplayName="Property" Priority="100"&gt;&#xD;
+        &lt;Entry.Match&gt;&#xD;
+          &lt;PropertyPart Match="Property" /&gt;&#xD;
+        &lt;/Entry.Match&gt;&#xD;
+      &lt;/Entry&gt;&#xD;
+    &lt;/Property&gt;&#xD;
+    &lt;Entry DisplayName="Properties, Indexers"&gt;&#xD;
+      &lt;Entry.Match&gt;&#xD;
+        &lt;Or&gt;&#xD;
+          &lt;Kind Is="Property" /&gt;&#xD;
+          &lt;Kind Is="Indexer" /&gt;&#xD;
+        &lt;/Or&gt;&#xD;
+      &lt;/Entry.Match&gt;&#xD;
+      &lt;Entry.SortBy&gt;&#xD;
+        &lt;Access Is="0" /&gt;&#xD;
+        &lt;Name /&gt;&#xD;
+      &lt;/Entry.SortBy&gt;&#xD;
+    &lt;/Entry&gt;&#xD;
+    &lt;Entry DisplayName="Interface Implementations" Priority="100"&gt;&#xD;
+      &lt;Entry.Match&gt;&#xD;
+        &lt;And&gt;&#xD;
+          &lt;Kind Is="Member" /&gt;&#xD;
+          &lt;ImplementsInterface /&gt;&#xD;
+        &lt;/And&gt;&#xD;
+      &lt;/Entry.Match&gt;&#xD;
+      &lt;Entry.SortBy&gt;&#xD;
+        &lt;ImplementsInterface Immediate="True" /&gt;&#xD;
+      &lt;/Entry.SortBy&gt;&#xD;
+    &lt;/Entry&gt;&#xD;
+    &lt;Entry DisplayName="All other members"&gt;&#xD;
+      &lt;Entry.SortBy&gt;&#xD;
+        &lt;Access Is="0" /&gt;&#xD;
+        &lt;Name /&gt;&#xD;
+        &lt;Override /&gt;&#xD;
+      &lt;/Entry.SortBy&gt;&#xD;
+    &lt;/Entry&gt;&#xD;
+    &lt;Entry DisplayName="Nested Types"&gt;&#xD;
+      &lt;Entry.Match&gt;&#xD;
+        &lt;Kind Is="Type" /&gt;&#xD;
+      &lt;/Entry.Match&gt;&#xD;
+      &lt;Entry.SortBy&gt;&#xD;
+        &lt;Access Is="0" /&gt;&#xD;
+        &lt;Name /&gt;&#xD;
+      &lt;/Entry.SortBy&gt;&#xD;
+    &lt;/Entry&gt;&#xD;
+  &lt;/TypePattern&gt;&#xD;
+&lt;/Patterns&gt;</s:String>
+	<s:String x:Key="/Default/CodeStyle/CSharpVarKeywordUsage/ForBuiltInTypes/@EntryValue">UseVarWhenEvident</s:String>
+	<s:String x:Key="/Default/CodeStyle/CSharpVarKeywordUsage/ForOtherTypes/@EntryValue">UseExplicitType</s:String>
+	<s:String x:Key="/Default/CodeStyle/CSharpVarKeywordUsage/ForSimpleTypes/@EntryValue">UseVarWhenEvident</s:String>
+	<s:Boolean x:Key="/Default/CodeStyle/CSharpVarKeywordUsage/PreferSeparateDeconstructedVariablesDeclaration/@EntryValue">True</s:Boolean>
+	<s:Boolean x:Key="/Default/CodeStyle/EditorConfig/EnableClangFormatSupport/@EntryValue">False</s:Boolean>
+	<s:Boolean x:Key="/Default/CodeStyle/EditorConfig/EnableEditorConfigSupport/@EntryValue">False</s:Boolean>
+	<s:Boolean x:Key="/Default/CodeStyle/EditorConfig/ShowEditorConfigStatusBarIndicator/@EntryValue">True</s:Boolean>
+	<s:Boolean x:Key="/Default/CodeStyle/EditorConfig/SyncToVisualStudio/@EntryValue">True</s:Boolean>
+	<s:Boolean x:Key="/Default/CodeStyle/Naming/CSharpNaming/ApplyAutoDetectedRules/@EntryValue">False</s:Boolean>
+	<s:Boolean x:Key="/Default/Environment/SettingsMigration/IsMigratorApplied/=JetBrains_002EReSharper_002EPsi_002ECSharp_002ECodeStyle_002ECSharpKeepExistingMigration/@EntryIndexedValue">True</s:Boolean>
+	<s:Boolean x:Key="/Default/Environment/SettingsMigration/IsMigratorApplied/=JetBrains_002EReSharper_002EPsi_002ECSharp_002ECodeStyle_002ECSharpPlaceEmbeddedOnSameLineMigration/@EntryIndexedValue">True</s:Boolean>
+	<s:Boolean x:Key="/Default/Environment/SettingsMigration/IsMigratorApplied/=JetBrains_002EReSharper_002EPsi_002ECSharp_002ECodeStyle_002ECSharpUseContinuousIndentInsideBracesMigration/@EntryIndexedValue">True</s:Boolean>
+	<s:Boolean x:Key="/Default/Environment/SettingsMigration/IsMigratorApplied/=JetBrains_002EReSharper_002EPsi_002ECSharp_002ECodeStyle_002ESettingsUpgrade_002EMigrateBlankLinesAroundFieldToBlankLinesAroundProperty/@EntryIndexedValue">True</s:Boolean>
+</wpf:ResourceDictionary>

+ 336 - 263
UICatalog/Scenarios/Dialogs.cs

@@ -1,267 +1,340 @@
-using System.Text;
-using System;
+using System;
 using System.Collections.Generic;
 using Terminal.Gui;
 
-namespace UICatalog.Scenarios {
-	[ScenarioMetadata (Name: "Dialogs", Description: "Demonstrates how to the Dialog class")]
-	[ScenarioCategory ("Dialogs")]
-	public class Dialogs : Scenario {
-		static int CODE_POINT = '你'; // We know this is a wide char
-
-		public override void Setup ()
-		{
-			var frame = new FrameView ("Dialog Options") {
-				X = Pos.Center (),
-				Y = 1,
-				Width = Dim.Percent (75),
-				Height = Dim.Auto ()
-			};
-
-			var label = new Label ("Width:") {
-				X = 0,
-				Y = 0,
-				Width = 15,
-				Height = 1,
-				TextAlignment = Terminal.Gui.TextAlignment.Right,
-			};
-			frame.Add (label);
-
-			var widthEdit = new TextField ("0") {
-				X = Pos.Right (label) + 1,
-				Y = Pos.Top (label),
-				Width = 5,
-				Height = 1
-			};
-			frame.Add (widthEdit);
-
-			label = new Label ("Height:") {
-				X = 0,
-				Y = Pos.Bottom (label),
-				Width = Dim.Width (label),
-				Height = 1,
-				TextAlignment = Terminal.Gui.TextAlignment.Right,
-			};
-			frame.Add (label);
-
-			var heightEdit = new TextField ("0") {
-				X = Pos.Right (label) + 1,
-				Y = Pos.Top (label),
-				Width = 5,
-				Height = 1
-			};
-			frame.Add (heightEdit);
-
-			frame.Add (new Label ("If height & width are both 0,") {
-				X = Pos.Right (widthEdit) + 2,
-				Y = Pos.Top (widthEdit),
-			});
-			frame.Add (new Label ("the Dialog will size to 80% of container.") {
-				X = Pos.Right (heightEdit) + 2,
-				Y = Pos.Top (heightEdit),
-			});
-
-			label = new Label ("Title:") {
-				X = 0,
-				Y = Pos.Bottom (label),
-				Width = Dim.Width (label),
-				Height = 1,
-				TextAlignment = Terminal.Gui.TextAlignment.Right,
-			};
-			frame.Add (label);
-
-			var titleEdit = new TextField ("Title") {
-				X = Pos.Right (label) + 1,
-				Y = Pos.Top (label),
-				Width = Dim.Fill (),
-				Height = 1
-			};
-			frame.Add (titleEdit);
-
-			label = new Label ("Num Buttons:") {
-				X = 0,
-				Y = Pos.Bottom (label),  // BUGBUG: if this is Pos.Bottom (titleEdit) the initial LayoutSubviews does not work correctly?!?!
-				Width = Dim.Width (label),
-				Height = 1,
-				TextAlignment = Terminal.Gui.TextAlignment.Right,
-			};
-			frame.Add (label);
-
-			var numButtonsEdit = new TextField ("3") {
-				X = Pos.Right (label) + 1,
-				Y = Pos.Top (label),
-				Width = 5,
-				Height = 1
-			};
-			frame.Add (numButtonsEdit);
-
-			var glyphsNotWords = new CheckBox ($"Add {Char.ConvertFromUtf32 (CODE_POINT)} to button text to stress wide char support", false) {
-				X = Pos.Left (numButtonsEdit),
-				Y = Pos.Bottom (label),
-				TextAlignment = Terminal.Gui.TextAlignment.Right,
-			};
-			frame.Add (glyphsNotWords);
-
-			label = new Label ("Button Style:") {
-				X = 0,
-				Y = Pos.Bottom (glyphsNotWords),
-				TextAlignment = Terminal.Gui.TextAlignment.Right
-			};
-			frame.Add (label);
-
-			var styleRadioGroup = new RadioGroup (new string [] { "_Center", "_Justify", "_Left", "_Right" }) {
-				X = Pos.Right (label) + 1,
-				Y = Pos.Top (label),
-			};
-			frame.Add (styleRadioGroup);
-
-			frame.ValidatePosDim = true;
-			void Top_LayoutComplete (object sender, EventArgs args)
-			{
-				frame.Height =
-					widthEdit.Frame.Height +
-					heightEdit.Frame.Height +
-					titleEdit.Frame.Height +
-					numButtonsEdit.Frame.Height +
-					glyphsNotWords.Frame.Height +
-					styleRadioGroup.Frame.Height + frame.GetAdornmentsThickness().Vertical;
-			}
-			Application.Top.LayoutComplete += Top_LayoutComplete;
-
-			Win.Add (frame);
-
-			label = new Label ("Button Pressed:") {
-				X = Pos.Center (),
-				Y = Pos.Bottom (frame) + 4,
-				Height = 1,
-				TextAlignment = Terminal.Gui.TextAlignment.Right,
-			};
-			Win.Add (label);
-
-			var buttonPressedLabel = new Label (" ") {
-				X = Pos.Center (),
-				Y = Pos.Bottom (frame) + 5,
-				Width = 25,
-				Height = 1,
-				ColorScheme = Colors.ColorSchemes ["Error"],
-			};
-			// glyphsNotWords
-			// false:var btnText = new [] { "Zero", "One", "Two", "Three", "Four", "Five", "Six", "Seven", "Eight", "Nine" };
-			// true: var btnText = new [] { "0", "\u2780", "➁", "\u2783", "\u2784", "\u2785", "\u2786", "\u2787", "\u2788", "\u2789" };
-			// \u2781 is ➁ dingbats \ufb70 is	
-
-			var showDialogButton = new Button ("_Show Dialog") {
-				X = Pos.Center (),
-				Y = Pos.Bottom (frame) + 2,
-				IsDefault = true,
-			};
-			showDialogButton.Clicked += (s, e) => {
-				var dlg = CreateDemoDialog (widthEdit, heightEdit, titleEdit, numButtonsEdit, glyphsNotWords, styleRadioGroup, buttonPressedLabel);
-				Application.Run (dlg);
-			};
-
-			Win.Add (showDialogButton);
-
-			Win.Add (buttonPressedLabel);
-		}
-
-		Dialog CreateDemoDialog (TextField widthEdit, TextField heightEdit, TextField titleEdit, TextField numButtonsEdit, CheckBox glyphsNotWords, RadioGroup styleRadioGroup,  Label buttonPressedLabel)
-		{
-			Dialog dialog = null;
-			try {
-
-				int width = 0;
-				int.TryParse (widthEdit.Text, out width);
-				int height = 0;
-				int.TryParse (heightEdit.Text, out height);
-				int numButtons = 3;
-				int.TryParse (numButtonsEdit.Text, out numButtons);
-
-				var buttons = new List<Button> ();
-				var clicked = -1;
-				for (int i = 0; i < numButtons; i++) {
-					int buttonId = i;
-					Button button = null;
-					if (glyphsNotWords.Checked == true) {
-						buttonId = i;
-						button = new Button (NumberToWords.Convert (buttonId) + " " + Char.ConvertFromUtf32 (buttonId + CODE_POINT),
-							is_default: buttonId == 0);
-					} else {
-						button = new Button (NumberToWords.Convert (buttonId),
-						       is_default: buttonId == 0);
-					}
-					button.Clicked += (s, e) => {
-						clicked = buttonId;
-						Application.RequestStop ();
-					};
-					buttons.Add (button);
-				}
-				//if (buttons.Count > 1) {
-				//	buttons [1].Text = "Accept";
-				//	buttons [1].IsDefault = true;
-				//	buttons [0].Visible = false;
-				//	buttons [0].Text = "_Back";
-				//	buttons [0].IsDefault = false;
-				//}
-
-				// This tests dynamically adding buttons; ensuring the dialog resizes if needed and 
-				// the buttons are laid out correctly
-				dialog = new Dialog (buttons.ToArray ()) {
-					Title = titleEdit.Text,
-					ButtonAlignment = (Dialog.ButtonAlignments)styleRadioGroup.SelectedItem
-				};
-				if (height != 0 || width != 0) {
-					dialog.Height = height;
-					dialog.Width = width;
-				}
-
-				var add = new Button ("Add a button") {
-					X = Pos.Center (),
-					Y = 10//Pos.Center ()
-				};
-				add.Clicked += (s, e) => {
-					var buttonId = buttons.Count;
-					Button button;
-					if (glyphsNotWords.Checked == true) {
-						button = new Button (NumberToWords.Convert (buttonId) + " " + Char.ConvertFromUtf32 (buttonId + CODE_POINT),
-							is_default: buttonId == 0);
-					} else {
-						button = new Button (NumberToWords.Convert (buttonId),
-							is_default: buttonId == 0);
-					}
-					button.Clicked += (s, e) => {
-						clicked = buttonId;
-						Application.RequestStop ();
-
-					};
-					buttons.Add (button);
-					dialog.AddButton (button);
-					if (buttons.Count > 1) {
-						button.TabIndex = buttons [buttons.Count - 2].TabIndex + 1;
-					}
-				};
-				dialog.Add (add);
-
-				var addChar = new Button ($"Add a {Char.ConvertFromUtf32 (CODE_POINT)} to each button") {
-					X = Pos.Center (),
-					Y = 11//Pos.Center () + 1
-				};
-				addChar.Clicked += (s, e) => {
-					foreach (var button in buttons) {
-						button.Text += Char.ConvertFromUtf32 (CODE_POINT);
-					}
-					dialog.LayoutSubviews ();
-				};
-				dialog.Closed += (s, e) => {
-					buttonPressedLabel.Text = $"{clicked}";
-				};
-				dialog.Add (addChar);
-
-			} catch (FormatException) {
-				buttonPressedLabel.Text = "Invalid Options";
-			}
-			return dialog;
-		}
-	}
-}
+namespace UICatalog.Scenarios;
+
+[ScenarioMetadata ("Dialogs", "Demonstrates how to the Dialog class")]
+[ScenarioCategory ("Dialogs")]
+public class Dialogs : Scenario
+{
+    private static readonly int CODE_POINT = '你'; // We know this is a wide char
+
+    public override void Setup ()
+    {
+        var frame = new FrameView ("Dialog Options")
+        {
+            X = Pos.Center (),
+            Y = 1,
+            Width = Dim.Percent (75),
+            Height = Dim.Auto ()
+        };
+
+        var label = new Label ("Width:")
+        {
+            X = 0,
+            Y = 0,
+            Width = 15,
+            Height = 1,
+            TextAlignment = TextAlignment.Right
+        };
+        frame.Add (label);
+
+        var widthEdit = new TextField ("0")
+        {
+            X = Pos.Right (label) + 1,
+            Y = Pos.Top (label),
+            Width = 5,
+            Height = 1
+        };
+        frame.Add (widthEdit);
+
+        label = new Label ("Height:")
+        {
+            X = 0,
+            Y = Pos.Bottom (label),
+            Width = Dim.Width (label),
+            Height = 1,
+            TextAlignment = TextAlignment.Right
+        };
+        frame.Add (label);
+
+        var heightEdit = new TextField ("0")
+        {
+            X = Pos.Right (label) + 1,
+            Y = Pos.Top (label),
+            Width = 5,
+            Height = 1
+        };
+        frame.Add (heightEdit);
+
+        frame.Add (
+                   new Label ("If height & width are both 0,")
+                   {
+                       X = Pos.Right (widthEdit) + 2,
+                       Y = Pos.Top (widthEdit)
+                   });
+
+        frame.Add (
+                   new Label ("the Dialog will size to 80% of container.")
+                   {
+                       X = Pos.Right (heightEdit) + 2,
+                       Y = Pos.Top (heightEdit)
+                   });
+
+        label = new Label ("Title:")
+        {
+            X = 0,
+            Y = Pos.Bottom (label),
+            Width = Dim.Width (label),
+            Height = 1,
+            TextAlignment = TextAlignment.Right
+        };
+        frame.Add (label);
+
+        var titleEdit = new TextField ("Title")
+        {
+            X = Pos.Right (label) + 1,
+            Y = Pos.Top (label),
+            Width = Dim.Fill (),
+            Height = 1
+        };
+        frame.Add (titleEdit);
+
+        label = new Label ("Num Buttons:")
+        {
+            X = 0,
+            Y = Pos.Bottom (label), // BUGBUG: if this is Pos.Bottom (titleEdit) the initial LayoutSubviews does not work correctly?!?!
+            Width = Dim.Width (label),
+            Height = 1,
+            TextAlignment = TextAlignment.Right
+        };
+        frame.Add (label);
+
+        var numButtonsEdit = new TextField ("3")
+        {
+            X = Pos.Right (label) + 1,
+            Y = Pos.Top (label),
+            Width = 5,
+            Height = 1
+        };
+        frame.Add (numButtonsEdit);
+
+        var glyphsNotWords = new CheckBox ($"Add {char.ConvertFromUtf32 (CODE_POINT)} to button text to stress wide char support")
+        {
+            X = Pos.Left (numButtonsEdit),
+            Y = Pos.Bottom (label),
+            TextAlignment = TextAlignment.Right
+        };
+        frame.Add (glyphsNotWords);
+
+        label = new Label ("Button Style:")
+        {
+            X = 0,
+            Y = Pos.Bottom (glyphsNotWords),
+            TextAlignment = TextAlignment.Right
+        };
+        frame.Add (label);
+
+        var styleRadioGroup = new RadioGroup (new [] { "_Center", "_Justify", "_Left", "_Right" })
+        {
+            X = Pos.Right (label) + 1,
+            Y = Pos.Top (label)
+        };
+        frame.Add (styleRadioGroup);
+
+        frame.ValidatePosDim = true;
+
+        void Top_LayoutComplete (object sender, EventArgs args)
+        {
+            frame.Height =
+                widthEdit.Frame.Height
+                + heightEdit.Frame.Height
+                + titleEdit.Frame.Height
+                + numButtonsEdit.Frame.Height
+                + glyphsNotWords.Frame.Height
+                + styleRadioGroup.Frame.Height
+                + frame.GetAdornmentsThickness ().Vertical;
+        }
+
+        Application.Top.LayoutComplete += Top_LayoutComplete;
+
+        Win.Add (frame);
+
+        label = new Label ("Button Pressed:")
+        {
+            X = Pos.Center (),
+            Y = Pos.Bottom (frame) + 4,
+            Height = 1,
+            TextAlignment = TextAlignment.Right
+        };
+        Win.Add (label);
+
+        var buttonPressedLabel = new Label (" ")
+        {
+            X = Pos.Center (),
+            Y = Pos.Bottom (frame) + 5,
+            Width = 25,
+            Height = 1,
+            ColorScheme = Colors.ColorSchemes ["Error"]
+        };
+
+        // glyphsNotWords
+        // false:var btnText = new [] { "Zero", "One", "Two", "Three", "Four", "Five", "Six", "Seven", "Eight", "Nine" };
+        // true: var btnText = new [] { "0", "\u2780", "➁", "\u2783", "\u2784", "\u2785", "\u2786", "\u2787", "\u2788", "\u2789" };
+        // \u2781 is ➁ dingbats \ufb70 is	
 
+        var showDialogButton = new Button ("_Show Dialog")
+        {
+            X = Pos.Center (),
+            Y = Pos.Bottom (frame) + 2,
+            IsDefault = true
+        };
+
+        showDialogButton.Clicked += (s, e) =>
+                                    {
+                                        Dialog dlg = CreateDemoDialog (
+                                                                       widthEdit,
+                                                                       heightEdit,
+                                                                       titleEdit,
+                                                                       numButtonsEdit,
+                                                                       glyphsNotWords,
+                                                                       styleRadioGroup,
+                                                                       buttonPressedLabel);
+                                        Application.Run (dlg);
+                                    };
+
+        Win.Add (showDialogButton);
+
+        Win.Add (buttonPressedLabel);
+    }
+
+    private Dialog CreateDemoDialog (
+        TextField widthEdit,
+        TextField heightEdit,
+        TextField titleEdit,
+        TextField numButtonsEdit,
+        CheckBox glyphsNotWords,
+        RadioGroup styleRadioGroup,
+        Label buttonPressedLabel
+    )
+    {
+        Dialog dialog = null;
+
+        try
+        {
+            var width = 0;
+            int.TryParse (widthEdit.Text, out width);
+            var height = 0;
+            int.TryParse (heightEdit.Text, out height);
+            var numButtons = 3;
+            int.TryParse (numButtonsEdit.Text, out numButtons);
+
+            List<Button> buttons = new List<Button> ();
+            int clicked = -1;
+
+            for (var i = 0; i < numButtons; i++)
+            {
+                int buttonId = i;
+                Button button = null;
+
+                if (glyphsNotWords.Checked == true)
+                {
+                    buttonId = i;
+
+                    button = new Button (
+                                         NumberToWords.Convert (buttonId) + " " + char.ConvertFromUtf32 (buttonId + CODE_POINT),
+                                         buttonId == 0);
+                }
+                else
+                {
+                    button = new Button (
+                                         NumberToWords.Convert (buttonId),
+                                         buttonId == 0);
+                }
+
+                button.Clicked += (s, e) =>
+                                  {
+                                      clicked = buttonId;
+                                      Application.RequestStop ();
+                                  };
+                buttons.Add (button);
+            }
+
+            //if (buttons.Count > 1) {
+            //	buttons [1].Text = "Accept";
+            //	buttons [1].IsDefault = true;
+            //	buttons [0].Visible = false;
+            //	buttons [0].Text = "_Back";
+            //	buttons [0].IsDefault = false;
+            //}
+
+            // This tests dynamically adding buttons; ensuring the dialog resizes if needed and 
+            // the buttons are laid out correctly
+            dialog = new Dialog (buttons.ToArray ())
+            {
+                Title = titleEdit.Text,
+                ButtonAlignment = (Dialog.ButtonAlignments)styleRadioGroup.SelectedItem
+            };
+
+            if (height != 0 || width != 0)
+            {
+                dialog.Height = height;
+                dialog.Width = width;
+            }
+
+            var add = new Button ("Add a button")
+            {
+                X = Pos.Center (),
+                Y = 10 //Pos.Center ()
+            };
+
+            add.Clicked += (s, e) =>
+                           {
+                               int buttonId = buttons.Count;
+                               Button button;
+
+                               if (glyphsNotWords.Checked == true)
+                               {
+                                   button = new Button (
+                                                        NumberToWords.Convert (buttonId) + " " + char.ConvertFromUtf32 (buttonId + CODE_POINT),
+                                                        buttonId == 0);
+                               }
+                               else
+                               {
+                                   button = new Button (
+                                                        NumberToWords.Convert (buttonId),
+                                                        buttonId == 0);
+                               }
+
+                               button.Clicked += (s, e) =>
+                                                 {
+                                                     clicked = buttonId;
+                                                     Application.RequestStop ();
+                                                 };
+                               buttons.Add (button);
+                               dialog.AddButton (button);
+
+                               if (buttons.Count > 1)
+                               {
+                                   button.TabIndex = buttons [buttons.Count - 2].TabIndex + 1;
+                               }
+                           };
+            dialog.Add (add);
+
+            var addChar = new Button ($"Add a {char.ConvertFromUtf32 (CODE_POINT)} to each button")
+            {
+                X = Pos.Center (),
+                Y = 11 //Pos.Center () + 1
+            };
+
+            addChar.Clicked += (s, e) =>
+                               {
+                                   foreach (Button button in buttons)
+                                   {
+                                       button.Text += char.ConvertFromUtf32 (CODE_POINT);
+                                   }
+
+                                   dialog.LayoutSubviews ();
+                               };
+            dialog.Closed += (s, e) => { buttonPressedLabel.Text = $"{clicked}"; };
+            dialog.Add (addChar);
+        }
+        catch (FormatException)
+        {
+            buttonPressedLabel.Text = "Invalid Options";
+        }
+
+        return dialog;
+    }
+}

+ 180 - 165
UICatalog/Scenarios/DimAutoDemo.cs

@@ -1,3 +1,4 @@
+using System;
 using Terminal.Gui;
 using static Terminal.Gui.Dim;
 
@@ -5,168 +6,182 @@ namespace UICatalog.Scenarios;
 
 [ScenarioMetadata ("DimAuto", "Demonstrates Dim.Auto")]
 [ScenarioCategory ("Layout")]
-public class DimAutoDemo : Scenario {
-	public override void Init ()
-	{
-		Application.Init ();
-		ConfigurationManager.Themes.Theme = Theme;
-		ConfigurationManager.Apply ();
-		Application.Top.ColorScheme = Colors.ColorSchemes [TopLevelColorScheme];
-	}
-
-	public override void Setup ()
-	{
-		var view = new FrameView () {
-			Title = "Type to make View grow",
-			X = 1,
-			Y = 1,
-			Width = Dim.Auto (style: DimAutoStyle.Subviews, min: 40),
-			Height = Dim.Auto (style: DimAutoStyle.Subviews, min: 10)
-		};
-		view.ValidatePosDim = true;
-
-		var textEdit = new TextView { Text = "", X = 1, Y = 0, Width = 20, Height = 4 };
-		view.Add (textEdit);
-
-		var hlabel = new Label {
-			Text = textEdit.Text,
-			X = Pos.Left (textEdit) + 1,
-			Y = Pos.Bottom (textEdit),
-			AutoSize = false,
-			Width = Dim.Auto (style: DimAutoStyle.Text, min: 20),
-			Height = 1,
-			ColorScheme = Colors.ColorSchemes ["Error"]
-		};
-		view.Add (hlabel);
-
-		var vlabel = new Label {
-			Text = textEdit.Text,
-			X = Pos.Left (textEdit),
-			Y = Pos.Bottom (textEdit) + 1,
-			AutoSize = false,
-			Width = 1,
-			Height = Dim.Auto (style: DimAutoStyle.Text, min: 8),
-			ColorScheme = Colors.ColorSchemes ["Error"],
-			//TextDirection = TextDirection.TopBottom_LeftRight
-		};
-		vlabel.Id = "vlabel";
-		view.Add (vlabel);
-
-		var heightAuto = new View () {
-			X = Pos.Right (vlabel) + 1,
-			Y = Pos.Bottom (hlabel) + 1,
-			Width = 20,
-			Height = Dim.Auto (),
-			ColorScheme = Colors.ColorSchemes ["Error"],
-			Title = "W: 20, H: Auto",
-			BorderStyle = LineStyle.Rounded
-		};
-		heightAuto.Id = "heightAuto";
-		view.Add (heightAuto);
-
-		var widthAuto = new View () {
-			X = Pos.Right (heightAuto) + 1,
-			Y = Pos.Bottom (hlabel) + 1,
-			Width = Dim.Auto (),
-			Height = 5,
-			ColorScheme = Colors.ColorSchemes ["Error"],
-			Title = "W: Auto, H: 5",
-			BorderStyle = LineStyle.Rounded
-		};
-		widthAuto.Id = "widthAuto";
-		view.Add (widthAuto);
-
-		var bothAuto = new View () {
-			X = Pos.Right (widthAuto) + 1,
-			Y = Pos.Bottom (hlabel) + 1,
-			Width = Dim.Auto (),
-			Height = Dim.Auto (),
-			ColorScheme = Colors.ColorSchemes ["Error"],
-			Title = "W: Auto, H: Auto",
-			BorderStyle = LineStyle.Rounded
-		};
-		bothAuto.Id = "bothAuto";
-		view.Add (bothAuto);
-
-		textEdit.ContentsChanged += (s, e) => {
-			hlabel.Text = textEdit.Text;
-			vlabel.Text = textEdit.Text;
-			heightAuto.Text = textEdit.Text;
-			widthAuto.Text = textEdit.Text;
-			bothAuto.Text = textEdit.Text;
-		};
-
-		var movingButton = new Button () {
-			Text = "_Move down",
-			X = Pos.Right (vlabel),
-			Y = Pos.Bottom (vlabel),
-			Width = 10
-		};
-		movingButton.Clicked += (s, e) => {
-			movingButton.Y = movingButton.Frame.Y + 1;
-		};
-		view.Add (movingButton);
-
-		var resetButton = new Button () {
-			Text = "_Reset Button",
-			X = Pos.Right(movingButton),
-			Y = Pos.Top (movingButton),
-		};
-
-		resetButton.Clicked += (s, e) => {
-			movingButton.Y = Pos.Bottom (hlabel);
-		};
-		view.Add (resetButton);
-
-		var dlgButton = new Button () {
-			Text = "Open Test _Dialog",
-			X = Pos.Right (view),
-			Y = Pos.Top (view)
-		};
-		dlgButton.Clicked += DlgButton_Clicked;
-
-		Application.Top.Add (view, dlgButton);
-	}
-
-	private void DlgButton_Clicked (object sender, System.EventArgs e)
-	{
-		var dlg = new Dialog () {
-			Title = "Test Dialog"
-		};
-
-		//var ok = new Button ("Bye") { IsDefault = true };
-		//ok.Clicked += (s, _) => Application.RequestStop (dlg);
-		//dlg.AddButton (ok);
-
-		//var cancel = new Button ("Abort") { };
-		//cancel.Clicked += (s, _) => Application.RequestStop (dlg);
-		//dlg.AddButton (cancel);
-
-		var label = new Label ("This is a label (AutoSize = false; Dim.Auto(3/20). Press Esc to close. Even more text.") {
-			AutoSize = false,
-			X = Pos.Center (),
-			Y = 0,
-			Height = Dim.Auto (min: 3),
-			Width = Dim.Auto (min: 20),
-			ColorScheme = Colors.ColorSchemes ["Menu"]
-		};
-
-		var text = new TextField () {
-			Text = "TextField... X = 1; Y = Pos.Bottom (label), Width = Dim.Fill (1); Height = Dim.Fill(1)",
-			TextFormatter = new TextFormatter () { WordWrap = true },
-			X = 20,
-			Y = Pos.Bottom (label),
-			Width = Dim.Fill (20),
-			Height = Dim.Fill (10)
-		};
-		var btn = new Button ("AnchorEnd") {
-			Y = Pos.AnchorEnd (1)
-		};
-		// TODO: We should really fix AnchorEnd to do this automatically. 
-		btn.X = Pos.AnchorEnd () - (Pos.Right (btn) - Pos.Left (btn));
-		dlg.Add (label);
-		dlg.Add (text);
-		dlg.Add (btn);
-		Application.Run (dlg);
-	}
-}
+public class DimAutoDemo : Scenario
+{
+    public override void Init ()
+    {
+        Application.Init ();
+        ConfigurationManager.Themes.Theme = Theme;
+        ConfigurationManager.Apply ();
+        Application.Top.ColorScheme = Colors.ColorSchemes [TopLevelColorScheme];
+    }
+
+    public override void Setup ()
+    {
+        var view = new FrameView
+        {
+            Title = "Type to make View grow",
+            X = 1,
+            Y = 1,
+            Width = Auto (DimAutoStyle.Subviews, 40),
+            Height = Auto (DimAutoStyle.Subviews, 10)
+        };
+        view.ValidatePosDim = true;
+
+        var textEdit = new TextView { Text = "", X = 1, Y = 0, Width = 20, Height = 4 };
+        view.Add (textEdit);
+
+        var hlabel = new Label
+        {
+            Text = textEdit.Text,
+            X = Pos.Left (textEdit) + 1,
+            Y = Pos.Bottom (textEdit),
+            AutoSize = false,
+            Width = Auto (DimAutoStyle.Text, 20),
+            Height = 1,
+            ColorScheme = Colors.ColorSchemes ["Error"]
+        };
+        view.Add (hlabel);
+
+        var vlabel = new Label
+        {
+            Text = textEdit.Text,
+            X = Pos.Left (textEdit),
+            Y = Pos.Bottom (textEdit) + 1,
+            AutoSize = false,
+            Width = 1,
+            Height = Auto (DimAutoStyle.Text, 8),
+            ColorScheme = Colors.ColorSchemes ["Error"]
+
+            //TextDirection = TextDirection.TopBottom_LeftRight
+        };
+        vlabel.Id = "vlabel";
+        view.Add (vlabel);
+
+        var heightAuto = new View
+        {
+            X = Pos.Right (vlabel) + 1,
+            Y = Pos.Bottom (hlabel) + 1,
+            Width = 20,
+            Height = Auto (),
+            ColorScheme = Colors.ColorSchemes ["Error"],
+            Title = "W: 20, H: Auto",
+            BorderStyle = LineStyle.Rounded
+        };
+        heightAuto.Id = "heightAuto";
+        view.Add (heightAuto);
+
+        var widthAuto = new View
+        {
+            X = Pos.Right (heightAuto) + 1,
+            Y = Pos.Bottom (hlabel) + 1,
+            Width = Auto (),
+            Height = 5,
+            ColorScheme = Colors.ColorSchemes ["Error"],
+            Title = "W: Auto, H: 5",
+            BorderStyle = LineStyle.Rounded
+        };
+        widthAuto.Id = "widthAuto";
+        view.Add (widthAuto);
+
+        var bothAuto = new View
+        {
+            X = Pos.Right (widthAuto) + 1,
+            Y = Pos.Bottom (hlabel) + 1,
+            Width = Auto (),
+            Height = Auto (),
+            ColorScheme = Colors.ColorSchemes ["Error"],
+            Title = "W: Auto, H: Auto",
+            BorderStyle = LineStyle.Rounded
+        };
+        bothAuto.Id = "bothAuto";
+        view.Add (bothAuto);
+
+        textEdit.ContentsChanged += (s, e) =>
+                                    {
+                                        hlabel.Text = textEdit.Text;
+                                        vlabel.Text = textEdit.Text;
+                                        heightAuto.Text = textEdit.Text;
+                                        widthAuto.Text = textEdit.Text;
+                                        bothAuto.Text = textEdit.Text;
+                                    };
+
+        var movingButton = new Button
+        {
+            Text = "_Move down",
+            X = Pos.Right (vlabel),
+            Y = Pos.Bottom (vlabel),
+            Width = 10
+        };
+        movingButton.Clicked += (s, e) => { movingButton.Y = movingButton.Frame.Y + 1; };
+        view.Add (movingButton);
+
+        var resetButton = new Button
+        {
+            Text = "_Reset Button",
+            X = Pos.Right (movingButton),
+            Y = Pos.Top (movingButton)
+        };
+
+        resetButton.Clicked += (s, e) => { movingButton.Y = Pos.Bottom (hlabel); };
+        view.Add (resetButton);
+
+        var dlgButton = new Button
+        {
+            Text = "Open Test _Dialog",
+            X = Pos.Right (view),
+            Y = Pos.Top (view)
+        };
+        dlgButton.Clicked += DlgButton_Clicked;
+
+        Application.Top.Add (view, dlgButton);
+    }
+
+    private void DlgButton_Clicked (object sender, EventArgs e)
+    {
+        var dlg = new Dialog
+        {
+            Title = "Test Dialog"
+        };
+
+        //var ok = new Button ("Bye") { IsDefault = true };
+        //ok.Clicked += (s, _) => Application.RequestStop (dlg);
+        //dlg.AddButton (ok);
+
+        //var cancel = new Button ("Abort") { };
+        //cancel.Clicked += (s, _) => Application.RequestStop (dlg);
+        //dlg.AddButton (cancel);
+
+        var label = new Label ("This is a label (AutoSize = false; Dim.Auto(3/20). Press Esc to close. Even more text.")
+        {
+            AutoSize = false,
+            X = Pos.Center (),
+            Y = 0,
+            Height = Auto (min: 3),
+            Width = Auto (min: 20),
+            ColorScheme = Colors.ColorSchemes ["Menu"]
+        };
+
+        var text = new TextField
+        {
+            Text = "TextField... X = 1; Y = Pos.Bottom (label), Width = Dim.Fill (1); Height = Dim.Fill(1)",
+            TextFormatter = new TextFormatter { WordWrap = true },
+            X = 20,
+            Y = Pos.Bottom (label),
+            Width = Fill (20),
+            Height = Fill (10)
+        };
+
+        var btn = new Button ("AnchorEnd")
+        {
+            Y = Pos.AnchorEnd (1)
+        };
+
+        // TODO: We should really fix AnchorEnd to do this automatically. 
+        btn.X = Pos.AnchorEnd () - (Pos.Right (btn) - Pos.Left (btn));
+        dlg.Add (label);
+        dlg.Add (text);
+        dlg.Add (btn);
+        Application.Run (dlg);
+    }
+}

+ 1015 - 844
UICatalog/UICatalog.cs

@@ -11,860 +11,1031 @@ using System.Reflection;
 using System.Runtime.InteropServices;
 using System.Text;
 using System.Text.Json.Serialization;
-using System.Threading.Tasks;
 using Terminal.Gui;
 using static Terminal.Gui.ConfigurationManager;
+using Command = Terminal.Gui.Command;
+using RuntimeEnvironment = Microsoft.DotNet.PlatformAbstractions.RuntimeEnvironment;
 
 #nullable enable
 
 namespace UICatalog;
 
 /// <summary>
-/// UI Catalog is a comprehensive sample library for Terminal.Gui. It provides a simple UI for adding to the catalog of scenarios.
+///     UI Catalog is a comprehensive sample library for Terminal.Gui. It provides a simple UI for adding to the catalog of
+///     scenarios.
 /// </summary>
 /// <remarks>
-/// <para>
-///	UI Catalog attempts to satisfy the following goals:
-/// </para>
-/// <para>
-/// <list type="number">
-///	<item>
-///		<description>
-///		Be an easy to use showcase for Terminal.Gui concepts and features.
-///		</description>
-///	</item>
-///	<item>
-///		<description>
-///		Provide sample code that illustrates how to properly implement said concepts & features.
-///		</description>
-///	</item>
-///	<item>
-///		<description>
-///		Make it easy for contributors to add additional samples in a structured way.
-///		</description>
-///	</item>
-/// </list>
-/// </para>	
-/// <para>
-///	See the project README for more details (https://github.com/gui-cs/Terminal.Gui/tree/master/UICatalog/README.md).
-/// </para>	
+///     <para>
+///         UI Catalog attempts to satisfy the following goals:
+///     </para>
+///     <para>
+///         <list type="number">
+///             <item>
+///                 <description>
+///                     Be an easy to use showcase for Terminal.Gui concepts and features.
+///                 </description>
+///             </item>
+///             <item>
+///                 <description>
+///                     Provide sample code that illustrates how to properly implement said concepts & features.
+///                 </description>
+///             </item>
+///             <item>
+///                 <description>
+///                     Make it easy for contributors to add additional samples in a structured way.
+///                 </description>
+///             </item>
+///         </list>
+///     </para>
+///     <para>
+///         See the project README for more details
+///         (https://github.com/gui-cs/Terminal.Gui/tree/master/UICatalog/README.md).
+///     </para>
 /// </remarks>
-class UICatalogApp {
-	[SerializableConfigurationProperty (Scope = typeof (AppScope), OmitClassName = true)]
-	[JsonPropertyName ("UICatalog.StatusBar")]
-	public static bool ShowStatusBar { get; set; } = true;
-
-	static readonly FileSystemWatcher _currentDirWatcher = new FileSystemWatcher ();
-	static readonly FileSystemWatcher _homeDirWatcher = new FileSystemWatcher ();
-
-	struct Options {
-		public string Driver;
-		public string Scenario;
-		/* etc. */
-	}
-
-	static Options _options;
-
-	static int Main (string [] args)
-	{
-		Console.OutputEncoding = Encoding.Default;
-
-		if (Debugger.IsAttached) {
-			CultureInfo.DefaultThreadCurrentUICulture = CultureInfo.GetCultureInfo ("en-US");
-		}
-
-		_scenarios = Scenario.GetScenarios ();
-		_categories = Scenario.GetAllCategories ();
-
-		// Process command line args
-		// "UICatalog [-driver <driver>] [scenario name]"
-		// If no driver is provided, the default driver is used.
-		var driverOption = new Option<string> (
-			"--driver",
-			"The ConsoleDriver to use."
-		).FromAmong (Application.GetDriverTypes ().Select (d => d.Name).ToArray ());
-		driverOption.AddAlias ("-d");
-		driverOption.AddAlias ("--d");
-
-		var scenarioArgument = new Argument<string> (
-			"scenario",
-			description: "The name of the scenario to run.",
-			getDefaultValue: () => "none"
-		).FromAmong (_scenarios.Select (s => s.GetName ()).Append ("none").ToArray ());
-
-		var rootCommand = new RootCommand (description: "A comprehensive sample library for Terminal.Gui") {
-			scenarioArgument,
-			driverOption
-		};
-
-		rootCommand.SetHandler (context => {
-			Options options = new Options () {
-				Driver = context.ParseResult.GetValueForOption (driverOption) ?? string.Empty,
-				Scenario = context.ParseResult.GetValueForArgument (scenarioArgument),
-				/* etc. */
-			};
-			// See https://github.com/dotnet/command-line-api/issues/796 for the rationale behind this hackery
-			_options = options; ;
-		});
-
-		rootCommand.Invoke (args);
-
-		UICatalogMain (_options);
-		return 0;
-	}
-
-	static void UICatalogMain (Options options)
-	{
-		StartConfigFileWatcher ();
-
-		// By setting _forceDriver we ensure that if the user has specified a driver on the command line, it will be used
-		// regardless of what's in a config file.
-		Application.ForceDriver = _forceDriver = options.Driver;
-
-		// If a Scenario name has been provided on the commandline
-		// run it and exit when done.
-		if (options.Scenario != "none") {
-			_topLevelColorScheme = "Base";
-
-			int item = _scenarios!.FindIndex (s => s.GetName ().Equals (options.Scenario, StringComparison.OrdinalIgnoreCase));
-			_selectedScenario = (Scenario)Activator.CreateInstance (_scenarios [item].GetType ())!;
-
-			Application.Init (driverName: _forceDriver);
-			_selectedScenario.Theme = _cachedTheme;
-			_selectedScenario.TopLevelColorScheme = _topLevelColorScheme;
-			_selectedScenario.Init ();
-			_selectedScenario.Setup ();
-			_selectedScenario.Run ();
-			_selectedScenario.Dispose ();
-			_selectedScenario = null;
-			Application.Shutdown ();
-			VerifyObjectsWereDisposed ();
-			return;
-		}
-
-		_aboutMessage = new StringBuilder ();
-		_aboutMessage.AppendLine (@"A comprehensive sample library for");
-		_aboutMessage.AppendLine (@"");
-		_aboutMessage.AppendLine (@"  _______                  _             _   _____       _  ");
-		_aboutMessage.AppendLine (@" |__   __|                (_)           | | / ____|     (_) ");
-		_aboutMessage.AppendLine (@"    | | ___ _ __ _ __ ___  _ _ __   __ _| || |  __ _   _ _  ");
-		_aboutMessage.AppendLine (@"    | |/ _ \ '__| '_ ` _ \| | '_ \ / _` | || | |_ | | | | | ");
-		_aboutMessage.AppendLine (@"    | |  __/ |  | | | | | | | | | | (_| | || |__| | |_| | | ");
-		_aboutMessage.AppendLine (@"    |_|\___|_|  |_| |_| |_|_|_| |_|\__,_|_(_)_____|\__,_|_| ");
-		_aboutMessage.AppendLine (@"");
-		_aboutMessage.AppendLine (@"v2 - Work in Progress");
-		_aboutMessage.AppendLine (@"");
-		_aboutMessage.AppendLine (@"https://github.com/gui-cs/Terminal.Gui");
-
-		while (RunUICatalogTopLevel () is { } scenario) {
-			VerifyObjectsWereDisposed ();
-			Themes!.Theme = _cachedTheme!;
-			Apply ();
-			scenario.Theme = _cachedTheme;
-			scenario.TopLevelColorScheme = _topLevelColorScheme;
-			scenario.Init ();
-			scenario.Setup ();
-			scenario.Run ();
-			scenario.Dispose ();
-
-			// This call to Application.Shutdown brackets the Application.Init call
-			// made by Scenario.Init() above
-			Application.Shutdown ();
-
-			VerifyObjectsWereDisposed ();
-		}
-
-		StopConfigFileWatcher ();
-		VerifyObjectsWereDisposed ();
-	}
-
-	static void StopConfigFileWatcher ()
-	{
-		_currentDirWatcher.EnableRaisingEvents = false;
-		_currentDirWatcher.Changed -= ConfigFileChanged;
-		_currentDirWatcher.Created -= ConfigFileChanged;
-
-		_homeDirWatcher.EnableRaisingEvents = false;
-		_homeDirWatcher.Changed -= ConfigFileChanged;
-		_homeDirWatcher.Created -= ConfigFileChanged;
-	}
-
-	static void StartConfigFileWatcher ()
-	{
-		// Setup a file system watcher for `./.tui/`
-		_currentDirWatcher.NotifyFilter = NotifyFilters.LastWrite;
-
-		string assemblyLocation = Assembly.GetExecutingAssembly ().Location;
-		string tuiDir;
-
-		if (!string.IsNullOrEmpty (assemblyLocation)) {
-			var assemblyFile = new FileInfo (assemblyLocation);
-			tuiDir = Path.Combine (assemblyFile.Directory!.FullName, ".tui");
-		} else {
-			tuiDir = Path.Combine (AppContext.BaseDirectory, ".tui");
-		}
-
-
-
-		if (!Directory.Exists (tuiDir)) {
-			Directory.CreateDirectory (tuiDir);
-		}
-		_currentDirWatcher.Path = tuiDir;
-		_currentDirWatcher.Filter = "*config.json";
-
-		// Setup a file system watcher for `~/.tui/`
-		_homeDirWatcher.NotifyFilter = NotifyFilters.LastWrite;
-		var f = new FileInfo (Environment.GetFolderPath (Environment.SpecialFolder.UserProfile));
-		tuiDir = Path.Combine (f.FullName, ".tui");
-
-		if (!Directory.Exists (tuiDir)) {
-			Directory.CreateDirectory (tuiDir);
-		}
-		_homeDirWatcher.Path = tuiDir;
-		_homeDirWatcher.Filter = "*config.json";
-
-		_currentDirWatcher.Changed += ConfigFileChanged;
-		//_currentDirWatcher.Created += ConfigFileChanged;
-		_currentDirWatcher.EnableRaisingEvents = true;
-
-		_homeDirWatcher.Changed += ConfigFileChanged;
-		//_homeDirWatcher.Created += ConfigFileChanged;
-		_homeDirWatcher.EnableRaisingEvents = true;
-	}
-
-	static void ConfigFileChanged (object sender, FileSystemEventArgs e)
-	{
-		if (Application.Top == null) {
-			return;
-		}
-
-		// TODO: This is a hack. Figure out how to ensure that the file is fully written before reading it.
-		//Thread.Sleep (500);
-		Load ();
-		Apply ();
-	}
-
-	/// <summary>
-	/// Shows the UI Catalog selection UI. When the user selects a Scenario to run, the
-	/// UI Catalog main app UI is killed and the Scenario is run as though it were Application.Top. 
-	/// When the Scenario exits, this function exits.
-	/// </summary>
-	/// <returns></returns>
-	static Scenario RunUICatalogTopLevel ()
-	{
-
-		// Run UI Catalog UI. When it exits, if _selectedScenario is != null then
-		// a Scenario was selected. Otherwise, the user wants to quit UI Catalog.
-
-		// If the user specified a driver on the command line then use it,
-		// ignoring Config files.
-
-		Application.Init (driverName: _forceDriver);
-
-		if (_cachedTheme is null) {
-			_cachedTheme = Themes?.Theme;
-		} else {
-			Themes!.Theme = _cachedTheme;
-			Apply ();
-		}
-
-		Application.Run<UICatalogTopLevel> ();
-		Application.Shutdown ();
-
-		return _selectedScenario!;
-	}
-
-	static List<Scenario>? _scenarios;
-	static List<string>? _categories;
-
-	// When a scenario is run, the main app is killed. These items
-	// are therefore cached so that when the scenario exits the
-	// main app UI can be restored to previous state
-	static int _cachedScenarioIndex = 0;
-	static int _cachedCategoryIndex = 0;
-	static string? _cachedTheme = string.Empty;
-
-	static StringBuilder? _aboutMessage = null;
-
-	// If set, holds the scenario the user selected
-	static Scenario? _selectedScenario = null;
-
-	static string _forceDriver = string.Empty;
-	static ConsoleDriver.DiagnosticFlags _diagnosticFlags;
-	static bool _isFirstRunning = true;
-	static string _topLevelColorScheme = string.Empty;
-
-	static MenuItem []? _themeMenuItems;
-	static MenuBarItem? _themeMenuBarItem;
-
-	/// <summary>
-	/// This is the main UI Catalog app view. It is run fresh when the app loads (if a Scenario has not been passed on 
-	/// the command line) and each time a Scenario ends.
-	/// </summary>
-	public class UICatalogTopLevel : Toplevel {
-		public MenuItem? miUseSubMenusSingleFrame;
-		public MenuItem? miIsMenuBorderDisabled;
-		public MenuItem? miForce16Colors;
-		public MenuItem? miIsMouseDisabled;
-
-		public ListView CategoryList;
-
-		// UI Catalog uses TableView for the scenario list instead of a ListView to demonstate how
-		// TableView works. There's no real reason not to use ListView. Because we use TableView, and TableView
-		// doesn't (currently) have CollectionNavigator support built in, we implement it here, within the app.
-		public TableView ScenarioList;
-		CollectionNavigator _scenarioCollectionNav = new CollectionNavigator ();
-
-		public StatusItem DriverName;
-		public StatusItem OS;
-
-		public UICatalogTopLevel ()
-		{
-			_themeMenuItems = CreateThemeMenuItems ();
-			_themeMenuBarItem = new MenuBarItem ("_Themes", _themeMenuItems);
-			MenuBar = new MenuBar (new MenuBarItem [] {
-				new MenuBarItem ("_File", new MenuItem [] {
-					new MenuItem ("_Quit", "Quit UI Catalog", RequestStop, null, null)
-				}),
-				_themeMenuBarItem,
-				new MenuBarItem ("Diag_nostics", CreateDiagnosticMenuItems ()),
-				new MenuBarItem ("_Help", new MenuItem [] {
-					new MenuItem ("_Documentation", "", () => OpenUrl ("https://gui-cs.github.io/Terminal.GuiV2Docs"), null, null, (KeyCode)Key.F1),
-					new MenuItem ("_README", "", () => OpenUrl ("https://github.com/gui-cs/Terminal.Gui"), null, null, (KeyCode)Key.F2),
-					new MenuItem ("_About...", "About UI Catalog", () => MessageBox.Query ("About UI Catalog", _aboutMessage!.ToString (), 0, false, "_Ok"), null, null, (KeyCode)Key.A.WithCtrl)
-				})
-			});
-
-			DriverName = new StatusItem (Key.Empty, "Driver:", null);
-			OS = new StatusItem (Key.Empty, "OS:", null);
-
-			StatusBar = new StatusBar () {
-				Visible = ShowStatusBar
-			};
-
-			StatusBar.Items = new StatusItem [] {
-				new StatusItem (Application.QuitKey, $"~{Application.QuitKey} to quit", () => {
-					if (_selectedScenario is null) {
-						// This causes GetScenarioToRun to return null
-						_selectedScenario = null;
-						RequestStop ();
-					} else {
-						_selectedScenario.RequestStop ();
-					}
-				}),
-				new StatusItem (Key.F10, "~F10~ Status Bar", () => {
-					StatusBar.Visible = !StatusBar.Visible;
-					//ContentPane!.Height = Dim.Fill(StatusBar.Visible ? 1 : 0);
-					LayoutSubviews ();
-					SetSubViewNeedsDisplay ();
-				}),
-				DriverName,
-				OS
-			};
-
-			// Create the Category list view. This list never changes.
-			CategoryList = new ListView (_categories) {
-				X = 0,
-				Y = 1,
-				Width = Dim.Percent (30),
-				Height = Dim.Fill (1),
-				AllowsMarking = false,
-				CanFocus = true,
-				Title = "Categories",
-				BorderStyle = LineStyle.Single,
-				SuperViewRendersLineCanvas = true
-			};
-			CategoryList.OpenSelectedItem += (s, a) => {
-				ScenarioList!.SetFocus ();
-			};
-			CategoryList.SelectedItemChanged += CategoryView_SelectedChanged;
-
-			// Create the scenario list. The contents of the scenario list changes whenever the
-			// Category list selection changes (to show just the scenarios that belong to the selected
-			// category).
-			ScenarioList = new TableView () {
-				X = Pos.Right (CategoryList) - 1,
-				Y = 1,
-				Width = Dim.Fill (0),
-				Height = Dim.Fill (1),
-				//AllowsMarking = false,
-				CanFocus = true,
-				Title = "Scenarios",
-				BorderStyle = LineStyle.Single,
-				SuperViewRendersLineCanvas = true
-			};
-
-			// TableView provides many options for table headers. For simplicity we turn all 
-			// of these off. By enabling FullRowSelect and turning off headers, TableView looks just
-			// like a ListView
-			ScenarioList.FullRowSelect = true;
-			ScenarioList.Style.ShowHeaders = false;
-			ScenarioList.Style.ShowHorizontalHeaderOverline = false;
-			ScenarioList.Style.ShowHorizontalHeaderUnderline = false;
-			ScenarioList.Style.ShowHorizontalBottomline = false;
-			ScenarioList.Style.ShowVerticalCellLines = false;
-			ScenarioList.Style.ShowVerticalHeaderLines = false;
-
-			/* By default TableView lays out columns at render time and only
-			* measures y rows of data at a time.  Where y is the height of the
-			* console. This is for the following reasons:
-			* 
-			* - Performance, when tables have a large amount of data
-			* - Defensive, prevents a single wide cell value pushing other
-			*   columns off screen (requiring horizontal scrolling
-			* 
-			* In the case of UICatalog here, such an approach is overkill so
-			* we just measure all the data ourselves and set the appropriate
-			* max widths as ColumnStyles 
-			*/
-			int longestName = _scenarios!.Max (s => s.GetName ().Length);
-			ScenarioList.Style.ColumnStyles.Add (0, new ColumnStyle () { MaxWidth = longestName, MinWidth = longestName, MinAcceptableWidth = longestName });
-			ScenarioList.Style.ColumnStyles.Add (1, new ColumnStyle () { MaxWidth = 1 });
-
-			// Enable user to find & select a scenario by typing text
-			// TableView does not (currently) have built-in CollectionNavigator support (the ability for the 
-			// user to type and the items that match get selected). We implement it in the app instead. 
-			ScenarioList.KeyDown += (s, a) => {
-				if (CollectionNavigatorBase.IsCompatibleKey (a)) {
-					int? newItem = _scenarioCollectionNav?.GetNextMatchingItem (ScenarioList.SelectedRow, (char)a);
-					if (newItem is int v && newItem != -1) {
-						ScenarioList.SelectedRow = v;
-						ScenarioList.EnsureSelectedCellIsVisible ();
-						ScenarioList.SetNeedsDisplay ();
-						a.Handled = true;
-					}
-				}
-			};
-			ScenarioList.CellActivated += ScenarioView_OpenSelectedItem;
-
-			// TableView typically is a grid where nav keys are biased for moving left/right.
-			ScenarioList.KeyBindings.Add (Key.Home, Terminal.Gui.Command.TopHome);
-			ScenarioList.KeyBindings.Add (Key.End, Terminal.Gui.Command.BottomEnd);
-
-			// Ideally, TableView.MultiSelect = false would turn off any keybindings for
-			// multi-select options. But it currently does not. UI Catalog uses Ctrl-A for
-			// a shortcut to About.
-			ScenarioList.MultiSelect = false;
-			ScenarioList.KeyBindings.Remove (Key.A.WithCtrl);
-
-			Add (CategoryList);
-			Add (ScenarioList);
-
-			Add (MenuBar);
-			Add (StatusBar);
-
-			Loaded += LoadedHandler;
-			Unloaded += UnloadedHandler;
-
-			// Restore previous selections
-			CategoryList.SelectedItem = _cachedCategoryIndex;
-			ScenarioList.SelectedRow = _cachedScenarioIndex;
-
-			Applied += ConfigAppliedHandler;
-		}
-
-		void LoadedHandler (object? sender, EventArgs? args)
-		{
-			ConfigChanged ();
-
-			miIsMouseDisabled!.Checked = Application.IsMouseDisabled;
-			DriverName.Title = $"Driver: {Driver.GetVersionInfo ()}";
-			OS.Title = $"OS: {Microsoft.DotNet.PlatformAbstractions.RuntimeEnvironment.OperatingSystem} {Microsoft.DotNet.PlatformAbstractions.RuntimeEnvironment.OperatingSystemVersion}";
-
-			if (_selectedScenario != null) {
-				_selectedScenario = null;
-				_isFirstRunning = false;
-			}
-			if (!_isFirstRunning) {
-				ScenarioList.SetFocus ();
-			}
-
-			StatusBar.VisibleChanged += (s, e) => {
-				ShowStatusBar = StatusBar.Visible;
-
-				int height = StatusBar.Visible ? 1 : 0;
-				CategoryList.Height = Dim.Fill (height);
-				ScenarioList.Height = Dim.Fill (height);
-				// ContentPane.Height = Dim.Fill (height);
-				LayoutSubviews ();
-				SetSubViewNeedsDisplay ();
-			};
-
-			Loaded -= LoadedHandler;
-			CategoryList.EnsureSelectedItemVisible ();
-			ScenarioList.EnsureSelectedCellIsVisible ();
-		}
-
-		void UnloadedHandler (object? sender, EventArgs? args)
-		{
-			Applied -= ConfigAppliedHandler;
-			Unloaded -= UnloadedHandler;
-		}
-
-		void ConfigAppliedHandler (object? sender, ConfigurationManagerEventArgs? a) => ConfigChanged ();
-
-		/// <summary>
-		/// Launches the selected scenario, setting the global _selectedScenario
-		/// </summary>
-		/// <param name="e"></param>
-		void ScenarioView_OpenSelectedItem (object? sender, EventArgs? e)
-		{
-			if (_selectedScenario is null) {
-				// Save selected item state
-				_cachedCategoryIndex = CategoryList.SelectedItem;
-				_cachedScenarioIndex = ScenarioList.SelectedRow;
-
-				// Create new instance of scenario (even though Scenarios contains instances)
-				string selectedScenarioName = (string)ScenarioList.Table [ScenarioList.SelectedRow, 0];
-				_selectedScenario = (Scenario)Activator.CreateInstance (_scenarios!.FirstOrDefault (s => s.GetName () == selectedScenarioName)!.GetType ())!;
-
-				// Tell the main app to stop
-				Application.RequestStop ();
-			}
-		}
-
-		List<MenuItem []> CreateDiagnosticMenuItems ()
-		{
-			var menuItems = new List<MenuItem []> {
-				CreateDiagnosticFlagsMenuItems (),
-				new MenuItem [] { null! },
-				CreateDisabledEnabledMouseItems (),
-				CreateDisabledEnabledMenuBorder (),
-				CreateDisabledEnableUseSubMenusSingleFrame (),
-				CreateKeyBindingsMenuItems ()
-			};
-			return menuItems;
-		}
-
-		// TODO: This should be an ConfigurationManager setting
-		MenuItem [] CreateDisabledEnableUseSubMenusSingleFrame ()
-		{
-			var menuItems = new List<MenuItem> ();
-			miUseSubMenusSingleFrame = new MenuItem {
-				Title = "Enable _Sub-Menus Single Frame"
-			};
-			miUseSubMenusSingleFrame.Shortcut = KeyCode.CtrlMask | KeyCode.AltMask | (KeyCode)miUseSubMenusSingleFrame!.Title!.Substring (8, 1) [0];
-			miUseSubMenusSingleFrame.CheckType |= MenuItemCheckStyle.Checked;
-			miUseSubMenusSingleFrame.Action += () => {
-				miUseSubMenusSingleFrame.Checked = (bool)!miUseSubMenusSingleFrame.Checked!;
-				MenuBar.UseSubMenusSingleFrame = (bool)miUseSubMenusSingleFrame.Checked;
-			};
-			menuItems.Add (miUseSubMenusSingleFrame);
-
-			return menuItems.ToArray ();
-		}
-
-		// TODO: This should be an ConfigurationManager setting
-		MenuItem [] CreateDisabledEnabledMenuBorder ()
-		{
-			var menuItems = new List<MenuItem> ();
-			miIsMenuBorderDisabled = new MenuItem {
-				Title = "Disable Menu _Border"
-			};
-			miIsMenuBorderDisabled.Shortcut = (KeyCode)new Key (miIsMenuBorderDisabled!.Title!.Substring (14, 1) [0]).WithAlt.WithCtrl;
-			miIsMenuBorderDisabled.CheckType |= MenuItemCheckStyle.Checked;
-			miIsMenuBorderDisabled.Action += () => {
-				miIsMenuBorderDisabled.Checked = (bool)!miIsMenuBorderDisabled.Checked!;
-				MenuBar.MenusBorderStyle = !(bool)miIsMenuBorderDisabled.Checked ? LineStyle.Single : LineStyle.None;
-			};
-			menuItems.Add (miIsMenuBorderDisabled);
-
-			return menuItems.ToArray ();
-		}
-
-
-		MenuItem [] CreateForce16ColorItems ()
-		{
-			var menuItems = new List<MenuItem> ();
-			miForce16Colors = new MenuItem {
-				Title = "Force _16 Colors",
-				Shortcut = (KeyCode)Key.F6,
-				Checked = Application.Force16Colors,
-				CanExecute = () => (bool)Application.Driver.SupportsTrueColor
-			};
-			miForce16Colors.CheckType |= MenuItemCheckStyle.Checked;
-			miForce16Colors.Action += () => {
-				miForce16Colors.Checked = Application.Force16Colors = (bool)!miForce16Colors.Checked!;
-				Application.Refresh ();
-			};
-			menuItems.Add (miForce16Colors);
-
-			return menuItems.ToArray ();
-		}
-
-		MenuItem [] CreateDisabledEnabledMouseItems ()
-		{
-			var menuItems = new List<MenuItem> ();
-			miIsMouseDisabled = new MenuItem {
-				Title = "_Disable Mouse"
-			};
-			miIsMouseDisabled.Shortcut = (KeyCode)new Key (miIsMouseDisabled!.Title!.Substring (1, 1) [0]).WithAlt.WithCtrl;
-			miIsMouseDisabled.CheckType |= MenuItemCheckStyle.Checked;
-			miIsMouseDisabled.Action += () => {
-				miIsMouseDisabled.Checked = Application.IsMouseDisabled = (bool)!miIsMouseDisabled.Checked!;
-			};
-			menuItems.Add (miIsMouseDisabled);
-
-			return menuItems.ToArray ();
-		}
-
-		MenuItem [] CreateKeyBindingsMenuItems ()
-		{
-			var menuItems = new List<MenuItem> ();
-			var item = new MenuItem {
-				Title = "_Key Bindings",
-				Help = "Change which keys do what"
-			};
-			item.Action += () => {
-				var dlg = new KeyBindingsDialog ();
-				Application.Run (dlg);
-			};
-
-			menuItems.Add (null!);
-			menuItems.Add (item);
-
-			return menuItems.ToArray ();
-		}
-
-		MenuItem [] CreateDiagnosticFlagsMenuItems ()
-		{
-			const string OFF = "Diagnostics: _Off";
-			const string FRAME_RULER = "Diagnostics: Frame _Ruler";
-			const string FRAME_PADDING = "Diagnostics: _Frame Padding";
-			int index = 0;
-
-			var menuItems = new List<MenuItem> ();
-			foreach (Enum diag in Enum.GetValues (_diagnosticFlags.GetType ())) {
-				var item = new MenuItem {
-					Title = GetDiagnosticsTitle (diag),
-					Shortcut = (KeyCode)new Key (index.ToString () [0]).WithAlt
-				};
-				index++;
-				item.CheckType |= MenuItemCheckStyle.Checked;
-				if (GetDiagnosticsTitle (ConsoleDriver.DiagnosticFlags.Off) == item.Title) {
-					item.Checked = (_diagnosticFlags & (ConsoleDriver.DiagnosticFlags.FramePadding
-									| ConsoleDriver.DiagnosticFlags.FrameRuler)) == 0;
-				} else {
-					item.Checked = _diagnosticFlags.HasFlag (diag);
-				}
-				item.Action += () => {
-					string t = GetDiagnosticsTitle (ConsoleDriver.DiagnosticFlags.Off);
-					if (item.Title == t && item.Checked == false) {
-						_diagnosticFlags &= ~(ConsoleDriver.DiagnosticFlags.FramePadding | ConsoleDriver.DiagnosticFlags.FrameRuler);
-						item.Checked = true;
-					} else if (item.Title == t && item.Checked == true) {
-						_diagnosticFlags |= ConsoleDriver.DiagnosticFlags.FramePadding | ConsoleDriver.DiagnosticFlags.FrameRuler;
-						item.Checked = false;
-					} else {
-						var f = GetDiagnosticsEnumValue (item.Title);
-						if (_diagnosticFlags.HasFlag (f)) {
-							SetDiagnosticsFlag (f, false);
-						} else {
-							SetDiagnosticsFlag (f, true);
-						}
-					}
-					foreach (var menuItem in menuItems) {
-						if (menuItem.Title == t) {
-							menuItem.Checked = !_diagnosticFlags.HasFlag (ConsoleDriver.DiagnosticFlags.FrameRuler)
-									&& !_diagnosticFlags.HasFlag (ConsoleDriver.DiagnosticFlags.FramePadding);
-						} else if (menuItem.Title != t) {
-							menuItem.Checked = _diagnosticFlags.HasFlag (GetDiagnosticsEnumValue (menuItem.Title));
-						}
-					}
-					ConsoleDriver.Diagnostics = _diagnosticFlags;
-					Application.Top.SetNeedsDisplay ();
-				};
-				menuItems.Add (item);
-			}
-			return menuItems.ToArray ();
-
-			string GetDiagnosticsTitle (Enum diag) => Enum.GetName (_diagnosticFlags.GetType (), diag) switch {
-				"Off" => OFF,
-				"FrameRuler" => FRAME_RULER,
-				"FramePadding" => FRAME_PADDING,
-				_ => ""
-			};
-
-			Enum GetDiagnosticsEnumValue (string title) => title switch {
-				FRAME_RULER => ConsoleDriver.DiagnosticFlags.FrameRuler,
-				FRAME_PADDING => ConsoleDriver.DiagnosticFlags.FramePadding,
-				_ => null!
-			};
-
-			void SetDiagnosticsFlag (Enum diag, bool add)
-			{
-				switch (diag) {
-				case ConsoleDriver.DiagnosticFlags.FrameRuler:
-					if (add) {
-						_diagnosticFlags |= ConsoleDriver.DiagnosticFlags.FrameRuler;
-					} else {
-						_diagnosticFlags &= ~ConsoleDriver.DiagnosticFlags.FrameRuler;
-					}
-					break;
-				case ConsoleDriver.DiagnosticFlags.FramePadding:
-					if (add) {
-						_diagnosticFlags |= ConsoleDriver.DiagnosticFlags.FramePadding;
-					} else {
-						_diagnosticFlags &= ~ConsoleDriver.DiagnosticFlags.FramePadding;
-					}
-					break;
-				default:
-					_diagnosticFlags = default;
-					break;
-				}
-			}
-		}
-
-		public MenuItem []? CreateThemeMenuItems ()
-		{
-			var menuItems = CreateForce16ColorItems ().ToList ();
-			menuItems.Add (null!);
-
-			int schemeCount = 0;
-			foreach (var theme in Themes!) {
-				var item = new MenuItem {
-					Title = $"_{theme.Key}",
-					Shortcut = (KeyCode)new Key ((KeyCode)((uint)KeyCode.D1 + schemeCount++)).WithCtrl
-				};
-				item.CheckType |= MenuItemCheckStyle.Checked;
-				item.Checked = theme.Key == _cachedTheme; // CM.Themes.Theme;
-				item.Action += () => {
-					Themes.Theme = _cachedTheme = theme.Key;
-					Apply ();
-				};
-				menuItems.Add (item);
-			}
-
-			var schemeMenuItems = new List<MenuItem> ();
-			foreach (var sc in Colors.ColorSchemes) {
-				var item = new MenuItem {
-					Title = $"_{sc.Key}",
-					Data = sc.Key
-				};
-				item.CheckType |= MenuItemCheckStyle.Radio;
-				item.Checked = sc.Key == _topLevelColorScheme;
-				item.Action += () => {
-					_topLevelColorScheme = (string)item.Data;
-					foreach (var schemeMenuItem in schemeMenuItems) {
-						schemeMenuItem.Checked = (string)schemeMenuItem.Data == _topLevelColorScheme;
-					}
-					ColorScheme = Colors.ColorSchemes [_topLevelColorScheme];
-					Application.Top.SetNeedsDisplay ();
-				};
-				schemeMenuItems.Add (item);
-			}
-			menuItems.Add (null!);
-			var mbi = new MenuBarItem ("_Color Scheme for Application.Top", schemeMenuItems.ToArray ());
-			menuItems.Add (mbi);
-
-			return menuItems.ToArray ();
-		}
-
-		public void ConfigChanged ()
-		{
-			if (_topLevelColorScheme == null || !Colors.ColorSchemes.ContainsKey (_topLevelColorScheme)) {
-				_topLevelColorScheme = "Base";
-			}
-
-			_cachedTheme = Themes?.Theme;
-
-			_themeMenuItems = CreateThemeMenuItems ();
-			_themeMenuBarItem!.Children = _themeMenuItems;
-			foreach (var mi in _themeMenuItems!) {
-				if (mi is { Parent: null }) {
-					mi.Parent = _themeMenuBarItem;
-				}
-			}
-
-			ColorScheme = Colors.ColorSchemes [_topLevelColorScheme];
-
-			MenuBar.Menus [0].Children [0].Shortcut = (KeyCode)Application.QuitKey;
-			StatusBar.Items [0].Shortcut = Application.QuitKey;
-			StatusBar.Items [0].Title = $"~{Application.QuitKey} to quit";
-
-			miIsMouseDisabled!.Checked = Application.IsMouseDisabled;
-
-			int height = ShowStatusBar ? 1 : 0; // + (MenuBar.Visible ? 1 : 0);
-							    //ContentPane.Height = Dim.Fill (height);
-
-			StatusBar.Visible = ShowStatusBar;
-
-			Application.Top.SetNeedsDisplay ();
-		}
-
-		void CategoryView_SelectedChanged (object? sender, ListViewItemEventArgs? e)
-		{
-			string item = _categories! [e!.Item];
-			List<Scenario> newlist;
-			if (e.Item == 0) {
-				// First category is "All"
-				newlist = _scenarios!;
-				newlist = _scenarios!;
-
-			} else {
-				newlist = _scenarios!.Where (s => s.GetCategories ().Contains (item)).ToList ();
-			}
-			ScenarioList.Table = new EnumerableTableSource<Scenario> (newlist, new Dictionary<string, Func<Scenario, object>> () {
-				{ "Name", (s) => s.GetName () },
-				{ "Description", (s) => s.GetDescription () }
-			});
-
-			// Create a collection of just the scenario names (the 1st column in our TableView)
-			// for CollectionNavigator. 
-			var firstColumnList = new List<object> ();
-			for (int i = 0; i < ScenarioList.Table.Rows; i++) {
-				firstColumnList.Add (ScenarioList.Table [i, 0]);
-			}
-			_scenarioCollectionNav.Collection = firstColumnList;
-
-		}
-	}
-
-	static void VerifyObjectsWereDisposed ()
-	{
+internal class UICatalogApp
+{
+    private static StringBuilder? _aboutMessage;
+    private static int _cachedCategoryIndex;
+
+    // When a scenario is run, the main app is killed. These items
+    // are therefore cached so that when the scenario exits the
+    // main app UI can be restored to previous state
+    private static int _cachedScenarioIndex;
+    private static string? _cachedTheme = string.Empty;
+    private static List<string>? _categories;
+
+    private static readonly FileSystemWatcher _currentDirWatcher = new ();
+    private static ConsoleDriver.DiagnosticFlags _diagnosticFlags;
+
+    private static string _forceDriver = string.Empty;
+    private static readonly FileSystemWatcher _homeDirWatcher = new ();
+    private static bool _isFirstRunning = true;
+
+    private static Options _options;
+
+    private static List<Scenario>? _scenarios;
+
+    // If set, holds the scenario the user selected
+    private static Scenario? _selectedScenario;
+    private static MenuBarItem? _themeMenuBarItem;
+
+    private static MenuItem []? _themeMenuItems;
+    private static string _topLevelColorScheme = string.Empty;
+
+    [SerializableConfigurationProperty (Scope = typeof (AppScope), OmitClassName = true)]
+    [JsonPropertyName ("UICatalog.StatusBar")]
+    public static bool ShowStatusBar { get; set; } = true;
+
+    private static void ConfigFileChanged (object sender, FileSystemEventArgs e)
+    {
+        if (Application.Top == null)
+        {
+            return;
+        }
+
+        // TODO: This is a hack. Figure out how to ensure that the file is fully written before reading it.
+        //Thread.Sleep (500);
+        Load ();
+        Apply ();
+    }
+
+    private static int Main (string [] args)
+    {
+        Console.OutputEncoding = Encoding.Default;
+
+        if (Debugger.IsAttached)
+        {
+            CultureInfo.DefaultThreadCurrentUICulture = CultureInfo.GetCultureInfo ("en-US");
+        }
+
+        _scenarios = Scenario.GetScenarios ();
+        _categories = Scenario.GetAllCategories ();
+
+        // Process command line args
+        // "UICatalog [-driver <driver>] [scenario name]"
+        // If no driver is provided, the default driver is used.
+        Option<string> driverOption = new Option<string> (
+                                                          "--driver",
+                                                          "The ConsoleDriver to use."
+                                                         ).FromAmong (Application.GetDriverTypes ().Select (d => d.Name).ToArray ());
+        driverOption.AddAlias ("-d");
+        driverOption.AddAlias ("--d");
+
+        Argument<string> scenarioArgument = new Argument<string> (
+                                                                  "scenario",
+                                                                  description: "The name of the scenario to run.",
+                                                                  getDefaultValue: () => "none"
+                                                                 ).FromAmong (_scenarios.Select (s => s.GetName ()).Append ("none").ToArray ());
+
+        var rootCommand = new RootCommand ("A comprehensive sample library for Terminal.Gui")
+        {
+            scenarioArgument,
+            driverOption
+        };
+
+        rootCommand.SetHandler (
+                                context =>
+                                {
+                                    var options = new Options
+                                    {
+                                        Driver = context.ParseResult.GetValueForOption (driverOption) ?? string.Empty,
+                                        Scenario = context.ParseResult.GetValueForArgument (scenarioArgument)
+                                        /* etc. */
+                                    };
+
+                                    // See https://github.com/dotnet/command-line-api/issues/796 for the rationale behind this hackery
+                                    _options = options;
+                                    ;
+                                });
+
+        rootCommand.Invoke (args);
+
+        UICatalogMain (_options);
+
+        return 0;
+    }
+
+    private static void OpenUrl (string url)
+    {
+        if (RuntimeInformation.IsOSPlatform (OSPlatform.Windows))
+        {
+            url = url.Replace ("&", "^&");
+            Process.Start (new ProcessStartInfo ("cmd", $"/c start {url}") { CreateNoWindow = true });
+        }
+        else if (RuntimeInformation.IsOSPlatform (OSPlatform.Linux))
+        {
+            using var process = new Process
+            {
+                StartInfo = new ProcessStartInfo
+                {
+                    FileName = "xdg-open",
+                    Arguments = url,
+                    RedirectStandardError = true,
+                    RedirectStandardOutput = true,
+                    CreateNoWindow = true,
+                    UseShellExecute = false
+                }
+            };
+            process.Start ();
+        }
+        else if (RuntimeInformation.IsOSPlatform (OSPlatform.OSX))
+        {
+            Process.Start ("open", url);
+        }
+    }
+
+    /// <summary>
+    ///     Shows the UI Catalog selection UI. When the user selects a Scenario to run, the
+    ///     UI Catalog main app UI is killed and the Scenario is run as though it were Application.Top.
+    ///     When the Scenario exits, this function exits.
+    /// </summary>
+    /// <returns></returns>
+    private static Scenario RunUICatalogTopLevel ()
+    {
+        // Run UI Catalog UI. When it exits, if _selectedScenario is != null then
+        // a Scenario was selected. Otherwise, the user wants to quit UI Catalog.
+
+        // If the user specified a driver on the command line then use it,
+        // ignoring Config files.
+
+        Application.Init (driverName: _forceDriver);
+
+        if (_cachedTheme is null)
+        {
+            _cachedTheme = Themes?.Theme;
+        }
+        else
+        {
+            Themes!.Theme = _cachedTheme;
+            Apply ();
+        }
+
+        Application.Run<UICatalogTopLevel> ();
+        Application.Shutdown ();
+
+        return _selectedScenario!;
+    }
+
+    private static void StartConfigFileWatcher ()
+    {
+        // Setup a file system watcher for `./.tui/`
+        _currentDirWatcher.NotifyFilter = NotifyFilters.LastWrite;
+
+        string assemblyLocation = Assembly.GetExecutingAssembly ().Location;
+        string tuiDir;
+
+        if (!string.IsNullOrEmpty (assemblyLocation))
+        {
+            var assemblyFile = new FileInfo (assemblyLocation);
+            tuiDir = Path.Combine (assemblyFile.Directory!.FullName, ".tui");
+        }
+        else
+        {
+            tuiDir = Path.Combine (AppContext.BaseDirectory, ".tui");
+        }
+
+        if (!Directory.Exists (tuiDir))
+        {
+            Directory.CreateDirectory (tuiDir);
+        }
+
+        _currentDirWatcher.Path = tuiDir;
+        _currentDirWatcher.Filter = "*config.json";
+
+        // Setup a file system watcher for `~/.tui/`
+        _homeDirWatcher.NotifyFilter = NotifyFilters.LastWrite;
+        var f = new FileInfo (Environment.GetFolderPath (Environment.SpecialFolder.UserProfile));
+        tuiDir = Path.Combine (f.FullName, ".tui");
+
+        if (!Directory.Exists (tuiDir))
+        {
+            Directory.CreateDirectory (tuiDir);
+        }
+
+        _homeDirWatcher.Path = tuiDir;
+        _homeDirWatcher.Filter = "*config.json";
+
+        _currentDirWatcher.Changed += ConfigFileChanged;
+
+        //_currentDirWatcher.Created += ConfigFileChanged;
+        _currentDirWatcher.EnableRaisingEvents = true;
+
+        _homeDirWatcher.Changed += ConfigFileChanged;
+
+        //_homeDirWatcher.Created += ConfigFileChanged;
+        _homeDirWatcher.EnableRaisingEvents = true;
+    }
+
+    private static void StopConfigFileWatcher ()
+    {
+        _currentDirWatcher.EnableRaisingEvents = false;
+        _currentDirWatcher.Changed -= ConfigFileChanged;
+        _currentDirWatcher.Created -= ConfigFileChanged;
+
+        _homeDirWatcher.EnableRaisingEvents = false;
+        _homeDirWatcher.Changed -= ConfigFileChanged;
+        _homeDirWatcher.Created -= ConfigFileChanged;
+    }
+
+    private static void UICatalogMain (Options options)
+    {
+        StartConfigFileWatcher ();
+
+        // By setting _forceDriver we ensure that if the user has specified a driver on the command line, it will be used
+        // regardless of what's in a config file.
+        Application.ForceDriver = _forceDriver = options.Driver;
+
+        // If a Scenario name has been provided on the commandline
+        // run it and exit when done.
+        if (options.Scenario != "none")
+        {
+            _topLevelColorScheme = "Base";
+
+            int item = _scenarios!.FindIndex (s => s.GetName ().Equals (options.Scenario, StringComparison.OrdinalIgnoreCase));
+            _selectedScenario = (Scenario)Activator.CreateInstance (_scenarios [item].GetType ())!;
+
+            Application.Init (driverName: _forceDriver);
+            _selectedScenario.Theme = _cachedTheme;
+            _selectedScenario.TopLevelColorScheme = _topLevelColorScheme;
+            _selectedScenario.Init ();
+            _selectedScenario.Setup ();
+            _selectedScenario.Run ();
+            _selectedScenario.Dispose ();
+            _selectedScenario = null;
+            Application.Shutdown ();
+            VerifyObjectsWereDisposed ();
+
+            return;
+        }
+
+        _aboutMessage = new StringBuilder ();
+        _aboutMessage.AppendLine (@"A comprehensive sample library for");
+        _aboutMessage.AppendLine (@"");
+        _aboutMessage.AppendLine (@"  _______                  _             _   _____       _  ");
+        _aboutMessage.AppendLine (@" |__   __|                (_)           | | / ____|     (_) ");
+        _aboutMessage.AppendLine (@"    | | ___ _ __ _ __ ___  _ _ __   __ _| || |  __ _   _ _  ");
+        _aboutMessage.AppendLine (@"    | |/ _ \ '__| '_ ` _ \| | '_ \ / _` | || | |_ | | | | | ");
+        _aboutMessage.AppendLine (@"    | |  __/ |  | | | | | | | | | | (_| | || |__| | |_| | | ");
+        _aboutMessage.AppendLine (@"    |_|\___|_|  |_| |_| |_|_|_| |_|\__,_|_(_)_____|\__,_|_| ");
+        _aboutMessage.AppendLine (@"");
+        _aboutMessage.AppendLine (@"v2 - Work in Progress");
+        _aboutMessage.AppendLine (@"");
+        _aboutMessage.AppendLine (@"https://github.com/gui-cs/Terminal.Gui");
+
+        while (RunUICatalogTopLevel () is { } scenario)
+        {
+            VerifyObjectsWereDisposed ();
+            Themes!.Theme = _cachedTheme!;
+            Apply ();
+            scenario.Theme = _cachedTheme;
+            scenario.TopLevelColorScheme = _topLevelColorScheme;
+            scenario.Init ();
+            scenario.Setup ();
+            scenario.Run ();
+            scenario.Dispose ();
+
+            // This call to Application.Shutdown brackets the Application.Init call
+            // made by Scenario.Init() above
+            Application.Shutdown ();
+
+            VerifyObjectsWereDisposed ();
+        }
+
+        StopConfigFileWatcher ();
+        VerifyObjectsWereDisposed ();
+    }
+
+    private static void VerifyObjectsWereDisposed ()
+    {
 #if DEBUG_IDISPOSABLE
-		// Validate there are no outstanding Responder-based instances 
-		// after a scenario was selected to run. This proves the main UI Catalog
-		// 'app' closed cleanly.
-		foreach (var inst in Responder.Instances) {
-
-			Debug.Assert (inst.WasDisposed);
-		}
-		Responder.Instances.Clear ();
-
-		// Validate there are no outstanding Application.RunState-based instances 
-		// after a scenario was selected to run. This proves the main UI Catalog
-		// 'app' closed cleanly.
-		foreach (var inst in RunState.Instances) {
-			Debug.Assert (inst.WasDisposed);
-		}
-		RunState.Instances.Clear ();
+
+        // Validate there are no outstanding Responder-based instances 
+        // after a scenario was selected to run. This proves the main UI Catalog
+        // 'app' closed cleanly.
+        foreach (Responder? inst in Responder.Instances)
+        {
+            Debug.Assert (inst.WasDisposed);
+        }
+
+        Responder.Instances.Clear ();
+
+        // Validate there are no outstanding Application.RunState-based instances 
+        // after a scenario was selected to run. This proves the main UI Catalog
+        // 'app' closed cleanly.
+        foreach (RunState? inst in RunState.Instances)
+        {
+            Debug.Assert (inst.WasDisposed);
+        }
+
+        RunState.Instances.Clear ();
 #endif
-	}
-
-	static void OpenUrl (string url)
-	{
-		try {
-			if (RuntimeInformation.IsOSPlatform (OSPlatform.Windows)) {
-				url = url.Replace ("&", "^&");
-				Process.Start (new ProcessStartInfo ("cmd", $"/c start {url}") { CreateNoWindow = true });
-			} else if (RuntimeInformation.IsOSPlatform (OSPlatform.Linux)) {
-				using var process = new Process {
-					StartInfo = new ProcessStartInfo {
-						FileName = "xdg-open",
-						Arguments = url,
-						RedirectStandardError = true,
-						RedirectStandardOutput = true,
-						CreateNoWindow = true,
-						UseShellExecute = false
-					}
-				};
-				process.Start ();
-			} else if (RuntimeInformation.IsOSPlatform (OSPlatform.OSX)) {
-				Process.Start ("open", url);
-			}
-		} catch {
-			throw;
-		}
-	}
-}
+    }
+
+    /// <summary>
+    ///     This is the main UI Catalog app view. It is run fresh when the app loads (if a Scenario has not been passed on
+    ///     the command line) and each time a Scenario ends.
+    /// </summary>
+    public class UICatalogTopLevel : Toplevel
+    {
+        public ListView CategoryList;
+
+        public StatusItem DriverName;
+        public MenuItem? miForce16Colors;
+        public MenuItem? miIsMenuBorderDisabled;
+        public MenuItem? miIsMouseDisabled;
+        public MenuItem? miUseSubMenusSingleFrame;
+        public StatusItem OS;
+
+        // UI Catalog uses TableView for the scenario list instead of a ListView to demonstate how
+        // TableView works. There's no real reason not to use ListView. Because we use TableView, and TableView
+        // doesn't (currently) have CollectionNavigator support built in, we implement it here, within the app.
+        public TableView ScenarioList;
+        private readonly CollectionNavigator _scenarioCollectionNav = new ();
+
+        public UICatalogTopLevel ()
+        {
+            _themeMenuItems = CreateThemeMenuItems ();
+            _themeMenuBarItem = new MenuBarItem ("_Themes", _themeMenuItems);
+
+            MenuBar = new MenuBar (
+                                   new []
+                                   {
+                                       new (
+                                            "_File",
+                                            new MenuItem []
+                                            {
+                                                new ("_Quit", "Quit UI Catalog", RequestStop)
+                                            }),
+                                       _themeMenuBarItem,
+                                       new ("Diag_nostics", CreateDiagnosticMenuItems ()),
+                                       new (
+                                            "_Help",
+                                            new MenuItem []
+                                            {
+                                                new (
+                                                     "_Documentation",
+                                                     "",
+                                                     () => OpenUrl ("https://gui-cs.github.io/Terminal.GuiV2Docs"),
+                                                     null,
+                                                     null,
+                                                     (KeyCode)Key.F1),
+                                                new ("_README", "", () => OpenUrl ("https://github.com/gui-cs/Terminal.Gui"), null, null, (KeyCode)Key.F2),
+                                                new (
+                                                     "_About...",
+                                                     "About UI Catalog",
+                                                     () => MessageBox.Query ("About UI Catalog", _aboutMessage!.ToString (), 0, false, "_Ok"),
+                                                     null,
+                                                     null,
+                                                     (KeyCode)Key.A.WithCtrl)
+                                            })
+                                   });
+
+            DriverName = new StatusItem (Key.Empty, "Driver:", null);
+            OS = new StatusItem (Key.Empty, "OS:", null);
+
+            StatusBar = new StatusBar
+            {
+                Visible = ShowStatusBar
+            };
+
+            StatusBar.Items = new []
+            {
+                new (
+                     Application.QuitKey,
+                     $"~{Application.QuitKey} to quit",
+                     () =>
+                     {
+                         if (_selectedScenario is null)
+                         {
+                             // This causes GetScenarioToRun to return null
+                             _selectedScenario = null;
+                             RequestStop ();
+                         }
+                         else
+                         {
+                             _selectedScenario.RequestStop ();
+                         }
+                     }),
+                new (
+                     Key.F10,
+                     "~F10~ Status Bar",
+                     () =>
+                     {
+                         StatusBar.Visible = !StatusBar.Visible;
+
+                         //ContentPane!.Height = Dim.Fill(StatusBar.Visible ? 1 : 0);
+                         LayoutSubviews ();
+                         SetSubViewNeedsDisplay ();
+                     }),
+                DriverName,
+                OS
+            };
+
+            // Create the Category list view. This list never changes.
+            CategoryList = new ListView (_categories)
+            {
+                X = 0,
+                Y = 1,
+                Width = Dim.Percent (30),
+                Height = Dim.Fill (1),
+                AllowsMarking = false,
+                CanFocus = true,
+                Title = "Categories",
+                BorderStyle = LineStyle.Single,
+                SuperViewRendersLineCanvas = true
+            };
+            CategoryList.OpenSelectedItem += (s, a) => { ScenarioList!.SetFocus (); };
+            CategoryList.SelectedItemChanged += CategoryView_SelectedChanged;
+
+            // Create the scenario list. The contents of the scenario list changes whenever the
+            // Category list selection changes (to show just the scenarios that belong to the selected
+            // category).
+            ScenarioList = new TableView
+            {
+                X = Pos.Right (CategoryList) - 1,
+                Y = 1,
+                Width = Dim.Fill (),
+                Height = Dim.Fill (1),
+
+                //AllowsMarking = false,
+                CanFocus = true,
+                Title = "Scenarios",
+                BorderStyle = LineStyle.Single,
+                SuperViewRendersLineCanvas = true
+            };
+
+            // TableView provides many options for table headers. For simplicity we turn all 
+            // of these off. By enabling FullRowSelect and turning off headers, TableView looks just
+            // like a ListView
+            ScenarioList.FullRowSelect = true;
+            ScenarioList.Style.ShowHeaders = false;
+            ScenarioList.Style.ShowHorizontalHeaderOverline = false;
+            ScenarioList.Style.ShowHorizontalHeaderUnderline = false;
+            ScenarioList.Style.ShowHorizontalBottomline = false;
+            ScenarioList.Style.ShowVerticalCellLines = false;
+            ScenarioList.Style.ShowVerticalHeaderLines = false;
+
+            /* By default TableView lays out columns at render time and only
+             * measures y rows of data at a time.  Where y is the height of the
+             * console. This is for the following reasons:
+             *
+             * - Performance, when tables have a large amount of data
+             * - Defensive, prevents a single wide cell value pushing other
+             *   columns off screen (requiring horizontal scrolling
+             *
+             * In the case of UICatalog here, such an approach is overkill so
+             * we just measure all the data ourselves and set the appropriate
+             * max widths as ColumnStyles
+             */
+            int longestName = _scenarios!.Max (s => s.GetName ().Length);
+            ScenarioList.Style.ColumnStyles.Add (0, new ColumnStyle { MaxWidth = longestName, MinWidth = longestName, MinAcceptableWidth = longestName });
+            ScenarioList.Style.ColumnStyles.Add (1, new ColumnStyle { MaxWidth = 1 });
+
+            // Enable user to find & select a scenario by typing text
+            // TableView does not (currently) have built-in CollectionNavigator support (the ability for the 
+            // user to type and the items that match get selected). We implement it in the app instead. 
+            ScenarioList.KeyDown += (s, a) =>
+                                    {
+                                        if (CollectionNavigatorBase.IsCompatibleKey (a))
+                                        {
+                                            int? newItem = _scenarioCollectionNav?.GetNextMatchingItem (ScenarioList.SelectedRow, (char)a);
+
+                                            if (newItem is int v && newItem != -1)
+                                            {
+                                                ScenarioList.SelectedRow = v;
+                                                ScenarioList.EnsureSelectedCellIsVisible ();
+                                                ScenarioList.SetNeedsDisplay ();
+                                                a.Handled = true;
+                                            }
+                                        }
+                                    };
+            ScenarioList.CellActivated += ScenarioView_OpenSelectedItem;
+
+            // TableView typically is a grid where nav keys are biased for moving left/right.
+            ScenarioList.KeyBindings.Add (Key.Home, Command.TopHome);
+            ScenarioList.KeyBindings.Add (Key.End, Command.BottomEnd);
+
+            // Ideally, TableView.MultiSelect = false would turn off any keybindings for
+            // multi-select options. But it currently does not. UI Catalog uses Ctrl-A for
+            // a shortcut to About.
+            ScenarioList.MultiSelect = false;
+            ScenarioList.KeyBindings.Remove (Key.A.WithCtrl);
+
+            Add (CategoryList);
+            Add (ScenarioList);
+
+            Add (MenuBar);
+            Add (StatusBar);
+
+            Loaded += LoadedHandler;
+            Unloaded += UnloadedHandler;
+
+            // Restore previous selections
+            CategoryList.SelectedItem = _cachedCategoryIndex;
+            ScenarioList.SelectedRow = _cachedScenarioIndex;
+
+            Applied += ConfigAppliedHandler;
+        }
+
+        public void ConfigChanged ()
+        {
+            if (_topLevelColorScheme == null || !Colors.ColorSchemes.ContainsKey (_topLevelColorScheme))
+            {
+                _topLevelColorScheme = "Base";
+            }
+
+            _cachedTheme = Themes?.Theme;
+
+            _themeMenuItems = CreateThemeMenuItems ();
+            _themeMenuBarItem!.Children = _themeMenuItems;
+
+            foreach (MenuItem mi in _themeMenuItems!)
+            {
+                if (mi is { Parent: null })
+                {
+                    mi.Parent = _themeMenuBarItem;
+                }
+            }
+
+            ColorScheme = Colors.ColorSchemes [_topLevelColorScheme];
+
+            MenuBar.Menus [0].Children [0].Shortcut = (KeyCode)Application.QuitKey;
+            StatusBar.Items [0].Shortcut = Application.QuitKey;
+            StatusBar.Items [0].Title = $"~{Application.QuitKey} to quit";
+
+            miIsMouseDisabled!.Checked = Application.IsMouseDisabled;
+
+            int height = ShowStatusBar ? 1 : 0; // + (MenuBar.Visible ? 1 : 0);
+
+            //ContentPane.Height = Dim.Fill (height);
+
+            StatusBar.Visible = ShowStatusBar;
+
+            Application.Top.SetNeedsDisplay ();
+        }
+
+        public MenuItem []? CreateThemeMenuItems ()
+        {
+            List<MenuItem> menuItems = CreateForce16ColorItems ().ToList ();
+            menuItems.Add (null!);
+
+            var schemeCount = 0;
+
+            foreach (KeyValuePair<string, ThemeScope> theme in Themes!)
+            {
+                var item = new MenuItem
+                {
+                    Title = $"_{theme.Key}",
+                    Shortcut = (KeyCode)new Key ((KeyCode)((uint)KeyCode.D1 + schemeCount++)).WithCtrl
+                };
+                item.CheckType |= MenuItemCheckStyle.Checked;
+                item.Checked = theme.Key == _cachedTheme; // CM.Themes.Theme;
+
+                item.Action += () =>
+                               {
+                                   Themes.Theme = _cachedTheme = theme.Key;
+                                   Apply ();
+                               };
+                menuItems.Add (item);
+            }
+
+            List<MenuItem> schemeMenuItems = new ();
+
+            foreach (KeyValuePair<string, ColorScheme> sc in Colors.ColorSchemes)
+            {
+                var item = new MenuItem
+                {
+                    Title = $"_{sc.Key}",
+                    Data = sc.Key
+                };
+                item.CheckType |= MenuItemCheckStyle.Radio;
+                item.Checked = sc.Key == _topLevelColorScheme;
+
+                item.Action += () =>
+                               {
+                                   _topLevelColorScheme = (string)item.Data;
+
+                                   foreach (MenuItem schemeMenuItem in schemeMenuItems)
+                                   {
+                                       schemeMenuItem.Checked = (string)schemeMenuItem.Data == _topLevelColorScheme;
+                                   }
+
+                                   ColorScheme = Colors.ColorSchemes [_topLevelColorScheme];
+                                   Application.Top.SetNeedsDisplay ();
+                               };
+                schemeMenuItems.Add (item);
+            }
+
+            menuItems.Add (null!);
+            var mbi = new MenuBarItem ("_Color Scheme for Application.Top", schemeMenuItems.ToArray ());
+            menuItems.Add (mbi);
+
+            return menuItems.ToArray ();
+        }
+
+        private void CategoryView_SelectedChanged (object? sender, ListViewItemEventArgs? e)
+        {
+            string item = _categories! [e!.Item];
+            List<Scenario> newlist;
+
+            if (e.Item == 0)
+            {
+                // First category is "All"
+                newlist = _scenarios!;
+                newlist = _scenarios!;
+            }
+            else
+            {
+                newlist = _scenarios!.Where (s => s.GetCategories ().Contains (item)).ToList ();
+            }
+
+            ScenarioList.Table = new EnumerableTableSource<Scenario> (
+                                                                      newlist,
+                                                                      new Dictionary<string, Func<Scenario, object>>
+                                                                      {
+                                                                          { "Name", s => s.GetName () },
+                                                                          { "Description", s => s.GetDescription () }
+                                                                      });
+
+            // Create a collection of just the scenario names (the 1st column in our TableView)
+            // for CollectionNavigator. 
+            List<object> firstColumnList = new ();
+
+            for (var i = 0; i < ScenarioList.Table.Rows; i++)
+            {
+                firstColumnList.Add (ScenarioList.Table [i, 0]);
+            }
+
+            _scenarioCollectionNav.Collection = firstColumnList;
+        }
+
+        private void ConfigAppliedHandler (object? sender, ConfigurationManagerEventArgs? a) { ConfigChanged (); }
+
+        private MenuItem [] CreateDiagnosticFlagsMenuItems ()
+        {
+            const string OFF = "Diagnostics: _Off";
+            const string FRAME_RULER = "Diagnostics: Frame _Ruler";
+            const string FRAME_PADDING = "Diagnostics: _Frame Padding";
+            var index = 0;
+
+            List<MenuItem> menuItems = new ();
+
+            foreach (Enum diag in Enum.GetValues (_diagnosticFlags.GetType ()))
+            {
+                var item = new MenuItem
+                {
+                    Title = GetDiagnosticsTitle (diag),
+                    Shortcut = (KeyCode)new Key (index.ToString () [0]).WithAlt
+                };
+                index++;
+                item.CheckType |= MenuItemCheckStyle.Checked;
+
+                if (GetDiagnosticsTitle (ConsoleDriver.DiagnosticFlags.Off) == item.Title)
+                {
+                    item.Checked = (_diagnosticFlags
+                                    & (ConsoleDriver.DiagnosticFlags.FramePadding
+                                       | ConsoleDriver.DiagnosticFlags.FrameRuler))
+                                   == 0;
+                }
+                else
+                {
+                    item.Checked = _diagnosticFlags.HasFlag (diag);
+                }
+
+                item.Action += () =>
+                               {
+                                   string t = GetDiagnosticsTitle (ConsoleDriver.DiagnosticFlags.Off);
+
+                                   if (item.Title == t && item.Checked == false)
+                                   {
+                                       _diagnosticFlags &= ~(ConsoleDriver.DiagnosticFlags.FramePadding | ConsoleDriver.DiagnosticFlags.FrameRuler);
+                                       item.Checked = true;
+                                   }
+                                   else if (item.Title == t && item.Checked == true)
+                                   {
+                                       _diagnosticFlags |= ConsoleDriver.DiagnosticFlags.FramePadding | ConsoleDriver.DiagnosticFlags.FrameRuler;
+                                       item.Checked = false;
+                                   }
+                                   else
+                                   {
+                                       Enum f = GetDiagnosticsEnumValue (item.Title);
+
+                                       if (_diagnosticFlags.HasFlag (f))
+                                       {
+                                           SetDiagnosticsFlag (f, false);
+                                       }
+                                       else
+                                       {
+                                           SetDiagnosticsFlag (f, true);
+                                       }
+                                   }
+
+                                   foreach (MenuItem menuItem in menuItems)
+                                   {
+                                       if (menuItem.Title == t)
+                                       {
+                                           menuItem.Checked = !_diagnosticFlags.HasFlag (ConsoleDriver.DiagnosticFlags.FrameRuler)
+                                                              && !_diagnosticFlags.HasFlag (ConsoleDriver.DiagnosticFlags.FramePadding);
+                                       }
+                                       else if (menuItem.Title != t)
+                                       {
+                                           menuItem.Checked = _diagnosticFlags.HasFlag (GetDiagnosticsEnumValue (menuItem.Title));
+                                       }
+                                   }
+
+                                   ConsoleDriver.Diagnostics = _diagnosticFlags;
+                                   Application.Top.SetNeedsDisplay ();
+                               };
+                menuItems.Add (item);
+            }
+
+            return menuItems.ToArray ();
+
+            string GetDiagnosticsTitle (Enum diag)
+            {
+                return Enum.GetName (_diagnosticFlags.GetType (), diag) switch
+                       {
+                           "Off" => OFF,
+                           "FrameRuler" => FRAME_RULER,
+                           "FramePadding" => FRAME_PADDING,
+                           _ => ""
+                       };
+            }
+
+            Enum GetDiagnosticsEnumValue (string title)
+            {
+                return title switch
+                       {
+                           FRAME_RULER => ConsoleDriver.DiagnosticFlags.FrameRuler,
+                           FRAME_PADDING => ConsoleDriver.DiagnosticFlags.FramePadding,
+                           _ => null!
+                       };
+            }
+
+            void SetDiagnosticsFlag (Enum diag, bool add)
+            {
+                switch (diag)
+                {
+                    case ConsoleDriver.DiagnosticFlags.FrameRuler:
+                        if (add)
+                        {
+                            _diagnosticFlags |= ConsoleDriver.DiagnosticFlags.FrameRuler;
+                        }
+                        else
+                        {
+                            _diagnosticFlags &= ~ConsoleDriver.DiagnosticFlags.FrameRuler;
+                        }
+
+                        break;
+                    case ConsoleDriver.DiagnosticFlags.FramePadding:
+                        if (add)
+                        {
+                            _diagnosticFlags |= ConsoleDriver.DiagnosticFlags.FramePadding;
+                        }
+                        else
+                        {
+                            _diagnosticFlags &= ~ConsoleDriver.DiagnosticFlags.FramePadding;
+                        }
+
+                        break;
+                    default:
+                        _diagnosticFlags = default (ConsoleDriver.DiagnosticFlags);
+
+                        break;
+                }
+            }
+        }
+
+        private List<MenuItem []> CreateDiagnosticMenuItems ()
+        {
+            List<MenuItem []> menuItems = new()
+            {
+                CreateDiagnosticFlagsMenuItems (),
+                new MenuItem [] { null! },
+                CreateDisabledEnabledMouseItems (),
+                CreateDisabledEnabledMenuBorder (),
+                CreateDisabledEnableUseSubMenusSingleFrame (),
+                CreateKeyBindingsMenuItems ()
+            };
+
+            return menuItems;
+        }
+
+        // TODO: This should be an ConfigurationManager setting
+        private MenuItem [] CreateDisabledEnabledMenuBorder ()
+        {
+            List<MenuItem> menuItems = new ();
+
+            miIsMenuBorderDisabled = new MenuItem
+            {
+                Title = "Disable Menu _Border"
+            };
+            miIsMenuBorderDisabled.Shortcut = (KeyCode)new Key (miIsMenuBorderDisabled!.Title!.Substring (14, 1) [0]).WithAlt.WithCtrl;
+            miIsMenuBorderDisabled.CheckType |= MenuItemCheckStyle.Checked;
+
+            miIsMenuBorderDisabled.Action += () =>
+                                             {
+                                                 miIsMenuBorderDisabled.Checked = (bool)!miIsMenuBorderDisabled.Checked!;
+                                                 MenuBar.MenusBorderStyle = !(bool)miIsMenuBorderDisabled.Checked ? LineStyle.Single : LineStyle.None;
+                                             };
+            menuItems.Add (miIsMenuBorderDisabled);
+
+            return menuItems.ToArray ();
+        }
+
+        private MenuItem [] CreateDisabledEnabledMouseItems ()
+        {
+            List<MenuItem> menuItems = new ();
+
+            miIsMouseDisabled = new MenuItem
+            {
+                Title = "_Disable Mouse"
+            };
+            miIsMouseDisabled.Shortcut = (KeyCode)new Key (miIsMouseDisabled!.Title!.Substring (1, 1) [0]).WithAlt.WithCtrl;
+            miIsMouseDisabled.CheckType |= MenuItemCheckStyle.Checked;
+            miIsMouseDisabled.Action += () => { miIsMouseDisabled.Checked = Application.IsMouseDisabled = (bool)!miIsMouseDisabled.Checked!; };
+            menuItems.Add (miIsMouseDisabled);
+
+            return menuItems.ToArray ();
+        }
+
+        // TODO: This should be an ConfigurationManager setting
+        private MenuItem [] CreateDisabledEnableUseSubMenusSingleFrame ()
+        {
+            List<MenuItem> menuItems = new ();
+
+            miUseSubMenusSingleFrame = new MenuItem
+            {
+                Title = "Enable _Sub-Menus Single Frame"
+            };
+            miUseSubMenusSingleFrame.Shortcut = KeyCode.CtrlMask | KeyCode.AltMask | (KeyCode)miUseSubMenusSingleFrame!.Title!.Substring (8, 1) [0];
+            miUseSubMenusSingleFrame.CheckType |= MenuItemCheckStyle.Checked;
+
+            miUseSubMenusSingleFrame.Action += () =>
+                                               {
+                                                   miUseSubMenusSingleFrame.Checked = (bool)!miUseSubMenusSingleFrame.Checked!;
+                                                   MenuBar.UseSubMenusSingleFrame = (bool)miUseSubMenusSingleFrame.Checked;
+                                               };
+            menuItems.Add (miUseSubMenusSingleFrame);
+
+            return menuItems.ToArray ();
+        }
+
+        private MenuItem [] CreateForce16ColorItems ()
+        {
+            List<MenuItem> menuItems = new ();
+
+            miForce16Colors = new MenuItem
+            {
+                Title = "Force _16 Colors",
+                Shortcut = (KeyCode)Key.F6,
+                Checked = Application.Force16Colors,
+                CanExecute = () => Application.Driver.SupportsTrueColor
+            };
+            miForce16Colors.CheckType |= MenuItemCheckStyle.Checked;
+
+            miForce16Colors.Action += () =>
+                                      {
+                                          miForce16Colors.Checked = Application.Force16Colors = (bool)!miForce16Colors.Checked!;
+                                          Application.Refresh ();
+                                      };
+            menuItems.Add (miForce16Colors);
+
+            return menuItems.ToArray ();
+        }
+
+        private MenuItem [] CreateKeyBindingsMenuItems ()
+        {
+            List<MenuItem> menuItems = new ();
+
+            var item = new MenuItem
+            {
+                Title = "_Key Bindings",
+                Help = "Change which keys do what"
+            };
+
+            item.Action += () =>
+                           {
+                               var dlg = new KeyBindingsDialog ();
+                               Application.Run (dlg);
+                           };
+
+            menuItems.Add (null!);
+            menuItems.Add (item);
+
+            return menuItems.ToArray ();
+        }
+
+        private void LoadedHandler (object? sender, EventArgs? args)
+        {
+            ConfigChanged ();
+
+            miIsMouseDisabled!.Checked = Application.IsMouseDisabled;
+            DriverName.Title = $"Driver: {Driver.GetVersionInfo ()}";
+            OS.Title = $"OS: {RuntimeEnvironment.OperatingSystem} {RuntimeEnvironment.OperatingSystemVersion}";
+
+            if (_selectedScenario != null)
+            {
+                _selectedScenario = null;
+                _isFirstRunning = false;
+            }
+
+            if (!_isFirstRunning)
+            {
+                ScenarioList.SetFocus ();
+            }
+
+            StatusBar.VisibleChanged += (s, e) =>
+                                        {
+                                            ShowStatusBar = StatusBar.Visible;
+
+                                            int height = StatusBar.Visible ? 1 : 0;
+                                            CategoryList.Height = Dim.Fill (height);
+                                            ScenarioList.Height = Dim.Fill (height);
+
+                                            // ContentPane.Height = Dim.Fill (height);
+                                            LayoutSubviews ();
+                                            SetSubViewNeedsDisplay ();
+                                        };
+
+            Loaded -= LoadedHandler;
+            CategoryList.EnsureSelectedItemVisible ();
+            ScenarioList.EnsureSelectedCellIsVisible ();
+        }
+
+        /// <summary>
+        ///     Launches the selected scenario, setting the global _selectedScenario
+        /// </summary>
+        /// <param name="e"></param>
+        private void ScenarioView_OpenSelectedItem (object? sender, EventArgs? e)
+        {
+            if (_selectedScenario is null)
+            {
+                // Save selected item state
+                _cachedCategoryIndex = CategoryList.SelectedItem;
+                _cachedScenarioIndex = ScenarioList.SelectedRow;
+
+                // Create new instance of scenario (even though Scenarios contains instances)
+                var selectedScenarioName = (string)ScenarioList.Table [ScenarioList.SelectedRow, 0];
+                _selectedScenario = (Scenario)Activator.CreateInstance (_scenarios!.FirstOrDefault (s => s.GetName () == selectedScenarioName)!.GetType ())!;
+
+                // Tell the main app to stop
+                Application.RequestStop ();
+            }
+        }
+
+        private void UnloadedHandler (object? sender, EventArgs? args)
+        {
+            Applied -= ConfigAppliedHandler;
+            Unloaded -= UnloadedHandler;
+        }
+    }
+
+    private struct Options
+    {
+        public string Driver;
+
+        public string Scenario;
+        /* etc. */
+    }
+}

+ 390 - 382
UnitTests/View/Layout/AbsoluteLayoutTests.cs

@@ -1,5 +1,4 @@
-using Xunit;
-using Xunit.Abstractions;
+using Xunit.Abstractions;
 
 //using GraphViewTests = Terminal.Gui.Views.GraphViewTests;
 
@@ -7,383 +6,392 @@ using Xunit.Abstractions;
 
 namespace Terminal.Gui.ViewTests;
 
-public class AbsoluteLayoutTests {
-	readonly ITestOutputHelper _output;
-
-	public AbsoluteLayoutTests (ITestOutputHelper output) => _output = output;
-
-	[Fact]
-	[TestRespondersDisposed]
-	public void AbsoluteLayout_Constructor ()
-	{
-		var v = new View ();
-		Assert.True (v.LayoutStyle == LayoutStyle.Absolute);
-		v.Dispose ();
-
-		var frame = Rect.Empty;
-		v = new View (frame);
-		Assert.True (v.LayoutStyle == LayoutStyle.Absolute);
-		Assert.Equal (frame, v.Frame);
-		Assert.Equal (new Rect (0, 0, frame.Width, frame.Height), v.Bounds); // With Absolute Bounds *is* deterministic before Layout
-		Assert.Equal (Pos.At (0), v.X);
-		Assert.Equal (Pos.At (0), v.Y);
-		Assert.Equal (Dim.Sized (0), v.Width);
-		Assert.Equal (Dim.Sized (0), v.Height);
-		v.Dispose ();
-
-		frame = new Rect (1, 2, 3, 4);
-		v = new View (frame);
-		Assert.True (v.LayoutStyle == LayoutStyle.Absolute);
-		Assert.Equal (frame,                                      v.Frame);
-		Assert.Equal (new Rect (0, 0, frame.Width, frame.Height), v.Bounds); // With Absolute Bounds *is* deterministic before Layout
-		Assert.Equal (Pos.At (1), v.X);
-		Assert.Equal (Pos.At (2), v.Y);
-		Assert.Equal (Dim.Sized (3), v.Width);
-		Assert.Equal (Dim.Sized (4), v.Height);
-		v.Dispose ();
-
-		v = new View (frame, "v");
-		Assert.True (v.LayoutStyle == LayoutStyle.Absolute);
-		Assert.Equal (frame,                                      v.Frame);
-		Assert.Equal (new Rect (0, 0, frame.Width, frame.Height), v.Bounds); // With Absolute Bounds *is* deterministic before Layout
-		Assert.Equal (Pos.At (1), v.X);
-		Assert.Equal (Pos.At (2), v.Y);
-		Assert.Equal (Dim.Sized (3), v.Width);
-		Assert.Equal (Dim.Sized (4), v.Height);
-		v.Dispose ();
-
-		v = new View (frame.X, frame.Y, "v");
-		Assert.True (v.LayoutStyle == LayoutStyle.Absolute);
-		// BUGBUG: v2 - I think the default size should be 0,0 not 1,1
-		Assert.Equal (new Rect (frame.X, frame.Y, 1, 1), v.Frame);
-		Assert.Equal (new Rect (0, 0, 1, 1), v.Bounds); // With Absolute Bounds *is* deterministic before Layout
-		Assert.Equal (Pos.At (1), v.X);
-		Assert.Equal (Pos.At (2), v.Y);
-		Assert.Equal (Dim.Sized (1), v.Width);
-		Assert.Equal (Dim.Sized (1), v.Height);
-		v.Dispose ();
-
-		v = new View ();
-		Assert.True (v.LayoutStyle == LayoutStyle.Absolute);
-		Assert.Equal (new Rect (0, 0, 0, 0), v.Frame);
-		Assert.Equal (new Rect (0, 0, 0, 0), v.Bounds); // With Absolute Bounds *is* deterministic before Layout
-		Assert.Equal (Pos.At (0), v.X);
-		Assert.Equal (Pos.At (0), v.Y);
-		Assert.Equal (Dim.Sized (0), v.Width);
-		Assert.Equal (Dim.Sized (0), v.Height);
-		v.Dispose ();
-
-		v = new View {
-			X = frame.X,
-			Y = frame.Y,
-			Width = frame.Width,
-			Height = frame.Height
-		};
-		Assert.True (v.LayoutStyle == LayoutStyle.Absolute);
-		Assert.Equal (new Rect (frame.X, frame.Y, 3, 4), v.Frame);
-		Assert.Equal (new Rect (0, 0, 3, 4), v.Bounds); // With Absolute Bounds *is* deterministic before Layout
-		Assert.Equal (Pos.At (1), v.X);
-		Assert.Equal (Pos.At (2), v.Y);
-		Assert.Equal (Dim.Sized (3), v.Width);
-		Assert.Equal (Dim.Sized (4), v.Height);
-		v.Dispose ();
-	}
-
-	[Fact]
-	[TestRespondersDisposed]
-	public void AbsoluteLayout_Change_Frame ()
-	{
-		var frame = new Rect (1,    2, 3,  4);
-		var newFrame = new Rect (1, 2, 30, 40);
-
-		var v = new View ();
-		Assert.True (v.LayoutStyle == LayoutStyle.Absolute);
-		v.Dispose ();
-
-		v = new View (frame);
-		Assert.True (v.LayoutStyle == LayoutStyle.Absolute);
-
-		v.Frame = newFrame;
-		Assert.True (v.LayoutStyle == LayoutStyle.Absolute);
-		Assert.Equal (newFrame,                                         v.Frame);
-		Assert.Equal (new Rect (0, 0, newFrame.Width, newFrame.Height), v.Bounds); // With Absolute Bounds *is* deterministic before Layout
-		Assert.Equal (Pos.At (1), v.X);
-		Assert.Equal (Pos.At (2), v.Y);
-		Assert.Equal (Dim.Sized (30), v.Width);
-		Assert.Equal (Dim.Sized (40), v.Height);
-		v.Dispose ();
-
-		v = new View (frame.X, frame.Y, "v");
-		v.Frame = newFrame;
-		Assert.True (v.LayoutStyle == LayoutStyle.Absolute);
-		Assert.Equal (newFrame, v.Frame);
-		Assert.Equal (new Rect (0, 0, newFrame.Width, newFrame.Height), v.Bounds); // With Absolute Bounds *is* deterministic before Layout
-		Assert.Equal (Pos.At (1), v.X);
-		Assert.Equal (Pos.At (2), v.Y);
-		Assert.Equal (Dim.Sized (30), v.Width);
-		Assert.Equal (Dim.Sized (40), v.Height);
-		v.Dispose ();
-
-		newFrame = new Rect (10, 20, 30, 40);
-		v = new View (frame);
-		v.Frame = newFrame;
-		Assert.True (v.LayoutStyle == LayoutStyle.Absolute);
-		Assert.Equal (newFrame, v.Frame);
-		Assert.Equal (new Rect (0, 0, newFrame.Width, newFrame.Height), v.Bounds); // With Absolute Bounds *is* deterministic before Layout
-		Assert.Equal (Pos.At (10), v.X);
-		Assert.Equal (Pos.At (20), v.Y);
-		Assert.Equal (Dim.Sized (30), v.Width);
-		Assert.Equal (Dim.Sized (40), v.Height);
-		v.Dispose ();
-
-		v = new View (frame.X, frame.Y, "v");
-		v.Frame = newFrame;
-		Assert.True (v.LayoutStyle == LayoutStyle.Absolute);
-		Assert.Equal (newFrame, v.Frame);
-		Assert.Equal (new Rect (0, 0, newFrame.Width, newFrame.Height), v.Bounds); // With Absolute Bounds *is* deterministic before Layout
-		Assert.Equal (Pos.At (10), v.X);
-		Assert.Equal (Pos.At (20), v.Y);
-		Assert.Equal (Dim.Sized (30), v.Width);
-		Assert.Equal (Dim.Sized (40), v.Height);
-		v.Dispose ();
-	}
-
-	[Fact]
-	[TestRespondersDisposed]
-	public void AbsoluteLayout_Change_Height_or_Width_Absolute ()
-	{
-		var frame = new Rect (1,    2, 3,  4);
-		var newFrame = new Rect (1, 2, 30, 40);
-
-		var v = new View (frame);
-		v.Height = newFrame.Height;
-		v.Width = newFrame.Width;
-		Assert.True (v.LayoutStyle == LayoutStyle.Absolute);
-		Assert.Equal (newFrame,                                         v.Frame);
-		Assert.Equal (new Rect (0, 0, newFrame.Width, newFrame.Height), v.Bounds); // With Absolute Bounds *is* deterministic before Layout
-		Assert.Equal (Pos.At (1), v.X);
-		Assert.Equal (Pos.At (2), v.Y);
-		Assert.Equal ($"Absolute({newFrame.Height})", v.Height.ToString ());
-		Assert.Equal ($"Absolute({newFrame.Width})", v.Width.ToString ());
-		v.Dispose ();
-	}
-
-	[Fact]
-	[TestRespondersDisposed]
-	public void AbsoluteLayout_Change_Height_or_Width_MakesComputed ()
-	{
-		var v = new View (Rect.Empty);
-		v.Height = Dim.Fill ();
-		v.Width = Dim.Fill ();
-		Assert.True (v.LayoutStyle == LayoutStyle.Computed);
-		v.Dispose ();
-	}
-
-	[Fact]
-	[TestRespondersDisposed]
-	public void AbsoluteLayout_Change_X_or_Y_Absolute ()
-	{
-		var frame = new Rect (1,     2,  3, 4);
-		var newFrame = new Rect (10, 20, 3, 4);
-
-		var v = new View (frame);
-		v.X = newFrame.X;
-		v.Y = newFrame.Y;
-		Assert.True (v.LayoutStyle == LayoutStyle.Absolute);
-		Assert.Equal (newFrame,                                         v.Frame);
-		Assert.Equal (new Rect (0, 0, newFrame.Width, newFrame.Height), v.Bounds); // With Absolute Bounds *is* deterministic before Layout
-		Assert.Equal ($"Absolute({newFrame.X})", v.X.ToString ());
-		Assert.Equal ($"Absolute({newFrame.Y})", v.Y.ToString ());
-		Assert.Equal (Dim.Sized (3), v.Width);
-		Assert.Equal (Dim.Sized (4), v.Height);
-		v.Dispose ();
-	}
-
-	[Fact]
-	[TestRespondersDisposed]
-	public void AbsoluteLayout_Change_X_or_Y_MakesComputed ()
-	{
-		var v = new View (Rect.Empty);
-		v.X = Pos.Center ();
-		v.Y = Pos.Center ();
-		Assert.True (v.LayoutStyle == LayoutStyle.Computed);
-		v.Dispose ();
-	}
-
-	[Fact]
-	[TestRespondersDisposed]
-	public void AbsoluteLayout_Change_X_Y_Height_Width_Absolute ()
-	{
-		var v = new View (Rect.Empty);
-		v.X = 1;
-		v.Y = 2;
-		v.Height = 3;
-		v.Width = 4;
-		Assert.True (v.LayoutStyle == LayoutStyle.Absolute);
-		v.Dispose ();
-
-		v = new View (Rect.Empty);
-		v.X = Pos.Center ();
-		v.Y = Pos.Center ();
-		v.Width = Dim.Fill ();
-		v.Height = Dim.Fill ();
-		Assert.True (v.LayoutStyle == LayoutStyle.Computed);
-		v.Dispose ();
-
-		v = new View (Rect.Empty);
-		v.X = Pos.Center ();
-		v.Y = Pos.Center ();
-		v.Width = Dim.Fill ();
-		v.Height = Dim.Fill ();
-		Assert.True (v.LayoutStyle == LayoutStyle.Computed);
-
-		v.X = 1;
-		Assert.True (v.LayoutStyle == LayoutStyle.Computed);
-		v.Dispose ();
-
-		v = new View (Rect.Empty);
-		v.X = Pos.Center ();
-		v.Y = Pos.Center ();
-		v.Width = Dim.Fill ();
-		v.Height = Dim.Fill ();
-		Assert.True (v.LayoutStyle == LayoutStyle.Computed);
-
-		v.Y = 2;
-		Assert.True (v.LayoutStyle == LayoutStyle.Computed);
-		v.Dispose ();
-
-		v = new View (Rect.Empty);
-		v.X = Pos.Center ();
-		v.Y = Pos.Center ();
-		v.Width = Dim.Fill ();
-		v.Height = Dim.Fill ();
-		Assert.True (v.LayoutStyle == LayoutStyle.Computed);
-
-		v.Width = 3;
-		Assert.True (v.LayoutStyle == LayoutStyle.Computed);
-		v.Dispose ();
-
-		v = new View (Rect.Empty);
-		v.X = Pos.Center ();
-		v.Y = Pos.Center ();
-		v.Width = Dim.Fill ();
-		v.Height = Dim.Fill ();
-		Assert.True (v.LayoutStyle == LayoutStyle.Computed);
-
-		v.Height = 3;
-		Assert.True (v.LayoutStyle == LayoutStyle.Computed);
-		v.Dispose ();
-
-		v = new View (Rect.Empty);
-		v.X = Pos.Center ();
-		v.Y = Pos.Center ();
-		v.Width = Dim.Fill ();
-		v.Height = Dim.Fill ();
-		Assert.True (v.LayoutStyle == LayoutStyle.Computed);
-
-		v.X = 1;
-		v.Y = 2;
-		v.Height = 3;
-		v.Width = 4;
-		Assert.True (v.LayoutStyle == LayoutStyle.Absolute);
-		v.Dispose ();
-	}
-
-	[Fact]
-	[TestRespondersDisposed]
-	public void AbsoluteLayout_LayoutSubviews ()
-	{
-		var superRect = new Rect (0, 0, 100, 100);
-		var super = new View (superRect, "super");
-		Assert.True (super.LayoutStyle == LayoutStyle.Absolute);
-		var v1 = new View {
-			X = 0,
-			Y = 0,
-			Width = 10,
-			Height = 10
-		};
-		Assert.True (v1.LayoutStyle == LayoutStyle.Absolute);
-
-		var v2 = new View {
-			X = 10,
-			Y = 10,
-			Width = 10,
-			Height = 10
-		};
-		Assert.True (v2.LayoutStyle == LayoutStyle.Absolute);
-
-		super.Add (v1, v2);
-		Assert.True (v1.LayoutStyle == LayoutStyle.Absolute);
-		Assert.True (v2.LayoutStyle == LayoutStyle.Absolute);
-
-		super.LayoutSubviews ();
-		Assert.Equal (new Rect (0,  0,  10, 10), v1.Frame);
-		Assert.Equal (new Rect (10, 10, 10, 10), v2.Frame);
-		super.Dispose ();
-	}
-
-	[Fact]
-	public void AbsoluteLayout_Setting_Bounds_Location_NotEmpty ()
-	{
-		// TODO: Should we enforce Bounds.X/Y == 0? The code currently ignores value.X/Y which is
-		// TODO: correct behavior, but is silent. Perhaps an exception?
-		var frame = new Rect (1, 2, 3, 4);
-		var newBounds = new Rect (10, 20, 30, 40);
-		var view = new View (frame);
-		view.Bounds = newBounds;
-		Assert.Equal (new Rect (0, 0, 30, 40), view.Bounds);
-		Assert.Equal (new Rect (1, 2, 30, 40), view.Frame);
-	}
-
-	[Fact]
-	public void AbsoluteLayout_Setting_Bounds_Sets_Frame ()
-	{
-		var frame = new Rect (1, 2, 3, 4);
-		var newBounds = new Rect (0, 0, 30, 40);
-
-		var v = new View (frame);
-		Assert.True (v.LayoutStyle == LayoutStyle.Absolute);
-
-		v.Bounds = newBounds;
-		Assert.True (v.LayoutStyle == LayoutStyle.Absolute);
-		Assert.Equal (newBounds, v.Bounds);
-		Assert.Equal (new Rect (1, 2, newBounds.Width, newBounds.Height), v.Frame);
-		Assert.Equal (new Rect (0, 0, newBounds.Width, newBounds.Height), v.Bounds);
-		Assert.Equal (Pos.At (1), v.X);
-		Assert.Equal (Pos.At (2), v.Y);
-		Assert.Equal (Dim.Sized (30), v.Width);
-		Assert.Equal (Dim.Sized (40), v.Height);
-
-		newBounds = new Rect (0, 0, 3, 4);
-		v.Bounds = newBounds;
-		Assert.Equal (newBounds, v.Bounds);
-		Assert.Equal (new Rect (1, 2, newBounds.Width, newBounds.Height), v.Frame);
-		Assert.Equal (new Rect (0, 0, newBounds.Width, newBounds.Height), v.Bounds);
-		Assert.Equal (Pos.At (1), v.X);
-		Assert.Equal (Pos.At (2), v.Y);
-		Assert.Equal (Dim.Sized (3), v.Width);
-		Assert.Equal (Dim.Sized (4), v.Height);
-
-		v.BorderStyle = LineStyle.Single;
-		// Bounds should shrink
-		Assert.Equal (new Rect (0, 0, 1, 2), v.Bounds);
-		// Frame should not change
-		Assert.Equal (new Rect (1, 2, 3, 4), v.Frame);
-		Assert.Equal (Pos.At (1), v.X);
-		Assert.Equal (Pos.At (2), v.Y);
-		Assert.Equal (Dim.Sized (3), v.Width);
-		Assert.Equal (Dim.Sized (4), v.Height);
-
-		// Now set bounds bigger as before
-		newBounds = new Rect (0, 0, 3, 4);
-		v.Bounds = newBounds;
-		Assert.Equal (newBounds, v.Bounds);
-		// Frame grows because there's now a border
-		Assert.Equal (new Rect (1, 2, 5, 6), v.Frame);
-		Assert.Equal (new Rect (0, 0, newBounds.Width, newBounds.Height), v.Bounds);
-		Assert.Equal (Pos.At (1), v.X);
-		Assert.Equal (Pos.At (2), v.Y);
-		Assert.Equal (Dim.Sized (5), v.Width);
-		Assert.Equal (Dim.Sized (6), v.Height);
-	}
-}
+public class AbsoluteLayoutTests
+{
+    private readonly ITestOutputHelper _output;
+
+    public AbsoluteLayoutTests (ITestOutputHelper output) { _output = output; }
+
+    [Fact]
+    [TestRespondersDisposed]
+    public void AbsoluteLayout_Change_Frame ()
+    {
+        var frame = new Rect (1, 2, 3, 4);
+        var newFrame = new Rect (1, 2, 30, 40);
+
+        var v = new View ();
+        Assert.True (v.LayoutStyle == LayoutStyle.Absolute);
+        v.Dispose ();
+
+        v = new View (frame);
+        Assert.True (v.LayoutStyle == LayoutStyle.Absolute);
+
+        v.Frame = newFrame;
+        Assert.True (v.LayoutStyle == LayoutStyle.Absolute);
+        Assert.Equal (newFrame, v.Frame);
+        Assert.Equal (new Rect (0, 0, newFrame.Width, newFrame.Height), v.Bounds); // With Absolute Bounds *is* deterministic before Layout
+        Assert.Equal (Pos.At (1), v.X);
+        Assert.Equal (Pos.At (2), v.Y);
+        Assert.Equal (Dim.Sized (30), v.Width);
+        Assert.Equal (Dim.Sized (40), v.Height);
+        v.Dispose ();
+
+        v = new View (frame.X, frame.Y, "v");
+        v.Frame = newFrame;
+        Assert.True (v.LayoutStyle == LayoutStyle.Absolute);
+        Assert.Equal (newFrame, v.Frame);
+        Assert.Equal (new Rect (0, 0, newFrame.Width, newFrame.Height), v.Bounds); // With Absolute Bounds *is* deterministic before Layout
+        Assert.Equal (Pos.At (1), v.X);
+        Assert.Equal (Pos.At (2), v.Y);
+        Assert.Equal (Dim.Sized (30), v.Width);
+        Assert.Equal (Dim.Sized (40), v.Height);
+        v.Dispose ();
+
+        newFrame = new Rect (10, 20, 30, 40);
+        v = new View (frame);
+        v.Frame = newFrame;
+        Assert.True (v.LayoutStyle == LayoutStyle.Absolute);
+        Assert.Equal (newFrame, v.Frame);
+        Assert.Equal (new Rect (0, 0, newFrame.Width, newFrame.Height), v.Bounds); // With Absolute Bounds *is* deterministic before Layout
+        Assert.Equal (Pos.At (10), v.X);
+        Assert.Equal (Pos.At (20), v.Y);
+        Assert.Equal (Dim.Sized (30), v.Width);
+        Assert.Equal (Dim.Sized (40), v.Height);
+        v.Dispose ();
+
+        v = new View (frame.X, frame.Y, "v");
+        v.Frame = newFrame;
+        Assert.True (v.LayoutStyle == LayoutStyle.Absolute);
+        Assert.Equal (newFrame, v.Frame);
+        Assert.Equal (new Rect (0, 0, newFrame.Width, newFrame.Height), v.Bounds); // With Absolute Bounds *is* deterministic before Layout
+        Assert.Equal (Pos.At (10), v.X);
+        Assert.Equal (Pos.At (20), v.Y);
+        Assert.Equal (Dim.Sized (30), v.Width);
+        Assert.Equal (Dim.Sized (40), v.Height);
+        v.Dispose ();
+    }
+
+    [Fact]
+    [TestRespondersDisposed]
+    public void AbsoluteLayout_Change_Height_or_Width_Absolute ()
+    {
+        var frame = new Rect (1, 2, 3, 4);
+        var newFrame = new Rect (1, 2, 30, 40);
+
+        var v = new View (frame);
+        v.Height = newFrame.Height;
+        v.Width = newFrame.Width;
+        Assert.True (v.LayoutStyle == LayoutStyle.Absolute);
+        Assert.Equal (newFrame, v.Frame);
+        Assert.Equal (new Rect (0, 0, newFrame.Width, newFrame.Height), v.Bounds); // With Absolute Bounds *is* deterministic before Layout
+        Assert.Equal (Pos.At (1), v.X);
+        Assert.Equal (Pos.At (2), v.Y);
+        Assert.Equal ($"Absolute({newFrame.Height})", v.Height.ToString ());
+        Assert.Equal ($"Absolute({newFrame.Width})", v.Width.ToString ());
+        v.Dispose ();
+    }
+
+    [Fact]
+    [TestRespondersDisposed]
+    public void AbsoluteLayout_Change_Height_or_Width_MakesComputed ()
+    {
+        var v = new View (Rect.Empty);
+        v.Height = Dim.Fill ();
+        v.Width = Dim.Fill ();
+        Assert.True (v.LayoutStyle == LayoutStyle.Computed);
+        v.Dispose ();
+    }
+
+    [Fact]
+    [TestRespondersDisposed]
+    public void AbsoluteLayout_Change_X_or_Y_Absolute ()
+    {
+        var frame = new Rect (1, 2, 3, 4);
+        var newFrame = new Rect (10, 20, 3, 4);
+
+        var v = new View (frame);
+        v.X = newFrame.X;
+        v.Y = newFrame.Y;
+        Assert.True (v.LayoutStyle == LayoutStyle.Absolute);
+        Assert.Equal (newFrame, v.Frame);
+        Assert.Equal (new Rect (0, 0, newFrame.Width, newFrame.Height), v.Bounds); // With Absolute Bounds *is* deterministic before Layout
+        Assert.Equal ($"Absolute({newFrame.X})", v.X.ToString ());
+        Assert.Equal ($"Absolute({newFrame.Y})", v.Y.ToString ());
+        Assert.Equal (Dim.Sized (3), v.Width);
+        Assert.Equal (Dim.Sized (4), v.Height);
+        v.Dispose ();
+    }
+
+    [Fact]
+    [TestRespondersDisposed]
+    public void AbsoluteLayout_Change_X_or_Y_MakesComputed ()
+    {
+        var v = new View (Rect.Empty);
+        v.X = Pos.Center ();
+        v.Y = Pos.Center ();
+        Assert.True (v.LayoutStyle == LayoutStyle.Computed);
+        v.Dispose ();
+    }
+
+    [Fact]
+    [TestRespondersDisposed]
+    public void AbsoluteLayout_Change_X_Y_Height_Width_Absolute ()
+    {
+        var v = new View (Rect.Empty);
+        v.X = 1;
+        v.Y = 2;
+        v.Height = 3;
+        v.Width = 4;
+        Assert.True (v.LayoutStyle == LayoutStyle.Absolute);
+        v.Dispose ();
+
+        v = new View (Rect.Empty);
+        v.X = Pos.Center ();
+        v.Y = Pos.Center ();
+        v.Width = Dim.Fill ();
+        v.Height = Dim.Fill ();
+        Assert.True (v.LayoutStyle == LayoutStyle.Computed);
+        v.Dispose ();
+
+        v = new View (Rect.Empty);
+        v.X = Pos.Center ();
+        v.Y = Pos.Center ();
+        v.Width = Dim.Fill ();
+        v.Height = Dim.Fill ();
+        Assert.True (v.LayoutStyle == LayoutStyle.Computed);
+
+        v.X = 1;
+        Assert.True (v.LayoutStyle == LayoutStyle.Computed);
+        v.Dispose ();
+
+        v = new View (Rect.Empty);
+        v.X = Pos.Center ();
+        v.Y = Pos.Center ();
+        v.Width = Dim.Fill ();
+        v.Height = Dim.Fill ();
+        Assert.True (v.LayoutStyle == LayoutStyle.Computed);
+
+        v.Y = 2;
+        Assert.True (v.LayoutStyle == LayoutStyle.Computed);
+        v.Dispose ();
+
+        v = new View (Rect.Empty);
+        v.X = Pos.Center ();
+        v.Y = Pos.Center ();
+        v.Width = Dim.Fill ();
+        v.Height = Dim.Fill ();
+        Assert.True (v.LayoutStyle == LayoutStyle.Computed);
+
+        v.Width = 3;
+        Assert.True (v.LayoutStyle == LayoutStyle.Computed);
+        v.Dispose ();
+
+        v = new View (Rect.Empty);
+        v.X = Pos.Center ();
+        v.Y = Pos.Center ();
+        v.Width = Dim.Fill ();
+        v.Height = Dim.Fill ();
+        Assert.True (v.LayoutStyle == LayoutStyle.Computed);
+
+        v.Height = 3;
+        Assert.True (v.LayoutStyle == LayoutStyle.Computed);
+        v.Dispose ();
+
+        v = new View (Rect.Empty);
+        v.X = Pos.Center ();
+        v.Y = Pos.Center ();
+        v.Width = Dim.Fill ();
+        v.Height = Dim.Fill ();
+        Assert.True (v.LayoutStyle == LayoutStyle.Computed);
+
+        v.X = 1;
+        v.Y = 2;
+        v.Height = 3;
+        v.Width = 4;
+        Assert.True (v.LayoutStyle == LayoutStyle.Absolute);
+        v.Dispose ();
+    }
+
+    [Fact]
+    [TestRespondersDisposed]
+    public void AbsoluteLayout_Constructor ()
+    {
+        var v = new View ();
+        Assert.True (v.LayoutStyle == LayoutStyle.Absolute);
+        v.Dispose ();
+
+        var frame = Rect.Empty;
+        v = new View (frame);
+        Assert.True (v.LayoutStyle == LayoutStyle.Absolute);
+        Assert.Equal (frame, v.Frame);
+        Assert.Equal (new Rect (0, 0, frame.Width, frame.Height), v.Bounds); // With Absolute Bounds *is* deterministic before Layout
+        Assert.Equal (Pos.At (0), v.X);
+        Assert.Equal (Pos.At (0), v.Y);
+        Assert.Equal (Dim.Sized (0), v.Width);
+        Assert.Equal (Dim.Sized (0), v.Height);
+        v.Dispose ();
+
+        frame = new Rect (1, 2, 3, 4);
+        v = new View (frame);
+        Assert.True (v.LayoutStyle == LayoutStyle.Absolute);
+        Assert.Equal (frame, v.Frame);
+        Assert.Equal (new Rect (0, 0, frame.Width, frame.Height), v.Bounds); // With Absolute Bounds *is* deterministic before Layout
+        Assert.Equal (Pos.At (1), v.X);
+        Assert.Equal (Pos.At (2), v.Y);
+        Assert.Equal (Dim.Sized (3), v.Width);
+        Assert.Equal (Dim.Sized (4), v.Height);
+        v.Dispose ();
+
+        v = new View (frame, "v");
+        Assert.True (v.LayoutStyle == LayoutStyle.Absolute);
+        Assert.Equal (frame, v.Frame);
+        Assert.Equal (new Rect (0, 0, frame.Width, frame.Height), v.Bounds); // With Absolute Bounds *is* deterministic before Layout
+        Assert.Equal (Pos.At (1), v.X);
+        Assert.Equal (Pos.At (2), v.Y);
+        Assert.Equal (Dim.Sized (3), v.Width);
+        Assert.Equal (Dim.Sized (4), v.Height);
+        v.Dispose ();
+
+        v = new View (frame.X, frame.Y, "v");
+        Assert.True (v.LayoutStyle == LayoutStyle.Absolute);
+
+        // BUGBUG: v2 - I think the default size should be 0,0 not 1,1
+        Assert.Equal (new Rect (frame.X, frame.Y, 1, 1), v.Frame);
+        Assert.Equal (new Rect (0, 0, 1, 1), v.Bounds); // With Absolute Bounds *is* deterministic before Layout
+        Assert.Equal (Pos.At (1), v.X);
+        Assert.Equal (Pos.At (2), v.Y);
+        Assert.Equal (Dim.Sized (1), v.Width);
+        Assert.Equal (Dim.Sized (1), v.Height);
+        v.Dispose ();
+
+        v = new View ();
+        Assert.True (v.LayoutStyle == LayoutStyle.Absolute);
+        Assert.Equal (new Rect (0, 0, 0, 0), v.Frame);
+        Assert.Equal (new Rect (0, 0, 0, 0), v.Bounds); // With Absolute Bounds *is* deterministic before Layout
+        Assert.Equal (Pos.At (0), v.X);
+        Assert.Equal (Pos.At (0), v.Y);
+        Assert.Equal (Dim.Sized (0), v.Width);
+        Assert.Equal (Dim.Sized (0), v.Height);
+        v.Dispose ();
+
+        v = new View
+        {
+            X = frame.X,
+            Y = frame.Y,
+            Width = frame.Width,
+            Height = frame.Height
+        };
+        Assert.True (v.LayoutStyle == LayoutStyle.Absolute);
+        Assert.Equal (new Rect (frame.X, frame.Y, 3, 4), v.Frame);
+        Assert.Equal (new Rect (0, 0, 3, 4), v.Bounds); // With Absolute Bounds *is* deterministic before Layout
+        Assert.Equal (Pos.At (1), v.X);
+        Assert.Equal (Pos.At (2), v.Y);
+        Assert.Equal (Dim.Sized (3), v.Width);
+        Assert.Equal (Dim.Sized (4), v.Height);
+        v.Dispose ();
+    }
+
+    [Fact]
+    [TestRespondersDisposed]
+    public void AbsoluteLayout_LayoutSubviews ()
+    {
+        var superRect = new Rect (0, 0, 100, 100);
+        var super = new View (superRect, "super");
+        Assert.True (super.LayoutStyle == LayoutStyle.Absolute);
+
+        var v1 = new View
+        {
+            X = 0,
+            Y = 0,
+            Width = 10,
+            Height = 10
+        };
+        Assert.True (v1.LayoutStyle == LayoutStyle.Absolute);
+
+        var v2 = new View
+        {
+            X = 10,
+            Y = 10,
+            Width = 10,
+            Height = 10
+        };
+        Assert.True (v2.LayoutStyle == LayoutStyle.Absolute);
+
+        super.Add (v1, v2);
+        Assert.True (v1.LayoutStyle == LayoutStyle.Absolute);
+        Assert.True (v2.LayoutStyle == LayoutStyle.Absolute);
+
+        super.LayoutSubviews ();
+        Assert.Equal (new Rect (0, 0, 10, 10), v1.Frame);
+        Assert.Equal (new Rect (10, 10, 10, 10), v2.Frame);
+        super.Dispose ();
+    }
+
+    [Fact]
+    public void AbsoluteLayout_Setting_Bounds_Location_NotEmpty ()
+    {
+        // TODO: Should we enforce Bounds.X/Y == 0? The code currently ignores value.X/Y which is
+        // TODO: correct behavior, but is silent. Perhaps an exception?
+        var frame = new Rect (1, 2, 3, 4);
+        var newBounds = new Rect (10, 20, 30, 40);
+        var view = new View (frame);
+        view.Bounds = newBounds;
+        Assert.Equal (new Rect (0, 0, 30, 40), view.Bounds);
+        Assert.Equal (new Rect (1, 2, 30, 40), view.Frame);
+    }
+
+    [Fact]
+    public void AbsoluteLayout_Setting_Bounds_Sets_Frame ()
+    {
+        var frame = new Rect (1, 2, 3, 4);
+        var newBounds = new Rect (0, 0, 30, 40);
+
+        var v = new View (frame);
+        Assert.True (v.LayoutStyle == LayoutStyle.Absolute);
+
+        v.Bounds = newBounds;
+        Assert.True (v.LayoutStyle == LayoutStyle.Absolute);
+        Assert.Equal (newBounds, v.Bounds);
+        Assert.Equal (new Rect (1, 2, newBounds.Width, newBounds.Height), v.Frame);
+        Assert.Equal (new Rect (0, 0, newBounds.Width, newBounds.Height), v.Bounds);
+        Assert.Equal (Pos.At (1), v.X);
+        Assert.Equal (Pos.At (2), v.Y);
+        Assert.Equal (Dim.Sized (30), v.Width);
+        Assert.Equal (Dim.Sized (40), v.Height);
+
+        newBounds = new Rect (0, 0, 3, 4);
+        v.Bounds = newBounds;
+        Assert.Equal (newBounds, v.Bounds);
+        Assert.Equal (new Rect (1, 2, newBounds.Width, newBounds.Height), v.Frame);
+        Assert.Equal (new Rect (0, 0, newBounds.Width, newBounds.Height), v.Bounds);
+        Assert.Equal (Pos.At (1), v.X);
+        Assert.Equal (Pos.At (2), v.Y);
+        Assert.Equal (Dim.Sized (3), v.Width);
+        Assert.Equal (Dim.Sized (4), v.Height);
+
+        v.BorderStyle = LineStyle.Single;
+
+        // Bounds should shrink
+        Assert.Equal (new Rect (0, 0, 1, 2), v.Bounds);
+
+        // Frame should not change
+        Assert.Equal (new Rect (1, 2, 3, 4), v.Frame);
+        Assert.Equal (Pos.At (1), v.X);
+        Assert.Equal (Pos.At (2), v.Y);
+        Assert.Equal (Dim.Sized (3), v.Width);
+        Assert.Equal (Dim.Sized (4), v.Height);
+
+        // Now set bounds bigger as before
+        newBounds = new Rect (0, 0, 3, 4);
+        v.Bounds = newBounds;
+        Assert.Equal (newBounds, v.Bounds);
+
+        // Frame grows because there's now a border
+        Assert.Equal (new Rect (1, 2, 5, 6), v.Frame);
+        Assert.Equal (new Rect (0, 0, newBounds.Width, newBounds.Height), v.Bounds);
+        Assert.Equal (Pos.At (1), v.X);
+        Assert.Equal (Pos.At (2), v.Y);
+        Assert.Equal (Dim.Sized (5), v.Width);
+        Assert.Equal (Dim.Sized (6), v.Height);
+    }
+}

+ 472 - 459
UnitTests/View/Layout/DimAutoTests.cs

@@ -1,7 +1,5 @@
-using System;
-using System.Globalization;
-using System.Threading;
-using Xunit;
+using System.Globalization;
+using System.Text;
 using Xunit.Abstractions;
 
 // Alias Console to MockConsole so we don't accidentally use Console
@@ -9,458 +7,473 @@ using Console = Terminal.Gui.FakeConsole;
 
 namespace Terminal.Gui.ViewTests;
 
-public class DimAutoTests {
-	readonly ITestOutputHelper _output;
-
-	public DimAutoTests (ITestOutputHelper output)
-	{
-		_output = output;
-		Console.OutputEncoding = System.Text.Encoding.Default;
-		// Change current culture
-		var culture = CultureInfo.CreateSpecificCulture ("en-US");
-		Thread.CurrentThread.CurrentCulture = culture;
-		Thread.CurrentThread.CurrentUICulture = culture;
-	}
-
-	[Fact]
-	public void NoSubViews_Does_Nothing ()
-	{
-		var superView = new View () {
-			X = 0,
-			Y = 0,
-			Width = Dim.Auto (),
-			Height = Dim.Auto (),
-			ValidatePosDim = true,
-		};
-
-		superView.BeginInit ();
-		superView.EndInit ();
-		superView.SetRelativeLayout (new Rect (0, 0, 0, 0));
-		Assert.Equal (new Rect (0, 0, 0, 0), superView.Frame);
-
-		superView.SetRelativeLayout (new Rect (0, 0, 10, 10));
-		Assert.Equal (new Rect (0, 0, 0, 0), superView.Frame);
-
-		superView.SetRelativeLayout (new Rect (10, 10, 10, 10));
-		Assert.Equal (new Rect (0, 0, 0, 0), superView.Frame);
-	}
-	
-	[Theory]
-	[InlineData (0, 0, 0, 0, 0, 0)]
-	[InlineData (0, 0, 5, 0, 5, 0)]
-	[InlineData (0, 0, 0, 5, 0, 5)]
-	[InlineData (0, 0, 5, 5, 5, 5)]
-
-	[InlineData (1, 0, 5, 0, 6, 0)]
-	[InlineData (1, 0, 0, 5, 1, 5)]
-	[InlineData (1, 0, 5, 5, 6, 5)]
-	[InlineData (1, 1, 5, 5, 6, 6)]
-
-	[InlineData (-1, 0, 5, 0, 4, 0)]
-	[InlineData (-1, 0, 0, 5, 0, 5)]
-	[InlineData (-1, 0, 5, 5, 4, 5)]
-	[InlineData (-1, -1, 5, 5, 4, 4)]
-	public void SubView_ChangesSuperViewSize (int subX, int subY, int subWidth, int subHeight, int expectedWidth, int expectedHeight)
-	{
-		var superView = new View () {
-			X = 0,
-			Y = 0,
-			Width = Dim.Auto (),
-			Height = Dim.Auto (),
-			ValidatePosDim = true,
-		};
-
-		var subView = new View () {
-			X = subX,
-			Y = subY,
-			Width = subWidth,
-			Height = subHeight,
-			ValidatePosDim = true,
-		};
-
-		superView.Add (subView);
-
-		superView.BeginInit ();
-		superView.EndInit ();
-		superView.SetRelativeLayout (new Rect (0, 0, 0, 0));
-		Assert.Equal (new Rect (0, 0, expectedWidth, expectedHeight), superView.Frame);
-	}
-
-	[Theory]
-	[InlineData (0, 0, 0, 0, 0)]
-	[InlineData (0, 0, 5, 0, 5)]
-	[InlineData (0, 0, 0, 5, 0)]
-	[InlineData (0, 0, 5, 5, 5)]
-
-	[InlineData (1, 0, 5, 0, 6)]
-	[InlineData (1, 0, 0, 5, 1)]
-	[InlineData (1, 0, 5, 5, 6)]
-	[InlineData (1, 1, 5, 5, 6)]
-
-	[InlineData (-1, 0, 5, 0, 4)]
-	[InlineData (-1, 0, 0, 5, 0)]
-	[InlineData (-1, 0, 5, 5, 4)]
-	[InlineData (-1, -1, 5, 5, 4)]
-	public void Width_Auto_Height_NotChanged (int subX, int subY, int subWidth, int subHeight, int expectedWidth)
-	{
-		var superView = new View () {
-			X = 0,
-			Y = 0,
-			Width = Dim.Auto (),
-			Height = 10,
-			ValidatePosDim = true,
-		};
-
-		var subView = new View () {
-			X = subX,
-			Y = subY,
-			Width = subWidth,
-			Height = subHeight,
-			ValidatePosDim = true,
-		};
-
-		superView.Add (subView);
-
-		superView.BeginInit ();
-		superView.EndInit ();
-		superView.SetRelativeLayout (new Rect (0, 0, 0, 0));
-		Assert.Equal (new Rect (0, 0, expectedWidth, 10), superView.Frame);
-	}
-
-	[Theory]
-	[InlineData (0, 0, 0, 0, 0)]
-	[InlineData (0, 0, 5, 0, 0)]
-	[InlineData (0, 0, 0, 5, 5)]
-	[InlineData (0, 0, 5, 5, 5)]
-
-	[InlineData (1, 0, 5, 0, 0)]
-	[InlineData (1, 0, 0, 5, 5)]
-	[InlineData (1, 0, 5, 5, 5)]
-	[InlineData (1, 1, 5, 5, 6)]
-
-	[InlineData (-1, 0, 5, 0, 0)]
-	[InlineData (-1, 0, 0, 5, 5)]
-	[InlineData (-1, 0, 5, 5, 5)]
-	[InlineData (-1, -1, 5, 5, 4)]
-	public void Height_Auto_Width_NotChanged (int subX, int subY, int subWidth, int subHeight, int expectedHeight)
-	{
-		var superView = new View () {
-			X = 0,
-			Y = 0,
-			Width = 10,
-			Height = Dim.Auto (),
-			ValidatePosDim = true,
-		};
-
-		var subView = new View () {
-			X = subX,
-			Y = subY,
-			Width = subWidth,
-			Height = subHeight,
-			ValidatePosDim = true,
-		};
-
-		superView.Add (subView);
-
-		superView.BeginInit ();
-		superView.EndInit ();
-		superView.SetRelativeLayout (new Rect (0, 0, 0, 0));
-		Assert.Equal (new Rect (0, 0, 10, expectedHeight), superView.Frame);
-	}
-
-	// Test validation
-	[Fact]
-	public void ValidatePosDim_True_Throws_When_SubView_Uses_SuperView_Dims ()
-	{
-		var superView = new View () {
-			X = 0,
-			Y = 0,
-			Width = Dim.Auto (),
-			Height = Dim.Auto (),
-			ValidatePosDim = true,
-		};
-
-		var subView = new View () {
-			X = 0,
-			Y = 0,
-			Width = Dim.Fill (),
-			Height = 10,
-			ValidatePosDim = true,
-		};
-
-		superView.BeginInit ();
-		superView.EndInit ();
-
-		Assert.Throws<InvalidOperationException> (() => superView.Add (subView));
-
-		subView.Width = 10;
-		superView.Add (subView);
-		superView.BeginInit ();
-		superView.EndInit ();
-		superView.SetRelativeLayout (new Rect (0, 0, 0, 0));
-		superView.LayoutSubviews (); // no throw
-
-		subView.Width = Dim.Fill ();
-		Assert.Throws<InvalidOperationException> (() => superView.SetRelativeLayout (new Rect (0, 0, 0, 0)));
-		subView.Width = 10;
-
-		subView.Height = Dim.Fill ();
-		Assert.Throws<InvalidOperationException> (() => superView.SetRelativeLayout (new Rect (0, 0, 0, 0)));
-		subView.Height = 10;
-
-		subView.Height = Dim.Percent (50);
-		Assert.Throws<InvalidOperationException> (() => superView.SetRelativeLayout (new Rect (0, 0, 0, 0)));
-		subView.Height = 10;
-		
-		subView.X = Pos.Center ();
-		Assert.Throws<InvalidOperationException> (() => superView.SetRelativeLayout (new Rect (0, 0, 0, 0)));
-		subView.X = 0;
-
-		subView.Y = Pos.Center ();
-		Assert.Throws<InvalidOperationException> (() => superView.SetRelativeLayout (new Rect (0, 0, 0, 0)));
-		subView.Y = 0;
-
-		subView.Width = 10;
-		subView.Height = 10;
-		subView.X = 0;
-		subView.Y = 0;
-		superView.SetRelativeLayout (new Rect (0, 0, 0, 0));
-		superView.LayoutSubviews ();
-	}
-
-	// Test validation
-	[Fact]
-	public void ValidatePosDim_True_Throws_When_SubView_Uses_SuperView_Dims_Combine ()
-	{
-		var superView = new View () {
-			X = 0,
-			Y = 0,
-			Width = Dim.Auto (),
-			Height = Dim.Auto (),
-			ValidatePosDim = true,
-		};
-
-		var subView = new View () {
-			X = 0,
-			Y = 0,
-			Width = 10,
-			Height = 10
-		};
-
-
-		var subView2 = new View () {
-			X = 0,
-			Y = 0,
-			Width = 10,
-			Height = 10
-		};
-
-		superView.Add (subView, subView2);
-		superView.BeginInit ();
-		superView.EndInit ();
-		superView.SetRelativeLayout (new Rect (0, 0, 0, 0));
-		superView.LayoutSubviews (); // no throw
-
-		subView.Height = Dim.Fill () + 3;
-		Assert.Throws<InvalidOperationException> (() => superView.SetRelativeLayout (new Rect (0, 0, 0, 0)));
-		subView.Height = 0;
-
-		subView.Height = 3 + Dim.Fill ();
-		Assert.Throws<InvalidOperationException> (() => superView.SetRelativeLayout (new Rect (0, 0, 0, 0)));
-		subView.Height = 0;
-
-		subView.Height = 3 + 5 + Dim.Fill ();
-		Assert.Throws<InvalidOperationException> (() => superView.SetRelativeLayout (new Rect (0, 0, 0, 0)));
-		subView.Height = 0;
-
-		subView.Height = 3 + 5 + Dim.Percent (10);
-		Assert.Throws<InvalidOperationException> (() => superView.SetRelativeLayout (new Rect (0, 0, 0, 0)));
-		subView.Height = 0;
-
-		// Tests nested Combine
-		subView.Height = 5 + new Dim.DimCombine (true, 3, new Dim.DimCombine (true, Dim.Percent (10), 9));
-		Assert.Throws<InvalidOperationException> (() => superView.SetRelativeLayout (new Rect (0, 0, 0, 0)));
-	}
-
-	[Fact]
-	public void ValidatePosDim_True_Throws_When_SubView_Uses_SuperView_Pos_Combine ()
-	{
-		var superView = new View () {
-			X = 0,
-			Y = 0,
-			Width = Dim.Auto (),
-			Height = Dim.Auto (),
-			ValidatePosDim = true,
-		};
-
-		var subView = new View () {
-			X = 0,
-			Y = 0,
-			Width = 10,
-			Height = 10
-		};
-
-		var subView2 = new View () {
-			X = 0,
-			Y = 0,
-			Width = 10,
-			Height = 10
-		};
-
-		superView.Add (subView, subView2);
-		superView.BeginInit ();
-		superView.EndInit ();
-		superView.SetRelativeLayout (new Rect (0, 0, 0, 0));
-		superView.LayoutSubviews (); // no throw
-
-		subView.X = Pos.Right (subView2);
-		superView.SetRelativeLayout (new Rect (0, 0, 0, 0));
-		superView.LayoutSubviews (); // no throw
-
-		subView.X = Pos.Right (subView2) + 3;
-		superView.SetRelativeLayout (new Rect (0, 0, 0, 0)); // no throw
-		superView.LayoutSubviews (); // no throw
-
-		subView.X = new Pos.PosCombine (true, Pos.Right (subView2), new Pos.PosCombine (true, 7, 9));
-		superView.SetRelativeLayout (new Rect (0, 0, 0, 0)); // no throw
-
-		subView.X = Pos.Center () + 3;
-		Assert.Throws<InvalidOperationException> (() => superView.SetRelativeLayout (new Rect (0, 0, 0, 0)));
-		subView.X = 0;
-		
-		subView.X = 3 + Pos.Center ();
-		Assert.Throws<InvalidOperationException> (() => superView.SetRelativeLayout (new Rect (0, 0, 0, 0)));
-		subView.X = 0;
-
-		subView.X = 3 + 5 + Pos.Center ();
-		Assert.Throws<InvalidOperationException> (() => superView.SetRelativeLayout (new Rect (0, 0, 0, 0)));
-		subView.X = 0;
-
-		subView.X = 3 + 5 + Pos.Percent (10);
-		Assert.Throws<InvalidOperationException> (() => superView.SetRelativeLayout (new Rect (0, 0, 0, 0)));
-		subView.X = 0;
-
-		subView.X = Pos.Percent (10) + Pos.Center ();
-		Assert.Throws<InvalidOperationException> (() => superView.SetRelativeLayout (new Rect (0, 0, 0, 0)));
-		subView.X = 0;
-
-		// Tests nested Combine
-		subView.X = 5 + new Pos.PosCombine (true, Pos.Right (subView2), new Pos.PosCombine (true, Pos.Center (), 9));
-		Assert.Throws<InvalidOperationException> (() => superView.SetRelativeLayout (new Rect (0, 0, 0, 0)));
-		subView.X = 0;
-	}
-
-	// Test min - ensure that if min is specified in the DimAuto constructor it is honored
-	[Fact]
-	public void DimAuto_Min ()
-	{
-		var superView = new View () {
-			X = 0,
-			Y = 0,
-			Width = Dim.Auto (min: 10),
-			Height = Dim.Auto (min: 10),
-			ValidatePosDim = true,
-		};
-
-		var subView = new View () {
-			X = 0,
-			Y = 0,
-			Width = 5,
-			Height = 5
-		};
-
-		superView.Add (subView);
-		superView.BeginInit ();
-		superView.EndInit ();
-
-		superView.SetRelativeLayout (new Rect (0, 0, 0, 0));
-		superView.LayoutSubviews (); // no throw
-
-		Assert.Equal (10, superView.Frame.Width);
-		Assert.Equal (10, superView.Frame.Height);
-	}
-
-	[Fact]
-	public void DimAuto_Min_Resets_If_Subview_Shrinks ()
-	{
-		var superView = new View () {
-			X = 0,
-			Y = 0,
-			Width = Dim.Auto (min: 10),
-			Height = Dim.Auto (min: 10),
-			ValidatePosDim = true,
-		};
-
-		var subView = new View () {
-			X = 0,
-			Y = 0,
-			Width = 5,
-			Height = 5
-		};
-
-		superView.Add (subView);
-		superView.BeginInit ();
-		superView.EndInit ();
-
-		superView.SetRelativeLayout (new Rect (0, 0, 0, 0));
-		superView.LayoutSubviews (); // no throw
-
-		Assert.Equal (10, superView.Frame.Width);
-		Assert.Equal (10, superView.Frame.Height);
-
-		subView.Width = 3;
-		subView.Height = 3;
-		superView.SetRelativeLayout (new Rect (0, 0, 0, 0));
-		superView.LayoutSubviews (); // no throw
-
-		Assert.Equal (3, subView.Frame.Width);
-		Assert.Equal (3, subView.Frame.Height);
-
-		Assert.Equal (10, superView.Frame.Width);
-		Assert.Equal (10, superView.Frame.Height);
-	}
-
-	// what happens if DimAuto (min: 10) and the subview moves to a negative coord?
-	[Fact]
-	public void DimAuto_Min_Resets_If_Subview_Moves_Negative ()
-	{
-		var superView = new View () {
-			X = 0,
-			Y = 0,
-			Width = Dim.Auto (min: 10),
-			Height = Dim.Auto (min: 10),
-			ValidatePosDim = true,
-		};
-
-		var subView = new View () {
-			X = 0,
-			Y = 0,
-			Width = 5,
-			Height = 5
-		};
-
-		superView.Add (subView);
-		superView.BeginInit ();
-		superView.EndInit ();
-
-		superView.SetRelativeLayout (new Rect (0, 0, 0, 0));
-		superView.LayoutSubviews (); // no throw
-
-		Assert.Equal (10, superView.Frame.Width);
-		Assert.Equal (10, superView.Frame.Height);
-
-		subView.X = -1;
-		subView.Y = -1;
-		superView.SetRelativeLayout (new Rect (0, 0, 0, 0));
-		superView.LayoutSubviews (); // no throw
-
-		Assert.Equal (5, subView.Frame.Width);
-		Assert.Equal (5, subView.Frame.Height);
-
-		Assert.Equal (10, superView.Frame.Width);
-		Assert.Equal (10, superView.Frame.Height);
-	}
-
-	// Test variations of Frame
-
-}
+public class DimAutoTests
+{
+    private readonly ITestOutputHelper _output;
+
+    public DimAutoTests (ITestOutputHelper output)
+    {
+        _output = output;
+        Console.OutputEncoding = Encoding.Default;
+
+        // Change current culture
+        var culture = CultureInfo.CreateSpecificCulture ("en-US");
+        Thread.CurrentThread.CurrentCulture = culture;
+        Thread.CurrentThread.CurrentUICulture = culture;
+    }
+
+    // Test min - ensure that if min is specified in the DimAuto constructor it is honored
+    [Fact]
+    public void DimAuto_Min ()
+    {
+        var superView = new View
+        {
+            X = 0,
+            Y = 0,
+            Width = Dim.Auto (min: 10),
+            Height = Dim.Auto (min: 10),
+            ValidatePosDim = true
+        };
+
+        var subView = new View
+        {
+            X = 0,
+            Y = 0,
+            Width = 5,
+            Height = 5
+        };
+
+        superView.Add (subView);
+        superView.BeginInit ();
+        superView.EndInit ();
+
+        superView.SetRelativeLayout (new Rect (0, 0, 0, 0));
+        superView.LayoutSubviews (); // no throw
+
+        Assert.Equal (10, superView.Frame.Width);
+        Assert.Equal (10, superView.Frame.Height);
+    }
+
+    // what happens if DimAuto (min: 10) and the subview moves to a negative coord?
+    [Fact]
+    public void DimAuto_Min_Resets_If_Subview_Moves_Negative ()
+    {
+        var superView = new View
+        {
+            X = 0,
+            Y = 0,
+            Width = Dim.Auto (min: 10),
+            Height = Dim.Auto (min: 10),
+            ValidatePosDim = true
+        };
+
+        var subView = new View
+        {
+            X = 0,
+            Y = 0,
+            Width = 5,
+            Height = 5
+        };
+
+        superView.Add (subView);
+        superView.BeginInit ();
+        superView.EndInit ();
+
+        superView.SetRelativeLayout (new Rect (0, 0, 0, 0));
+        superView.LayoutSubviews (); // no throw
+
+        Assert.Equal (10, superView.Frame.Width);
+        Assert.Equal (10, superView.Frame.Height);
+
+        subView.X = -1;
+        subView.Y = -1;
+        superView.SetRelativeLayout (new Rect (0, 0, 0, 0));
+        superView.LayoutSubviews (); // no throw
+
+        Assert.Equal (5, subView.Frame.Width);
+        Assert.Equal (5, subView.Frame.Height);
+
+        Assert.Equal (10, superView.Frame.Width);
+        Assert.Equal (10, superView.Frame.Height);
+    }
+
+    [Fact]
+    public void DimAuto_Min_Resets_If_Subview_Shrinks ()
+    {
+        var superView = new View
+        {
+            X = 0,
+            Y = 0,
+            Width = Dim.Auto (min: 10),
+            Height = Dim.Auto (min: 10),
+            ValidatePosDim = true
+        };
+
+        var subView = new View
+        {
+            X = 0,
+            Y = 0,
+            Width = 5,
+            Height = 5
+        };
+
+        superView.Add (subView);
+        superView.BeginInit ();
+        superView.EndInit ();
+
+        superView.SetRelativeLayout (new Rect (0, 0, 0, 0));
+        superView.LayoutSubviews (); // no throw
+
+        Assert.Equal (10, superView.Frame.Width);
+        Assert.Equal (10, superView.Frame.Height);
+
+        subView.Width = 3;
+        subView.Height = 3;
+        superView.SetRelativeLayout (new Rect (0, 0, 0, 0));
+        superView.LayoutSubviews (); // no throw
+
+        Assert.Equal (3, subView.Frame.Width);
+        Assert.Equal (3, subView.Frame.Height);
+
+        Assert.Equal (10, superView.Frame.Width);
+        Assert.Equal (10, superView.Frame.Height);
+    }
+
+    [Theory]
+    [InlineData (0, 0, 0, 0, 0)]
+    [InlineData (0, 0, 5, 0, 0)]
+    [InlineData (0, 0, 0, 5, 5)]
+    [InlineData (0, 0, 5, 5, 5)]
+    [InlineData (1, 0, 5, 0, 0)]
+    [InlineData (1, 0, 0, 5, 5)]
+    [InlineData (1, 0, 5, 5, 5)]
+    [InlineData (1, 1, 5, 5, 6)]
+    [InlineData (-1, 0, 5, 0, 0)]
+    [InlineData (-1, 0, 0, 5, 5)]
+    [InlineData (-1, 0, 5, 5, 5)]
+    [InlineData (-1, -1, 5, 5, 4)]
+    public void Height_Auto_Width_NotChanged (int subX, int subY, int subWidth, int subHeight, int expectedHeight)
+    {
+        var superView = new View
+        {
+            X = 0,
+            Y = 0,
+            Width = 10,
+            Height = Dim.Auto (),
+            ValidatePosDim = true
+        };
+
+        var subView = new View
+        {
+            X = subX,
+            Y = subY,
+            Width = subWidth,
+            Height = subHeight,
+            ValidatePosDim = true
+        };
+
+        superView.Add (subView);
+
+        superView.BeginInit ();
+        superView.EndInit ();
+        superView.SetRelativeLayout (new Rect (0, 0, 0, 0));
+        Assert.Equal (new Rect (0, 0, 10, expectedHeight), superView.Frame);
+    }
+
+    [Fact]
+    public void NoSubViews_Does_Nothing ()
+    {
+        var superView = new View
+        {
+            X = 0,
+            Y = 0,
+            Width = Dim.Auto (),
+            Height = Dim.Auto (),
+            ValidatePosDim = true
+        };
+
+        superView.BeginInit ();
+        superView.EndInit ();
+        superView.SetRelativeLayout (new Rect (0, 0, 0, 0));
+        Assert.Equal (new Rect (0, 0, 0, 0), superView.Frame);
+
+        superView.SetRelativeLayout (new Rect (0, 0, 10, 10));
+        Assert.Equal (new Rect (0, 0, 0, 0), superView.Frame);
+
+        superView.SetRelativeLayout (new Rect (10, 10, 10, 10));
+        Assert.Equal (new Rect (0, 0, 0, 0), superView.Frame);
+    }
+
+    [Theory]
+    [InlineData (0, 0, 0, 0, 0, 0)]
+    [InlineData (0, 0, 5, 0, 5, 0)]
+    [InlineData (0, 0, 0, 5, 0, 5)]
+    [InlineData (0, 0, 5, 5, 5, 5)]
+    [InlineData (1, 0, 5, 0, 6, 0)]
+    [InlineData (1, 0, 0, 5, 1, 5)]
+    [InlineData (1, 0, 5, 5, 6, 5)]
+    [InlineData (1, 1, 5, 5, 6, 6)]
+    [InlineData (-1, 0, 5, 0, 4, 0)]
+    [InlineData (-1, 0, 0, 5, 0, 5)]
+    [InlineData (-1, 0, 5, 5, 4, 5)]
+    [InlineData (-1, -1, 5, 5, 4, 4)]
+    public void SubView_ChangesSuperViewSize (int subX, int subY, int subWidth, int subHeight, int expectedWidth, int expectedHeight)
+    {
+        var superView = new View
+        {
+            X = 0,
+            Y = 0,
+            Width = Dim.Auto (),
+            Height = Dim.Auto (),
+            ValidatePosDim = true
+        };
+
+        var subView = new View
+        {
+            X = subX,
+            Y = subY,
+            Width = subWidth,
+            Height = subHeight,
+            ValidatePosDim = true
+        };
+
+        superView.Add (subView);
+
+        superView.BeginInit ();
+        superView.EndInit ();
+        superView.SetRelativeLayout (new Rect (0, 0, 0, 0));
+        Assert.Equal (new Rect (0, 0, expectedWidth, expectedHeight), superView.Frame);
+    }
+
+    // Test validation
+    [Fact]
+    public void ValidatePosDim_True_Throws_When_SubView_Uses_SuperView_Dims ()
+    {
+        var superView = new View
+        {
+            X = 0,
+            Y = 0,
+            Width = Dim.Auto (),
+            Height = Dim.Auto (),
+            ValidatePosDim = true
+        };
+
+        var subView = new View
+        {
+            X = 0,
+            Y = 0,
+            Width = Dim.Fill (),
+            Height = 10,
+            ValidatePosDim = true
+        };
+
+        superView.BeginInit ();
+        superView.EndInit ();
+
+        Assert.Throws<InvalidOperationException> (() => superView.Add (subView));
+
+        subView.Width = 10;
+        superView.Add (subView);
+        superView.BeginInit ();
+        superView.EndInit ();
+        superView.SetRelativeLayout (new Rect (0, 0, 0, 0));
+        superView.LayoutSubviews (); // no throw
+
+        subView.Width = Dim.Fill ();
+        Assert.Throws<InvalidOperationException> (() => superView.SetRelativeLayout (new Rect (0, 0, 0, 0)));
+        subView.Width = 10;
+
+        subView.Height = Dim.Fill ();
+        Assert.Throws<InvalidOperationException> (() => superView.SetRelativeLayout (new Rect (0, 0, 0, 0)));
+        subView.Height = 10;
+
+        subView.Height = Dim.Percent (50);
+        Assert.Throws<InvalidOperationException> (() => superView.SetRelativeLayout (new Rect (0, 0, 0, 0)));
+        subView.Height = 10;
+
+        subView.X = Pos.Center ();
+        Assert.Throws<InvalidOperationException> (() => superView.SetRelativeLayout (new Rect (0, 0, 0, 0)));
+        subView.X = 0;
+
+        subView.Y = Pos.Center ();
+        Assert.Throws<InvalidOperationException> (() => superView.SetRelativeLayout (new Rect (0, 0, 0, 0)));
+        subView.Y = 0;
+
+        subView.Width = 10;
+        subView.Height = 10;
+        subView.X = 0;
+        subView.Y = 0;
+        superView.SetRelativeLayout (new Rect (0, 0, 0, 0));
+        superView.LayoutSubviews ();
+    }
+
+    // Test validation
+    [Fact]
+    public void ValidatePosDim_True_Throws_When_SubView_Uses_SuperView_Dims_Combine ()
+    {
+        var superView = new View
+        {
+            X = 0,
+            Y = 0,
+            Width = Dim.Auto (),
+            Height = Dim.Auto (),
+            ValidatePosDim = true
+        };
+
+        var subView = new View
+        {
+            X = 0,
+            Y = 0,
+            Width = 10,
+            Height = 10
+        };
+
+        var subView2 = new View
+        {
+            X = 0,
+            Y = 0,
+            Width = 10,
+            Height = 10
+        };
+
+        superView.Add (subView, subView2);
+        superView.BeginInit ();
+        superView.EndInit ();
+        superView.SetRelativeLayout (new Rect (0, 0, 0, 0));
+        superView.LayoutSubviews (); // no throw
+
+        subView.Height = Dim.Fill () + 3;
+        Assert.Throws<InvalidOperationException> (() => superView.SetRelativeLayout (new Rect (0, 0, 0, 0)));
+        subView.Height = 0;
+
+        subView.Height = 3 + Dim.Fill ();
+        Assert.Throws<InvalidOperationException> (() => superView.SetRelativeLayout (new Rect (0, 0, 0, 0)));
+        subView.Height = 0;
+
+        subView.Height = 3 + 5 + Dim.Fill ();
+        Assert.Throws<InvalidOperationException> (() => superView.SetRelativeLayout (new Rect (0, 0, 0, 0)));
+        subView.Height = 0;
+
+        subView.Height = 3 + 5 + Dim.Percent (10);
+        Assert.Throws<InvalidOperationException> (() => superView.SetRelativeLayout (new Rect (0, 0, 0, 0)));
+        subView.Height = 0;
+
+        // Tests nested Combine
+        subView.Height = 5 + new Dim.DimCombine (true, 3, new Dim.DimCombine (true, Dim.Percent (10), 9));
+        Assert.Throws<InvalidOperationException> (() => superView.SetRelativeLayout (new Rect (0, 0, 0, 0)));
+    }
+
+    [Fact]
+    public void ValidatePosDim_True_Throws_When_SubView_Uses_SuperView_Pos_Combine ()
+    {
+        var superView = new View
+        {
+            X = 0,
+            Y = 0,
+            Width = Dim.Auto (),
+            Height = Dim.Auto (),
+            ValidatePosDim = true
+        };
+
+        var subView = new View
+        {
+            X = 0,
+            Y = 0,
+            Width = 10,
+            Height = 10
+        };
+
+        var subView2 = new View
+        {
+            X = 0,
+            Y = 0,
+            Width = 10,
+            Height = 10
+        };
+
+        superView.Add (subView, subView2);
+        superView.BeginInit ();
+        superView.EndInit ();
+        superView.SetRelativeLayout (new Rect (0, 0, 0, 0));
+        superView.LayoutSubviews (); // no throw
+
+        subView.X = Pos.Right (subView2);
+        superView.SetRelativeLayout (new Rect (0, 0, 0, 0));
+        superView.LayoutSubviews (); // no throw
+
+        subView.X = Pos.Right (subView2) + 3;
+        superView.SetRelativeLayout (new Rect (0, 0, 0, 0)); // no throw
+        superView.LayoutSubviews (); // no throw
+
+        subView.X = new Pos.PosCombine (true, Pos.Right (subView2), new Pos.PosCombine (true, 7, 9));
+        superView.SetRelativeLayout (new Rect (0, 0, 0, 0)); // no throw
+
+        subView.X = Pos.Center () + 3;
+        Assert.Throws<InvalidOperationException> (() => superView.SetRelativeLayout (new Rect (0, 0, 0, 0)));
+        subView.X = 0;
+
+        subView.X = 3 + Pos.Center ();
+        Assert.Throws<InvalidOperationException> (() => superView.SetRelativeLayout (new Rect (0, 0, 0, 0)));
+        subView.X = 0;
+
+        subView.X = 3 + 5 + Pos.Center ();
+        Assert.Throws<InvalidOperationException> (() => superView.SetRelativeLayout (new Rect (0, 0, 0, 0)));
+        subView.X = 0;
+
+        subView.X = 3 + 5 + Pos.Percent (10);
+        Assert.Throws<InvalidOperationException> (() => superView.SetRelativeLayout (new Rect (0, 0, 0, 0)));
+        subView.X = 0;
+
+        subView.X = Pos.Percent (10) + Pos.Center ();
+        Assert.Throws<InvalidOperationException> (() => superView.SetRelativeLayout (new Rect (0, 0, 0, 0)));
+        subView.X = 0;
+
+        // Tests nested Combine
+        subView.X = 5 + new Pos.PosCombine (true, Pos.Right (subView2), new Pos.PosCombine (true, Pos.Center (), 9));
+        Assert.Throws<InvalidOperationException> (() => superView.SetRelativeLayout (new Rect (0, 0, 0, 0)));
+        subView.X = 0;
+    }
+
+    [Theory]
+    [InlineData (0, 0, 0, 0, 0)]
+    [InlineData (0, 0, 5, 0, 5)]
+    [InlineData (0, 0, 0, 5, 0)]
+    [InlineData (0, 0, 5, 5, 5)]
+    [InlineData (1, 0, 5, 0, 6)]
+    [InlineData (1, 0, 0, 5, 1)]
+    [InlineData (1, 0, 5, 5, 6)]
+    [InlineData (1, 1, 5, 5, 6)]
+    [InlineData (-1, 0, 5, 0, 4)]
+    [InlineData (-1, 0, 0, 5, 0)]
+    [InlineData (-1, 0, 5, 5, 4)]
+    [InlineData (-1, -1, 5, 5, 4)]
+    public void Width_Auto_Height_NotChanged (int subX, int subY, int subWidth, int subHeight, int expectedWidth)
+    {
+        var superView = new View
+        {
+            X = 0,
+            Y = 0,
+            Width = Dim.Auto (),
+            Height = 10,
+            ValidatePosDim = true
+        };
+
+        var subView = new View
+        {
+            X = subX,
+            Y = subY,
+            Width = subWidth,
+            Height = subHeight,
+            ValidatePosDim = true
+        };
+
+        superView.Add (subView);
+
+        superView.BeginInit ();
+        superView.EndInit ();
+        superView.SetRelativeLayout (new Rect (0, 0, 0, 0));
+        Assert.Equal (new Rect (0, 0, expectedWidth, 10), superView.Frame);
+    }
+
+    // Test variations of Frame
+}

+ 990 - 936
UnitTests/View/Layout/DimTests.cs

@@ -1,38 +1,37 @@
-using System;
-using System.Collections.Generic;
-using System.Globalization;
+using System.Globalization;
 using System.Text;
-using System.Threading;
-using Xunit;
 using Xunit.Abstractions;
+
 // Alias Console to MockConsole so we don't accidentally use Console
 using Console = Terminal.Gui.FakeConsole;
 
 namespace Terminal.Gui.ViewTests;
 
-public class DimTests {
-	readonly ITestOutputHelper _output;
+public class DimTests
+{
+    private readonly ITestOutputHelper _output;
 
-	readonly string [] expecteds = new string [21] {
-		@"
+    private readonly string [] expecteds = new string [21]
+    {
+        @"
 ┌────────────────────┐
 │View with long text │
 │                    │
 └────────────────────┘",
-		@"
+        @"
 ┌────────────────────┐
 │View with long text │
 │Label 0             │
 │Label 0             │
 └────────────────────┘",
-		@"
+        @"
 ┌────────────────────┐
 │View with long text │
 │Label 0             │
 │Label 1             │
 │Label 1             │
 └────────────────────┘",
-		@"
+        @"
 ┌────────────────────┐
 │View with long text │
 │Label 0             │
@@ -40,7 +39,7 @@ public class DimTests {
 │Label 2             │
 │Label 2             │
 └────────────────────┘",
-		@"
+        @"
 ┌────────────────────┐
 │View with long text │
 │Label 0             │
@@ -49,7 +48,7 @@ public class DimTests {
 │Label 3             │
 │Label 3             │
 └────────────────────┘",
-		@"
+        @"
 ┌────────────────────┐
 │View with long text │
 │Label 0             │
@@ -59,7 +58,7 @@ public class DimTests {
 │Label 4             │
 │Label 4             │
 └────────────────────┘",
-		@"
+        @"
 ┌────────────────────┐
 │View with long text │
 │Label 0             │
@@ -70,7 +69,7 @@ public class DimTests {
 │Label 5             │
 │Label 5             │
 └────────────────────┘",
-		@"
+        @"
 ┌────────────────────┐
 │View with long text │
 │Label 0             │
@@ -82,7 +81,7 @@ public class DimTests {
 │Label 6             │
 │Label 6             │
 └────────────────────┘",
-		@"
+        @"
 ┌────────────────────┐
 │View with long text │
 │Label 0             │
@@ -95,7 +94,7 @@ public class DimTests {
 │Label 7             │
 │Label 7             │
 └────────────────────┘",
-		@"
+        @"
 ┌────────────────────┐
 │View with long text │
 │Label 0             │
@@ -109,7 +108,7 @@ public class DimTests {
 │Label 8             │
 │Label 8             │
 └────────────────────┘",
-		@"
+        @"
 ┌────────────────────┐
 │View with long text │
 │Label 0             │
@@ -124,7 +123,7 @@ public class DimTests {
 │Label 9             │
 │Label 9             │
 └────────────────────┘",
-		@"
+        @"
 ┌────────────────────┐
 │View with long text │
 │Label 0             │
@@ -140,7 +139,7 @@ public class DimTests {
 │Label 10            │
 │Label 10            │
 └────────────────────┘",
-		@"
+        @"
 ┌────────────────────┐
 │View with long text │
 │Label 0             │
@@ -157,7 +156,7 @@ public class DimTests {
 │Label 11            │
 │Label 11            │
 └────────────────────┘",
-		@"
+        @"
 ┌────────────────────┐
 │View with long text │
 │Label 0             │
@@ -175,7 +174,7 @@ public class DimTests {
 │Label 12            │
 │Label 12            │
 └────────────────────┘",
-		@"
+        @"
 ┌────────────────────┐
 │View with long text │
 │Label 0             │
@@ -194,7 +193,7 @@ public class DimTests {
 │Label 13            │
 │Label 13            │
 └────────────────────┘",
-		@"
+        @"
 ┌────────────────────┐
 │View with long text │
 │Label 0             │
@@ -214,7 +213,7 @@ public class DimTests {
 │Label 14            │
 │Label 14            │
 └────────────────────┘",
-		@"
+        @"
 ┌────────────────────┐
 │View with long text │
 │Label 0             │
@@ -235,7 +234,7 @@ public class DimTests {
 │Label 15            │
 │Label 15            │
 └────────────────────┘",
-		@"
+        @"
 ┌────────────────────┐
 │View with long text │
 │Label 0             │
@@ -257,7 +256,7 @@ public class DimTests {
 │Label 16            │
 │Label 16            │
 └────────────────────┘",
-		@"
+        @"
 ┌────────────────────┐
 │View with long text │
 │Label 0             │
@@ -280,7 +279,7 @@ public class DimTests {
 │Label 17            │
 │Label 17            │
 └────────────────────┘",
-		@"
+        @"
 ┌────────────────────┐
 │View with long text │
 │Label 0             │
@@ -304,7 +303,7 @@ public class DimTests {
 │Label 18            │
 │Label 18            │
 └────────────────────┘",
-		@"
+        @"
 ┌────────────────────┐
 │View with long text │
 │Label 0             │
@@ -329,912 +328,967 @@ public class DimTests {
 │Label 19            │
 │Label 19            │
 └────────────────────┘"
-	};
-
-	public DimTests (ITestOutputHelper output)
-	{
-		_output = output;
-		Console.OutputEncoding = Encoding.Default;
-		// Change current culture
-		var culture = CultureInfo.CreateSpecificCulture ("en-US");
-		Thread.CurrentThread.CurrentCulture = culture;
-		Thread.CurrentThread.CurrentUICulture = culture;
-	}
-
-	[Fact]
-	public void New_Works ()
-	{
-		var dim = new Dim ();
-		Assert.Equal ("Terminal.Gui.Dim", dim.ToString ());
-	}
-
-	[Fact]
-	public void Sized_SetsValue ()
-	{
-		var dim = Dim.Sized (0);
-		Assert.Equal ("Absolute(0)", dim.ToString ());
-
-		var testVal = 5;
-		dim = Dim.Sized (testVal);
-		Assert.Equal ($"Absolute({testVal})", dim.ToString ());
-
-		testVal = -1;
-		dim = Dim.Sized (testVal);
-		Assert.Equal ($"Absolute({testVal})", dim.ToString ());
-	}
-
-	[Fact]
-	public void Sized_Equals ()
-	{
-		var n1 = 0;
-		var n2 = 0;
-		var dim1 = Dim.Sized (n1);
-		var dim2 = Dim.Sized (n2);
-		Assert.Equal (dim1, dim2);
-
-		n1 = n2 = 1;
-		dim1 = Dim.Sized (n1);
-		dim2 = Dim.Sized (n2);
-		Assert.Equal (dim1, dim2);
-
-		n1 = n2 = -1;
-		dim1 = Dim.Sized (n1);
-		dim2 = Dim.Sized (n2);
-		Assert.Equal (dim1, dim2);
-
-		n1 = 0;
-		n2 = 1;
-		dim1 = Dim.Sized (n1);
-		dim2 = Dim.Sized (n2);
-		Assert.NotEqual (dim1, dim2);
-	}
-
-	[Fact]
-	public void Width_Set_To_Null_Throws ()
-	{
-		var dim = Dim.Width (null);
-		Assert.Throws<NullReferenceException> (() => dim.ToString ());
-	}
-
-	[Fact]
-	[TestRespondersDisposed]
-	public void SetsValue ()
-	{
-		var testVal = Rect.Empty;
-		var testValView = new View (testVal);
-		var dim = Dim.Width (testValView);
-		Assert.Equal ($"View(Width,View(){testVal})", dim.ToString ());
-		testValView.Dispose ();
-
-		testVal = new Rect (1, 2, 3, 4);
-		testValView = new View (testVal);
-		dim = Dim.Width (testValView);
-		Assert.Equal ($"View(Width,View(){testVal})", dim.ToString ());
-		testValView.Dispose ();
-	}
-
-	[Fact]
-	[TestRespondersDisposed]
-	public void Width_Equals ()
-	{
-		var testRect1 = Rect.Empty;
-		var view1 = new View (testRect1);
-		var testRect2 = Rect.Empty;
-		var view2 = new View (testRect2);
-
-		var dim1 = Dim.Width (view1);
-		var dim2 = Dim.Width (view1);
-		// FIXED: Dim.Width should support Equals() and this should change to Equal.
-		Assert.Equal (dim1, dim2);
-
-		dim2 = Dim.Width (view2);
-		Assert.NotEqual (dim1, dim2);
-
-		testRect1 = new Rect (0, 1, 2, 3);
-		view1 = new View (testRect1);
-		testRect2 = new Rect (0, 1, 2, 3);
-		dim1 = Dim.Width (view1);
-		dim2 = Dim.Width (view1);
-		// FIXED: Dim.Width should support Equals() and this should change to Equal.
-		Assert.Equal (dim1, dim2);
-
-		testRect1 = new Rect (0, -1, 2, 3);
-		view1 = new View (testRect1);
-		testRect2 = new Rect (0, -1, 2, 3);
-		dim1 = Dim.Width (view1);
-		dim2 = Dim.Width (view1);
-		// FIXED: Dim.Width should support Equals() and this should change to Equal.
-		Assert.Equal (dim1, dim2);
-
-		testRect1 = new Rect (0, -1, 2, 3);
-		view1 = new View (testRect1);
-		testRect2 = Rect.Empty;
-		view2 = new View (testRect2);
-		dim1 = Dim.Width (view1);
-		dim2 = Dim.Width (view2);
-		Assert.NotEqual (dim1, dim2);
+    };
+
+    public DimTests (ITestOutputHelper output)
+    {
+        _output = output;
+        Console.OutputEncoding = Encoding.Default;
+
+        // Change current culture
+        var culture = CultureInfo.CreateSpecificCulture ("en-US");
+        Thread.CurrentThread.CurrentCulture = culture;
+        Thread.CurrentThread.CurrentUICulture = culture;
+    }
+
+    // TODO: This actually a SetRelativeLayout/LayoutSubViews test and should be moved
+    // A new test that does not depend on Application is needed.
+    [Fact]
+    [AutoInitShutdown]
+    public void Dim_Add_Operator ()
+    {
+        Toplevel top = Application.Top;
+
+        var view = new View { X = 0, Y = 0, Width = 20, Height = 0 };
+        var field = new TextField { X = 0, Y = Pos.Bottom (view), Width = 20 };
+        var count = 0;
+
+        field.KeyDown += (s, k) =>
+                         {
+                             if (k.KeyCode == KeyCode.Enter)
+                             {
+                                 field.Text = $"Label {count}";
+                                 var label = new Label (field.Text) { X = 0, Y = view.Bounds.Height, Width = 20 };
+                                 view.Add (label);
+                                 Assert.Equal ($"Label {count}", label.Text);
+                                 Assert.Equal ($"Absolute({count})", label.Y.ToString ());
+
+                                 Assert.Equal ($"Absolute({count})", view.Height.ToString ());
+                                 view.Height += 1;
+                                 count++;
+                                 Assert.Equal ($"Absolute({count})", view.Height.ToString ());
+                             }
+                         };
+
+        Application.Iteration += (s, a) =>
+                                 {
+                                     while (count < 20)
+                                     {
+                                         field.NewKeyDownEvent (new Key (KeyCode.Enter));
+                                     }
+
+                                     Application.RequestStop ();
+                                 };
+
+        var win = new Window ();
+        win.Add (view);
+        win.Add (field);
+
+        top.Add (win);
+
+        Application.Run (top);
+
+        Assert.Equal (20, count);
+    }
+
+    // TODO: This actually a SetRelativeLayout/LayoutSubViews test and should be moved
+    // TODO: A new test that calls SetRelativeLayout directly is needed.
+    [Fact]
+    [TestRespondersDisposed]
+    public void Dim_Referencing_SuperView_Does_Not_Throw ()
+    {
+        var super = new View ("super")
+        {
+            Width = 10,
+            Height = 10
+        };
+
+        var view = new View ("view")
+        {
+            Width = Dim.Width (super), // this is allowed
+            Height = Dim.Height (super) // this is allowed
+        };
+
+        super.Add (view);
+        super.BeginInit ();
+        super.EndInit ();
+
+        Exception exception = Record.Exception (super.LayoutSubviews);
+        Assert.Null (exception);
+        super.Dispose ();
+    }
+
+    // TODO: This actually a SetRelativeLayout/LayoutSubViews test and should be moved
+    // TODO: A new test that calls SetRelativeLayout directly is needed.
+    [Fact]
+    [AutoInitShutdown]
+    public void Dim_Subtract_Operator ()
+    {
+        Toplevel top = Application.Top;
+
+        var view = new View { X = 0, Y = 0, Width = 20, Height = 0 };
+        var field = new TextField { X = 0, Y = Pos.Bottom (view), Width = 20 };
+        var count = 20;
+        List<Label> listLabels = new List<Label> ();
+
+        for (var i = 0; i < count; i++)
+        {
+            field.Text = $"Label {i}";
+            var label = new Label (field.Text) { X = 0, Y = view.Bounds.Height, Width = 20 };
+            view.Add (label);
+            Assert.Equal ($"Label {i}", label.Text);
+            Assert.Equal ($"Absolute({i})", label.Y.ToString ());
+            listLabels.Add (label);
+
+            Assert.Equal ($"Absolute({i})", view.Height.ToString ());
+            view.Height += 1;
+            Assert.Equal ($"Absolute({i + 1})", view.Height.ToString ());
+        }
+
+        field.KeyDown += (s, k) =>
+                         {
+                             if (k.KeyCode == KeyCode.Enter)
+                             {
+                                 Assert.Equal ($"Label {count - 1}", listLabels [count - 1].Text);
+                                 view.Remove (listLabels [count - 1]);
+                                 listLabels [count - 1].Dispose ();
+
+                                 Assert.Equal ($"Absolute({count})", view.Height.ToString ());
+                                 view.Height -= 1;
+                                 count--;
+                                 Assert.Equal ($"Absolute({count})", view.Height.ToString ());
+                             }
+                         };
+
+        Application.Iteration += (s, a) =>
+                                 {
+                                     while (count > 0)
+                                     {
+                                         field.NewKeyDownEvent (new Key (KeyCode.Enter));
+                                     }
+
+                                     Application.RequestStop ();
+                                 };
+
+        var win = new Window ();
+        win.Add (view);
+        win.Add (field);
+
+        top.Add (win);
+
+        Application.Run (top);
+
+        Assert.Equal (0, count);
+    }
+
+    // TODO: This actually a SetRelativeLayout/LayoutSubViews test and should be moved
+    // TODO: A new test that calls SetRelativeLayout directly is needed.
+    [Fact]
+    [TestRespondersDisposed]
+    public void Dim_SyperView_Referencing_SubView_Throws ()
+    {
+        var super = new View ("super")
+        {
+            Width = 10,
+            Height = 10
+        };
+
+        var view2 = new View ("view2")
+        {
+            Width = 10,
+            Height = 10
+        };
+
+        var view = new View ("view")
+        {
+            Width = Dim.Width (view2), // this is not allowed
+            Height = Dim.Height (view2) // this is not allowed
+        };
+
+        view.Add (view2);
+        super.Add (view);
+        super.BeginInit ();
+        super.EndInit ();
+
+        Assert.Throws<InvalidOperationException> (super.LayoutSubviews);
+        super.Dispose ();
+    }
+
+    // TODO: This actually a SetRelativeLayout/LayoutSubViews test and should be moved
+    // TODO: A new test that calls SetRelativeLayout directly is needed.
+    [Fact]
+    [TestRespondersDisposed]
+    public void Dim_Validation_Do_Not_Throws_If_NewValue_Is_DimAbsolute_And_OldValue_Is_Another_Type_After_Sets_To_LayoutStyle_Absolute ()
+    {
+        var t = new View ("top") { Width = 80, Height = 25 };
+
+        var w = new Window
+        {
+            Width = Dim.Fill (),
+            Height = Dim.Sized (10)
+        };
+
+        var v = new View ("v")
+        {
+            Width = Dim.Width (w) - 2,
+            Height = Dim.Percent (10)
+        };
+
+        w.Add (v);
+        t.Add (w);
+
+        Assert.Equal (LayoutStyle.Absolute, t.LayoutStyle);
+        Assert.Equal (LayoutStyle.Computed, w.LayoutStyle);
+        Assert.Equal (LayoutStyle.Computed, v.LayoutStyle);
+
+        t.LayoutSubviews ();
+        Assert.Equal (2, v.Width = 2);
+        Assert.Equal (2, v.Height = 2);
+
+        // Force v to be LayoutStyle.Absolute;
+        v.Frame = new Rect (0, 1, 3, 4);
+        Assert.Equal (LayoutStyle.Absolute, v.LayoutStyle);
+        t.LayoutSubviews ();
+
+        Assert.Equal (2, v.Width = 2);
+        Assert.Equal (2, v.Height = 2);
+        t.Dispose ();
+    }
+
+    // TODO: This actually a SetRelativeLayout/LayoutSubViews test and should be moved
+    // TODO: A new test that calls SetRelativeLayout directly is needed.
+    [Fact]
+    [TestRespondersDisposed]
+    public void Dim_Validation_Do_Not_Throws_If_NewValue_Is_DimAbsolute_And_OldValue_Is_Null ()
+    {
+        var t = new View ("top") { Width = 80, Height = 25 };
+
+        var w = new Window (new Rect (1, 2, 4, 5)) { Title = "w" };
+        t.Add (w);
+        t.LayoutSubviews ();
+
+        Assert.Equal (3, w.Width = 3);
+        Assert.Equal (4, w.Height = 4);
+        t.Dispose ();
+    }
+
+    // TODO: This actually a SetRelativeLayout/LayoutSubViews test and should be moved
+    // TODO: A new test that calls SetRelativeLayout directly is needed.
+    [Fact]
+    [TestRespondersDisposed]
+    public void DimCombine_ObtuseScenario_Does_Not_Throw_If_Two_SubViews_Refs_The_Same_SuperView ()
+    {
+        var t = new View ("top") { Width = 80, Height = 25 };
+
+        var w = new Window
+        {
+            Width = Dim.Width (t) - 2, // 78
+            Height = Dim.Height (t) - 2 // 23
+        };
+        var f = new FrameView ();
+
+        var v1 = new View
+        {
+            Width = Dim.Width (w) - 2, // 76
+            Height = Dim.Height (w) - 2 // 21
+        };
+
+        var v2 = new View
+        {
+            Width = Dim.Width (v1) - 2, // 74
+            Height = Dim.Height (v1) - 2 // 19
+        };
+
+        f.Add (v1, v2);
+        w.Add (f);
+        t.Add (w);
+        t.BeginInit ();
+        t.EndInit ();
+
+        f.Width = Dim.Width (t) - Dim.Width (w) + 4; // 80 - 74 = 6
+        f.Height = Dim.Height (t) - Dim.Height (w) + 4; // 25 - 19 = 6
+
+        // BUGBUG: v2 - f references t and w here; t is f's super-superview and w is f's superview. This is supported!
+        Exception exception = Record.Exception (t.LayoutSubviews);
+        Assert.Null (exception);
+        Assert.Equal (80, t.Frame.Width);
+        Assert.Equal (25, t.Frame.Height);
+        Assert.Equal (78, w.Frame.Width);
+        Assert.Equal (23, w.Frame.Height);
+        Assert.Equal (6, f.Frame.Width);
+        Assert.Equal (6, f.Frame.Height);
+        Assert.Equal (76, v1.Frame.Width);
+        Assert.Equal (21, v1.Frame.Height);
+        Assert.Equal (74, v2.Frame.Width);
+        Assert.Equal (19, v2.Frame.Height);
+        t.Dispose ();
+    }
+
+    // See #2461
+    //[Fact]
+    //public void Dim_Referencing_SuperView_Throws ()
+    //{
+    //	var super = new View ("super") {
+    //		Width = 10,
+    //		Height = 10
+    //	};
+    //	var view = new View ("view") {
+    //		Width = Dim.Width (super),	// this is not allowed
+    //		Height = Dim.Height (super),    // this is not allowed
+    //	};
+
+    //	super.Add (view);
+    //	super.BeginInit ();
+    //	super.EndInit ();
+    //	Assert.Throws<InvalidOperationException> (() => super.LayoutSubviews ());
+    //}
+
+    // TODO: This actually a SetRelativeLayout/LayoutSubViews test and should be moved
+    // TODO: A new test that calls SetRelativeLayout directly is needed.
+
+    /// <summary>
+    ///     This is an intentionally obtuse test. See https://github.com/gui-cs/Terminal.Gui/issues/2461
+    /// </summary>
+    [Fact]
+    [TestRespondersDisposed]
+    public void DimCombine_ObtuseScenario_Throw_If_SuperView_Refs_SubView ()
+    {
+        var t = new View { Width = 80, Height = 25 };
+
+        var w = new Window
+        {
+            Width = Dim.Width (t) - 2, // 78
+            Height = Dim.Height (t) - 2 // 23
+        };
+        var f = new FrameView ();
+
+        var v1 = new View
+        {
+            Width = Dim.Width (w) - 2, // 76
+            Height = Dim.Height (w) - 2 // 21
+        };
+
+        var v2 = new View
+        {
+            Width = Dim.Width (v1) - 2, // 74
+            Height = Dim.Height (v1) - 2 // 19
+        };
+
+        f.Add (v1, v2);
+        w.Add (f);
+        t.Add (w);
+        t.BeginInit ();
+        t.EndInit ();
+
+        f.Width = Dim.Width (t) - Dim.Width (v2); // 80 - 74 = 6
+        f.Height = Dim.Height (t) - Dim.Height (v2); // 25 - 19 = 6
+
+        Assert.Throws<InvalidOperationException> (t.LayoutSubviews);
+        Assert.Equal (80, t.Frame.Width);
+        Assert.Equal (25, t.Frame.Height);
+        Assert.Equal (78, w.Frame.Width);
+        Assert.Equal (23, w.Frame.Height);
+        Assert.Equal (6, f.Frame.Width);
+        Assert.Equal (6, f.Frame.Height);
+        Assert.Equal (76, v1.Frame.Width);
+        Assert.Equal (21, v1.Frame.Height);
+        Assert.Equal (74, v2.Frame.Width);
+        Assert.Equal (19, v2.Frame.Height);
+        t.Dispose ();
+    }
+
+    // TODO: This actually a SetRelativeLayout/LayoutSubViews test and should be moved
+    // TODO: A new test that calls SetRelativeLayout directly is needed.
+    [Theory]
+    [AutoInitShutdown]
+    [InlineData (0, true)]
+    [InlineData (0, false)]
+    [InlineData (50, true)]
+    [InlineData (50, false)]
+    public void DimPercentPlusOne (int startingDistance, bool testHorizontal)
+    {
+        var container = new View
+        {
+            Width = 100,
+            Height = 100
+        };
+
+        var label = new Label
+        {
+            X = testHorizontal ? startingDistance : 0,
+            Y = testHorizontal ? 0 : startingDistance,
+            Width = testHorizontal ? Dim.Percent (50) + 1 : 1,
+            Height = testHorizontal ? 1 : Dim.Percent (50) + 1
+        };
+
+        container.Add (label);
+        Application.Top.Add (container);
+        Application.Top.BeginInit ();
+        Application.Top.EndInit ();
+        Application.Top.LayoutSubviews ();
+
+        Assert.Equal (100, container.Frame.Width);
+        Assert.Equal (100, container.Frame.Height);
+
+        if (testHorizontal)
+        {
+            Assert.Equal (51, label.Frame.Width);
+            Assert.Equal (1, label.Frame.Height);
+        }
+        else
+        {
+            Assert.Equal (1, label.Frame.Width);
+            Assert.Equal (51, label.Frame.Height);
+        }
+    }
+
+    [Fact]
+    public void Fill_Equal ()
+    {
+        var margin1 = 0;
+        var margin2 = 0;
+        Dim dim1 = Dim.Fill (margin1);
+        Dim dim2 = Dim.Fill (margin2);
+        Assert.Equal (dim1, dim2);
+    }
+
+    // TODO: Other Dim.Height tests (e.g. Equal?)
+
+    [Fact]
+    public void Fill_SetsValue ()
+    {
+        var testMargin = 0;
+        Dim dim = Dim.Fill ();
+        Assert.Equal ($"Fill({testMargin})", dim.ToString ());
+
+        testMargin = 0;
+        dim = Dim.Fill (testMargin);
+        Assert.Equal ($"Fill({testMargin})", dim.ToString ());
+
+        testMargin = 5;
+        dim = Dim.Fill (testMargin);
+        Assert.Equal ($"Fill({testMargin})", dim.ToString ());
+    }
+
+    [Fact]
+    public void Function_Equal ()
+    {
+        Func<int> f1 = () => 0;
+        Func<int> f2 = () => 0;
+
+        Dim dim1 = Dim.Function (f1);
+        Dim dim2 = Dim.Function (f2);
+        Assert.Equal (dim1, dim2);
+
+        f2 = () => 1;
+        dim2 = Dim.Function (f2);
+        Assert.NotEqual (dim1, dim2);
+    }
+
+    [Fact]
+    public void Function_SetsValue ()
+    {
+        var text = "Test";
+        Dim dim = Dim.Function (() => text.Length);
+        Assert.Equal ("DimFunc(4)", dim.ToString ());
+
+        text = "New Test";
+        Assert.Equal ("DimFunc(8)", dim.ToString ());
+
+        text = "";
+        Assert.Equal ("DimFunc(0)", dim.ToString ());
+    }
+
+    [Fact]
+    public void Height_Set_To_Null_Throws ()
+    {
+        Dim dim = Dim.Height (null);
+        Assert.Throws<NullReferenceException> (() => dim.ToString ());
+    }
+
+    [Fact]
+    [TestRespondersDisposed]
+    public void Height_SetsValue ()
+    {
+        var testVal = Rect.Empty;
+        var testValview = new View (testVal);
+        Dim dim = Dim.Height (testValview);
+        Assert.Equal ($"View(Height,View(){testVal})", dim.ToString ());
+        testValview.Dispose ();
+
+        testVal = new Rect (1, 2, 3, 4);
+        testValview = new View (testVal);
+        dim = Dim.Height (testValview);
+        Assert.Equal ($"View(Height,View(){testVal})", dim.ToString ());
+        testValview.Dispose ();
+    }
+
+    [Fact]
+    [TestRespondersDisposed]
+    public void Internal_Tests ()
+    {
+        var dimFactor = new Dim.DimFactor (0.10F);
+        Assert.Equal (10, dimFactor.Anchor (100));
+
+        var dimAbsolute = new Dim.DimAbsolute (10);
+        Assert.Equal (10, dimAbsolute.Anchor (0));
+
+        var dimFill = new Dim.DimFill (1);
+        Assert.Equal (99, dimFill.Anchor (100));
+
+        var dimCombine = new Dim.DimCombine (true, dimFactor, dimAbsolute);
+        Assert.Equal (dimCombine._left, dimFactor);
+        Assert.Equal (dimCombine._right, dimAbsolute);
+        Assert.Equal (20, dimCombine.Anchor (100));
+
+        var view = new View (new Rect (20, 10, 20, 1));
+        var dimViewHeight = new Dim.DimView (view, 0);
+        Assert.Equal (1, dimViewHeight.Anchor (0));
+        var dimViewWidth = new Dim.DimView (view, 1);
+        Assert.Equal (20, dimViewWidth.Anchor (0));
+
+        view.Dispose ();
+    }
+
+    [Fact]
+    public void New_Works ()
+    {
+        var dim = new Dim ();
+        Assert.Equal ("Terminal.Gui.Dim", dim.ToString ());
+    }
+
+    // TODO: This actually a SetRelativeLayout/LayoutSubViews test and should be moved
+    // TODO: A new test that calls SetRelativeLayout directly is needed.
+    [Fact]
+    [AutoInitShutdown]
+    public void Only_DimAbsolute_And_DimFactor_As_A_Different_Procedure_For_Assigning_Value_To_Width_Or_Height ()
+    {
+        // Testing with the Button because it properly handles the Dim class.
+        Toplevel t = Application.Top;
+
+        var w = new Window
+        {
+            Width = 100,
+            Height = 100
+        };
+
+        var f1 = new FrameView ("f1")
+        {
+            X = 0,
+            Y = 0,
+            Width = Dim.Percent (50),
+            Height = 5
+        };
+
+        var f2 = new FrameView ("f2")
+        {
+            X = Pos.Right (f1),
+            Y = 0,
+            Width = Dim.Fill (),
+            Height = 5
+        };
+
+        var v1 = new Button ("v1")
+        {
+            AutoSize = false,
+            X = Pos.X (f1) + 2,
+            Y = Pos.Bottom (f1) + 2,
+            Width = Dim.Width (f1) - 2,
+            Height = Dim.Fill () - 2,
+            ValidatePosDim = true
+        };
+
+        var v2 = new Button ("v2")
+        {
+            AutoSize = false,
+            X = Pos.X (f2) + 2,
+            Y = Pos.Bottom (f2) + 2,
+            Width = Dim.Width (f2) - 2,
+            Height = Dim.Fill () - 2,
+            ValidatePosDim = true
+        };
+
+        var v3 = new Button ("v3")
+        {
+            AutoSize = false,
+            Width = Dim.Percent (10),
+            Height = Dim.Percent (10),
+            ValidatePosDim = true
+        };
+
+        var v4 = new Button ("v4")
+        {
+            AutoSize = false,
+            Width = Dim.Sized (50),
+            Height = Dim.Sized (50),
+            ValidatePosDim = true
+        };
+
+        var v5 = new Button ("v5")
+        {
+            AutoSize = false,
+            Width = Dim.Width (v1) - Dim.Width (v3),
+            Height = Dim.Height (v1) - Dim.Height (v3),
+            ValidatePosDim = true
+        };
+
+        var v6 = new Button ("v6")
+        {
+            AutoSize = false,
+            X = Pos.X (f2),
+            Y = Pos.Bottom (f2) + 2,
+            Width = Dim.Percent (20, true),
+            Height = Dim.Percent (20, true),
+            ValidatePosDim = true
+        };
+
+        w.Add (f1, f2, v1, v2, v3, v4, v5, v6);
+        t.Add (w);
+
+        t.Ready += (s, e) =>
+                   {
+                       Assert.Equal ("Absolute(100)", w.Width.ToString ());
+                       Assert.Equal ("Absolute(100)", w.Height.ToString ());
+                       Assert.Equal (100, w.Frame.Width);
+                       Assert.Equal (100, w.Frame.Height);
+
+                       Assert.Equal ("Factor(0.5,False)", f1.Width.ToString ());
+                       Assert.Equal ("Absolute(5)", f1.Height.ToString ());
+                       Assert.Equal (49, f1.Frame.Width); // 50-1=49
+                       Assert.Equal (5, f1.Frame.Height);
+
+                       Assert.Equal ("Fill(0)", f2.Width.ToString ());
+                       Assert.Equal ("Absolute(5)", f2.Height.ToString ());
+                       Assert.Equal (49, f2.Frame.Width); // 50-1=49
+                       Assert.Equal (5, f2.Frame.Height);
+
+                       Assert.Equal ("Combine(View(Width,FrameView(f1)(0,0,49,5))-Absolute(2))", v1.Width.ToString ());
+                       Assert.Equal ("Combine(Fill(0)-Absolute(2))", v1.Height.ToString ());
+                       Assert.Equal (47, v1.Frame.Width); // 49-2=47
+                       Assert.Equal (89, v1.Frame.Height); // 98-5-2-2=89
+
+                       Assert.Equal ("Combine(View(Width,FrameView(f2)(49,0,49,5))-Absolute(2))", v2.Width.ToString ());
+                       Assert.Equal ("Combine(Fill(0)-Absolute(2))", v2.Height.ToString ());
+                       Assert.Equal (47, v2.Frame.Width); // 49-2=47
+                       Assert.Equal (89, v2.Frame.Height); // 98-5-2-2=89
+
+                       Assert.Equal ("Factor(0.1,False)", v3.Width.ToString ());
+                       Assert.Equal ("Factor(0.1,False)", v3.Height.ToString ());
+                       Assert.Equal (9, v3.Frame.Width); // 98*10%=9
+                       Assert.Equal (9, v3.Frame.Height); // 98*10%=9
+
+                       Assert.Equal ("Absolute(50)", v4.Width.ToString ());
+                       Assert.Equal ("Absolute(50)", v4.Height.ToString ());
+                       Assert.Equal (50, v4.Frame.Width);
+                       Assert.Equal (50, v4.Frame.Height);
+
+                       Assert.Equal ("Combine(View(Width,Button(v1)(2,7,47,89))-View(Width,Button(v3)(0,0,9,9)))", v5.Width.ToString ());
+                       Assert.Equal ("Combine(View(Height,Button(v1)(2,7,47,89))-View(Height,Button(v3)(0,0,9,9)))", v5.Height.ToString ());
+                       Assert.Equal (38, v5.Frame.Width); // 47-9=38
+                       Assert.Equal (80, v5.Frame.Height); // 89-9=80
+
+                       Assert.Equal ("Factor(0.2,True)", v6.Width.ToString ());
+                       Assert.Equal ("Factor(0.2,True)", v6.Height.ToString ());
+                       Assert.Equal (9, v6.Frame.Width); // 47*20%=9
+                       Assert.Equal (18, v6.Frame.Height); // 89*20%=18
+
+                       w.Width = 200;
+                       Assert.True (t.LayoutNeeded);
+                       w.Height = 200;
+                       t.LayoutSubviews ();
+
+                       Assert.Equal ("Absolute(200)", w.Width.ToString ());
+                       Assert.Equal ("Absolute(200)", w.Height.ToString ());
+                       Assert.Equal (200, w.Frame.Width);
+                       Assert.Equal (200, w.Frame.Height);
+
+                       f1.Text = "Frame1";
+                       Assert.Equal ("Factor(0.5,False)", f1.Width.ToString ());
+                       Assert.Equal ("Absolute(5)", f1.Height.ToString ());
+                       Assert.Equal (99, f1.Frame.Width); // 100-1=99
+                       Assert.Equal (5, f1.Frame.Height);
+
+                       f2.Text = "Frame2";
+                       Assert.Equal ("Fill(0)", f2.Width.ToString ());
+                       Assert.Equal ("Absolute(5)", f2.Height.ToString ());
+                       Assert.Equal (99, f2.Frame.Width); // 100-1=99
+                       Assert.Equal (5, f2.Frame.Height);
+
+                       v1.Text = "Button1";
+                       Assert.Equal ("Combine(View(Width,FrameView(f1)(0,0,99,5))-Absolute(2))", v1.Width.ToString ());
+                       Assert.Equal ("Combine(Fill(0)-Absolute(2))", v1.Height.ToString ());
+                       Assert.Equal (97, v1.Frame.Width); // 99-2=97
+                       Assert.Equal (189, v1.Frame.Height); // 198-2-7=189
+
+                       v2.Text = "Button2";
+                       Assert.Equal ("Combine(View(Width,FrameView(f2)(99,0,99,5))-Absolute(2))", v2.Width.ToString ());
+                       Assert.Equal ("Combine(Fill(0)-Absolute(2))", v2.Height.ToString ());
+                       Assert.Equal (97, v2.Frame.Width); // 99-2=97
+                       Assert.Equal (189, v2.Frame.Height); // 198-2-7=189
+
+                       v3.Text = "Button3";
+                       Assert.Equal ("Factor(0.1,False)", v3.Width.ToString ());
+                       Assert.Equal ("Factor(0.1,False)", v3.Height.ToString ());
+                       Assert.Equal (19, v3.Frame.Width); // 198*10%=19 * Percent is related to the super-view if it isn't null otherwise the view width
+                       Assert.Equal (19, v3.Frame.Height); // 199*10%=19
+
+                       v4.Text = "Button4";
+                       v4.AutoSize = false;
+                       Assert.Equal ("Absolute(50)", v4.Width.ToString ());
+                       Assert.Equal ("Absolute(50)", v4.Height.ToString ());
+                       Assert.Equal (50, v4.Frame.Width);
+                       Assert.Equal (50, v4.Frame.Height);
+                       v4.AutoSize = true;
+                       Assert.Equal ("Absolute(11)", v4.Width.ToString ());
+                       Assert.Equal ("Absolute(1)", v4.Height.ToString ());
+                       Assert.Equal (11, v4.Frame.Width); // 11 is the text length and because is Dim.DimAbsolute
+                       Assert.Equal (1, v4.Frame.Height); // 1 because is Dim.DimAbsolute
+
+                       v5.Text = "Button5";
+                       Assert.Equal ("Combine(View(Width,Button(v1)(2,7,97,189))-View(Width,Button(v3)(0,0,19,19)))", v5.Width.ToString ());
+                       Assert.Equal ("Combine(View(Height,Button(v1)(2,7,97,189))-View(Height,Button(v3)(0,0,19,19)))", v5.Height.ToString ());
+                       Assert.Equal (78, v5.Frame.Width); // 97-9=78
+                       Assert.Equal (170, v5.Frame.Height); // 189-19=170
+
+                       v6.Text = "Button6";
+                       Assert.Equal ("Factor(0.2,True)", v6.Width.ToString ());
+                       Assert.Equal ("Factor(0.2,True)", v6.Height.ToString ());
+                       Assert.Equal (19, v6.Frame.Width); // 99*20%=19
+                       Assert.Equal (38, v6.Frame.Height); // 198-7*20=18
+                   };
+
+        Application.Iteration += (s, a) => Application.RequestStop ();
+
+        Application.Run ();
+    }
+
+    [Fact]
+    public void Percent_Equals ()
+    {
+        float n1 = 0;
+        float n2 = 0;
+        Dim dim1 = Dim.Percent (n1);
+        Dim dim2 = Dim.Percent (n2);
+        Assert.Equal (dim1, dim2);
+
+        n1 = n2 = 1;
+        dim1 = Dim.Percent (n1);
+        dim2 = Dim.Percent (n2);
+        Assert.Equal (dim1, dim2);
+
+        n1 = n2 = 0.5f;
+        dim1 = Dim.Percent (n1);
+        dim2 = Dim.Percent (n2);
+        Assert.Equal (dim1, dim2);
+
+        n1 = n2 = 100f;
+        dim1 = Dim.Percent (n1);
+        dim2 = Dim.Percent (n2);
+        Assert.Equal (dim1, dim2);
+
+        n1 = n2 = 0.3f;
+        dim1 = Dim.Percent (n1, true);
+        dim2 = Dim.Percent (n2, true);
+        Assert.Equal (dim1, dim2);
+
+        n1 = n2 = 0.3f;
+        dim1 = Dim.Percent (n1);
+        dim2 = Dim.Percent (n2, true);
+        Assert.NotEqual (dim1, dim2);
+
+        n1 = 0;
+        n2 = 1;
+        dim1 = Dim.Percent (n1);
+        dim2 = Dim.Percent (n2);
+        Assert.NotEqual (dim1, dim2);
+
+        n1 = 0.5f;
+        n2 = 1.5f;
+        dim1 = Dim.Percent (n1);
+        dim2 = Dim.Percent (n2);
+        Assert.NotEqual (dim1, dim2);
+    }
+
+    [Fact]
+    public void Percent_Invalid_Throws ()
+    {
+        Dim dim = Dim.Percent (0);
+        Assert.Throws<ArgumentException> (() => dim = Dim.Percent (-1));
+        Assert.Throws<ArgumentException> (() => dim = Dim.Percent (101));
+        Assert.Throws<ArgumentException> (() => dim = Dim.Percent (100.0001F));
+        Assert.Throws<ArgumentException> (() => dim = Dim.Percent (1000001));
+    }
+
+    [Fact]
+    public void Percent_SetsValue ()
+    {
+        float f = 0;
+        Dim dim = Dim.Percent (f);
+        Assert.Equal ($"Factor({f / 100:0.###},{false})", dim.ToString ());
+        f = 0.5F;
+        dim = Dim.Percent (f);
+        Assert.Equal ($"Factor({f / 100:0.###},{false})", dim.ToString ());
+        f = 100;
+        dim = Dim.Percent (f);
+        Assert.Equal ($"Factor({f / 100:0.###},{false})", dim.ToString ());
+    }
+
+    // TODO: This actually a SetRelativeLayout/LayoutSubViews test and should be moved
+    // TODO: A new test that calls SetRelativeLayout directly is needed.
+    [Fact]
+    [TestRespondersDisposed]
+    public void PosCombine_View_Not_Added_Throws ()
+    {
+        var t = new View { Width = 80, Height = 50 };
+
+        var super = new View
+        {
+            Width = Dim.Width (t) - 2,
+            Height = Dim.Height (t) - 2
+        };
+        t.Add (super);
+
+        var sub = new View ();
+        super.Add (sub);
+
+        var v1 = new View
+        {
+            Width = Dim.Width (super) - 2,
+            Height = Dim.Height (super) - 2
+        };
+
+        var v2 = new View
+        {
+            Width = Dim.Width (v1) - 2,
+            Height = Dim.Height (v1) - 2
+        };
+        sub.Add (v1);
+
+        // v2 not added to sub; should cause exception on Layout since it's referenced by sub.
+        sub.Width = Dim.Fill () - Dim.Width (v2);
+        sub.Height = Dim.Fill () - Dim.Height (v2);
+
+        t.BeginInit ();
+        t.EndInit ();
+
+        Assert.Throws<InvalidOperationException> (() => t.LayoutSubviews ());
+        t.Dispose ();
+        v2.Dispose ();
+    }
+
+    [Fact]
+    [TestRespondersDisposed]
+    public void SetsValue ()
+    {
+        var testVal = Rect.Empty;
+        var testValView = new View (testVal);
+        Dim dim = Dim.Width (testValView);
+        Assert.Equal ($"View(Width,View(){testVal})", dim.ToString ());
+        testValView.Dispose ();
+
+        testVal = new Rect (1, 2, 3, 4);
+        testValView = new View (testVal);
+        dim = Dim.Width (testValView);
+        Assert.Equal ($"View(Width,View(){testVal})", dim.ToString ());
+        testValView.Dispose ();
+    }
+
+    [Fact]
+    public void Sized_Equals ()
+    {
+        var n1 = 0;
+        var n2 = 0;
+        Dim dim1 = Dim.Sized (n1);
+        Dim dim2 = Dim.Sized (n2);
+        Assert.Equal (dim1, dim2);
+
+        n1 = n2 = 1;
+        dim1 = Dim.Sized (n1);
+        dim2 = Dim.Sized (n2);
+        Assert.Equal (dim1, dim2);
+
+        n1 = n2 = -1;
+        dim1 = Dim.Sized (n1);
+        dim2 = Dim.Sized (n2);
+        Assert.Equal (dim1, dim2);
+
+        n1 = 0;
+        n2 = 1;
+        dim1 = Dim.Sized (n1);
+        dim2 = Dim.Sized (n2);
+        Assert.NotEqual (dim1, dim2);
+    }
+
+    [Fact]
+    public void Sized_SetsValue ()
+    {
+        Dim dim = Dim.Sized (0);
+        Assert.Equal ("Absolute(0)", dim.ToString ());
+
+        var testVal = 5;
+        dim = Dim.Sized (testVal);
+        Assert.Equal ($"Absolute({testVal})", dim.ToString ());
+
+        testVal = -1;
+        dim = Dim.Sized (testVal);
+        Assert.Equal ($"Absolute({testVal})", dim.ToString ());
+    }
+
+    [Fact]
+    [TestRespondersDisposed]
+    public void Width_Equals ()
+    {
+        var testRect1 = Rect.Empty;
+        var view1 = new View (testRect1);
+        var testRect2 = Rect.Empty;
+        var view2 = new View (testRect2);
+
+        Dim dim1 = Dim.Width (view1);
+        Dim dim2 = Dim.Width (view1);
+
+        // FIXED: Dim.Width should support Equals() and this should change to Equal.
+        Assert.Equal (dim1, dim2);
+
+        dim2 = Dim.Width (view2);
+        Assert.NotEqual (dim1, dim2);
+
+        testRect1 = new Rect (0, 1, 2, 3);
+        view1 = new View (testRect1);
+        testRect2 = new Rect (0, 1, 2, 3);
+        dim1 = Dim.Width (view1);
+        dim2 = Dim.Width (view1);
+
+        // FIXED: Dim.Width should support Equals() and this should change to Equal.
+        Assert.Equal (dim1, dim2);
+
+        testRect1 = new Rect (0, -1, 2, 3);
+        view1 = new View (testRect1);
+        testRect2 = new Rect (0, -1, 2, 3);
+        dim1 = Dim.Width (view1);
+        dim2 = Dim.Width (view1);
+
+        // FIXED: Dim.Width should support Equals() and this should change to Equal.
+        Assert.Equal (dim1, dim2);
+
+        testRect1 = new Rect (0, -1, 2, 3);
+        view1 = new View (testRect1);
+        testRect2 = Rect.Empty;
+        view2 = new View (testRect2);
+        dim1 = Dim.Width (view1);
+        dim2 = Dim.Width (view2);
+        Assert.NotEqual (dim1, dim2);
 #if DEBUG_IDISPOSABLE
-		// HACK: Force clean up of Responders to avoid having to Dispose all the Views created above.
-		Responder.Instances.Clear ();
-		Assert.Empty (Responder.Instances);
+
+        // HACK: Force clean up of Responders to avoid having to Dispose all the Views created above.
+        Responder.Instances.Clear ();
+        Assert.Empty (Responder.Instances);
 #endif
-	}
-
-	[Fact]
-	public void Height_Set_To_Null_Throws ()
-	{
-		var dim = Dim.Height (null);
-		Assert.Throws<NullReferenceException> (() => dim.ToString ());
-	}
-
-	[Fact]
-	[TestRespondersDisposed]
-	public void Height_SetsValue ()
-	{
-		var testVal = Rect.Empty;
-		var testValview = new View (testVal);
-		var dim = Dim.Height (testValview);
-		Assert.Equal ($"View(Height,View(){testVal})", dim.ToString ());
-		testValview.Dispose ();
-
-		testVal = new Rect (1, 2, 3, 4);
-		testValview = new View (testVal);
-		dim = Dim.Height (testValview);
-		Assert.Equal ($"View(Height,View(){testVal})", dim.ToString ());
-		testValview.Dispose ();
-	}
-
-	// TODO: Other Dim.Height tests (e.g. Equal?)
-
-	[Fact]
-	public void Fill_SetsValue ()
-	{
-		var testMargin = 0;
-		var dim = Dim.Fill ();
-		Assert.Equal ($"Fill({testMargin})", dim.ToString ());
-
-		testMargin = 0;
-		dim = Dim.Fill (testMargin);
-		Assert.Equal ($"Fill({testMargin})", dim.ToString ());
-
-		testMargin = 5;
-		dim = Dim.Fill (testMargin);
-		Assert.Equal ($"Fill({testMargin})", dim.ToString ());
-	}
-
-	[Fact]
-	public void Fill_Equal ()
-	{
-		var margin1 = 0;
-		var margin2 = 0;
-		var dim1 = Dim.Fill (margin1);
-		var dim2 = Dim.Fill (margin2);
-		Assert.Equal (dim1, dim2);
-	}
-
-	[Fact]
-	public void Percent_SetsValue ()
-	{
-		float f = 0;
-		var dim = Dim.Percent (f);
-		Assert.Equal ($"Factor({f / 100:0.###},{false})", dim.ToString ());
-		f = 0.5F;
-		dim = Dim.Percent (f);
-		Assert.Equal ($"Factor({f / 100:0.###},{false})", dim.ToString ());
-		f = 100;
-		dim = Dim.Percent (f);
-		Assert.Equal ($"Factor({f / 100:0.###},{false})", dim.ToString ());
-	}
-
-	[Fact]
-	public void Percent_Equals ()
-	{
-		float n1 = 0;
-		float n2 = 0;
-		var dim1 = Dim.Percent (n1);
-		var dim2 = Dim.Percent (n2);
-		Assert.Equal (dim1, dim2);
-
-		n1 = n2 = 1;
-		dim1 = Dim.Percent (n1);
-		dim2 = Dim.Percent (n2);
-		Assert.Equal (dim1, dim2);
-
-		n1 = n2 = 0.5f;
-		dim1 = Dim.Percent (n1);
-		dim2 = Dim.Percent (n2);
-		Assert.Equal (dim1, dim2);
-
-		n1 = n2 = 100f;
-		dim1 = Dim.Percent (n1);
-		dim2 = Dim.Percent (n2);
-		Assert.Equal (dim1, dim2);
-
-		n1 = n2 = 0.3f;
-		dim1 = Dim.Percent (n1, true);
-		dim2 = Dim.Percent (n2, true);
-		Assert.Equal (dim1, dim2);
-
-		n1 = n2 = 0.3f;
-		dim1 = Dim.Percent (n1);
-		dim2 = Dim.Percent (n2, true);
-		Assert.NotEqual (dim1, dim2);
-
-		n1 = 0;
-		n2 = 1;
-		dim1 = Dim.Percent (n1);
-		dim2 = Dim.Percent (n2);
-		Assert.NotEqual (dim1, dim2);
-
-		n1 = 0.5f;
-		n2 = 1.5f;
-		dim1 = Dim.Percent (n1);
-		dim2 = Dim.Percent (n2);
-		Assert.NotEqual (dim1, dim2);
-	}
-
-	[Fact]
-	public void Percent_Invalid_Throws ()
-	{
-		var dim = Dim.Percent (0);
-		Assert.Throws<ArgumentException> (() => dim = Dim.Percent (-1));
-		Assert.Throws<ArgumentException> (() => dim = Dim.Percent (101));
-		Assert.Throws<ArgumentException> (() => dim = Dim.Percent (100.0001F));
-		Assert.Throws<ArgumentException> (() => dim = Dim.Percent (1000001));
-	}
-
-	// TODO: This actually a SetRelativeLayout/LayoutSubViews test and should be moved
-	// TODO: A new test that calls SetRelativeLayout directly is needed.
-	[Fact]
-	[TestRespondersDisposed]
-	public void Dim_Validation_Do_Not_Throws_If_NewValue_Is_DimAbsolute_And_OldValue_Is_Null ()
-	{
-		var t = new View ("top") { Width = 80, Height = 25 };
-
-		var w = new Window (new Rect (1, 2, 4, 5)) { Title = "w" };
-		t.Add (w);
-		t.LayoutSubviews ();
-
-		Assert.Equal (3, w.Width = 3);
-		Assert.Equal (4, w.Height = 4);
-		t.Dispose ();
-	}
-
-	// TODO: This actually a SetRelativeLayout/LayoutSubViews test and should be moved
-	// TODO: A new test that calls SetRelativeLayout directly is needed.
-	[Fact]
-	[TestRespondersDisposed]
-	public void Dim_Validation_Do_Not_Throws_If_NewValue_Is_DimAbsolute_And_OldValue_Is_Another_Type_After_Sets_To_LayoutStyle_Absolute ()
-	{
-		var t = new View ("top") { Width = 80, Height = 25 };
-
-		var w = new Window {
-			Width = Dim.Fill (),
-			Height = Dim.Sized (10)
-		};
-		var v = new View ("v") {
-			Width = Dim.Width (w) - 2,
-			Height = Dim.Percent (10)
-		};
-		
-		w.Add (v);
-		t.Add (w);
-
-		Assert.Equal (LayoutStyle.Absolute, t.LayoutStyle);
-		Assert.Equal (LayoutStyle.Computed, w.LayoutStyle);
-		Assert.Equal (LayoutStyle.Computed, v.LayoutStyle);
-
-		t.LayoutSubviews ();
-		Assert.Equal (2, v.Width = 2);
-		Assert.Equal (2, v.Height = 2);
-
-		// Force v to be LayoutStyle.Absolute;
-		v.Frame = new Rect (0, 1, 3, 4);
-		Assert.Equal (LayoutStyle.Absolute, v.LayoutStyle);
-		t.LayoutSubviews ();
-
-		Assert.Equal (2, v.Width = 2);
-		Assert.Equal (2, v.Height = 2);
-		t.Dispose ();
-	}
-
-	// TODO: This actually a SetRelativeLayout/LayoutSubViews test and should be moved
-	// TODO: A new test that calls SetRelativeLayout directly is needed.
-	[Fact]
-	[AutoInitShutdown]
-	public void Only_DimAbsolute_And_DimFactor_As_A_Different_Procedure_For_Assigning_Value_To_Width_Or_Height ()
-	{
-		// Testing with the Button because it properly handles the Dim class.
-		var t = Application.Top;
-
-		var w = new Window {
-			Width = 100,
-			Height = 100
-		};
-
-		var f1 = new FrameView ("f1") {
-			X = 0,
-			Y = 0,
-			Width = Dim.Percent (50),
-			Height = 5
-		};
-
-		var f2 = new FrameView ("f2") {
-			X = Pos.Right (f1),
-			Y = 0,
-			Width = Dim.Fill (),
-			Height = 5
-		};
-
-		var v1 = new Button ("v1") {
-			AutoSize = false,
-			X = Pos.X (f1) + 2,
-			Y = Pos.Bottom (f1) + 2,
-			Width = Dim.Width (f1) - 2,
-			Height = Dim.Fill () - 2,
-			ValidatePosDim = true
-		};
-
-		var v2 = new Button ("v2") {
-			AutoSize = false,
-			X = Pos.X (f2) + 2,
-			Y = Pos.Bottom (f2) + 2,
-			Width = Dim.Width (f2) - 2,
-			Height = Dim.Fill () - 2,
-			ValidatePosDim = true
-		};
-
-		var v3 = new Button ("v3") {
-			AutoSize = false,
-			Width = Dim.Percent (10),
-			Height = Dim.Percent (10),
-			ValidatePosDim = true
-		};
-
-		var v4 = new Button ("v4") {
-			AutoSize = false,
-			Width = Dim.Sized (50),
-			Height = Dim.Sized (50),
-			ValidatePosDim = true
-		};
-
-		var v5 = new Button ("v5") {
-			AutoSize = false,
-			Width = Dim.Width (v1) - Dim.Width (v3),
-			Height = Dim.Height (v1) - Dim.Height (v3),
-			ValidatePosDim = true
-		};
-
-		var v6 = new Button ("v6") {
-			AutoSize = false,
-			X = Pos.X (f2),
-			Y = Pos.Bottom (f2) + 2,
-			Width = Dim.Percent (20,  true),
-			Height = Dim.Percent (20, true),
-			ValidatePosDim = true
-		};
-
-		w.Add (f1, f2, v1, v2, v3, v4, v5, v6);
-		t.Add (w);
-
-		t.Ready += (s, e) => {
-			Assert.Equal ("Absolute(100)", w.Width.ToString ());
-			Assert.Equal ("Absolute(100)", w.Height.ToString ());
-			Assert.Equal (100,             w.Frame.Width);
-			Assert.Equal (100,             w.Frame.Height);
-
-			Assert.Equal ("Factor(0.5,False)", f1.Width.ToString ());
-			Assert.Equal ("Absolute(5)",       f1.Height.ToString ());
-			Assert.Equal (49,                  f1.Frame.Width); // 50-1=49
-			Assert.Equal (5,                   f1.Frame.Height);
-
-			Assert.Equal ("Fill(0)",     f2.Width.ToString ());
-			Assert.Equal ("Absolute(5)", f2.Height.ToString ());
-			Assert.Equal (49,            f2.Frame.Width); // 50-1=49
-			Assert.Equal (5,             f2.Frame.Height);
-
-			Assert.Equal ("Combine(View(Width,FrameView(f1)(0,0,49,5))-Absolute(2))", v1.Width.ToString ());
-			Assert.Equal ("Combine(Fill(0)-Absolute(2))", v1.Height.ToString ());
-			Assert.Equal (47, v1.Frame.Width);  // 49-2=47
-			Assert.Equal (89, v1.Frame.Height); // 98-5-2-2=89
-
-			Assert.Equal ("Combine(View(Width,FrameView(f2)(49,0,49,5))-Absolute(2))", v2.Width.ToString ());
-			Assert.Equal ("Combine(Fill(0)-Absolute(2))", v2.Height.ToString ());
-			Assert.Equal (47, v2.Frame.Width);  // 49-2=47
-			Assert.Equal (89, v2.Frame.Height); // 98-5-2-2=89
-
-			Assert.Equal ("Factor(0.1,False)", v3.Width.ToString ());
-			Assert.Equal ("Factor(0.1,False)", v3.Height.ToString ());
-			Assert.Equal (9, v3.Frame.Width);  // 98*10%=9
-			Assert.Equal (9, v3.Frame.Height); // 98*10%=9
-
-			Assert.Equal ("Absolute(50)", v4.Width.ToString ());
-			Assert.Equal ("Absolute(50)", v4.Height.ToString ());
-			Assert.Equal (50,             v4.Frame.Width);
-			Assert.Equal (50,             v4.Frame.Height);
-
-			Assert.Equal ("Combine(View(Width,Button(v1)(2,7,47,89))-View(Width,Button(v3)(0,0,9,9)))", v5.Width.ToString ());
-			Assert.Equal ("Combine(View(Height,Button(v1)(2,7,47,89))-View(Height,Button(v3)(0,0,9,9)))", v5.Height.ToString ());
-			Assert.Equal (38, v5.Frame.Width);  // 47-9=38
-			Assert.Equal (80, v5.Frame.Height); // 89-9=80
-
-			Assert.Equal ("Factor(0.2,True)", v6.Width.ToString ());
-			Assert.Equal ("Factor(0.2,True)", v6.Height.ToString ());
-			Assert.Equal (9, v6.Frame.Width);  // 47*20%=9
-			Assert.Equal (18, v6.Frame.Height); // 89*20%=18
-
-			w.Width = 200;
-			Assert.True (t.LayoutNeeded);
-			w.Height = 200;
-			t.LayoutSubviews ();
-
-			Assert.Equal ("Absolute(200)", w.Width.ToString ());
-			Assert.Equal ("Absolute(200)", w.Height.ToString ());
-			Assert.Equal (200,             w.Frame.Width);
-			Assert.Equal (200,             w.Frame.Height);
-
-			f1.Text = "Frame1";
-			Assert.Equal ("Factor(0.5,False)", f1.Width.ToString ());
-			Assert.Equal ("Absolute(5)",       f1.Height.ToString ());
-			Assert.Equal (99,                  f1.Frame.Width); // 100-1=99
-			Assert.Equal (5,                   f1.Frame.Height);
-
-			f2.Text = "Frame2";
-			Assert.Equal ("Fill(0)",     f2.Width.ToString ());
-			Assert.Equal ("Absolute(5)", f2.Height.ToString ());
-			Assert.Equal (99,            f2.Frame.Width); // 100-1=99
-			Assert.Equal (5,             f2.Frame.Height);
-
-			v1.Text = "Button1";
-			Assert.Equal ("Combine(View(Width,FrameView(f1)(0,0,99,5))-Absolute(2))", v1.Width.ToString ());
-			Assert.Equal ("Combine(Fill(0)-Absolute(2))", v1.Height.ToString ());
-			Assert.Equal (97, v1.Frame.Width);  // 99-2=97
-			Assert.Equal (189, v1.Frame.Height); // 198-2-7=189
-
-			v2.Text = "Button2";
-			Assert.Equal ("Combine(View(Width,FrameView(f2)(99,0,99,5))-Absolute(2))", v2.Width.ToString ());
-			Assert.Equal ("Combine(Fill(0)-Absolute(2))", v2.Height.ToString ());
-			Assert.Equal (97, v2.Frame.Width);  // 99-2=97
-			Assert.Equal (189, v2.Frame.Height); // 198-2-7=189
-
-			v3.Text = "Button3";
-			Assert.Equal ("Factor(0.1,False)", v3.Width.ToString ());
-			Assert.Equal ("Factor(0.1,False)", v3.Height.ToString ());
-			Assert.Equal (19, v3.Frame.Width);  // 198*10%=19 * Percent is related to the super-view if it isn't null otherwise the view width
-			Assert.Equal (19, v3.Frame.Height); // 199*10%=19
-
-			v4.Text = "Button4";
-			v4.AutoSize = false;
-			Assert.Equal ("Absolute(50)", v4.Width.ToString ());
-			Assert.Equal ("Absolute(50)", v4.Height.ToString ());
-			Assert.Equal (50,             v4.Frame.Width);
-			Assert.Equal (50,             v4.Frame.Height);
-			v4.AutoSize = true;
-			Assert.Equal ("Absolute(11)", v4.Width.ToString ());
-			Assert.Equal ("Absolute(1)", v4.Height.ToString ());
-			Assert.Equal (11, v4.Frame.Width);  // 11 is the text length and because is Dim.DimAbsolute
-			Assert.Equal (1, v4.Frame.Height); // 1 because is Dim.DimAbsolute
-
-			v5.Text = "Button5";
-			Assert.Equal ("Combine(View(Width,Button(v1)(2,7,97,189))-View(Width,Button(v3)(0,0,19,19)))", v5.Width.ToString ());
-			Assert.Equal ("Combine(View(Height,Button(v1)(2,7,97,189))-View(Height,Button(v3)(0,0,19,19)))", v5.Height.ToString ());
-			Assert.Equal (78, v5.Frame.Width);  // 97-9=78
-			Assert.Equal (170, v5.Frame.Height); // 189-19=170
-
-			v6.Text = "Button6";
-			Assert.Equal ("Factor(0.2,True)", v6.Width.ToString ());
-			Assert.Equal ("Factor(0.2,True)", v6.Height.ToString ());
-			Assert.Equal (19, v6.Frame.Width);  // 99*20%=19
-			Assert.Equal (38, v6.Frame.Height); // 198-7*20=18
-		};
-
-		Application.Iteration += (s, a) => Application.RequestStop ();
-
-		Application.Run ();
-	}
-
-	// See #2461
-	//[Fact]
-	//public void Dim_Referencing_SuperView_Throws ()
-	//{
-	//	var super = new View ("super") {
-	//		Width = 10,
-	//		Height = 10
-	//	};
-	//	var view = new View ("view") {
-	//		Width = Dim.Width (super),	// this is not allowed
-	//		Height = Dim.Height (super),    // this is not allowed
-	//	};
-
-	//	super.Add (view);
-	//	super.BeginInit ();
-	//	super.EndInit ();
-	//	Assert.Throws<InvalidOperationException> (() => super.LayoutSubviews ());
-	//}
-
-	// TODO: This actually a SetRelativeLayout/LayoutSubViews test and should be moved
-	// TODO: A new test that calls SetRelativeLayout directly is needed.
-
-	/// <summary>
-	/// This is an intentionally obtuse test. See https://github.com/gui-cs/Terminal.Gui/issues/2461
-	/// </summary>
-	[Fact]
-	[TestRespondersDisposed]
-	public void DimCombine_ObtuseScenario_Throw_If_SuperView_Refs_SubView ()
-	{
-		var t = new View { Width = 80, Height = 25 };
-
-		var w = new Window {
-			Width = Dim.Width (t) - 2,  // 78
-			Height = Dim.Height (t) - 2 // 23
-		};
-		var f = new FrameView ();
-		var v1 = new View {
-			Width = Dim.Width (w) - 2,  // 76
-			Height = Dim.Height (w) - 2 // 21
-		};
-		var v2 = new View {
-			Width = Dim.Width (v1) - 2,  // 74
-			Height = Dim.Height (v1) - 2 // 19
-		};
-
-		f.Add (v1, v2);
-		w.Add (f);
-		t.Add (w);
-		t.BeginInit ();
-		t.EndInit ();
-
-		f.Width = Dim.Width (t) - Dim.Width (v2);    // 80 - 74 = 6
-		f.Height = Dim.Height (t) - Dim.Height (v2); // 25 - 19 = 6
-
-		Assert.Throws<InvalidOperationException> (t.LayoutSubviews);
-		Assert.Equal (80, t.Frame.Width);
-		Assert.Equal (25, t.Frame.Height);
-		Assert.Equal (78, w.Frame.Width);
-		Assert.Equal (23, w.Frame.Height);
-		Assert.Equal (6, f.Frame.Width);
-		Assert.Equal (6, f.Frame.Height);
-		Assert.Equal (76, v1.Frame.Width);
-		Assert.Equal (21, v1.Frame.Height);
-		Assert.Equal (74, v2.Frame.Width);
-		Assert.Equal (19, v2.Frame.Height);
-		t.Dispose ();
-	}
-
-	// TODO: This actually a SetRelativeLayout/LayoutSubViews test and should be moved
-	// TODO: A new test that calls SetRelativeLayout directly is needed.
-	[Fact]
-	[TestRespondersDisposed]
-	public void DimCombine_ObtuseScenario_Does_Not_Throw_If_Two_SubViews_Refs_The_Same_SuperView ()
-	{
-		var t = new View ("top") { Width = 80, Height = 25 };
-
-		var w = new Window {
-			Width = Dim.Width (t) - 2,  // 78
-			Height = Dim.Height (t) - 2 // 23
-		};
-		var f = new FrameView ();
-		var v1 = new View {
-			Width = Dim.Width (w) - 2,  // 76
-			Height = Dim.Height (w) - 2 // 21
-		};
-		var v2 = new View {
-			Width = Dim.Width (v1) - 2,  // 74
-			Height = Dim.Height (v1) - 2 // 19
-		};
-
-		f.Add (v1, v2);
-		w.Add (f);
-		t.Add (w);
-		t.BeginInit ();
-		t.EndInit ();
-
-		f.Width = Dim.Width (t) - Dim.Width (w) + 4;    // 80 - 74 = 6
-		f.Height = Dim.Height (t) - Dim.Height (w) + 4; // 25 - 19 = 6
-
-		// BUGBUG: v2 - f references t and w here; t is f's super-superview and w is f's superview. This is supported!
-		var exception = Record.Exception (t.LayoutSubviews);
-		Assert.Null (exception);
-		Assert.Equal (80, t.Frame.Width);
-		Assert.Equal (25, t.Frame.Height);
-		Assert.Equal (78, w.Frame.Width);
-		Assert.Equal (23, w.Frame.Height);
-		Assert.Equal (6,  f.Frame.Width);
-		Assert.Equal (6,  f.Frame.Height);
-		Assert.Equal (76, v1.Frame.Width);
-		Assert.Equal (21, v1.Frame.Height);
-		Assert.Equal (74, v2.Frame.Width);
-		Assert.Equal (19, v2.Frame.Height);
-		t.Dispose ();
-	}
-
-	// TODO: This actually a SetRelativeLayout/LayoutSubViews test and should be moved
-	// TODO: A new test that calls SetRelativeLayout directly is needed.
-	[Fact]
-	[TestRespondersDisposed]
-	public void PosCombine_View_Not_Added_Throws ()
-	{
-		var t = new View { Width = 80, Height = 50 };
-
-		var super = new View {
-			Width = Dim.Width (t) - 2,
-			Height = Dim.Height (t) - 2
-		};
-		t.Add (super);
-
-		var sub = new View ();
-		super.Add (sub);
-
-		var v1 = new View {
-			Width = Dim.Width (super) - 2,
-			Height = Dim.Height (super) - 2
-		};
-		var v2 = new View {
-			Width = Dim.Width (v1) - 2,
-			Height = Dim.Height (v1) - 2
-		};
-		sub.Add (v1);
-		// v2 not added to sub; should cause exception on Layout since it's referenced by sub.
-		sub.Width = Dim.Fill () - Dim.Width (v2);
-		sub.Height = Dim.Fill () - Dim.Height (v2);
-
-		t.BeginInit ();
-		t.EndInit ();
-
-		Assert.Throws<InvalidOperationException> (() => t.LayoutSubviews ());
-		t.Dispose ();
-		v2.Dispose ();
-	}
-
-	// TODO: This actually a SetRelativeLayout/LayoutSubViews test and should be moved
-	// A new test that does not depend on Application is needed.
-	[Fact]
-	[AutoInitShutdown]
-	public void Dim_Add_Operator ()
-	{
-		var top = Application.Top;
-
-		var view = new View { X = 0, Y = 0, Width = 20, Height = 0 };
-		var field = new TextField { X = 0, Y = Pos.Bottom (view), Width = 20 };
-		var count = 0;
-
-		field.KeyDown += (s, k) => {
-			if (k.KeyCode == KeyCode.Enter) {
-				field.Text = $"Label {count}";
-				var label = new Label (field.Text) { X = 0, Y = view.Bounds.Height, Width = 20 };
-				view.Add (label);
-				Assert.Equal ($"Label {count}",     label.Text);
-				Assert.Equal ($"Absolute({count})", label.Y.ToString ());
-
-				Assert.Equal ($"Absolute({count})", view.Height.ToString ());
-				view.Height += 1;
-				count++;
-				Assert.Equal ($"Absolute({count})", view.Height.ToString ());
-			}
-		};
-
-		Application.Iteration += (s, a) => {
-			while (count < 20) {
-				field.NewKeyDownEvent (new Key (KeyCode.Enter));
-			}
-
-			Application.RequestStop ();
-		};
-
-		var win = new Window ();
-		win.Add (view);
-		win.Add (field);
-
-		top.Add (win);
-
-		Application.Run (top);
-
-		Assert.Equal (20, count);
-	}
-
-	// TODO: This actually a SetRelativeLayout/LayoutSubViews test and should be moved
-	// TODO: A new test that calls SetRelativeLayout directly is needed.
-	[Fact]
-	[AutoInitShutdown]
-	public void Dim_Subtract_Operator ()
-	{
-		var top = Application.Top;
-
-		var view = new View { X = 0, Y = 0, Width = 20, Height = 0 };
-		var field = new TextField { X = 0, Y = Pos.Bottom (view), Width = 20 };
-		var count = 20;
-		var listLabels = new List<Label> ();
-
-		for (var i = 0; i < count; i++) {
-			field.Text = $"Label {i}";
-			var label = new Label (field.Text) { X = 0, Y = view.Bounds.Height, Width = 20 };
-			view.Add (label);
-			Assert.Equal ($"Label {i}", label.Text);
-			Assert.Equal ($"Absolute({i})", label.Y.ToString ());
-			listLabels.Add (label);
-
-			Assert.Equal ($"Absolute({i})", view.Height.ToString ());
-			view.Height += 1;
-			Assert.Equal ($"Absolute({i + 1})", view.Height.ToString ());
-		}
-
-		field.KeyDown += (s, k) => {
-			if (k.KeyCode == KeyCode.Enter) {
-				Assert.Equal ($"Label {count - 1}", listLabels [count - 1].Text);
-				view.Remove (listLabels [count - 1]);
-				listLabels [count - 1].Dispose ();
-
-				Assert.Equal ($"Absolute({count})", view.Height.ToString ());
-				view.Height -= 1;
-				count--;
-				Assert.Equal ($"Absolute({count})", view.Height.ToString ());
-			}
-		};
-
-		Application.Iteration += (s, a) => {
-			while (count > 0) {
-				field.NewKeyDownEvent (new Key (KeyCode.Enter));
-			}
-
-			Application.RequestStop ();
-		};
-
-		var win = new Window ();
-		win.Add (view);
-		win.Add (field);
-
-		top.Add (win);
-
-		Application.Run (top);
-
-		Assert.Equal (0, count);
-	}
-
-	[Fact]
-	[TestRespondersDisposed]
-	public void Internal_Tests ()
-	{
-		var dimFactor = new Dim.DimFactor (0.10F);
-		Assert.Equal (10, dimFactor.Anchor (100));
-
-		var dimAbsolute = new Dim.DimAbsolute (10);
-		Assert.Equal (10, dimAbsolute.Anchor (0));
-
-		var dimFill = new Dim.DimFill (1);
-		Assert.Equal (99, dimFill.Anchor (100));
-
-		var dimCombine = new Dim.DimCombine (true, dimFactor, dimAbsolute);
-		Assert.Equal (dimCombine._left,  dimFactor);
-		Assert.Equal (dimCombine._right, dimAbsolute);
-		Assert.Equal (20,                dimCombine.Anchor (100));
-
-		var view = new View (new Rect (20, 10, 20, 1));
-		var dimViewHeight = new Dim.DimView (view, 0);
-		Assert.Equal (1, dimViewHeight.Anchor (0));
-		var dimViewWidth = new Dim.DimView (view, 1);
-		Assert.Equal (20, dimViewWidth.Anchor (0));
-
-		view.Dispose ();
-	}
-
-	[Fact]
-	public void Function_SetsValue ()
-	{
-		var text = "Test";
-		var dim = Dim.Function (() => text.Length);
-		Assert.Equal ("DimFunc(4)", dim.ToString ());
-
-		text = "New Test";
-		Assert.Equal ("DimFunc(8)", dim.ToString ());
-
-		text = "";
-		Assert.Equal ("DimFunc(0)", dim.ToString ());
-	}
-
-	[Fact]
-	public void Function_Equal ()
-	{
-		var f1 = () => 0;
-		var f2 = () => 0;
-
-		var dim1 = Dim.Function (f1);
-		var dim2 = Dim.Function (f2);
-		Assert.Equal (dim1, dim2);
-
-		f2 = () => 1;
-		dim2 = Dim.Function (f2);
-		Assert.NotEqual (dim1, dim2);
-	}
-
-	// TODO: This actually a SetRelativeLayout/LayoutSubViews test and should be moved
-	// TODO: A new test that calls SetRelativeLayout directly is needed.
-	[Theory]
-	[AutoInitShutdown]
-	[InlineData (0, true)]
-	[InlineData (0, false)]
-	[InlineData (50, true)]
-	[InlineData (50, false)]
-	public void DimPercentPlusOne (int startingDistance, bool testHorizontal)
-	{
-		var container = new View {
-			Width = 100,
-			Height = 100
-		};
-
-		var label = new Label {
-			X = testHorizontal ? startingDistance : 0,
-			Y = testHorizontal ? 0 : startingDistance,
-			Width = testHorizontal ? Dim.Percent (50) + 1 : 1,
-			Height = testHorizontal ? 1 : Dim.Percent (50) + 1
-		};
-
-		container.Add (label);
-		Application.Top.Add (container);
-		Application.Top.BeginInit ();
-		Application.Top.EndInit ();
-		Application.Top.LayoutSubviews ();
-
-		Assert.Equal (100, container.Frame.Width);
-		Assert.Equal (100, container.Frame.Height);
-
-		if (testHorizontal) {
-			Assert.Equal (51, label.Frame.Width);
-			Assert.Equal (1,  label.Frame.Height);
-		} else {
-			Assert.Equal (1,  label.Frame.Width);
-			Assert.Equal (51, label.Frame.Height);
-		}
-	}
-
-	// TODO: This actually a SetRelativeLayout/LayoutSubViews test and should be moved
-	// TODO: A new test that calls SetRelativeLayout directly is needed.
-	[Fact]
-	[TestRespondersDisposed]
-	public void Dim_Referencing_SuperView_Does_Not_Throw ()
-	{
-		var super = new View ("super") {
-			Width = 10,
-			Height = 10
-		};
-		var view = new View ("view") {
-			Width = Dim.Width (super),  // this is allowed
-			Height = Dim.Height (super) // this is allowed
-		};
-
-		super.Add (view);
-		super.BeginInit ();
-		super.EndInit ();
-
-		var exception = Record.Exception (super.LayoutSubviews);
-		Assert.Null (exception);
-		super.Dispose ();
-	}
-
-	// TODO: This actually a SetRelativeLayout/LayoutSubViews test and should be moved
-	// TODO: A new test that calls SetRelativeLayout directly is needed.
-	[Fact]
-	[TestRespondersDisposed]
-	public void Dim_SyperView_Referencing_SubView_Throws ()
-	{
-		var super = new View ("super") {
-			Width = 10,
-			Height = 10
-		};
-		var view2 = new View ("view2") {
-			Width = 10,
-			Height = 10
-		};
-		var view = new View ("view") {
-			Width = Dim.Width (view2),  // this is not allowed
-			Height = Dim.Height (view2) // this is not allowed
-		};
-
-		view.Add (view2);
-		super.Add (view);
-		super.BeginInit ();
-		super.EndInit ();
-
-		Assert.Throws<InvalidOperationException> (super.LayoutSubviews);
-		super.Dispose ();
-	}
-}
+    }
+
+    [Fact]
+    public void Width_Set_To_Null_Throws ()
+    {
+        Dim dim = Dim.Width (null);
+        Assert.Throws<NullReferenceException> (() => dim.ToString ());
+    }
+}

Файлын зөрүү хэтэрхий том тул дарагдсан байна
+ 501 - 465
UnitTests/View/Layout/LayoutTests.cs


+ 260 - 261
UnitTests/Views/AppendAutocompleteTests.cs

@@ -1,265 +1,264 @@
-using System;
-using System.Collections.Generic;
-using System.Linq;
-using Xunit;
 using Xunit.Abstractions;
 
 namespace Terminal.Gui.TextTests;
 
-public class AppendAutocompleteTests {
-	readonly ITestOutputHelper output;
-
-	public AppendAutocompleteTests (ITestOutputHelper output) => this.output = output;
-
-	[Fact]
-	[AutoInitShutdown]
-	public void TestAutoAppend_ShowThenAccept_MatchCase ()
-	{
-		var tf = GetTextFieldsInView ();
-
-		tf.Autocomplete = new AppendAutocomplete (tf);
-		var generator = (SingleWordSuggestionGenerator)tf.Autocomplete.SuggestionGenerator;
-		generator.AllSuggestions = new List<string> { "fish" };
-
-		tf.Draw ();
-		tf.PositionCursor ();
-		TestHelpers.AssertDriverContentsAre ("", output);
-
-		tf.NewKeyDownEvent (new Key ('f'));
-
-		tf.Draw ();
-		tf.PositionCursor ();
-		TestHelpers.AssertDriverContentsAre ("fish", output);
-		Assert.Equal ("f", tf.Text);
-
-		Application.Driver.SendKeys ('\t', ConsoleKey.Tab, false, false, false);
-
-		tf.Draw ();
-		TestHelpers.AssertDriverContentsAre ("fish", output);
-		Assert.Equal ("fish", tf.Text);
-
-		// Tab should autcomplete but not move focus
-		Assert.Same (tf, Application.Top.Focused);
-
-		// Second tab should move focus (nothing to autocomplete)
-		Application.Driver.SendKeys ('\t', ConsoleKey.Tab, false, false, false);
-		Assert.NotSame (tf, Application.Top.Focused);
-	}
-
-	[Fact]
-	[AutoInitShutdown]
-	public void TestAutoAppend_ShowThenAccept_CasesDiffer ()
-	{
-		var tf = GetTextFieldsInView ();
-
-		tf.Autocomplete = new AppendAutocomplete (tf);
-		var generator = (SingleWordSuggestionGenerator)tf.Autocomplete.SuggestionGenerator;
-		generator.AllSuggestions = new List<string> { "FISH" };
-
-		tf.Draw ();
-		tf.PositionCursor ();
-		TestHelpers.AssertDriverContentsAre ("", output);
-		tf.NewKeyDownEvent (new Key ((KeyCode)'m'));
-		tf.NewKeyDownEvent (new Key ((KeyCode)'y'));
-		tf.NewKeyDownEvent (new Key (KeyCode.Space));
-		tf.NewKeyDownEvent (new Key ((KeyCode)'f'));
-		Assert.Equal ("my f", tf.Text);
-
-		// Even though there is no match on case we should still get the suggestion
-		tf.Draw ();
-		tf.PositionCursor ();
-		TestHelpers.AssertDriverContentsAre ("my fISH", output);
-		Assert.Equal ("my f", tf.Text);
-
-		// When tab completing the case of the whole suggestion should be applied
-		Application.Driver.SendKeys ('\t', ConsoleKey.Tab, false, false, false);
-		tf.Draw ();
-		TestHelpers.AssertDriverContentsAre ("my FISH", output);
-		Assert.Equal ("my FISH", tf.Text);
-	}
-
-	[Fact]
-	[AutoInitShutdown]
-	public void TestAutoAppend_AfterCloseKey_NoAutocomplete ()
-	{
-		var tf = GetTextFieldsInViewSuggesting ("fish");
-
-		// f is typed and suggestion is "fish"
-		Application.Driver.SendKeys ('f', ConsoleKey.F, false, false, false);
-		tf.Draw ();
-		tf.PositionCursor ();
-		TestHelpers.AssertDriverContentsAre ("fish", output);
-		Assert.Equal ("f", tf.Text);
-
-		// When cancelling autocomplete
-		Application.Driver.SendKeys ('e', ConsoleKey.Escape, false, false, false);
-
-		// Suggestion should disappear
-		tf.Draw ();
-		TestHelpers.AssertDriverContentsAre ("f", output);
-		Assert.Equal ("f", tf.Text);
-
-		// Still has focus though
-		Assert.Same (tf, Application.Top.Focused);
-
-		// But can tab away
-		Application.Driver.SendKeys ('\t', ConsoleKey.Tab, false, false, false);
-		Assert.NotSame (tf, Application.Top.Focused);
-	}
-
-	[Fact]
-	[AutoInitShutdown]
-	public void TestAutoAppend_AfterCloseKey_ReappearsOnLetter ()
-	{
-		var tf = GetTextFieldsInViewSuggesting ("fish");
-
-		// f is typed and suggestion is "fish"
-		Application.Driver.SendKeys ('f', ConsoleKey.F, false, false, false);
-		tf.Draw ();
-		tf.PositionCursor ();
-		TestHelpers.AssertDriverContentsAre ("fish", output);
-		Assert.Equal ("f", tf.Text);
-
-		// When cancelling autocomplete
-		Application.Driver.SendKeys ('\0', ConsoleKey.Escape, false, false, false);
-
-		// Suggestion should disappear
-		tf.Draw ();
-		TestHelpers.AssertDriverContentsAre ("f", output);
-		Assert.Equal ("f", tf.Text);
-
-		// Should reappear when you press next letter
-		Application.Driver.SendKeys ('i', ConsoleKey.I, false, false, false);
-		tf.Draw ();
-		tf.PositionCursor ();
-		TestHelpers.AssertDriverContentsAre ("fish", output);
-		Assert.Equal ("fi", tf.Text);
-	}
-
-	[Theory]
-	[AutoInitShutdown]
-	[InlineData ("ffffffffffffffffffffffffff", "ffffffffff")]
-	[InlineData ("f234567890",                 "f234567890")]
-	[InlineData ("fisérables",                 "fisérables")]
-	public void TestAutoAppendRendering_ShouldNotOverspill (string overspillUsing, string expectRender)
-	{
-		var tf = GetTextFieldsInViewSuggesting (overspillUsing);
-
-		// f is typed we should only see 'f' up to size of View (10)
-		Application.Driver.SendKeys ('f', ConsoleKey.F, false, false, false);
-		tf.Draw ();
-		tf.PositionCursor ();
-		TestHelpers.AssertDriverContentsAre (expectRender, output);
-		Assert.Equal ("f", tf.Text);
-	}
-
-	[Theory]
-	[AutoInitShutdown]
-	[InlineData (ConsoleKey.UpArrow)]
-	[InlineData (ConsoleKey.DownArrow)]
-	public void TestAutoAppend_CycleSelections (ConsoleKey cycleKey)
-	{
-		var tf = GetTextFieldsInViewSuggesting ("fish", "friend");
-
-		// f is typed and suggestion is "fish"
-		Application.Driver.SendKeys ('f', ConsoleKey.F, false, false, false);
-		tf.Draw ();
-		tf.PositionCursor ();
-		TestHelpers.AssertDriverContentsAre ("fish", output);
-		Assert.Equal ("f", tf.Text);
-
-		// When cycling autocomplete
-		Application.Driver.SendKeys (' ', cycleKey, false, false, false);
-
-		tf.Draw ();
-		tf.PositionCursor ();
-		TestHelpers.AssertDriverContentsAre ("friend", output);
-		Assert.Equal ("f", tf.Text);
-
-		// Should be able to cycle in circles endlessly
-		Application.Driver.SendKeys (' ', cycleKey, false, false, false);
-		tf.Draw ();
-		tf.PositionCursor ();
-		TestHelpers.AssertDriverContentsAre ("fish", output);
-		Assert.Equal ("f", tf.Text);
-	}
-
-	[Fact]
-	[AutoInitShutdown]
-	public void TestAutoAppend_NoRender_WhenNoMatch ()
-	{
-		var tf = GetTextFieldsInViewSuggesting ("fish");
-
-		// f is typed and suggestion is "fish"
-		Application.Driver.SendKeys ('f', ConsoleKey.F, false, false, false);
-		tf.Draw ();
-		tf.PositionCursor ();
-		TestHelpers.AssertDriverContentsAre ("fish", output);
-		Assert.Equal ("f", tf.Text);
-
-		// x is typed and suggestion should disappear
-		Application.Driver.SendKeys ('x', ConsoleKey.X, false, false, false);
-		tf.Draw ();
-		TestHelpers.AssertDriverContentsAre ("fx", output);
-		Assert.Equal ("fx", tf.Text);
-	}
-
-	[Fact]
-	[AutoInitShutdown]
-	public void TestAutoAppend_NoRender_WhenCursorNotAtEnd ()
-	{
-		var tf = GetTextFieldsInViewSuggesting ("fish");
-
-		// f is typed and suggestion is "fish"
-		Application.Driver.SendKeys ('f', ConsoleKey.F, false, false, false);
-		tf.Draw ();
-		tf.PositionCursor ();
-		TestHelpers.AssertDriverContentsAre ("fish", output);
-		Assert.Equal ("f", tf.Text);
-
-		// add a space then go back 1
-		Application.Driver.SendKeys (' ', ConsoleKey.Spacebar,  false, false, false);
-		Application.Driver.SendKeys ('<', ConsoleKey.LeftArrow, false, false, false);
-
-		tf.Draw ();
-		TestHelpers.AssertDriverContentsAre ("f", output);
-		Assert.Equal ("f ", tf.Text);
-	}
-
-
-	TextField GetTextFieldsInViewSuggesting (params string [] suggestions)
-	{
-		var tf = GetTextFieldsInView ();
-
-		tf.Autocomplete = new AppendAutocomplete (tf);
-		var generator = (SingleWordSuggestionGenerator)tf.Autocomplete.SuggestionGenerator;
-		generator.AllSuggestions = suggestions.ToList ();
-
-		tf.Draw ();
-		tf.PositionCursor ();
-		TestHelpers.AssertDriverContentsAre ("", output);
-
-		return tf;
-	}
-
-	TextField GetTextFieldsInView ()
-	{
-		var tf = new TextField {
-					       Width = 10
-				       };
-		var tf2 = new TextField {
-						Y     = 1,
-						Width = 10
-					};
-
-		var top = Application.Top;
-		top.Add (tf);
-		top.Add (tf2);
-
-		Application.Begin (top);
-
-		Assert.Same (tf, top.Focused);
-
-		return tf;
-	}
-}
+public class AppendAutocompleteTests
+{
+    private readonly ITestOutputHelper output;
+
+    public AppendAutocompleteTests (ITestOutputHelper output) { this.output = output; }
+
+    [Fact]
+    [AutoInitShutdown]
+    public void TestAutoAppend_AfterCloseKey_NoAutocomplete ()
+    {
+        TextField tf = GetTextFieldsInViewSuggesting ("fish");
+
+        // f is typed and suggestion is "fish"
+        Application.Driver.SendKeys ('f', ConsoleKey.F, false, false, false);
+        tf.Draw ();
+        tf.PositionCursor ();
+        TestHelpers.AssertDriverContentsAre ("fish", output);
+        Assert.Equal ("f", tf.Text);
+
+        // When cancelling autocomplete
+        Application.Driver.SendKeys ('e', ConsoleKey.Escape, false, false, false);
+
+        // Suggestion should disappear
+        tf.Draw ();
+        TestHelpers.AssertDriverContentsAre ("f", output);
+        Assert.Equal ("f", tf.Text);
+
+        // Still has focus though
+        Assert.Same (tf, Application.Top.Focused);
+
+        // But can tab away
+        Application.Driver.SendKeys ('\t', ConsoleKey.Tab, false, false, false);
+        Assert.NotSame (tf, Application.Top.Focused);
+    }
+
+    [Fact]
+    [AutoInitShutdown]
+    public void TestAutoAppend_AfterCloseKey_ReappearsOnLetter ()
+    {
+        TextField tf = GetTextFieldsInViewSuggesting ("fish");
+
+        // f is typed and suggestion is "fish"
+        Application.Driver.SendKeys ('f', ConsoleKey.F, false, false, false);
+        tf.Draw ();
+        tf.PositionCursor ();
+        TestHelpers.AssertDriverContentsAre ("fish", output);
+        Assert.Equal ("f", tf.Text);
+
+        // When cancelling autocomplete
+        Application.Driver.SendKeys ('\0', ConsoleKey.Escape, false, false, false);
+
+        // Suggestion should disappear
+        tf.Draw ();
+        TestHelpers.AssertDriverContentsAre ("f", output);
+        Assert.Equal ("f", tf.Text);
+
+        // Should reappear when you press next letter
+        Application.Driver.SendKeys ('i', ConsoleKey.I, false, false, false);
+        tf.Draw ();
+        tf.PositionCursor ();
+        TestHelpers.AssertDriverContentsAre ("fish", output);
+        Assert.Equal ("fi", tf.Text);
+    }
+
+    [Theory]
+    [AutoInitShutdown]
+    [InlineData (ConsoleKey.UpArrow)]
+    [InlineData (ConsoleKey.DownArrow)]
+    public void TestAutoAppend_CycleSelections (ConsoleKey cycleKey)
+    {
+        TextField tf = GetTextFieldsInViewSuggesting ("fish", "friend");
+
+        // f is typed and suggestion is "fish"
+        Application.Driver.SendKeys ('f', ConsoleKey.F, false, false, false);
+        tf.Draw ();
+        tf.PositionCursor ();
+        TestHelpers.AssertDriverContentsAre ("fish", output);
+        Assert.Equal ("f", tf.Text);
+
+        // When cycling autocomplete
+        Application.Driver.SendKeys (' ', cycleKey, false, false, false);
+
+        tf.Draw ();
+        tf.PositionCursor ();
+        TestHelpers.AssertDriverContentsAre ("friend", output);
+        Assert.Equal ("f", tf.Text);
+
+        // Should be able to cycle in circles endlessly
+        Application.Driver.SendKeys (' ', cycleKey, false, false, false);
+        tf.Draw ();
+        tf.PositionCursor ();
+        TestHelpers.AssertDriverContentsAre ("fish", output);
+        Assert.Equal ("f", tf.Text);
+    }
+
+    [Fact]
+    [AutoInitShutdown]
+    public void TestAutoAppend_NoRender_WhenCursorNotAtEnd ()
+    {
+        TextField tf = GetTextFieldsInViewSuggesting ("fish");
+
+        // f is typed and suggestion is "fish"
+        Application.Driver.SendKeys ('f', ConsoleKey.F, false, false, false);
+        tf.Draw ();
+        tf.PositionCursor ();
+        TestHelpers.AssertDriverContentsAre ("fish", output);
+        Assert.Equal ("f", tf.Text);
+
+        // add a space then go back 1
+        Application.Driver.SendKeys (' ', ConsoleKey.Spacebar, false, false, false);
+        Application.Driver.SendKeys ('<', ConsoleKey.LeftArrow, false, false, false);
+
+        tf.Draw ();
+        TestHelpers.AssertDriverContentsAre ("f", output);
+        Assert.Equal ("f ", tf.Text);
+    }
+
+    [Fact]
+    [AutoInitShutdown]
+    public void TestAutoAppend_NoRender_WhenNoMatch ()
+    {
+        TextField tf = GetTextFieldsInViewSuggesting ("fish");
+
+        // f is typed and suggestion is "fish"
+        Application.Driver.SendKeys ('f', ConsoleKey.F, false, false, false);
+        tf.Draw ();
+        tf.PositionCursor ();
+        TestHelpers.AssertDriverContentsAre ("fish", output);
+        Assert.Equal ("f", tf.Text);
+
+        // x is typed and suggestion should disappear
+        Application.Driver.SendKeys ('x', ConsoleKey.X, false, false, false);
+        tf.Draw ();
+        TestHelpers.AssertDriverContentsAre ("fx", output);
+        Assert.Equal ("fx", tf.Text);
+    }
+
+    [Fact]
+    [AutoInitShutdown]
+    public void TestAutoAppend_ShowThenAccept_CasesDiffer ()
+    {
+        TextField tf = GetTextFieldsInView ();
+
+        tf.Autocomplete = new AppendAutocomplete (tf);
+        var generator = (SingleWordSuggestionGenerator)tf.Autocomplete.SuggestionGenerator;
+        generator.AllSuggestions = new List<string> { "FISH" };
+
+        tf.Draw ();
+        tf.PositionCursor ();
+        TestHelpers.AssertDriverContentsAre ("", output);
+        tf.NewKeyDownEvent (new Key ((KeyCode)'m'));
+        tf.NewKeyDownEvent (new Key ((KeyCode)'y'));
+        tf.NewKeyDownEvent (new Key (KeyCode.Space));
+        tf.NewKeyDownEvent (new Key ((KeyCode)'f'));
+        Assert.Equal ("my f", tf.Text);
+
+        // Even though there is no match on case we should still get the suggestion
+        tf.Draw ();
+        tf.PositionCursor ();
+        TestHelpers.AssertDriverContentsAre ("my fISH", output);
+        Assert.Equal ("my f", tf.Text);
+
+        // When tab completing the case of the whole suggestion should be applied
+        Application.Driver.SendKeys ('\t', ConsoleKey.Tab, false, false, false);
+        tf.Draw ();
+        TestHelpers.AssertDriverContentsAre ("my FISH", output);
+        Assert.Equal ("my FISH", tf.Text);
+    }
+
+    [Fact]
+    [AutoInitShutdown]
+    public void TestAutoAppend_ShowThenAccept_MatchCase ()
+    {
+        TextField tf = GetTextFieldsInView ();
+
+        tf.Autocomplete = new AppendAutocomplete (tf);
+        var generator = (SingleWordSuggestionGenerator)tf.Autocomplete.SuggestionGenerator;
+        generator.AllSuggestions = new List<string> { "fish" };
+
+        tf.Draw ();
+        tf.PositionCursor ();
+        TestHelpers.AssertDriverContentsAre ("", output);
+
+        tf.NewKeyDownEvent (new Key ('f'));
+
+        tf.Draw ();
+        tf.PositionCursor ();
+        TestHelpers.AssertDriverContentsAre ("fish", output);
+        Assert.Equal ("f", tf.Text);
+
+        Application.Driver.SendKeys ('\t', ConsoleKey.Tab, false, false, false);
+
+        tf.Draw ();
+        TestHelpers.AssertDriverContentsAre ("fish", output);
+        Assert.Equal ("fish", tf.Text);
+
+        // Tab should autcomplete but not move focus
+        Assert.Same (tf, Application.Top.Focused);
+
+        // Second tab should move focus (nothing to autocomplete)
+        Application.Driver.SendKeys ('\t', ConsoleKey.Tab, false, false, false);
+        Assert.NotSame (tf, Application.Top.Focused);
+    }
+
+    [Theory]
+    [AutoInitShutdown]
+    [InlineData ("ffffffffffffffffffffffffff", "ffffffffff")]
+    [InlineData ("f234567890", "f234567890")]
+    [InlineData ("fisérables", "fisérables")]
+    public void TestAutoAppendRendering_ShouldNotOverspill (string overspillUsing, string expectRender)
+    {
+        TextField tf = GetTextFieldsInViewSuggesting (overspillUsing);
+
+        // f is typed we should only see 'f' up to size of View (10)
+        Application.Driver.SendKeys ('f', ConsoleKey.F, false, false, false);
+        tf.Draw ();
+        tf.PositionCursor ();
+        TestHelpers.AssertDriverContentsAre (expectRender, output);
+        Assert.Equal ("f", tf.Text);
+    }
+
+    private TextField GetTextFieldsInView ()
+    {
+        var tf = new TextField
+        {
+            Width = 10
+        };
+
+        var tf2 = new TextField
+        {
+            Y = 1,
+            Width = 10
+        };
+
+        Toplevel top = Application.Top;
+        top.Add (tf);
+        top.Add (tf2);
+
+        Application.Begin (top);
+
+        Assert.Same (tf, top.Focused);
+
+        return tf;
+    }
+
+    private TextField GetTextFieldsInViewSuggesting (params string [] suggestions)
+    {
+        TextField tf = GetTextFieldsInView ();
+
+        tf.Autocomplete = new AppendAutocomplete (tf);
+        var generator = (SingleWordSuggestionGenerator)tf.Autocomplete.SuggestionGenerator;
+        generator.AllSuggestions = suggestions.ToList ();
+
+        tf.Draw ();
+        tf.PositionCursor ();
+        TestHelpers.AssertDriverContentsAre ("", output);
+
+        return tf;
+    }
+}

Файлын зөрүү хэтэрхий том тул дарагдсан байна
+ 573 - 565
UnitTests/Views/ToplevelTests.cs


+ 191 - 167
UnitTests/Views/WindowTests.cs

@@ -1,128 +1,81 @@
-using Xunit;
 using Xunit.Abstractions;
 
 namespace Terminal.Gui.ViewsTests;
 
-public class WindowTests {
-	readonly ITestOutputHelper _output;
-
-	public WindowTests (ITestOutputHelper output) => _output = output;
-
-	[Fact]
-	public void New_Initializes ()
-	{
-		// Parameterless
-		var r = new Window ();
-		Assert.NotNull (r);
-		Assert.Equal (string.Empty, r.Title);
-		// Toplevels have Width/Height set to Dim.Fill
-		Assert.Equal (LayoutStyle.Computed, r.LayoutStyle);
-		// If there's no SuperView, Top, or Driver, the default Fill width is int.MaxValue
-		Assert.Equal ("Window()(0,0,2147483647,2147483647)", r.ToString ());
-		Assert.True (r.CanFocus);
-		Assert.False (r.HasFocus);
-		Assert.Equal (new Rect (0, 0, 2147483645, 2147483645), r.Bounds);
-		Assert.Equal (new Rect (0, 0, 2147483647, 2147483647), r.Frame);
-		Assert.Null (r.Focused);
-		Assert.NotNull (r.ColorScheme);
-		Assert.Equal (0, r.X);
-		Assert.Equal (0, r.Y);
-		Assert.Equal (Dim.Fill (), r.Width);
-		Assert.Equal (Dim.Fill (), r.Height);
-		Assert.False (r.IsCurrentTop);
-		Assert.Empty (r.Id);
-		Assert.False (r.WantContinuousButtonPressed);
-		Assert.False (r.WantMousePositionReports);
-		Assert.Null (r.SuperView);
-		Assert.Null (r.MostFocused);
-		Assert.Equal (TextDirection.LeftRight_TopBottom, r.TextDirection);
-
-		// Empty Rect
-		r = new Window (Rect.Empty) { Title = "title" };
-		Assert.NotNull (r);
-		Assert.Equal ("title", r.Title);
-		Assert.Equal (LayoutStyle.Absolute, r.LayoutStyle);
-		Assert.Equal ("title", r.Title);
-		Assert.Equal (LayoutStyle.Absolute, r.LayoutStyle);
-		Assert.Equal ("Window(title)(0,0,0,0)", r.ToString ());
-		Assert.True (r.CanFocus);
-		Assert.False (r.HasFocus);
-		Assert.Equal (new Rect (0, 0, 0, 0), r.Bounds);
-		Assert.Equal (new Rect (0, 0, 0, 0), r.Frame);
-		Assert.Null (r.Focused);
-		Assert.NotNull (r.ColorScheme);
-		Assert.Equal (0, r.X);
-		Assert.Equal (0, r.Y);
-		Assert.Equal (0, r.Width);
-		Assert.Equal (0, r.Height);
-		Assert.False (r.IsCurrentTop);
-		Assert.Equal (r.Title, r.Id);
-		Assert.False (r.WantContinuousButtonPressed);
-		Assert.False (r.WantMousePositionReports);
-		Assert.Null (r.SuperView);
-		Assert.Null (r.MostFocused);
-		Assert.Equal (TextDirection.LeftRight_TopBottom, r.TextDirection);
-
-		// Rect with values
-		r = new Window (new Rect (1, 2, 3, 4)) { Title = "title" };
-		Assert.Equal ("title", r.Title);
-		Assert.NotNull (r);
-		Assert.Equal (LayoutStyle.Absolute, r.LayoutStyle);
-		Assert.Equal (LayoutStyle.Absolute, r.LayoutStyle);
-		Assert.Equal ("Window(title)(1,2,3,4)", r.ToString ());
-		Assert.True (r.CanFocus);
-		Assert.False (r.HasFocus);
-		Assert.Equal (new Rect (0, 0, 1, 2), r.Bounds);
-		Assert.Equal (new Rect (1, 2, 3, 4), r.Frame);
-		Assert.Null (r.Focused);
-		Assert.NotNull (r.ColorScheme);
-		Assert.Equal (1, r.X);
-		Assert.Equal (2, r.Y);
-		Assert.Equal (3, r.Width);
-		Assert.Equal (4, r.Height);
-		Assert.False (r.IsCurrentTop);
-		Assert.Equal (r.Title, r.Id);
-		Assert.False (r.WantContinuousButtonPressed);
-		Assert.False (r.WantMousePositionReports);
-		Assert.Null (r.SuperView);
-		Assert.Null (r.MostFocused);
-		Assert.Equal (TextDirection.LeftRight_TopBottom, r.TextDirection);
-		r.Dispose ();
-	}
-
-	[Fact]
-	[AutoInitShutdown]
-	public void MenuBar_And_StatusBar_Inside_Window ()
-	{
-		var menu = new MenuBar (new MenuBarItem [] {
-			new ("File", new MenuItem [] {
-				new ("Open", "", null),
-				new ("Quit", "", null)
-			}),
-			new ("Edit", new MenuItem [] {
-				new ("Copy", "", null)
-			})
-		});
-
-		var sb = new StatusBar (new StatusItem [] {
-			new (KeyCode.CtrlMask | KeyCode.Q, "~^Q~ Quit", null),
-			new (KeyCode.CtrlMask | KeyCode.O, "~^O~ Open", null),
-			new (KeyCode.CtrlMask | KeyCode.C, "~^C~ Copy", null)
-		});
-
-		var fv = new FrameView ("Frame View") {
-			Y = 1,
-			Width = Dim.Fill (),
-			Height = Dim.Fill (1)
-		};
-		var win = new Window ();
-		win.Add (menu, sb, fv);
-		var top = Application.Top;
-		top.Add (win);
-		Application.Begin (top);
-		((FakeDriver)Application.Driver).SetBufferSize (20, 10);
-
-		TestHelpers.AssertDriverContentsWithFrameAre (@"
+public class WindowTests
+{
+    private readonly ITestOutputHelper _output;
+
+    public WindowTests (ITestOutputHelper output) { _output = output; }
+
+    [Fact]
+    [AutoInitShutdown]
+    public void Activating_MenuBar_By_Alt_Key_Does_Not_Throw ()
+    {
+        var menu = new MenuBar (
+                                new MenuBarItem []
+                                {
+                                    new (
+                                         "Child",
+                                         new MenuItem []
+                                         {
+                                             new ("_Create Child", "", null)
+                                         })
+                                });
+        var win = new Window ();
+        win.Add (menu);
+        Application.Top.Add (win);
+        Application.Begin (Application.Top);
+
+        Exception exception = Record.Exception (() => win.NewKeyDownEvent (new Key (KeyCode.AltMask)));
+        Assert.Null (exception);
+    }
+
+    [Fact]
+    [AutoInitShutdown]
+    public void MenuBar_And_StatusBar_Inside_Window ()
+    {
+        var menu = new MenuBar (
+                                new MenuBarItem []
+                                {
+                                    new (
+                                         "File",
+                                         new MenuItem []
+                                         {
+                                             new ("Open", "", null),
+                                             new ("Quit", "", null)
+                                         }),
+                                    new (
+                                         "Edit",
+                                         new MenuItem []
+                                         {
+                                             new ("Copy", "", null)
+                                         })
+                                });
+
+        var sb = new StatusBar (
+                                new StatusItem []
+                                {
+                                    new (KeyCode.CtrlMask | KeyCode.Q, "~^Q~ Quit", null),
+                                    new (KeyCode.CtrlMask | KeyCode.O, "~^O~ Open", null),
+                                    new (KeyCode.CtrlMask | KeyCode.C, "~^C~ Copy", null)
+                                });
+
+        var fv = new FrameView ("Frame View")
+        {
+            Y = 1,
+            Width = Dim.Fill (),
+            Height = Dim.Fill (1)
+        };
+        var win = new Window ();
+        win.Add (menu, sb, fv);
+        Toplevel top = Application.Top;
+        top.Add (win);
+        Application.Begin (top);
+        ((FakeDriver)Application.Driver).SetBufferSize (20, 10);
+
+        TestHelpers.AssertDriverContentsWithFrameAre (
+                                                      @"
 ┌──────────────────┐
 │ File  Edit       │
 │┌┤Frame View├────┐│
@@ -132,11 +85,13 @@ public class WindowTests {
 ││                ││
 │└────────────────┘│
 │ ^Q Quit │ ^O Open│
-└──────────────────┘", _output);
+└──────────────────┘",
+                                                      _output);
 
-		((FakeDriver)Application.Driver).SetBufferSize (40, 20);
+        ((FakeDriver)Application.Driver).SetBufferSize (40, 20);
 
-		TestHelpers.AssertDriverContentsWithFrameAre (@"
+        TestHelpers.AssertDriverContentsWithFrameAre (
+                                                      @"
 ┌──────────────────────────────────────┐
 │ File  Edit                           │
 │┌┤Frame View├────────────────────────┐│
@@ -156,11 +111,13 @@ public class WindowTests {
 ││                                    ││
 │└────────────────────────────────────┘│
 │ ^Q Quit │ ^O Open │ ^C Copy          │
-└──────────────────────────────────────┘", _output);
+└──────────────────────────────────────┘",
+                                                      _output);
 
-		((FakeDriver)Application.Driver).SetBufferSize (20, 10);
+        ((FakeDriver)Application.Driver).SetBufferSize (20, 10);
 
-		TestHelpers.AssertDriverContentsWithFrameAre (@"
+        TestHelpers.AssertDriverContentsWithFrameAre (
+                                                      @"
 ┌──────────────────┐
 │ File  Edit       │
 │┌┤Frame View├────┐│
@@ -170,43 +127,110 @@ public class WindowTests {
 ││                ││
 │└────────────────┘│
 │ ^Q Quit │ ^O Open│
-└──────────────────┘", _output);
-	}
-
-	[Fact]
-	[AutoInitShutdown]
-	public void OnCanFocusChanged_Only_Must_ContentView_Forces_SetFocus_After_IsInitialized_Is_True ()
-	{
-		var win1 = new Window { Id = "win1", Width = 10, Height = 1 };
-		var view1 = new View { Id = "view1", Width = Dim.Fill (), Height = Dim.Fill (), CanFocus = true };
-		var win2 = new Window { Id = "win2", Y = 6, Width = 10, Height = 1 };
-		var view2 = new View { Id = "view2", Width = Dim.Fill (), Height = Dim.Fill (), CanFocus = true };
-		win2.Add (view2);
-		win1.Add (view1, win2);
-
-		Application.Begin (win1);
-
-		Assert.True (win1.HasFocus);
-		Assert.True (view1.HasFocus);
-		Assert.False (win2.HasFocus);
-		Assert.False (view2.HasFocus);
-	}
-
-	[Fact]
-	[AutoInitShutdown]
-	public void Activating_MenuBar_By_Alt_Key_Does_Not_Throw ()
-	{
-		var menu = new MenuBar (new MenuBarItem [] {
-			new ("Child", new MenuItem [] {
-				new ("_Create Child", "", null)
-			})
-		});
-		var win = new Window ();
-		win.Add (menu);
-		Application.Top.Add (win);
-		Application.Begin (Application.Top);
-
-		var exception = Record.Exception (() => win.NewKeyDownEvent (new Key (KeyCode.AltMask)));
-		Assert.Null (exception);
-	}
-}
+└──────────────────┘",
+                                                      _output);
+    }
+
+    [Fact]
+    public void New_Initializes ()
+    {
+        // Parameterless
+        var r = new Window ();
+        Assert.NotNull (r);
+        Assert.Equal (string.Empty, r.Title);
+
+        // Toplevels have Width/Height set to Dim.Fill
+        Assert.Equal (LayoutStyle.Computed, r.LayoutStyle);
+
+        // If there's no SuperView, Top, or Driver, the default Fill width is int.MaxValue
+        Assert.Equal ("Window()(0,0,2147483647,2147483647)", r.ToString ());
+        Assert.True (r.CanFocus);
+        Assert.False (r.HasFocus);
+        Assert.Equal (new Rect (0, 0, 2147483645, 2147483645), r.Bounds);
+        Assert.Equal (new Rect (0, 0, 2147483647, 2147483647), r.Frame);
+        Assert.Null (r.Focused);
+        Assert.NotNull (r.ColorScheme);
+        Assert.Equal (0, r.X);
+        Assert.Equal (0, r.Y);
+        Assert.Equal (Dim.Fill (), r.Width);
+        Assert.Equal (Dim.Fill (), r.Height);
+        Assert.False (r.IsCurrentTop);
+        Assert.Empty (r.Id);
+        Assert.False (r.WantContinuousButtonPressed);
+        Assert.False (r.WantMousePositionReports);
+        Assert.Null (r.SuperView);
+        Assert.Null (r.MostFocused);
+        Assert.Equal (TextDirection.LeftRight_TopBottom, r.TextDirection);
+
+        // Empty Rect
+        r = new Window (Rect.Empty) { Title = "title" };
+        Assert.NotNull (r);
+        Assert.Equal ("title", r.Title);
+        Assert.Equal (LayoutStyle.Absolute, r.LayoutStyle);
+        Assert.Equal ("title", r.Title);
+        Assert.Equal (LayoutStyle.Absolute, r.LayoutStyle);
+        Assert.Equal ("Window(title)(0,0,0,0)", r.ToString ());
+        Assert.True (r.CanFocus);
+        Assert.False (r.HasFocus);
+        Assert.Equal (new Rect (0, 0, 0, 0), r.Bounds);
+        Assert.Equal (new Rect (0, 0, 0, 0), r.Frame);
+        Assert.Null (r.Focused);
+        Assert.NotNull (r.ColorScheme);
+        Assert.Equal (0, r.X);
+        Assert.Equal (0, r.Y);
+        Assert.Equal (0, r.Width);
+        Assert.Equal (0, r.Height);
+        Assert.False (r.IsCurrentTop);
+        Assert.Equal (r.Title, r.Id);
+        Assert.False (r.WantContinuousButtonPressed);
+        Assert.False (r.WantMousePositionReports);
+        Assert.Null (r.SuperView);
+        Assert.Null (r.MostFocused);
+        Assert.Equal (TextDirection.LeftRight_TopBottom, r.TextDirection);
+
+        // Rect with values
+        r = new Window (new Rect (1, 2, 3, 4)) { Title = "title" };
+        Assert.Equal ("title", r.Title);
+        Assert.NotNull (r);
+        Assert.Equal (LayoutStyle.Absolute, r.LayoutStyle);
+        Assert.Equal (LayoutStyle.Absolute, r.LayoutStyle);
+        Assert.Equal ("Window(title)(1,2,3,4)", r.ToString ());
+        Assert.True (r.CanFocus);
+        Assert.False (r.HasFocus);
+        Assert.Equal (new Rect (0, 0, 1, 2), r.Bounds);
+        Assert.Equal (new Rect (1, 2, 3, 4), r.Frame);
+        Assert.Null (r.Focused);
+        Assert.NotNull (r.ColorScheme);
+        Assert.Equal (1, r.X);
+        Assert.Equal (2, r.Y);
+        Assert.Equal (3, r.Width);
+        Assert.Equal (4, r.Height);
+        Assert.False (r.IsCurrentTop);
+        Assert.Equal (r.Title, r.Id);
+        Assert.False (r.WantContinuousButtonPressed);
+        Assert.False (r.WantMousePositionReports);
+        Assert.Null (r.SuperView);
+        Assert.Null (r.MostFocused);
+        Assert.Equal (TextDirection.LeftRight_TopBottom, r.TextDirection);
+        r.Dispose ();
+    }
+
+    [Fact]
+    [AutoInitShutdown]
+    public void OnCanFocusChanged_Only_Must_ContentView_Forces_SetFocus_After_IsInitialized_Is_True ()
+    {
+        var win1 = new Window { Id = "win1", Width = 10, Height = 1 };
+        var view1 = new View { Id = "view1", Width = Dim.Fill (), Height = Dim.Fill (), CanFocus = true };
+        var win2 = new Window { Id = "win2", Y = 6, Width = 10, Height = 1 };
+        var view2 = new View { Id = "view2", Width = Dim.Fill (), Height = Dim.Fill (), CanFocus = true };
+        win2.Add (view2);
+        win1.Add (view1, win2);
+
+        Application.Begin (win1);
+
+        Assert.True (win1.HasFocus);
+        Assert.True (view1.HasFocus);
+        Assert.False (win2.HasFocus);
+        Assert.False (view2.HasFocus);
+    }
+}

Энэ ялгаанд хэт олон файл өөрчлөгдсөн тул зарим файлыг харуулаагүй болно