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

Merge pull request #664 from tig/word_wrap_label

Label & MessageBox: Add Word Wrap
Charlie Kindel 5 жил өмнө
parent
commit
4cd0ee9c20

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

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

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