Browse Source

crazy refactoring

Charlie Kindel 5 years ago
parent
commit
a08a411a91

+ 637 - 142
Terminal.Gui/Core/View.cs

@@ -1,4 +1,4 @@
-//
+//
 // Authors:
 //   Miguel de Icaza ([email protected])
 //
@@ -13,6 +13,7 @@
 using System;
 using System.Collections;
 using System.Collections.Generic;
+using System.Diagnostics;
 using System.Linq;
 using NStack;
 
@@ -132,6 +133,482 @@ namespace Terminal.Gui {
 	/// </para>
 	/// </remarks>
 	public class View : Responder, IEnumerable {
+		/// <summary>
+		/// Suppports text formatting, including horizontal alignment and word wrap for <see cref="View"/>.
+		/// </summary>
+		public class ViewText {
+			List<ustring> lines = new List<ustring> ();
+			ustring text;
+			TextAlignment textAlignment;
+			Attribute textColor = -1;
+			View view;
+
+			/// <summary>
+			///  Inititalizes a new <see cref="ViewText"/> object.
+			/// </summary>
+			/// <param name="view"></param>
+			public ViewText (View view)
+			{
+				this.view = view;
+				recalcPending = true;
+			}
+
+			/// <summary>
+			///   The text to be displayed.
+			/// </summary>
+			public virtual ustring Text {
+				get => text;
+				set {
+					text = value;
+					recalcPending = true;
+					view.SetNeedsDisplay ();
+				}
+			}
+
+			// TODO: Add Vertical Text Alignment
+			/// <summary>
+			/// Controls the horizontal text-alignment property. 
+			/// </summary>
+			/// <value>The text alignment.</value>
+			public TextAlignment TextAlignment {
+				get => textAlignment;
+				set {
+					textAlignment = value;
+					recalcPending = true;
+					view.SetNeedsDisplay ();
+				}
+			}
+
+			/// <summary>
+			///   The color used for the drawing of the <see cref="Text"/>.
+			/// </summary>
+			public Attribute TextColor {
+				get => textColor;
+				set {
+					textColor = value;
+					recalcPending = true;
+					view.SetNeedsDisplay ();
+				}
+			}
+
+			/// <summary>
+			///  Gets the size of the area the text will be drawn in. 
+			/// </summary>
+			public Size TextSize { get; internal set; }
+
+			bool recalcPending = false;
+
+			public int HotKeyPos { get => hotKeyPos; set => hotKeyPos = value; }
+			public Rune HotKey { get => hotKey; set => hotKey = value; }
+			Rune hotKey;
+
+			/// <summary>
+			/// The specifier character for the hotkey (e.g. '_'). Set to '\xffff' to disable hotkey support for this View instance. The default is '\xffff'. 
+			/// </summary>
+			public Rune HotKeySpecifier { get; set; } = (Rune)0xFFFF;
+
+			/// <summary>
+			/// Causes the Text to be formatted, based on <see cref="TextAlignment"/> and <see cref="TextSize"/>.
+			/// </summary>
+			public void ReFormat ()
+			{
+				// With this check, we protect against subclasses with overrides of Text
+				if (ustring.IsNullOrEmpty (Text)) {
+					return;
+				}
+				recalcPending = false;
+				var shown_text = ProcessHotKeyText (text, HotKeySpecifier, false, out hotKeyPos, out hotKey);
+				Reformat (shown_text, lines, TextSize.Width, textAlignment, TextSize.Height > 1);
+			}
+
+			static ustring StripCRLF (ustring str)
+			{
+				var runes = new List<Rune> ();
+				foreach (var r in str.ToRunes ()) {
+					if (r != '\r' && r != '\n') {
+						runes.Add (r);
+					}
+				}
+				return ustring.Make (runes); ;
+			}
+			static ustring ReplaceCRLFWithSpace (ustring str)
+			{
+				var runes = new List<Rune> ();
+				foreach (var r in str.ToRunes ()) {
+					if (r == '\r' || r == '\n') {
+						runes.Add (new Rune (' ')); // r + 0x2400));         // U+25A1 □ WHITE SQUARE
+					} else {
+						runes.Add (r);
+					}
+				}
+				return ustring.Make (runes); ;
+			}
+
+			static List<ustring> WordWrap (ustring text, int margin)
+			{
+				int start = 0, end;
+				var lines = new List<ustring> ();
+
+				text = StripCRLF (text);
+
+				while ((end = start + margin) < text.Length) {
+					while (text [end] != ' ' && end > start)
+						end -= 1;
+					if (end == start)
+						end = start + margin;
+
+					lines.Add (text [start, end]);
+					start = end + 1;
+				}
+
+				if (start < text.Length)
+					lines.Add (text.Substring (start));
+
+				return lines;
+			}
+
+			static ustring ClipAndJustify (ustring str, int width, TextAlignment talign)
+			{
+				int slen = str.RuneCount;
+				if (slen > width) {
+					var uints = str.ToRunes (width);
+					var runes = new Rune [uints.Length];
+					for (int i = 0; i < uints.Length; i++)
+						runes [i] = uints [i];
+					return ustring.Make (runes);
+				} else {
+					if (talign == TextAlignment.Justified) {
+						// TODO: ustring needs this
+						var words = str.ToString ().Split (whitespace, StringSplitOptions.RemoveEmptyEntries);
+						int textCount = words.Sum (arg => arg.Length);
+
+						var spaces = words.Length > 1 ? (width - textCount) / (words.Length - 1) : 0;
+						var extras = words.Length > 1 ? (width - textCount) % words.Length : 0;
+
+						var s = new System.Text.StringBuilder ();
+						//s.Append ($"tc={textCount} sp={spaces},x={extras} - ");
+						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 (' ');
+							if (extras > 0) {
+								//s.Append ('_');
+								extras--;
+							}
+						}
+						return ustring.Make (s.ToString ());
+					}
+					return str;
+				}
+			}
+
+			static char [] whitespace = new char [] { ' ', '\t' };
+			private int hotKeyPos;
+
+			/// <summary>
+			/// Reformats text into lines, applying text alignment and word wraping.
+			/// </summary>
+			/// <param name="textStr"></param>
+			/// <param name="lineResult"></param>
+			/// <param name="width"></param>
+			/// <param name="talign"></param>
+			/// <param name="wordWrap">if <c>false</c>, forces text to fit a single line. Line breaks are converted to spaces.</param>
+			static void Reformat (ustring textStr, List<ustring> lineResult, int width, TextAlignment talign, bool wordWrap)
+			{
+				lineResult.Clear ();
+
+				if (wordWrap == false) {
+					textStr = ReplaceCRLFWithSpace (textStr);
+					lineResult.Add (ClipAndJustify (textStr, width, talign));
+					return;
+				}
+
+				int textLen = textStr.Length;
+				int lp = 0;
+				for (int i = 0; i < textLen; i++) {
+					Rune c = textStr [i];
+					if (c == '\n') {
+						var wrappedLines = WordWrap (textStr [lp, i], width);
+						foreach (var line in wrappedLines) {
+							lineResult.Add (ClipAndJustify (line, width, talign));
+						}
+						if (wrappedLines.Count == 0) {
+							lineResult.Add (ustring.Empty);
+						}
+						lp = i + 1;
+					}
+				}
+				foreach (var line in WordWrap (textStr [lp, textLen], width)) {
+					lineResult.Add (ClipAndJustify (line, width, talign));
+				}
+			}
+
+			/// <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 (ustring text, int width)
+			{
+				var result = new List<ustring> ();
+				ViewText.Reformat (text, result, width, TextAlignment.Left, true);
+				return result.Count;
+			}
+
+			/// <summary>
+			/// Computes the maximum width needed to render the text (single line or multple lines).
+			/// </summary>
+			/// <returns>Max width of lines.</returns>
+			/// <param name="text">Text, may contain newlines.</param>
+			/// <param name="width">The minimum width for the text.</param>
+			public static int MaxWidth (ustring text, int width)
+			{
+				var result = new List<ustring> ();
+				ViewText.Reformat (text, result, width, TextAlignment.Left, true);
+				return result.Max (s => s.RuneCount);
+			}
+
+			internal void Draw (Rect bounds)
+			{
+				// With this check, we protect against subclasses with overrides of Text
+				if (ustring.IsNullOrEmpty (text)) {
+					return;
+				}
+
+				if (recalcPending) {
+					ReFormat ();
+				}
+
+				if (TextColor != -1)
+					Driver.SetAttribute (TextColor);
+				else
+					Driver.SetAttribute (view.ColorScheme.Normal);
+
+				view.Clear ();
+				for (int line = 0; line < lines.Count; line++) {
+					if (line < bounds.Top || line >= bounds.Bottom)
+						continue;
+					var str = lines [line];
+					int x;
+					switch (textAlignment) {
+					case TextAlignment.Left:
+						x = 0;
+						break;
+					case TextAlignment.Justified:
+						x = bounds.Left;
+						break;
+					case TextAlignment.Right:
+						x = bounds.Right - str.Length;
+						break;
+					case TextAlignment.Centered:
+						x = bounds.Left + (bounds.Width - str.Length) / 2;
+						break;
+					default:
+						throw new ArgumentOutOfRangeException ();
+					}
+					view.Move (x, line);
+					Driver.AddStr (str);
+				}
+
+				if (HotKeyPos != -1) {
+					_ = GetAlignedText (lines [0], TextSize.Width, hotKeyPos, out hotKeyPos, textAlignment);
+
+					view.Move (HotKeyPos, 0);
+					Driver.SetAttribute (view.HasFocus ? view.ColorScheme.HotFocus : view.ColorScheme.HotNormal);
+					Driver.AddRune (hotKey);
+				}
+			}
+
+			/// <summary>
+			///  Calculates the rectangle requried to hold text, assuming no word wrapping.
+			/// </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>
+			/// <returns></returns>
+			public static Rect CalcRect (int x, int y, ustring text)
+			{
+				if (ustring.IsNullOrEmpty (text))
+					return Rect.Empty;
+
+				int mw = 0;
+				int ml = 1;
+
+				int cols = 0;
+				foreach (var rune in text) {
+					if (rune == '\n') {
+						ml++;
+						if (cols > mw)
+							mw = cols;
+						cols = 0;
+					} else
+						cols++;
+				}
+				if (cols > mw)
+					mw = cols;
+
+				return new Rect (x, y, mw, ml);
+			}
+
+
+			/// <summary>
+			/// Gets the position and Rune for the hotkey in text and removes the hotkey specifier.
+			/// </summary>
+			/// <param name="text">The text to manipulate.</param>
+			/// <param name="hotKeySpecifier">The hot-key specifier (e.g. '_') to look for.</param>
+			/// <param name="firstUpperCase">If <c>true</c> and no hotkey is found via the hotkey specifier, the first upper case char found will be the hotkey.</param>
+			/// <param name="hotPos">Returns the postion of the hot-key in the text. -1 if not found.</param>
+			/// <param name="showHotKey">Returns the Rune immediately to the right of the hot-key position</param>
+			/// <returns>The input text with the hotkey specifier ('_') removed.</returns>
+			public static ustring ProcessHotKeyText (ustring text, Rune hotKeySpecifier, bool firstUpperCase, out int hotPos, out Rune showHotKey)
+			{
+				if (hotKeySpecifier == (Rune)0xFFFF) {
+					hotPos = -1;
+					showHotKey = (Rune)0xFFFF;
+					return text;
+				}
+				Rune hot_key = (Rune)0;
+				int hot_pos = -1;
+				ustring shown_text = text;
+
+				// Use first hot_key char passed into 'hotKey'.
+				// TODO: Ignore hot_key of two are provided
+				int i = 0;
+				foreach (Rune c in shown_text) {
+					if ((char)c != 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 shown_text) {
+						if ((char)c != 0xFFFD) {
+							if (Rune.IsUpper (c)) {
+								hot_key = c;
+								hot_pos = i;
+								break;
+							}
+						}
+						i++;
+					}
+				}
+				else {
+					if (hot_pos != -1) {
+						// Use char after 'hotKey'
+						ustring start = "";
+						i = 0;
+						foreach (Rune c in shown_text) {
+							start += ustring.Make (c);
+							i++;
+							if (i == hot_pos)
+								break;
+						}
+						var st = shown_text;
+						shown_text = start;
+						i = 0;
+						foreach (Rune c in st) {
+							i++;
+							if (i > hot_pos + 1) {
+								shown_text += ustring.Make (c);
+							}
+						}
+					}
+				}
+				hotPos = hot_pos;
+				showHotKey = hot_key;
+				return shown_text;
+			}
+
+			/// <summary>
+			/// Formats a single line of text with a hot-key and <see cref="TextAlignment"/>.
+			/// </summary>
+			/// <param name="shown_text">The text to align.</param>
+			/// <param name="width">The maximum width for the text.</param>
+			/// <param name="hot_pos">The hot-key position before reformatting.</param>
+			/// <param name="c_hot_pos">The hot-key position after reformatting.</param>
+			/// <param name="textAlignment">The <see cref="TextAlignment"/> to align to.</param>
+			/// <returns>The aligned text.</returns>
+			public static ustring GetAlignedText (ustring shown_text, int width, int hot_pos, out int c_hot_pos, TextAlignment textAlignment)
+			{
+				int start;
+				var caption = shown_text;
+				c_hot_pos = hot_pos;
+
+				if (width > shown_text.Length + 1) {
+					switch (textAlignment) {
+					case TextAlignment.Left:
+						caption += new string (' ', width - caption.RuneCount);
+						break;
+					case TextAlignment.Right:
+						start = width - caption.RuneCount;
+						caption = $"{new string (' ', width - caption.RuneCount)}{caption}";
+						if (c_hot_pos > -1) {
+							c_hot_pos += start;
+						}
+						break;
+					case TextAlignment.Centered:
+						start = width / 2 - caption.RuneCount / 2;
+						caption = $"{new string (' ', start)}{caption}{new string (' ', width - caption.RuneCount - start)}";
+						if (c_hot_pos > -1) {
+							c_hot_pos += start;
+						}
+						break;
+					case TextAlignment.Justified:
+						var words = caption.Split (" ");
+						var wLen = GetWordsLength (words, c_hot_pos, out int runeCount, out int w_hot_pos);
+						var space = (width - runeCount) / (caption.Length - wLen);
+						caption = "";
+						for (int i = 0; i < words.Length; i++) {
+							if (i == words.Length - 1) {
+								caption += new string (' ', width - caption.RuneCount - 1);
+								caption += words [i];
+							} else {
+								caption += words [i];
+							}
+							if (i < words.Length - 1) {
+								caption += new string (' ', space);
+							}
+						}
+						if (c_hot_pos > -1) {
+							c_hot_pos += w_hot_pos * space - space - w_hot_pos + 1;
+						}
+						break;
+					}
+				}
+
+				return caption;
+			}
+
+			static int GetWordsLength (ustring [] words, int hotPos, out int runeCount, out int wordHotPos)
+			{
+				int length = 0;
+				int rCount = 0;
+				int wHotPos = -1;
+				for (int i = 0; i < words.Length; i++) {
+					if (wHotPos == -1 && rCount + words [i].RuneCount >= hotPos)
+						wHotPos = i;
+					length += words [i].Length;
+					rCount += words [i].RuneCount;
+				}
+				if (wHotPos == -1 && hotPos > -1)
+					wHotPos = words.Length;
+				runeCount = rCount;
+				wordHotPos = wHotPos;
+				return length;
+			}
+		}
+
 		internal enum Direction {
 			Forward,
 			Backward
@@ -142,6 +619,8 @@ namespace Terminal.Gui {
 		View focused = null;
 		Direction focusDirection;
 
+		ViewText viewText;
+
 		/// <summary>
 		/// Event fired when the view gets focus.
 		/// </summary>
@@ -167,6 +646,27 @@ namespace Terminal.Gui {
 		/// </summary>
 		public Action<MouseEventArgs> MouseClick;
 
+		/// <summary>
+		/// The HotKey defined for this view. A user pressing HotKey on the keyboard while this view has focus will cause the Clicked event to fire.
+		/// </summary>
+		public Rune HotKey { get => viewText.HotKey; set => viewText.HotKey = value; }
+
+		/// <summary>
+		/// 
+		/// </summary>
+		public Rune HotKeySpecifier { get => viewText.HotKeySpecifier; set => viewText.HotKeySpecifier = value; }
+
+		/// <summary>
+		///   Clicked <see cref="Action"/>, raised when the user clicks the primary mouse button within the Bounds of this <see cref="View"/>
+		///   or if the user presses the action key while this view is focused. (TODO: IsDefault)
+		/// </summary>
+		/// <remarks>
+		///   Client code can hook up to this event, it is
+		///   raised when the button is activated either with
+		///   the mouse or the keyboard.
+		/// </remarks>
+		public Action Clicked;
+
 		internal Direction FocusDirection {
 			get => SuperView?.FocusDirection ?? focusDirection;
 			set {
@@ -389,29 +889,99 @@ namespace Terminal.Gui {
 		/// </remarks>
 		public View (Rect frame)
 		{
+			viewText = new ViewText (this);
+			this.Text = ustring.Empty;
+
 			this.Frame = frame;
-			CanFocus = false;
 			LayoutStyle = LayoutStyle.Absolute;
 		}
 
 		/// <summary>
-		/// Initializes a new instance of <see cref="LayoutStyle.Computed"/> <see cref="View"/> class.
+		///   Initializes a new instance of <see cref="View"/> using <see cref="LayoutStyle.Computed"/> layout.
 		/// </summary>
 		/// <remarks>
+		/// <para>
 		///   Use <see cref="X"/>, <see cref="Y"/>, <see cref="Width"/>, and <see cref="Height"/> properties to dynamically control the size and location of the view.
-		/// </remarks>
-		/// <remarks>
+		///   The <see cref="Label"/> will be created using <see cref="LayoutStyle.Computed"/>
+		///   coordinates. The initial size (<see cref="View.Frame"/> will be 
+		///   adjusted to fit the contents of <see cref="Text"/>, including newlines ('\n') for multiple lines. 
+		/// </para>
+		/// <para>
+		///   If <c>Height</c> is greater than one, word wrapping is provided.
+		/// </para>
+		/// <para>
 		///   This constructor intitalize a View with a <see cref="LayoutStyle"/> of <see cref="LayoutStyle.Computed"/>. 
 		///   Use <see cref="X"/>, <see cref="Y"/>, <see cref="Width"/>, and <see cref="Height"/> properties to dynamically control the size and location of the view.
+		/// </para>
+		/// </remarks>
+		public View () : this (text: string.Empty) { }
+
+
+		/// <summary>
+		///   Initializes a new instance of <see cref="View"/> using <see cref="LayoutStyle.Absolute"/> layout.
+		/// </summary>
+		/// <remarks>
+		/// <para>
+		///   The <see cref="View"/> will be created at the given
+		///   coordinates with the given string. The size (<see cref="View.Frame"/> will be 
+		///   adjusted to fit the contents of <see cref="Text"/>, including newlines ('\n') for multiple lines. 
+		/// </para>
+		/// <para>
+		///   No line wrapping is provided.
+		/// </para>
+		/// </remarks>
+		/// <param name="x">column to locate the Label.</param>
+		/// <param name="y">row to locate the Label.</param>
+		/// <param name="text">text to initialize the <see cref="Text"/> property with.</param>
+		public View (int x, int y, ustring text) : this (ViewText.CalcRect (x, y, text), text) { }
+
+		/// <summary>
+		///   Initializes a new instance of <see cref="View"/> using <see cref="LayoutStyle.Absolute"/> layout.
+		/// </summary>
+		/// <remarks>
+		/// <para>
+		///   The <see cref="View"/> will be created at the given
+		///   coordinates with the given string. The initial size (<see cref="View.Frame"/> will be 
+		///   adjusted to fit the contents of <see cref="Text"/>, including newlines ('\n') for multiple lines. 
+		/// </para>
+		/// <para>
+		///   If <c>rect.Height</c> is greater than one, word wrapping is provided.
+		/// </para>
+		/// </remarks>
+		/// <param name="rect">Location.</param>
+		/// <param name="text">text to initialize the <see cref="Text"/> property with.</param>
+		public View (Rect rect, ustring text) : this (rect)
+		{
+			viewText = new ViewText (this);
+			this.Text = text;
+		}
+
+		/// <summary>
+		///   Initializes a new instance of <see cref="View"/> using <see cref="LayoutStyle.Computed"/> layout.
+		/// </summary>
+		/// <remarks>
+		/// <para>
+		///   The <see cref="View"/> will be created using <see cref="LayoutStyle.Computed"/>
+		///   coordinates with the given string. The initial size (<see cref="View.Frame"/> will be 
+		///   adjusted to fit the contents of <see cref="Text"/>, including newlines ('\n') for multiple lines. 
+		/// </para>
+		/// <para>
+		///   If <c>Height</c> is greater than one, word wrapping is provided.
+		/// </para>
 		/// </remarks>
-		public View ()
+		/// <param name="text">text to initialize the <see cref="Text"/> property with.</param>
+		public View (ustring text) : base ()
 		{
+			viewText = new ViewText (this);
+			this.Text = text;
+
 			CanFocus = false;
 			LayoutStyle = LayoutStyle.Computed;
+			var r = ViewText.CalcRect (0, 0, text);
 			x = Pos.At (0);
 			y = Pos.At (0);
-			Height = 0;
-			Width = 0;
+			Width = r.Width;
+			Height = r.Height;
 		}
 
 		/// <summary>
@@ -432,6 +1002,7 @@ namespace Terminal.Gui {
 			if (SuperView == null)
 				return;
 			SuperView.SetNeedsLayout ();
+			viewText.ReFormat ();
 		}
 
 		/// <summary>
@@ -819,8 +1390,13 @@ namespace Terminal.Gui {
 		{
 			if (focused != null)
 				focused.PositionCursor ();
-			else
-				Move (frame.X, frame.Y);
+			else {
+				if (CanFocus && HasFocus) {
+					Move (viewText.HotKeyPos == -1 ? 1 : viewText.HotKeyPos, 0);
+				} else {
+					Move (frame.X, frame.Y);
+				}
+			}
 		}
 
 		/// <inheritdoc/>
@@ -971,6 +1547,13 @@ namespace Terminal.Gui {
 		{
 			var clipRect = new Rect (Point.Empty, frame.Size);
 
+			Driver.SetAttribute (HasFocus ? ColorScheme.Focus : ColorScheme.Normal);
+
+			// Draw any Text
+			// TODO: Figure out if this should go here or after OnDrawContent
+			viewText?.ReFormat ();
+			viewText?.Draw (bounds);
+
 			// Invoke DrawContentEvent
 			OnDrawContent (bounds);
 
@@ -1083,7 +1666,6 @@ namespace Terminal.Gui {
 		/// <inheritdoc/>
 		public override bool ProcessKey (KeyEvent keyEvent)
 		{
-
 			KeyEventEventArgs args = new KeyEventEventArgs (keyEvent);
 			KeyPress?.Invoke (args);
 			if (args.Handled)
@@ -1091,6 +1673,12 @@ namespace Terminal.Gui {
 			if (Focused?.ProcessKey (keyEvent) == true)
 				return true;
 
+			var c = keyEvent.KeyValue;
+			if (c == '\n' || c == ' ' || Rune.ToUpper ((uint)c) == HotKey) {
+				Clicked?.Invoke ();
+				return true;
+			}
+
 			return false;
 		}
 
@@ -1381,7 +1969,7 @@ namespace Terminal.Gui {
 			}
 
 			if (edges.Any ()) {
-				if (!object.ReferenceEquals(edges.First ().From, edges.First ().To)) {
+				if (!object.ReferenceEquals (edges.First ().From, edges.First ().To)) {
 					throw new InvalidOperationException ($"TopologicalSort (for Pos/Dim) cannot find {edges.First ().From}. Did you forget to add it to {this}?");
 				} else {
 					throw new InvalidOperationException ("TopologicalSort encountered a recursive cycle in the relative Pos/Dim in the views of " + this);
@@ -1430,6 +2018,9 @@ namespace Terminal.Gui {
 			if (!layoutNeeded)
 				return;
 
+			viewText.TextSize = Bounds.Size;
+			viewText.ReFormat ();
+
 			Rect oldBounds = Bounds;
 
 			// Sort out the dependencies of the X, Y, Width, Height properties
@@ -1471,146 +2062,40 @@ namespace Terminal.Gui {
 		}
 
 		/// <summary>
-		/// A generic virtual method at the level of View to manipulate any hot-keys.
+		///   The text displayed by the <see cref="View"/>.
 		/// </summary>
-		/// <param name="text">The text to manipulate.</param>
-		/// <param name="hotKey">The hot-key to look for.</param>
-		/// <param name="hotPos">The returning hot-key position.</param>
-		/// <param name="showHotKey">The character immediately to the right relative to the hot-key position</param>
-		/// <returns>It aims to facilitate the preparation for <see cref="TextAlignment"/> procedures.</returns>
-		public virtual ustring GetTextFromHotKey (ustring text, Rune hotKey, out int hotPos, out Rune showHotKey)
-		{
-			Rune hot_key = (Rune)0;
-			int hot_pos = -1;
-			ustring shown_text = text;
-
-			// Use first hot_key char passed into 'hotKey'.
-			int i = 0;
-			foreach (Rune c in shown_text) {
-				if ((char)c != 0xFFFD) {
-					if (c == hotKey) {
-						hot_pos = i;
-					} else if (hot_pos > -1) {
-						hot_key = c;
-						break;
-					}
-				}
-				i++;
-			}
-
-			if (hot_pos == -1) {
-				// Use first upper-case char if there are no hot-key in the text.
-				i = 0;
-				foreach (Rune c in shown_text) {
-					if ((char)c != 0xFFFD) {
-						if (Rune.IsUpper (c)) {
-							hot_key = c;
-							hot_pos = i;
-							break;
-						}
-					}
-					i++;
-				}
-			} else {
-				// Use char after 'hotKey'
-				ustring start = "";
-				i = 0;
-				foreach (Rune c in shown_text) {
-					start += ustring.Make (c);
-					i++;
-					if (i == hot_pos)
-						break;
-				}
-				var st = shown_text;
-				shown_text = start;
-				i = 0;
-				foreach (Rune c in st) {
-					i++;
-					if (i > hot_pos + 1) {
-						shown_text += ustring.Make (c);
-					}
-				}
+		/// <remarks>
+		///  The text will only be displayed if the View has no subviews.
+		/// </remarks>
+		public virtual ustring Text {
+			get => viewText.Text;
+			set {
+				viewText.Text = value;
+				SetNeedsDisplay ();
 			}
-			hotPos = hot_pos;
-			showHotKey = hot_key;
-			return shown_text;
 		}
 
 		/// <summary>
-		/// A generic virtual method at the level of View to manipulate any hot-keys with <see cref="TextAlignment"/> process.
+		/// Controls the text-alignment property of the View. Changing this property will redisplay the <see cref="View"/>.
 		/// </summary>
-		/// <param name="shown_text">The text to manipulate to align.</param>
-		/// <param name="hot_pos">The passed in hot-key position.</param>
-		/// <param name="c_hot_pos">The returning hot-key position.</param>
-		/// <param name="textAlignment">The <see cref="TextAlignment"/> to align to.</param>
-		/// <returns>It performs the <see cref="TextAlignment"/> process to the caller.</returns>
-		public virtual ustring GetTextAlignment (ustring shown_text, int hot_pos, out int c_hot_pos, TextAlignment textAlignment)
-		{
-			int start;
-			var caption = shown_text;
-			c_hot_pos = hot_pos;
-
-			if (Frame.Width > shown_text.Length + 1) {
-				switch (textAlignment) {
-				case TextAlignment.Left:
-					caption += new string (' ', Frame.Width - caption.RuneCount);
-					break;
-				case TextAlignment.Right:
-					start = Frame.Width - caption.RuneCount;
-					caption = $"{new string (' ', Frame.Width - caption.RuneCount)}{caption}";
-					if (c_hot_pos > -1) {
-						c_hot_pos += start;
-					}
-					break;
-				case TextAlignment.Centered:
-					start = Frame.Width / 2 - caption.RuneCount / 2;
-					caption = $"{new string (' ', start)}{caption}{new string (' ', Frame.Width - caption.RuneCount - start)}";
-					if (c_hot_pos > -1) {
-						c_hot_pos += start;
-					}
-					break;
-				case TextAlignment.Justified:
-					var words = caption.Split (" ");
-					var wLen = GetWordsLength (words, c_hot_pos, out int runeCount, out int w_hot_pos);
-					var space = (Frame.Width - runeCount) / (caption.Length - wLen);
-					caption = "";
-					for (int i = 0; i < words.Length; i++) {
-						if (i == words.Length - 1) {
-							caption += new string (' ', Frame.Width - caption.RuneCount - 1);
-							caption += words [i];
-						} else {
-							caption += words [i];
-						}
-						if (i < words.Length - 1) {
-							caption += new string (' ', space);
-						}
-					}
-					if (c_hot_pos > -1) {
-						c_hot_pos += w_hot_pos * space - space - w_hot_pos + 1;
-					}
-					break;
-				}
+		/// <value>The text alignment.</value>
+		public virtual TextAlignment TextAlignment {
+			get => viewText.TextAlignment;
+			set {
+				viewText.TextAlignment = value;
+				SetNeedsDisplay ();
 			}
-
-			return caption;
 		}
 
-		int GetWordsLength (ustring [] words, int hotPos, out int runeCount, out int wordHotPos)
-		{
-			int length = 0;
-			int rCount = 0;
-			int wHotPos = -1;
-			for (int i = 0; i < words.Length; i++) {
-				if (wHotPos == -1 && rCount + words [i].RuneCount >= hotPos)
-					wHotPos = i;
-				length += words [i].Length;
-				rCount += words [i].RuneCount;
+		/// <summary>
+		///   The color used for the <see cref="Label"/>.
+		/// </summary>
+		public virtual Attribute TextColor {
+			get => viewText.TextColor;
+			set {
+				viewText.TextColor = value;
+				SetNeedsDisplay ();
 			}
-			if (wHotPos == -1 && hotPos > -1)
-				wHotPos = words.Length;
-			runeCount = rCount;
-			wordHotPos = wHotPos;
-			return length;
 		}
 
 		/// <summary>
@@ -1682,6 +2167,16 @@ namespace Terminal.Gui {
 			if (MouseEvent (mouseEvent))
 				return true;
 
+
+			if (mouseEvent.Flags == MouseFlags.Button1Clicked) {
+				if (!HasFocus) {
+					SuperView.SetFocus (this);
+					SetNeedsDisplay ();
+				}
+
+				Clicked?.Invoke ();
+				return true;
+			}
 			return false;
 		}
 	}

+ 41 - 16
Terminal.Gui/Core/Window.cs

@@ -2,6 +2,13 @@
 // Authors:
 //   Miguel de Icaza ([email protected])
 //
+// NOTE: Window is functionally identical to FrameView with the following exceptions. 
+//  - Window is a Toplevel
+//  - FrameView Does not support padding (but should)
+//  - FrameView Does not support mouse dragging
+//  - FrameView Does not support IEnumerable
+// Any udpates done here should probably be done in FrameView as well; TODO: Merge these classes
+
 using System.Collections;
 using NStack;
 
@@ -29,7 +36,6 @@ namespace Terminal.Gui {
 			}
 		}
 
-
 		/// <summary>
 		/// ContentView is an internal implementation detail of Window. It is used to host Views added with <see cref="Add(View)"/>. 
 		/// Its ONLY reason for being is to provide a simple way for Window to expose to those SubViews that the Window's Bounds 
@@ -38,21 +44,6 @@ namespace Terminal.Gui {
 		class ContentView : View {
 			public ContentView (Rect frame) : base (frame) { }
 			public ContentView () : base () { }
-#if false
-			public override void Redraw (Rect bounds)
-			{
-				Driver.SetAttribute (ColorScheme.Focus);
-
-				for (int y = 0; y < Frame.Height; y++) {
-					Move (0, y);
-					for (int x = 0; x < Frame.Width; x++) {
-
-						Driver.AddRune ('x');
-					}
-				}
-				base.Redraw (region);
-			}
-#endif
 		}
 
 		/// <summary>
@@ -263,5 +254,39 @@ namespace Terminal.Gui {
 			//Demo.ml.Text = me.ToString ();
 			return false;
 		}
+
+		/// <summary>
+		///   The text displayed by the <see cref="Label"/>.
+		/// </summary>
+		public override ustring Text {
+			get => contentView.Text;
+			set {
+				base.Text = value;
+				if (contentView != null) {
+					contentView.Text = value;
+				}
+			}
+		}
+
+		/// <summary>
+		/// Controls the text-alignment property of the label, changing it will redisplay the <see cref="Label"/>.
+		/// </summary>
+		/// <value>The text alignment.</value>
+		public override TextAlignment TextAlignment {
+			get => contentView.TextAlignment;
+			set {
+				base.TextAlignment = contentView.TextAlignment = value;
+			}
+		}
+
+		/// <summary>
+		///   The color used for the <see cref="Label"/>.
+		/// </summary>
+		public override Attribute TextColor {
+			get => contentView.TextColor;
+			set {
+				base.TextColor = contentView.TextColor = value;
+			}
+		}
 	}
 }

+ 7 - 72
Terminal.Gui/Views/Button.cs

@@ -26,11 +26,7 @@ namespace Terminal.Gui {
 	/// </remarks>
 	public class Button : View {
 		ustring text;
-		ustring shown_text;
-		Rune hot_key;
-		int hot_pos = -1;
 		bool is_default;
-		TextAlignment textAlignment = TextAlignment.Centered;
 
 		/// <summary>
 		/// Gets or sets whether the <see cref="Button"/> is the default action to activate in a dialog.
@@ -45,16 +41,6 @@ namespace Terminal.Gui {
 			}
 		}
 
-		/// <summary>
-		///   Clicked <see cref="Action"/>, raised when the button is clicked.
-		/// </summary>
-		/// <remarks>
-		///   Client code can hook up to this event, it is
-		///   raised when the button is activated either with
-		///   the mouse or the keyboard.
-		/// </remarks>
-		public Action Clicked;
-
 		/// <summary>
 		///   Initializes a new instance of <see cref="Button"/> using <see cref="LayoutStyle.Computed"/> layout.
 		/// </summary>
@@ -120,6 +106,8 @@ namespace Terminal.Gui {
 
 		void Init (ustring text, bool is_default)
 		{
+			HotKeySpecifier = new Rune ('_');
+
 			_leftBracket = new Rune (Driver != null ? Driver.LeftBracket : '[');
 			_rightBracket = new Rune (Driver != null ? Driver.RightBracket : ']');
 			_leftDefault = new Rune (Driver != null ? Driver.LeftDefaultIndicator : '<');
@@ -144,7 +132,7 @@ namespace Terminal.Gui {
 		/// <summary>
 		///   The text displayed by this <see cref="Button"/>.
 		/// </summary>
-		public ustring Text {
+		public new ustring Text {
 			get {
 				return text;
 			}
@@ -156,58 +144,19 @@ namespace Terminal.Gui {
 			}
 		}
 
-		/// <summary>
-		/// Sets or gets the text alignment for the <see cref="Button"/>.
-		/// </summary>
-		public TextAlignment TextAlignment {
-			get => textAlignment;
-			set {
-				textAlignment = value;
-				Update ();
-			}
-		}
-
 		internal void Update ()
 		{
 			if (IsDefault)
-				shown_text = ustring.Make (_leftBracket) + ustring.Make (_leftDefault) + " " + text + " " + ustring.Make (_rightDefault) + ustring.Make (_rightBracket);
+				base.Text = ustring.Make (_leftBracket) + ustring.Make (_leftDefault) + " " + text + " " + ustring.Make (_rightDefault) + ustring.Make (_rightBracket);
 			else
-				shown_text = ustring.Make (_leftBracket) + " " + text + " " + ustring.Make (_rightBracket);
-
-			shown_text = GetTextFromHotKey (shown_text, '_', out hot_pos, out hot_key);
+				base.Text = ustring.Make (_leftBracket) + " " + text + " " + ustring.Make (_rightBracket);
 
 			SetNeedsDisplay ();
 		}
 
-		int c_hot_pos;
-
-		///<inheritdoc/>
-		public override void Redraw (Rect bounds)
-		{
-			Driver.SetAttribute (HasFocus ? ColorScheme.Focus : ColorScheme.Normal);
-			Move (0, 0);
-
-			var caption = GetTextAlignment (shown_text, hot_pos, out int s_hot_pos, TextAlignment);
-			c_hot_pos = s_hot_pos;
-
-			Driver.AddStr (caption);
-
-			if (c_hot_pos != -1) {
-				Move (c_hot_pos, 0);
-				Driver.SetAttribute (HasFocus ? ColorScheme.HotFocus : ColorScheme.HotNormal);
-				Driver.AddRune (hot_key);
-			}
-		}
-
-		///<inheritdoc/>
-		public override void PositionCursor ()
-		{
-			Move (c_hot_pos == -1 ? 1 : c_hot_pos, 0);
-		}
-
 		bool CheckKey (KeyEvent key)
 		{
-			if ((char)key.KeyValue == hot_key) {
+			if ((char)key.KeyValue == HotKey) {
 				this.SuperView.SetFocus (this);
 				Clicked?.Invoke ();
 				return true;
@@ -238,26 +187,12 @@ namespace Terminal.Gui {
 		public override bool ProcessKey (KeyEvent kb)
 		{
 			var c = kb.KeyValue;
-			if (c == '\n' || c == ' ' || Rune.ToUpper ((uint)c) == hot_key) {
+			if (c == '\n' || c == ' ' || Rune.ToUpper ((uint)c) == HotKey) {
 				Clicked?.Invoke ();
 				return true;
 			}
 			return base.ProcessKey (kb);
 		}
 
-		///<inheritdoc/>
-		public override bool MouseEvent (MouseEvent me)
-		{
-			if (me.Flags == MouseFlags.Button1Clicked) {
-				if (!HasFocus) {
-					SuperView.SetFocus (this);
-					SetNeedsDisplay ();
-				}
-
-				Clicked?.Invoke ();
-				return true;
-			}
-			return false;
-		}
 	}
 }

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

@@ -88,7 +88,7 @@ namespace Terminal.Gui {
 		/// <summary>
 		///   The text displayed by this <see cref="CheckBox"/>
 		/// </summary>
-		public ustring Text {
+		public new ustring Text {
 			get {
 				return text;
 			}

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

@@ -270,7 +270,7 @@ namespace Terminal.Gui {
 		/// <summary>
 		/// The currently selected list item
 		/// </summary>
-		public ustring Text
+		public new ustring Text
 		{
 			get
 			{

+ 49 - 6
Terminal.Gui/Views/FrameView.cs

@@ -1,12 +1,14 @@
 //
-// FrameView.cs: Frame control
-//
 // Authors:
 //   Miguel de Icaza ([email protected])
 //
-using System;
-using System.Collections;
-using System.Collections.Generic;
+// NOTE: FrameView is functionally identical to Window with the following exceptions. 
+//  - Is not a Toplevel
+//  - Does not support mouse dragging
+//  - Does not support padding (but should)
+//  - Does not support IEnumerable
+// Any udpates done here should probably be done in Window as well; TODO: Merge these classes
+
 using NStack;
 
 namespace Terminal.Gui {
@@ -30,6 +32,11 @@ namespace Terminal.Gui {
 			}
 		}
 
+		/// <summary>
+		/// ContentView is an internal implementation detail of Window. It is used to host Views added with <see cref="Add(View)"/>. 
+		/// Its ONLY reason for being is to provide a simple way for Window to expose to those SubViews that the Window's Bounds 
+		/// are actually deflated due to the border. 
+		/// </summary>
 		class ContentView : View {
 			public ContentView (Rect frame) : base (frame) { }
 			public ContentView () : base () { }
@@ -40,7 +47,7 @@ namespace Terminal.Gui {
 		/// </summary>
 		/// <param name="frame">Frame.</param>
 		/// <param name="title">Title.</param>
-		public FrameView (Rect frame, ustring title) : base (frame)
+		public FrameView (Rect frame, ustring title = null) : base (frame)
 		{
 			var cFrame = new Rect (1, 1, frame.Width - 2, frame.Height - 2);
 			this.title = title;
@@ -86,6 +93,7 @@ namespace Terminal.Gui {
 		void Initialize ()
 		{
 			base.Add (contentView);
+			contentView.Text = base.Text;
 		}
 
 		void DrawFrame ()
@@ -158,5 +166,40 @@ namespace Terminal.Gui {
 			Driver.DrawWindowTitle (scrRect, Title, padding, padding, padding, padding);
 			Driver.SetAttribute (ColorScheme.Normal);
 		}
+
+		/// <summary>
+		///   The text displayed by the <see cref="Label"/>.
+		/// </summary>
+		public override ustring Text {
+			get => contentView.Text;
+			set {
+				base.Text = value;
+				if (contentView != null) {
+					contentView.Text = value;
+				}
+			}
+		}
+
+		/// <summary>
+		/// Controls the text-alignment property of the label, changing it will redisplay the <see cref="Label"/>.
+		/// </summary>
+		/// <value>The text alignment.</value>
+		public override TextAlignment TextAlignment {
+			get => contentView.TextAlignment;
+			set {
+				base.TextAlignment = contentView.TextAlignment = value;
+			}
+		}
+
+		/// <summary>
+		///   The color used for the <see cref="Label"/>.
+		/// </summary>
+		public override Attribute TextColor {
+			get => contentView.TextColor;
+			set {
+				base.TextColor = contentView.TextColor = value;
+			}
+		}
+
 	}
 }

+ 13 - 330
Terminal.Gui/Views/Label.cs

@@ -15,350 +15,33 @@ namespace Terminal.Gui {
 	/// <summary>
 	/// The Label <see cref="View"/> displays a string at a given position and supports multiple lines separted by newline characters. Multi-line Labels support word wrap.
 	/// </summary>
+	/// <remarks>
+	/// The <see cref="Label"/> view is functionality identical to <see cref="View"/> and is included for API backwards compatibilty.
+	/// </remarks>
 	public class Label : View {
-		List<ustring> lines = new List<ustring> ();
-		bool recalcPending = true;
-		ustring text;
-		TextAlignment textAlignment;
-
-		static Rect CalcRect (int x, int y, ustring s)
-		{
-			int mw = 0;
-			int ml = 1;
-
-			int cols = 0;
-			foreach (var rune in s) {
-				if (rune == '\n') {
-					ml++;
-					if (cols > mw)
-						mw = cols;
-					cols = 0;
-				} else
-					cols++;
-			}
-			if (cols > mw)
-				mw = cols;
-
-			return new Rect (x, y, mw, ml);
-		}
-
-		/// <summary>
-		///   Initializes a new instance of <see cref="Label"/> using <see cref="LayoutStyle.Absolute"/> layout.
-		/// </summary>
-		/// <remarks>
-		/// <para>
-		///   The <see cref="Label"/> will be created at the given
-		///   coordinates with the given string. The size (<see cref="View.Frame"/> will be 
-		///   adjusted to fit the contents of <see cref="Text"/>, including newlines ('\n') for multiple lines. 
-		/// </para>
-		/// <para>
-		///   No line wrapping is provided.
-		/// </para>
-		/// </remarks>
-		/// <param name="x">column to locate the Label.</param>
-		/// <param name="y">row to locate the Label.</param>
-		/// <param name="text">text to initialize the <see cref="Text"/> property with.</param>
-		public Label (int x, int y, ustring text) : this (CalcRect (x, y, text), text)
-		{
-		}
-
-		/// <summary>
-		///   Initializes a new instance of <see cref="Label"/> using <see cref="LayoutStyle.Absolute"/> layout.
-		/// </summary>
-		/// <remarks>
-		/// <para>
-		///   The <see cref="Label"/> will be created at the given
-		///   coordinates with the given string. The initial size (<see cref="View.Frame"/> will be 
-		///   adjusted to fit the contents of <see cref="Text"/>, including newlines ('\n') for multiple lines. 
-		/// </para>
-		/// <para>
-		///   If <c>rect.Height</c> is greater than one, word wrapping is provided.
-		/// </para>
-		/// </remarks>
-		/// <param name="rect">Location.</param>
-		/// <param name="text">text to initialize the <see cref="Text"/> property with.</param>
-		public Label (Rect rect, ustring text) : base (rect)
+		/// <inheritdoc/>
+		public Label ()
 		{
-			this.text = text;
 		}
 
-		/// <summary>
-		///   Initializes a new instance of <see cref="Label"/> using <see cref="LayoutStyle.Computed"/> layout.
-		/// </summary>
-		/// <remarks>
-		/// <para>
-		///   The <see cref="Label"/> will be created using <see cref="LayoutStyle.Computed"/>
-		///   coordinates with the given string. The initial size (<see cref="View.Frame"/> will be 
-		///   adjusted to fit the contents of <see cref="Text"/>, including newlines ('\n') for multiple lines. 
-		/// </para>
-		/// <para>
-		///   If <c>Height</c> is greater than one, word wrapping is provided.
-		/// </para>
-		/// </remarks>
-		/// <param name="text">text to initialize the <see cref="Text"/> property with.</param>
-		public Label (ustring text) : base ()
+		/// <inheritdoc/>
+		public Label (Rect frame) : base (frame)
 		{
-			this.text = text;
-			var r = CalcRect (0, 0, text);
-			Width = r.Width;
-			Height = r.Height;
 		}
 
-		/// <summary>
-		///   Initializes a new instance of <see cref="Label"/> using <see cref="LayoutStyle.Computed"/> layout.
-		/// </summary>
-		/// <remarks>
-		/// <para>
-		///   The <see cref="Label"/> will be created using <see cref="LayoutStyle.Computed"/>
-		///   coordinates. The initial size (<see cref="View.Frame"/> will be 
-		///   adjusted to fit the contents of <see cref="Text"/>, including newlines ('\n') for multiple lines. 
-		/// </para>
-		/// <para>
-		///   If <c>Height</c> is greater than one, word wrapping is provided.
-		/// </para>
-		/// </remarks>
-		public Label () : this (text: string.Empty) { }
-
-		static char [] whitespace = new char [] { ' ', '\t' };
-
-		static ustring ClipAndJustify (ustring str, int width, TextAlignment talign)
+		/// <inheritdoc/>
+		public Label (ustring text) : base (text)
 		{
-			int slen = str.RuneCount;
-			if (slen > width) {
-				var uints = str.ToRunes (width);
-				var runes = new Rune [uints.Length];
-				for (int i = 0; i < uints.Length; i++)
-					runes [i] = uints [i];
-				return ustring.Make (runes);
-			} else {
-				if (talign == TextAlignment.Justified) {
-					// TODO: ustring needs this
-					var words = str.ToString ().Split (whitespace, StringSplitOptions.RemoveEmptyEntries);
-					int textCount = words.Sum (arg => arg.Length);
-
-					var spaces = words.Length > 1 ? (width - textCount) / (words.Length - 1) : 0;
-					var extras = words.Length > 1 ? (width - textCount) % words.Length : 0;
-
-					var s = new System.Text.StringBuilder ();
-					//s.Append ($"tc={textCount} sp={spaces},x={extras} - ");
-					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 (' ');
-						if (extras > 0) {
-							//s.Append ('_');
-							extras--;
-						}
-					}
-					return ustring.Make (s.ToString ());
-				}
-				return str;
-			}
 		}
 
-		void Recalc ()
+		/// <inheritdoc/>
+		public Label (Rect rect, ustring text) : base (rect, text)
 		{
-			recalcPending = false;
-			Recalc (text, lines, Frame.Width, textAlignment, Bounds.Height > 1);
 		}
 
-		static ustring StripCRLF (ustring str)
+		/// <inheritdoc/>
+		public Label (int x, int y, ustring text) : base (x, y, text)
 		{
-			var runes = new List<Rune> ();
-			foreach (var r in str.ToRunes ()) {
-				if (r != '\r' && r != '\n') {
-					runes.Add (r);
-				}
-			}
-			return ustring.Make (runes); ;
-		}
-		static ustring ReplaceCRLFWithSpace (ustring str)
-		{
-			var runes = new List<Rune> ();
-			foreach (var r in str.ToRunes ()) {
-				if (r == '\r' || r == '\n') {
-					runes.Add (new Rune (' ')); // r + 0x2400));         // U+25A1 □ WHITE SQUARE
-				} else {
-					runes.Add (r);
-				}
-			}
-			return ustring.Make (runes); ;
-		}
-
-		static List<ustring> WordWrap (ustring text, int margin)
-		{
-			int start = 0, end;
-			var lines = new List<ustring> ();
-
-			text = StripCRLF (text);
-
-			while ((end = start + margin) < text.Length) {
-				while (text [end] != ' ' && end > start)
-					end -= 1;
-
-				if (end == start)
-					end = start + margin;
-
-				lines.Add (text [start, end]);
-				start = end + 1;
-			}
-
-			if (start < text.Length)
-				lines.Add (text.Substring (start));
-
-			return lines;
-		}
-
-		static void Recalc (ustring textStr, List<ustring> lineResult, int width, TextAlignment talign, bool wordWrap)
-		{
-			lineResult.Clear ();
-
-			if (wordWrap == false) {
-				textStr = ReplaceCRLFWithSpace (textStr);
-				lineResult.Add (ClipAndJustify (textStr, width, talign));
-				return;
-			}
-
-			int textLen = textStr.Length;
-			int lp = 0;
-			for (int i = 0; i < textLen; i++) {
-				Rune c = textStr [i];
-				if (c == '\n') {
-					var wrappedLines = WordWrap (textStr [lp, i], width);
-					foreach (var line in wrappedLines) {
-						lineResult.Add (ClipAndJustify (line, width, talign));
-					}
-					if (wrappedLines.Count == 0) {
-						lineResult.Add (ustring.Empty);
-					}
-					lp = i + 1;
-				}
-			}
-			foreach (var line in WordWrap (textStr [lp, textLen], width)) {
-				lineResult.Add (ClipAndJustify (line, width, talign));
-			}
-		}
-
-		///<inheritdoc/>
-		public override void LayoutSubviews ()
-		{
-			recalcPending = true;
-		}
-
-		///<inheritdoc/>
-		public override void Redraw (Rect bounds)
-		{
-			if (recalcPending)
-				Recalc ();
-
-			if (TextColor != -1)
-				Driver.SetAttribute (TextColor);
-			else
-				Driver.SetAttribute (ColorScheme.Normal);
-
-			Clear ();
-			for (int line = 0; line < lines.Count; line++) {
-				if (line < bounds.Top || line >= bounds.Bottom)
-					continue;
-				var str = lines [line];
-				int x;
-				switch (textAlignment) {
-				case TextAlignment.Left:
-					x = 0;
-					break;
-				case TextAlignment.Justified:
-					x = Bounds.Left;
-					break;
-				case TextAlignment.Right:
-					x = Bounds.Right - str.Length;
-					break;
-				case TextAlignment.Centered:
-					x = Bounds.Left + (Bounds.Width - str.Length) / 2;
-					break;
-				default:
-					throw new ArgumentOutOfRangeException ();
-				}
-				Move (x, line);
-				Driver.AddStr (str);
-			}
-		}
-
-		/// <summary>
-		/// Computes the number of lines needed to render the specified text by the <see cref="Label"/> view
-		/// </summary>
-		/// <returns>Number of lines.</returns>
-		/// <param name="text">Text, may contain newlines.</param>
-		/// <param name="width">The width for the text.</param>
-		public static int MeasureLines (ustring text, int width)
-		{
-			var result = new List<ustring> ();
-			Recalc (text, result, width, TextAlignment.Left, true);
-			return result.Count;
-		}
-
-		/// <summary>
-		/// Computes the max width of a line or multilines needed to render by the Label control
-		/// </summary>
-		/// <returns>Max width of lines.</returns>
-		/// <param name="text">Text, may contain newlines.</param>
-		/// <param name="width">The width for the text.</param>
-		public static int MaxWidth (ustring text, int width)
-		{
-			var result = new List<ustring> ();
-			Recalc (text, result, width, TextAlignment.Left, true);
-			return result.Max (s => s.RuneCount);
-		}
-
-		/// <summary>
-		/// Computes the max height of a line or multilines needed to render by the Label control
-		/// </summary>
-		/// <returns>Max height of lines.</returns>
-		/// <param name="text">Text, may contain newlines.</param>
-		/// <param name="width">The width for the text.</param>
-		public static int MaxHeight (ustring text, int width)
-		{
-			var result = new List<ustring> ();
-			Recalc (text, result, width, TextAlignment.Left, true);
-			return result.Count;
-		}
-
-		/// <summary>
-		///   The text displayed by the <see cref="Label"/>.
-		/// </summary>
-		public virtual ustring Text {
-			get => text;
-			set {
-				text = value;
-				recalcPending = true;
-				SetNeedsDisplay ();
-			}
-		}
-
-		/// <summary>
-		/// Controls the text-alignment property of the label, changing it will redisplay the <see cref="Label"/>.
-		/// </summary>
-		/// <value>The text alignment.</value>
-		public TextAlignment TextAlignment {
-			get => textAlignment;
-			set {
-				textAlignment = value;
-				SetNeedsDisplay ();
-			}
-		}
-
-		Attribute textColor = -1;
-		/// <summary>
-		///   The color used for the <see cref="Label"/>.
-		/// </summary>
-		public Attribute TextColor {
-			get => textColor;
-			set {
-				textColor = value;
-				SetNeedsDisplay ();
-			}
 		}
 	}
-
 }

+ 0 - 1
Terminal.Gui/Views/RadioGroup.cs

@@ -144,7 +144,6 @@ namespace Terminal.Gui {
 				Driver.AddStr (ustring.Make(new Rune[] { (i == selected ? Driver.Selected : Driver.UnSelected), ' '}));
 				DrawHotString (radioLabels [i], HasFocus && i == cursor, ColorScheme);
 			}
-			base.Redraw (bounds);
 		}
 
 		///<inheritdoc/>

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

@@ -63,7 +63,8 @@ namespace Terminal.Gui {
 		/// <param name="text">Initial text contents.</param>
 		public TextField (ustring text)
 		{
-			Initialize (text, Frame.Width);
+			Initialize (text, 0);
+			Width = text.Length + 1;
 		}
 
 		/// <summary>
@@ -122,7 +123,7 @@ namespace Terminal.Gui {
 		/// </summary>
 		/// <remarks>
 		/// </remarks>
-		public ustring Text {
+		public new ustring Text {
 			get {
 				return ustring.Make (text);
 			}

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

@@ -326,7 +326,7 @@ namespace Terminal.Gui {
 		/// </summary>
 		/// <remarks>
 		/// </remarks>
-		public ustring Text {
+		public override ustring Text {
 			get {
 				return model.ToString ();
 			}

+ 1 - 1
Terminal.Gui/Windows/FileDialog.cs

@@ -440,7 +440,7 @@ namespace Terminal.Gui {
 		public FileDialog (ustring title, ustring prompt, ustring nameFieldLabel, ustring message) : base (title, Driver.Cols - 20, Driver.Rows - 5, null)
 		{
 			this.message = new Label (Rect.Empty, "MESSAGE" + message);
-			var msgLines = Label.MeasureLines (message, Driver.Cols - 20);
+			var msgLines = ViewText.MaxLines (message, Driver.Cols - 20);
 
 			dirLabel = new Label ("Directory: ") {
 				X = 1,

+ 2 - 2
Terminal.Gui/Windows/MessageBox.cs

@@ -94,8 +94,8 @@ namespace Terminal.Gui {
 		static int QueryFull (bool useErrorColors, int width, int height, ustring title, ustring message, params ustring [] buttons)
 		{
 			const int defaultWidth = 50;
-			int textWidth = Label.MaxWidth (message, width);
-			int textHeight = Label.MaxHeight (message, width == 0 ? defaultWidth : width); // message.Count (ustring.Make ('\n')) + 1;
+			int textWidth = View.ViewText.MaxWidth (message, width == 0 ? defaultWidth : width);
+			int textHeight = View.ViewText.MaxLines (message, textWidth); // message.Count (ustring.Make ('\n')) + 1;
 			int msgboxHeight = Math.Max (1, textHeight) + 3; // textHeight + (top + top padding + buttons + bottom)
 
 			// Create button array for Dialog

+ 2 - 1
UICatalog/Scenarios/CharacterMap.cs

@@ -24,7 +24,8 @@ namespace UICatalog {
 				Width = CharMap.RowWidth + 2,
 				Height = Dim.Fill (),
 				Start = 0x2500,
-				ColorScheme = Colors.Dialog
+				ColorScheme = Colors.Dialog,
+				CanFocus = true,
 			};
 
 			Win.Add (charMap);

+ 271 - 0
UICatalog/Scenarios/LabelsAsButtons.cs

@@ -0,0 +1,271 @@
+using NStack;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Reflection;
+using Terminal.Gui;
+
+namespace UICatalog {
+	[ScenarioMetadata (Name: "LabelsAsButtons", Description: "POC to see how making Label more a base class would work")]
+	[ScenarioCategory ("Controls")]
+	[ScenarioCategory ("POC")]
+	class LabelsAsButtons : Scenario {
+		public override void Setup ()
+		{
+			// Add a label & text field so we can demo IsDefault
+			var editLabel = new Label ("TextField (to demo IsDefault):") {
+				X = 0,
+				Y = 0,
+			};
+			Win.Add (editLabel);
+			// Add a TextField using Absolute layout. 
+			var edit = new TextField (31, 0, 15, "");
+			Win.Add (edit);
+
+			// This is the default button (IsDefault = true); if user presses ENTER in the TextField
+			// the scenario will quit
+			var defaultButton = new Label ("_Quit") {
+				X = Pos.Center (),
+				//TODO: Change to use Pos.AnchorEnd()
+				Y = Pos.Bottom (Win) - 3,
+				//IsDefault = true,
+				Clicked = () => Application.RequestStop (),
+			};
+			Win.Add (defaultButton);
+
+			var swapButton = new Label (50, 0, "Swap Default (Absolute Layout)");
+			swapButton.Clicked = () => {
+				//defaultButton.IsDefault = !defaultButton.IsDefault;
+				//swapButton.IsDefault = !swapButton.IsDefault;
+			};
+			Win.Add (swapButton);
+
+			static void DoMessage (Label button, ustring txt)
+			{
+				button.Clicked = () => {
+					var btnText = button.Text.ToString ();
+					MessageBox.Query ("Message", $"Did you click {txt}?", "Yes", "No");
+				};
+			}
+
+			var colorButtonsLabel = new Label ("Color Buttons:") {
+				X = 0,
+				Y = Pos.Bottom (editLabel) + 1,
+			};
+			Win.Add (colorButtonsLabel);
+
+			//View prev = colorButtonsLabel;
+
+			//With this method there is no need to call Top.Ready += () => Top.Redraw (Top.Bounds);
+			var x = Pos.Right (colorButtonsLabel) + 2;
+			foreach (var colorScheme in Colors.ColorSchemes) {
+				var colorButton = new Label ($"{colorScheme.Key}") {
+					ColorScheme = colorScheme.Value,
+					//X = Pos.Right (prev) + 2,
+					X = x,
+					Y = Pos.Y (colorButtonsLabel),
+				};
+				DoMessage (colorButton, colorButton.Text);
+				Win.Add (colorButton);
+				//prev = colorButton;
+				x += colorButton.Frame.Width + 2;
+			}
+			// BUGBUG: For some reason these buttons don't move to correct locations initially. 
+			// This was the only way I find to resolves this with the View prev variable.
+			//Top.Ready += () => Top.Redraw (Top.Bounds);
+
+			Label button;
+			Win.Add (button = new Label ("A super long _Button that will probably expose a bug in clipping or wrapping of text. Will it?") {
+				X = 2,
+				Y = Pos.Bottom (colorButtonsLabel) + 1,
+			});
+			DoMessage (button, button.Text);
+
+			// Note the 'N' in 'Newline' will be the hotkey
+			Win.Add (button = new Label ("a Newline\nin the button") {
+				X = 2,
+				Y = Pos.Bottom (button) + 1,
+				Clicked = () => MessageBox.Query ("Message", "Question?", "Yes", "No")
+			});
+
+			var textChanger = new Label ("Te_xt Changer") {
+				X = 2,
+				Y = Pos.Bottom (button) + 1,
+			};
+			Win.Add (textChanger);
+			textChanger.Clicked = () => textChanger.Text += "!";
+
+			Win.Add (button = new Label ("Lets see if this will move as \"Text Changer\" grows") {
+				X = Pos.Right (textChanger) + 2,
+				Y = Pos.Y (textChanger),
+			});
+
+			var removeButton = new Label ("Remove this button") {
+				X = 2,
+				Y = Pos.Bottom (button) + 1,
+				ColorScheme = Colors.Error
+			};
+			Win.Add (removeButton);
+			// This in intresting test case because `moveBtn` and below are laid out relative to this one!
+			removeButton.Clicked = () => Win.Remove (removeButton);
+
+			var computedFrame = new FrameView ("Computed Layout") {
+				X = 0,
+				Y = Pos.Bottom (removeButton) + 1,
+				Width = Dim.Percent (50),
+				Height = 5
+			};
+			Win.Add (computedFrame);
+
+			// Demonstrates how changing the View.Frame property can move Views
+			var moveBtn = new Label ("Move This \u263b Button _via Pos") {
+				X = 0,
+				Y = Pos.Center () - 1,
+				Width = 30,
+				ColorScheme = Colors.Error,
+			};
+			moveBtn.Clicked = () => {
+				moveBtn.X = moveBtn.Frame.X + 5;
+				// This is already fixed with the call to SetNeedDisplay() in the Pos Dim.
+				//computedFrame.LayoutSubviews (); // BUGBUG: This call should not be needed. View.X is not causing relayout correctly
+			};
+			computedFrame.Add (moveBtn);
+
+			// Demonstrates how changing the View.Frame property can SIZE Views (#583)
+			var sizeBtn = new Label ("Size This \u263a Button _via Pos") {
+				X = 0,
+				Y = Pos.Center () + 1,
+				Width = 30,
+				ColorScheme = Colors.Error,
+			};
+			sizeBtn.Clicked = () => {
+				sizeBtn.Width = sizeBtn.Frame.Width + 5;
+				//computedFrame.LayoutSubviews (); // FIXED: This call should not be needed. View.X is not causing relayout correctly
+			};
+			computedFrame.Add (sizeBtn);
+
+			var absoluteFrame = new FrameView ("Absolute Layout") {
+				X = Pos.Right (computedFrame),
+				Y = Pos.Bottom (removeButton) + 1,
+				Width = Dim.Fill (),
+				Height = 5
+			};
+			Win.Add (absoluteFrame);
+
+			// Demonstrates how changing the View.Frame property can move Views
+			var moveBtnA = new Label (0, 0, "Move This Button via Frame") {
+				ColorScheme = Colors.Error,
+			};
+			moveBtnA.Clicked = () => {
+				moveBtnA.Frame = new Rect (moveBtnA.Frame.X + 5, moveBtnA.Frame.Y, moveBtnA.Frame.Width, moveBtnA.Frame.Height);
+			};
+			absoluteFrame.Add (moveBtnA);
+
+			// Demonstrates how changing the View.Frame property can SIZE Views (#583)
+			var sizeBtnA = new Label (0, 2, " ~  s  gui.cs   master ↑10 = Со_хранить") {
+				ColorScheme = Colors.Error,
+			};
+			sizeBtnA.Clicked = () => {
+				sizeBtnA.Frame = new Rect (sizeBtnA.Frame.X, sizeBtnA.Frame.Y, sizeBtnA.Frame.Width + 5, sizeBtnA.Frame.Height);
+			};
+			absoluteFrame.Add (sizeBtnA);
+
+			var label = new Label ("Text Alignment (changes the four buttons above): ") {
+				X = 2,
+				Y = Pos.Bottom (computedFrame) + 1,
+			};
+			Win.Add (label);
+
+			var radioGroup = new RadioGroup (new ustring [] { "Left", "Right", "Centered", "Justified" }) {
+				X = 4,
+				Y = Pos.Bottom (label) + 1,
+				SelectedItem = 2,
+			};
+			Win.Add (radioGroup);
+
+			// Demo changing hotkey
+			ustring MoveHotkey (ustring txt)
+			{
+				// Remove the '_'
+				var i = txt.IndexOf ('_');
+				ustring start = "";
+				if (i > -1)
+					start = txt [0, i];
+				txt = start + txt [i + 1, txt.Length];
+
+				// Move over one or go to start
+				i++;
+				if (i >= txt.Length) {
+					i = 0;
+				}
+
+				// Slip in the '_'
+				start = txt [0, i];
+				txt = start + ustring.Make ('_') + txt [i, txt.Length];
+
+				return txt;
+			}
+
+			var mhkb = "Click to Change th_is Button's Hotkey";
+			var moveHotKeyBtn = new Label (mhkb) {
+				X = 2,
+				Y = Pos.Bottom (radioGroup) + 1,
+				Width = mhkb.Length + 10,
+				ColorScheme = Colors.TopLevel,
+			};
+			moveHotKeyBtn.Clicked = () => {
+				moveHotKeyBtn.Text = MoveHotkey (moveHotKeyBtn.Text);
+			};
+			Win.Add (moveHotKeyBtn);
+
+			var muhkb = " ~  s  gui.cs   master ↑10 = Сохранить";
+			var moveUnicodeHotKeyBtn = new Label (muhkb) {
+				X = Pos.Left (absoluteFrame) + 1,
+				Y = Pos.Bottom (radioGroup) + 1,
+				Width = muhkb.Length + 30,
+				ColorScheme = Colors.TopLevel,
+			};
+			moveUnicodeHotKeyBtn.Clicked = () => {
+				moveUnicodeHotKeyBtn.Text = MoveHotkey (moveUnicodeHotKeyBtn.Text);
+			};
+			Win.Add (moveUnicodeHotKeyBtn);
+
+			radioGroup.SelectedItemChanged += (args) => {
+				switch (args.SelectedItem) {
+				case 0:
+					moveBtn.TextAlignment = TextAlignment.Left;
+					sizeBtn.TextAlignment = TextAlignment.Left;
+					moveBtnA.TextAlignment = TextAlignment.Left;
+					sizeBtnA.TextAlignment = TextAlignment.Left;
+					moveHotKeyBtn.TextAlignment = TextAlignment.Left;
+					moveUnicodeHotKeyBtn.TextAlignment = TextAlignment.Left;
+					break;
+				case 1:
+					moveBtn.TextAlignment = TextAlignment.Right;
+					sizeBtn.TextAlignment = TextAlignment.Right;
+					moveBtnA.TextAlignment = TextAlignment.Right;
+					sizeBtnA.TextAlignment = TextAlignment.Right;
+					moveHotKeyBtn.TextAlignment = TextAlignment.Right;
+					moveUnicodeHotKeyBtn.TextAlignment = TextAlignment.Right;
+					break;
+				case 2:
+					moveBtn.TextAlignment = TextAlignment.Centered;
+					sizeBtn.TextAlignment = TextAlignment.Centered;
+					moveBtnA.TextAlignment = TextAlignment.Centered;
+					sizeBtnA.TextAlignment = TextAlignment.Centered;
+					moveHotKeyBtn.TextAlignment = TextAlignment.Centered;
+					moveUnicodeHotKeyBtn.TextAlignment = TextAlignment.Centered;
+					break;
+				case 3:
+					moveBtn.TextAlignment = TextAlignment.Justified;
+					sizeBtn.TextAlignment = TextAlignment.Justified;
+					moveBtnA.TextAlignment = TextAlignment.Justified;
+					sizeBtnA.TextAlignment = TextAlignment.Justified;
+					moveHotKeyBtn.TextAlignment = TextAlignment.Justified;
+					moveUnicodeHotKeyBtn.TextAlignment = TextAlignment.Justified;
+					break;
+				}
+			};
+		}
+	}
+}

+ 1 - 1
UICatalog/Scenarios/MessageBoxes.cs

@@ -142,7 +142,7 @@ namespace UICatalog {
 				ColorScheme = Colors.Error,
 			};
 
-			var btnText = new [] { "Zero", "One", "Two", "Three", "Four", "Five", "Six", "Seven", "Eight", "Nine" };
+			var btnText = new [] { "_Zero", "_One", "T_wo", "_Three", "_Four", "Fi_ve", "Si_x", "_Seven", "_Eight", "_Nine" };
 
 			var showMessageBoxButton = new Button ("Show MessageBox") {
 				X = Pos.Center(),

+ 50 - 0
UICatalog/Scenarios/ViewWithText.cs

@@ -0,0 +1,50 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using Terminal.Gui;
+
+namespace UICatalog {
+	[ScenarioMetadata (Name: "View Text", Description: "Demos and tests View's Text capabilities.")]
+	[ScenarioCategory ("Text")]
+	[ScenarioCategory ("POC")]
+	class ViewWithText : Scenario {
+		public override void Setup ()
+		{
+			Win.Text = "This is the Te_xt for the host Win object. TextAlignment.Centered was specified. It is intentionally very long to illustrate word wrap.\n" +
+				"<-- There is a new line here to show a hard line break. You should see this text bleed underneath the subviews, which start at Y = 3.";
+			Win.TextAlignment = TextAlignment.Centered;
+			Win.TextColor = Application.Driver.MakeAttribute (Color.BrightGreen, Color.Black);
+#if true
+			string txt = "Hello world, how are you today? Pretty neat!";
+#else
+			string txt = "Hello world, how are you today? Unicode:  ~  gui.cs  . Neat?";
+#endif
+			var alignments = Enum.GetValues (typeof (Terminal.Gui.TextAlignment)).Cast<Terminal.Gui.TextAlignment> ().ToList ();
+			var label = new View ($"Demonstrating single-line (should clip!):") { Y = 3 };
+			Win.Add (label);
+			foreach (var alignment in alignments) {
+				label = new Label ($"{alignment}:") { Y = Pos.Bottom (label) };
+				Win.Add (label);
+				label = new Label (txt) {
+					TextAlignment = alignment,
+					Y = Pos.Bottom (label),
+					Width = Dim.Fill (),
+					Height = 1,
+					ColorScheme = Colors.Dialog,
+					TextColor = Application.Driver.MakeAttribute (Color.BrightRed, Color.White),
+				};
+				Win.Add (label);
+			}
+
+			txt += "\nSecond line\n\nFourth Line.";
+			label = new View ($"Demonstrating multi-line and word wrap:") { Y = Pos.Bottom (label) + 1 };
+			Win.Add (label);
+			foreach (var alignment in alignments) {
+				label = new View ($"{alignment}:") { Y = Pos.Bottom (label) };
+				Win.Add (label);
+				label = new View (txt) { TextAlignment = alignment, Width = Dim.Fill (), Height = 6, ColorScheme = Colors.Dialog, Y = Pos.Bottom (label) };
+				Win.Add (label);
+			}
+		}
+	}
+}

+ 17 - 9
UICatalog/Scenarios/WindowsAndFrameViews.cs

@@ -64,11 +64,11 @@ namespace UICatalog {
 				X = Pos.Center (),
 				Y = 0,
 				ColorScheme = Colors.Error,
-				Clicked = () => About()
+				Clicked = () => About ()
 			});
 			Win.Add (new Button ("Press ME! (Y = Pos.AnchorEnd(1))") {
 				X = Pos.Center (),
-				Y = Pos.AnchorEnd(1),
+				Y = Pos.AnchorEnd (1),
 				ColorScheme = Colors.Error
 			});
 			Top.Add (Win);
@@ -80,7 +80,7 @@ namespace UICatalog {
 					X = margin,
 					Y = Pos.Bottom (listWin.Last ()) + (margin),
 					Width = Dim.Fill (margin),
-					Height = contentHeight + (i*2) + 2,
+					Height = contentHeight + (i * 2) + 2,
 				};
 				win.ColorScheme = Colors.Dialog;
 				win.Add (new Button ("Press me! (Y = 0)") {
@@ -95,8 +95,10 @@ namespace UICatalog {
 					Width = Dim.Percent (50),
 					Height = 5,
 					ColorScheme = Colors.Base,
+					Text = "The Text in the Window",
 				};
-				subWin.Add (new TextField (win.Title.ToString ()) {
+				subWin.Add (new TextField ("Edit me! " + win.Title.ToString ()) {
+					Y = 1,
 					ColorScheme = Colors.Error
 				});
 				win.Add (subWin);
@@ -106,8 +108,12 @@ namespace UICatalog {
 					Width = Dim.Percent (100),
 					Height = 5,
 					ColorScheme = Colors.Base,
+					Text = "The Text in the FrameView",
+
 				};
-				frameView.Add (new TextField ("Edit Me"));
+				frameView.Add (new TextField ("Edit Me!") {
+					Y = 1,
+				});
 				win.Add (frameView);
 
 				Top.Add (win);
@@ -135,6 +141,7 @@ namespace UICatalog {
 				Width = Dim.Percent (50),
 				Height = Dim.Fill () - 1,
 				ColorScheme = Colors.Base,
+				Text = "The Text in the Window",
 			};
 			subWinofFV.Add (new TextField ("Edit Me") {
 				ColorScheme = Colors.Error
@@ -150,8 +157,9 @@ namespace UICatalog {
 				Width = Dim.Percent (100),
 				Height = Dim.Fill () - 1,
 				ColorScheme = Colors.Base,
+				Text = "The Text in the FrameView",
 			};
-			subFrameViewofFV.Add (new TextField ("Edit Me"));
+			subFrameViewofFV.Add (new TextField (0, 0, 15, "Edit Me"));
 
 			subFrameViewofFV.Add (new CheckBox (0, 1, "Check me"));
 			// BUGBUG: This checkbox is not shown even though frameViewFV has 3 rows in 
@@ -160,12 +168,12 @@ namespace UICatalog {
 
 			frame.Add (new CheckBox ("Btn1 (Y = Pos.AnchorEnd (1))") {
 				X = 0,
-				Y = Pos.AnchorEnd (1), 
+				Y = Pos.AnchorEnd (1),
 			});
 			CheckBox c = new CheckBox ("Btn2 (Y = Pos.AnchorEnd (1))") {
-				Y = Pos.AnchorEnd (1), 
+				Y = Pos.AnchorEnd (1),
 			};
-			c.X = Pos.AnchorEnd () - (Pos.Right (c) - Pos.Left (c)); 
+			c.X = Pos.AnchorEnd () - (Pos.Right (c) - Pos.Left (c));
 			frame.Add (c);
 
 			frame.Add (subFrameViewofFV);

+ 1 - 1
UnitTests/ViewTests.cs

@@ -25,7 +25,7 @@ namespace Terminal.Gui {
 			Assert.Equal (new Rect (0, 0, 0, 0), r.Frame);
 			Assert.Null (r.Focused);
 			Assert.Null (r.ColorScheme);
-			Assert.Equal (Dim.Sized (0), r.Height);
+			Assert.Equal (Dim.Sized (0), r.Width);
 			Assert.Equal (Dim.Sized (0), r.Height);
 			// BUGBUG: Pos needs eqality implemented
 			//Assert.Equal (Pos.At (0), r.X);