浏览代码

Merge branch 'master' into testsformainloop

Charlie Kindel 5 年之前
父节点
当前提交
3e208beb24

+ 21 - 21
Example/demo.cs

@@ -421,26 +421,26 @@ static class Demo {
 		MessageBox.Query (60, 10, "Selected Animals", result == "" ? "No animals selected" : result, "Ok");
 	}
 
-	//static void ComboBoxDemo ()
-	//{
-	//	IList<string> items = new List<string> ();
-	//	foreach (var dir in new [] { "/etc", @"\windows\System32" }) {
-	//		if (Directory.Exists (dir)) {
-	//			items = Directory.GetFiles (dir)
-	//			.Select (Path.GetFileName)
-	//			.Where (x => char.IsLetterOrDigit (x [0]))
-	//			.Distinct ()
-	//			.OrderBy (x => x).ToList ();
-	//		}
-	//	}
-	//	var list = new ComboBox (0, 0, 36, 7, items);
-	//	list.Changed += (object sender, ustring text) => { Application.RequestStop (); };
-
-	//	var d = new Dialog ("Select source file", 40, 12) { list };
-	//	Application.Run (d);
-
-	//	MessageBox.Query (60, 10, "Selected file", list.Text.ToString() == "" ? "Nothing selected" : list.Text.ToString(), "Ok");
-	//}
+	static void ComboBoxDemo ()
+	{
+		IList<string> items = new List<string> ();
+		foreach (var dir in new [] { "/etc", @"\windows\System32" }) {
+			if (Directory.Exists (dir)) {
+				items = Directory.GetFiles (dir).Union (Directory.GetDirectories (dir))
+					.Select (Path.GetFileName)
+					.Where (x => char.IsLetterOrDigit (x [0]))
+					.OrderBy (x => x).ToList ();
+			}
+		}
+		var list = new ComboBox () { X = 0, Y = 0, Width = Dim.Fill(), Height = Dim.Fill() };
+		list.SetSource(items.ToList());
+		list.SelectedItemChanged += (object sender, ustring text) => { Application.RequestStop (); };
+
+		var d = new Dialog ("Select source file", 40, 12) { list };
+		Application.Run (d);
+
+		MessageBox.Query (60, 10, "Selected file", list.Text.ToString() == "" ? "Nothing selected" : list.Text.ToString(), "Ok");
+	}
 	#endregion
 
 
@@ -571,7 +571,7 @@ static class Demo {
 			new MenuBarItem ("_List Demos", new MenuItem [] {
 				new MenuItem ("Select _Multiple Items", "", () => ListSelectionDemo (true)),
 				new MenuItem ("Select _Single Item", "", () => ListSelectionDemo (false)),
-//				new MenuItem ("Search Single Item", "", ComboBoxDemo)
+				new MenuItem ("Search Single Item", "", ComboBoxDemo)
 			}),
 			new MenuBarItem ("A_ssorted", new MenuItem [] {
 				new MenuItem ("_Show text alignments", "", () => ShowTextAlignments ()),

+ 5 - 0
Terminal.Gui/Core/PosDim.cs

@@ -178,6 +178,11 @@ namespace Terminal.Gui {
 			{
 				return n;
 			}
+
+			public override int GetHashCode () => n.GetHashCode ();
+
+			public override bool Equals (object other) => other is PosAbsolute abs && abs.n == n;
+
 		}
 
 		/// <summary>

+ 0 - 5
Terminal.Gui/Core/View.cs

@@ -984,15 +984,10 @@ namespace Terminal.Gui {
 								view.LayoutSubviews ();
 							Application.CurrentView = view;
 
-							// Clip the sub-view
-							var savedClip = view.ClipToBounds ();
-
 							// Draw the subview
 							// Use the view's bounds (view-relative; Location will always be (0,0) because
 							view.Redraw (view.Bounds);
 
-							// Undo the clip
-							Driver.Clip = savedClip;
 						}
 						view.NeedDisplay = Rect.Empty;
 						view.childNeedsDisplay = false;

+ 0 - 6
Terminal.Gui/Terminal.Gui.csproj

@@ -183,12 +183,6 @@
     <PackageReference Include="Microsoft.NETFramework.ReferenceAssemblies" Version="1.0.0" PrivateAssets="true" />
     <PackageReference Include="NStack.Core" Version="0.14.0" />
   </ItemGroup>
-  <ItemGroup Condition="'$(Configuration)'!='Debug'">
-    <PackageReference Include="SauceControl.InheritDoc" Version="1.0.0" PrivateAssets="all" />
-  </ItemGroup>
-  <ItemGroup>
-    <Compile Remove="Views\ComboBox.cs" />
-  </ItemGroup>
   <!--<ItemGroup>
     <Reference Include="NStack">
       <HintPath>..\..\..\Users\miguel\.nuget\packages\nstack.core\0.14.0\lib\netstandard2.0\NStack.dll</HintPath>

+ 8 - 0
Terminal.Gui/Views/Button.cs

@@ -166,6 +166,14 @@ namespace Terminal.Gui {
 			else
 				shown_text = ustring.Make (_leftBracket) + " " + text + " " + ustring.Make (_rightBracket);
 
+			shown_text = shown_text
+				.Replace ("\f", "\u21a1")		// U+21A1 ↡ DOWNWARDS TWO HEADED ARROW
+				.Replace ("\n", "\u240a")		// U+240A (SYMBOL FOR LINE FEED, ␊)
+				.Replace ("\r", "\u240d")		// U+240D (SYMBOL FOR CARRIAGE RETURN, ␍)
+				.Replace ("\t", "\u2409")		// U+2409 ␉ SYMBOL FOR HORIZONTAL TABULATION
+				.Replace ("\v", "\u240b")		// U+240B ␋ SYMBOL FOR VERTICAL TABULATION
+				.TrimSpace ();
+
 			shown_text = GetTextFromHotKey (shown_text, '_', out hot_pos, out hot_key);
 
 			SetNeedsDisplay ();

+ 146 - 86
Terminal.Gui/Views/ComboBox.cs

@@ -4,10 +4,15 @@
 // Authors:
 //   Ross Ferguson ([email protected])
 //
+// TODO:
+//  LayoutComplete() resize Height implement
+//	Cursor rolls of end of list when Height = Dim.Fill() and list fills frame
+//
 
 using System;
-using System.Linq;
+using System.Collections;
 using System.Collections.Generic;
+using System.Linq;
 using NStack;
 
 namespace Terminal.Gui {
@@ -15,6 +20,39 @@ namespace Terminal.Gui {
 	/// ComboBox control
 	/// </summary>
 	public class ComboBox : View {
+
+		IListDataSource source;
+		/// <summary>
+		/// Gets or sets the <see cref="IListDataSource"/> backing this <see cref="ComboBox"/>, enabling custom rendering.
+		/// </summary>
+		/// <value>The source.</value>
+		/// <remarks>
+		///  Use <see cref="SetSource"/> to set a new <see cref="IList"/> source.
+		/// </remarks>
+		public IListDataSource Source {
+			get => source;
+			set {
+				source = value;
+				SetNeedsDisplay ();
+			}
+		}
+
+		/// <summary>
+		/// Sets the source of the <see cref="ComboBox"/> to an <see cref="IList"/>.
+		/// </summary>
+		/// <value>An object implementing the IList interface.</value>
+		/// <remarks>
+		///  Use the <see cref="Source"/> property to set a new <see cref="IListDataSource"/> source and use custome rendering.
+		/// </remarks>
+		public void SetSource (IList source)
+		{
+			if (source == null)
+				Source = null;
+			else {
+				Source = MakeWrapper (source);
+			}
+		}
+
 		/// <summary>
 		///   Changed event, raised when the selection has been confirmed.
 		/// </summary>
@@ -22,15 +60,12 @@ namespace Terminal.Gui {
 		///   Client code can hook up to this event, it is
 		///   raised when the selection has been confirmed.
 		/// </remarks>
-		public Action<ustring> SelectedItemChanged;
+		public event EventHandler<ustring> SelectedItemChanged;
 
-		IList<string> listsource;
-		IList<string> searchset;
+		IList searchset;
 		ustring text = "";
-		TextField search;
-		ListView listview;
-		int x;
-		int y;
+		readonly TextField search;
+		readonly ListView listview;
 		int height;
 		int width;
 		bool autoHide = true;
@@ -40,8 +75,8 @@ namespace Terminal.Gui {
 		/// </summary>
 		public ComboBox () : base()
 		{
-			search = new TextField ("") { LayoutStyle = LayoutStyle.Computed };
-			listview = new ListView () { LayoutStyle = LayoutStyle.Computed, CanFocus = true /* why? */ };
+			search = new TextField ("");
+			listview = new ListView () { LayoutStyle = LayoutStyle.Computed, CanFocus = true };
 
 			Initialize ();
 		}
@@ -49,39 +84,45 @@ namespace Terminal.Gui {
 		/// <summary>
 		/// Public constructor
 		/// </summary>
-		/// <param name="x">The x coordinate</param>
-		/// <param name="y">The y coordinate</param>
-		/// <param name="w">The width</param>
-		/// <param name="h">The height</param>
-		/// <param name="source">Auto completion source</param>
-		public ComboBox (int x, int y, int w, int h, IList<string> source)
+		/// <param name="rect"></param>
+		/// <param name="source"></param>
+		public ComboBox (Rect rect, IList source) : base (rect)
 		{
 			SetSource (source);
-			this.x = x;
-			this.y = y;
-			height = h;
-			width = w;
-
-			search = new TextField (x, y, w, "");
+			this.height = rect.Height;
+			this.width = rect.Width;
 
-			listview = new ListView (new Rect (x, y + 1, w, 0), listsource.ToList ()) {
-				LayoutStyle = LayoutStyle.Computed,
-			};
+			search = new TextField ("") { Width = width };
+			listview = new ListView (rect, source) { LayoutStyle = LayoutStyle.Computed };
 
 			Initialize ();
 		}
 
+		static IListDataSource MakeWrapper (IList source)
+		{
+			return new ListWrapper (source);
+		}
+
 		private void Initialize()
 		{
-			search.Changed += Search_Changed;
+			ColorScheme = Colors.Base;
+
+			search.TextChanged += Search_Changed;
+
+			// On resize
+			LayoutComplete += (LayoutEventArgs a) => {
+
+				search.Width = Bounds.Width;
+				listview.Width = autoHide ? Bounds.Width - 1 : Bounds.Width;
+			};
+
+			listview.SelectedItemChanged += (ListViewItemEventArgs e) => {
 
-			listview.SelectedChanged += (object sender, ListViewItemEventArgs e) => {
 				if(searchset.Count > 0)
-					SetValue (searchset [listview.SelectedItem]);
+					SetValue ((string)searchset [listview.SelectedItem]);
 			};
 
-			// TODO: LayoutComplete event breaks cursor up/down. Revert to Application.Loaded 
-			Application.Loaded += (sender, a) => {
+			Application.Loaded += (Application.ResizedEventArgs a) => {
 				// Determine if this view is hosted inside a dialog
 				for (View view = this.SuperView; view != null; view = view.SuperView) {
 					if (view is Dialog) {
@@ -90,34 +131,37 @@ namespace Terminal.Gui {
 					}
 				}
 
-				searchset = autoHide ? new List<string> () : listsource;
+				ResetSearchSet ();
+
+				ColorScheme = autoHide ? Colors.Base : ColorScheme = null;
 
 				// Needs to be re-applied for LayoutStyle.Computed
 				// If Dim or Pos are null, these are the from the parametrized constructor
-				if (X == null) 
-					listview.X = x;
+				listview.Y = 1;
 
-				if (Y == null)
-					listview.Y = y + 1;
-				else
-					listview.Y = Pos.Bottom (search);
-
-				if (Width == null)
+				if (Width == null) {
 					listview.Width = CalculateWidth ();
-				else {
-					width = GetDimAsInt (Width);
+					search.Width = width;
+				} else {
+					width = GetDimAsInt (Width, vertical: false);
+					search.Width = width;
 					listview.Width = CalculateWidth ();
 				}
 
-				if (Height == null)
-					listview.Height = CalculatetHeight ();
-				else {
-					height = GetDimAsInt (Height);
+				if (Height == null) {
+					var h = CalculatetHeight ();
+					listview.Height = h;
+					this.Height = h + 1; // adjust view to account for search box
+				} else {
+					if (height == 0)
+						height = GetDimAsInt (Height, vertical: true);
+
 					listview.Height = CalculatetHeight ();
+					this.Height = height + 1; // adjust view to account for search box
 				}
 
 				if (this.Text != null)
-					Search_Changed (search, Text);
+					Search_Changed (Text);
 
 				if (autoHide)
 					listview.ColorScheme = Colors.Menu;
@@ -131,20 +175,12 @@ namespace Terminal.Gui {
 			this.SetFocus(search);
 		}
 
-		/// <summary>
-		/// Set search list source
-		/// </summary>
-		public void SetSource(IList<string> source)
-		{
-			listsource = new List<string> (source);
-		}
-
-		private void Search_MouseClick (object sender, MouseEventArgs e)
+		private void Search_MouseClick (MouseEventArgs e)
 		{
 			if (e.MouseEvent.Flags != MouseFlags.Button1Clicked)
 				return;
 
-			SuperView.SetFocus ((View)sender);
+			SuperView.SetFocus (search);
 		}
 
 		///<inheritdoc/>
@@ -158,11 +194,23 @@ namespace Terminal.Gui {
 			return true;
 		}
 
+		/// <summary>
+		/// Invokes the SelectedChanged event if it is defined.
+		/// </summary>
+		/// <returns></returns>
+		public virtual bool OnSelectedChanged ()
+		{
+			// Note: Cannot rely on "listview.SelectedItem != lastSelectedItem" because the list is dynamic. 
+			// So we cannot optimize. Ie: Don't call if not changed
+			SelectedItemChanged?.Invoke (this, search.Text);
+
+			return true;
+		}
+
 		///<inheritdoc/>
 		public override bool ProcessKey(KeyEvent e)
 		{
-			if (e.Key == Key.Tab)
-			{
+			if (e.Key == Key.Tab) {
 				base.ProcessKey(e);
 				return false; // allow tab-out to next control
 			}
@@ -173,10 +221,10 @@ namespace Terminal.Gui {
 					return true;
 				}
 
-				SetValue( searchset [listview.SelectedItem]);
+				SetValue((string)searchset [listview.SelectedItem]);
 				search.CursorPosition = search.Text.Length;
-				Search_Changed (search, search.Text);
-				Changed?.Invoke (this, text);
+				Search_Changed (search.Text);
+				OnSelectedChanged ();
 
 				searchset.Clear();
 				listview.Clear ();
@@ -188,7 +236,7 @@ namespace Terminal.Gui {
 
 			if (e.Key == Key.CursorDown && search.HasFocus && listview.SelectedItem == 0 && searchset.Count > 0) { // jump to list
 				this.SetFocus (listview);
-				SetValue (searchset [listview.SelectedItem]);
+				SetValue ((string)searchset [listview.SelectedItem]);
 				return true;
 			}
 
@@ -205,7 +253,7 @@ namespace Terminal.Gui {
 			if (e.Key == Key.Esc) {
 				this.SetFocus (search);
 				search.Text = text = "";
-				Changed?.Invoke (this, search.Text);
+				OnSelectedChanged ();
 				return true;
 			}
 
@@ -235,10 +283,10 @@ namespace Terminal.Gui {
 
 		private void SetValue(ustring text)
 		{
-			search.Changed -= Search_Changed;
+			search.TextChanged -= Search_Changed;
 			this.text = search.Text = text;
 			search.CursorPosition = 0;
-			search.Changed += Search_Changed;
+			search.TextChanged += Search_Changed;
 		}
 
 		/// <summary>
@@ -247,26 +295,38 @@ namespace Terminal.Gui {
 		private void Reset()
 		{
 			search.Text = text = "";
-			Changed?.Invoke (this, search.Text);
-			searchset = autoHide ? new List<string> () : listsource;
+			OnSelectedChanged();
 
-			listview.SetSource(searchset.ToList());
+			ResetSearchSet ();
+
+			listview.SetSource(searchset);
 			listview.Height = CalculatetHeight ();
 
 			this.SetFocus(search);
 		}
 
-		private void Search_Changed (object sender, ustring text)
+		private void ResetSearchSet()
 		{
-			if (listsource == null) // Object initialization
+			if (autoHide) {
+				if (searchset == null)
+					searchset = new List<string> ();
+				else
+					searchset.Clear ();
+			} else
+				searchset = source.ToList ();
+		}
+
+		private void Search_Changed (ustring text)
+		{
+			if (source == null) // Object initialization
 				return;
 
-			if (string.IsNullOrEmpty (search.Text.ToString()))
-				searchset = autoHide ? new List<string> () : listsource;
+			if (string.IsNullOrEmpty (search.Text.ToString ()))
+				ResetSearchSet ();
 			else
-				searchset = listsource.Where (x => x.StartsWith (search.Text.ToString (), StringComparison.CurrentCultureIgnoreCase)).ToList ();
+				searchset = source.ToList().Cast<string>().Where (x => x.StartsWith (search.Text.ToString (), StringComparison.CurrentCultureIgnoreCase)).ToList();
 
-			listview.SetSource (searchset.ToList ());
+			listview.SetSource (searchset);
 			listview.Height = CalculatetHeight ();
 
 			listview.Redraw (new Rect (0, 0, width, height)); // for any view behind this
@@ -292,21 +352,21 @@ namespace Terminal.Gui {
 		}
 
 		/// <summary>
-		/// Get DimAbsolute as integer value
+		/// Get Dim as integer value
 		/// </summary>
 		/// <param name="dim"></param>
-		/// <returns></returns>
-		private int GetDimAsInt(Dim dim)
+		/// <param name="vertical"></param>
+		/// <returns></returns>n
+		private int GetDimAsInt (Dim dim, bool vertical)
 		{
-			if (!(dim is Dim.DimAbsolute))
-				throw new ArgumentException ("Dim must be an absolute value");
-
-			// Anchor in the case of DimAbsolute returns absolute value. No documentation on what Anchor() does so not sure if this will change in the future.
-			//
-			// TODO: Does Dim need:- 
-			//		public static implicit operator int (Dim d)
-			//
-			return dim.Anchor (0);
+			if (dim is Dim.DimAbsolute)
+				return dim.Anchor (0);
+			else { // Dim.Fill Dim.Factor
+				if(autoHide)
+					return vertical ? dim.Anchor (SuperView.Bounds.Height) : dim.Anchor (SuperView.Bounds.Width);
+				else 
+					return vertical ? dim.Anchor (Bounds.Height) : dim.Anchor (Bounds.Width);
+			}
 		}
 	}
 }

+ 88 - 22
Terminal.Gui/Views/Label.cs

@@ -8,11 +8,12 @@
 using System;
 using System.Collections.Generic;
 using System.Linq;
+using System.Text.RegularExpressions;
 using NStack;
 
 namespace Terminal.Gui {
 	/// <summary>
-	/// The Label <see cref="View"/> displays a string at a given position and supports multiple lines separted by newline characters.
+	/// 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>
 	public class Label : View {
 		List<ustring> lines = new List<ustring> ();
@@ -51,7 +52,7 @@ namespace Terminal.Gui {
 		///   adjusted to fit the contents of <see cref="Text"/>, including newlines ('\n') for multiple lines. 
 		/// </para>
 		/// <para>
-		///   No line wraping is provided.
+		///   No line wrapping is provided.
 		/// </para>
 		/// </remarks>
 		/// <param name="x">column to locate the Label.</param>
@@ -71,7 +72,7 @@ namespace Terminal.Gui {
 		///   adjusted to fit the contents of <see cref="Text"/>, including newlines ('\n') for multiple lines. 
 		/// </para>
 		/// <para>
-		///   No line wraping is provided.
+		///   If <c>rect.Height</c> is greater than one, word wrapping is provided.
 		/// </para>
 		/// </remarks>
 		/// <param name="rect">Location.</param>
@@ -91,7 +92,7 @@ namespace Terminal.Gui {
 		///   adjusted to fit the contents of <see cref="Text"/>, including newlines ('\n') for multiple lines. 
 		/// </para>
 		/// <para>
-		///   No line wraping is provided.
+		///   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>
@@ -113,7 +114,7 @@ namespace Terminal.Gui {
 		///   adjusted to fit the contents of <see cref="Text"/>, including newlines ('\n') for multiple lines. 
 		/// </para>
 		/// <para>
-		///   No line wraping is provided.
+		///   If <c>Height</c> is greater than one, word wrapping is provided.
 		/// </para>
 		/// </remarks>
 		public Label () : this (text: string.Empty) { }
@@ -125,7 +126,7 @@ namespace Terminal.Gui {
 			// Get rid of any '\r' added by Windows
 			str = str.Replace ("\r", ustring.Empty);
 			int slen = str.RuneCount;
-			if (slen > width){
+			if (slen > width) {
 				var uints = str.ToRunes (width);
 				var runes = new Rune [uints.Length];
 				for (int i = 0; i < uints.Length; i++)
@@ -134,11 +135,11 @@ namespace Terminal.Gui {
 			} else {
 				if (talign == TextAlignment.Justified) {
 					// TODO: ustring needs this
-			               	var words = str.ToString ().Split (whitespace, StringSplitOptions.RemoveEmptyEntries);
+					var words = str.ToString ().Split (whitespace, StringSplitOptions.RemoveEmptyEntries);
 					int textCount = words.Sum (arg => arg.Length);
 
-					var spaces = (width- textCount) / (words.Length - 1);
-					var extras = (width - textCount) % words.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} - ");
@@ -162,27 +163,80 @@ namespace Terminal.Gui {
 		void Recalc ()
 		{
 			recalcPending = false;
-			Recalc (text, lines, Frame.Width, textAlignment);
+			Recalc (text, lines, Frame.Width, textAlignment, Bounds.Height > 1);
 		}
 
-		static void Recalc (ustring textStr, List<ustring> lineResult, int width, TextAlignment talign)
+		static ustring ReplaceNonPrintables (ustring str)
+		{
+			var runes = new List<Rune> ();
+			foreach (var r in str.ToRunes ()) {
+				if (r < 0x20) {
+					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 = ReplaceNonPrintables (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 (textStr.IndexOf ('\n') == -1) {
+
+			if (wordWrap == false) {
+				textStr = ReplaceNonPrintables (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') {
-					lineResult.Add (ClipAndJustify (textStr [lp, i], width, talign));
+					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;
 				}
 			}
-			lineResult.Add(ClipAndJustify(textStr[lp, textLen], width, talign));
+			foreach (var line in WordWrap (textStr [lp, textLen], width)) {
+				lineResult.Add (ClipAndJustify (line, width, talign));
+			}
+		}
+
+		///<inheritdoc/>
+		public override void LayoutSubviews ()
+		{
+			recalcPending = true;
 		}
 
 		///<inheritdoc/>
@@ -198,7 +252,7 @@ namespace Terminal.Gui {
 
 			Clear ();
 			for (int line = 0; line < lines.Count; line++) {
-				if (line < bounds.Top || line > bounds.Bottom)
+				if (line < bounds.Top || line >= bounds.Bottom)
 					continue;
 				var str = lines [line];
 				int x;
@@ -207,7 +261,6 @@ namespace Terminal.Gui {
 					x = 0;
 					break;
 				case TextAlignment.Justified:
-					Recalc ();
 					x = Bounds.Left;
 					break;
 				case TextAlignment.Right:
@@ -233,7 +286,7 @@ namespace Terminal.Gui {
 		public static int MeasureLines (ustring text, int width)
 		{
 			var result = new List<ustring> ();
-			Recalc (text, result, width, TextAlignment.Left);
+			Recalc (text, result, width, TextAlignment.Left, true);
 			return result.Count;
 		}
 
@@ -243,11 +296,24 @@ namespace Terminal.Gui {
 		/// <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)
+		public static int MaxWidth (ustring text, int width)
 		{
-			var result = new List<ustring>();
-			Recalc(text, result, width, TextAlignment.Left);
-			return result.Max(s => s.RuneCount);
+			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>

+ 38 - 15
Terminal.Gui/Views/ScrollView.cs

@@ -159,24 +159,36 @@ namespace Terminal.Gui {
 					Driver.AddRune (Driver.UpArrow);
 					Move (col, Bounds.Height - 1);
 					Driver.AddRune (Driver.DownArrow);
+
+					bool hasTopTee = false;
+					bool hasDiamond = false;
+					bool hasBottomTee = false;
 					for (int y = 0; y < bh; y++) {
 						Move (col, y + 1);
-						if (y < by1 - 1 || y > by2)
+						if ((y < by1 || y > by2) && ((position > 0 && !hasTopTee) || (hasTopTee && hasBottomTee))) {
 							special = Driver.Stipple;
-						else {
-							if (by2 - by1 == 0 && by1 < bh - 1)
+						} else {
+							if (y != by2 && y > 1 && by2 - by1 == 0 && by1 < bh - 1 && hasTopTee && !hasDiamond) {
+								hasDiamond = true;
 								special = Driver.Diamond;
-							else {
-								if (y == by1 - 1)
+							} else {
+								if (y == by1 && !hasTopTee) {
+									hasTopTee = true;
 									special = Driver.TopTee;
-								else if (y == by2)
+								} else if ((y >= by2 || by2 == 0) && !hasBottomTee) {
+									hasBottomTee = true;
 									special = Driver.BottomTee;
-								else
+								} else {
 									special = Driver.VLine;
+								}
 							}
 						}
 						Driver.AddRune (special);
 					}
+					if (!hasTopTee) {
+						Move (col, Bounds.Height - 2);
+						Driver.AddRune (Driver.TopTee);
+					}
 				}
 			} else {
 				if (region.Bottom < Bounds.Height - 1)
@@ -206,24 +218,35 @@ namespace Terminal.Gui {
 					Move (0, row);
 					Driver.AddRune (Driver.LeftArrow);
 
+					bool hasLeftTee = false;
+					bool hasDiamond = false;
+					bool hasRightTee = false;
 					for (int x = 0; x < bw; x++) {
-
-						if (x < bx1 || x > bx2) {
+						if ((x < bx1 || x >= bx2 + 1) && ((position > 0 && !hasLeftTee) || (hasLeftTee && hasRightTee))) {
 							special = Driver.Stipple;
 						} else {
-							if (bx2 - bx1 == 0)
+							if (x != bx2 && x > 1 && bx2 - bx1 == 0 && bx1 < bw - 1 && hasLeftTee && !hasDiamond) {
+								hasDiamond = true;
 								special = Driver.Diamond;
-							else {
-								if (x == bx1)
+							} else {
+								if (x == bx1 && !hasLeftTee) {
+									hasLeftTee = true;
 									special = Driver.LeftTee;
-								else if (x == bx2)
+								} else if ((x >= bx2 || bx2 == 0) && !hasRightTee) {
+									hasRightTee = true;
 									special = Driver.RightTee;
-								else
+								} else {
 									special = Driver.HLine;
+								}
 							}
 						}
 						Driver.AddRune (special);
 					}
+					if (!hasLeftTee) {
+						Move (Bounds.Width -2, row);
+						Driver.AddRune (Driver.LeftTee);
+					}
+
 					Driver.AddRune (Driver.RightArrow);
 				}
 			}
@@ -250,7 +273,7 @@ namespace Terminal.Gui {
 					if (pos > 0)
 						SetPosition (pos - 1);
 				} else if (location == barsize + 1) {
-					if (pos + 1 + barsize < Size)
+					if (pos + 1 <= Size + 2)
 						SetPosition (pos + 1);
 				} else {
 					var b1 = pos * barsize / Size;

+ 6 - 2
Terminal.Gui/Views/StatusBar.cs

@@ -181,7 +181,11 @@ namespace Terminal.Gui {
 					}
 					Driver.AddRune (title [n]);
 				}
-				Driver.AddRune (' ');
+				if (i + 1 < Items.Length) {
+					Driver.AddRune (' ');
+					Driver.AddRune (Driver.VLine);
+					Driver.AddRune (' ');
+				}
 			}
 		}
 
@@ -208,7 +212,7 @@ namespace Terminal.Gui {
 				if (me.X >= pos && me.X < pos + GetItemTitleLength (Items [i].Title)) {
 					Run (Items [i].Action);
 				}
-				pos += GetItemTitleLength (Items [i].Title) + 1;
+				pos += GetItemTitleLength (Items [i].Title) + 3;
 			}
 			return true;
 		}

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

@@ -32,7 +32,7 @@ namespace Terminal.Gui {
 		/// <param name="width">Width for the window.</param>
 		/// <param name="height">Height for the window.</param>
 		/// <param name="title">Title for the query.</param>
-		/// <param name="message">Message to display, might contain multiple lines..</param>
+		/// <param name="message">Message to display, might contain multiple lines.</param>
 		/// <param name="buttons">Array of buttons to add.</param>
 		/// <remarks>
 		/// Use <see cref="Query(ustring, ustring, ustring[])"/> instead; it automatically sizes the MessageBox based on the contents.
@@ -93,10 +93,10 @@ namespace Terminal.Gui {
 
 		static int QueryFull (bool useErrorColors, int width, int height, ustring title, ustring message, params ustring [] buttons)
 		{
-			const int defaultWidth = 30;
+			const int defaultWidth = 50;
 			int textWidth = Label.MaxWidth (message, width);
-			int textHeight = message.Count (ustring.Make ('\n')) + 1;
-			int msgboxHeight = Math.Max (1, textHeight) + 4; // textHeight + (top + top padding + buttons + bottom)
+			int textHeight = Label.MaxHeight (message, width == 0 ? defaultWidth : width); // message.Count (ustring.Make ('\n')) + 1;
+			int msgboxHeight = Math.Max (1, textHeight) + 3; // textHeight + (top + top padding + buttons + bottom)
 
 			// Create button array for Dialog
 			int count = 0;
@@ -130,7 +130,7 @@ namespace Terminal.Gui {
 				l.X = Pos.Center ();
 				l.Y = Pos.Center ();
 				l.Width = Dim.Fill (2);
-				l.Height = Dim.Fill (2);
+				l.Height = Dim.Fill (1);
 				d.Add (l);
 			}
 

+ 9 - 8
UICatalog/Scenarios/ListsAndCombos.cs

@@ -32,27 +32,28 @@ namespace UICatalog.Scenarios {
 			var listview = new ListView (items) {
 				X = 0,
 				Y = Pos.Bottom (lbListView) + 1,
+				Height = Dim.Fill(2),
 				Width = 30
 			};
-			listview.OpenSelectedItem += (object sender, ListViewItemEventArgs e) => lbListView.Text = items [listview.SelectedItem];
+			listview.OpenSelectedItem += (ListViewItemEventArgs e) => lbListView.Text = items [listview.SelectedItem];
 			Win.Add (lbListView, listview);
 
 			// ComboBox
 			var lbComboBox = new Label ("ComboBox") {
 				ColorScheme = Colors.TopLevel,
 				X = Pos.Right (lbListView) + 1,
-				Width = 30
+				Width = Dim.Percent(60)
 			};
 
-			var comboBox = new ComboBox() {
-				X = Pos.Right(listview) + 1 , 
-				Y = Pos.Bottom (lbListView) +1,
-				Height = 10,
-				Width = 30
+			var comboBox = new ComboBox () {
+				X = Pos.Right (listview) + 1,
+				Y = Pos.Bottom (lbListView) + 1,
+				Height = Dim.Fill (2),
+				Width = Dim.Percent(60)
 			};
 			comboBox.SetSource (items);
 
-			comboBox.Changed += (object sender, ustring text) => lbComboBox.Text = text;
+			comboBox.SelectedItemChanged += (object sender, ustring text) => lbComboBox.Text = text;
 			Win.Add (lbComboBox, comboBox);
 		}
 	}

+ 1 - 1
UICatalog/Scenarios/Scrolling.cs

@@ -214,7 +214,7 @@ namespace UICatalog {
 
 			int count = 0;
 			var mousePos = new Label ("Mouse: ");
-			mousePos.X = Pos.Center ();
+			mousePos.X = Pos.Right(scrollView) + 1;
 			mousePos.Y = Pos.AnchorEnd (1);
 			mousePos.Width = 50;
 			Application.RootMouseEvent += delegate (MouseEvent me) {

+ 19 - 14
UICatalog/Scenarios/TextAlignments.cs

@@ -9,24 +9,29 @@ namespace UICatalog {
 	class TextAlignments : Scenario {
 		public override void Setup ()
 		{
-			int i = 1;
-			string txt = "Hello world, how are you doing today?";
-
-			var alignments = Enum.GetValues (typeof (Terminal.Gui.TextAlignment)).Cast<Terminal.Gui.TextAlignment> ().ToList();
-
+#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 Label ($"Demonstrating single-line (should clip!):") { Y = 0 };
+			Win.Add (label);
 			foreach (var alignment in alignments) {
-				Win.Add (new Label ($"{alignment}:") { Y = ++i });
-				Win.Add (new Label (txt) { TextAlignment = alignment, Y = i++, Width = Dim.Fill(), ColorScheme = Colors.Dialog });
+				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 };
+				Win.Add (label);
 			}
 
-			// Demonstrate that wrapping labels are not yet implemented (#352)
-			txt += "\nSecond line";
-			Win.Add (new Label ($"Demonstrating multi-line (note wrap is not yet implemented):") { Y = ++i });
-
+			txt += "\nSecond line\n\nFourth Line.";
+			label = new Label ($"Demonstrating multi-line and word wrap:") { Y = Pos.Bottom (label) + 1 };
+			Win.Add (label);
 			foreach (var alignment in alignments) {
-				Win.Add (new Label ($"{alignment}:") { Y = ++i });
-				Win.Add (new Label (txt) { TextAlignment = alignment, Y = ++i, Width = Dim.Fill (), Height = 2, ColorScheme = Colors.Dialog });
-				i += 2;
+				label = new Label ($"{alignment}:") { Y = Pos.Bottom (label) };
+				Win.Add (label);
+				label = new Label (txt) { TextAlignment = alignment, Width = Dim.Fill (), Height = 6, ColorScheme = Colors.Dialog, Y = Pos.Bottom (label) };
+				Win.Add (label);
 			}
 		}
 	}

+ 12 - 10
UICatalog/Scenarios/Unicode.cs

@@ -35,16 +35,18 @@ namespace UICatalog {
 			var checkBox = new CheckBox (" ~  s  gui.cs   master ↑10") { X = 15, Y = Pos.Y (label), Width = Dim.Percent (50) };
 			Win.Add (checkBox);
 
-			//label = new Label ("ComboBox:") { X = Pos.X (label), Y = Pos.Bottom (label) + 1 };
-			//Win.Add (label);
-			//var comboBox = new ComboBox (1, 1, 30, 5, new List<string> () { "item #1", " ~  s  gui.cs   master ↑10", "Со_хранить" }) {
-			//	X = 15,
-			//	Y = Pos.Y (label),
-			//	Width = 30,
-			//	ColorScheme = Colors.Error
-			//};
-			//Win.Add (comboBox);
-			//comboBox.Text = " ~  s  gui.cs   master ↑10";
+			label = new Label ("ComboBox:") { X = Pos.X (label), Y = Pos.Bottom (label) + 1 };
+			Win.Add (label);
+			var comboBox = new ComboBox () {
+				X = 15,
+				Y = Pos.Y (label),
+				Width = Dim.Percent (50),
+				ColorScheme = Colors.Error
+			};
+			comboBox.SetSource (new List<string> () { "item #1", " ~  s  gui.cs   master ↑10", "Со_хранить" });
+
+			Win.Add (comboBox);
+			comboBox.Text = " ~  s  gui.cs   master ↑10";
 
 			label = new Label ("HexView:") { X = Pos.X (label), Y = Pos.Bottom (label) + 2 };
 			Win.Add (label);

+ 12 - 12
UICatalog/UICatalog.cs

@@ -262,11 +262,14 @@ namespace UICatalog {
 			_categoryListView.SelectedItem = 0;
 			_categoryListView.OnSelectedChanged ();
 
-			_capslock = new StatusItem (Key.CharMask, "Capslock", null);
-			_numlock = new StatusItem (Key.CharMask, "Numlock", null);
-			_scrolllock = new StatusItem (Key.CharMask, "Scrolllock", null);
+			_capslock = new StatusItem (Key.CharMask, "Caps", null);
+			_numlock = new StatusItem (Key.CharMask, "Num", null);
+			_scrolllock = new StatusItem (Key.CharMask, "Scroll", null);
 
 			_statusBar = new StatusBar (new StatusItem [] {
+				_capslock,
+				_numlock,
+				_scrolllock,
 				new StatusItem(Key.ControlQ, "~CTRL-Q~ Quit", () => {
 					if (_runningScenario is null){
 						// This causes GetScenarioToRun to return null
@@ -276,9 +279,6 @@ namespace UICatalog {
 						_runningScenario.RequestStop();
 					}
 				}),
-				_capslock,
-				_numlock,
-				_scrolllock
 			});
 
 			SetColorScheme ();
@@ -357,26 +357,26 @@ namespace UICatalog {
 			//}
 
 			if (a.KeyEvent.IsCapslock) {
-				_capslock.Title = "Capslock: On";
+				_capslock.Title = "Caps: On";
 				_statusBar.SetNeedsDisplay ();
 			} else {
-				_capslock.Title = "Capslock: Off";
+				_capslock.Title = "Caps: Off";
 				_statusBar.SetNeedsDisplay ();
 			}
 
 			if (a.KeyEvent.IsNumlock) {
-				_numlock.Title = "Numlock: On";
+				_numlock.Title = "Num: On";
 				_statusBar.SetNeedsDisplay ();
 			} else {
-				_numlock.Title = "Numlock: Off";
+				_numlock.Title = "Num: Off";
 				_statusBar.SetNeedsDisplay ();
 			}
 
 			if (a.KeyEvent.IsScrolllock) {
-				_scrolllock.Title = "Scrolllock: On";
+				_scrolllock.Title = "Scroll: On";
 				_statusBar.SetNeedsDisplay ();
 			} else {
-				_scrolllock.Title = "Scrolllock: Off";
+				_scrolllock.Title = "Scroll: Off";
 				_statusBar.SetNeedsDisplay ();
 			}
 		}

+ 0 - 4
UICatalog/UICatalog.csproj

@@ -8,10 +8,6 @@
     <LangVersion>8.0</LangVersion>
   </PropertyGroup>
 
-  <ItemGroup>
-    <Compile Remove="Scenarios\ListsAndCombos.cs" />
-  </ItemGroup>
-
   <ItemGroup>
     <ProjectReference Include="..\Terminal.Gui\Terminal.Gui.csproj" />
   </ItemGroup>

+ 1 - 2
UnitTests/PosTests.cs

@@ -66,8 +66,7 @@ namespace Terminal.Gui {
 
 			var pos1 = Pos.At (n1);
 			var pos2 = Pos.At (n2);
-			// BUGBUG: Pos should implement equality and this should change to Equal
-			Assert.NotEqual (pos1, pos2);
+			Assert.Equal (pos1, pos2);
 		}
 
 		[Fact]