// // Label.cs: Label control // // Authors: // Miguel de Icaza (miguel@gnome.org) // using System; using System.Collections.Generic; using System.Linq; using NStack; namespace Terminal.Gui { /// /// Text alignment enumeration, controls how text is displayed. /// public enum TextAlignment { /// /// Aligns the text to the left of the frame. /// Left, /// /// Aligns the text to the right side of the frame. /// Right, /// /// Centers the text in the frame. /// Centered, /// /// Shows the text as justified text in the frame. /// Justified } /// /// The Label displays a string at a given position and supports multiple lines separted by newline characters. /// public class Label : View { List lines = new List (); 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); } /// /// Initializes a new instance of at the given /// coordinate with the given string, computes the bounding box /// based on the size of the string, assumes that the string contains /// newlines for multiple lines, no special breaking rules are used. /// public Label (int x, int y, ustring text) : this (CalcRect (x, y, text), text) { } /// /// Initializes a new instance of at the given /// coordinate with the given string and uses the specified /// frame for the string. /// public Label (Rect rect, ustring text) : base (rect) { this.text = text; } /// /// Initializes a new instance of and configures the default Width and Height based on the text, the result is suitable for Computed layout. /// /// Text. public Label (ustring text) : base () { this.text = text; var r = CalcRect (0, 0, text); Width = r.Width; Height = r.Height; } static char [] whitespace = new char [] { ' ', '\t' }; 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 = (width- textCount) / (words.Length - 1); var extras = (width - textCount) % words.Length; 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 () { recalcPending = false; Recalc (text, lines, Frame.Width, textAlignment); } static void Recalc (ustring textStr, List lineResult, int width, TextAlignment talign) { lineResult.Clear (); if (textStr.IndexOf ('\n') == -1) { 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)); lp = i + 1; } } lineResult.Add(ClipAndJustify(textStr[lp, textLen], width, talign)); } /// public override void Redraw (Rect region) { 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 < region.Top || line > region.Bottom) continue; var str = lines [line]; int x; switch (textAlignment) { case TextAlignment.Left: x = 0; break; case TextAlignment.Justified: Recalc (); 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); } } /// /// Computes the number of lines needed to render the specified text by the view /// /// Number of lines. /// Text, may contain newlines. /// The width for the text. public static int MeasureLines (ustring text, int width) { var result = new List (); Recalc (text, result, width, TextAlignment.Left); return result.Count; } /// /// Computes the the max width of a line or multilines needed to render by the Label control /// /// Max width of lines. /// Text, may contain newlines. /// The width for the text. public static int MaxWidth(ustring text, int width) { var result = new List(); Recalc(text, result, width, TextAlignment.Left); return result.Max(s => s.RuneCount); } /// /// The text displayed by the . /// public virtual ustring Text { get => text; set { text = value; recalcPending = true; SetNeedsDisplay (); } } /// /// Controls the text-alignemtn property of the label, changing it will redisplay the . /// /// The text alignment. public TextAlignment TextAlignment { get => textAlignment; set { textAlignment = value; SetNeedsDisplay (); } } Attribute textColor = -1; /// /// The color used for the . /// public Attribute TextColor { get => textColor; set { textColor = value; SetNeedsDisplay (); } } } }